diff --git a/.github/workflows/validate-runners.yaml b/.github/workflows/validate-runners.yaml index 8de3ee7451..cab0eb7072 100644 --- a/.github/workflows/validate-runners.yaml +++ b/.github/workflows/validate-runners.yaml @@ -17,7 +17,7 @@ jobs: name: runner / shellcheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: shellcheck uses: reviewdog/action-shellcheck@v1 with: diff --git a/Makefile b/Makefile index a3ac7fc738..87dfd6c236 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,17 @@ TOOLS_PATH=$(PWD)/.tools OS_NAME := $(shell uname -s | tr A-Z a-z) +# The etcd packages that coreos maintain use different extensions for each *nix OS on their github release page. +# ETCD_EXTENSION: the storage format file extension listed on the release page. +# EXTRACT_COMMAND: the appropriate CLI command for extracting this file format. +ifeq ($(OS_NAME), darwin) +ETCD_EXTENSION:=zip +EXTRACT_COMMAND:=unzip +else +ETCD_EXTENSION:=tar.gz +EXTRACT_COMMAND:=tar -xzf +endif + # default list of platforms for which multiarch image is built ifeq (${PLATFORMS}, ) export PLATFORMS="linux/amd64,linux/arm64" @@ -287,12 +298,10 @@ ifeq (, $(wildcard $(TEST_ASSETS)/etcd)) set -xe ;\ INSTALL_TMP_DIR=$$(mktemp -d) ;\ cd $$INSTALL_TMP_DIR ;\ - wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.2/kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\ + wget https://github.com/coreos/etcd/releases/download/v3.4.22/etcd-v3.4.22-$(OS_NAME)-amd64.$(ETCD_EXTENSION);\ mkdir -p $(TEST_ASSETS) ;\ - tar zxvf kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\ - mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/etcd $(TEST_ASSETS)/etcd ;\ - mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kube-apiserver $(TEST_ASSETS)/kube-apiserver ;\ - mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kubectl $(TEST_ASSETS)/kubectl ;\ + $(EXTRACT_COMMAND) etcd-v3.4.22-$(OS_NAME)-amd64.$(ETCD_EXTENSION) ;\ + mv etcd-v3.4.22-$(OS_NAME)-amd64/etcd $(TEST_ASSETS)/etcd ;\ rm -rf $$INSTALL_TMP_DIR ;\ } ETCD_BIN=$(TEST_ASSETS)/etcd @@ -314,9 +323,7 @@ ifeq (, $(wildcard $(TEST_ASSETS)/kube-apiserver)) wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.2/kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\ mkdir -p $(TEST_ASSETS) ;\ tar zxvf kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\ - mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/etcd $(TEST_ASSETS)/etcd ;\ mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kube-apiserver $(TEST_ASSETS)/kube-apiserver ;\ - mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kubectl $(TEST_ASSETS)/kubectl ;\ rm -rf $$INSTALL_TMP_DIR ;\ } KUBE_APISERVER_BIN=$(TEST_ASSETS)/kube-apiserver @@ -338,8 +345,6 @@ ifeq (, $(wildcard $(TEST_ASSETS)/kubectl)) wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.2/kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\ mkdir -p $(TEST_ASSETS) ;\ tar zxvf kubebuilder_2.3.2_$(OS_NAME)_amd64.tar.gz ;\ - mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/etcd $(TEST_ASSETS)/etcd ;\ - mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kube-apiserver $(TEST_ASSETS)/kube-apiserver ;\ mv kubebuilder_2.3.2_$(OS_NAME)_amd64/bin/kubectl $(TEST_ASSETS)/kubectl ;\ rm -rf $$INSTALL_TMP_DIR ;\ } diff --git a/acceptance/testdata/runnerdeploy.envsubst.yaml b/acceptance/testdata/runnerdeploy.envsubst.yaml index 8430a4ea7f..17ce69e6d6 100644 --- a/acceptance/testdata/runnerdeploy.envsubst.yaml +++ b/acceptance/testdata/runnerdeploy.envsubst.yaml @@ -74,6 +74,8 @@ spec: value: "172.17.0.0/12" - name: DOCKER_DEFAULT_ADDRESS_POOL_SIZE value: "24" + - name: WAIT_FOR_DOCKER_SECONDS + value: "3" dockerMTU: 1400 diff --git a/charts/actions-runner-controller/README.md b/charts/actions-runner-controller/README.md index 678ac867e8..ec08904866 100644 --- a/charts/actions-runner-controller/README.md +++ b/charts/actions-runner-controller/README.md @@ -8,107 +8,107 @@ All additional docs are kept in the `docs/` folder, this README is solely for do > _Default values are the defaults set in the charts `values.yaml`, some properties have default configurations in the code for when the property is omitted or invalid_ -| Key | Description | Default | -|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| -| `labels` | Set labels to apply to all resources in the chart | | -| `replicaCount` | Set the number of controller pods | 1 | -| `webhookPort` | Set the containerPort for the webhook Pod | 9443 | -| `syncPeriod` | Set the period in which the controller reconciles the desired runners count | 1m | -| `enableLeaderElection` | Enable election configuration | true | -| `leaderElectionId` | Set the election ID for the controller group | | -| `githubEnterpriseServerURL` | Set the URL for a self-hosted GitHub Enterprise Server | | -| `githubURL` | Override GitHub URL to be used for GitHub API calls | | -| `githubUploadURL` | Override GitHub Upload URL to be used for GitHub API calls | | -| `runnerGithubURL` | Override GitHub URL to be used by runners during registration | | -| `logLevel` | Set the log level of the controller container | | -| `logFormat` | Set the log format of the controller. Valid options are "text" and "json" | text | -| `additionalVolumes` | Set additional volumes to add to the manager container | | -| `additionalVolumeMounts` | Set additional volume mounts to add to the manager container | | -| `authSecret.create` | Deploy the controller auth secret | false | -| `authSecret.name` | Set the name of the auth secret | controller-manager | -| `authSecret.annotations` | Set annotations for the auth Secret | | -| `authSecret.github_app_id` | The ID of your GitHub App. **This can't be set at the same time as `authSecret.github_token`** | | -| `authSecret.github_app_installation_id` | The ID of your GitHub App installation. **This can't be set at the same time as `authSecret.github_token`** | | -| `authSecret.github_app_private_key` | The multiline string of your GitHub App's private key. **This can't be set at the same time as `authSecret.github_token`** | | -| `authSecret.github_token` | Your chosen GitHub PAT token. **This can't be set at the same time as the `authSecret.github_app_*`** | | -| `authSecret.github_basicauth_username` | Username for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | | -| `authSecret.github_basicauth_password` | Password for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | | -| `dockerRegistryMirror` | The default Docker Registry Mirror used by runners. | | -| `hostNetwork` | The "hostNetwork" of the controller container | false | -| `image.repository` | The "repository/image" of the controller container | summerwind/actions-runner-controller | -| `image.tag` | The tag of the controller container | | -| `image.actionsRunnerRepositoryAndTag` | The "repository/image" of the actions runner container | summerwind/actions-runner:latest | -| `image.actionsRunnerImagePullSecrets` | Optional image pull secrets to be included in the runner pod's ImagePullSecrets | | -| `image.dindSidecarRepositoryAndTag` | The "repository/image" of the dind sidecar container | docker:dind | -| `image.pullPolicy` | The pull policy of the controller image | IfNotPresent | -| `metrics.serviceMonitor` | Deploy serviceMonitor kind for for use with prometheus-operator CRDs | false | -| `metrics.serviceAnnotations` | Set annotations for the provisioned metrics service resource | | -| `metrics.port` | Set port of metrics service | 8443 | -| `metrics.proxy.enabled` | Deploy kube-rbac-proxy container in controller pod | true | -| `metrics.proxy.image.repository` | The "repository/image" of the kube-proxy container | quay.io/brancz/kube-rbac-proxy | -| `metrics.proxy.image.tag` | The tag of the kube-proxy image to use when pulling the container | v0.10.0 | -| `metrics.serviceMonitorLabels` | Set labels to apply to ServiceMonitor resources | | -| `imagePullSecrets` | Specifies the secret to be used when pulling the controller pod containers | | -| `fullnameOverride` | Override the full resource names | | -| `nameOverride` | Override the resource name prefix | | -| `serviceAccount.annotations` | Set annotations to the service account | | -| `serviceAccount.create` | Deploy the controller pod under a service account | true | -| `podAnnotations` | Set annotations for the controller pod | | -| `podLabels` | Set labels for the controller pod | | -| `serviceAccount.name` | Set the name of the service account | | -| `securityContext` | Set the security context for each container in the controller pod | | -| `podSecurityContext` | Set the security context to controller pod | | -| `service.annotations` | Set annotations for the provisioned webhook service resource | | -| `service.port` | Set controller service ports | | -| `service.type` | Set controller service type | | -| `topologySpreadConstraints` | Set the controller pod topologySpreadConstraints | | -| `nodeSelector` | Set the controller pod nodeSelector | | -| `resources` | Set the controller pod resources | | -| `affinity` | Set the controller pod affinity rules | | -| `podDisruptionBudget.enabled` | Enables a PDB to ensure HA of controller pods | false | -| `podDisruptionBudget.minAvailable` | Minimum number of pods that must be available after eviction | | -| `podDisruptionBudget.maxUnavailable` | Maximum number of pods that can be unavailable after eviction. Kubernetes 1.7+ required. | | -| `tolerations` | Set the controller pod tolerations | | -| `env` | Set environment variables for the controller container | | -| `priorityClassName` | Set the controller pod priorityClassName | | -| `scope.watchNamespace` | Tells the controller and the github webhook server which namespace to watch if `scope.singleNamespace` is true | `Release.Namespace` (the default namespace of the helm chart). | -| `scope.singleNamespace` | Limit the controller to watch a single namespace | false | -| `certManagerEnabled` | Enable cert-manager. If disabled you must set admissionWebHooks.caBundle and create TLS secrets manually | true | -| `runner.statusUpdateHook.enabled` | Use custom RBAC for runners (role, role binding and service account), this will enable reporting runner statuses | false | -| `admissionWebHooks.caBundle` | Base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate | | -| `githubWebhookServer.logLevel` | Set the log level of the githubWebhookServer container | | -| `githubWebhookServer.logFormat` | Set the log format of the githubWebhookServer controller. Valid options are "text" and "json" | text | -| `githubWebhookServer.replicaCount` | Set the number of webhook server pods | 1 | -| `githubWebhookServer.useRunnerGroupsVisibility` | Enable supporting runner groups with custom visibility. This will incur in extra API calls and may blow up your budget. Currently, you also need to set `githubWebhookServer.secret.enabled` to enable this feature. | false | -| `githubWebhookServer.enabled` | Deploy the webhook server pod | false | -| `githubWebhookServer.queueLimit` | Set the queue size limit in the githubWebhookServer | | -| `githubWebhookServer.secret.enabled` | Passes the webhook hook secret to the github-webhook-server | false | -| `githubWebhookServer.secret.create` | Deploy the webhook hook secret | false | -| `githubWebhookServer.secret.name` | Set the name of the webhook hook secret | github-webhook-server | -| `githubWebhookServer.secret.github_webhook_secret_token` | Set the webhook secret token value | | -| `githubWebhookServer.imagePullSecrets` | Specifies the secret to be used when pulling the githubWebhookServer pod containers | | -| `githubWebhookServer.nameOverride` | Override the resource name prefix | | -| `githubWebhookServer.fullnameOverride` | Override the full resource names | | -| `githubWebhookServer.serviceAccount.create` | Deploy the githubWebhookServer under a service account | true | -| `githubWebhookServer.serviceAccount.annotations` | Set annotations for the service account | | -| `githubWebhookServer.serviceAccount.name` | Set the service account name | | -| `githubWebhookServer.podAnnotations` | Set annotations for the githubWebhookServer pod | | -| `githubWebhookServer.podLabels` | Set labels for the githubWebhookServer pod | | -| `githubWebhookServer.podSecurityContext` | Set the security context to githubWebhookServer pod | | -| `githubWebhookServer.securityContext` | Set the security context for each container in the githubWebhookServer pod | | -| `githubWebhookServer.resources` | Set the githubWebhookServer pod resources | | -| `githubWebhookServer.topologySpreadConstraints` | Set the githubWebhookServer pod topologySpreadConstraints | | -| `githubWebhookServer.nodeSelector` | Set the githubWebhookServer pod nodeSelector | | -| `githubWebhookServer.tolerations` | Set the githubWebhookServer pod tolerations | | -| `githubWebhookServer.affinity` | Set the githubWebhookServer pod affinity rules | | -| `githubWebhookServer.priorityClassName` | Set the githubWebhookServer pod priorityClassName | | -| `githubWebhookServer.service.type` | Set githubWebhookServer service type | | -| `githubWebhookServer.service.ports` | Set githubWebhookServer service ports | `[{"port":80, "targetPort:"http", "protocol":"TCP", "name":"http"}]` | -| `githubWebhookServer.ingress.enabled` | Deploy an ingress kind for the githubWebhookServer | false | -| `githubWebhookServer.ingress.annotations` | Set annotations for the ingress kind | | -| `githubWebhookServer.ingress.hosts` | Set hosts configuration for ingress | `[{"host": "chart-example.local", "paths": []}]` | -| `githubWebhookServer.ingress.tls` | Set tls configuration for ingress | | -| `githubWebhookServer.ingress.ingressClassName` | Set ingress class name | | -| `githubWebhookServer.podDisruptionBudget.enabled` | Enables a PDB to ensure HA of githubwebhook pods | false | -| `githubWebhookServer.podDisruptionBudget.minAvailable` | Minimum number of pods that must be available after eviction | | -| `githubWebhookServer.podDisruptionBudget.maxUnavailable` | Maximum number of pods that can be unavailable after eviction. Kubernetes 1.7+ required. | | +| Key | Description | Default | +|----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| +| `labels` | Set labels to apply to all resources in the chart | | +| `replicaCount` | Set the number of controller pods | 1 | +| `webhookPort` | Set the containerPort for the webhook Pod | 9443 | +| `syncPeriod` | Set the period in which the controller reconciles the desired runners count | 1m | +| `enableLeaderElection` | Enable election configuration | true | +| `leaderElectionId` | Set the election ID for the controller group | | +| `githubEnterpriseServerURL` | Set the URL for a self-hosted GitHub Enterprise Server | | +| `githubURL` | Override GitHub URL to be used for GitHub API calls | | +| `githubUploadURL` | Override GitHub Upload URL to be used for GitHub API calls | | +| `runnerGithubURL` | Override GitHub URL to be used by runners during registration | | +| `logLevel` | Set the log level of the controller container | | +| `logFormat` | Set the log format of the controller. Valid options are "text" and "json" | text | +| `additionalVolumes` | Set additional volumes to add to the manager container | | +| `additionalVolumeMounts` | Set additional volume mounts to add to the manager container | | +| `authSecret.create` | Deploy the controller auth secret | false | +| `authSecret.name` | Set the name of the auth secret | controller-manager | +| `authSecret.annotations` | Set annotations for the auth Secret | | +| `authSecret.github_app_id` | The ID of your GitHub App. **This can't be set at the same time as `authSecret.github_token`** | | +| `authSecret.github_app_installation_id` | The ID of your GitHub App installation. **This can't be set at the same time as `authSecret.github_token`** | | +| `authSecret.github_app_private_key` | The multiline string of your GitHub App's private key. **This can't be set at the same time as `authSecret.github_token`** | | +| `authSecret.github_token` | Your chosen GitHub PAT token. **This can't be set at the same time as the `authSecret.github_app_*`** | | +| `authSecret.github_basicauth_username` | Username for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | | +| `authSecret.github_basicauth_password` | Password for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | | +| `dockerRegistryMirror` | The default Docker Registry Mirror used by runners. | | +| `hostNetwork` | The "hostNetwork" of the controller container | false | +| `image.repository` | The "repository/image" of the controller container | summerwind/actions-runner-controller | +| `image.tag` | The tag of the controller container | | +| `image.actionsRunnerRepositoryAndTag` | The "repository/image" of the actions runner container | summerwind/actions-runner:latest | +| `image.actionsRunnerImagePullSecrets` | Optional image pull secrets to be included in the runner pod's ImagePullSecrets | | +| `image.dindSidecarRepositoryAndTag` | The "repository/image" of the dind sidecar container | docker:dind | +| `image.pullPolicy` | The pull policy of the controller image | IfNotPresent | +| `metrics.serviceMonitor` | Deploy serviceMonitor kind for for use with prometheus-operator CRDs | false | +| `metrics.serviceAnnotations` | Set annotations for the provisioned metrics service resource | | +| `metrics.port` | Set port of metrics service | 8443 | +| `metrics.proxy.enabled` | Deploy kube-rbac-proxy container in controller pod | true | +| `metrics.proxy.image.repository` | The "repository/image" of the kube-proxy container | quay.io/brancz/kube-rbac-proxy | +| `metrics.proxy.image.tag` | The tag of the kube-proxy image to use when pulling the container | v0.10.0 | +| `metrics.serviceMonitorLabels` | Set labels to apply to ServiceMonitor resources | | +| `imagePullSecrets` | Specifies the secret to be used when pulling the controller pod containers | | +| `fullnameOverride` | Override the full resource names | | +| `nameOverride` | Override the resource name prefix | | +| `serviceAccount.annotations` | Set annotations to the service account | | +| `serviceAccount.create` | Deploy the controller pod under a service account | true | +| `podAnnotations` | Set annotations for the controller pod | | +| `podLabels` | Set labels for the controller pod | | +| `serviceAccount.name` | Set the name of the service account | | +| `securityContext` | Set the security context for each container in the controller pod | | +| `podSecurityContext` | Set the security context to controller pod | | +| `service.annotations` | Set annotations for the provisioned webhook service resource | | +| `service.port` | Set controller service ports | | +| `service.type` | Set controller service type | | +| `topologySpreadConstraints` | Set the controller pod topologySpreadConstraints | | +| `nodeSelector` | Set the controller pod nodeSelector | | +| `resources` | Set the controller pod resources | | +| `affinity` | Set the controller pod affinity rules | | +| `podDisruptionBudget.enabled` | Enables a PDB to ensure HA of controller pods | false | +| `podDisruptionBudget.minAvailable` | Minimum number of pods that must be available after eviction | | +| `podDisruptionBudget.maxUnavailable` | Maximum number of pods that can be unavailable after eviction. Kubernetes 1.7+ required. | | +| `tolerations` | Set the controller pod tolerations | | +| `env` | Set environment variables for the controller container | | +| `priorityClassName` | Set the controller pod priorityClassName | | +| `scope.watchNamespace` | Tells the controller and the github webhook server which namespace to watch if `scope.singleNamespace` is true | `Release.Namespace` (the default namespace of the helm chart). | +| `scope.singleNamespace` | Limit the controller to watch a single namespace | false | +| `certManagerEnabled` | Enable cert-manager. If disabled you must set admissionWebHooks.caBundle and create TLS secrets manually | true | +| `runner.statusUpdateHook.enabled` | Use custom RBAC for runners (role, role binding and service account), this will enable reporting runner statuses | false | +| `admissionWebHooks.caBundle` | Base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate | | +| `githubWebhookServer.logLevel` | Set the log level of the githubWebhookServer container | | +| `githubWebhookServer.logFormat` | Set the log format of the githubWebhookServer controller. Valid options are "text" and "json" | text | +| `githubWebhookServer.replicaCount` | Set the number of webhook server pods | 1 | +| `githubWebhookServer.useRunnerGroupsVisibility` | Enable supporting runner groups with custom visibility, you also need to set `githubWebhookServer.secret.enabled` to enable this feature. | false | +| `githubWebhookServer.enabled` | Deploy the webhook server pod | false | +| `githubWebhookServer.queueLimit` | Set the queue size limit in the githubWebhookServer | | +| `githubWebhookServer.secret.enabled` | Passes the webhook hook secret to the github-webhook-server | false | +| `githubWebhookServer.secret.create` | Deploy the webhook hook secret | false | +| `githubWebhookServer.secret.name` | Set the name of the webhook hook secret | github-webhook-server | +| `githubWebhookServer.secret.github_webhook_secret_token` | Set the webhook secret token value | | +| `githubWebhookServer.imagePullSecrets` | Specifies the secret to be used when pulling the githubWebhookServer pod containers | | +| `githubWebhookServer.nameOverride` | Override the resource name prefix | | +| `githubWebhookServer.fullnameOverride` | Override the full resource names | | +| `githubWebhookServer.serviceAccount.create` | Deploy the githubWebhookServer under a service account | true | +| `githubWebhookServer.serviceAccount.annotations` | Set annotations for the service account | | +| `githubWebhookServer.serviceAccount.name` | Set the service account name | | +| `githubWebhookServer.podAnnotations` | Set annotations for the githubWebhookServer pod | | +| `githubWebhookServer.podLabels` | Set labels for the githubWebhookServer pod | | +| `githubWebhookServer.podSecurityContext` | Set the security context to githubWebhookServer pod | | +| `githubWebhookServer.securityContext` | Set the security context for each container in the githubWebhookServer pod | | +| `githubWebhookServer.resources` | Set the githubWebhookServer pod resources | | +| `githubWebhookServer.topologySpreadConstraints` | Set the githubWebhookServer pod topologySpreadConstraints | | +| `githubWebhookServer.nodeSelector` | Set the githubWebhookServer pod nodeSelector | | +| `githubWebhookServer.tolerations` | Set the githubWebhookServer pod tolerations | | +| `githubWebhookServer.affinity` | Set the githubWebhookServer pod affinity rules | | +| `githubWebhookServer.priorityClassName` | Set the githubWebhookServer pod priorityClassName | | +| `githubWebhookServer.service.type` | Set githubWebhookServer service type | | +| `githubWebhookServer.service.ports` | Set githubWebhookServer service ports | `[{"port":80, "targetPort:"http", "protocol":"TCP", "name":"http"}]` | +| `githubWebhookServer.ingress.enabled` | Deploy an ingress kind for the githubWebhookServer | false | +| `githubWebhookServer.ingress.annotations` | Set annotations for the ingress kind | | +| `githubWebhookServer.ingress.hosts` | Set hosts configuration for ingress | `[{"host": "chart-example.local", "paths": []}]` | +| `githubWebhookServer.ingress.tls` | Set tls configuration for ingress | | +| `githubWebhookServer.ingress.ingressClassName` | Set ingress class name | | +| `githubWebhookServer.podDisruptionBudget.enabled` | Enables a PDB to ensure HA of githubwebhook pods | false | +| `githubWebhookServer.podDisruptionBudget.minAvailable` | Minimum number of pods that must be available after eviction | | +| `githubWebhookServer.podDisruptionBudget.maxUnavailable` | Maximum number of pods that can be unavailable after eviction. Kubernetes 1.7+ required. | | diff --git a/controllers/horizontal_runner_autoscaler_batch_scale.go b/controllers/horizontal_runner_autoscaler_batch_scale.go index f6317ba553..e797da3424 100644 --- a/controllers/horizontal_runner_autoscaler_batch_scale.go +++ b/controllers/horizontal_runner_autoscaler_batch_scale.go @@ -190,7 +190,7 @@ func (s *batchScaler) batchScale(ctx context.Context, batch batchScaleOperation) after := len(copy.Spec.CapacityReservations) s.Log.V(1).Info( - fmt.Sprintf("Updating hra %s for capacityReservations update", hra.Name), + fmt.Sprintf("Patching hra %s for capacityReservations update", hra.Name), "before", before, "expired", expired, "added", added, @@ -198,8 +198,8 @@ func (s *batchScaler) batchScale(ctx context.Context, batch batchScaleOperation) "after", after, ) - if err := s.Client.Update(ctx, copy); err != nil { - return fmt.Errorf("updating horizontalrunnerautoscaler to add capacity reservation: %w", err) + if err := s.Client.Patch(ctx, copy, client.MergeFrom(&hra)); err != nil { + return fmt.Errorf("patching horizontalrunnerautoscaler to add capacity reservation: %w", err) } return nil diff --git a/controllers/horizontal_runner_autoscaler_webhook.go b/controllers/horizontal_runner_autoscaler_webhook.go index 0a96939078..58c1f7a1e4 100644 --- a/controllers/horizontal_runner_autoscaler_webhook.go +++ b/controllers/horizontal_runner_autoscaler_webhook.go @@ -175,56 +175,6 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) Handle(w http.Respons enterpriseSlug := enterpriseEvent.Enterprise.Slug switch e := event.(type) { - case *gogithub.PushEvent: - target, err = autoscaler.getScaleUpTarget( - context.TODO(), - log, - e.Repo.GetName(), - e.Repo.Owner.GetLogin(), - e.Repo.Owner.GetType(), - // Most go-github Event types don't seem to contain Enteprirse(.Slug) fields - // we need, so we parse it by ourselves. - enterpriseSlug, - autoscaler.MatchPushEvent(e), - ) - case *gogithub.PullRequestEvent: - target, err = autoscaler.getScaleUpTarget( - context.TODO(), - log, - e.Repo.GetName(), - e.Repo.Owner.GetLogin(), - e.Repo.Owner.GetType(), - // Most go-github Event types don't seem to contain Enteprirse(.Slug) fields - // we need, so we parse it by ourselves. - enterpriseSlug, - autoscaler.MatchPullRequestEvent(e), - ) - - if pullRequest := e.PullRequest; pullRequest != nil { - log = log.WithValues( - "pullRequest.base.ref", e.PullRequest.Base.GetRef(), - "action", e.GetAction(), - ) - } - case *gogithub.CheckRunEvent: - target, err = autoscaler.getScaleUpTarget( - context.TODO(), - log, - e.Repo.GetName(), - e.Repo.Owner.GetLogin(), - e.Repo.Owner.GetType(), - // Most go-github Event types don't seem to contain Enteprirse(.Slug) fields - // we need, so we parse it by ourselves. - enterpriseSlug, - autoscaler.MatchCheckRunEvent(e), - ) - - if checkRun := e.GetCheckRun(); checkRun != nil { - log = log.WithValues( - "checkRun.status", checkRun.GetStatus(), - "action", e.GetAction(), - ) - } case *gogithub.WorkflowJobEvent: if workflowJob := e.GetWorkflowJob(); workflowJob != nil { log = log.WithValues( diff --git a/controllers/horizontal_runner_autoscaler_webhook_on_check_run.go b/controllers/horizontal_runner_autoscaler_webhook_on_check_run.go deleted file mode 100644 index 1b30cced51..0000000000 --- a/controllers/horizontal_runner_autoscaler_webhook_on_check_run.go +++ /dev/null @@ -1,53 +0,0 @@ -package controllers - -import ( - "github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1" - "github.com/actions-runner-controller/actions-runner-controller/pkg/actionsglob" - "github.com/google/go-github/v47/github" -) - -func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchCheckRunEvent(event *github.CheckRunEvent) func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool { - return func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool { - g := scaleUpTrigger.GitHubEvent - - if g == nil { - return false - } - - cr := g.CheckRun - - if cr == nil { - return false - } - - if !matchTriggerConditionAgainstEvent(cr.Types, event.Action) { - return false - } - - if cr.Status != "" && (event.CheckRun == nil || event.CheckRun.Status == nil || *event.CheckRun.Status != cr.Status) { - return false - } - - if checkRun := event.CheckRun; checkRun != nil && len(cr.Names) > 0 { - for _, pat := range cr.Names { - if r := actionsglob.Match(pat, checkRun.GetName()); r { - return true - } - } - - return false - } - - if len(scaleUpTrigger.GitHubEvent.CheckRun.Repositories) > 0 { - for _, repository := range scaleUpTrigger.GitHubEvent.CheckRun.Repositories { - if repository == *event.Repo.Name { - return true - } - } - - return false - } - - return true - } -} diff --git a/controllers/horizontal_runner_autoscaler_webhook_on_pull_request.go b/controllers/horizontal_runner_autoscaler_webhook_on_pull_request.go deleted file mode 100644 index 892229d3de..0000000000 --- a/controllers/horizontal_runner_autoscaler_webhook_on_pull_request.go +++ /dev/null @@ -1,32 +0,0 @@ -package controllers - -import ( - "github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1" - "github.com/google/go-github/v47/github" -) - -func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchPullRequestEvent(event *github.PullRequestEvent) func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool { - return func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool { - g := scaleUpTrigger.GitHubEvent - - if g == nil { - return false - } - - pr := g.PullRequest - - if pr == nil { - return false - } - - if !matchTriggerConditionAgainstEvent(pr.Types, event.Action) { - return false - } - - if !matchTriggerConditionAgainstEvent(pr.Branches, event.PullRequest.Base.Ref) { - return false - } - - return true - } -} diff --git a/controllers/horizontal_runner_autoscaler_webhook_on_push.go b/controllers/horizontal_runner_autoscaler_webhook_on_push.go deleted file mode 100644 index 675f353d30..0000000000 --- a/controllers/horizontal_runner_autoscaler_webhook_on_push.go +++ /dev/null @@ -1,20 +0,0 @@ -package controllers - -import ( - "github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1" - "github.com/google/go-github/v47/github" -) - -func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchPushEvent(event *github.PushEvent) func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool { - return func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool { - g := scaleUpTrigger.GitHubEvent - - if g == nil { - return false - } - - push := g.Push - - return push != nil - } -} diff --git a/controllers/horizontal_runner_autoscaler_webhook_test.go b/controllers/horizontal_runner_autoscaler_webhook_test.go index d1a336e600..e231bd5254 100644 --- a/controllers/horizontal_runner_autoscaler_webhook_test.go +++ b/controllers/horizontal_runner_autoscaler_webhook_test.go @@ -30,78 +30,6 @@ func init() { _ = actionsv1alpha1.AddToScheme(sc) } -func TestOrgWebhookCheckRun(t *testing.T) { - f, err := os.Open("testdata/org_webhook_check_run_payload.json") - if err != nil { - t.Fatalf("could not open the fixture: %s", err) - } - defer f.Close() - var e github.CheckRunEvent - if err := json.NewDecoder(f).Decode(&e); err != nil { - t.Fatalf("invalid json: %s", err) - } - testServer(t, - "check_run", - &e, - 200, - "no horizontalrunnerautoscaler to scale for this github event", - ) -} - -func TestRepoWebhookCheckRun(t *testing.T) { - f, err := os.Open("testdata/repo_webhook_check_run_payload.json") - if err != nil { - t.Fatalf("could not open the fixture: %s", err) - } - defer f.Close() - var e github.CheckRunEvent - if err := json.NewDecoder(f).Decode(&e); err != nil { - t.Fatalf("invalid json: %s", err) - } - testServer(t, - "check_run", - &e, - 200, - "no horizontalrunnerautoscaler to scale for this github event", - ) -} - -func TestWebhookPullRequest(t *testing.T) { - testServer(t, - "pull_request", - &github.PullRequestEvent{ - PullRequest: &github.PullRequest{ - Base: &github.PullRequestBranch{ - Ref: github.String("main"), - }, - }, - Repo: &github.Repository{ - Name: github.String("myorg/myrepo"), - Organization: &github.Organization{ - Name: github.String("myorg"), - }, - }, - Action: github.String("created"), - }, - 200, - "no horizontalrunnerautoscaler to scale for this github event", - ) -} - -func TestWebhookPush(t *testing.T) { - testServer(t, - "push", - &github.PushEvent{ - Repo: &github.PushEventRepository{ - Name: github.String("myrepo"), - Organization: github.String("myorg"), - }, - }, - 200, - "no horizontalrunnerautoscaler to scale for this github event", - ) -} - func TestWebhookPing(t *testing.T) { testServer(t, "ping", diff --git a/controllers/integration_test.go b/controllers/integration_test.go index a0d7d22cec..614e20ff7a 100644 --- a/controllers/integration_test.go +++ b/controllers/integration_test.go @@ -192,885 +192,7 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() { Describe("when no existing resources exist", func() { - It("should create and scale organizational runners without any scaling metrics on pull_request event", func() { - name := "example-runnerdeploy" - - { - rd := &actionsv1alpha1.RunnerDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.RunnerDeploymentSpec{ - Replicas: intPtr(1), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "foo": "bar", - }, - }, - Template: actionsv1alpha1.RunnerTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - Spec: actionsv1alpha1.RunnerSpec{ - RunnerConfig: actionsv1alpha1.RunnerConfig{ - Organization: "test", - Image: "bar", - Group: "baz", - }, - RunnerPodSpec: actionsv1alpha1.RunnerPodSpec{ - Env: []corev1.EnvVar{ - {Name: "FOO", Value: "FOOVALUE"}, - }, - }, - }, - }, - }, - } - - ExpectCreate(ctx, rd, "test RunnerDeployment") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - } - - // Scale-up to 2 replicas - { - hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ - ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ - Name: name, - }, - MinReplicas: intPtr(2), - MaxReplicas: intPtr(5), - ScaleDownDelaySecondsAfterScaleUp: intPtr(1), - Metrics: nil, - ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{ - { - GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{ - PullRequest: &actionsv1alpha1.PullRequestSpec{ - Types: []string{"created"}, - Branches: []string{"main"}, - }, - }, - Amount: 1, - Duration: metav1.Duration{Duration: time.Minute}, - }, - }, - }, - } - - ExpectCreate(ctx, hra, "test HorizontalRunnerAutoscaler") - - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2) - } - - { - env.ExpectRegisteredNumberCountEventuallyEquals(2, "count of fake runners after HRA creation") - } - - // Scale-up to 3 replicas on second pull_request create webhook event - { - env.SendOrgPullRequestEvent("test", "valid", "main", "created") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3, "runners after second webhook event") - } - }) - - It("should create and scale organization's repository runners on pull_request event", func() { - name := "example-runnerdeploy" - - { - rd := &actionsv1alpha1.RunnerDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.RunnerDeploymentSpec{ - Replicas: intPtr(1), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "foo": "bar", - }, - }, - Template: actionsv1alpha1.RunnerTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - Spec: actionsv1alpha1.RunnerSpec{ - RunnerConfig: actionsv1alpha1.RunnerConfig{ - Repository: "test/valid", - Image: "bar", - Group: "baz", - }, - RunnerPodSpec: actionsv1alpha1.RunnerPodSpec{ - Env: []corev1.EnvVar{ - {Name: "FOO", Value: "FOOVALUE"}, - }, - }, - }, - }, - }, - } - - ExpectCreate(ctx, rd, "test RunnerDeployment") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - } - - { - ExpectRunnerDeploymentEventuallyUpdates(ctx, ns.Name, name, func(rd *actionsv1alpha1.RunnerDeployment) { - rd.Spec.Replicas = intPtr(2) - }) - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2) - } - - // Scale-up to 3 replicas - { - hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ - ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ - Name: name, - }, - MinReplicas: intPtr(1), - MaxReplicas: intPtr(3), - ScaleDownDelaySecondsAfterScaleUp: intPtr(1), - Metrics: []actionsv1alpha1.MetricSpec{ - { - Type: actionsv1alpha1.AutoscalingMetricTypeTotalNumberOfQueuedAndInProgressWorkflowRuns, - }, - }, - ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{ - { - GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{ - PullRequest: &actionsv1alpha1.PullRequestSpec{ - Types: []string{"created"}, - Branches: []string{"main"}, - }, - }, - Amount: 1, - Duration: metav1.Duration{Duration: time.Minute}, - }, - }, - }, - } - - ExpectCreate(ctx, hra, "test HorizontalRunnerAutoscaler") - - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3) - } - - { - env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake runners after HRA creation") - } - - // Scale-down to 1 replica - { - time.Sleep(time.Second) - - env.Responses.ListRepositoryWorkflowRuns.Body = workflowRunsFor1Replicas - env.Responses.ListRepositoryWorkflowRuns.Statuses["queued"] = workflowRunsFor1Replicas_queued - env.Responses.ListRepositoryWorkflowRuns.Statuses["in_progress"] = workflowRunsFor1Replicas_in_progress - - var hra actionsv1alpha1.HorizontalRunnerAutoscaler - - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns.Name, Name: name}, &hra) - - Expect(err).NotTo(HaveOccurred(), "failed to get test HorizontalRunnerAutoscaler resource") - - hra.Annotations = map[string]string{ - "force-update": "1", - } - - err = k8sClient.Update(ctx, &hra) - - Expect(err).NotTo(HaveOccurred(), "failed to get test HorizontalRunnerAutoscaler resource") - - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1, "runners after HRA force update for scale-down") - } - - // Scale-up to 2 replicas on first pull_request create webhook event - { - env.SendOrgPullRequestEvent("test", "valid", "main", "created") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2, "runners after first webhook event") - } - - // Scale-up to 3 replicas on second pull_request create webhook event - { - env.SendOrgPullRequestEvent("test", "valid", "main", "created") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3, "runners after second webhook event") - } - }) - - It("should create and scale organization's repository runners on check_run event", func() { - name := "example-runnerdeploy" - - { - rd := &actionsv1alpha1.RunnerDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.RunnerDeploymentSpec{ - Replicas: intPtr(1), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "foo": "bar", - }, - }, - Template: actionsv1alpha1.RunnerTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - Spec: actionsv1alpha1.RunnerSpec{ - RunnerConfig: actionsv1alpha1.RunnerConfig{ - Repository: "test/valid", - Image: "bar", - Group: "baz", - }, - RunnerPodSpec: actionsv1alpha1.RunnerPodSpec{ - Env: []corev1.EnvVar{ - {Name: "FOO", Value: "FOOVALUE"}, - }, - }, - }, - }, - }, - } - - ExpectCreate(ctx, rd, "test RunnerDeployment") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - env.ExpectRegisteredNumberCountEventuallyEquals(1, "count of fake list runners") - } - - // Scale-up to 3 replicas by the default TotalNumberOfQueuedAndInProgressWorkflowRuns-based scaling - // See workflowRunsFor3Replicas_queued and workflowRunsFor3Replicas_in_progress for GitHub List-Runners API responses - // used while testing. - { - hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ - ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ - Name: name, - }, - MinReplicas: intPtr(1), - MaxReplicas: intPtr(5), - ScaleDownDelaySecondsAfterScaleUp: intPtr(1), - Metrics: []actionsv1alpha1.MetricSpec{ - { - Type: actionsv1alpha1.AutoscalingMetricTypeTotalNumberOfQueuedAndInProgressWorkflowRuns, - }, - }, - ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{ - { - GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{ - CheckRun: &actionsv1alpha1.CheckRunSpec{ - Types: []string{"created"}, - Status: "pending", - Repositories: []string{"valid", "foo", "bar"}, - }, - }, - Amount: 1, - Duration: metav1.Duration{Duration: time.Minute}, - }, - }, - }, - } - - ExpectCreate(ctx, hra, "test HorizontalRunnerAutoscaler") - - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3) - env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake list runners") - env.SyncRunnerRegistrations() - ExpectRunnerCountEventuallyEquals(ctx, ns.Name, 3) - } - - // Scale-up to 4 replicas on first check_run create webhook event - { - env.SendOrgCheckRunEvent("test", "valid", "pending", "created") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 4, "runners after first webhook event") - env.ExpectRegisteredNumberCountEventuallyEquals(4, "count of fake list runners") - env.SyncRunnerRegistrations() - ExpectRunnerCountEventuallyEquals(ctx, ns.Name, 4) - } - - // Scale-up to 5 replicas on second check_run create webhook event - replicasAfterSecondWebhook := 5 - { - env.SendOrgCheckRunEvent("test", "valid", "pending", "created") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, replicasAfterSecondWebhook, "runners after second webhook event") - env.ExpectRegisteredNumberCountEventuallyEquals(replicasAfterSecondWebhook, "count of fake list runners") - env.SyncRunnerRegistrations() - ExpectRunnerCountEventuallyEquals(ctx, ns.Name, replicasAfterSecondWebhook) - } - - // Do not scale-up on third check_run create webhook event - // example repo is not in specified in actionsv1alpha1.CheckRunSpec.Repositories - { - env.SendOrgCheckRunEvent("test", "example", "pending", "created") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, replicasAfterSecondWebhook, "runners after third webhook event") - env.ExpectRegisteredNumberCountEventuallyEquals(replicasAfterSecondWebhook, "count of fake list runners") - env.SyncRunnerRegistrations() - ExpectRunnerCountEventuallyEquals(ctx, ns.Name, replicasAfterSecondWebhook) - } - }) - - It("should create and scale organization's repository runners on workflow_job event", func() { - name := "example-runnerdeploy" - - { - rd := &actionsv1alpha1.RunnerDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.RunnerDeploymentSpec{ - Replicas: intPtr(1), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "foo": "bar", - }, - }, - Template: actionsv1alpha1.RunnerTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - Spec: actionsv1alpha1.RunnerSpec{ - RunnerConfig: actionsv1alpha1.RunnerConfig{ - Repository: "test/valid", - Image: "bar", - Group: "baz", - }, - RunnerPodSpec: actionsv1alpha1.RunnerPodSpec{ - Env: []corev1.EnvVar{ - {Name: "FOO", Value: "FOOVALUE"}, - }, - }, - }, - }, - }, - } - - ExpectCreate(ctx, rd, "test RunnerDeployment") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - env.ExpectRegisteredNumberCountEventuallyEquals(1, "count of fake list runners") - } - - // Scale-up to 1 replica via ScaleUpTriggers.GitHubEvent.WorkflowJob based scaling - { - hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ - ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ - Name: name, - }, - MinReplicas: intPtr(1), - MaxReplicas: intPtr(5), - ScaleDownDelaySecondsAfterScaleUp: intPtr(1), - ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{ - { - GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{ - WorkflowJob: &actionsv1alpha1.WorkflowJobSpec{}, - }, - Amount: 1, - Duration: metav1.Duration{Duration: time.Minute}, - }, - }, - }, - } - - ExpectCreate(ctx, hra, "test HorizontalRunnerAutoscaler") - - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - env.ExpectRegisteredNumberCountEventuallyEquals(1, "count of fake list runners") - } - - // Scale-up to 2 replicas on first workflow_job.queued webhook event - { - env.SendWorkflowJobEvent("test", "valid", "queued", []string{"self-hosted"}, int64(1234), int64(4321)) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2, "runners after first webhook event") - env.ExpectRegisteredNumberCountEventuallyEquals(2, "count of fake list runners") - } - - // Scale-up to 3 replicas on second workflow_job.queued webhook event - { - env.SendWorkflowJobEvent("test", "valid", "queued", []string{"self-hosted"}, int64(1234), int64(4321)) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3, "runners after second webhook event") - env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake list runners") - } - - // Do not scale-up on third workflow_job.queued webhook event - // repo "example" doesn't match our Spec - { - env.SendWorkflowJobEvent("test", "example", "queued", []string{"self-hosted"}, int64(1234), int64(4321)) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3, "runners after third webhook event") - env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake list runners") - } - }) - - It("should create and scale organization's repository runners only on check_run event", func() { - name := "example-runnerdeploy" - - { - rd := &actionsv1alpha1.RunnerDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.RunnerDeploymentSpec{ - Replicas: intPtr(1), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "foo": "bar", - }, - }, - Template: actionsv1alpha1.RunnerTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - Spec: actionsv1alpha1.RunnerSpec{ - RunnerConfig: actionsv1alpha1.RunnerConfig{ - Repository: "test/valid", - Image: "bar", - Group: "baz", - }, - RunnerPodSpec: actionsv1alpha1.RunnerPodSpec{ - Env: []corev1.EnvVar{ - {Name: "FOO", Value: "FOOVALUE"}, - }, - }, - }, - }, - }, - } - - ExpectCreate(ctx, rd, "test RunnerDeployment") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - env.ExpectRegisteredNumberCountEventuallyEquals(1, "count of fake list runners") - } - - // Scale-up to 1 replica via ScaleUpTriggers.GitHubEvent.CheckRun based scaling - { - hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ - ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ - Name: name, - }, - MinReplicas: intPtr(1), - MaxReplicas: intPtr(5), - ScaleDownDelaySecondsAfterScaleUp: intPtr(1), - ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{ - { - GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{ - CheckRun: &actionsv1alpha1.CheckRunSpec{ - Types: []string{"created"}, - Status: "pending", - }, - }, - Amount: 1, - Duration: metav1.Duration{Duration: time.Minute}, - }, - }, - }, - } - - ExpectCreate(ctx, hra, "test HorizontalRunnerAutoscaler") - - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - env.ExpectRegisteredNumberCountEventuallyEquals(1, "count of fake list runners") - } - - // Scale-up to 2 replicas on first check_run create webhook event - { - env.SendOrgCheckRunEvent("test", "valid", "pending", "created") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2, "runners after first webhook event") - env.ExpectRegisteredNumberCountEventuallyEquals(2, "count of fake list runners") - } - - // Scale-up to 3 replicas on second check_run create webhook event - { - env.SendOrgCheckRunEvent("test", "valid", "pending", "created") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3, "runners after second webhook event") - env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake list runners") - } - }) - - It("should create and scale user's repository runners on pull_request event", func() { - name := "example-runnerdeploy" - - { - rd := &actionsv1alpha1.RunnerDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.RunnerDeploymentSpec{ - Replicas: intPtr(1), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "foo": "bar", - }, - }, - Template: actionsv1alpha1.RunnerTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - Spec: actionsv1alpha1.RunnerSpec{ - RunnerConfig: actionsv1alpha1.RunnerConfig{ - Repository: "test/valid", - Image: "bar", - Group: "baz", - }, - RunnerPodSpec: actionsv1alpha1.RunnerPodSpec{ - Env: []corev1.EnvVar{ - {Name: "FOO", Value: "FOOVALUE"}, - }, - }, - }, - }, - }, - } - - ExpectCreate(ctx, rd, "test RunnerDeployment") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - } - - { - ExpectRunnerDeploymentEventuallyUpdates(ctx, ns.Name, name, func(rd *actionsv1alpha1.RunnerDeployment) { - rd.Spec.Replicas = intPtr(2) - }) - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2) - } - - // Scale-up to 3 replicas - { - hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ - ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ - Name: name, - }, - MinReplicas: intPtr(1), - MaxReplicas: intPtr(3), - ScaleDownDelaySecondsAfterScaleUp: intPtr(1), - Metrics: []actionsv1alpha1.MetricSpec{ - { - Type: actionsv1alpha1.AutoscalingMetricTypeTotalNumberOfQueuedAndInProgressWorkflowRuns, - }, - }, - ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{ - { - GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{ - PullRequest: &actionsv1alpha1.PullRequestSpec{ - Types: []string{"created"}, - Branches: []string{"main"}, - }, - }, - Amount: 1, - Duration: metav1.Duration{Duration: time.Minute}, - }, - }, - }, - } - - ExpectCreate(ctx, hra, "test HorizontalRunnerAutoscaler") - - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3) - } - - { - env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake runners after HRA creation") - } - - // Scale-down to 1 replica - { - time.Sleep(time.Second) - - env.Responses.ListRepositoryWorkflowRuns.Body = workflowRunsFor1Replicas - env.Responses.ListRepositoryWorkflowRuns.Statuses["queued"] = workflowRunsFor1Replicas_queued - env.Responses.ListRepositoryWorkflowRuns.Statuses["in_progress"] = workflowRunsFor1Replicas_in_progress - - var hra actionsv1alpha1.HorizontalRunnerAutoscaler - - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns.Name, Name: name}, &hra) - - Expect(err).NotTo(HaveOccurred(), "failed to get test HorizontalRunnerAutoscaler resource") - - hra.Annotations = map[string]string{ - "force-update": "1", - } - - err = k8sClient.Update(ctx, &hra) - - Expect(err).NotTo(HaveOccurred(), "failed to get test HorizontalRunnerAutoscaler resource") - - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1, "runners after HRA force update for scale-down") - ExpectHRADesiredReplicasEquals(ctx, ns.Name, name, 1, "runner deployment desired replicas") - } - - // Scale-up to 2 replicas on first pull_request create webhook event - { - env.SendUserPullRequestEvent("test", "valid", "main", "created") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2, "runners after first webhook event") - ExpectHRADesiredReplicasEquals(ctx, ns.Name, name, 2, "runner deployment desired replicas") - } - - // Scale-up to 3 replicas on second pull_request create webhook event - { - env.SendUserPullRequestEvent("test", "valid", "main", "created") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3, "runners after second webhook event") - ExpectHRADesiredReplicasEquals(ctx, ns.Name, name, 3, "runner deployment desired replicas") - } - }) - - It("should create and scale user's repository runners only on pull_request event", func() { - name := "example-runnerdeploy" - - { - rd := &actionsv1alpha1.RunnerDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.RunnerDeploymentSpec{ - Replicas: intPtr(1), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "foo": "bar", - }, - }, - Template: actionsv1alpha1.RunnerTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - Spec: actionsv1alpha1.RunnerSpec{ - RunnerConfig: actionsv1alpha1.RunnerConfig{ - Repository: "test/valid", - Image: "bar", - Group: "baz", - }, - RunnerPodSpec: actionsv1alpha1.RunnerPodSpec{ - Env: []corev1.EnvVar{ - {Name: "FOO", Value: "FOOVALUE"}, - }, - }, - }, - }, - }, - } - - ExpectCreate(ctx, rd, "test RunnerDeployment") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - } - - { - hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ - ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ - Name: name, - }, - MinReplicas: intPtr(1), - MaxReplicas: intPtr(3), - ScaleDownDelaySecondsAfterScaleUp: intPtr(1), - ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{ - { - GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{ - PullRequest: &actionsv1alpha1.PullRequestSpec{ - Types: []string{"created"}, - Branches: []string{"main"}, - }, - }, - Amount: 1, - Duration: metav1.Duration{Duration: time.Minute}, - }, - }, - }, - } - - ExpectCreate(ctx, hra, "test HorizontalRunnerAutoscaler") - - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - } - - { - env.ExpectRegisteredNumberCountEventuallyEquals(1, "count of fake runners after HRA creation") - } - - // Scale-up to 2 replicas on first pull_request create webhook event - { - env.SendUserPullRequestEvent("test", "valid", "main", "created") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2, "runners after first webhook event") - ExpectHRADesiredReplicasEquals(ctx, ns.Name, name, 2, "runner deployment desired replicas") - } - - // Scale-up to 3 replicas on second pull_request create webhook event - { - env.SendUserPullRequestEvent("test", "valid", "main", "created") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3, "runners after second webhook event") - ExpectHRADesiredReplicasEquals(ctx, ns.Name, name, 3, "runner deployment desired replicas") - } - }) - - It("should create and scale user's repository runners on check_run event", func() { - name := "example-runnerdeploy" - - { - rd := &actionsv1alpha1.RunnerDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.RunnerDeploymentSpec{ - Replicas: intPtr(1), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "foo": "bar", - }, - }, - Template: actionsv1alpha1.RunnerTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "foo": "bar", - }, - }, - Spec: actionsv1alpha1.RunnerSpec{ - RunnerConfig: actionsv1alpha1.RunnerConfig{ - Repository: "test/valid", - Image: "bar", - Group: "baz", - }, - RunnerPodSpec: actionsv1alpha1.RunnerPodSpec{ - Env: []corev1.EnvVar{ - {Name: "FOO", Value: "FOOVALUE"}, - }, - }, - }, - }, - }, - } - - ExpectCreate(ctx, rd, "test RunnerDeployment") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - env.ExpectRegisteredNumberCountEventuallyEquals(1, "count of fake list runners") - } - - // Scale-up to 3 replicas by the default TotalNumberOfQueuedAndInProgressWorkflowRuns-based scaling - // See workflowRunsFor3Replicas_queued and workflowRunsFor3Replicas_in_progress for GitHub List-Runners API responses - // used while testing. - { - hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns.Name, - }, - Spec: actionsv1alpha1.HorizontalRunnerAutoscalerSpec{ - ScaleTargetRef: actionsv1alpha1.ScaleTargetRef{ - Name: name, - }, - MinReplicas: intPtr(1), - MaxReplicas: intPtr(5), - ScaleDownDelaySecondsAfterScaleUp: intPtr(1), - Metrics: []actionsv1alpha1.MetricSpec{ - { - Type: actionsv1alpha1.AutoscalingMetricTypeTotalNumberOfQueuedAndInProgressWorkflowRuns, - }, - }, - ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{ - { - GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{ - CheckRun: &actionsv1alpha1.CheckRunSpec{ - Types: []string{"created"}, - Status: "pending", - }, - }, - Amount: 1, - Duration: metav1.Duration{Duration: time.Minute}, - }, - }, - }, - } - - ExpectCreate(ctx, hra, "test HorizontalRunnerAutoscaler") - - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3) - env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake list runners") - } - - // Scale-up to 4 replicas on first check_run create webhook event - { - env.SendUserCheckRunEvent("test", "valid", "pending", "created") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 4, "runners after first webhook event") - env.ExpectRegisteredNumberCountEventuallyEquals(4, "count of fake list runners") - } - - // Scale-up to 5 replicas on second check_run create webhook event - { - env.SendUserCheckRunEvent("test", "valid", "pending", "created") - ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 5, "runners after second webhook event") - env.ExpectRegisteredNumberCountEventuallyEquals(5, "count of fake list runners") - } - }) - - It("should create and scale user's repository runners only on check_run event", func() { + It("should create and scale organization's repository runners on workflow_job event", func() { name := "example-runnerdeploy" { @@ -1111,15 +233,10 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() { ExpectCreate(ctx, rd, "test RunnerDeployment") ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - } - - { env.ExpectRegisteredNumberCountEventuallyEquals(1, "count of fake list runners") } - // Scale-up to 3 replicas by the default TotalNumberOfQueuedAndInProgressWorkflowRuns-based scaling - // See workflowRunsFor3Replicas_queued and workflowRunsFor3Replicas_in_progress for GitHub List-Runners API responses - // used while testing. + // Scale-up to 1 replica via ScaleUpTriggers.GitHubEvent.WorkflowJob based scaling { hra := &actionsv1alpha1.HorizontalRunnerAutoscaler{ ObjectMeta: metav1.ObjectMeta{ @@ -1136,10 +253,7 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() { ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{ { GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{ - CheckRun: &actionsv1alpha1.CheckRunSpec{ - Types: []string{"created"}, - Status: "pending", - }, + WorkflowJob: &actionsv1alpha1.WorkflowJobSpec{}, }, Amount: 1, Duration: metav1.Duration{Duration: time.Minute}, @@ -1152,26 +266,30 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() { ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1) ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 1) - } - - { env.ExpectRegisteredNumberCountEventuallyEquals(1, "count of fake list runners") } - // Scale-up to 2 replicas on first check_run create webhook event + // Scale-up to 2 replicas on first workflow_job.queued webhook event { - env.SendUserCheckRunEvent("test", "valid", "pending", "created") - ExpectRunnerSetsCountEventuallyEquals(ctx, ns.Name, 1, "runner sets after webhook") + env.SendWorkflowJobEvent("test", "valid", "queued", []string{"self-hosted"}, int64(1234), int64(4321)) ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 2, "runners after first webhook event") env.ExpectRegisteredNumberCountEventuallyEquals(2, "count of fake list runners") } - // Scale-up to 3 replicas on second check_run create webhook event + // Scale-up to 3 replicas on second workflow_job.queued webhook event { - env.SendUserCheckRunEvent("test", "valid", "pending", "created") + env.SendWorkflowJobEvent("test", "valid", "queued", []string{"self-hosted"}, int64(1234), int64(4321)) ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3, "runners after second webhook event") env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake list runners") } + + // Do not scale-up on third workflow_job.queued webhook event + // repo "example" doesn't match our Spec + { + env.SendWorkflowJobEvent("test", "example", "queued", []string{"self-hosted"}, int64(1234), int64(4321)) + ExpectRunnerSetsManagedReplicasCountEventuallyEquals(ctx, ns.Name, 3, "runners after third webhook event") + env.ExpectRegisteredNumberCountEventuallyEquals(3, "count of fake list runners") + } }) It("should be able to scale visible organization runner group with default labels", func() { @@ -1370,51 +488,6 @@ func (env *testEnvironment) ExpectRegisteredNumberCountEventuallyEquals(want int time.Second*10, time.Millisecond*500).Should(Equal(want), optionalDescriptions...) } -func (env *testEnvironment) SendOrgPullRequestEvent(org, repo, branch, action string) { - resp, err := sendWebhook(env.webhookServer, "pull_request", &github.PullRequestEvent{ - PullRequest: &github.PullRequest{ - Base: &github.PullRequestBranch{ - Ref: github.String(branch), - }, - }, - Repo: &github.Repository{ - Name: github.String(repo), - Owner: &github.User{ - Login: github.String(org), - Type: github.String("Organization"), - }, - }, - Action: github.String(action), - }) - - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to send pull_request event") - - ExpectWithOffset(1, resp.StatusCode).To(Equal(200)) -} - -func (env *testEnvironment) SendOrgCheckRunEvent(org, repo, status, action string) { - resp, err := sendWebhook(env.webhookServer, "check_run", &github.CheckRunEvent{ - CheckRun: &github.CheckRun{ - Status: github.String(status), - }, - Org: &github.Organization{ - Login: github.String(org), - }, - Repo: &github.Repository{ - Name: github.String(repo), - Owner: &github.User{ - Login: github.String(org), - Type: github.String("Organization"), - }, - }, - Action: github.String(action), - }) - - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to send check_run event") - - ExpectWithOffset(1, resp.StatusCode).To(Equal(200)) -} - func (env *testEnvironment) SendWorkflowJobEvent(org, repo, statusAndAction string, labels []string, runID int64, ID int64) { resp, err := sendWebhook(env.webhookServer, "workflow_job", &github.WorkflowJobEvent{ WorkflowJob: &github.WorkflowJob{ @@ -1441,48 +514,6 @@ func (env *testEnvironment) SendWorkflowJobEvent(org, repo, statusAndAction stri ExpectWithOffset(1, resp.StatusCode).To(Equal(200)) } -func (env *testEnvironment) SendUserPullRequestEvent(owner, repo, branch, action string) { - resp, err := sendWebhook(env.webhookServer, "pull_request", &github.PullRequestEvent{ - PullRequest: &github.PullRequest{ - Base: &github.PullRequestBranch{ - Ref: github.String(branch), - }, - }, - Repo: &github.Repository{ - Name: github.String(repo), - Owner: &github.User{ - Login: github.String(owner), - Type: github.String("User"), - }, - }, - Action: github.String(action), - }) - - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to send pull_request event") - - ExpectWithOffset(1, resp.StatusCode).To(Equal(200)) -} - -func (env *testEnvironment) SendUserCheckRunEvent(owner, repo, status, action string) { - resp, err := sendWebhook(env.webhookServer, "check_run", &github.CheckRunEvent{ - CheckRun: &github.CheckRun{ - Status: github.String(status), - }, - Repo: &github.Repository{ - Name: github.String(repo), - Owner: &github.User{ - Login: github.String(owner), - Type: github.String("User"), - }, - }, - Action: github.String(action), - }) - - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to send check_run event") - - ExpectWithOffset(1, resp.StatusCode).To(Equal(200)) -} - func (env *testEnvironment) SyncRunnerRegistrations() { var runnerList actionsv1alpha1.RunnerList diff --git a/controllers/new_runner_pod_test.go b/controllers/new_runner_pod_test.go index 2d95601e6e..54fd75ada1 100644 --- a/controllers/new_runner_pod_test.go +++ b/controllers/new_runner_pod_test.go @@ -160,9 +160,7 @@ func TestNewRunnerPod(t *testing.T) { }, }, ImagePullPolicy: corev1.PullAlways, - SecurityContext: &corev1.SecurityContext{ - Privileged: func() *bool { v := false; return &v }(), - }, + SecurityContext: &corev1.SecurityContext{}, }, { Name: "docker", @@ -366,9 +364,7 @@ func TestNewRunnerPod(t *testing.T) { }, }, ImagePullPolicy: corev1.PullAlways, - SecurityContext: &corev1.SecurityContext{ - Privileged: boolPtr(false), - }, + SecurityContext: &corev1.SecurityContext{}, }, }, RestartPolicy: corev1.RestartPolicyNever, @@ -690,9 +686,7 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) { }, }, ImagePullPolicy: corev1.PullAlways, - SecurityContext: &corev1.SecurityContext{ - Privileged: func() *bool { v := false; return &v }(), - }, + SecurityContext: &corev1.SecurityContext{}, }, { Name: "docker", @@ -930,9 +924,7 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) { }, }, ImagePullPolicy: corev1.PullAlways, - SecurityContext: &corev1.SecurityContext{ - Privileged: boolPtr(false), - }, + SecurityContext: &corev1.SecurityContext{}, }, }, RestartPolicy: corev1.RestartPolicyNever, diff --git a/controllers/runner_controller.go b/controllers/runner_controller.go index ae306edcbc..f217cd5961 100644 --- a/controllers/runner_controller.go +++ b/controllers/runner_controller.go @@ -849,10 +849,6 @@ func newRunnerPodWithContainerMode(containerMode string, template corev1.Pod, ru runnerContainerIndex = -1 runnerContainer = &corev1.Container{ Name: containerName, - SecurityContext: &corev1.SecurityContext{ - // Runner need to run privileged if it contains DinD - Privileged: &dockerdInRunnerPrivileged, - }, } } @@ -887,8 +883,10 @@ func newRunnerPodWithContainerMode(containerMode string, template corev1.Pod, ru runnerContainer.SecurityContext = &corev1.SecurityContext{} } - if runnerContainer.SecurityContext.Privileged == nil { - // Runner need to run privileged if it contains DinD + // Runner need to run privileged if it contains DinD. + // Do not explicitly set SecurityContext.Privileged to false which is default, + // otherwise Windows pods don't get admitted on GKE. + if dockerdInRunnerPrivileged { runnerContainer.SecurityContext.Privileged = &dockerdInRunnerPrivileged } diff --git a/docs/detailed-docs.md b/docs/detailed-docs.md index 57aa23e79b..0942a3f738 100644 --- a/docs/detailed-docs.md +++ b/docs/detailed-docs.md @@ -1573,9 +1573,26 @@ spec: template: spec: env: + # Disable various runner entrypoint log levels + - name: LOG_DEBUG_DISABLED + value: "true" + - name: LOG_NOTICE_DISABLED + value: "true" + - name: LOG_WARNING_DISABLED + value: "true" + - name: LOG_ERROR_DISABLED + value: "true" + - name: LOG_SUCCESS_DISABLED + value: "true" # Issues a sleep command at the start of the entrypoint - name: STARTUP_DELAY_IN_SECONDS value: "2" + # Specify the duration to wait for the docker daemon to be available + # The default duration of 120 seconds is sometimes too short + # to reliably wait for the docker daemon to start + # See https://github.com/actions-runner-controller/actions-runner-controller/issues/1804 + - name: WAIT_FOR_DOCKER_SECONDS + value: 120 # Disables the wait for the docker daemon to be available check - name: DISABLE_WAIT_FOR_DOCKER value: "true" @@ -1644,8 +1661,10 @@ The GitHub hosted runners include a large amount of pre-installed software packa This solution maintains a few runner images with `latest` aligning with GitHub's Ubuntu version, these images do not contain all of the software installed on the GitHub runners. The images contain the following subset of packages from the GitHub runners: - Basic CLI packages -- git -- docker +- Git +- Git LFS +- Docker +- Docker Compose - build-essentials The virtual environments from GitHub contain a lot more software packages (different versions of Java, Node.js, Golang, .NET, etc) which are not provided in the runner image. Most of these have dedicated setup actions which allow the tools to be installed on-demand in a workflow, for example: `actions/setup-java` or `actions/setup-node` @@ -1763,7 +1782,6 @@ spec: labels: - windows - X64 - - devops-managed ``` #### Dockerfile @@ -1821,7 +1839,6 @@ spec: labels: - linux - X64 - - devops-managed ```

diff --git a/go.mod b/go.mod index d3fb07d842..dfd4ca4ed8 100644 --- a/go.mod +++ b/go.mod @@ -19,9 +19,9 @@ require ( go.uber.org/zap v1.23.0 golang.org/x/oauth2 v0.2.0 gomodules.xyz/jsonpatch/v2 v2.2.0 - k8s.io/api v0.25.3 - k8s.io/apimachinery v0.25.3 - k8s.io/client-go v0.25.3 + k8s.io/api v0.25.4 + k8s.io/apimachinery v0.25.4 + k8s.io/client-go v0.25.4 sigs.k8s.io/controller-runtime v0.13.1 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index 9e15bfe669..936e6ff863 100644 --- a/go.sum +++ b/go.sum @@ -1104,6 +1104,8 @@ k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= k8s.io/api v0.25.3 h1:Q1v5UFfYe87vi5H7NU0p4RXC26PPMT8KOpr1TLQbCMQ= k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI= +k8s.io/api v0.25.4 h1:3YO8J4RtmG7elEgaWMb4HgmpS2CfY1QlaOz9nwB+ZSs= +k8s.io/api v0.25.4/go.mod h1:IG2+RzyPQLllQxnhzD8KQNEu4c4YvyDTpSMztf4A0OQ= k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k= k8s.io/apiextensions-apiserver v0.24.2/go.mod h1:e5t2GMFVngUEHUd0wuCJzw8YDwZoqZfJiGOW6mm2hLQ= k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY= @@ -1119,6 +1121,8 @@ k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= k8s.io/apimachinery v0.25.3 h1:7o9ium4uyUOM76t6aunP0nZuex7gDf8VGwkR5RcJnQc= k8s.io/apimachinery v0.25.3/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= +k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc= +k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= k8s.io/apiserver v0.24.2/go.mod h1:pSuKzr3zV+L+MWqsEo0kHHYwCo77AT5qXbFXP2jbvFI= k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= k8s.io/client-go v0.24.3 h1:Nl1840+6p4JqkFWEW2LnMKU667BUxw03REfLAVhuKQY= @@ -1131,6 +1135,8 @@ k8s.io/client-go v0.25.2 h1:SUPp9p5CwM0yXGQrwYurw9LWz+YtMwhWd0GqOsSiefo= k8s.io/client-go v0.25.2/go.mod h1:i7cNU7N+yGQmJkewcRD2+Vuj4iz7b30kI8OcL3horQ4= k8s.io/client-go v0.25.3 h1:oB4Dyl8d6UbfDHD8Bv8evKylzs3BXzzufLiO27xuPs0= k8s.io/client-go v0.25.3/go.mod h1:t39LPczAIMwycjcXkVc+CB+PZV69jQuNx4um5ORDjQA= +k8s.io/client-go v0.25.4 h1:3RNRDffAkNU56M/a7gUfXaEzdhZlYhoW8dgViGy5fn8= +k8s.io/client-go v0.25.4/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw= k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= k8s.io/component-base v0.24.2 h1:kwpQdoSfbcH+8MPN4tALtajLDfSfYxBDYlXobNWI6OU= k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM= diff --git a/runner/Makefile b/runner/Makefile index 930caf5592..249b2877b9 100644 --- a/runner/Makefile +++ b/runner/Makefile @@ -2,6 +2,7 @@ DOCKER_USER ?= summerwind DOCKER ?= docker NAME ?= ${DOCKER_USER}/actions-runner DIND_RUNNER_NAME ?= ${DOCKER_USER}/actions-runner-dind +DIND_ROOTLESS_RUNNER_NAME ?= ${DOCKER_USER}/actions-runner-dind-rootless TAG ?= latest TARGETPLATFORM ?= $(shell arch) @@ -26,7 +27,19 @@ else export PUSH_ARG="--push" endif -docker-build-ubuntu: +check-target-platform: +# Handle target platform variants. +# arch command on OS X reports "i386" for Intel CPUs regardless of bitness +ifeq ($(TARGETPLATFORM), $(filter $(TARGETPLATFORM), x86_64 x64 amd64 i386 linux/amd64)) + TARGETPLATFORM = linux/amd64 +else ifeq ($(TARGETPLATFORM), $(filter $(TARGETPLATFORM), arm64 aarch64 linux/arm64)) + TARGETPLATFORM = linux/arm64 +else + $(warning Unsupported target platform $(TARGETPLATFORM)) + $(error Supported target platforms: linux/amd64 and linux/arm64) +endif + +docker-build-ubuntu: check-target-platform ${DOCKER} build \ --build-arg TARGETPLATFORM=${TARGETPLATFORM} \ --build-arg RUNNER_VERSION=${RUNNER_VERSION} \ @@ -40,10 +53,17 @@ docker-build-ubuntu: --build-arg DOCKER_VERSION=${DOCKER_VERSION} \ -f actions-runner-dind.dockerfile \ -t ${DIND_RUNNER_NAME}:${TAG} . + ${DOCKER} build \ + --build-arg TARGETPLATFORM=${TARGETPLATFORM} \ + --build-arg RUNNER_VERSION=${RUNNER_VERSION} \ + --build-arg DOCKER_VERSION=${DOCKER_VERSION} \ + -f actions-runner-dind-rootless.dockerfile \ + -t ${DIND_ROOTLESS_RUNNER_NAME}:${TAG} . docker-push-ubuntu: ${DOCKER} push ${NAME}:${TAG} ${DOCKER} push ${DIND_RUNNER_NAME}:${TAG} + ${DOCKER} push ${DIND_ROOTLESS_RUNNER_NAME}:${TAG} docker-buildx-ubuntu: export DOCKER_CLI_EXPERIMENTAL=enabled ;\ @@ -65,3 +85,9 @@ docker-buildx-ubuntu: -f actions-runner-dind.dockerfile \ -t "${DIND_RUNNER_NAME}:${TAG}" \ . ${PUSH_ARG} + ${DOCKER} buildx build --platform ${PLATFORMS} \ + --build-arg RUNNER_VERSION=${RUNNER_VERSION} \ + --build-arg DOCKER_VERSION=${DOCKER_VERSION} \ + -f actions-runner-dind-rootless.dockerfile \ + -t "${DIND_ROOTLESS_RUNNER_NAME}:${TAG}" \ + . ${PUSH_ARG} diff --git a/runner/actions-runner-dind-rootless.dockerfile b/runner/actions-runner-dind-rootless.dockerfile index 6ac1fcc6db..3062963ac3 100644 --- a/runner/actions-runner-dind-rootless.dockerfile +++ b/runner/actions-runner-dind-rootless.dockerfile @@ -1,24 +1,19 @@ FROM ubuntu:20.04 -# Target architecture -ARG TARGETPLATFORM=linux/amd64 - -# GitHub runner arguments +ARG TARGETPLATFORM ARG RUNNER_VERSION=2.299.1 - +ARG RUNNER_CONTAINER_HOOKS_VERSION=0.1.2 # Docker and Docker Compose arguments ENV CHANNEL=stable -ARG COMPOSE_VERSION=v2.6.0 - -# Dumb-init version +ARG DOCKER_COMPOSE_VERSION=v2.6.0 ARG DUMB_INIT_VERSION=1.2.5 # Other arguments ARG DEBUG=false -# Set environment variables needed at build -ENV DEBIAN_FRONTEND=noninteractive +RUN test -n "$TARGETPLATFORM" || (echo "TARGETPLATFORM must be set" && false) +ENV DEBIAN_FRONTEND=noninteractive RUN apt update -y \ && apt-get install -y software-properties-common \ && add-apt-repository -y ppa:git-core/ppa \ @@ -63,57 +58,63 @@ RUN apt update -y \ # Runner user RUN adduser --disabled-password --gecos "" --uid 1000 runner -RUN test -n "$TARGETPLATFORM" || (echo "TARGETPLATFORM must be set" && false) +ENV HOME=/home/runner -# Setup subuid and subgid so that "--userns-remap=default" works +# Set-up subuid and subgid so that "--userns-remap=default" works RUN set -eux; \ addgroup --system dockremap; \ adduser --system --ingroup dockremap dockremap; \ echo 'dockremap:165536:65536' >> /etc/subuid; \ echo 'dockremap:165536:65536' >> /etc/subgid -ENV RUNNER_ASSETS_DIR=/runnertmp +RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ + && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ + && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ + && curl -fLo /usr/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_${ARCH} \ + && chmod +x /usr/bin/dumb-init -# Runner download supports amd64 as x64 -RUN ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ - && export ARCH \ - && if [ "$ARCH" = "amd64" ]; then export ARCH=x64 ; fi \ +ENV RUNNER_ASSETS_DIR=/runnertmp +RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ + && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x64 ; fi \ && mkdir -p "$RUNNER_ASSETS_DIR" \ && cd "$RUNNER_ASSETS_DIR" \ - && curl -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \ + && curl -fLo runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \ && tar xzf ./runner.tar.gz \ && rm runner.tar.gz \ && ./bin/installdependencies.sh \ + && mv ./externals ./externalstmp \ + # libyaml-dev is required for ruby/setup-ruby action. + # It is installed after installdependencies.sh and before removing /var/lib/apt/lists + # to avoid rerunning apt-update on its own. && apt-get install -y libyaml-dev \ && rm -rf /var/lib/apt/lists/* -RUN echo AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache > /runner.env \ - && mkdir /opt/hostedtoolcache \ +ENV RUNNER_TOOL_CACHE=/opt/hostedtoolcache +RUN mkdir /opt/hostedtoolcache \ && chgrp runner /opt/hostedtoolcache \ && chmod g+rwx /opt/hostedtoolcache -# Configure hooks folder structure. -COPY hooks /etc/arc/hooks/ +RUN cd "$RUNNER_ASSETS_DIR" \ + && curl -fLo runner-container-hooks.zip https://github.com/actions/runner-container-hooks/releases/download/v${RUNNER_CONTAINER_HOOKS_VERSION}/actions-runner-hooks-k8s-${RUNNER_CONTAINER_HOOKS_VERSION}.zip \ + && unzip ./runner-container-hooks.zip -d ./k8s \ + && rm -f runner-container-hooks.zip -# arch command on OS X reports "i386" for Intel CPUs regardless of bitness -RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ - && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ - && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ - && curl -f -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_${ARCH} \ - && chmod +x /usr/local/bin/dumb-init +# Make the rootless runner directory executable +RUN mkdir /run/user/1000 \ + && chown runner:runner /run/user/1000 \ + && chmod a+x /run/user/1000 +# We place the scripts in `/usr/bin` so that users who extend this image can +# override them with scripts of the same name placed in `/usr/local/bin`. COPY entrypoint-dind-rootless.sh startup.sh logger.sh graceful-stop.sh update-status /usr/bin/ - RUN chmod +x /usr/bin/entrypoint-dind-rootless.sh /usr/bin/startup.sh # Copy the docker shim which propagates the docker MTU to underlying networks # to replace the docker binary in the PATH. COPY docker-shim.sh /usr/local/bin/docker -# Make the rootless runner directory executable -RUN mkdir /run/user/1000 \ - && chown runner:runner /run/user/1000 \ - && chmod a+x /run/user/1000 +# Configure hooks folder structure. +COPY hooks /etc/arc/hooks/ # Add the Python "User Script Directory" to the PATH ENV PATH="${PATH}:${HOME}/.local/bin:/home/runner/bin" @@ -126,19 +127,18 @@ RUN echo "PATH=${PATH}" > /etc/environment \ && echo "DOCKER_HOST=${DOCKER_HOST}" >> /etc/environment \ && echo "XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR}" >> /etc/environment -ENV HOME=/home/runner - # No group definition, as that makes it harder to run docker. USER runner -# Docker installation -ENV SKIP_IPTABLES=1 # This will install docker under $HOME/bin according to the content of the script -RUN curl -fsSL https://get.docker.com/rootless | sh +RUN export SKIP_IPTABLES=1 \ + && curl -fsSL https://get.docker.com/rootless | sh -# Docker-compose installation -RUN curl -L "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-Linux-x86_64" -o /home/runner/bin/docker-compose ; \ - chmod +x /home/runner/bin/docker-compose +RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ + && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ + && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ + && curl -fLo /home/runner/bin/docker-compose https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-${ARCH} \ + && chmod +x /home/runner/bin/docker-compose ENTRYPOINT ["/bin/bash", "-c"] CMD ["entrypoint-dind-rootless.sh"] diff --git a/runner/actions-runner-dind.dockerfile b/runner/actions-runner-dind.dockerfile index fbcbc7fa9d..a46ea278e0 100644 --- a/runner/actions-runner-dind.dockerfile +++ b/runner/actions-runner-dind.dockerfile @@ -2,8 +2,11 @@ FROM ubuntu:20.04 ARG TARGETPLATFORM ARG RUNNER_VERSION=2.299.1 -ARG DOCKER_CHANNEL=stable +ARG RUNNER_CONTAINER_HOOKS_VERSION=0.1.2 +# Docker and Docker Compose arguments +ARG CHANNEL=stable ARG DOCKER_VERSION=20.10.18 +ARG DOCKER_COMPOSE_VERSION=v2.6.0 ARG DUMB_INIT_VERSION=1.2.5 RUN test -n "$TARGETPLATFORM" || (echo "TARGETPLATFORM must be set" && false) @@ -57,39 +60,26 @@ RUN adduser --disabled-password --gecos "" --uid 1000 runner \ && echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers \ && echo "Defaults env_keep += \"DEBIAN_FRONTEND\"" >> /etc/sudoers -# arch command on OS X reports "i386" for Intel CPUs regardless of bitness -# Docker download supports arm64 as aarch64 & amd64 / i386 as x86_64 +ENV HOME=/home/runner + RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ - && if ! curl -f -L -o docker.tgz "https://download.docker.com/linux/static/${DOCKER_CHANNEL}/${ARCH}/docker-${DOCKER_VERSION}.tgz"; then \ - echo >&2 "error: failed to download 'docker-${DOCKER_VERSION}' from '${DOCKER_CHANNEL}' for '${ARCH}'"; \ - exit 1; \ - fi; \ - echo "Downloaded Docker from https://download.docker.com/linux/static/${DOCKER_CHANNEL}/${ARCH}/docker-${DOCKER_VERSION}.tgz"; \ - tar --extract \ - --file docker.tgz \ - --strip-components 1 \ - --directory /usr/bin/ \ - ; \ - rm docker.tgz; \ - dockerd --version; \ - docker --version - -# Runner download supports amd64 as x64 -# -# libyaml-dev is required for ruby/setup-ruby action. -# It is installed after installdependencies.sh and before removing /var/lib/apt/lists -# to avoid rerunning apt-update on its own. + && curl -fLo /usr/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_${ARCH} \ + && chmod +x /usr/bin/dumb-init + ENV RUNNER_ASSETS_DIR=/runnertmp RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x64 ; fi \ && mkdir -p "$RUNNER_ASSETS_DIR" \ && cd "$RUNNER_ASSETS_DIR" \ - && curl -f -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \ + && curl -fLo runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \ && tar xzf ./runner.tar.gz \ - && rm runner.tar.gz \ + && rm -f runner.tar.gz \ && ./bin/installdependencies.sh \ + # libyaml-dev is required for ruby/setup-ruby action. + # It is installed after installdependencies.sh and before removing /var/lib/apt/lists + # to avoid rerunning apt-update on its own. && apt-get install -y libyaml-dev \ && rm -rf /var/lib/apt/lists/* @@ -98,6 +88,26 @@ RUN mkdir /opt/hostedtoolcache \ && chgrp docker /opt/hostedtoolcache \ && chmod g+rwx /opt/hostedtoolcache +RUN cd "$RUNNER_ASSETS_DIR" \ + && curl -fLo runner-container-hooks.zip https://github.com/actions/runner-container-hooks/releases/download/v${RUNNER_CONTAINER_HOOKS_VERSION}/actions-runner-hooks-k8s-${RUNNER_CONTAINER_HOOKS_VERSION}.zip \ + && unzip ./runner-container-hooks.zip -d ./k8s \ + && rm -f runner-container-hooks.zip + +RUN set -vx; \ + export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ + && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ + && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ + && curl -fLo docker.tgz https://download.docker.com/linux/static/${CHANNEL}/${ARCH}/docker-${DOCKER_VERSION}.tgz \ + && tar zxvf docker.tgz \ + && install -o root -g root -m 755 docker/* /usr/bin/ \ + && rm -rf docker docker.tgz + +RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ + && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ + && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ + && curl -fLo /usr/bin/docker-compose https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-${ARCH} \ + && chmod +x /usr/bin/docker-compose + # We place the scripts in `/usr/bin` so that users who extend this image can # override them with scripts of the same name placed in `/usr/local/bin`. COPY entrypoint-dind.sh startup.sh logger.sh wait.sh graceful-stop.sh update-status /usr/bin/ @@ -111,16 +121,8 @@ COPY docker-shim.sh /usr/local/bin/docker # Configure hooks folder structure. COPY hooks /etc/arc/hooks/ -# arch command on OS X reports "i386" for Intel CPUs regardless of bitness -RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ - && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ - && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ - && curl -f -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_${ARCH} \ - && chmod +x /usr/local/bin/dumb-init - VOLUME /var/lib/docker -ENV HOME=/home/runner # Add the Python "User Script Directory" to the PATH ENV PATH="${PATH}:${HOME}/.local/bin" ENV ImageOS=ubuntu20 diff --git a/runner/actions-runner.dockerfile b/runner/actions-runner.dockerfile index 4697e6246b..181fdb0527 100644 --- a/runner/actions-runner.dockerfile +++ b/runner/actions-runner.dockerfile @@ -3,8 +3,10 @@ FROM ubuntu:20.04 ARG TARGETPLATFORM ARG RUNNER_VERSION=2.299.1 ARG RUNNER_CONTAINER_HOOKS_VERSION=0.1.2 -ARG DOCKER_CHANNEL=stable +# Docker and Docker Compose arguments +ARG CHANNEL=stable ARG DOCKER_VERSION=20.10.18 +ARG DOCKER_COMPOSE_VERSION=v2.6.0 ARG DUMB_INIT_VERSION=1.2.5 RUN test -n "$TARGETPLATFORM" || (echo "TARGETPLATFORM must be set" && false) @@ -46,76 +48,62 @@ RUN apt update -y \ && ln -sf /usr/bin/pip3 /usr/bin/pip \ && rm -rf /var/lib/apt/lists/* -# arch command on OS X reports "i386" for Intel CPUs regardless of bitness -RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ - && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ - && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ - && curl -f -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_${ARCH} \ - && chmod +x /usr/local/bin/dumb-init - -# Docker download supports arm64 as aarch64 & amd64 / i386 as x86_64 -RUN set -vx; \ - export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ - && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ - && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ - && curl -f -L -o docker.tgz https://download.docker.com/linux/static/${DOCKER_CHANNEL}/${ARCH}/docker-${DOCKER_VERSION}.tgz \ - && tar zxvf docker.tgz \ - && install -o root -g root -m 755 docker/docker /usr/bin/docker \ - && rm -rf docker docker.tgz \ - && adduser --disabled-password --gecos "" --uid 1000 runner \ +RUN adduser --disabled-password --gecos "" --uid 1000 runner \ && groupadd docker \ && usermod -aG sudo runner \ && usermod -aG docker runner \ && echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers \ && echo "Defaults env_keep += \"DEBIAN_FRONTEND\"" >> /etc/sudoers -# Uncomment the below COPY to use your own custom build of actions-runner. -# -# To build a custom runner: -# - Clone the actions/runner repo `git clone git@github.com:actions/runner.git $repo` -# - Run `cd $repo/src` -# - Run `./dev.sh layout Release linux-x64` -# - Run `./dev.sh package Release linux-x64` -# - Run cp ../_package/actions-runner-linux-x64-2.280.3.tar.gz ../../actions-runner-controller/runner/ -# - Beware that `2.280.3` might change across versions -# -# See https://github.com/actions/runner/blob/main/.github/workflows/release.yml for more informatino on how you can use dev.sh -# -# If you're willing to uncomment the following line, you'd also need to comment-out the -# && curl -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \ -# line in the next `RUN` command in this Dockerfile, to avoid overwiting this runner.tar.gz with a remote one. - -# COPY actions-runner-linux-x64-2.280.3.tar.gz /runnertmp/runner.tar.gz - -# Runner download supports amd64 as x64. Externalstmp is needed for making mount points work inside DinD. -# -# libyaml-dev is required for ruby/setup-ruby action. -# It is installed after installdependencies.sh and before removing /var/lib/apt/lists -# to avoid rerunning apt-update on its own. +ENV HOME=/home/runner + +RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ + && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ + && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ + && curl -fLo /usr/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_${ARCH} \ + && chmod +x /usr/bin/dumb-init + ENV RUNNER_ASSETS_DIR=/runnertmp RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x64 ; fi \ && mkdir -p "$RUNNER_ASSETS_DIR" \ && cd "$RUNNER_ASSETS_DIR" \ - # Comment-out the below curl invocation when you use your own build of actions/runner - && curl -f -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \ + && curl -fLo runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \ && tar xzf ./runner.tar.gz \ && rm runner.tar.gz \ && ./bin/installdependencies.sh \ && mv ./externals ./externalstmp \ + # libyaml-dev is required for ruby/setup-ruby action. + # It is installed after installdependencies.sh and before removing /var/lib/apt/lists + # to avoid rerunning apt-update on its own. && apt-get install -y libyaml-dev \ && rm -rf /var/lib/apt/lists/* -RUN cd "$RUNNER_ASSETS_DIR" \ - && curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-container-hooks/releases/download/v${RUNNER_CONTAINER_HOOKS_VERSION}/actions-runner-hooks-k8s-${RUNNER_CONTAINER_HOOKS_VERSION}.zip \ - && unzip ./runner-container-hooks.zip -d ./k8s \ - && rm runner-container-hooks.zip - ENV RUNNER_TOOL_CACHE=/opt/hostedtoolcache RUN mkdir /opt/hostedtoolcache \ && chgrp docker /opt/hostedtoolcache \ && chmod g+rwx /opt/hostedtoolcache +RUN cd "$RUNNER_ASSETS_DIR" \ + && curl -fLo runner-container-hooks.zip https://github.com/actions/runner-container-hooks/releases/download/v${RUNNER_CONTAINER_HOOKS_VERSION}/actions-runner-hooks-k8s-${RUNNER_CONTAINER_HOOKS_VERSION}.zip \ + && unzip ./runner-container-hooks.zip -d ./k8s \ + && rm -f runner-container-hooks.zip + +RUN set -vx; \ + export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ + && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ + && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ + && curl -fLo docker.tgz https://download.docker.com/linux/static/${CHANNEL}/${ARCH}/docker-${DOCKER_VERSION}.tgz \ + && tar zxvf docker.tgz \ + && install -o root -g root -m 755 docker/docker /usr/bin/docker \ + && rm -rf docker docker.tgz + +RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ + && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ + && if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "i386" ]; then export ARCH=x86_64 ; fi \ + && curl -fLo /usr/bin/docker-compose https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-${ARCH} \ + && chmod +x /usr/bin/docker-compose + # We place the scripts in `/usr/bin` so that users who extend this image can # override them with scripts of the same name placed in `/usr/local/bin`. COPY entrypoint.sh startup.sh logger.sh graceful-stop.sh update-status /usr/bin/ @@ -127,9 +115,8 @@ COPY docker-shim.sh /usr/local/bin/docker # Configure hooks folder structure. COPY hooks /etc/arc/hooks/ -ENV HOME=/home/runner # Add the Python "User Script Directory" to the PATH -ENV PATH="${PATH}:${HOME}/.local/bin" +ENV PATH="${PATH}:${HOME}/.local/bin/" ENV ImageOS=ubuntu20 RUN echo "PATH=${PATH}" > /etc/environment \ diff --git a/runner/startup.sh b/runner/startup.sh index 2e873fbf2e..f726a7a7f7 100755 --- a/runner/startup.sh +++ b/runner/startup.sh @@ -145,10 +145,14 @@ if [ -z "${UNITTEST:-}" ] && [ -e ./externalstmp ]; then mv ./externalstmp/* ./externals/ fi +WAIT_FOR_DOCKER_SECONDS=${WAIT_FOR_DOCKER_SECONDS:-120} if [[ "${DISABLE_WAIT_FOR_DOCKER}" != "true" ]] && [[ "${DOCKER_ENABLED}" == "true" ]]; then log.debug 'Docker enabled runner detected and Docker daemon wait is enabled' - log.debug 'Waiting until Docker is available or the timeout is reached' - timeout 120s bash -c 'until docker ps ;do sleep 1; done' + log.debug "Waiting until Docker is available or the timeout of ${WAIT_FOR_DOCKER_SECONDS} seconds is reached" + if ! timeout "${WAIT_FOR_DOCKER_SECONDS}s" bash -c 'until docker ps ;do sleep 1; done'; then + log.notice "Docker has not become available within ${WAIT_FOR_DOCKER_SECONDS} seconds. Exiting with status 1." + exit 1 + fi else log.notice 'Docker wait check skipped. Either Docker is disabled or the wait is disabled, continuing with entrypoint' fi