Compare commits

..

12 Commits

Author SHA1 Message Date
Moto Ishizawa
f2d3ca672f Unset environment variables for runner config 2020-02-06 22:15:26 +09:00
Moto Ishizawa
829a167303 Add 'env' field to runner resource 2020-02-06 22:09:07 +09:00
Moto Ishizawa
c66916a4ee Use dumb-init to handle signal properly 2020-02-06 18:47:50 +09:00
Moto Ishizawa
f5c8a0e655 Update README 2020-02-03 21:52:28 +09:00
Moto Ishizawa
a436216d5e Implement finalizer 2020-02-03 21:35:01 +09:00
Moto Ishizawa
497ddba82d Record event of runner resource 2020-02-03 18:40:59 +09:00
Moto Ishizawa
10f6cb5e90 Add additional printer columns 2020-02-03 17:37:48 +09:00
Moto Ishizawa
13ef78ce20 Sync runner status with pod status 2020-02-03 17:25:38 +09:00
Moto Ishizawa
0061979e3e Add '-github-token' flag 2020-02-03 17:02:27 +09:00
Moto Ishizawa
e6952f5ca1 Add '-runner-image' and '-docker-image' flags 2020-02-03 16:56:52 +09:00
Moto Ishizawa
ffdbe5cee9 Set container image version properly in the release task 2020-02-03 16:55:38 +09:00
Moto Ishizawa
4970814b6c Do not run build workflow when .github changed 2020-02-03 16:54:49 +09:00
10 changed files with 366 additions and 55 deletions

View File

@@ -4,6 +4,7 @@ on:
- master - master
paths-ignore: paths-ignore:
- 'runner/**' - 'runner/**'
- '.github/**'
jobs: jobs:
build: build:

View File

@@ -64,6 +64,7 @@ docker-push:
# Generate the release manifest file # Generate the release manifest file
release: manifests release: manifests
cd config/manager && kustomize edit set image controller=${NAME}:${VERSION}
mkdir -p release mkdir -p release
kustomize build config/default > release/actions-runner-controller.yaml kustomize build config/default > release/actions-runner-controller.yaml

View File

@@ -13,7 +13,7 @@ This controller operates self-hosted runners for GitHub Actions on your Kubernet
First, install *actions-runner-controller* with a manifest file. This will create a *actions-runner-system* namespace in your Kubernetes and deploy the required resources. First, install *actions-runner-controller* with a manifest file. This will create a *actions-runner-system* namespace in your Kubernetes and deploy the required resources.
``` ```
$ kubectl -f https://github.com/summerwind/actions-runner-controller/releases/download/latest/actions-runner-controller.yaml $ kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/latest/download/actions-runner-controller.yaml
``` ```
Set your access token of GitHub to the secret. `${GITHUB_TOKEN}` is the value you must replace with your access token. This token is used to register Self-hosted runner by *actions-runner-controller*. Set your access token of GitHub to the secret. `${GITHUB_TOKEN}` is the value you must replace with your access token. This token is used to register Self-hosted runner by *actions-runner-controller*.
@@ -27,9 +27,7 @@ $ kubectl create secret generic controller-manager --from-literal=github_token=$
To launch Self-hosted runner, you need to create a manifest file includes *Runner* resource as follows. This example launches a self-hosted runner with name *example-runner* for the *summerwind/actions-runner-controller* repository. To launch Self-hosted runner, you need to create a manifest file includes *Runner* resource as follows. This example launches a self-hosted runner with name *example-runner* for the *summerwind/actions-runner-controller* repository.
``` ```
$ vim runner.yaml # runner.yaml
```
```
apiVersion: actions.summerwind.dev/v1alpha1 apiVersion: actions.summerwind.dev/v1alpha1
kind: Runner kind: Runner
metadata: metadata:
@@ -42,14 +40,15 @@ Apply the created manifest file to your Kubernetes.
``` ```
$ kubectl apply -f runner.yaml $ kubectl apply -f runner.yaml
runner.actions.summerwind.dev/example-runner created
``` ```
You can see that the Runner resource has been created. You can see that the Runner resource has been created.
``` ```
$ kubectl get runners $ kubectl get runners
NAME AGE NAME REPOSITORY STATUS
example-runner 1m example-runner summerwind/actions-runner-controller Running
``` ```
You can also see that the runner pod has been running. You can also see that the runner pod has been running.
@@ -64,4 +63,4 @@ The runner you created has been registerd to your repository.
<img width="756" alt="Actions tab in your repository settings" src="https://user-images.githubusercontent.com/230145/73618667-8cbf9700-466c-11ea-80b6-c67e6d3f70e7.png"> <img width="756" alt="Actions tab in your repository settings" src="https://user-images.githubusercontent.com/230145/73618667-8cbf9700-466c-11ea-80b6-c67e6d3f70e7.png">
Now your can use your self-hosted runner. See the [documentation](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-self-hosted-runners-in-a-workflow) on how to run a job with it. Now your can use your self-hosted runner. See the [official documentation](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-self-hosted-runners-in-a-workflow) on how to run a job with it.

View File

@@ -17,6 +17,7 @@ limitations under the License.
package v1alpha1 package v1alpha1
import ( import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@@ -28,14 +29,17 @@ type RunnerSpec struct {
// +optional // +optional
Image string `json:"image"` Image string `json:"image"`
// +optional
Env []corev1.EnvVar `json:"env"`
} }
// RunnerStatus defines the observed state of Runner // RunnerStatus defines the observed state of Runner
type RunnerStatus struct { type RunnerStatus struct {
Registration RunnerStatusRegistration `json:"registration"` Registration RunnerStatusRegistration `json:"registration"`
Phase string `json:"Phase"` Phase string `json:"phase"`
Reason string `json:"Reason"` Reason string `json:"reason"`
Message string `json:"Message"` Message string `json:"message"`
} }
type RunnerStatusRegistration struct { type RunnerStatusRegistration struct {
@@ -46,6 +50,8 @@ type RunnerStatusRegistration struct {
// +kubebuilder:object:root=true // +kubebuilder:object:root=true
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
// +kubebuilder:printcolumn:JSONPath=".spec.repository",name=Repository,type=string
// +kubebuilder:printcolumn:JSONPath=".status.phase",name=Status,type=string
// Runner is the Schema for the runners API // Runner is the Schema for the runners API
type Runner struct { type Runner struct {

View File

@@ -21,6 +21,7 @@ limitations under the License.
package v1alpha1 package v1alpha1
import ( import (
"k8s.io/api/core/v1"
runtime "k8s.io/apimachinery/pkg/runtime" runtime "k8s.io/apimachinery/pkg/runtime"
) )
@@ -29,7 +30,7 @@ func (in *Runner) DeepCopyInto(out *Runner) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status) in.Status.DeepCopyInto(&out.Status)
} }
@@ -86,6 +87,13 @@ func (in *RunnerList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RunnerSpec) DeepCopyInto(out *RunnerSpec) { func (in *RunnerSpec) DeepCopyInto(out *RunnerSpec) {
*out = *in *out = *in
if in.Env != nil {
in, out := &in.Env, &out.Env
*out = make([]v1.EnvVar, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunnerSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunnerSpec.

View File

@@ -8,6 +8,13 @@ metadata:
creationTimestamp: null creationTimestamp: null
name: runners.actions.summerwind.dev name: runners.actions.summerwind.dev
spec: spec:
additionalPrinterColumns:
- JSONPath: .spec.repository
name: Repository
type: string
- JSONPath: .status.phase
name: Status
type: string
group: actions.summerwind.dev group: actions.summerwind.dev
names: names:
kind: Runner kind: Runner
@@ -36,6 +43,103 @@ spec:
spec: spec:
description: RunnerSpec defines the desired state of Runner description: RunnerSpec defines the desired state of Runner
properties: properties:
env:
items:
description: EnvVar represents an environment variable present in
a Container.
properties:
name:
description: Name of the environment variable. Must be a C_IDENTIFIER.
type: string
value:
description: 'Variable references $(VAR_NAME) are expanded using
the previous defined environment variables in the container
and any service environment variables. If a variable cannot
be resolved, the reference in the input string will be unchanged.
The $(VAR_NAME) syntax can be escaped with a double $$, ie:
$$(VAR_NAME). Escaped references will never be expanded, regardless
of whether the variable exists or not. Defaults to "".'
type: string
valueFrom:
description: Source for the environment variable's value. Cannot
be used if value is not empty.
properties:
configMapKeyRef:
description: Selects a key of a ConfigMap.
properties:
key:
description: The key to select.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the ConfigMap or its key
must be defined
type: boolean
required:
- key
type: object
fieldRef:
description: 'Selects a field of the pod: supports metadata.name,
metadata.namespace, metadata.labels, metadata.annotations,
spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP.'
properties:
apiVersion:
description: Version of the schema the FieldPath is written
in terms of, defaults to "v1".
type: string
fieldPath:
description: Path of the field to select in the specified
API version.
type: string
required:
- fieldPath
type: object
resourceFieldRef:
description: 'Selects a resource of the container: only resources
limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage,
requests.cpu, requests.memory and requests.ephemeral-storage)
are currently supported.'
properties:
containerName:
description: 'Container name: required for volumes, optional
for env vars'
type: string
divisor:
description: Specifies the output format of the exposed
resources, defaults to "1"
type: string
resource:
description: 'Required: resource to select'
type: string
required:
- resource
type: object
secretKeyRef:
description: Selects a key of a secret in the pod's namespace
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
type: object
required:
- name
type: object
type: array
image: image:
type: string type: string
repository: repository:
@@ -48,11 +152,11 @@ spec:
status: status:
description: RunnerStatus defines the observed state of Runner description: RunnerStatus defines the observed state of Runner
properties: properties:
Message: message:
type: string type: string
Phase: phase:
type: string type: string
Reason: reason:
type: string type: string
registration: registration:
properties: properties:
@@ -69,9 +173,9 @@ spec:
- token - token
type: object type: object
required: required:
- Message - message
- Phase - phase
- Reason - reason
- registration - registration
type: object type: object
type: object type: object

View File

@@ -26,6 +26,7 @@ import (
"github.com/google/go-github/v29/github" "github.com/google/go-github/v29/github"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
@@ -36,11 +37,18 @@ import (
) )
const ( const (
defaultImage = "summerwind/actions-runner:latest"
containerName = "runner" containerName = "runner"
finalizerName = "runner.actions.summerwind.dev"
) )
type RegistrationToken struct { type GitHubRunner struct {
ID int `json:"id"`
Name string `json:"name"`
OS string `json:"os"`
Status string `json:"status"`
}
type GitHubRegistrationToken struct {
Token string `json:"token"` Token string `json:"token"`
ExpiresAt string `json:"expires_at"` ExpiresAt string `json:"expires_at"`
} }
@@ -49,8 +57,11 @@ type RegistrationToken struct {
type RunnerReconciler struct { type RunnerReconciler struct {
client.Client client.Client
Log logr.Logger Log logr.Logger
Recorder record.EventRecorder
Scheme *runtime.Scheme Scheme *runtime.Scheme
GitHubClient *github.Client GitHubClient *github.Client
RunnerImage string
DockerImage string
} }
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners,verbs=get;list;watch;create;update;patch;delete
@@ -63,14 +74,56 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
var runner v1alpha1.Runner var runner v1alpha1.Runner
if err := r.Get(ctx, req.NamespacedName, &runner); err != nil { if err := r.Get(ctx, req.NamespacedName, &runner); err != nil {
log.Error(err, "Unable to fetch Runner")
return ctrl.Result{}, client.IgnoreNotFound(err) return ctrl.Result{}, client.IgnoreNotFound(err)
} }
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)
if removed {
ok, err := r.unregisterRunner(ctx, runner.Spec.Repository, runner.Name)
if err != nil {
log.Error(err, "Failed to unregister runner")
return ctrl.Result{}, err
}
if !ok {
log.V(1).Info("Runner no longer exists on GitHub")
}
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
}
log.Info("Removed runner from GitHub", "repository", runner.Spec.Repository)
}
return ctrl.Result{}, nil
}
if !runner.IsRegisterable() { if !runner.IsRegisterable() {
reg, err := r.newRegistration(ctx, runner.Spec.Repository) reg, err := r.newRegistration(ctx, runner.Spec.Repository)
if err != nil { if err != nil {
log.Error(err, "Failed to get new registration") r.Recorder.Event(&runner, corev1.EventTypeWarning, "FailedUpdateRegistrationToken", "Updating registration token failed")
log.Error(err, "Failed to get new registration token")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
@@ -78,10 +131,11 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
updated.Status.Registration = reg updated.Status.Registration = reg
if err := r.Status().Update(ctx, updated); err != nil { if err := r.Status().Update(ctx, updated); err != nil {
log.Error(err, "Unable to update Runner status") log.Error(err, "Failed to update runner status")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
r.Recorder.Event(&runner, corev1.EventTypeNormal, "RegistrationTokenUpdated", "Successfully update registration token")
log.Info("Updated registration token", "repository", runner.Spec.Repository) log.Info("Updated registration token", "repository", runner.Spec.Repository)
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
@@ -94,17 +148,32 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
newPod, err := r.newPod(runner) newPod, err := r.newPod(runner)
if err != nil { if err != nil {
log.Error(err, "could not create pod") log.Error(err, "Could not create pod")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
if err := r.Create(ctx, &newPod); err != nil { if err := r.Create(ctx, &newPod); err != nil {
log.Error(err, "failed to create pod resource") log.Error(err, "Failed to create pod resource")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
log.Info("Created a runner pod", "repository", runner.Spec.Repository) r.Recorder.Event(&runner, corev1.EventTypeNormal, "PodCreated", fmt.Sprintf("Created pod '%s'", newPod.Name))
log.Info("Created runner pod", "repository", runner.Spec.Repository)
} else { } else {
if runner.Status.Phase != string(pod.Status.Phase) {
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 {
log.Error(err, "Failed to update runner status")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
if !pod.ObjectMeta.DeletionTimestamp.IsZero() { if !pod.ObjectMeta.DeletionTimestamp.IsZero() {
return ctrl.Result{}, err return ctrl.Result{}, err
} }
@@ -125,7 +194,7 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
newPod, err := r.newPod(runner) newPod, err := r.newPod(runner)
if err != nil { if err != nil {
log.Error(err, "could not create pod") log.Error(err, "Could not create pod")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
@@ -140,11 +209,12 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
} }
if err := r.Delete(ctx, &pod); err != nil { if err := r.Delete(ctx, &pod); err != nil {
log.Error(err, "failed to delete pod resource") log.Error(err, "Failed to delete pod resource")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
log.Info("Restarted a runner pod", "repository", runner.Spec.Repository) r.Recorder.Event(&runner, corev1.EventTypeNormal, "PodDeleted", fmt.Sprintf("Deleted pod '%s'", newPod.Name))
log.Info("Deleted runner pod", "repository", runner.Spec.Repository)
} }
return ctrl.Result{}, nil return ctrl.Result{}, nil
@@ -170,8 +240,8 @@ func (r *RunnerReconciler) newRegistration(ctx context.Context, repo string) (v1
return reg, err return reg, err
} }
func (r *RunnerReconciler) getRegistrationToken(ctx context.Context, repo string) (RegistrationToken, error) { func (r *RunnerReconciler) getRegistrationToken(ctx context.Context, repo string) (GitHubRegistrationToken, error) {
var regToken RegistrationToken var regToken GitHubRegistrationToken
req, err := r.GitHubClient.NewRequest("POST", fmt.Sprintf("/repos/%s/actions/runners/registration-token", repo), nil) req, err := r.GitHubClient.NewRequest("POST", fmt.Sprintf("/repos/%s/actions/runners/registration-token", repo), nil)
if err != nil { if err != nil {
@@ -190,30 +260,81 @@ func (r *RunnerReconciler) getRegistrationToken(ctx context.Context, repo string
return regToken, nil return regToken, nil
} }
func (r *RunnerReconciler) unregisterRunner(ctx context.Context, repo, name string) (bool, error) {
runners, err := r.listRunners(ctx, repo)
if err != nil {
return false, err
}
id := 0
for _, runner := range runners {
if runner.Name == name {
id = runner.ID
break
}
}
if id == 0 {
return false, nil
}
if err := r.removeRunner(ctx, repo, id); err != nil {
return false, err
}
return true, nil
}
func (r *RunnerReconciler) listRunners(ctx context.Context, repo string) ([]GitHubRunner, error) {
runners := []GitHubRunner{}
req, err := r.GitHubClient.NewRequest("GET", fmt.Sprintf("/repos/%s/actions/runners", repo), nil)
if err != nil {
return runners, err
}
res, err := r.GitHubClient.Do(ctx, req, &runners)
if err != nil {
return runners, err
}
if res.StatusCode != 200 {
return runners, fmt.Errorf("unexpected status: %d", res.StatusCode)
}
return runners, nil
}
func (r *RunnerReconciler) removeRunner(ctx context.Context, repo string, id int) error {
req, err := r.GitHubClient.NewRequest("DELETE", fmt.Sprintf("/repos/%s/actions/runners/%d", repo, id), nil)
if err != nil {
return err
}
res, err := r.GitHubClient.Do(ctx, req, nil)
if err != nil {
return err
}
if res.StatusCode != 204 {
return fmt.Errorf("unexpected status: %d", res.StatusCode)
}
return nil
}
func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) { func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
var ( var (
privileged bool = true privileged bool = true
group int64 = 0 group int64 = 0
) )
image := runner.Spec.Image runnerImage := runner.Spec.Image
if image == "" { if runnerImage == "" {
image = defaultImage runnerImage = r.RunnerImage
} }
pod := corev1.Pod{ env := []corev1.EnvVar{
ObjectMeta: metav1.ObjectMeta{
Name: runner.Name,
Namespace: runner.Namespace,
},
Spec: corev1.PodSpec{
RestartPolicy: "OnFailure",
Containers: []corev1.Container{
{
Name: containerName,
Image: image,
ImagePullPolicy: "Always",
Env: []corev1.EnvVar{
{ {
Name: "RUNNER_NAME", Name: "RUNNER_NAME",
Value: runner.Name, Value: runner.Name,
@@ -226,7 +347,22 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
Name: "RUNNER_TOKEN", Name: "RUNNER_TOKEN",
Value: runner.Status.Registration.Token, Value: runner.Status.Registration.Token,
}, },
}
env = append(env, runner.Spec.Env...)
pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: runner.Name,
Namespace: runner.Namespace,
}, },
Spec: corev1.PodSpec{
RestartPolicy: "OnFailure",
Containers: []corev1.Container{
{
Name: containerName,
Image: runnerImage,
ImagePullPolicy: "Always",
Env: env,
VolumeMounts: []corev1.VolumeMount{ VolumeMounts: []corev1.VolumeMount{
{ {
Name: "docker", Name: "docker",
@@ -239,7 +375,7 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
}, },
{ {
Name: "docker", Name: "docker",
Image: "docker:19.03.5-dind", Image: r.DockerImage,
VolumeMounts: []corev1.VolumeMount{ VolumeMounts: []corev1.VolumeMount{
{ {
Name: "docker", Name: "docker",
@@ -270,8 +406,40 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
} }
func (r *RunnerReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *RunnerReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.Recorder = mgr.GetEventRecorderFor("runner-controller")
return ctrl.NewControllerManagedBy(mgr). return ctrl.NewControllerManagedBy(mgr).
For(&v1alpha1.Runner{}). For(&v1alpha1.Runner{}).
Owns(&corev1.Pod{}). Owns(&corev1.Pod{}).
Complete(r) Complete(r)
} }
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
}

27
main.go
View File

@@ -34,6 +34,11 @@ import (
// +kubebuilder:scaffold:imports // +kubebuilder:scaffold:imports
) )
const (
defaultRunnerImage = "summerwind/actions-runner:v2.165.1"
defaultDockerImage = "docker:19.03.5-dind"
)
var ( var (
scheme = runtime.NewScheme() scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup") setupLog = ctrl.Log.WithName("setup")
@@ -47,16 +52,28 @@ func init() {
} }
func main() { func main() {
var metricsAddr string var (
var enableLeaderElection bool metricsAddr string
enableLeaderElection bool
runnerImage string
dockerImage string
ghToken string
)
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
flag.StringVar(&runnerImage, "runner-image", defaultRunnerImage, "The image name of self-hosted runner container.")
flag.StringVar(&dockerImage, "docker-image", defaultDockerImage, "The image name of docker sidecar container.")
flag.StringVar(&ghToken, "github-token", "", "The access token of GitHub.")
flag.Parse() flag.Parse()
ghToken := os.Getenv("GITHUB_TOKEN")
if ghToken == "" { if ghToken == "" {
fmt.Fprintln(os.Stderr, "Error: access token is not specified in the environment variable 'GITHUB_TOKEN'") ghToken = os.Getenv("GITHUB_TOKEN")
}
if ghToken == "" {
fmt.Fprintln(os.Stderr, "Error: GitHub access token must be specified.")
os.Exit(1) os.Exit(1)
} }
@@ -85,6 +102,8 @@ func main() {
Log: ctrl.Log.WithName("controllers").WithName("Runner"), Log: ctrl.Log.WithName("controllers").WithName("Runner"),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
GitHubClient: ghClient, GitHubClient: ghClient,
RunnerImage: runnerImage,
DockerImage: dockerImage,
} }
if err = runnerReconciler.SetupWithManager(mgr); err != nil { if err = runnerReconciler.SetupWithManager(mgr); err != nil {

View File

@@ -9,6 +9,8 @@ RUN apt update \
&& tar zxvf docker.tgz \ && tar zxvf docker.tgz \
&& install -o root -g root -m 755 docker/docker /usr/local/bin/docker \ && install -o root -g root -m 755 docker/docker /usr/local/bin/docker \
&& rm -rf docker docker.tgz \ && rm -rf docker docker.tgz \
&& curl -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64 \
&& chmod +x /usr/local/bin/dumb-init \
&& adduser --disabled-password --gecos "" --uid 1000 runner && adduser --disabled-password --gecos "" --uid 1000 runner
RUN mkdir -p /runner \ RUN mkdir -p /runner \
@@ -21,4 +23,5 @@ RUN mkdir -p /runner \
COPY entrypoint.sh /runner COPY entrypoint.sh /runner
USER runner:runner USER runner:runner
ENTRYPOINT ["/runner/entrypoint.sh"] ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
CMD ["/runner/entrypoint.sh"]

View File

@@ -17,4 +17,6 @@ fi
cd /runner cd /runner
./config.sh --unattended --replace --name "${RUNNER_NAME}" --url "https://github.com/${RUNNER_REPO}" --token "${RUNNER_TOKEN}" ./config.sh --unattended --replace --name "${RUNNER_NAME}" --url "https://github.com/${RUNNER_REPO}" --token "${RUNNER_TOKEN}"
./run.sh --once
unset RUNNER_NAME RUNNER_REPO RUNNER_TOKEN
exec ./run.sh --once