2020-01-28 15:03:23 +09:00
/ *
Copyright 2020 The actions - runner - controller authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package controllers
import (
"context"
2021-02-09 10:17:52 +09:00
"errors"
2020-01-28 21:58:01 +09:00
"fmt"
2021-02-09 10:17:52 +09:00
gogithub "github.com/google/go-github/v33/github"
2020-12-08 17:56:06 +09:00
"github.com/summerwind/actions-runner-controller/hash"
2020-04-24 11:29:52 +02:00
"strings"
2021-02-09 10:17:52 +09:00
"time"
2020-01-28 15:03:23 +09:00
"github.com/go-logr/logr"
2021-02-09 10:17:52 +09:00
kerrors "k8s.io/apimachinery/pkg/api/errors"
2020-01-28 15:03:23 +09:00
"k8s.io/apimachinery/pkg/runtime"
2020-02-03 18:40:59 +09:00
"k8s.io/client-go/tools/record"
2020-01-28 15:03:23 +09:00
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
2020-01-28 21:58:01 +09:00
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
2020-04-13 22:28:07 +09:00
"github.com/summerwind/actions-runner-controller/github"
2020-01-28 21:58:01 +09:00
)
const (
2020-02-01 00:06:30 +09:00
containerName = "runner"
2020-02-03 21:35:01 +09:00
finalizerName = "runner.actions.summerwind.dev"
2020-12-08 17:56:06 +09:00
LabelKeyPodTemplateHash = "pod-template-hash"
2021-02-09 10:17:52 +09:00
retryDelayOnGitHubAPIRateLimitError = 30 * time . Second
2020-01-28 15:03:23 +09:00
)
// RunnerReconciler reconciles a Runner object
type RunnerReconciler struct {
client . Client
2020-01-28 21:58:01 +09:00
Log logr . Logger
2020-02-03 18:40:59 +09:00
Recorder record . EventRecorder
2020-01-28 21:58:01 +09:00
Scheme * runtime . Scheme
GitHubClient * github . Client
2020-02-03 16:56:52 +09:00
RunnerImage string
DockerImage string
2020-01-28 15:03:23 +09:00
}
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners,verbs=get;list;watch;create;update;patch;delete
2020-10-06 09:23:03 +09:00
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners/finalizers,verbs=get;list;watch;create;update;patch;delete
2020-01-28 15:03:23 +09:00
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners/status,verbs=get;update;patch
2020-02-02 19:49:10 +09:00
// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete
2020-10-06 09:23:03 +09:00
// +kubebuilder:rbac:groups=core,resources=pods/finalizers,verbs=get;list;watch;create;update;patch;delete
2020-03-27 23:25:37 +09:00
// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
2020-01-28 15:03:23 +09:00
func ( r * RunnerReconciler ) Reconcile ( req ctrl . Request ) ( ctrl . Result , error ) {
2020-01-28 21:58:01 +09:00
ctx := context . Background ( )
log := r . Log . WithValues ( "runner" , req . NamespacedName )
var runner v1alpha1 . Runner
if err := r . Get ( ctx , req . NamespacedName , & runner ) ; err != nil {
return ctrl . Result { } , client . IgnoreNotFound ( err )
}
2020-04-30 22:11:59 +09:00
err := runner . Validate ( )
2020-04-28 07:24:37 +02:00
if err != nil {
log . Info ( "Failed to validate runner spec" , "error" , err . Error ( ) )
return ctrl . Result { } , nil
}
2020-02-03 21:35:01 +09:00
if runner . ObjectMeta . DeletionTimestamp . IsZero ( ) {
finalizers , added := addFinalizer ( runner . ObjectMeta . Finalizers )
if added {
newRunner := runner . DeepCopy ( )
newRunner . ObjectMeta . Finalizers = finalizers
if err := r . Update ( ctx , newRunner ) ; err != nil {
log . Error ( err , "Failed to update runner" )
return ctrl . Result { } , err
}
return ctrl . Result { } , nil
}
} else {
finalizers , removed := removeFinalizer ( runner . ObjectMeta . Finalizers )
2020-04-30 18:41:17 +09:00
2020-02-03 21:35:01 +09:00
if removed {
2020-04-28 16:31:05 +02:00
if len ( runner . Status . Registration . Token ) > 0 {
2021-02-05 02:31:06 +02:00
ok , err := r . unregisterRunner ( ctx , runner . Spec . Enterprise , runner . Spec . Organization , runner . Spec . Repository , runner . Name )
2020-04-28 16:31:05 +02:00
if err != nil {
2021-02-09 10:17:52 +09:00
if errors . Is ( err , & gogithub . RateLimitError { } ) {
// We log the underlying error when we failed calling GitHub API to list or unregisters,
// or the runner is still busy.
log . Error (
err ,
fmt . Sprintf (
"Failed to unregister runner due to GitHub API rate limits. Delaying retry for %s to avoid excessive GitHub API calls" ,
retryDelayOnGitHubAPIRateLimitError ,
) ,
)
return ctrl . Result { RequeueAfter : retryDelayOnGitHubAPIRateLimitError } , err
}
2020-04-28 16:31:05 +02:00
return ctrl . Result { } , err
}
if ! ok {
log . V ( 1 ) . Info ( "Runner no longer exists on GitHub" )
}
} else {
log . V ( 1 ) . Info ( "Runner was never registered on GitHub" )
2020-02-03 21:35:01 +09:00
}
newRunner := runner . DeepCopy ( )
newRunner . ObjectMeta . Finalizers = finalizers
if err := r . Update ( ctx , newRunner ) ; err != nil {
log . Error ( err , "Failed to update runner" )
return ctrl . Result { } , err
}
2020-04-28 16:31:05 +02:00
log . Info ( "Removed runner from GitHub" , "repository" , runner . Spec . Repository , "organization" , runner . Spec . Organization )
2020-02-03 21:35:01 +09:00
}
return ctrl . Result { } , nil
}
2020-01-29 23:12:07 +09:00
var pod corev1 . Pod
if err := r . Get ( ctx , req . NamespacedName , & pod ) ; err != nil {
2021-02-09 10:17:52 +09:00
if ! kerrors . IsNotFound ( err ) {
2020-01-29 23:12:07 +09:00
return ctrl . Result { } , err
2020-01-28 21:58:01 +09:00
}
2020-11-11 09:38:05 +09:00
if updated , err := r . updateRegistrationToken ( ctx , runner ) ; err != nil {
return ctrl . Result { } , err
} else if updated {
return ctrl . Result { Requeue : true } , nil
}
newPod , err := r . newPod ( runner )
2020-01-28 21:58:01 +09:00
if err != nil {
2020-02-03 18:40:59 +09:00
log . Error ( err , "Could not create pod" )
2020-01-28 21:58:01 +09:00
return ctrl . Result { } , err
}
2020-01-29 23:12:07 +09:00
if err := r . Create ( ctx , & newPod ) ; err != nil {
2020-02-03 18:40:59 +09:00
log . Error ( err , "Failed to create pod resource" )
2020-01-29 23:12:07 +09:00
return ctrl . Result { } , err
}
2020-01-28 21:58:01 +09:00
2020-02-03 18:40:59 +09:00
r . Recorder . Event ( & runner , corev1 . EventTypeNormal , "PodCreated" , fmt . Sprintf ( "Created pod '%s'" , newPod . Name ) )
log . Info ( "Created runner pod" , "repository" , runner . Spec . Repository )
2020-01-29 23:12:07 +09:00
} else {
2020-10-21 15:32:26 +03:00
// If pod has ended up succeeded we need to restart it
// Happens e.g. when dind is in runner and run completes
restart := pod . Status . Phase == corev1 . PodSucceeded
if ! restart && runner . Status . Phase != string ( pod . Status . Phase ) {
2020-02-03 17:25:38 +09:00
updated := runner . DeepCopy ( )
updated . Status . Phase = string ( pod . Status . Phase )
updated . Status . Reason = pod . Status . Reason
updated . Status . Message = pod . Status . Message
if err := r . Status ( ) . Update ( ctx , updated ) ; err != nil {
2020-02-03 18:40:59 +09:00
log . Error ( err , "Failed to update runner status" )
2020-02-03 17:25:38 +09:00
return ctrl . Result { } , err
}
return ctrl . Result { } , nil
}
2020-01-31 22:47:53 +09:00
if ! pod . ObjectMeta . DeletionTimestamp . IsZero ( ) {
2021-02-15 01:32:28 +01:00
deletionTimeout := 1 * time . Minute
currentTime := time . Now ( )
deletionDidTimeout := currentTime . Sub ( pod . DeletionTimestamp . Add ( deletionTimeout ) ) > 0
if deletionDidTimeout {
log . Info (
"Pod failed to delete itself in a timely manner. " +
"This is typically the case when a Kubernetes node became unreachable " +
"and the kube controller started evicting nodes. Forcefully deleting the pod to not get stuck." ,
"podDeletionTimestamp" , pod . DeletionTimestamp ,
"currentTime" , currentTime ,
"configuredDeletionTimeout" , deletionTimeout ,
)
var force int64 = 0
// forcefully delete runner as we would otherwise get stuck if the node stays unreachable
if err := r . Delete ( ctx , & pod , & client . DeleteOptions { GracePeriodSeconds : & force } ) ; err != nil {
// probably
if ! kerrors . IsNotFound ( err ) {
log . Error ( err , "Failed to forcefully delete pod resource ..." )
return ctrl . Result { } , err
}
// forceful deletion finally succeeded
return ctrl . Result { Requeue : true } , nil
}
r . Recorder . Event ( & runner , corev1 . EventTypeNormal , "PodDeleted" , fmt . Sprintf ( "Forcefully deleted pod '%s'" , pod . Name ) )
log . Info ( "Forcefully deleted runner pod" , "repository" , runner . Spec . Repository )
// give kube manager a little time to forcefully delete the stuck pod
return ctrl . Result { RequeueAfter : 3 * time . Second } , err
} else {
return ctrl . Result { } , err
}
2020-01-31 22:47:53 +09:00
}
2020-02-01 00:06:30 +09:00
if pod . Status . Phase == corev1 . PodRunning {
for _ , status := range pod . Status . ContainerStatuses {
if status . Name != containerName {
continue
}
if status . State . Terminated != nil && status . State . Terminated . ExitCode == 0 {
restart = true
}
}
}
2020-11-11 09:38:05 +09:00
if updated , err := r . updateRegistrationToken ( ctx , runner ) ; err != nil {
return ctrl . Result { } , err
} else if updated {
return ctrl . Result { Requeue : true } , nil
}
newPod , err := r . newPod ( runner )
2020-01-29 23:12:07 +09:00
if err != nil {
2020-02-03 18:40:59 +09:00
log . Error ( err , "Could not create pod" )
2020-01-29 23:12:07 +09:00
return ctrl . Result { } , err
2020-01-28 21:58:01 +09:00
}
2020-01-28 15:03:23 +09:00
2021-02-22 02:08:04 +01:00
// all checks done below only decide whether a restart is needed
// if a restart was already decided before, there is no need for the checks
// saving API calls and scary log messages
if ! restart {
notRegistered := false
offline := false
runnerBusy , err := r . GitHubClient . IsRunnerBusy ( ctx , runner . Spec . Enterprise , runner . Spec . Organization , runner . Spec . Repository , runner . Name )
if err != nil {
var notFoundException * github . RunnerNotFound
var offlineException * github . RunnerOffline
if errors . As ( err , & notFoundException ) {
log . V ( 1 ) . Info ( "Failed to check if runner is busy. Either this runner has never been successfully registered to GitHub or it still needs more time." , "runnerName" , runner . Name )
notRegistered = true
} else if errors . As ( err , & offlineException ) {
log . V ( 1 ) . Info ( "GitHub runner appears to be offline, waiting for runner to get online ..." , "runnerName" , runner . Name )
offline = true
} else {
var e * gogithub . RateLimitError
if errors . As ( err , & e ) {
// We log the underlying error when we failed calling GitHub API to list or unregisters,
// or the runner is still busy.
log . Error (
err ,
fmt . Sprintf (
"Failed to check if runner is busy due to Github API rate limit. Retrying in %s to avoid excessive GitHub API calls" ,
retryDelayOnGitHubAPIRateLimitError ,
) ,
)
2021-02-09 10:17:52 +09:00
2021-02-22 02:08:04 +01:00
return ctrl . Result { RequeueAfter : retryDelayOnGitHubAPIRateLimitError } , err
}
2021-02-09 10:17:52 +09:00
2021-02-22 02:08:04 +01:00
return ctrl . Result { } , err
2021-02-09 10:17:52 +09:00
}
2021-02-22 02:08:04 +01:00
}
2021-02-09 10:17:52 +09:00
2021-02-22 02:08:04 +01:00
// See the `newPod` function called above for more information
// about when this hash changes.
curHash := pod . Labels [ LabelKeyPodTemplateHash ]
newHash := newPod . Labels [ LabelKeyPodTemplateHash ]
if ! runnerBusy && curHash != newHash {
restart = true
2021-02-09 10:17:52 +09:00
}
2020-10-05 01:06:37 +01:00
2021-02-22 02:08:04 +01:00
registrationTimeout := 10 * time . Minute
currentTime := time . Now ( )
registrationDidTimeout := currentTime . Sub ( pod . CreationTimestamp . Add ( registrationTimeout ) ) > 0
2020-12-08 01:48:35 +02:00
2021-02-22 02:08:04 +01:00
if notRegistered && registrationDidTimeout {
log . Info (
"Runner failed to register itself to GitHub in timely manner. " +
"Recreating the pod to see if it resolves the issue. " +
"CAUTION: If you see this a lot, you should investigate the root cause. " +
"See https://github.com/summerwind/actions-runner-controller/issues/288" ,
"podCreationTimestamp" , pod . CreationTimestamp ,
"currentTime" , currentTime ,
"configuredRegistrationTimeout" , registrationTimeout ,
)
restart = true
}
if offline && registrationDidTimeout {
log . Info (
"Already existing GitHub runner still appears offline . " +
"Recreating the pod to see if it resolves the issue. " +
"CAUTION: If you see this a lot, you should investigate the root cause. " ,
"podCreationTimestamp" , pod . CreationTimestamp ,
"currentTime" , currentTime ,
"configuredRegistrationTimeout" , registrationTimeout ,
)
restart = true
}
2020-10-05 01:06:37 +01:00
2021-02-09 10:17:52 +09:00
}
2020-12-08 01:48:35 +02:00
// Don't do anything if there's no need to restart the runner
2020-02-01 00:06:30 +09:00
if ! restart {
2021-02-09 10:17:52 +09:00
return ctrl . Result { } , nil
2020-01-29 23:12:07 +09:00
}
2020-01-28 21:58:01 +09:00
2020-12-08 01:48:35 +02:00
// Delete current pod if recreation is needed
2020-01-31 22:47:53 +09:00
if err := r . Delete ( ctx , & pod ) ; err != nil {
2020-02-03 18:40:59 +09:00
log . Error ( err , "Failed to delete pod resource" )
2020-01-28 21:58:01 +09:00
return ctrl . Result { } , err
}
2020-01-29 23:12:07 +09:00
2020-02-03 18:40:59 +09:00
r . Recorder . Event ( & runner , corev1 . EventTypeNormal , "PodDeleted" , fmt . Sprintf ( "Deleted pod '%s'" , newPod . Name ) )
log . Info ( "Deleted runner pod" , "repository" , runner . Spec . Repository )
2020-01-28 21:58:01 +09:00
}
2020-01-28 15:03:23 +09:00
return ctrl . Result { } , nil
}
2021-02-05 02:31:06 +02:00
func ( r * RunnerReconciler ) unregisterRunner ( ctx context . Context , enterprise , org , repo , name string ) ( bool , error ) {
runners , err := r . GitHubClient . ListRunners ( ctx , enterprise , org , repo )
2020-02-03 21:35:01 +09:00
if err != nil {
return false , err
}
2020-04-13 22:28:07 +09:00
id := int64 ( 0 )
for _ , runner := range runners {
if runner . GetName ( ) == name {
2020-10-05 01:06:37 +01:00
if runner . GetBusy ( ) {
return false , fmt . Errorf ( "runner is busy" )
}
2020-04-13 22:28:07 +09:00
id = runner . GetID ( )
2020-02-03 21:35:01 +09:00
break
}
}
2020-04-13 22:28:07 +09:00
if id == int64 ( 0 ) {
2020-02-03 21:35:01 +09:00
return false , nil
}
2021-02-05 02:31:06 +02:00
if err := r . GitHubClient . RemoveRunner ( ctx , enterprise , org , repo , id ) ; err != nil {
2020-02-03 21:35:01 +09:00
return false , err
}
return true , nil
}
2020-11-11 09:38:05 +09:00
func ( r * RunnerReconciler ) updateRegistrationToken ( ctx context . Context , runner v1alpha1 . Runner ) ( bool , error ) {
if runner . IsRegisterable ( ) {
return false , nil
}
log := r . Log . WithValues ( "runner" , runner . Name )
2021-02-05 02:31:06 +02:00
rt , err := r . GitHubClient . GetRegistrationToken ( ctx , runner . Spec . Enterprise , runner . Spec . Organization , runner . Spec . Repository , runner . Name )
2020-11-11 09:38:05 +09:00
if err != nil {
r . Recorder . Event ( & runner , corev1 . EventTypeWarning , "FailedUpdateRegistrationToken" , "Updating registration token failed" )
log . Error ( err , "Failed to get new registration token" )
return false , err
}
updated := runner . DeepCopy ( )
updated . Status . Registration = v1alpha1 . RunnerStatusRegistration {
Organization : runner . Spec . Organization ,
Repository : runner . Spec . Repository ,
Labels : runner . Spec . Labels ,
Token : rt . GetToken ( ) ,
ExpiresAt : metav1 . NewTime ( rt . GetExpiresAt ( ) . Time ) ,
}
if err := r . Status ( ) . Update ( ctx , updated ) ; err != nil {
log . Error ( err , "Failed to update runner status" )
return false , err
}
r . Recorder . Event ( & runner , corev1 . EventTypeNormal , "RegistrationTokenUpdated" , "Successfully update registration token" )
log . Info ( "Updated registration token" , "repository" , runner . Spec . Repository )
return true , nil
}
func ( r * RunnerReconciler ) newPod ( runner v1alpha1 . Runner ) ( corev1 . Pod , error ) {
2020-01-30 23:52:40 +09:00
var (
2020-10-20 02:48:28 +03:00
privileged bool = true
2020-10-21 21:32:40 +09:00
dockerdInRunner bool = runner . Spec . DockerdWithinRunnerContainer != nil && * runner . Spec . DockerdWithinRunnerContainer
2020-11-16 09:41:12 +09:00
dockerEnabled bool = runner . Spec . DockerEnabled == nil || * runner . Spec . DockerEnabled
2020-01-30 23:52:40 +09:00
)
2020-02-03 16:56:52 +09:00
runnerImage := runner . Spec . Image
if runnerImage == "" {
runnerImage = r . RunnerImage
2020-01-28 21:58:01 +09:00
}
2020-11-25 00:55:26 +01:00
workDir := runner . Spec . WorkDir
if workDir == "" {
workDir = "/runner/_work"
}
2020-07-08 23:53:52 -07:00
runnerImagePullPolicy := runner . Spec . ImagePullPolicy
if runnerImagePullPolicy == "" {
runnerImagePullPolicy = corev1 . PullAlways
}
2020-02-06 22:09:07 +09:00
env := [ ] corev1 . EnvVar {
{
Name : "RUNNER_NAME" ,
Value : runner . Name ,
} ,
2020-04-23 16:36:40 +02:00
{
Name : "RUNNER_ORG" ,
Value : runner . Spec . Organization ,
} ,
2020-02-06 22:09:07 +09:00
{
Name : "RUNNER_REPO" ,
Value : runner . Spec . Repository ,
} ,
2021-02-05 02:31:06 +02:00
{
Name : "RUNNER_ENTERPRISE" ,
Value : runner . Spec . Enterprise ,
} ,
2020-04-24 11:29:52 +02:00
{
Name : "RUNNER_LABELS" ,
Value : strings . Join ( runner . Spec . Labels , "," ) ,
} ,
2020-11-10 08:15:54 +00:00
{
Name : "RUNNER_GROUP" ,
Value : runner . Spec . Group ,
} ,
2020-02-06 22:09:07 +09:00
{
Name : "RUNNER_TOKEN" ,
2020-11-11 09:38:05 +09:00
Value : runner . Status . Registration . Token ,
2020-02-06 22:09:07 +09:00
} ,
2020-10-20 02:48:28 +03:00
{
Name : "DOCKERD_IN_RUNNER" ,
Value : fmt . Sprintf ( "%v" , dockerdInRunner ) ,
} ,
2020-10-28 15:15:53 +02:00
{
Name : "GITHUB_URL" ,
Value : r . GitHubClient . GithubBaseURL ,
} ,
2020-11-25 00:55:26 +01:00
{
Name : "RUNNER_WORKDIR" ,
Value : workDir ,
} ,
2020-02-06 22:09:07 +09:00
}
2020-03-20 15:50:50 +02:00
env = append ( env , runner . Spec . Env ... )
2020-12-08 17:56:06 +09:00
labels := map [ string ] string { }
for k , v := range runner . Labels {
labels [ k ] = v
}
// This implies that...
//
// (1) We recreate the runner pod whenever the runner has changes in:
// - metadata.labels (excluding "runner-template-hash" added by the parent RunnerReplicaSet
// - metadata.annotations
// - metadata.spec (including image, env, organization, repository, group, and so on)
// - GithubBaseURL setting of the controller (can be configured via GITHUB_ENTERPRISE_URL)
//
// (2) We don't recreate the runner pod when there are changes in:
// - runner.status.registration.token
// - This token expires and changes hourly, but you don't need to recreate the pod due to that.
// It's the opposite.
// An unexpired token is required only when the runner agent is registering itself on launch.
//
// In other words, the registered runner doesn't get invalidated on registration token expiration.
// A registered runner's session and the a registration token seem to have two different and independent
// lifecycles.
//
// See https://github.com/summerwind/actions-runner-controller/issues/143 for more context.
labels [ LabelKeyPodTemplateHash ] = hash . FNVHashStringObjects (
filterLabels ( runner . Labels , LabelKeyRunnerTemplateHash ) ,
runner . Annotations ,
runner . Spec ,
r . GitHubClient . GithubBaseURL ,
)
2020-01-29 23:12:07 +09:00
pod := corev1 . Pod {
2020-01-28 21:58:01 +09:00
ObjectMeta : metav1 . ObjectMeta {
2020-03-20 15:50:50 +02:00
Name : runner . Name ,
Namespace : runner . Namespace ,
2020-12-08 17:56:06 +09:00
Labels : labels ,
2020-03-20 15:50:50 +02:00
Annotations : runner . Annotations ,
2020-01-28 21:58:01 +09:00
} ,
Spec : corev1 . PodSpec {
2020-02-01 00:06:30 +09:00
RestartPolicy : "OnFailure" ,
2020-01-28 21:58:01 +09:00
Containers : [ ] corev1 . Container {
{
2020-02-01 00:06:30 +09:00
Name : containerName ,
2020-02-03 16:56:52 +09:00
Image : runnerImage ,
2020-07-08 23:53:52 -07:00
ImagePullPolicy : runnerImagePullPolicy ,
2020-02-06 22:09:07 +09:00
Env : env ,
2020-03-20 15:50:50 +02:00
EnvFrom : runner . Spec . EnvFrom ,
2020-01-30 23:52:40 +09:00
SecurityContext : & corev1 . SecurityContext {
2020-10-20 02:48:28 +03:00
// Runner need to run privileged if it contains DinD
2020-10-21 21:32:40 +09:00
Privileged : runner . Spec . DockerdWithinRunnerContainer ,
2020-01-30 23:52:40 +09:00
} ,
2020-03-20 15:50:50 +02:00
Resources : runner . Spec . Resources ,
2020-01-30 23:52:40 +09:00
} ,
2020-10-20 02:48:28 +03:00
} ,
} ,
}
2020-11-16 09:41:12 +09:00
if ! dockerdInRunner && dockerEnabled {
2021-01-24 10:58:35 +09:00
runnerVolumeName := "runner"
runnerVolumeMountPath := "/runner"
2020-10-20 02:48:28 +03:00
pod . Spec . Volumes = [ ] corev1 . Volume {
{
Name : "work" ,
VolumeSource : corev1 . VolumeSource {
EmptyDir : & corev1 . EmptyDirVolumeSource { } ,
2020-01-30 23:52:40 +09:00
} ,
} ,
2020-11-25 01:53:47 +02:00
{
2021-01-24 10:58:35 +09:00
Name : runnerVolumeName ,
2020-11-25 01:53:47 +02:00
VolumeSource : corev1 . VolumeSource {
EmptyDir : & corev1 . EmptyDirVolumeSource { } ,
} ,
} ,
2020-11-30 08:57:33 +09:00
{
Name : "certs-client" ,
VolumeSource : corev1 . VolumeSource {
EmptyDir : & corev1 . EmptyDirVolumeSource { } ,
} ,
} ,
2020-10-20 02:48:28 +03:00
}
pod . Spec . Containers [ 0 ] . VolumeMounts = [ ] corev1 . VolumeMount {
{
Name : "work" ,
2020-11-25 00:55:26 +01:00
MountPath : workDir ,
2020-10-20 02:48:28 +03:00
} ,
2020-11-25 01:53:47 +02:00
{
2021-01-24 10:58:35 +09:00
Name : runnerVolumeName ,
MountPath : runnerVolumeMountPath ,
2020-11-25 01:53:47 +02:00
} ,
2020-11-30 08:57:33 +09:00
{
Name : "certs-client" ,
MountPath : "/certs/client" ,
ReadOnly : true ,
} ,
2020-10-20 02:48:28 +03:00
}
2020-11-30 08:57:33 +09:00
pod . Spec . Containers [ 0 ] . Env = append ( pod . Spec . Containers [ 0 ] . Env , [ ] corev1 . EnvVar {
{
Name : "DOCKER_HOST" ,
Value : "tcp://localhost:2376" ,
} ,
{
Name : "DOCKER_TLS_VERIFY" ,
Value : "1" ,
} ,
{
Name : "DOCKER_CERT_PATH" ,
Value : "/certs/client" ,
} ,
} ... )
2020-10-20 02:48:28 +03:00
pod . Spec . Containers = append ( pod . Spec . Containers , corev1 . Container {
Name : "docker" ,
Image : r . DockerImage ,
VolumeMounts : [ ] corev1 . VolumeMount {
2020-04-24 22:36:27 +09:00
{
2020-10-20 02:48:28 +03:00
Name : "work" ,
2020-11-25 00:55:26 +01:00
MountPath : workDir ,
2020-04-24 22:36:27 +09:00
} ,
2020-11-25 01:53:47 +02:00
{
2021-01-24 10:58:35 +09:00
Name : runnerVolumeName ,
MountPath : runnerVolumeMountPath ,
2020-11-25 01:53:47 +02:00
} ,
2020-11-30 08:57:33 +09:00
{
Name : "certs-client" ,
MountPath : "/certs/client" ,
} ,
2020-11-12 08:07:52 +09:00
} ,
2020-11-12 09:20:06 +09:00
Env : [ ] corev1 . EnvVar {
2020-04-24 22:36:27 +09:00
{
2020-11-12 09:20:06 +09:00
Name : "DOCKER_TLS_CERTDIR" ,
2020-11-30 08:57:33 +09:00
Value : "/certs" ,
2020-01-28 21:58:01 +09:00
} ,
} ,
2020-10-20 02:48:28 +03:00
SecurityContext : & corev1 . SecurityContext {
Privileged : & privileged ,
} ,
2021-01-29 01:18:28 +01:00
Resources : runner . Spec . DockerdContainerResources ,
2020-10-20 02:48:28 +03:00
} )
2020-01-28 21:58:01 +09:00
}
2020-01-29 23:12:07 +09:00
2020-03-20 15:50:50 +02:00
if len ( runner . Spec . Containers ) != 0 {
pod . Spec . Containers = runner . Spec . Containers
2020-10-20 01:43:53 +02:00
for i := 0 ; i < len ( pod . Spec . Containers ) ; i ++ {
if pod . Spec . Containers [ i ] . Name == containerName {
pod . Spec . Containers [ i ] . Env = append ( pod . Spec . Containers [ i ] . Env , env ... )
}
}
2020-03-20 15:50:50 +02:00
}
if len ( runner . Spec . VolumeMounts ) != 0 {
pod . Spec . Containers [ 0 ] . VolumeMounts = append ( pod . Spec . Containers [ 0 ] . VolumeMounts , runner . Spec . VolumeMounts ... )
}
if len ( runner . Spec . Volumes ) != 0 {
2020-04-30 18:41:17 +09:00
pod . Spec . Volumes = append ( pod . Spec . Volumes , runner . Spec . Volumes ... )
2020-03-20 15:50:50 +02:00
}
if len ( runner . Spec . InitContainers ) != 0 {
pod . Spec . InitContainers = append ( pod . Spec . InitContainers , runner . Spec . InitContainers ... )
}
if runner . Spec . NodeSelector != nil {
pod . Spec . NodeSelector = runner . Spec . NodeSelector
}
if runner . Spec . ServiceAccountName != "" {
pod . Spec . ServiceAccountName = runner . Spec . ServiceAccountName
}
if runner . Spec . AutomountServiceAccountToken != nil {
pod . Spec . AutomountServiceAccountToken = runner . Spec . AutomountServiceAccountToken
}
if len ( runner . Spec . SidecarContainers ) != 0 {
pod . Spec . Containers = append ( pod . Spec . Containers , runner . Spec . SidecarContainers ... )
}
if runner . Spec . SecurityContext != nil {
pod . Spec . SecurityContext = runner . Spec . SecurityContext
}
if len ( runner . Spec . ImagePullSecrets ) != 0 {
pod . Spec . ImagePullSecrets = runner . Spec . ImagePullSecrets
}
if runner . Spec . Affinity != nil {
pod . Spec . Affinity = runner . Spec . Affinity
}
if len ( runner . Spec . Tolerations ) != 0 {
pod . Spec . Tolerations = runner . Spec . Tolerations
}
if len ( runner . Spec . EphemeralContainers ) != 0 {
pod . Spec . EphemeralContainers = runner . Spec . EphemeralContainers
}
if runner . Spec . TerminationGracePeriodSeconds != nil {
pod . Spec . TerminationGracePeriodSeconds = runner . Spec . TerminationGracePeriodSeconds
}
2020-01-29 23:12:07 +09:00
if err := ctrl . SetControllerReference ( & runner , & pod , r . Scheme ) ; err != nil {
return pod , err
}
return pod , nil
2020-01-28 21:58:01 +09:00
}
2020-01-28 15:03:23 +09:00
func ( r * RunnerReconciler ) SetupWithManager ( mgr ctrl . Manager ) error {
2021-02-16 18:51:33 +09:00
name := "runner-controller"
r . Recorder = mgr . GetEventRecorderFor ( name )
2020-02-03 18:40:59 +09:00
2020-01-28 15:03:23 +09:00
return ctrl . NewControllerManagedBy ( mgr ) .
2020-01-28 21:58:01 +09:00
For ( & v1alpha1 . Runner { } ) .
2020-01-29 23:12:07 +09:00
Owns ( & corev1 . Pod { } ) .
2021-02-16 18:51:33 +09:00
Named ( name ) .
2020-01-28 15:03:23 +09:00
Complete ( r )
}
2020-02-03 21:35:01 +09:00
func addFinalizer ( finalizers [ ] string ) ( [ ] string , bool ) {
exists := false
for _ , name := range finalizers {
if name == finalizerName {
exists = true
}
}
if exists {
return finalizers , false
}
return append ( finalizers , finalizerName ) , true
}
func removeFinalizer ( finalizers [ ] string ) ( [ ] string , bool ) {
removed := false
result := [ ] string { }
for _ , name := range finalizers {
if name == finalizerName {
removed = true
continue
}
result = append ( result , name )
}
return result , removed
}