mirror of
https://github.com/actions/actions-runner-controller.git
synced 2025-12-10 11:41:27 +00:00
Compare commits
54 Commits
update-run
...
gha-runner
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3be7128f9a | ||
|
|
3bda9bb240 | ||
|
|
ab92e4edc3 | ||
|
|
fa7a4f584e | ||
|
|
9b51f25800 | ||
|
|
ea13873f14 | ||
|
|
a6d87c46cd | ||
|
|
51c70a64c3 | ||
|
|
a1b8e0cc3d | ||
|
|
2889029bc5 | ||
|
|
87f2e00971 | ||
|
|
d9af241a7d | ||
|
|
49490c4421 | ||
|
|
109750f816 | ||
|
|
9e191cdd21 | ||
|
|
f965dfef73 | ||
|
|
4ee49fee14 | ||
|
|
8075e5ee74 | ||
|
|
963ae48a3f | ||
|
|
98854ef9c0 | ||
|
|
1987d9eb2e | ||
|
|
0006dd5eb1 | ||
|
|
86f1714354 | ||
|
|
f68bbad579 | ||
|
|
d3a8a34bb2 | ||
|
|
d515b4a6e0 | ||
|
|
d971fedbe8 | ||
|
|
6c6d061f0a | ||
|
|
5b9b9f7ca2 | ||
|
|
4357525445 | ||
|
|
1d1790614b | ||
|
|
442d52cd56 | ||
|
|
b6a95ae879 | ||
|
|
9968141086 | ||
|
|
e59d127d41 | ||
|
|
fb1232c13e | ||
|
|
7a643a5107 | ||
|
|
46cfbb6ec7 | ||
|
|
c9099a5a56 | ||
|
|
48706584fd | ||
|
|
2c0e53951b | ||
|
|
a7af44e042 | ||
|
|
f225fef921 | ||
|
|
814947c60e | ||
|
|
039350a0d0 | ||
|
|
a0fb417f69 | ||
|
|
f5fd831c2f | ||
|
|
753afb75b9 | ||
|
|
309b53143e | ||
|
|
7da2d7f96a | ||
|
|
e06c7edc21 | ||
|
|
9fba37540a | ||
|
|
a68aa00bd8 | ||
|
|
9b053102ed |
@@ -78,7 +78,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
RUNNER_MESSAGE="runner to v${RUNNER_LATEST_VERSION}"
|
RUNNER_MESSAGE="runner to v${RUNNER_LATEST_VERSION}"
|
||||||
CONTAINER_HOOKS_MESSAGE="container-hooks to v${CONTAINER_HOOKS_LATEST_VERSION}"
|
CONTAINER_HOOKS_MESSAGE="container-hooks to v${CONTAINER_HOOKS_LATEST_VERSION}"
|
||||||
|
|
||||||
PR_NAME="Updates:"
|
PR_NAME="Updates:"
|
||||||
if [ "$RUNNER_CURRENT_VERSION" != "$RUNNER_LATEST_VERSION" ]
|
if [ "$RUNNER_CURRENT_VERSION" != "$RUNNER_LATEST_VERSION" ]
|
||||||
then
|
then
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
then
|
then
|
||||||
PR_NAME="$PR_NAME $CONTAINER_HOOKS_MESSAGE"
|
PR_NAME="$PR_NAME $CONTAINER_HOOKS_MESSAGE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
result=$(gh pr list --search "$PR_NAME" --json number --jq ".[].number" --limit 1)
|
result=$(gh pr list --search "$PR_NAME" --json number --jq ".[].number" --limit 1)
|
||||||
if [ -z "$result" ]
|
if [ -z "$result" ]
|
||||||
then
|
then
|
||||||
@@ -120,21 +120,25 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: New branch
|
- name: New branch
|
||||||
run: git checkout -b update-runner-"$(date +%Y-%m-%d)"
|
run: git checkout -b update-runner-"$(date +%Y-%m-%d)"
|
||||||
|
|
||||||
- name: Update files
|
- name: Update files
|
||||||
run: |
|
run: |
|
||||||
sed -i "s/$RUNNER_CURRENT_VERSION/$RUNNER_LATEST_VERSION/g" runner/VERSION
|
CURRENT_VERSION="${RUNNER_CURRENT_VERSION//./\\.}"
|
||||||
sed -i "s/$RUNNER_CURRENT_VERSION/$RUNNER_LATEST_VERSION/g" runner/Makefile
|
LATEST_VERSION="${RUNNER_LATEST_VERSION//./\\.}"
|
||||||
sed -i "s/$RUNNER_CURRENT_VERSION/$RUNNER_LATEST_VERSION/g" Makefile
|
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" runner/VERSION
|
||||||
sed -i "s/$RUNNER_CURRENT_VERSION/$RUNNER_LATEST_VERSION/g" test/e2e/e2e_test.go
|
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" runner/Makefile
|
||||||
|
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" Makefile
|
||||||
sed -i "s/$CONTAINER_HOOKS_CURRENT_VERSION/$CONTAINER_HOOKS_LATEST_VERSION/g" runner/VERSION
|
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" test/e2e/e2e_test.go
|
||||||
sed -i "s/$CONTAINER_HOOKS_CURRENT_VERSION/$CONTAINER_HOOKS_LATEST_VERSION/g" runner/Makefile
|
|
||||||
sed -i "s/$CONTAINER_HOOKS_CURRENT_VERSION/$CONTAINER_HOOKS_LATEST_VERSION/g" Makefile
|
CURRENT_VERSION="${CONTAINER_HOOKS_CURRENT_VERSION//./\\.}"
|
||||||
sed -i "s/$CONTAINER_HOOKS_CURRENT_VERSION/$CONTAINER_HOOKS_LATEST_VERSION/g" test/e2e/e2e_test.go
|
LATEST_VERSION="${CONTAINER_HOOKS_LATEST_VERSION//./\\.}"
|
||||||
|
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" runner/VERSION
|
||||||
|
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" runner/Makefile
|
||||||
|
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" Makefile
|
||||||
|
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" test/e2e/e2e_test.go
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/gha-e2e-tests.yaml
vendored
2
.github/workflows/gha-e2e-tests.yaml
vendored
@@ -16,7 +16,7 @@ env:
|
|||||||
TARGET_ORG: actions-runner-controller
|
TARGET_ORG: actions-runner-controller
|
||||||
TARGET_REPO: arc_e2e_test_dummy
|
TARGET_REPO: arc_e2e_test_dummy
|
||||||
IMAGE_NAME: "arc-test-image"
|
IMAGE_NAME: "arc-test-image"
|
||||||
IMAGE_VERSION: "0.8.2"
|
IMAGE_VERSION: "0.9.2"
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
# This will make sure we only apply the concurrency limits on pull requests
|
# This will make sure we only apply the concurrency limits on pull requests
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
run:
|
run:
|
||||||
timeout: 3m
|
timeout: 3m
|
||||||
output:
|
output:
|
||||||
format: github-actions
|
formats:
|
||||||
|
- format: github-actions
|
||||||
|
path: stdout
|
||||||
linters-settings:
|
linters-settings:
|
||||||
errcheck:
|
errcheck:
|
||||||
exclude-functions:
|
exclude-functions:
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
# actions-runner-controller maintainers
|
# actions-runner-controller maintainers
|
||||||
* @mumoshu @toast-gear @actions/actions-launch @nikola-jokic
|
* @mumoshu @toast-gear @actions/actions-launch @nikola-jokic @rentziass
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Build the manager binary
|
# Build the manager binary
|
||||||
FROM --platform=$BUILDPLATFORM golang:1.21.3 as builder
|
FROM --platform=$BUILDPLATFORM golang:1.22.1 as builder
|
||||||
|
|
||||||
WORKDIR /workspace
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -6,7 +6,7 @@ endif
|
|||||||
DOCKER_USER ?= $(shell echo ${DOCKER_IMAGE_NAME} | cut -d / -f1)
|
DOCKER_USER ?= $(shell echo ${DOCKER_IMAGE_NAME} | cut -d / -f1)
|
||||||
VERSION ?= dev
|
VERSION ?= dev
|
||||||
COMMIT_SHA = $(shell git rev-parse HEAD)
|
COMMIT_SHA = $(shell git rev-parse HEAD)
|
||||||
RUNNER_VERSION ?= 2.312.0
|
RUNNER_VERSION ?= 2.316.1
|
||||||
TARGETPLATFORM ?= $(shell arch)
|
TARGETPLATFORM ?= $(shell arch)
|
||||||
RUNNER_NAME ?= ${DOCKER_USER}/actions-runner
|
RUNNER_NAME ?= ${DOCKER_USER}/actions-runner
|
||||||
RUNNER_TAG ?= ${VERSION}
|
RUNNER_TAG ?= ${VERSION}
|
||||||
@@ -68,7 +68,7 @@ endif
|
|||||||
all: manager
|
all: manager
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
docker run --rm -v $(PWD):/app -w /app golangci/golangci-lint:v1.55.2 golangci-lint run
|
docker run --rm -v $(PWD):/app -w /app golangci/golangci-lint:v1.57.2 golangci-lint run
|
||||||
|
|
||||||
GO_TEST_ARGS ?= -short
|
GO_TEST_ARGS ?= -short
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ ifeq (, $(wildcard $(GOBIN)/controller-gen))
|
|||||||
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
|
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
|
||||||
cd $$CONTROLLER_GEN_TMP_DIR ;\
|
cd $$CONTROLLER_GEN_TMP_DIR ;\
|
||||||
go mod init tmp ;\
|
go mod init tmp ;\
|
||||||
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.13.0 ;\
|
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0 ;\
|
||||||
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
|
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
|
||||||
}
|
}
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ type EphemeralRunner struct {
|
|||||||
Status EphemeralRunnerStatus `json:"status,omitempty"`
|
Status EphemeralRunnerStatus `json:"status,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (er *EphemeralRunner) IsDone() bool {
|
||||||
|
return er.Status.Phase == corev1.PodSucceeded || er.Status.Phase == corev1.PodFailed
|
||||||
|
}
|
||||||
|
|
||||||
// EphemeralRunnerSpec defines the desired state of EphemeralRunner
|
// EphemeralRunnerSpec defines the desired state of EphemeralRunner
|
||||||
type EphemeralRunnerSpec struct {
|
type EphemeralRunnerSpec struct {
|
||||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import (
|
|||||||
type EphemeralRunnerSetSpec struct {
|
type EphemeralRunnerSetSpec struct {
|
||||||
// Replicas is the number of desired EphemeralRunner resources in the k8s namespace.
|
// Replicas is the number of desired EphemeralRunner resources in the k8s namespace.
|
||||||
Replicas int `json:"replicas,omitempty"`
|
Replicas int `json:"replicas,omitempty"`
|
||||||
|
// PatchID is the unique identifier for the patch issued by the listener app
|
||||||
|
PatchID int `json:"patchID"`
|
||||||
|
|
||||||
EphemeralRunnerSpec EphemeralRunnerSpec `json:"ephemeralRunnerSpec,omitempty"`
|
EphemeralRunnerSpec EphemeralRunnerSpec `json:"ephemeralRunnerSpec,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
controller-gen.kubebuilder.io/version: v0.13.0
|
controller-gen.kubebuilder.io/version: v0.14.0
|
||||||
name: horizontalrunnerautoscalers.actions.summerwind.dev
|
name: horizontalrunnerautoscalers.actions.summerwind.dev
|
||||||
spec:
|
spec:
|
||||||
group: actions.summerwind.dev
|
group: actions.summerwind.dev
|
||||||
@@ -35,10 +35,19 @@ spec:
|
|||||||
description: HorizontalRunnerAutoscaler is the Schema for the horizontalrunnerautoscaler API
|
description: HorizontalRunnerAutoscaler is the Schema for the horizontalrunnerautoscaler API
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
description: |-
|
||||||
|
APIVersion defines the versioned schema of this representation of an object.
|
||||||
|
Servers should convert recognized schemas to the latest internal value, and
|
||||||
|
may reject unrecognized values.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
description: |-
|
||||||
|
Kind is a string value representing the REST resource this object represents.
|
||||||
|
Servers may infer this from the endpoint the client submits requests to.
|
||||||
|
Cannot be updated.
|
||||||
|
In CamelCase.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||||
type: string
|
type: string
|
||||||
metadata:
|
metadata:
|
||||||
type: object
|
type: object
|
||||||
@@ -47,7 +56,9 @@ spec:
|
|||||||
properties:
|
properties:
|
||||||
capacityReservations:
|
capacityReservations:
|
||||||
items:
|
items:
|
||||||
description: CapacityReservation specifies the number of replicas temporarily added to the scale target until ExpirationTime.
|
description: |-
|
||||||
|
CapacityReservation specifies the number of replicas temporarily added
|
||||||
|
to the scale target until ExpirationTime.
|
||||||
properties:
|
properties:
|
||||||
effectiveTime:
|
effectiveTime:
|
||||||
format: date-time
|
format: date-time
|
||||||
@@ -79,30 +90,46 @@ spec:
|
|||||||
items:
|
items:
|
||||||
properties:
|
properties:
|
||||||
repositoryNames:
|
repositoryNames:
|
||||||
description: RepositoryNames is the list of repository names to be used for calculating the metric. For example, a repository name is the REPO part of `github.com/USER/REPO`.
|
description: |-
|
||||||
|
RepositoryNames is the list of repository names to be used for calculating the metric.
|
||||||
|
For example, a repository name is the REPO part of `github.com/USER/REPO`.
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
scaleDownAdjustment:
|
scaleDownAdjustment:
|
||||||
description: ScaleDownAdjustment is the number of runners removed on scale-down. You can only specify either ScaleDownFactor or ScaleDownAdjustment.
|
description: |-
|
||||||
|
ScaleDownAdjustment is the number of runners removed on scale-down.
|
||||||
|
You can only specify either ScaleDownFactor or ScaleDownAdjustment.
|
||||||
type: integer
|
type: integer
|
||||||
scaleDownFactor:
|
scaleDownFactor:
|
||||||
description: ScaleDownFactor is the multiplicative factor applied to the current number of runners used to determine how many pods should be removed.
|
description: |-
|
||||||
|
ScaleDownFactor is the multiplicative factor applied to the current number of runners used
|
||||||
|
to determine how many pods should be removed.
|
||||||
type: string
|
type: string
|
||||||
scaleDownThreshold:
|
scaleDownThreshold:
|
||||||
description: ScaleDownThreshold is the percentage of busy runners less than which will trigger the hpa to scale the runners down.
|
description: |-
|
||||||
|
ScaleDownThreshold is the percentage of busy runners less than which will
|
||||||
|
trigger the hpa to scale the runners down.
|
||||||
type: string
|
type: string
|
||||||
scaleUpAdjustment:
|
scaleUpAdjustment:
|
||||||
description: ScaleUpAdjustment is the number of runners added on scale-up. You can only specify either ScaleUpFactor or ScaleUpAdjustment.
|
description: |-
|
||||||
|
ScaleUpAdjustment is the number of runners added on scale-up.
|
||||||
|
You can only specify either ScaleUpFactor or ScaleUpAdjustment.
|
||||||
type: integer
|
type: integer
|
||||||
scaleUpFactor:
|
scaleUpFactor:
|
||||||
description: ScaleUpFactor is the multiplicative factor applied to the current number of runners used to determine how many pods should be added.
|
description: |-
|
||||||
|
ScaleUpFactor is the multiplicative factor applied to the current number of runners used
|
||||||
|
to determine how many pods should be added.
|
||||||
type: string
|
type: string
|
||||||
scaleUpThreshold:
|
scaleUpThreshold:
|
||||||
description: ScaleUpThreshold is the percentage of busy runners greater than which will trigger the hpa to scale runners up.
|
description: |-
|
||||||
|
ScaleUpThreshold is the percentage of busy runners greater than which will
|
||||||
|
trigger the hpa to scale runners up.
|
||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
description: Type is the type of metric to be used for autoscaling. It can be TotalNumberOfQueuedAndInProgressWorkflowRuns or PercentageRunnersBusy.
|
description: |-
|
||||||
|
Type is the type of metric to be used for autoscaling.
|
||||||
|
It can be TotalNumberOfQueuedAndInProgressWorkflowRuns or PercentageRunnersBusy.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
@@ -110,7 +137,9 @@ spec:
|
|||||||
description: MinReplicas is the minimum number of replicas the deployment is allowed to scale
|
description: MinReplicas is the minimum number of replicas the deployment is allowed to scale
|
||||||
type: integer
|
type: integer
|
||||||
scaleDownDelaySecondsAfterScaleOut:
|
scaleDownDelaySecondsAfterScaleOut:
|
||||||
description: ScaleDownDelaySecondsAfterScaleUp is the approximate delay for a scale down followed by a scale up Used to prevent flapping (down->up->down->... loop)
|
description: |-
|
||||||
|
ScaleDownDelaySecondsAfterScaleUp is the approximate delay for a scale down followed by a scale up
|
||||||
|
Used to prevent flapping (down->up->down->... loop)
|
||||||
type: integer
|
type: integer
|
||||||
scaleTargetRef:
|
scaleTargetRef:
|
||||||
description: ScaleTargetRef is the reference to scaled resource like RunnerDeployment
|
description: ScaleTargetRef is the reference to scaled resource like RunnerDeployment
|
||||||
@@ -126,7 +155,18 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
scaleUpTriggers:
|
scaleUpTriggers:
|
||||||
description: "ScaleUpTriggers is an experimental feature to increase the desired replicas by 1 on each webhook requested received by the webhookBasedAutoscaler. \n This feature requires you to also enable and deploy the webhookBasedAutoscaler onto your cluster. \n Note that the added runners remain until the next sync period at least, and they may or may not be used by GitHub Actions depending on the timing. They are intended to be used to gain \"resource slack\" immediately after you receive a webhook from GitHub, so that you can loosely expect MinReplicas runners to be always available."
|
description: |-
|
||||||
|
ScaleUpTriggers is an experimental feature to increase the desired replicas by 1
|
||||||
|
on each webhook requested received by the webhookBasedAutoscaler.
|
||||||
|
|
||||||
|
|
||||||
|
This feature requires you to also enable and deploy the webhookBasedAutoscaler onto your cluster.
|
||||||
|
|
||||||
|
|
||||||
|
Note that the added runners remain until the next sync period at least,
|
||||||
|
and they may or may not be used by GitHub Actions depending on the timing.
|
||||||
|
They are intended to be used to gain "resource slack" immediately after you
|
||||||
|
receive a webhook from GitHub, so that you can loosely expect MinReplicas runners to be always available.
|
||||||
items:
|
items:
|
||||||
properties:
|
properties:
|
||||||
amount:
|
amount:
|
||||||
@@ -139,12 +179,18 @@ spec:
|
|||||||
description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run
|
description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run
|
||||||
properties:
|
properties:
|
||||||
names:
|
names:
|
||||||
description: Names is a list of GitHub Actions glob patterns. Any check_run event whose name matches one of patterns in the list can trigger autoscaling. Note that check_run name seem to equal to the job name you've defined in your actions workflow yaml file. So it is very likely that you can utilize this to trigger depending on the job.
|
description: |-
|
||||||
|
Names is a list of GitHub Actions glob patterns.
|
||||||
|
Any check_run event whose name matches one of patterns in the list can trigger autoscaling.
|
||||||
|
Note that check_run name seem to equal to the job name you've defined in your actions workflow yaml file.
|
||||||
|
So it is very likely that you can utilize this to trigger depending on the job.
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
repositories:
|
repositories:
|
||||||
description: Repositories is a list of GitHub repositories. Any check_run event whose repository matches one of repositories in the list can trigger autoscaling.
|
description: |-
|
||||||
|
Repositories is a list of GitHub repositories.
|
||||||
|
Any check_run event whose repository matches one of repositories in the list can trigger autoscaling.
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
@@ -169,7 +215,9 @@ spec:
|
|||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
push:
|
push:
|
||||||
description: PushSpec is the condition for triggering scale-up on push event Also see https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push
|
description: |-
|
||||||
|
PushSpec is the condition for triggering scale-up on push event
|
||||||
|
Also see https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push
|
||||||
type: object
|
type: object
|
||||||
workflowJob:
|
workflowJob:
|
||||||
description: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_job
|
description: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_job
|
||||||
@@ -178,23 +226,33 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
scheduledOverrides:
|
scheduledOverrides:
|
||||||
description: ScheduledOverrides is the list of ScheduledOverride. It can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule. The earlier a scheduled override is, the higher it is prioritized.
|
description: |-
|
||||||
|
ScheduledOverrides is the list of ScheduledOverride.
|
||||||
|
It can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule.
|
||||||
|
The earlier a scheduled override is, the higher it is prioritized.
|
||||||
items:
|
items:
|
||||||
description: ScheduledOverride can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule. A schedule can optionally be recurring, so that the corresponding override happens every day, week, month, or year.
|
description: |-
|
||||||
|
ScheduledOverride can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule.
|
||||||
|
A schedule can optionally be recurring, so that the corresponding override happens every day, week, month, or year.
|
||||||
properties:
|
properties:
|
||||||
endTime:
|
endTime:
|
||||||
description: EndTime is the time at which the first override ends.
|
description: EndTime is the time at which the first override ends.
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
minReplicas:
|
minReplicas:
|
||||||
description: MinReplicas is the number of runners while overriding. If omitted, it doesn't override minReplicas.
|
description: |-
|
||||||
|
MinReplicas is the number of runners while overriding.
|
||||||
|
If omitted, it doesn't override minReplicas.
|
||||||
minimum: 0
|
minimum: 0
|
||||||
nullable: true
|
nullable: true
|
||||||
type: integer
|
type: integer
|
||||||
recurrenceRule:
|
recurrenceRule:
|
||||||
properties:
|
properties:
|
||||||
frequency:
|
frequency:
|
||||||
description: Frequency is the name of a predefined interval of each recurrence. The valid values are "Daily", "Weekly", "Monthly", and "Yearly". If empty, the corresponding override happens only once.
|
description: |-
|
||||||
|
Frequency is the name of a predefined interval of each recurrence.
|
||||||
|
The valid values are "Daily", "Weekly", "Monthly", and "Yearly".
|
||||||
|
If empty, the corresponding override happens only once.
|
||||||
enum:
|
enum:
|
||||||
- Daily
|
- Daily
|
||||||
- Weekly
|
- Weekly
|
||||||
@@ -202,7 +260,9 @@ spec:
|
|||||||
- Yearly
|
- Yearly
|
||||||
type: string
|
type: string
|
||||||
untilTime:
|
untilTime:
|
||||||
description: UntilTime is the time of the final recurrence. If empty, the schedule recurs forever.
|
description: |-
|
||||||
|
UntilTime is the time of the final recurrence.
|
||||||
|
If empty, the schedule recurs forever.
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
@@ -231,18 +291,24 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
desiredReplicas:
|
desiredReplicas:
|
||||||
description: DesiredReplicas is the total number of desired, non-terminated and latest pods to be set for the primary RunnerSet This doesn't include outdated pods while upgrading the deployment and replacing the runnerset.
|
description: |-
|
||||||
|
DesiredReplicas is the total number of desired, non-terminated and latest pods to be set for the primary RunnerSet
|
||||||
|
This doesn't include outdated pods while upgrading the deployment and replacing the runnerset.
|
||||||
type: integer
|
type: integer
|
||||||
lastSuccessfulScaleOutTime:
|
lastSuccessfulScaleOutTime:
|
||||||
format: date-time
|
format: date-time
|
||||||
nullable: true
|
nullable: true
|
||||||
type: string
|
type: string
|
||||||
observedGeneration:
|
observedGeneration:
|
||||||
description: ObservedGeneration is the most recent generation observed for the target. It corresponds to e.g. RunnerDeployment's generation, which is updated on mutation by the API Server.
|
description: |-
|
||||||
|
ObservedGeneration is the most recent generation observed for the target. It corresponds to e.g.
|
||||||
|
RunnerDeployment's generation, which is updated on mutation by the API Server.
|
||||||
format: int64
|
format: int64
|
||||||
type: integer
|
type: integer
|
||||||
scheduledOverridesSummary:
|
scheduledOverridesSummary:
|
||||||
description: ScheduledOverridesSummary is the summary of active and upcoming scheduled overrides to be shown in e.g. a column of a `kubectl get hra` output for observability.
|
description: |-
|
||||||
|
ScheduledOverridesSummary is the summary of active and upcoming scheduled overrides to be shown in e.g. a column of a `kubectl get hra` output
|
||||||
|
for observability.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,13 +15,13 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 0.8.2
|
version: 0.9.2
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
# It is recommended to use it with quotes.
|
# It is recommended to use it with quotes.
|
||||||
appVersion: "0.8.2"
|
appVersion: "0.9.2"
|
||||||
|
|
||||||
home: https://github.com/actions/actions-runner-controller
|
home: https://github.com/actions/actions-runner-controller
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,3 +2,4 @@ Thank you for installing {{ .Chart.Name }}.
|
|||||||
|
|
||||||
Your release is named {{ .Release.Name }}.
|
Your release is named {{ .Release.Name }}.
|
||||||
|
|
||||||
|
WARNING: Older version of the listener (githubrunnerscalesetlistener) is deprecated and will be removed in the future gha-runner-scale-set-0.10.0 release. If you are using environment variable override to force the old listener, please remove the environment variable and use the new listener (ghalistener) instead.
|
||||||
|
|||||||
@@ -126,7 +126,3 @@ Create the name of the service account to use
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- $names | join ","}}
|
{{- $names | join ","}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- define "gha-runner-scale-set-controller.serviceMonitorName" -}}
|
|
||||||
{{- include "gha-runner-scale-set-controller.fullname" . }}-service-monitor
|
|
||||||
{{- end }}
|
|
||||||
|
|||||||
@@ -110,10 +110,16 @@ spec:
|
|||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /tmp
|
- mountPath: /tmp
|
||||||
name: tmp
|
name: tmp
|
||||||
|
{{- range .Values.volumeMounts }}
|
||||||
|
- {{ toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
terminationGracePeriodSeconds: 10
|
terminationGracePeriodSeconds: 10
|
||||||
volumes:
|
volumes:
|
||||||
- name: tmp
|
- name: tmp
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
|
{{- range .Values.volumes }}
|
||||||
|
- {{ toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
{{- with .Values.nodeSelector }}
|
{{- with .Values.nodeSelector }}
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
@@ -122,6 +128,10 @@ spec:
|
|||||||
affinity:
|
affinity:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- with .Values.topologySpreadConstraints }}
|
||||||
|
topologySpreadConstraints:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
{{- with .Values.tolerations }}
|
{{- with .Values.tolerations }}
|
||||||
tolerations:
|
tolerations:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ func TestTemplate_ControllerDeployment_Defaults(t *testing.T) {
|
|||||||
|
|
||||||
assert.Len(t, deployment.Spec.Template.Spec.NodeSelector, 0)
|
assert.Len(t, deployment.Spec.Template.Spec.NodeSelector, 0)
|
||||||
assert.Nil(t, deployment.Spec.Template.Spec.Affinity)
|
assert.Nil(t, deployment.Spec.Template.Spec.Affinity)
|
||||||
|
assert.Len(t, deployment.Spec.Template.Spec.TopologySpreadConstraints, 0)
|
||||||
assert.Len(t, deployment.Spec.Template.Spec.Tolerations, 0)
|
assert.Len(t, deployment.Spec.Template.Spec.Tolerations, 0)
|
||||||
|
|
||||||
managerImage := "ghcr.io/actions/gha-runner-scale-set-controller:dev"
|
managerImage := "ghcr.io/actions/gha-runner-scale-set-controller:dev"
|
||||||
@@ -424,10 +425,17 @@ func TestTemplate_ControllerDeployment_Customize(t *testing.T) {
|
|||||||
"tolerations[0].key": "foo",
|
"tolerations[0].key": "foo",
|
||||||
"affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key": "foo",
|
"affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key": "foo",
|
||||||
"affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator": "bar",
|
"affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator": "bar",
|
||||||
"priorityClassName": "test-priority-class",
|
"topologySpreadConstraints[0].labelSelector.matchLabels.foo": "bar",
|
||||||
"flags.updateStrategy": "eventual",
|
"topologySpreadConstraints[0].maxSkew": "1",
|
||||||
"flags.logLevel": "info",
|
"topologySpreadConstraints[0].topologyKey": "foo",
|
||||||
"flags.logFormat": "json",
|
"priorityClassName": "test-priority-class",
|
||||||
|
"flags.updateStrategy": "eventual",
|
||||||
|
"flags.logLevel": "info",
|
||||||
|
"flags.logFormat": "json",
|
||||||
|
"volumes[0].name": "customMount",
|
||||||
|
"volumes[0].configMap.name": "my-configmap",
|
||||||
|
"volumeMounts[0].name": "customMount",
|
||||||
|
"volumeMounts[0].mountPath": "/my/mount/path",
|
||||||
},
|
},
|
||||||
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||||
}
|
}
|
||||||
@@ -470,9 +478,11 @@ func TestTemplate_ControllerDeployment_Customize(t *testing.T) {
|
|||||||
assert.Equal(t, int64(1000), *deployment.Spec.Template.Spec.SecurityContext.FSGroup)
|
assert.Equal(t, int64(1000), *deployment.Spec.Template.Spec.SecurityContext.FSGroup)
|
||||||
assert.Equal(t, "test-priority-class", deployment.Spec.Template.Spec.PriorityClassName)
|
assert.Equal(t, "test-priority-class", deployment.Spec.Template.Spec.PriorityClassName)
|
||||||
assert.Equal(t, int64(10), *deployment.Spec.Template.Spec.TerminationGracePeriodSeconds)
|
assert.Equal(t, int64(10), *deployment.Spec.Template.Spec.TerminationGracePeriodSeconds)
|
||||||
assert.Len(t, deployment.Spec.Template.Spec.Volumes, 1)
|
assert.Len(t, deployment.Spec.Template.Spec.Volumes, 2)
|
||||||
assert.Equal(t, "tmp", deployment.Spec.Template.Spec.Volumes[0].Name)
|
assert.Equal(t, "tmp", deployment.Spec.Template.Spec.Volumes[0].Name)
|
||||||
assert.NotNil(t, 10, deployment.Spec.Template.Spec.Volumes[0].EmptyDir)
|
assert.NotNil(t, deployment.Spec.Template.Spec.Volumes[0].EmptyDir)
|
||||||
|
assert.Equal(t, "customMount", deployment.Spec.Template.Spec.Volumes[1].Name)
|
||||||
|
assert.Equal(t, "my-configmap", deployment.Spec.Template.Spec.Volumes[1].ConfigMap.Name)
|
||||||
|
|
||||||
assert.Len(t, deployment.Spec.Template.Spec.NodeSelector, 1)
|
assert.Len(t, deployment.Spec.Template.Spec.NodeSelector, 1)
|
||||||
assert.Equal(t, "bar", deployment.Spec.Template.Spec.NodeSelector["foo"])
|
assert.Equal(t, "bar", deployment.Spec.Template.Spec.NodeSelector["foo"])
|
||||||
@@ -481,6 +491,11 @@ func TestTemplate_ControllerDeployment_Customize(t *testing.T) {
|
|||||||
assert.Equal(t, "foo", deployment.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions[0].Key)
|
assert.Equal(t, "foo", deployment.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions[0].Key)
|
||||||
assert.Equal(t, "bar", string(deployment.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions[0].Operator))
|
assert.Equal(t, "bar", string(deployment.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions[0].Operator))
|
||||||
|
|
||||||
|
assert.Len(t, deployment.Spec.Template.Spec.TopologySpreadConstraints, 1)
|
||||||
|
assert.Equal(t, "bar", deployment.Spec.Template.Spec.TopologySpreadConstraints[0].LabelSelector.MatchLabels["foo"])
|
||||||
|
assert.Equal(t, int32(1), deployment.Spec.Template.Spec.TopologySpreadConstraints[0].MaxSkew)
|
||||||
|
assert.Equal(t, "foo", deployment.Spec.Template.Spec.TopologySpreadConstraints[0].TopologyKey)
|
||||||
|
|
||||||
assert.Len(t, deployment.Spec.Template.Spec.Tolerations, 1)
|
assert.Len(t, deployment.Spec.Template.Spec.Tolerations, 1)
|
||||||
assert.Equal(t, "foo", deployment.Spec.Template.Spec.Tolerations[0].Key)
|
assert.Equal(t, "foo", deployment.Spec.Template.Spec.Tolerations[0].Key)
|
||||||
|
|
||||||
@@ -521,9 +536,11 @@ func TestTemplate_ControllerDeployment_Customize(t *testing.T) {
|
|||||||
assert.True(t, *deployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot)
|
assert.True(t, *deployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot)
|
||||||
assert.Equal(t, int64(1000), *deployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser)
|
assert.Equal(t, int64(1000), *deployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser)
|
||||||
|
|
||||||
assert.Len(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts, 1)
|
assert.Len(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts, 2)
|
||||||
assert.Equal(t, "tmp", deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name)
|
assert.Equal(t, "tmp", deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name)
|
||||||
assert.Equal(t, "/tmp", deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath)
|
assert.Equal(t, "/tmp", deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath)
|
||||||
|
assert.Equal(t, "customMount", deployment.Spec.Template.Spec.Containers[0].VolumeMounts[1].Name)
|
||||||
|
assert.Equal(t, "/my/mount/path", deployment.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplate_EnableLeaderElectionRole(t *testing.T) {
|
func TestTemplate_EnableLeaderElectionRole(t *testing.T) {
|
||||||
@@ -737,6 +754,7 @@ func TestTemplate_ControllerDeployment_WatchSingleNamespace(t *testing.T) {
|
|||||||
|
|
||||||
assert.Len(t, deployment.Spec.Template.Spec.NodeSelector, 0)
|
assert.Len(t, deployment.Spec.Template.Spec.NodeSelector, 0)
|
||||||
assert.Nil(t, deployment.Spec.Template.Spec.Affinity)
|
assert.Nil(t, deployment.Spec.Template.Spec.Affinity)
|
||||||
|
assert.Len(t, deployment.Spec.Template.Spec.TopologySpreadConstraints, 0)
|
||||||
assert.Len(t, deployment.Spec.Template.Spec.Tolerations, 0)
|
assert.Len(t, deployment.Spec.Template.Spec.Tolerations, 0)
|
||||||
|
|
||||||
managerImage := "ghcr.io/actions/gha-runner-scale-set-controller:dev"
|
managerImage := "ghcr.io/actions/gha-runner-scale-set-controller:dev"
|
||||||
|
|||||||
@@ -72,14 +72,20 @@ tolerations: []
|
|||||||
|
|
||||||
affinity: {}
|
affinity: {}
|
||||||
|
|
||||||
|
topologySpreadConstraints: []
|
||||||
|
|
||||||
|
# Mount volumes in the container.
|
||||||
|
volumes: []
|
||||||
|
volumeMounts: []
|
||||||
|
|
||||||
# Leverage a PriorityClass to ensure your pods survive resource shortages
|
# Leverage a PriorityClass to ensure your pods survive resource shortages
|
||||||
# ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/
|
# ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/
|
||||||
# PriorityClass: system-cluster-critical
|
# PriorityClass: system-cluster-critical
|
||||||
priorityClassName: ""
|
priorityClassName: ""
|
||||||
|
|
||||||
## If `metrics:` object is not provided, or commented out, the following flags
|
## If `metrics:` object is not provided, or commented out, the following flags
|
||||||
## will be applied the controller-manager and listener pods with empty values:
|
## will be applied the controller-manager and listener pods with empty values:
|
||||||
## `--metrics-addr`, `--listener-metrics-addr`, `--listener-metrics-endpoint`.
|
## `--metrics-addr`, `--listener-metrics-addr`, `--listener-metrics-endpoint`.
|
||||||
## This will disable metrics.
|
## This will disable metrics.
|
||||||
##
|
##
|
||||||
## To enable metrics, uncomment the following lines.
|
## To enable metrics, uncomment the following lines.
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 0.8.2
|
version: 0.9.2
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
# It is recommended to use it with quotes.
|
# It is recommended to use it with quotes.
|
||||||
appVersion: "0.8.2"
|
appVersion: "0.9.2"
|
||||||
|
|
||||||
home: https://github.com/actions/actions-runner-controller
|
home: https://github.com/actions/actions-runner-controller
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ volumeMounts:
|
|||||||
image: docker:dind
|
image: docker:dind
|
||||||
args:
|
args:
|
||||||
- dockerd
|
- dockerd
|
||||||
- --host=unix:///run/docker/docker.sock
|
- --host=unix:///var/run/docker.sock
|
||||||
- --group=$(DOCKER_GROUP_GID)
|
- --group=$(DOCKER_GROUP_GID)
|
||||||
env:
|
env:
|
||||||
- name: DOCKER_GROUP_GID
|
- name: DOCKER_GROUP_GID
|
||||||
@@ -110,7 +110,7 @@ volumeMounts:
|
|||||||
- name: work
|
- name: work
|
||||||
mountPath: /home/runner/_work
|
mountPath: /home/runner/_work
|
||||||
- name: dind-sock
|
- name: dind-sock
|
||||||
mountPath: /run/docker
|
mountPath: /var/run
|
||||||
- name: dind-externals
|
- name: dind-externals
|
||||||
mountPath: /home/runner/externals
|
mountPath: /home/runner/externals
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -223,7 +223,7 @@ env:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if $setDockerHost }}
|
{{- if $setDockerHost }}
|
||||||
- name: DOCKER_HOST
|
- name: DOCKER_HOST
|
||||||
value: unix:///run/docker/docker.sock
|
value: unix:///var/run/docker.sock
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if $setRunnerWaitDocker }}
|
{{- if $setRunnerWaitDocker }}
|
||||||
- name: RUNNER_WAIT_FOR_DOCKER_IN_SECONDS
|
- name: RUNNER_WAIT_FOR_DOCKER_IN_SECONDS
|
||||||
@@ -264,8 +264,7 @@ volumeMounts:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if $mountDindCert }}
|
{{- if $mountDindCert }}
|
||||||
- name: dind-sock
|
- name: dind-sock
|
||||||
mountPath: /run/docker
|
mountPath: /var/run
|
||||||
readOnly: true
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if $mountGitHubServerTLS }}
|
{{- if $mountGitHubServerTLS }}
|
||||||
- name: github-server-tls-cert
|
- name: github-server-tls-cert
|
||||||
@@ -526,13 +525,13 @@ volumeMounts:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if and (eq $multiNamespacesCounter 0) (eq $singleNamespaceCounter 0) }}
|
{{- if and (eq $multiNamespacesCounter 0) (eq $singleNamespaceCounter 0) }}
|
||||||
{{- fail "No gha-rs-controller deployment found using label (app.kubernetes.io/part-of=gha-rs-controller). Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
|
{{- fail "No gha-rs-controller deployment found using label (app.kubernetes.io/part-of=gha-rs-controller). Consider setting controllerServiceAccount.namespace in values.yaml to be explicit if you think the discovery is wrong." }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if and (gt $multiNamespacesCounter 0) (gt $singleNamespaceCounter 0) }}
|
{{- if and (gt $multiNamespacesCounter 0) (gt $singleNamespaceCounter 0) }}
|
||||||
{{- fail "Found both gha-rs-controller installed with flags.watchSingleNamespace set and unset in cluster, this is not supported. Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
|
{{- fail "Found both gha-rs-controller installed with flags.watchSingleNamespace set and unset in cluster, this is not supported. Consider setting controllerServiceAccount.namespace in values.yaml to be explicit if you think the discovery is wrong." }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if gt $multiNamespacesCounter 1 }}
|
{{- if gt $multiNamespacesCounter 1 }}
|
||||||
{{- fail "More than one gha-rs-controller deployment found using label (app.kubernetes.io/part-of=gha-rs-controller). Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
|
{{- fail "More than one gha-rs-controller deployment found using label (app.kubernetes.io/part-of=gha-rs-controller). Consider setting controllerServiceAccount.namespace in values.yaml to be explicit if you think the discovery is wrong." }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if eq $multiNamespacesCounter 1 }}
|
{{- if eq $multiNamespacesCounter 1 }}
|
||||||
{{- with $controllerDeployment.metadata }}
|
{{- with $controllerDeployment.metadata }}
|
||||||
@@ -545,11 +544,11 @@ volumeMounts:
|
|||||||
{{- $managerServiceAccountNamespace = (get $controllerDeployment.metadata.labels "actions.github.com/controller-service-account-namespace") }}
|
{{- $managerServiceAccountNamespace = (get $controllerDeployment.metadata.labels "actions.github.com/controller-service-account-namespace") }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- else }}
|
{{- else }}
|
||||||
{{- fail "No gha-rs-controller deployment that watch this namespace found using label (actions.github.com/controller-watch-single-namespace). Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
|
{{- fail "No gha-rs-controller deployment that watch this namespace found using label (actions.github.com/controller-watch-single-namespace). Consider setting controllerServiceAccount.namespace in values.yaml to be explicit if you think the discovery is wrong." }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if eq $managerServiceAccountNamespace "" }}
|
{{- if eq $managerServiceAccountNamespace "" }}
|
||||||
{{- fail "No service account namespace found for gha-rs-controller deployment using label (actions.github.com/controller-service-account-namespace), consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
|
{{- fail "No service account namespace found for gha-rs-controller deployment using label (actions.github.com/controller-service-account-namespace), consider setting controllerServiceAccount.namespace in values.yaml to be explicit if you think the discovery is wrong." }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- $managerServiceAccountNamespace }}
|
{{- $managerServiceAccountNamespace }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ metadata:
|
|||||||
app.kubernetes.io/component: "autoscaling-runner-set"
|
app.kubernetes.io/component: "autoscaling-runner-set"
|
||||||
{{- include "gha-runner-scale-set.labels" . | nindent 4 }}
|
{{- include "gha-runner-scale-set.labels" . | nindent 4 }}
|
||||||
annotations:
|
annotations:
|
||||||
|
actions.github.com/values-hash: {{ toJson .Values | sha256sum | trunc 63 }}
|
||||||
{{- $containerMode := .Values.containerMode }}
|
{{- $containerMode := .Values.containerMode }}
|
||||||
{{- if not (kindIs "string" .Values.githubConfigSecret) }}
|
{{- if not (kindIs "string" .Values.githubConfigSecret) }}
|
||||||
actions.github.com/cleanup-github-secret-name: {{ include "gha-runner-scale-set.githubsecret" . }}
|
actions.github.com/cleanup-github-secret-name: {{ include "gha-runner-scale-set.githubsecret" . }}
|
||||||
|
|||||||
@@ -900,7 +900,7 @@ func TestTemplateRenderedAutoScalingRunnerSet_EnableDinD(t *testing.T) {
|
|||||||
assert.Equal(t, "ghcr.io/actions/actions-runner:latest", ars.Spec.Template.Spec.Containers[0].Image)
|
assert.Equal(t, "ghcr.io/actions/actions-runner:latest", ars.Spec.Template.Spec.Containers[0].Image)
|
||||||
assert.Len(t, ars.Spec.Template.Spec.Containers[0].Env, 2, "The runner container should have 2 env vars, DOCKER_HOST and RUNNER_WAIT_FOR_DOCKER_IN_SECONDS")
|
assert.Len(t, ars.Spec.Template.Spec.Containers[0].Env, 2, "The runner container should have 2 env vars, DOCKER_HOST and RUNNER_WAIT_FOR_DOCKER_IN_SECONDS")
|
||||||
assert.Equal(t, "DOCKER_HOST", ars.Spec.Template.Spec.Containers[0].Env[0].Name)
|
assert.Equal(t, "DOCKER_HOST", ars.Spec.Template.Spec.Containers[0].Env[0].Name)
|
||||||
assert.Equal(t, "unix:///run/docker/docker.sock", ars.Spec.Template.Spec.Containers[0].Env[0].Value)
|
assert.Equal(t, "unix:///var/run/docker.sock", ars.Spec.Template.Spec.Containers[0].Env[0].Value)
|
||||||
assert.Equal(t, "RUNNER_WAIT_FOR_DOCKER_IN_SECONDS", ars.Spec.Template.Spec.Containers[0].Env[1].Name)
|
assert.Equal(t, "RUNNER_WAIT_FOR_DOCKER_IN_SECONDS", ars.Spec.Template.Spec.Containers[0].Env[1].Name)
|
||||||
assert.Equal(t, "120", ars.Spec.Template.Spec.Containers[0].Env[1].Value)
|
assert.Equal(t, "120", ars.Spec.Template.Spec.Containers[0].Env[1].Value)
|
||||||
|
|
||||||
@@ -910,8 +910,7 @@ func TestTemplateRenderedAutoScalingRunnerSet_EnableDinD(t *testing.T) {
|
|||||||
assert.False(t, ars.Spec.Template.Spec.Containers[0].VolumeMounts[0].ReadOnly)
|
assert.False(t, ars.Spec.Template.Spec.Containers[0].VolumeMounts[0].ReadOnly)
|
||||||
|
|
||||||
assert.Equal(t, "dind-sock", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].Name)
|
assert.Equal(t, "dind-sock", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].Name)
|
||||||
assert.Equal(t, "/run/docker", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath)
|
assert.Equal(t, "/var/run", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath)
|
||||||
assert.True(t, ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].ReadOnly)
|
|
||||||
|
|
||||||
assert.Equal(t, "dind", ars.Spec.Template.Spec.Containers[1].Name)
|
assert.Equal(t, "dind", ars.Spec.Template.Spec.Containers[1].Name)
|
||||||
assert.Equal(t, "docker:dind", ars.Spec.Template.Spec.Containers[1].Image)
|
assert.Equal(t, "docker:dind", ars.Spec.Template.Spec.Containers[1].Image)
|
||||||
@@ -921,7 +920,7 @@ func TestTemplateRenderedAutoScalingRunnerSet_EnableDinD(t *testing.T) {
|
|||||||
assert.Equal(t, "/home/runner/_work", ars.Spec.Template.Spec.Containers[1].VolumeMounts[0].MountPath)
|
assert.Equal(t, "/home/runner/_work", ars.Spec.Template.Spec.Containers[1].VolumeMounts[0].MountPath)
|
||||||
|
|
||||||
assert.Equal(t, "dind-sock", ars.Spec.Template.Spec.Containers[1].VolumeMounts[1].Name)
|
assert.Equal(t, "dind-sock", ars.Spec.Template.Spec.Containers[1].VolumeMounts[1].Name)
|
||||||
assert.Equal(t, "/run/docker", ars.Spec.Template.Spec.Containers[1].VolumeMounts[1].MountPath)
|
assert.Equal(t, "/var/run", ars.Spec.Template.Spec.Containers[1].VolumeMounts[1].MountPath)
|
||||||
|
|
||||||
assert.Equal(t, "dind-externals", ars.Spec.Template.Spec.Containers[1].VolumeMounts[2].Name)
|
assert.Equal(t, "dind-externals", ars.Spec.Template.Spec.Containers[1].VolumeMounts[2].Name)
|
||||||
assert.Equal(t, "/home/runner/externals", ars.Spec.Template.Spec.Containers[1].VolumeMounts[2].MountPath)
|
assert.Equal(t, "/home/runner/externals", ars.Spec.Template.Spec.Containers[1].VolumeMounts[2].MountPath)
|
||||||
@@ -2089,3 +2088,58 @@ func TestRunnerContainerVolumeNotEmptyMap(t *testing.T) {
|
|||||||
_, ok := m.Spec.Template.Spec.Containers[0]["volumeMounts"]
|
_, ok := m.Spec.Template.Spec.Containers[0]["volumeMounts"]
|
||||||
assert.False(t, ok, "volumeMounts should not be set")
|
assert.False(t, ok, "volumeMounts should not be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAutoscalingRunnerSetAnnotationValuesHash(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const valuesHash = "actions.github.com/values-hash"
|
||||||
|
|
||||||
|
// Path to the helm chart we will test
|
||||||
|
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
releaseName := "test-runners"
|
||||||
|
namespaceName := "test-" + strings.ToLower(random.UniqueId())
|
||||||
|
|
||||||
|
options := &helm.Options{
|
||||||
|
Logger: logger.Discard,
|
||||||
|
SetValues: map[string]string{
|
||||||
|
"githubConfigUrl": "https://github.com/actions",
|
||||||
|
"githubConfigSecret.github_token": "gh_token12345",
|
||||||
|
"controllerServiceAccount.name": "arc",
|
||||||
|
"controllerServiceAccount.namespace": "arc-system",
|
||||||
|
},
|
||||||
|
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||||
|
}
|
||||||
|
|
||||||
|
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"})
|
||||||
|
|
||||||
|
var autoscalingRunnerSet v1alpha1.AutoscalingRunnerSet
|
||||||
|
helm.UnmarshalK8SYaml(t, output, &autoscalingRunnerSet)
|
||||||
|
|
||||||
|
firstHash := autoscalingRunnerSet.Annotations["actions.github.com/values-hash"]
|
||||||
|
assert.NotEmpty(t, firstHash)
|
||||||
|
assert.LessOrEqual(t, len(firstHash), 63)
|
||||||
|
|
||||||
|
helmChartPath, err = filepath.Abs("../../gha-runner-scale-set")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
options = &helm.Options{
|
||||||
|
Logger: logger.Discard,
|
||||||
|
SetValues: map[string]string{
|
||||||
|
"githubConfigUrl": "https://github.com/actions",
|
||||||
|
"githubConfigSecret.github_token": "gh_token1234567890",
|
||||||
|
"controllerServiceAccount.name": "arc",
|
||||||
|
"controllerServiceAccount.namespace": "arc-system",
|
||||||
|
},
|
||||||
|
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||||
|
}
|
||||||
|
|
||||||
|
output = helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"})
|
||||||
|
|
||||||
|
helm.UnmarshalK8SYaml(t, output, &autoscalingRunnerSet)
|
||||||
|
secondHash := autoscalingRunnerSet.Annotations[valuesHash]
|
||||||
|
assert.NotEmpty(t, secondHash)
|
||||||
|
assert.NotEqual(t, firstHash, secondHash)
|
||||||
|
assert.LessOrEqual(t, len(secondHash), 63)
|
||||||
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ githubConfigSecret:
|
|||||||
# kubernetesModeServiceAccount:
|
# kubernetesModeServiceAccount:
|
||||||
# annotations:
|
# annotations:
|
||||||
|
|
||||||
## template is the PodSpec for each listener Pod
|
## listenerTemplate is the PodSpec for each listener Pod
|
||||||
## For reference: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec
|
## For reference: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec
|
||||||
# listenerTemplate:
|
# listenerTemplate:
|
||||||
# spec:
|
# spec:
|
||||||
@@ -125,18 +125,17 @@ template:
|
|||||||
## command: ["/home/runner/run.sh"]
|
## command: ["/home/runner/run.sh"]
|
||||||
## env:
|
## env:
|
||||||
## - name: DOCKER_HOST
|
## - name: DOCKER_HOST
|
||||||
## value: unix:///run/docker/docker.sock
|
## value: unix:///var/run/docker.sock
|
||||||
## volumeMounts:
|
## volumeMounts:
|
||||||
## - name: work
|
## - name: work
|
||||||
## mountPath: /home/runner/_work
|
## mountPath: /home/runner/_work
|
||||||
## - name: dind-sock
|
## - name: dind-sock
|
||||||
## mountPath: /run/docker
|
## mountPath: /var/run
|
||||||
## readOnly: true
|
|
||||||
## - name: dind
|
## - name: dind
|
||||||
## image: docker:dind
|
## image: docker:dind
|
||||||
## args:
|
## args:
|
||||||
## - dockerd
|
## - dockerd
|
||||||
## - --host=unix:///run/docker/docker.sock
|
## - --host=unix:///var/run/docker.sock
|
||||||
## - --group=$(DOCKER_GROUP_GID)
|
## - --group=$(DOCKER_GROUP_GID)
|
||||||
## env:
|
## env:
|
||||||
## - name: DOCKER_GROUP_GID
|
## - name: DOCKER_GROUP_GID
|
||||||
@@ -147,7 +146,7 @@ template:
|
|||||||
## - name: work
|
## - name: work
|
||||||
## mountPath: /home/runner/_work
|
## mountPath: /home/runner/_work
|
||||||
## - name: dind-sock
|
## - name: dind-sock
|
||||||
## mountPath: /run/docker
|
## mountPath: /var/run
|
||||||
## - name: dind-externals
|
## - name: dind-externals
|
||||||
## mountPath: /home/runner/externals
|
## mountPath: /home/runner/externals
|
||||||
## volumes:
|
## volumes:
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ type Listener interface {
|
|||||||
//go:generate mockery --name Worker --output ./mocks --outpkg mocks --case underscore
|
//go:generate mockery --name Worker --output ./mocks --outpkg mocks --case underscore
|
||||||
type Worker interface {
|
type Worker interface {
|
||||||
HandleJobStarted(ctx context.Context, jobInfo *actions.JobStarted) error
|
HandleJobStarted(ctx context.Context, jobInfo *actions.JobStarted) error
|
||||||
HandleDesiredRunnerCount(ctx context.Context, count int) (int, error)
|
HandleDesiredRunnerCount(ctx context.Context, count int, jobsCompleted int) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config config.Config) (*App, error) {
|
func New(config config.Config) (*App, error) {
|
||||||
@@ -117,15 +117,19 @@ func (app *App) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
g, ctx := errgroup.WithContext(ctx)
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
metricsCtx, cancelMetrics := context.WithCancelCause(ctx)
|
||||||
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
app.logger.Info("Starting listener")
|
app.logger.Info("Starting listener")
|
||||||
return app.listener.Listen(ctx, app.worker)
|
listnerErr := app.listener.Listen(ctx, app.worker)
|
||||||
|
cancelMetrics(fmt.Errorf("Listener exited: %w", listnerErr))
|
||||||
|
return listnerErr
|
||||||
})
|
})
|
||||||
|
|
||||||
if app.metrics != nil {
|
if app.metrics != nil {
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
app.logger.Info("Starting metrics server")
|
app.logger.Info("Starting metrics server")
|
||||||
return app.metrics.ListenAndServe(ctx)
|
return app.metrics.ListenAndServe(metricsCtx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,23 +15,23 @@ type Worker struct {
|
|||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleDesiredRunnerCount provides a mock function with given fields: ctx, count
|
// HandleDesiredRunnerCount provides a mock function with given fields: ctx, count, acquireCount
|
||||||
func (_m *Worker) HandleDesiredRunnerCount(ctx context.Context, count int) (int, error) {
|
func (_m *Worker) HandleDesiredRunnerCount(ctx context.Context, count int, acquireCount int) (int, error) {
|
||||||
ret := _m.Called(ctx, count)
|
ret := _m.Called(ctx, count, acquireCount)
|
||||||
|
|
||||||
var r0 int
|
var r0 int
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, int) (int, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, int, int) (int, error)); ok {
|
||||||
return rf(ctx, count)
|
return rf(ctx, count, acquireCount)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, int) int); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, int, int) int); ok {
|
||||||
r0 = rf(ctx, count)
|
r0 = rf(ctx, count, acquireCount)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(int)
|
r0 = ret.Get(0).(int)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, int, int) error); ok {
|
||||||
r1 = rf(ctx, count)
|
r1 = rf(ctx, count, acquireCount)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const (
|
|||||||
type Client interface {
|
type Client interface {
|
||||||
GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (*actions.AcquirableJobList, error)
|
GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (*actions.AcquirableJobList, error)
|
||||||
CreateMessageSession(ctx context.Context, runnerScaleSetId int, owner string) (*actions.RunnerScaleSetSession, error)
|
CreateMessageSession(ctx context.Context, runnerScaleSetId int, owner string) (*actions.RunnerScaleSetSession, error)
|
||||||
GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64) (*actions.RunnerScaleSetMessage, error)
|
GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*actions.RunnerScaleSetMessage, error)
|
||||||
DeleteMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, messageId int64) error
|
DeleteMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, messageId int64) error
|
||||||
AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error)
|
AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error)
|
||||||
RefreshMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) (*actions.RunnerScaleSetSession, error)
|
RefreshMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) (*actions.RunnerScaleSetSession, error)
|
||||||
@@ -80,6 +80,7 @@ type Listener struct {
|
|||||||
|
|
||||||
// updated fields
|
// updated fields
|
||||||
lastMessageID int64 // The ID of the last processed message.
|
lastMessageID int64 // The ID of the last processed message.
|
||||||
|
maxCapacity int // The maximum number of runners that can be created.
|
||||||
session *actions.RunnerScaleSetSession // The session for managing the runner scale set.
|
session *actions.RunnerScaleSetSession // The session for managing the runner scale set.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,10 +90,11 @@ func New(config Config) (*Listener, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listener := &Listener{
|
listener := &Listener{
|
||||||
scaleSetID: config.ScaleSetID,
|
scaleSetID: config.ScaleSetID,
|
||||||
client: config.Client,
|
client: config.Client,
|
||||||
logger: config.Logger,
|
logger: config.Logger,
|
||||||
metrics: metrics.Discard,
|
metrics: metrics.Discard,
|
||||||
|
maxCapacity: config.MaxRunners,
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Metrics != nil {
|
if config.Metrics != nil {
|
||||||
@@ -114,7 +116,7 @@ func New(config Config) (*Listener, error) {
|
|||||||
//go:generate mockery --name Handler --output ./mocks --outpkg mocks --case underscore
|
//go:generate mockery --name Handler --output ./mocks --outpkg mocks --case underscore
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
HandleJobStarted(ctx context.Context, jobInfo *actions.JobStarted) error
|
HandleJobStarted(ctx context.Context, jobInfo *actions.JobStarted) error
|
||||||
HandleDesiredRunnerCount(ctx context.Context, count int) (int, error)
|
HandleDesiredRunnerCount(ctx context.Context, count, jobsCompleted int) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen listens for incoming messages and handles them using the provided handler.
|
// Listen listens for incoming messages and handles them using the provided handler.
|
||||||
@@ -145,7 +147,7 @@ func (l *Listener) Listen(ctx context.Context, handler Handler) error {
|
|||||||
}
|
}
|
||||||
l.metrics.PublishStatistics(initialMessage.Statistics)
|
l.metrics.PublishStatistics(initialMessage.Statistics)
|
||||||
|
|
||||||
desiredRunners, err := handler.HandleDesiredRunnerCount(ctx, initialMessage.Statistics.TotalAssignedJobs)
|
desiredRunners, err := handler.HandleDesiredRunnerCount(ctx, initialMessage.Statistics.TotalAssignedJobs, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("handling initial message failed: %w", err)
|
return fmt.Errorf("handling initial message failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -164,11 +166,16 @@ func (l *Listener) Listen(ctx context.Context, handler Handler) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
|
_, err := handler.HandleDesiredRunnerCount(ctx, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("handling nil message failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// New context is created to avoid cancelation during message handling.
|
// Remove cancellation from the context to avoid cancelling the message handling.
|
||||||
if err := l.handleMessage(context.Background(), handler, msg); err != nil {
|
if err := l.handleMessage(context.WithoutCancel(ctx), handler, msg); err != nil {
|
||||||
return fmt.Errorf("failed to handle message: %w", err)
|
return fmt.Errorf("failed to handle message: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,7 +214,7 @@ func (l *Listener) handleMessage(ctx context.Context, handler Handler, msg *acti
|
|||||||
l.metrics.PublishJobStarted(jobStarted)
|
l.metrics.PublishJobStarted(jobStarted)
|
||||||
}
|
}
|
||||||
|
|
||||||
desiredRunners, err := handler.HandleDesiredRunnerCount(ctx, parsedMsg.statistics.TotalAssignedJobs)
|
desiredRunners, err := handler.HandleDesiredRunnerCount(ctx, parsedMsg.statistics.TotalAssignedJobs, len(parsedMsg.jobsCompleted))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to handle desired runner count: %w", err)
|
return fmt.Errorf("failed to handle desired runner count: %w", err)
|
||||||
}
|
}
|
||||||
@@ -262,7 +269,7 @@ func (l *Listener) createSession(ctx context.Context) error {
|
|||||||
|
|
||||||
func (l *Listener) getMessage(ctx context.Context) (*actions.RunnerScaleSetMessage, error) {
|
func (l *Listener) getMessage(ctx context.Context) (*actions.RunnerScaleSetMessage, error) {
|
||||||
l.logger.Info("Getting next message", "lastMessageID", l.lastMessageID)
|
l.logger.Info("Getting next message", "lastMessageID", l.lastMessageID)
|
||||||
msg, err := l.client.GetMessage(ctx, l.session.MessageQueueUrl, l.session.MessageQueueAccessToken, l.lastMessageID)
|
msg, err := l.client.GetMessage(ctx, l.session.MessageQueueUrl, l.session.MessageQueueAccessToken, l.lastMessageID, l.maxCapacity)
|
||||||
if err == nil { // if NO error
|
if err == nil { // if NO error
|
||||||
return msg, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
@@ -278,19 +285,33 @@ func (l *Listener) getMessage(ctx context.Context) (*actions.RunnerScaleSetMessa
|
|||||||
|
|
||||||
l.logger.Info("Getting next message", "lastMessageID", l.lastMessageID)
|
l.logger.Info("Getting next message", "lastMessageID", l.lastMessageID)
|
||||||
|
|
||||||
msg, err = l.client.GetMessage(ctx, l.session.MessageQueueUrl, l.session.MessageQueueAccessToken, l.lastMessageID)
|
msg, err = l.client.GetMessage(ctx, l.session.MessageQueueUrl, l.session.MessageQueueAccessToken, l.lastMessageID, l.maxCapacity)
|
||||||
if err != nil { // if NO error
|
if err != nil { // if NO error
|
||||||
return nil, fmt.Errorf("failed to get next message after message session refresh: %w", err)
|
return nil, fmt.Errorf("failed to get next message after message session refresh: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return msg, nil
|
return msg, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) deleteLastMessage(ctx context.Context) error {
|
func (l *Listener) deleteLastMessage(ctx context.Context) error {
|
||||||
l.logger.Info("Deleting last message", "lastMessageID", l.lastMessageID)
|
l.logger.Info("Deleting last message", "lastMessageID", l.lastMessageID)
|
||||||
if err := l.client.DeleteMessage(ctx, l.session.MessageQueueUrl, l.session.MessageQueueAccessToken, l.lastMessageID); err != nil {
|
err := l.client.DeleteMessage(ctx, l.session.MessageQueueUrl, l.session.MessageQueueAccessToken, l.lastMessageID)
|
||||||
return fmt.Errorf("failed to delete message: %w", err)
|
if err == nil { // if NO error
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
expiredError := &actions.MessageQueueTokenExpiredError{}
|
||||||
|
if !errors.As(err, &expiredError) {
|
||||||
|
return fmt.Errorf("failed to delete last message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := l.refreshSession(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.client.DeleteMessage(ctx, l.session.MessageQueueUrl, l.session.MessageQueueAccessToken, l.lastMessageID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete last message after message session refresh: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -384,9 +405,9 @@ func (l *Listener) acquireAvailableJobs(ctx context.Context, jobsAvailable []*ac
|
|||||||
|
|
||||||
l.logger.Info("Acquiring jobs", "count", len(ids), "requestIds", fmt.Sprint(ids))
|
l.logger.Info("Acquiring jobs", "count", len(ids), "requestIds", fmt.Sprint(ids))
|
||||||
|
|
||||||
ids, err := l.client.AcquireJobs(ctx, l.scaleSetID, l.session.MessageQueueAccessToken, ids)
|
idsAcquired, err := l.client.AcquireJobs(ctx, l.scaleSetID, l.session.MessageQueueAccessToken, ids)
|
||||||
if err == nil { // if NO errors
|
if err == nil { // if NO errors
|
||||||
return ids, nil
|
return idsAcquired, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
expiredError := &actions.MessageQueueTokenExpiredError{}
|
expiredError := &actions.MessageQueueTokenExpiredError{}
|
||||||
@@ -398,12 +419,12 @@ func (l *Listener) acquireAvailableJobs(ctx context.Context, jobsAvailable []*ac
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ids, err = l.client.AcquireJobs(ctx, l.scaleSetID, l.session.MessageQueueAccessToken, ids)
|
idsAcquired, err = l.client.AcquireJobs(ctx, l.scaleSetID, l.session.MessageQueueAccessToken, ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to acquire jobs after session refresh: %w", err)
|
return nil, fmt.Errorf("failed to acquire jobs after session refresh: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ids, nil
|
return idsAcquired, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) refreshSession(ctx context.Context) error {
|
func (l *Listener) refreshSession(ctx context.Context) error {
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ func TestNew(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.NotNil(t, l)
|
assert.NotNil(t, l)
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListener_createSession(t *testing.T) {
|
func TestListener_createSession(t *testing.T) {
|
||||||
@@ -124,13 +123,14 @@ func TestListener_getMessage(t *testing.T) {
|
|||||||
config := Config{
|
config := Config{
|
||||||
ScaleSetID: 1,
|
ScaleSetID: 1,
|
||||||
Metrics: metrics.Discard,
|
Metrics: metrics.Discard,
|
||||||
|
MaxRunners: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := listenermocks.NewClient(t)
|
client := listenermocks.NewClient(t)
|
||||||
want := &actions.RunnerScaleSetMessage{
|
want := &actions.RunnerScaleSetMessage{
|
||||||
MessageId: 1,
|
MessageId: 1,
|
||||||
}
|
}
|
||||||
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything).Return(want, nil).Once()
|
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything, 10).Return(want, nil).Once()
|
||||||
config.Client = client
|
config.Client = client
|
||||||
|
|
||||||
l, err := New(config)
|
l, err := New(config)
|
||||||
@@ -149,10 +149,11 @@ func TestListener_getMessage(t *testing.T) {
|
|||||||
config := Config{
|
config := Config{
|
||||||
ScaleSetID: 1,
|
ScaleSetID: 1,
|
||||||
Metrics: metrics.Discard,
|
Metrics: metrics.Discard,
|
||||||
|
MaxRunners: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := listenermocks.NewClient(t)
|
client := listenermocks.NewClient(t)
|
||||||
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything).Return(nil, &actions.HttpClientSideError{Code: http.StatusNotFound}).Once()
|
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything, 10).Return(nil, &actions.HttpClientSideError{Code: http.StatusNotFound}).Once()
|
||||||
config.Client = client
|
config.Client = client
|
||||||
|
|
||||||
l, err := New(config)
|
l, err := New(config)
|
||||||
@@ -171,6 +172,7 @@ func TestListener_getMessage(t *testing.T) {
|
|||||||
config := Config{
|
config := Config{
|
||||||
ScaleSetID: 1,
|
ScaleSetID: 1,
|
||||||
Metrics: metrics.Discard,
|
Metrics: metrics.Discard,
|
||||||
|
MaxRunners: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := listenermocks.NewClient(t)
|
client := listenermocks.NewClient(t)
|
||||||
@@ -186,12 +188,12 @@ func TestListener_getMessage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
client.On("RefreshMessageSession", ctx, mock.Anything, mock.Anything).Return(session, nil).Once()
|
client.On("RefreshMessageSession", ctx, mock.Anything, mock.Anything).Return(session, nil).Once()
|
||||||
|
|
||||||
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything).Return(nil, &actions.MessageQueueTokenExpiredError{}).Once()
|
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything, 10).Return(nil, &actions.MessageQueueTokenExpiredError{}).Once()
|
||||||
|
|
||||||
want := &actions.RunnerScaleSetMessage{
|
want := &actions.RunnerScaleSetMessage{
|
||||||
MessageId: 1,
|
MessageId: 1,
|
||||||
}
|
}
|
||||||
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything).Return(want, nil).Once()
|
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything, 10).Return(want, nil).Once()
|
||||||
|
|
||||||
config.Client = client
|
config.Client = client
|
||||||
|
|
||||||
@@ -215,6 +217,7 @@ func TestListener_getMessage(t *testing.T) {
|
|||||||
config := Config{
|
config := Config{
|
||||||
ScaleSetID: 1,
|
ScaleSetID: 1,
|
||||||
Metrics: metrics.Discard,
|
Metrics: metrics.Discard,
|
||||||
|
MaxRunners: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := listenermocks.NewClient(t)
|
client := listenermocks.NewClient(t)
|
||||||
@@ -230,7 +233,7 @@ func TestListener_getMessage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
client.On("RefreshMessageSession", ctx, mock.Anything, mock.Anything).Return(session, nil).Once()
|
client.On("RefreshMessageSession", ctx, mock.Anything, mock.Anything).Return(session, nil).Once()
|
||||||
|
|
||||||
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything).Return(nil, &actions.MessageQueueTokenExpiredError{}).Twice()
|
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything, 10).Return(nil, &actions.MessageQueueTokenExpiredError{}).Twice()
|
||||||
|
|
||||||
config.Client = client
|
config.Client = client
|
||||||
|
|
||||||
@@ -374,6 +377,93 @@ func TestListener_deleteLastMessage(t *testing.T) {
|
|||||||
err = l.deleteLastMessage(ctx)
|
err = l.deleteLastMessage(ctx)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("RefreshAndSucceeds", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
config := Config{
|
||||||
|
ScaleSetID: 1,
|
||||||
|
Metrics: metrics.Discard,
|
||||||
|
}
|
||||||
|
|
||||||
|
client := listenermocks.NewClient(t)
|
||||||
|
|
||||||
|
newUUID := uuid.New()
|
||||||
|
session := &actions.RunnerScaleSetSession{
|
||||||
|
SessionId: &newUUID,
|
||||||
|
OwnerName: "example",
|
||||||
|
RunnerScaleSet: &actions.RunnerScaleSet{},
|
||||||
|
MessageQueueUrl: "https://example.com",
|
||||||
|
MessageQueueAccessToken: "1234567890",
|
||||||
|
Statistics: nil,
|
||||||
|
}
|
||||||
|
client.On("RefreshMessageSession", ctx, mock.Anything, mock.Anything).Return(session, nil).Once()
|
||||||
|
|
||||||
|
client.On("DeleteMessage", ctx, mock.Anything, mock.Anything, mock.Anything).Return(&actions.MessageQueueTokenExpiredError{}).Once()
|
||||||
|
|
||||||
|
client.On("DeleteMessage", ctx, mock.Anything, mock.Anything, mock.MatchedBy(func(lastMessageID any) bool {
|
||||||
|
return lastMessageID.(int64) == int64(5)
|
||||||
|
})).Return(nil).Once()
|
||||||
|
config.Client = client
|
||||||
|
|
||||||
|
l, err := New(config)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
oldUUID := uuid.New()
|
||||||
|
l.session = &actions.RunnerScaleSetSession{
|
||||||
|
SessionId: &oldUUID,
|
||||||
|
RunnerScaleSet: &actions.RunnerScaleSet{},
|
||||||
|
}
|
||||||
|
l.lastMessageID = 5
|
||||||
|
|
||||||
|
config.Client = client
|
||||||
|
|
||||||
|
err = l.deleteLastMessage(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("RefreshAndFails", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
config := Config{
|
||||||
|
ScaleSetID: 1,
|
||||||
|
Metrics: metrics.Discard,
|
||||||
|
}
|
||||||
|
|
||||||
|
client := listenermocks.NewClient(t)
|
||||||
|
|
||||||
|
newUUID := uuid.New()
|
||||||
|
session := &actions.RunnerScaleSetSession{
|
||||||
|
SessionId: &newUUID,
|
||||||
|
OwnerName: "example",
|
||||||
|
RunnerScaleSet: &actions.RunnerScaleSet{},
|
||||||
|
MessageQueueUrl: "https://example.com",
|
||||||
|
MessageQueueAccessToken: "1234567890",
|
||||||
|
Statistics: nil,
|
||||||
|
}
|
||||||
|
client.On("RefreshMessageSession", ctx, mock.Anything, mock.Anything).Return(session, nil).Once()
|
||||||
|
|
||||||
|
client.On("DeleteMessage", ctx, mock.Anything, mock.Anything, mock.Anything).Return(&actions.MessageQueueTokenExpiredError{}).Twice()
|
||||||
|
|
||||||
|
config.Client = client
|
||||||
|
|
||||||
|
l, err := New(config)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
oldUUID := uuid.New()
|
||||||
|
l.session = &actions.RunnerScaleSetSession{
|
||||||
|
SessionId: &oldUUID,
|
||||||
|
RunnerScaleSet: &actions.RunnerScaleSet{},
|
||||||
|
}
|
||||||
|
l.lastMessageID = 5
|
||||||
|
|
||||||
|
config.Client = client
|
||||||
|
|
||||||
|
err = l.deleteLastMessage(ctx)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListener_Listen(t *testing.T) {
|
func TestListener_Listen(t *testing.T) {
|
||||||
@@ -428,7 +518,7 @@ func TestListener_Listen(t *testing.T) {
|
|||||||
|
|
||||||
var called bool
|
var called bool
|
||||||
handler := listenermocks.NewHandler(t)
|
handler := listenermocks.NewHandler(t)
|
||||||
handler.On("HandleDesiredRunnerCount", mock.Anything, mock.Anything).
|
handler.On("HandleDesiredRunnerCount", mock.Anything, mock.Anything, 0).
|
||||||
Return(0, nil).
|
Return(0, nil).
|
||||||
Run(
|
Run(
|
||||||
func(mock.Arguments) {
|
func(mock.Arguments) {
|
||||||
@@ -451,6 +541,7 @@ func TestListener_Listen(t *testing.T) {
|
|||||||
config := Config{
|
config := Config{
|
||||||
ScaleSetID: 1,
|
ScaleSetID: 1,
|
||||||
Metrics: metrics.Discard,
|
Metrics: metrics.Discard,
|
||||||
|
MaxRunners: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := listenermocks.NewClient(t)
|
client := listenermocks.NewClient(t)
|
||||||
@@ -471,7 +562,7 @@ func TestListener_Listen(t *testing.T) {
|
|||||||
MessageType: "RunnerScaleSetJobMessages",
|
MessageType: "RunnerScaleSetJobMessages",
|
||||||
Statistics: &actions.RunnerScaleSetStatistic{},
|
Statistics: &actions.RunnerScaleSetStatistic{},
|
||||||
}
|
}
|
||||||
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything).
|
client.On("GetMessage", ctx, mock.Anything, mock.Anything, mock.Anything, 10).
|
||||||
Return(msg, nil).
|
Return(msg, nil).
|
||||||
Run(
|
Run(
|
||||||
func(mock.Arguments) {
|
func(mock.Arguments) {
|
||||||
@@ -480,17 +571,17 @@ func TestListener_Listen(t *testing.T) {
|
|||||||
).
|
).
|
||||||
Once()
|
Once()
|
||||||
|
|
||||||
// Ensure delete message is called with background context
|
// Ensure delete message is called without cancel
|
||||||
client.On("DeleteMessage", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
client.On("DeleteMessage", context.WithoutCancel(ctx), mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||||
|
|
||||||
config.Client = client
|
config.Client = client
|
||||||
|
|
||||||
handler := listenermocks.NewHandler(t)
|
handler := listenermocks.NewHandler(t)
|
||||||
handler.On("HandleDesiredRunnerCount", mock.Anything, mock.Anything).
|
handler.On("HandleDesiredRunnerCount", mock.Anything, mock.Anything, 0).
|
||||||
Return(0, nil).
|
Return(0, nil).
|
||||||
Once()
|
Once()
|
||||||
|
|
||||||
handler.On("HandleDesiredRunnerCount", mock.Anything, mock.Anything).
|
handler.On("HandleDesiredRunnerCount", mock.Anything, mock.Anything, 0).
|
||||||
Return(0, nil).
|
Return(0, nil).
|
||||||
Once()
|
Once()
|
||||||
|
|
||||||
@@ -628,9 +719,6 @@ func TestListener_acquireAvailableJobs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
client.On("RefreshMessageSession", ctx, mock.Anything, mock.Anything).Return(session, nil).Once()
|
client.On("RefreshMessageSession", ctx, mock.Anything, mock.Anything).Return(session, nil).Once()
|
||||||
|
|
||||||
// First call to AcquireJobs will fail with a token expired error
|
|
||||||
client.On("AcquireJobs", ctx, mock.Anything, mock.Anything, mock.Anything).Return(nil, &actions.MessageQueueTokenExpiredError{}).Once()
|
|
||||||
|
|
||||||
// Second call to AcquireJobs will succeed
|
// Second call to AcquireJobs will succeed
|
||||||
want := []int64{1, 2, 3}
|
want := []int64{1, 2, 3}
|
||||||
availableJobs := []*actions.JobAvailable{
|
availableJobs := []*actions.JobAvailable{
|
||||||
@@ -650,7 +738,24 @@ func TestListener_acquireAvailableJobs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
client.On("AcquireJobs", ctx, mock.Anything, mock.Anything, mock.Anything).Return(want, nil).Once()
|
|
||||||
|
// First call to AcquireJobs will fail with a token expired error
|
||||||
|
client.On("AcquireJobs", ctx, mock.Anything, mock.Anything, mock.Anything).
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
ids := args.Get(3).([]int64)
|
||||||
|
assert.Equal(t, want, ids)
|
||||||
|
}).
|
||||||
|
Return(nil, &actions.MessageQueueTokenExpiredError{}).
|
||||||
|
Once()
|
||||||
|
|
||||||
|
// Second call should succeed
|
||||||
|
client.On("AcquireJobs", ctx, mock.Anything, mock.Anything, mock.Anything).
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
ids := args.Get(3).([]int64)
|
||||||
|
assert.Equal(t, want, ids)
|
||||||
|
}).
|
||||||
|
Return(want, nil).
|
||||||
|
Once()
|
||||||
|
|
||||||
config.Client = client
|
config.Client = client
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ func TestInitialMetrics(t *testing.T) {
|
|||||||
config.Client = client
|
config.Client = client
|
||||||
|
|
||||||
handler := listenermocks.NewHandler(t)
|
handler := listenermocks.NewHandler(t)
|
||||||
handler.On("HandleDesiredRunnerCount", mock.Anything, sessionStatistics.TotalAssignedJobs).
|
handler.On("HandleDesiredRunnerCount", mock.Anything, sessionStatistics.TotalAssignedJobs, 0).
|
||||||
Return(sessionStatistics.TotalAssignedJobs, nil).
|
Return(sessionStatistics.TotalAssignedJobs, nil).
|
||||||
Once()
|
Once()
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ func TestHandleMessageMetrics(t *testing.T) {
|
|||||||
|
|
||||||
handler := listenermocks.NewHandler(t)
|
handler := listenermocks.NewHandler(t)
|
||||||
handler.On("HandleJobStarted", mock.Anything, jobsStarted[0]).Return(nil).Once()
|
handler.On("HandleJobStarted", mock.Anything, jobsStarted[0]).Return(nil).Once()
|
||||||
handler.On("HandleDesiredRunnerCount", mock.Anything, mock.Anything).Return(desiredResult, nil).Once()
|
handler.On("HandleDesiredRunnerCount", mock.Anything, mock.Anything, 2).Return(desiredResult, nil).Once()
|
||||||
|
|
||||||
client := listenermocks.NewClient(t)
|
client := listenermocks.NewClient(t)
|
||||||
client.On("DeleteMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
client.On("DeleteMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||||
|
|||||||
@@ -123,25 +123,25 @@ func (_m *Client) GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMessage provides a mock function with given fields: ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId
|
// GetMessage provides a mock function with given fields: ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity
|
||||||
func (_m *Client) GetMessage(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, lastMessageId int64) (*actions.RunnerScaleSetMessage, error) {
|
func (_m *Client) GetMessage(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*actions.RunnerScaleSetMessage, error) {
|
||||||
ret := _m.Called(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId)
|
ret := _m.Called(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity)
|
||||||
|
|
||||||
var r0 *actions.RunnerScaleSetMessage
|
var r0 *actions.RunnerScaleSetMessage
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (*actions.RunnerScaleSetMessage, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64, int) (*actions.RunnerScaleSetMessage, error)); ok {
|
||||||
return rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId)
|
return rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) *actions.RunnerScaleSetMessage); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64, int) *actions.RunnerScaleSetMessage); ok {
|
||||||
r0 = rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId)
|
r0 = rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*actions.RunnerScaleSetMessage)
|
r0 = ret.Get(0).(*actions.RunnerScaleSetMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64) error); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64, int) error); ok {
|
||||||
r1 = rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId)
|
r1 = rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,23 +15,23 @@ type Handler struct {
|
|||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleDesiredRunnerCount provides a mock function with given fields: ctx, count
|
// HandleDesiredRunnerCount provides a mock function with given fields: ctx, count, jobsCompleted
|
||||||
func (_m *Handler) HandleDesiredRunnerCount(ctx context.Context, count int) (int, error) {
|
func (_m *Handler) HandleDesiredRunnerCount(ctx context.Context, count int, jobsCompleted int) (int, error) {
|
||||||
ret := _m.Called(ctx, count)
|
ret := _m.Called(ctx, count, jobsCompleted)
|
||||||
|
|
||||||
var r0 int
|
var r0 int
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, int) (int, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, int, int) (int, error)); ok {
|
||||||
return rf(ctx, count)
|
return rf(ctx, count, jobsCompleted)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, int) int); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, int, int) int); ok {
|
||||||
r0 = rf(ctx, count)
|
r0 = rf(ctx, count, jobsCompleted)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(int)
|
r0 = ret.Get(0).(int)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, int, int) error); ok {
|
||||||
r1 = rf(ctx, count)
|
r1 = rf(ctx, count, jobsCompleted)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
@@ -223,8 +224,8 @@ type baseLabels struct {
|
|||||||
func (b *baseLabels) jobLabels(jobBase *actions.JobMessageBase) prometheus.Labels {
|
func (b *baseLabels) jobLabels(jobBase *actions.JobMessageBase) prometheus.Labels {
|
||||||
return prometheus.Labels{
|
return prometheus.Labels{
|
||||||
labelKeyEnterprise: b.enterprise,
|
labelKeyEnterprise: b.enterprise,
|
||||||
labelKeyOrganization: b.organization,
|
labelKeyOrganization: jobBase.OwnerName,
|
||||||
labelKeyRepository: b.repository,
|
labelKeyRepository: jobBase.RepositoryName,
|
||||||
labelKeyJobName: jobBase.JobDisplayName,
|
labelKeyJobName: jobBase.JobDisplayName,
|
||||||
labelKeyJobWorkflowRef: jobBase.JobWorkflowRef,
|
labelKeyJobWorkflowRef: jobBase.JobWorkflowRef,
|
||||||
labelKeyEventName: jobBase.EventName,
|
labelKeyEventName: jobBase.EventName,
|
||||||
@@ -271,8 +272,10 @@ type ServerPublisher interface {
|
|||||||
ListenAndServe(ctx context.Context) error
|
ListenAndServe(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Publisher = &discard{}
|
var (
|
||||||
var _ ServerPublisher = &exporter{}
|
_ Publisher = &discard{}
|
||||||
|
_ ServerPublisher = &exporter{}
|
||||||
|
)
|
||||||
|
|
||||||
var Discard Publisher = &discard{}
|
var Discard Publisher = &discard{}
|
||||||
|
|
||||||
@@ -336,7 +339,9 @@ func (e *exporter) ListenAndServe(ctx context.Context) error {
|
|||||||
e.logger.Info("starting metrics server", "addr", e.srv.Addr)
|
e.logger.Info("starting metrics server", "addr", e.srv.Addr)
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
e.logger.Info("stopping metrics server")
|
e.logger.Info("stopping metrics server", "err", ctx.Err())
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
e.srv.Shutdown(ctx)
|
e.srv.Shutdown(ctx)
|
||||||
}()
|
}()
|
||||||
return e.srv.ListenAndServe()
|
return e.srv.ListenAndServe()
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ type Worker struct {
|
|||||||
clientset *kubernetes.Clientset
|
clientset *kubernetes.Clientset
|
||||||
config Config
|
config Config
|
||||||
lastPatch int
|
lastPatch int
|
||||||
|
patchSeq int
|
||||||
logger *logr.Logger
|
logger *logr.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ func New(config Config, options ...Option) (*Worker, error) {
|
|||||||
w := &Worker{
|
w := &Worker{
|
||||||
config: config,
|
config: config,
|
||||||
lastPatch: -1,
|
lastPatch: -1,
|
||||||
|
patchSeq: -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
conf, err := rest.InClusterConfig()
|
conf, err := rest.InClusterConfig()
|
||||||
@@ -161,28 +163,14 @@ func (w *Worker) HandleJobStarted(ctx context.Context, jobInfo *actions.JobStart
|
|||||||
// The function then scales the ephemeral runner set by applying the merge patch.
|
// The function then scales the ephemeral runner set by applying the merge patch.
|
||||||
// Finally, it logs the scaled ephemeral runner set details and returns nil if successful.
|
// Finally, it logs the scaled ephemeral runner set details and returns nil if successful.
|
||||||
// If any error occurs during the process, it returns an error with a descriptive message.
|
// If any error occurs during the process, it returns an error with a descriptive message.
|
||||||
func (w *Worker) HandleDesiredRunnerCount(ctx context.Context, count int) (int, error) {
|
func (w *Worker) HandleDesiredRunnerCount(ctx context.Context, count, jobsCompleted int) (int, error) {
|
||||||
// Max runners should always be set by the resource builder either to the configured value,
|
patchID := w.setDesiredWorkerState(count, jobsCompleted)
|
||||||
// or the maximum int32 (resourcebuilder.newAutoScalingListener()).
|
|
||||||
targetRunnerCount := min(w.config.MinRunners+count, w.config.MaxRunners)
|
|
||||||
|
|
||||||
logValues := []any{
|
|
||||||
"assigned job", count,
|
|
||||||
"decision", targetRunnerCount,
|
|
||||||
"min", w.config.MinRunners,
|
|
||||||
"max", w.config.MaxRunners,
|
|
||||||
"currentRunnerCount", w.lastPatch,
|
|
||||||
}
|
|
||||||
|
|
||||||
if targetRunnerCount == w.lastPatch {
|
|
||||||
w.logger.Info("Skipping patching of EphemeralRunnerSet as the desired count has not changed", logValues...)
|
|
||||||
return targetRunnerCount, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
original, err := json.Marshal(
|
original, err := json.Marshal(
|
||||||
&v1alpha1.EphemeralRunnerSet{
|
&v1alpha1.EphemeralRunnerSet{
|
||||||
Spec: v1alpha1.EphemeralRunnerSetSpec{
|
Spec: v1alpha1.EphemeralRunnerSetSpec{
|
||||||
Replicas: -1,
|
Replicas: -1,
|
||||||
|
PatchID: -1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -193,7 +181,8 @@ func (w *Worker) HandleDesiredRunnerCount(ctx context.Context, count int) (int,
|
|||||||
patch, err := json.Marshal(
|
patch, err := json.Marshal(
|
||||||
&v1alpha1.EphemeralRunnerSet{
|
&v1alpha1.EphemeralRunnerSet{
|
||||||
Spec: v1alpha1.EphemeralRunnerSetSpec{
|
Spec: v1alpha1.EphemeralRunnerSetSpec{
|
||||||
Replicas: targetRunnerCount,
|
Replicas: w.lastPatch,
|
||||||
|
PatchID: patchID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -202,14 +191,13 @@ func (w *Worker) HandleDesiredRunnerCount(ctx context.Context, count int) (int,
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.logger.Info("Compare", "original", string(original), "patch", string(patch))
|
||||||
mergePatch, err := jsonpatch.CreateMergePatch(original, patch)
|
mergePatch, err := jsonpatch.CreateMergePatch(original, patch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to create merge patch json for ephemeral runner set: %w", err)
|
return 0, fmt.Errorf("failed to create merge patch json for ephemeral runner set: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.logger.Info("Created merge patch json for EphemeralRunnerSet update", "json", string(mergePatch))
|
w.logger.Info("Preparing EphemeralRunnerSet update", "json", string(mergePatch))
|
||||||
|
|
||||||
w.logger.Info("Scaling ephemeral runner set", logValues...)
|
|
||||||
|
|
||||||
patchedEphemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{}
|
patchedEphemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{}
|
||||||
err = w.clientset.RESTClient().
|
err = w.clientset.RESTClient().
|
||||||
@@ -230,5 +218,40 @@ func (w *Worker) HandleDesiredRunnerCount(ctx context.Context, count int) (int,
|
|||||||
"name", w.config.EphemeralRunnerSetName,
|
"name", w.config.EphemeralRunnerSetName,
|
||||||
"replicas", patchedEphemeralRunnerSet.Spec.Replicas,
|
"replicas", patchedEphemeralRunnerSet.Spec.Replicas,
|
||||||
)
|
)
|
||||||
return targetRunnerCount, nil
|
return w.lastPatch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculateDesiredState calculates the desired state of the worker based on the desired count and the the number of jobs completed.
|
||||||
|
func (w *Worker) setDesiredWorkerState(count, jobsCompleted int) int {
|
||||||
|
// Max runners should always be set by the resource builder either to the configured value,
|
||||||
|
// or the maximum int32 (resourcebuilder.newAutoScalingListener()).
|
||||||
|
targetRunnerCount := min(w.config.MinRunners+count, w.config.MaxRunners)
|
||||||
|
w.patchSeq++
|
||||||
|
desiredPatchID := w.patchSeq
|
||||||
|
|
||||||
|
if count == 0 && jobsCompleted == 0 { // empty batch
|
||||||
|
targetRunnerCount = max(w.lastPatch, targetRunnerCount)
|
||||||
|
if targetRunnerCount == w.config.MinRunners {
|
||||||
|
// We have an empty batch, and the last patch was the min runners.
|
||||||
|
// Since this is an empty batch, and we are at the min runners, they should all be idle.
|
||||||
|
// If controller created few more pods on accident (during scale down events),
|
||||||
|
// this situation allows the controller to scale down to the min runners.
|
||||||
|
// However, it is important to keep the patch sequence increasing so we don't ignore one batch.
|
||||||
|
desiredPatchID = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.lastPatch = targetRunnerCount
|
||||||
|
|
||||||
|
w.logger.Info(
|
||||||
|
"Calculated target runner count",
|
||||||
|
"assigned job", count,
|
||||||
|
"decision", targetRunnerCount,
|
||||||
|
"min", w.config.MinRunners,
|
||||||
|
"max", w.config.MaxRunners,
|
||||||
|
"currentRunnerCount", w.lastPatch,
|
||||||
|
"jobsCompleted", jobsCompleted,
|
||||||
|
)
|
||||||
|
|
||||||
|
return desiredPatchID
|
||||||
}
|
}
|
||||||
|
|||||||
326
cmd/ghalistener/worker/worker_test.go
Normal file
326
cmd/ghalistener/worker/worker_test.go
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
package worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-logr/logr"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetDesiredWorkerState_MinMaxDefaults(t *testing.T) {
|
||||||
|
logger := logr.Discard()
|
||||||
|
newEmptyWorker := func() *Worker {
|
||||||
|
return &Worker{
|
||||||
|
config: Config{
|
||||||
|
MinRunners: 0,
|
||||||
|
MaxRunners: math.MaxInt32,
|
||||||
|
},
|
||||||
|
lastPatch: -1,
|
||||||
|
patchSeq: -1,
|
||||||
|
logger: &logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("init calculate with acquired 0", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(0, 0)
|
||||||
|
assert.Equal(t, 0, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("init calculate with acquired 1", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(1, 0)
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("increment patch when job done", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(1, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(0, 1)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 0, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("increment patch when called with same parameters", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(1, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(1, 0)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("calculate desired scale when acquired > 0 and completed > 0", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(1, 1)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("re-use the last state when acquired == 0 and completed == 0", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(1, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(0, 0)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("adjust when acquired == 0 and completed == 1", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(1, 1)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(0, 1)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 0, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetDesiredWorkerState_MinSet(t *testing.T) {
|
||||||
|
logger := logr.Discard()
|
||||||
|
newEmptyWorker := func() *Worker {
|
||||||
|
return &Worker{
|
||||||
|
config: Config{
|
||||||
|
MinRunners: 1,
|
||||||
|
MaxRunners: math.MaxInt32,
|
||||||
|
},
|
||||||
|
lastPatch: -1,
|
||||||
|
patchSeq: -1,
|
||||||
|
logger: &logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("initial scale when acquired == 0 and completed == 0", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(0, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("re-use the old state on count == 0 and completed == 0", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(2, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(0, 0)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 3, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("request back to 0 on job done", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(2, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(0, 1)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("desired patch is 0 but sequence continues on empty batch and min runners", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(3, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
assert.Equal(t, 4, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
|
||||||
|
patchID = w.setDesiredWorkerState(0, 3)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
|
||||||
|
// Empty batch on min runners
|
||||||
|
patchID = w.setDesiredWorkerState(0, 0)
|
||||||
|
assert.Equal(t, 0, patchID) // forcing the state
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 2, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetDesiredWorkerState_MaxSet(t *testing.T) {
|
||||||
|
logger := logr.Discard()
|
||||||
|
newEmptyWorker := func() *Worker {
|
||||||
|
return &Worker{
|
||||||
|
config: Config{
|
||||||
|
MinRunners: 0,
|
||||||
|
MaxRunners: 5,
|
||||||
|
},
|
||||||
|
lastPatch: -1,
|
||||||
|
patchSeq: -1,
|
||||||
|
logger: &logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("initial scale when acquired == 0 and completed == 0", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(0, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
assert.Equal(t, 0, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("re-use the old state on count == 0 and completed == 0", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(2, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(0, 0)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 2, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("request back to 0 on job done", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(2, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(0, 1)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 0, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scale up to max when count > max", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(6, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
assert.Equal(t, 5, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scale to max when count == max", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
w.setDesiredWorkerState(5, 0)
|
||||||
|
assert.Equal(t, 5, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scale to max when count > max and completed > 0", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(1, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(6, 1)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 5, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scale back to 0 when count was > max", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(6, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(0, 1)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 0, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("force 0 on empty batch and last patch == min runners", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(3, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
assert.Equal(t, 3, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
|
||||||
|
patchID = w.setDesiredWorkerState(0, 3)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 0, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
|
||||||
|
// Empty batch on min runners
|
||||||
|
patchID = w.setDesiredWorkerState(0, 0)
|
||||||
|
assert.Equal(t, 0, patchID) // forcing the state
|
||||||
|
assert.Equal(t, 0, w.lastPatch)
|
||||||
|
assert.Equal(t, 2, w.patchSeq)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetDesiredWorkerState_MinMaxSet(t *testing.T) {
|
||||||
|
logger := logr.Discard()
|
||||||
|
newEmptyWorker := func() *Worker {
|
||||||
|
return &Worker{
|
||||||
|
config: Config{
|
||||||
|
MinRunners: 1,
|
||||||
|
MaxRunners: 3,
|
||||||
|
},
|
||||||
|
lastPatch: -1,
|
||||||
|
patchSeq: -1,
|
||||||
|
logger: &logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("initial scale when acquired == 0 and completed == 0", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(0, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("re-use the old state on count == 0 and completed == 0", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(2, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(0, 0)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 3, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scale to min when count == 0", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(2, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
patchID = w.setDesiredWorkerState(0, 1)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scale up to max when count > max", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(4, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
assert.Equal(t, 3, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scale to max when count == max", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(3, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
assert.Equal(t, 3, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("force 0 on empty batch and last patch == min runners", func(t *testing.T) {
|
||||||
|
w := newEmptyWorker()
|
||||||
|
patchID := w.setDesiredWorkerState(3, 0)
|
||||||
|
assert.Equal(t, 0, patchID)
|
||||||
|
assert.Equal(t, 3, w.lastPatch)
|
||||||
|
assert.Equal(t, 0, w.patchSeq)
|
||||||
|
|
||||||
|
patchID = w.setDesiredWorkerState(0, 3)
|
||||||
|
assert.Equal(t, 1, patchID)
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 1, w.patchSeq)
|
||||||
|
|
||||||
|
// Empty batch on min runners
|
||||||
|
patchID = w.setDesiredWorkerState(0, 0)
|
||||||
|
assert.Equal(t, 0, patchID) // forcing the state
|
||||||
|
assert.Equal(t, 1, w.lastPatch)
|
||||||
|
assert.Equal(t, 2, w.patchSeq)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -129,7 +129,7 @@ func (m *AutoScalerClient) Close() error {
|
|||||||
return m.client.Close()
|
return m.client.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *AutoScalerClient) GetRunnerScaleSetMessage(ctx context.Context, handler func(msg *actions.RunnerScaleSetMessage) error) error {
|
func (m *AutoScalerClient) GetRunnerScaleSetMessage(ctx context.Context, handler func(msg *actions.RunnerScaleSetMessage) error, maxCapacity int) error {
|
||||||
if m.initialMessage != nil {
|
if m.initialMessage != nil {
|
||||||
err := handler(m.initialMessage)
|
err := handler(m.initialMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -141,7 +141,7 @@ func (m *AutoScalerClient) GetRunnerScaleSetMessage(ctx context.Context, handler
|
|||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
message, err := m.client.GetMessage(ctx, m.lastMessageId)
|
message, err := m.client.GetMessage(ctx, m.lastMessageId, maxCapacity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get message failed from refreshing client. %w", err)
|
return fmt.Errorf("get message failed from refreshing client. %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ func TestGetRunnerScaleSetMessage(t *testing.T) {
|
|||||||
Statistics: &actions.RunnerScaleSetStatistic{},
|
Statistics: &actions.RunnerScaleSetStatistic{},
|
||||||
}
|
}
|
||||||
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything).Return(session, nil)
|
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything).Return(session, nil)
|
||||||
mockSessionClient.On("GetMessage", ctx, int64(0)).Return(&actions.RunnerScaleSetMessage{
|
mockSessionClient.On("GetMessage", ctx, int64(0), mock.Anything).Return(&actions.RunnerScaleSetMessage{
|
||||||
MessageId: 1,
|
MessageId: 1,
|
||||||
MessageType: "test",
|
MessageType: "test",
|
||||||
Body: "test",
|
Body: "test",
|
||||||
@@ -332,7 +332,7 @@ func TestGetRunnerScaleSetMessage(t *testing.T) {
|
|||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
||||||
return nil
|
return nil
|
||||||
})
|
}, 10)
|
||||||
|
|
||||||
assert.NoError(t, err, "Error getting message")
|
assert.NoError(t, err, "Error getting message")
|
||||||
assert.Equal(t, int64(0), asClient.lastMessageId, "Initial message")
|
assert.Equal(t, int64(0), asClient.lastMessageId, "Initial message")
|
||||||
@@ -340,7 +340,7 @@ func TestGetRunnerScaleSetMessage(t *testing.T) {
|
|||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
||||||
return nil
|
return nil
|
||||||
})
|
}, 10)
|
||||||
|
|
||||||
assert.NoError(t, err, "Error getting message")
|
assert.NoError(t, err, "Error getting message")
|
||||||
assert.Equal(t, int64(1), asClient.lastMessageId, "Last message id should be updated")
|
assert.Equal(t, int64(1), asClient.lastMessageId, "Last message id should be updated")
|
||||||
@@ -368,7 +368,7 @@ func TestGetRunnerScaleSetMessage_HandleFailed(t *testing.T) {
|
|||||||
Statistics: &actions.RunnerScaleSetStatistic{},
|
Statistics: &actions.RunnerScaleSetStatistic{},
|
||||||
}
|
}
|
||||||
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything).Return(session, nil)
|
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything).Return(session, nil)
|
||||||
mockSessionClient.On("GetMessage", ctx, int64(0)).Return(&actions.RunnerScaleSetMessage{
|
mockSessionClient.On("GetMessage", ctx, int64(0), mock.Anything).Return(&actions.RunnerScaleSetMessage{
|
||||||
MessageId: 1,
|
MessageId: 1,
|
||||||
MessageType: "test",
|
MessageType: "test",
|
||||||
Body: "test",
|
Body: "test",
|
||||||
@@ -383,14 +383,14 @@ func TestGetRunnerScaleSetMessage_HandleFailed(t *testing.T) {
|
|||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
||||||
return nil
|
return nil
|
||||||
})
|
}, 10)
|
||||||
|
|
||||||
assert.NoError(t, err, "Error getting message")
|
assert.NoError(t, err, "Error getting message")
|
||||||
|
|
||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
||||||
return fmt.Errorf("error")
|
return fmt.Errorf("error")
|
||||||
})
|
}, 10)
|
||||||
|
|
||||||
assert.ErrorContains(t, err, "handle message failed. error", "Error getting message")
|
assert.ErrorContains(t, err, "handle message failed. error", "Error getting message")
|
||||||
assert.Equal(t, int64(0), asClient.lastMessageId, "Last message id should not be updated")
|
assert.Equal(t, int64(0), asClient.lastMessageId, "Last message id should not be updated")
|
||||||
@@ -419,7 +419,7 @@ func TestGetRunnerScaleSetMessage_HandleInitialMessage(t *testing.T) {
|
|||||||
TotalAssignedJobs: 2,
|
TotalAssignedJobs: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything).Return(session, nil)
|
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything, mock.Anything).Return(session, nil)
|
||||||
mockActionsClient.On("GetAcquirableJobs", ctx, 1).Return(&actions.AcquirableJobList{
|
mockActionsClient.On("GetAcquirableJobs", ctx, 1).Return(&actions.AcquirableJobList{
|
||||||
Count: 1,
|
Count: 1,
|
||||||
Jobs: []actions.AcquirableJob{
|
Jobs: []actions.AcquirableJob{
|
||||||
@@ -439,7 +439,7 @@ func TestGetRunnerScaleSetMessage_HandleInitialMessage(t *testing.T) {
|
|||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
||||||
return nil
|
return nil
|
||||||
})
|
}, 10)
|
||||||
|
|
||||||
assert.NoError(t, err, "Error getting message")
|
assert.NoError(t, err, "Error getting message")
|
||||||
assert.Nil(t, asClient.initialMessage, "Initial message should be nil")
|
assert.Nil(t, asClient.initialMessage, "Initial message should be nil")
|
||||||
@@ -488,7 +488,7 @@ func TestGetRunnerScaleSetMessage_HandleInitialMessageFailed(t *testing.T) {
|
|||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
||||||
return fmt.Errorf("error")
|
return fmt.Errorf("error")
|
||||||
})
|
}, 10)
|
||||||
|
|
||||||
assert.ErrorContains(t, err, "fail to process initial message. error", "Error getting message")
|
assert.ErrorContains(t, err, "fail to process initial message. error", "Error getting message")
|
||||||
assert.NotNil(t, asClient.initialMessage, "Initial message should be nil")
|
assert.NotNil(t, asClient.initialMessage, "Initial message should be nil")
|
||||||
@@ -516,8 +516,8 @@ func TestGetRunnerScaleSetMessage_RetryUntilGetMessage(t *testing.T) {
|
|||||||
Statistics: &actions.RunnerScaleSetStatistic{},
|
Statistics: &actions.RunnerScaleSetStatistic{},
|
||||||
}
|
}
|
||||||
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything).Return(session, nil)
|
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything).Return(session, nil)
|
||||||
mockSessionClient.On("GetMessage", ctx, int64(0)).Return(nil, nil).Times(3)
|
mockSessionClient.On("GetMessage", ctx, int64(0), mock.Anything).Return(nil, nil).Times(3)
|
||||||
mockSessionClient.On("GetMessage", ctx, int64(0)).Return(&actions.RunnerScaleSetMessage{
|
mockSessionClient.On("GetMessage", ctx, int64(0), mock.Anything).Return(&actions.RunnerScaleSetMessage{
|
||||||
MessageId: 1,
|
MessageId: 1,
|
||||||
MessageType: "test",
|
MessageType: "test",
|
||||||
Body: "test",
|
Body: "test",
|
||||||
@@ -532,13 +532,13 @@ func TestGetRunnerScaleSetMessage_RetryUntilGetMessage(t *testing.T) {
|
|||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
||||||
return nil
|
return nil
|
||||||
})
|
}, 10)
|
||||||
assert.NoError(t, err, "Error getting initial message")
|
assert.NoError(t, err, "Error getting initial message")
|
||||||
|
|
||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
||||||
return nil
|
return nil
|
||||||
})
|
}, 10)
|
||||||
|
|
||||||
assert.NoError(t, err, "Error getting message")
|
assert.NoError(t, err, "Error getting message")
|
||||||
assert.Equal(t, int64(1), asClient.lastMessageId, "Last message id should be updated")
|
assert.Equal(t, int64(1), asClient.lastMessageId, "Last message id should be updated")
|
||||||
@@ -565,7 +565,7 @@ func TestGetRunnerScaleSetMessage_ErrorOnGetMessage(t *testing.T) {
|
|||||||
Statistics: &actions.RunnerScaleSetStatistic{},
|
Statistics: &actions.RunnerScaleSetStatistic{},
|
||||||
}
|
}
|
||||||
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything).Return(session, nil)
|
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything).Return(session, nil)
|
||||||
mockSessionClient.On("GetMessage", ctx, int64(0)).Return(nil, fmt.Errorf("error"))
|
mockSessionClient.On("GetMessage", ctx, int64(0), mock.Anything).Return(nil, fmt.Errorf("error"))
|
||||||
|
|
||||||
asClient, err := NewAutoScalerClient(ctx, mockActionsClient, &logger, 1, func(asc *AutoScalerClient) {
|
asClient, err := NewAutoScalerClient(ctx, mockActionsClient, &logger, 1, func(asc *AutoScalerClient) {
|
||||||
asc.client = mockSessionClient
|
asc.client = mockSessionClient
|
||||||
@@ -575,12 +575,12 @@ func TestGetRunnerScaleSetMessage_ErrorOnGetMessage(t *testing.T) {
|
|||||||
// process initial message
|
// process initial message
|
||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
return nil
|
return nil
|
||||||
})
|
}, 10)
|
||||||
assert.NoError(t, err, "Error getting initial message")
|
assert.NoError(t, err, "Error getting initial message")
|
||||||
|
|
||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
return fmt.Errorf("Should not be called")
|
return fmt.Errorf("Should not be called")
|
||||||
})
|
}, 10)
|
||||||
|
|
||||||
assert.ErrorContains(t, err, "get message failed from refreshing client. error", "Error should be returned")
|
assert.ErrorContains(t, err, "get message failed from refreshing client. error", "Error should be returned")
|
||||||
assert.Equal(t, int64(0), asClient.lastMessageId, "Last message id should be updated")
|
assert.Equal(t, int64(0), asClient.lastMessageId, "Last message id should be updated")
|
||||||
@@ -608,7 +608,7 @@ func TestDeleteRunnerScaleSetMessage_Error(t *testing.T) {
|
|||||||
Statistics: &actions.RunnerScaleSetStatistic{},
|
Statistics: &actions.RunnerScaleSetStatistic{},
|
||||||
}
|
}
|
||||||
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything).Return(session, nil)
|
mockActionsClient.On("CreateMessageSession", ctx, 1, mock.Anything).Return(session, nil)
|
||||||
mockSessionClient.On("GetMessage", ctx, int64(0)).Return(&actions.RunnerScaleSetMessage{
|
mockSessionClient.On("GetMessage", ctx, int64(0), mock.Anything).Return(&actions.RunnerScaleSetMessage{
|
||||||
MessageId: 1,
|
MessageId: 1,
|
||||||
MessageType: "test",
|
MessageType: "test",
|
||||||
Body: "test",
|
Body: "test",
|
||||||
@@ -623,13 +623,13 @@ func TestDeleteRunnerScaleSetMessage_Error(t *testing.T) {
|
|||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
||||||
return nil
|
return nil
|
||||||
})
|
}, 10)
|
||||||
assert.NoError(t, err, "Error getting initial message")
|
assert.NoError(t, err, "Error getting initial message")
|
||||||
|
|
||||||
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
err = asClient.GetRunnerScaleSetMessage(ctx, func(msg *actions.RunnerScaleSetMessage) error {
|
||||||
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
logger.Info("Message received", "messageId", msg.MessageId, "messageType", msg.MessageType, "body", msg.Body)
|
||||||
return nil
|
return nil
|
||||||
})
|
}, 10)
|
||||||
|
|
||||||
assert.ErrorContains(t, err, "delete message failed from refreshing client. error", "Error getting message")
|
assert.ErrorContains(t, err, "delete message failed from refreshing client. error", "Error getting message")
|
||||||
assert.Equal(t, int64(1), asClient.lastMessageId, "Last message id should be updated")
|
assert.Equal(t, int64(1), asClient.lastMessageId, "Last message id should be updated")
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func (s *Service) Start() error {
|
|||||||
s.logger.Info("service is stopped.")
|
s.logger.Info("service is stopped.")
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
err := s.rsClient.GetRunnerScaleSetMessage(s.ctx, s.processMessage)
|
err := s.rsClient.GetRunnerScaleSetMessage(s.ctx, s.processMessage, s.settings.MaxRunners)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not get and process message. %w", err)
|
return fmt.Errorf("could not get and process message. %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func TestStart(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
mockRsClient.On("GetRunnerScaleSetMessage", service.ctx, mock.Anything).Run(func(args mock.Arguments) { cancel() }).Return(nil).Once()
|
mockRsClient.On("GetRunnerScaleSetMessage", service.ctx, mock.Anything, mock.Anything).Run(func(mock.Arguments) { cancel() }).Return(nil).Once()
|
||||||
|
|
||||||
err = service.Start()
|
err = service.Start()
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ func TestStart_ScaleToMinRunners(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
mockRsClient.On("GetRunnerScaleSetMessage", ctx, mock.Anything).Run(func(args mock.Arguments) {
|
mockRsClient.On("GetRunnerScaleSetMessage", ctx, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
||||||
_ = service.scaleForAssignedJobCount(5)
|
_ = service.scaleForAssignedJobCount(5)
|
||||||
}).Return(nil)
|
}).Return(nil)
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ func TestStart_ScaleToMinRunnersFailed(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
c := mockKubeManager.On("ScaleEphemeralRunnerSet", ctx, service.settings.Namespace, service.settings.ResourceName, 5).Return(fmt.Errorf("error")).Once()
|
c := mockKubeManager.On("ScaleEphemeralRunnerSet", ctx, service.settings.Namespace, service.settings.ResourceName, 5).Return(fmt.Errorf("error")).Once()
|
||||||
mockRsClient.On("GetRunnerScaleSetMessage", ctx, mock.Anything).Run(func(args mock.Arguments) {
|
mockRsClient.On("GetRunnerScaleSetMessage", ctx, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
||||||
_ = service.scaleForAssignedJobCount(5)
|
_ = service.scaleForAssignedJobCount(5)
|
||||||
}).Return(c.ReturnArguments.Get(0))
|
}).Return(c.ReturnArguments.Get(0))
|
||||||
|
|
||||||
@@ -172,8 +172,8 @@ func TestStart_GetMultipleMessages(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
mockRsClient.On("GetRunnerScaleSetMessage", service.ctx, mock.Anything).Return(nil).Times(5)
|
mockRsClient.On("GetRunnerScaleSetMessage", service.ctx, mock.Anything, mock.Anything).Return(nil).Times(5)
|
||||||
mockRsClient.On("GetRunnerScaleSetMessage", service.ctx, mock.Anything).Run(func(args mock.Arguments) { cancel() }).Return(nil).Once()
|
mockRsClient.On("GetRunnerScaleSetMessage", service.ctx, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { cancel() }).Return(nil).Once()
|
||||||
|
|
||||||
err = service.Start()
|
err = service.Start()
|
||||||
|
|
||||||
@@ -207,8 +207,8 @@ func TestStart_ErrorOnMessage(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
mockRsClient.On("GetRunnerScaleSetMessage", service.ctx, mock.Anything).Return(nil).Times(2)
|
mockRsClient.On("GetRunnerScaleSetMessage", service.ctx, mock.Anything, mock.Anything).Return(nil).Times(2)
|
||||||
mockRsClient.On("GetRunnerScaleSetMessage", service.ctx, mock.Anything).Return(fmt.Errorf("error")).Once()
|
mockRsClient.On("GetRunnerScaleSetMessage", service.ctx, mock.Anything, mock.Anything).Return(fmt.Errorf("error")).Once()
|
||||||
|
|
||||||
err = service.Start()
|
err = service.Start()
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ import (
|
|||||||
|
|
||||||
//go:generate mockery --inpackage --name=RunnerScaleSetClient
|
//go:generate mockery --inpackage --name=RunnerScaleSetClient
|
||||||
type RunnerScaleSetClient interface {
|
type RunnerScaleSetClient interface {
|
||||||
GetRunnerScaleSetMessage(ctx context.Context, handler func(msg *actions.RunnerScaleSetMessage) error) error
|
GetRunnerScaleSetMessage(ctx context.Context, handler func(msg *actions.RunnerScaleSetMessage) error, maxCapacity int) error
|
||||||
AcquireJobsForRunnerScaleSet(ctx context.Context, requestIds []int64) error
|
AcquireJobsForRunnerScaleSet(ctx context.Context, requestIds []int64) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ func (_m *MockRunnerScaleSetClient) AcquireJobsForRunnerScaleSet(ctx context.Con
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRunnerScaleSetMessage provides a mock function with given fields: ctx, handler
|
// GetRunnerScaleSetMessage provides a mock function with given fields: ctx, handler, maxCapacity
|
||||||
func (_m *MockRunnerScaleSetClient) GetRunnerScaleSetMessage(ctx context.Context, handler func(*actions.RunnerScaleSetMessage) error) error {
|
func (_m *MockRunnerScaleSetClient) GetRunnerScaleSetMessage(ctx context.Context, handler func(*actions.RunnerScaleSetMessage) error, maxCapacity int) error {
|
||||||
ret := _m.Called(ctx, handler)
|
ret := _m.Called(ctx, handler, maxCapacity)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, func(*actions.RunnerScaleSetMessage) error) error); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, func(*actions.RunnerScaleSetMessage) error, int) error); ok {
|
||||||
r0 = rf(ctx, handler)
|
r0 = rf(ctx, handler, maxCapacity)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,12 @@ func newSessionClient(client actions.ActionsService, logger *logr.Logger, sessio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *SessionRefreshingClient) GetMessage(ctx context.Context, lastMessageId int64) (*actions.RunnerScaleSetMessage, error) {
|
func (m *SessionRefreshingClient) GetMessage(ctx context.Context, lastMessageId int64, maxCapacity int) (*actions.RunnerScaleSetMessage, error) {
|
||||||
message, err := m.client.GetMessage(ctx, m.session.MessageQueueUrl, m.session.MessageQueueAccessToken, lastMessageId)
|
if maxCapacity < 0 {
|
||||||
|
return nil, fmt.Errorf("maxCapacity must be greater than or equal to 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
message, err := m.client.GetMessage(ctx, m.session.MessageQueueUrl, m.session.MessageQueueAccessToken, lastMessageId, maxCapacity)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return message, nil
|
return message, nil
|
||||||
}
|
}
|
||||||
@@ -42,7 +46,7 @@ func (m *SessionRefreshingClient) GetMessage(ctx context.Context, lastMessageId
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.session = session
|
m.session = session
|
||||||
message, err = m.client.GetMessage(ctx, m.session.MessageQueueUrl, m.session.MessageQueueAccessToken, lastMessageId)
|
message, err = m.client.GetMessage(ctx, m.session.MessageQueueUrl, m.session.MessageQueueAccessToken, lastMessageId, maxCapacity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("delete message failed after refresh message session. %w", err)
|
return nil, fmt.Errorf("delete message failed after refresh message session. %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,17 +31,17 @@ func TestGetMessage(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, session.MessageQueueAccessToken, int64(0)).Return(nil, nil).Once()
|
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, session.MessageQueueAccessToken, int64(0), 10).Return(nil, nil).Once()
|
||||||
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, session.MessageQueueAccessToken, int64(0)).Return(&actions.RunnerScaleSetMessage{MessageId: 1}, nil).Once()
|
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, session.MessageQueueAccessToken, int64(0), 10).Return(&actions.RunnerScaleSetMessage{MessageId: 1}, nil).Once()
|
||||||
|
|
||||||
client := newSessionClient(mockActionsClient, &logger, session)
|
client := newSessionClient(mockActionsClient, &logger, session)
|
||||||
|
|
||||||
msg, err := client.GetMessage(ctx, 0)
|
msg, err := client.GetMessage(ctx, 0, 10)
|
||||||
require.NoError(t, err, "GetMessage should not return an error")
|
require.NoError(t, err, "GetMessage should not return an error")
|
||||||
|
|
||||||
assert.Nil(t, msg, "GetMessage should return nil message")
|
assert.Nil(t, msg, "GetMessage should return nil message")
|
||||||
|
|
||||||
msg, err = client.GetMessage(ctx, 0)
|
msg, err = client.GetMessage(ctx, 0, 10)
|
||||||
require.NoError(t, err, "GetMessage should not return an error")
|
require.NoError(t, err, "GetMessage should not return an error")
|
||||||
|
|
||||||
assert.Equal(t, int64(1), msg.MessageId, "GetMessage should return a message with id 1")
|
assert.Equal(t, int64(1), msg.MessageId, "GetMessage should return a message with id 1")
|
||||||
@@ -146,11 +146,11 @@ func TestGetMessage_Error(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, session.MessageQueueAccessToken, int64(0)).Return(nil, fmt.Errorf("error")).Once()
|
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, session.MessageQueueAccessToken, int64(0), 10).Return(nil, fmt.Errorf("error")).Once()
|
||||||
|
|
||||||
client := newSessionClient(mockActionsClient, &logger, session)
|
client := newSessionClient(mockActionsClient, &logger, session)
|
||||||
|
|
||||||
msg, err := client.GetMessage(ctx, 0)
|
msg, err := client.GetMessage(ctx, 0, 10)
|
||||||
assert.ErrorContains(t, err, "get message failed. error", "GetMessage should return an error")
|
assert.ErrorContains(t, err, "get message failed. error", "GetMessage should return an error")
|
||||||
assert.Nil(t, msg, "GetMessage should return nil message")
|
assert.Nil(t, msg, "GetMessage should return nil message")
|
||||||
assert.True(t, mockActionsClient.AssertExpectations(t), "All expected calls to mockActionsClient should have been made")
|
assert.True(t, mockActionsClient.AssertExpectations(t), "All expected calls to mockActionsClient should have been made")
|
||||||
@@ -227,8 +227,8 @@ func TestGetMessage_RefreshToken(t *testing.T) {
|
|||||||
Id: 1,
|
Id: 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, session.MessageQueueAccessToken, int64(0)).Return(nil, &actions.MessageQueueTokenExpiredError{}).Once()
|
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, session.MessageQueueAccessToken, int64(0), 10).Return(nil, &actions.MessageQueueTokenExpiredError{}).Once()
|
||||||
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, "token2", int64(0)).Return(&actions.RunnerScaleSetMessage{
|
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, "token2", int64(0), 10).Return(&actions.RunnerScaleSetMessage{
|
||||||
MessageId: 1,
|
MessageId: 1,
|
||||||
MessageType: "test",
|
MessageType: "test",
|
||||||
Body: "test",
|
Body: "test",
|
||||||
@@ -243,7 +243,7 @@ func TestGetMessage_RefreshToken(t *testing.T) {
|
|||||||
}, nil).Once()
|
}, nil).Once()
|
||||||
|
|
||||||
client := newSessionClient(mockActionsClient, &logger, session)
|
client := newSessionClient(mockActionsClient, &logger, session)
|
||||||
msg, err := client.GetMessage(ctx, 0)
|
msg, err := client.GetMessage(ctx, 0, 10)
|
||||||
assert.NoError(t, err, "Error getting message")
|
assert.NoError(t, err, "Error getting message")
|
||||||
assert.Equal(t, int64(1), msg.MessageId, "message id should be updated")
|
assert.Equal(t, int64(1), msg.MessageId, "message id should be updated")
|
||||||
assert.Equal(t, "token2", client.session.MessageQueueAccessToken, "Message queue access token should be updated")
|
assert.Equal(t, "token2", client.session.MessageQueueAccessToken, "Message queue access token should be updated")
|
||||||
@@ -340,11 +340,11 @@ func TestGetMessage_RefreshToken_Failed(t *testing.T) {
|
|||||||
Id: 1,
|
Id: 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, session.MessageQueueAccessToken, int64(0)).Return(nil, &actions.MessageQueueTokenExpiredError{}).Once()
|
mockActionsClient.On("GetMessage", ctx, session.MessageQueueUrl, session.MessageQueueAccessToken, int64(0), 10).Return(nil, &actions.MessageQueueTokenExpiredError{}).Once()
|
||||||
mockActionsClient.On("RefreshMessageSession", ctx, session.RunnerScaleSet.Id, session.SessionId).Return(nil, fmt.Errorf("error"))
|
mockActionsClient.On("RefreshMessageSession", ctx, session.RunnerScaleSet.Id, session.SessionId).Return(nil, fmt.Errorf("error"))
|
||||||
|
|
||||||
client := newSessionClient(mockActionsClient, &logger, session)
|
client := newSessionClient(mockActionsClient, &logger, session)
|
||||||
msg, err := client.GetMessage(ctx, 0)
|
msg, err := client.GetMessage(ctx, 0, 10)
|
||||||
assert.ErrorContains(t, err, "refresh message session failed. error", "Error should be returned")
|
assert.ErrorContains(t, err, "refresh message session failed. error", "Error should be returned")
|
||||||
assert.Nil(t, msg, "Message should be nil")
|
assert.Nil(t, msg, "Message should be nil")
|
||||||
assert.Equal(t, "token", client.session.MessageQueueAccessToken, "Message queue access token should not be updated")
|
assert.Equal(t, "token", client.session.MessageQueueAccessToken, "Message queue access token should not be updated")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
|||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
controller-gen.kubebuilder.io/version: v0.13.0
|
controller-gen.kubebuilder.io/version: v0.14.0
|
||||||
name: horizontalrunnerautoscalers.actions.summerwind.dev
|
name: horizontalrunnerautoscalers.actions.summerwind.dev
|
||||||
spec:
|
spec:
|
||||||
group: actions.summerwind.dev
|
group: actions.summerwind.dev
|
||||||
@@ -35,10 +35,19 @@ spec:
|
|||||||
description: HorizontalRunnerAutoscaler is the Schema for the horizontalrunnerautoscaler API
|
description: HorizontalRunnerAutoscaler is the Schema for the horizontalrunnerautoscaler API
|
||||||
properties:
|
properties:
|
||||||
apiVersion:
|
apiVersion:
|
||||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
description: |-
|
||||||
|
APIVersion defines the versioned schema of this representation of an object.
|
||||||
|
Servers should convert recognized schemas to the latest internal value, and
|
||||||
|
may reject unrecognized values.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||||
type: string
|
type: string
|
||||||
kind:
|
kind:
|
||||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
description: |-
|
||||||
|
Kind is a string value representing the REST resource this object represents.
|
||||||
|
Servers may infer this from the endpoint the client submits requests to.
|
||||||
|
Cannot be updated.
|
||||||
|
In CamelCase.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||||
type: string
|
type: string
|
||||||
metadata:
|
metadata:
|
||||||
type: object
|
type: object
|
||||||
@@ -47,7 +56,9 @@ spec:
|
|||||||
properties:
|
properties:
|
||||||
capacityReservations:
|
capacityReservations:
|
||||||
items:
|
items:
|
||||||
description: CapacityReservation specifies the number of replicas temporarily added to the scale target until ExpirationTime.
|
description: |-
|
||||||
|
CapacityReservation specifies the number of replicas temporarily added
|
||||||
|
to the scale target until ExpirationTime.
|
||||||
properties:
|
properties:
|
||||||
effectiveTime:
|
effectiveTime:
|
||||||
format: date-time
|
format: date-time
|
||||||
@@ -79,30 +90,46 @@ spec:
|
|||||||
items:
|
items:
|
||||||
properties:
|
properties:
|
||||||
repositoryNames:
|
repositoryNames:
|
||||||
description: RepositoryNames is the list of repository names to be used for calculating the metric. For example, a repository name is the REPO part of `github.com/USER/REPO`.
|
description: |-
|
||||||
|
RepositoryNames is the list of repository names to be used for calculating the metric.
|
||||||
|
For example, a repository name is the REPO part of `github.com/USER/REPO`.
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
scaleDownAdjustment:
|
scaleDownAdjustment:
|
||||||
description: ScaleDownAdjustment is the number of runners removed on scale-down. You can only specify either ScaleDownFactor or ScaleDownAdjustment.
|
description: |-
|
||||||
|
ScaleDownAdjustment is the number of runners removed on scale-down.
|
||||||
|
You can only specify either ScaleDownFactor or ScaleDownAdjustment.
|
||||||
type: integer
|
type: integer
|
||||||
scaleDownFactor:
|
scaleDownFactor:
|
||||||
description: ScaleDownFactor is the multiplicative factor applied to the current number of runners used to determine how many pods should be removed.
|
description: |-
|
||||||
|
ScaleDownFactor is the multiplicative factor applied to the current number of runners used
|
||||||
|
to determine how many pods should be removed.
|
||||||
type: string
|
type: string
|
||||||
scaleDownThreshold:
|
scaleDownThreshold:
|
||||||
description: ScaleDownThreshold is the percentage of busy runners less than which will trigger the hpa to scale the runners down.
|
description: |-
|
||||||
|
ScaleDownThreshold is the percentage of busy runners less than which will
|
||||||
|
trigger the hpa to scale the runners down.
|
||||||
type: string
|
type: string
|
||||||
scaleUpAdjustment:
|
scaleUpAdjustment:
|
||||||
description: ScaleUpAdjustment is the number of runners added on scale-up. You can only specify either ScaleUpFactor or ScaleUpAdjustment.
|
description: |-
|
||||||
|
ScaleUpAdjustment is the number of runners added on scale-up.
|
||||||
|
You can only specify either ScaleUpFactor or ScaleUpAdjustment.
|
||||||
type: integer
|
type: integer
|
||||||
scaleUpFactor:
|
scaleUpFactor:
|
||||||
description: ScaleUpFactor is the multiplicative factor applied to the current number of runners used to determine how many pods should be added.
|
description: |-
|
||||||
|
ScaleUpFactor is the multiplicative factor applied to the current number of runners used
|
||||||
|
to determine how many pods should be added.
|
||||||
type: string
|
type: string
|
||||||
scaleUpThreshold:
|
scaleUpThreshold:
|
||||||
description: ScaleUpThreshold is the percentage of busy runners greater than which will trigger the hpa to scale runners up.
|
description: |-
|
||||||
|
ScaleUpThreshold is the percentage of busy runners greater than which will
|
||||||
|
trigger the hpa to scale runners up.
|
||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
description: Type is the type of metric to be used for autoscaling. It can be TotalNumberOfQueuedAndInProgressWorkflowRuns or PercentageRunnersBusy.
|
description: |-
|
||||||
|
Type is the type of metric to be used for autoscaling.
|
||||||
|
It can be TotalNumberOfQueuedAndInProgressWorkflowRuns or PercentageRunnersBusy.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
@@ -110,7 +137,9 @@ spec:
|
|||||||
description: MinReplicas is the minimum number of replicas the deployment is allowed to scale
|
description: MinReplicas is the minimum number of replicas the deployment is allowed to scale
|
||||||
type: integer
|
type: integer
|
||||||
scaleDownDelaySecondsAfterScaleOut:
|
scaleDownDelaySecondsAfterScaleOut:
|
||||||
description: ScaleDownDelaySecondsAfterScaleUp is the approximate delay for a scale down followed by a scale up Used to prevent flapping (down->up->down->... loop)
|
description: |-
|
||||||
|
ScaleDownDelaySecondsAfterScaleUp is the approximate delay for a scale down followed by a scale up
|
||||||
|
Used to prevent flapping (down->up->down->... loop)
|
||||||
type: integer
|
type: integer
|
||||||
scaleTargetRef:
|
scaleTargetRef:
|
||||||
description: ScaleTargetRef is the reference to scaled resource like RunnerDeployment
|
description: ScaleTargetRef is the reference to scaled resource like RunnerDeployment
|
||||||
@@ -126,7 +155,18 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
scaleUpTriggers:
|
scaleUpTriggers:
|
||||||
description: "ScaleUpTriggers is an experimental feature to increase the desired replicas by 1 on each webhook requested received by the webhookBasedAutoscaler. \n This feature requires you to also enable and deploy the webhookBasedAutoscaler onto your cluster. \n Note that the added runners remain until the next sync period at least, and they may or may not be used by GitHub Actions depending on the timing. They are intended to be used to gain \"resource slack\" immediately after you receive a webhook from GitHub, so that you can loosely expect MinReplicas runners to be always available."
|
description: |-
|
||||||
|
ScaleUpTriggers is an experimental feature to increase the desired replicas by 1
|
||||||
|
on each webhook requested received by the webhookBasedAutoscaler.
|
||||||
|
|
||||||
|
|
||||||
|
This feature requires you to also enable and deploy the webhookBasedAutoscaler onto your cluster.
|
||||||
|
|
||||||
|
|
||||||
|
Note that the added runners remain until the next sync period at least,
|
||||||
|
and they may or may not be used by GitHub Actions depending on the timing.
|
||||||
|
They are intended to be used to gain "resource slack" immediately after you
|
||||||
|
receive a webhook from GitHub, so that you can loosely expect MinReplicas runners to be always available.
|
||||||
items:
|
items:
|
||||||
properties:
|
properties:
|
||||||
amount:
|
amount:
|
||||||
@@ -139,12 +179,18 @@ spec:
|
|||||||
description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run
|
description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run
|
||||||
properties:
|
properties:
|
||||||
names:
|
names:
|
||||||
description: Names is a list of GitHub Actions glob patterns. Any check_run event whose name matches one of patterns in the list can trigger autoscaling. Note that check_run name seem to equal to the job name you've defined in your actions workflow yaml file. So it is very likely that you can utilize this to trigger depending on the job.
|
description: |-
|
||||||
|
Names is a list of GitHub Actions glob patterns.
|
||||||
|
Any check_run event whose name matches one of patterns in the list can trigger autoscaling.
|
||||||
|
Note that check_run name seem to equal to the job name you've defined in your actions workflow yaml file.
|
||||||
|
So it is very likely that you can utilize this to trigger depending on the job.
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
repositories:
|
repositories:
|
||||||
description: Repositories is a list of GitHub repositories. Any check_run event whose repository matches one of repositories in the list can trigger autoscaling.
|
description: |-
|
||||||
|
Repositories is a list of GitHub repositories.
|
||||||
|
Any check_run event whose repository matches one of repositories in the list can trigger autoscaling.
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
@@ -169,7 +215,9 @@ spec:
|
|||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
push:
|
push:
|
||||||
description: PushSpec is the condition for triggering scale-up on push event Also see https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push
|
description: |-
|
||||||
|
PushSpec is the condition for triggering scale-up on push event
|
||||||
|
Also see https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push
|
||||||
type: object
|
type: object
|
||||||
workflowJob:
|
workflowJob:
|
||||||
description: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_job
|
description: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_job
|
||||||
@@ -178,23 +226,33 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
scheduledOverrides:
|
scheduledOverrides:
|
||||||
description: ScheduledOverrides is the list of ScheduledOverride. It can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule. The earlier a scheduled override is, the higher it is prioritized.
|
description: |-
|
||||||
|
ScheduledOverrides is the list of ScheduledOverride.
|
||||||
|
It can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule.
|
||||||
|
The earlier a scheduled override is, the higher it is prioritized.
|
||||||
items:
|
items:
|
||||||
description: ScheduledOverride can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule. A schedule can optionally be recurring, so that the corresponding override happens every day, week, month, or year.
|
description: |-
|
||||||
|
ScheduledOverride can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule.
|
||||||
|
A schedule can optionally be recurring, so that the corresponding override happens every day, week, month, or year.
|
||||||
properties:
|
properties:
|
||||||
endTime:
|
endTime:
|
||||||
description: EndTime is the time at which the first override ends.
|
description: EndTime is the time at which the first override ends.
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
minReplicas:
|
minReplicas:
|
||||||
description: MinReplicas is the number of runners while overriding. If omitted, it doesn't override minReplicas.
|
description: |-
|
||||||
|
MinReplicas is the number of runners while overriding.
|
||||||
|
If omitted, it doesn't override minReplicas.
|
||||||
minimum: 0
|
minimum: 0
|
||||||
nullable: true
|
nullable: true
|
||||||
type: integer
|
type: integer
|
||||||
recurrenceRule:
|
recurrenceRule:
|
||||||
properties:
|
properties:
|
||||||
frequency:
|
frequency:
|
||||||
description: Frequency is the name of a predefined interval of each recurrence. The valid values are "Daily", "Weekly", "Monthly", and "Yearly". If empty, the corresponding override happens only once.
|
description: |-
|
||||||
|
Frequency is the name of a predefined interval of each recurrence.
|
||||||
|
The valid values are "Daily", "Weekly", "Monthly", and "Yearly".
|
||||||
|
If empty, the corresponding override happens only once.
|
||||||
enum:
|
enum:
|
||||||
- Daily
|
- Daily
|
||||||
- Weekly
|
- Weekly
|
||||||
@@ -202,7 +260,9 @@ spec:
|
|||||||
- Yearly
|
- Yearly
|
||||||
type: string
|
type: string
|
||||||
untilTime:
|
untilTime:
|
||||||
description: UntilTime is the time of the final recurrence. If empty, the schedule recurs forever.
|
description: |-
|
||||||
|
UntilTime is the time of the final recurrence.
|
||||||
|
If empty, the schedule recurs forever.
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
@@ -231,18 +291,24 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
desiredReplicas:
|
desiredReplicas:
|
||||||
description: DesiredReplicas is the total number of desired, non-terminated and latest pods to be set for the primary RunnerSet This doesn't include outdated pods while upgrading the deployment and replacing the runnerset.
|
description: |-
|
||||||
|
DesiredReplicas is the total number of desired, non-terminated and latest pods to be set for the primary RunnerSet
|
||||||
|
This doesn't include outdated pods while upgrading the deployment and replacing the runnerset.
|
||||||
type: integer
|
type: integer
|
||||||
lastSuccessfulScaleOutTime:
|
lastSuccessfulScaleOutTime:
|
||||||
format: date-time
|
format: date-time
|
||||||
nullable: true
|
nullable: true
|
||||||
type: string
|
type: string
|
||||||
observedGeneration:
|
observedGeneration:
|
||||||
description: ObservedGeneration is the most recent generation observed for the target. It corresponds to e.g. RunnerDeployment's generation, which is updated on mutation by the API Server.
|
description: |-
|
||||||
|
ObservedGeneration is the most recent generation observed for the target. It corresponds to e.g.
|
||||||
|
RunnerDeployment's generation, which is updated on mutation by the API Server.
|
||||||
format: int64
|
format: int64
|
||||||
type: integer
|
type: integer
|
||||||
scheduledOverridesSummary:
|
scheduledOverridesSummary:
|
||||||
description: ScheduledOverridesSummary is the summary of active and upcoming scheduled overrides to be shown in e.g. a column of a `kubectl get hra` output for observability.
|
description: |-
|
||||||
|
ScheduledOverridesSummary is the summary of active and upcoming scheduled overrides to be shown in e.g. a column of a `kubectl get hra` output
|
||||||
|
for observability.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -690,30 +690,6 @@ func (r *AutoscalingListenerReconciler) publishRunningListener(autoscalingListen
|
|||||||
|
|
||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
func (r *AutoscalingListenerReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *AutoscalingListenerReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
groupVersionIndexer := func(rawObj client.Object) []string {
|
|
||||||
groupVersion := v1alpha1.GroupVersion.String()
|
|
||||||
owner := metav1.GetControllerOf(rawObj)
|
|
||||||
if owner == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...make sure it is owned by this controller
|
|
||||||
if owner.APIVersion != groupVersion || owner.Kind != "AutoscalingListener" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...and if so, return it
|
|
||||||
return []string{owner.Name}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &corev1.Pod{}, resourceOwnerKey, groupVersionIndexer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &corev1.ServiceAccount{}, resourceOwnerKey, groupVersionIndexer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
labelBasedWatchFunc := func(_ context.Context, obj client.Object) []reconcile.Request {
|
labelBasedWatchFunc := func(_ context.Context, obj client.Object) []reconcile.Request {
|
||||||
var requests []reconcile.Request
|
var requests []reconcile.Request
|
||||||
labels := obj.GetLabels()
|
labels := obj.GetLabels()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
|
||||||
actionsv1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -34,9 +34,9 @@ var _ = Describe("Test AutoScalingListener controller", func() {
|
|||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
var mgr ctrl.Manager
|
var mgr ctrl.Manager
|
||||||
var autoscalingNS *corev1.Namespace
|
var autoscalingNS *corev1.Namespace
|
||||||
var autoscalingRunnerSet *actionsv1alpha1.AutoscalingRunnerSet
|
var autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet
|
||||||
var configSecret *corev1.Secret
|
var configSecret *corev1.Secret
|
||||||
var autoscalingListener *actionsv1alpha1.AutoscalingListener
|
var autoscalingListener *v1alpha1.AutoscalingListener
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
@@ -53,12 +53,12 @@ var _ = Describe("Test AutoScalingListener controller", func() {
|
|||||||
|
|
||||||
min := 1
|
min := 1
|
||||||
max := 10
|
max := 10
|
||||||
autoscalingRunnerSet = &actionsv1alpha1.AutoscalingRunnerSet{
|
autoscalingRunnerSet = &v1alpha1.AutoscalingRunnerSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-asrs",
|
Name: "test-asrs",
|
||||||
Namespace: autoscalingNS.Name,
|
Namespace: autoscalingNS.Name,
|
||||||
},
|
},
|
||||||
Spec: actionsv1alpha1.AutoscalingRunnerSetSpec{
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
MaxRunners: &max,
|
MaxRunners: &max,
|
||||||
@@ -79,12 +79,12 @@ var _ = Describe("Test AutoScalingListener controller", func() {
|
|||||||
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
||||||
|
|
||||||
autoscalingListener = &actionsv1alpha1.AutoscalingListener{
|
autoscalingListener = &v1alpha1.AutoscalingListener{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-asl",
|
Name: "test-asl",
|
||||||
Namespace: autoscalingNS.Name,
|
Namespace: autoscalingNS.Name,
|
||||||
},
|
},
|
||||||
Spec: actionsv1alpha1.AutoscalingListenerSpec{
|
Spec: v1alpha1.AutoscalingListenerSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
RunnerScaleSetId: 1,
|
RunnerScaleSetId: 1,
|
||||||
@@ -119,7 +119,7 @@ var _ = Describe("Test AutoScalingListener controller", func() {
|
|||||||
).Should(Succeed(), "Config secret should be created")
|
).Should(Succeed(), "Config secret should be created")
|
||||||
|
|
||||||
// Check if finalizer is added
|
// Check if finalizer is added
|
||||||
created := new(actionsv1alpha1.AutoscalingListener)
|
created := new(v1alpha1.AutoscalingListener)
|
||||||
Eventually(
|
Eventually(
|
||||||
func() (string, error) {
|
func() (string, error) {
|
||||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Namespace}, created)
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Namespace}, created)
|
||||||
@@ -298,7 +298,7 @@ var _ = Describe("Test AutoScalingListener controller", func() {
|
|||||||
// The AutoScalingListener should be deleted
|
// The AutoScalingListener should be deleted
|
||||||
Eventually(
|
Eventually(
|
||||||
func() error {
|
func() error {
|
||||||
listenerList := new(actionsv1alpha1.AutoscalingListenerList)
|
listenerList := new(v1alpha1.AutoscalingListenerList)
|
||||||
err := k8sClient.List(ctx, listenerList, client.InNamespace(autoscalingListener.Namespace), client.MatchingFields{".metadata.name": autoscalingListener.Name})
|
err := k8sClient.List(ctx, listenerList, client.InNamespace(autoscalingListener.Namespace), client.MatchingFields{".metadata.name": autoscalingListener.Name})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -415,9 +415,9 @@ var _ = Describe("Test AutoScalingListener customization", func() {
|
|||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
var mgr ctrl.Manager
|
var mgr ctrl.Manager
|
||||||
var autoscalingNS *corev1.Namespace
|
var autoscalingNS *corev1.Namespace
|
||||||
var autoscalingRunnerSet *actionsv1alpha1.AutoscalingRunnerSet
|
var autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet
|
||||||
var configSecret *corev1.Secret
|
var configSecret *corev1.Secret
|
||||||
var autoscalingListener *actionsv1alpha1.AutoscalingListener
|
var autoscalingListener *v1alpha1.AutoscalingListener
|
||||||
|
|
||||||
var runAsUser int64 = 1001
|
var runAsUser int64 = 1001
|
||||||
|
|
||||||
@@ -458,12 +458,12 @@ var _ = Describe("Test AutoScalingListener customization", func() {
|
|||||||
|
|
||||||
min := 1
|
min := 1
|
||||||
max := 10
|
max := 10
|
||||||
autoscalingRunnerSet = &actionsv1alpha1.AutoscalingRunnerSet{
|
autoscalingRunnerSet = &v1alpha1.AutoscalingRunnerSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-asrs",
|
Name: "test-asrs",
|
||||||
Namespace: autoscalingNS.Name,
|
Namespace: autoscalingNS.Name,
|
||||||
},
|
},
|
||||||
Spec: actionsv1alpha1.AutoscalingRunnerSetSpec{
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
MaxRunners: &max,
|
MaxRunners: &max,
|
||||||
@@ -484,12 +484,12 @@ var _ = Describe("Test AutoScalingListener customization", func() {
|
|||||||
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
||||||
|
|
||||||
autoscalingListener = &actionsv1alpha1.AutoscalingListener{
|
autoscalingListener = &v1alpha1.AutoscalingListener{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-asltest",
|
Name: "test-asltest",
|
||||||
Namespace: autoscalingNS.Name,
|
Namespace: autoscalingNS.Name,
|
||||||
},
|
},
|
||||||
Spec: actionsv1alpha1.AutoscalingListenerSpec{
|
Spec: v1alpha1.AutoscalingListenerSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
RunnerScaleSetId: 1,
|
RunnerScaleSetId: 1,
|
||||||
@@ -512,7 +512,7 @@ var _ = Describe("Test AutoScalingListener customization", func() {
|
|||||||
Context("When creating a new AutoScalingListener", func() {
|
Context("When creating a new AutoScalingListener", func() {
|
||||||
It("It should create customized pod with applied configuration", func() {
|
It("It should create customized pod with applied configuration", func() {
|
||||||
// Check if finalizer is added
|
// Check if finalizer is added
|
||||||
created := new(actionsv1alpha1.AutoscalingListener)
|
created := new(v1alpha1.AutoscalingListener)
|
||||||
Eventually(
|
Eventually(
|
||||||
func() (string, error) {
|
func() (string, error) {
|
||||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Namespace}, created)
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Namespace}, created)
|
||||||
@@ -570,19 +570,19 @@ var _ = Describe("Test AutoScalingListener controller with proxy", func() {
|
|||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
var mgr ctrl.Manager
|
var mgr ctrl.Manager
|
||||||
var autoscalingNS *corev1.Namespace
|
var autoscalingNS *corev1.Namespace
|
||||||
var autoscalingRunnerSet *actionsv1alpha1.AutoscalingRunnerSet
|
var autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet
|
||||||
var configSecret *corev1.Secret
|
var configSecret *corev1.Secret
|
||||||
var autoscalingListener *actionsv1alpha1.AutoscalingListener
|
var autoscalingListener *v1alpha1.AutoscalingListener
|
||||||
|
|
||||||
createRunnerSetAndListener := func(proxy *actionsv1alpha1.ProxyConfig) {
|
createRunnerSetAndListener := func(proxy *v1alpha1.ProxyConfig) {
|
||||||
min := 1
|
min := 1
|
||||||
max := 10
|
max := 10
|
||||||
autoscalingRunnerSet = &actionsv1alpha1.AutoscalingRunnerSet{
|
autoscalingRunnerSet = &v1alpha1.AutoscalingRunnerSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-asrs",
|
Name: "test-asrs",
|
||||||
Namespace: autoscalingNS.Name,
|
Namespace: autoscalingNS.Name,
|
||||||
},
|
},
|
||||||
Spec: actionsv1alpha1.AutoscalingRunnerSetSpec{
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
MaxRunners: &max,
|
MaxRunners: &max,
|
||||||
@@ -604,12 +604,12 @@ var _ = Describe("Test AutoScalingListener controller with proxy", func() {
|
|||||||
err := k8sClient.Create(ctx, autoscalingRunnerSet)
|
err := k8sClient.Create(ctx, autoscalingRunnerSet)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
||||||
|
|
||||||
autoscalingListener = &actionsv1alpha1.AutoscalingListener{
|
autoscalingListener = &v1alpha1.AutoscalingListener{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-asl",
|
Name: "test-asl",
|
||||||
Namespace: autoscalingNS.Name,
|
Namespace: autoscalingNS.Name,
|
||||||
},
|
},
|
||||||
Spec: actionsv1alpha1.AutoscalingListenerSpec{
|
Spec: v1alpha1.AutoscalingListenerSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
RunnerScaleSetId: 1,
|
RunnerScaleSetId: 1,
|
||||||
@@ -658,12 +658,12 @@ var _ = Describe("Test AutoScalingListener controller with proxy", func() {
|
|||||||
err := k8sClient.Create(ctx, proxyCredentials)
|
err := k8sClient.Create(ctx, proxyCredentials)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to create proxy credentials secret")
|
Expect(err).NotTo(HaveOccurred(), "failed to create proxy credentials secret")
|
||||||
|
|
||||||
proxy := &actionsv1alpha1.ProxyConfig{
|
proxy := &v1alpha1.ProxyConfig{
|
||||||
HTTP: &actionsv1alpha1.ProxyServerConfig{
|
HTTP: &v1alpha1.ProxyServerConfig{
|
||||||
Url: "http://localhost:8080",
|
Url: "http://localhost:8080",
|
||||||
CredentialSecretRef: "proxy-credentials",
|
CredentialSecretRef: "proxy-credentials",
|
||||||
},
|
},
|
||||||
HTTPS: &actionsv1alpha1.ProxyServerConfig{
|
HTTPS: &v1alpha1.ProxyServerConfig{
|
||||||
Url: "https://localhost:8443",
|
Url: "https://localhost:8443",
|
||||||
CredentialSecretRef: "proxy-credentials",
|
CredentialSecretRef: "proxy-credentials",
|
||||||
},
|
},
|
||||||
@@ -766,19 +766,19 @@ var _ = Describe("Test AutoScalingListener controller with template modification
|
|||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
var mgr ctrl.Manager
|
var mgr ctrl.Manager
|
||||||
var autoscalingNS *corev1.Namespace
|
var autoscalingNS *corev1.Namespace
|
||||||
var autoscalingRunnerSet *actionsv1alpha1.AutoscalingRunnerSet
|
var autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet
|
||||||
var configSecret *corev1.Secret
|
var configSecret *corev1.Secret
|
||||||
var autoscalingListener *actionsv1alpha1.AutoscalingListener
|
var autoscalingListener *v1alpha1.AutoscalingListener
|
||||||
|
|
||||||
createRunnerSetAndListener := func(listenerTemplate *corev1.PodTemplateSpec) {
|
createRunnerSetAndListener := func(listenerTemplate *corev1.PodTemplateSpec) {
|
||||||
min := 1
|
min := 1
|
||||||
max := 10
|
max := 10
|
||||||
autoscalingRunnerSet = &actionsv1alpha1.AutoscalingRunnerSet{
|
autoscalingRunnerSet = &v1alpha1.AutoscalingRunnerSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-asrs",
|
Name: "test-asrs",
|
||||||
Namespace: autoscalingNS.Name,
|
Namespace: autoscalingNS.Name,
|
||||||
},
|
},
|
||||||
Spec: actionsv1alpha1.AutoscalingRunnerSetSpec{
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
MaxRunners: &max,
|
MaxRunners: &max,
|
||||||
@@ -800,12 +800,12 @@ var _ = Describe("Test AutoScalingListener controller with template modification
|
|||||||
err := k8sClient.Create(ctx, autoscalingRunnerSet)
|
err := k8sClient.Create(ctx, autoscalingRunnerSet)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
||||||
|
|
||||||
autoscalingListener = &actionsv1alpha1.AutoscalingListener{
|
autoscalingListener = &v1alpha1.AutoscalingListener{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-asl",
|
Name: "test-asl",
|
||||||
Namespace: autoscalingNS.Name,
|
Namespace: autoscalingNS.Name,
|
||||||
},
|
},
|
||||||
Spec: actionsv1alpha1.AutoscalingListenerSpec{
|
Spec: v1alpha1.AutoscalingListenerSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
RunnerScaleSetId: 1,
|
RunnerScaleSetId: 1,
|
||||||
@@ -915,9 +915,9 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
|
|||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
var mgr ctrl.Manager
|
var mgr ctrl.Manager
|
||||||
var autoscalingNS *corev1.Namespace
|
var autoscalingNS *corev1.Namespace
|
||||||
var autoscalingRunnerSet *actionsv1alpha1.AutoscalingRunnerSet
|
var autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet
|
||||||
var configSecret *corev1.Secret
|
var configSecret *corev1.Secret
|
||||||
var autoscalingListener *actionsv1alpha1.AutoscalingListener
|
var autoscalingListener *v1alpha1.AutoscalingListener
|
||||||
var rootCAConfigMap *corev1.ConfigMap
|
var rootCAConfigMap *corev1.ConfigMap
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
@@ -955,16 +955,16 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
|
|||||||
|
|
||||||
min := 1
|
min := 1
|
||||||
max := 10
|
max := 10
|
||||||
autoscalingRunnerSet = &actionsv1alpha1.AutoscalingRunnerSet{
|
autoscalingRunnerSet = &v1alpha1.AutoscalingRunnerSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-asrs",
|
Name: "test-asrs",
|
||||||
Namespace: autoscalingNS.Name,
|
Namespace: autoscalingNS.Name,
|
||||||
},
|
},
|
||||||
Spec: actionsv1alpha1.AutoscalingRunnerSetSpec{
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
GitHubServerTLS: &actionsv1alpha1.GitHubServerTLSConfig{
|
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
||||||
CertificateFrom: &actionsv1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
Name: rootCAConfigMap.Name,
|
Name: rootCAConfigMap.Name,
|
||||||
@@ -991,16 +991,16 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
|
|||||||
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
||||||
|
|
||||||
autoscalingListener = &actionsv1alpha1.AutoscalingListener{
|
autoscalingListener = &v1alpha1.AutoscalingListener{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-asl",
|
Name: "test-asl",
|
||||||
Namespace: autoscalingNS.Name,
|
Namespace: autoscalingNS.Name,
|
||||||
},
|
},
|
||||||
Spec: actionsv1alpha1.AutoscalingListenerSpec{
|
Spec: v1alpha1.AutoscalingListenerSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
GitHubServerTLS: &actionsv1alpha1.GitHubServerTLSConfig{
|
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
||||||
CertificateFrom: &actionsv1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
Name: rootCAConfigMap.Name,
|
Name: rootCAConfigMap.Name,
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import (
|
|||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
@@ -42,10 +41,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
labelKeyRunnerSpecHash = "runner-spec-hash"
|
annotationKeyRunnerSpecHash = "actions.github.com/runner-spec-hash"
|
||||||
|
// annotationKeyValuesHash is hash of the entire values json.
|
||||||
|
// This is used to determine if the values have changed, so we can
|
||||||
|
// re-create listener.
|
||||||
|
annotationKeyValuesHash = "actions.github.com/values-hash"
|
||||||
|
|
||||||
autoscalingRunnerSetFinalizerName = "autoscalingrunnerset.actions.github.com/finalizer"
|
autoscalingRunnerSetFinalizerName = "autoscalingrunnerset.actions.github.com/finalizer"
|
||||||
runnerScaleSetIdAnnotationKey = "runner-scale-set-id"
|
runnerScaleSetIdAnnotationKey = "runner-scale-set-id"
|
||||||
runnerScaleSetNameAnnotationKey = "runner-scale-set-name"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UpdateStrategy string
|
type UpdateStrategy string
|
||||||
@@ -205,7 +208,7 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the runner scale set name is up to date
|
// Make sure the runner scale set name is up to date
|
||||||
currentRunnerScaleSetName, ok := autoscalingRunnerSet.Annotations[runnerScaleSetNameAnnotationKey]
|
currentRunnerScaleSetName, ok := autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerScaleSetName]
|
||||||
if !ok || (len(autoscalingRunnerSet.Spec.RunnerScaleSetName) > 0 && !strings.EqualFold(currentRunnerScaleSetName, autoscalingRunnerSet.Spec.RunnerScaleSetName)) {
|
if !ok || (len(autoscalingRunnerSet.Spec.RunnerScaleSetName) > 0 && !strings.EqualFold(currentRunnerScaleSetName, autoscalingRunnerSet.Spec.RunnerScaleSetName)) {
|
||||||
log.Info("AutoScalingRunnerSet runner scale set name changed. Updating the runner scale set.")
|
log.Info("AutoScalingRunnerSet runner scale set name changed. Updating the runner scale set.")
|
||||||
return r.updateRunnerScaleSetName(ctx, autoscalingRunnerSet, log)
|
return r.updateRunnerScaleSetName(ctx, autoscalingRunnerSet, log)
|
||||||
@@ -231,9 +234,8 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
|
|||||||
return r.createEphemeralRunnerSet(ctx, autoscalingRunnerSet, log)
|
return r.createEphemeralRunnerSet(ctx, autoscalingRunnerSet, log)
|
||||||
}
|
}
|
||||||
|
|
||||||
desiredSpecHash := autoscalingRunnerSet.RunnerSetSpecHash()
|
|
||||||
for _, runnerSet := range existingRunnerSets.all() {
|
for _, runnerSet := range existingRunnerSets.all() {
|
||||||
log.Info("Find existing ephemeral runner set", "name", runnerSet.Name, "specHash", runnerSet.Labels[labelKeyRunnerSpecHash])
|
log.Info("Find existing ephemeral runner set", "name", runnerSet.Name, "specHash", runnerSet.Annotations[annotationKeyRunnerSpecHash])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the AutoscalingListener is up and running in the controller namespace
|
// Make sure the AutoscalingListener is up and running in the controller namespace
|
||||||
@@ -250,7 +252,9 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Our listener pod is out of date, so we need to delete it to get a new recreate.
|
// Our listener pod is out of date, so we need to delete it to get a new recreate.
|
||||||
if listenerFound && (listener.Labels[labelKeyRunnerSpecHash] != autoscalingRunnerSet.ListenerSpecHash()) {
|
listenerValuesHashChanged := listener.Annotations[annotationKeyValuesHash] != autoscalingRunnerSet.Annotations[annotationKeyValuesHash]
|
||||||
|
listenerSpecHashChanged := listener.Annotations[annotationKeyRunnerSpecHash] != autoscalingRunnerSet.ListenerSpecHash()
|
||||||
|
if listenerFound && (listenerValuesHashChanged || listenerSpecHashChanged) {
|
||||||
log.Info("RunnerScaleSetListener is out of date. Deleting it so that it is recreated", "name", listener.Name)
|
log.Info("RunnerScaleSetListener is out of date. Deleting it so that it is recreated", "name", listener.Name)
|
||||||
if err := r.Delete(ctx, listener); err != nil {
|
if err := r.Delete(ctx, listener); err != nil {
|
||||||
if kerrors.IsNotFound(err) {
|
if kerrors.IsNotFound(err) {
|
||||||
@@ -264,7 +268,7 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
|
|||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if desiredSpecHash != latestRunnerSet.Labels[labelKeyRunnerSpecHash] {
|
if latestRunnerSet.Annotations[annotationKeyRunnerSpecHash] != autoscalingRunnerSet.RunnerSetSpecHash() {
|
||||||
if r.drainingJobs(&latestRunnerSet.Status) {
|
if r.drainingJobs(&latestRunnerSet.Status) {
|
||||||
log.Info("Latest runner set spec hash does not match the current autoscaling runner set. Waiting for the running and pending runners to finish:", "running", latestRunnerSet.Status.RunningEphemeralRunners, "pending", latestRunnerSet.Status.PendingEphemeralRunners)
|
log.Info("Latest runner set spec hash does not match the current autoscaling runner set. Waiting for the running and pending runners to finish:", "running", latestRunnerSet.Status.RunningEphemeralRunners, "pending", latestRunnerSet.Status.PendingEphemeralRunners)
|
||||||
log.Info("Scaling down the number of desired replicas to 0")
|
log.Info("Scaling down the number of desired replicas to 0")
|
||||||
@@ -272,6 +276,7 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
|
|||||||
// need to scale down to 0
|
// need to scale down to 0
|
||||||
err := patch(ctx, r.Client, latestRunnerSet, func(obj *v1alpha1.EphemeralRunnerSet) {
|
err := patch(ctx, r.Client, latestRunnerSet, func(obj *v1alpha1.EphemeralRunnerSet) {
|
||||||
obj.Spec.Replicas = 0
|
obj.Spec.Replicas = 0
|
||||||
|
obj.Spec.PatchID = 0
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err, "Failed to patch runner set to set desired count to 0")
|
log.Error(err, "Failed to patch runner set to set desired count to 0")
|
||||||
@@ -480,7 +485,7 @@ func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Contex
|
|||||||
|
|
||||||
logger.Info("Adding runner scale set ID, name and runner group name as an annotation and url labels")
|
logger.Info("Adding runner scale set ID, name and runner group name as an annotation and url labels")
|
||||||
if err = patch(ctx, r.Client, autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) {
|
if err = patch(ctx, r.Client, autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) {
|
||||||
obj.Annotations[runnerScaleSetNameAnnotationKey] = runnerScaleSet.Name
|
obj.Annotations[AnnotationKeyGitHubRunnerScaleSetName] = runnerScaleSet.Name
|
||||||
obj.Annotations[runnerScaleSetIdAnnotationKey] = strconv.Itoa(runnerScaleSet.Id)
|
obj.Annotations[runnerScaleSetIdAnnotationKey] = strconv.Itoa(runnerScaleSet.Id)
|
||||||
obj.Annotations[AnnotationKeyGitHubRunnerGroupName] = runnerScaleSet.RunnerGroupName
|
obj.Annotations[AnnotationKeyGitHubRunnerGroupName] = runnerScaleSet.RunnerGroupName
|
||||||
if err := applyGitHubURLLabels(obj.Spec.GitHubConfigUrl, obj.Labels); err != nil { // should never happen
|
if err := applyGitHubURLLabels(obj.Spec.GitHubConfigUrl, obj.Labels); err != nil { // should never happen
|
||||||
@@ -528,9 +533,10 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetRunnerGroup(ctx con
|
|||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("Updating runner scale set runner group name as an annotation")
|
logger.Info("Updating runner scale set name and runner group name as annotations")
|
||||||
if err := patch(ctx, r.Client, autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) {
|
if err := patch(ctx, r.Client, autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) {
|
||||||
obj.Annotations[AnnotationKeyGitHubRunnerGroupName] = updatedRunnerScaleSet.RunnerGroupName
|
obj.Annotations[AnnotationKeyGitHubRunnerGroupName] = updatedRunnerScaleSet.RunnerGroupName
|
||||||
|
obj.Annotations[AnnotationKeyGitHubRunnerScaleSetName] = updatedRunnerScaleSet.Name
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
logger.Error(err, "Failed to update runner group name annotation")
|
logger.Error(err, "Failed to update runner group name annotation")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
@@ -566,7 +572,7 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetName(ctx context.Co
|
|||||||
|
|
||||||
logger.Info("Updating runner scale set name as an annotation")
|
logger.Info("Updating runner scale set name as an annotation")
|
||||||
if err := patch(ctx, r.Client, autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) {
|
if err := patch(ctx, r.Client, autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) {
|
||||||
obj.Annotations[runnerScaleSetNameAnnotationKey] = updatedRunnerScaleSet.Name
|
obj.Annotations[AnnotationKeyGitHubRunnerScaleSetName] = updatedRunnerScaleSet.Name
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
logger.Error(err, "Failed to update runner scale set name annotation")
|
logger.Error(err, "Failed to update runner scale set name annotation")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
@@ -752,26 +758,6 @@ func (r *AutoscalingRunnerSetReconciler) actionsClientOptionsFor(ctx context.Con
|
|||||||
|
|
||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
func (r *AutoscalingRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *AutoscalingRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
groupVersionIndexer := func(rawObj client.Object) []string {
|
|
||||||
groupVersion := v1alpha1.GroupVersion.String()
|
|
||||||
owner := metav1.GetControllerOf(rawObj)
|
|
||||||
if owner == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...make sure it is owned by this controller
|
|
||||||
if owner.APIVersion != groupVersion || owner.Kind != "AutoscalingRunnerSet" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...and if so, return it
|
|
||||||
return []string{owner.Name}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &v1alpha1.EphemeralRunnerSet{}, resourceOwnerKey, groupVersionIndexer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
For(&v1alpha1.AutoscalingRunnerSet{}).
|
For(&v1alpha1.AutoscalingRunnerSet{}).
|
||||||
Owns(&v1alpha1.EphemeralRunnerSet{}).
|
Owns(&v1alpha1.EphemeralRunnerSet{}).
|
||||||
|
|||||||
@@ -280,6 +280,10 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() {
|
|||||||
// This should trigger re-creation of EphemeralRunnerSet and Listener
|
// This should trigger re-creation of EphemeralRunnerSet and Listener
|
||||||
patched := autoscalingRunnerSet.DeepCopy()
|
patched := autoscalingRunnerSet.DeepCopy()
|
||||||
patched.Spec.Template.Spec.PriorityClassName = "test-priority-class"
|
patched.Spec.Template.Spec.PriorityClassName = "test-priority-class"
|
||||||
|
if patched.ObjectMeta.Annotations == nil {
|
||||||
|
patched.ObjectMeta.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
patched.ObjectMeta.Annotations[annotationKeyValuesHash] = "test-hash"
|
||||||
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
||||||
autoscalingRunnerSet = patched.DeepCopy()
|
autoscalingRunnerSet = patched.DeepCopy()
|
||||||
@@ -297,10 +301,10 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() {
|
|||||||
return "", fmt.Errorf("We should have only 1 EphemeralRunnerSet, but got %v", len(runnerSetList.Items))
|
return "", fmt.Errorf("We should have only 1 EphemeralRunnerSet, but got %v", len(runnerSetList.Items))
|
||||||
}
|
}
|
||||||
|
|
||||||
return runnerSetList.Items[0].Labels[labelKeyRunnerSpecHash], nil
|
return runnerSetList.Items[0].Annotations[annotationKeyRunnerSpecHash], nil
|
||||||
},
|
},
|
||||||
autoscalingRunnerSetTestTimeout,
|
autoscalingRunnerSetTestTimeout,
|
||||||
autoscalingRunnerSetTestInterval).ShouldNot(BeEquivalentTo(runnerSet.Labels[labelKeyRunnerSpecHash]), "New EphemeralRunnerSet should be created")
|
autoscalingRunnerSetTestInterval).ShouldNot(BeEquivalentTo(runnerSet.Annotations[annotationKeyRunnerSpecHash]), "New EphemeralRunnerSet should be created")
|
||||||
|
|
||||||
// We should create a new listener
|
// We should create a new listener
|
||||||
Eventually(
|
Eventually(
|
||||||
@@ -334,6 +338,55 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() {
|
|||||||
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
||||||
|
|
||||||
|
// We should not re-create a new EphemeralRunnerSet
|
||||||
|
Consistently(
|
||||||
|
func() (string, error) {
|
||||||
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
||||||
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(runnerSetList.Items) != 1 {
|
||||||
|
return "", fmt.Errorf("We should have only 1 EphemeralRunnerSet, but got %v", len(runnerSetList.Items))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(runnerSetList.Items[0].UID), nil
|
||||||
|
},
|
||||||
|
autoscalingRunnerSetTestTimeout,
|
||||||
|
autoscalingRunnerSetTestInterval).Should(BeEquivalentTo(string(runnerSet.UID)), "New EphemeralRunnerSet should not be created")
|
||||||
|
|
||||||
|
// We should only re-create a new listener
|
||||||
|
Eventually(
|
||||||
|
func() (string, error) {
|
||||||
|
listener := new(v1alpha1.AutoscalingListener)
|
||||||
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, listener)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(listener.UID), nil
|
||||||
|
},
|
||||||
|
autoscalingRunnerSetTestTimeout,
|
||||||
|
autoscalingRunnerSetTestInterval).ShouldNot(BeEquivalentTo(string(listener.UID)), "New Listener should be created")
|
||||||
|
|
||||||
|
// Only update the values hash for the autoscaling runner set
|
||||||
|
// This should trigger re-creation of the Listener only
|
||||||
|
runnerSetList = new(v1alpha1.EphemeralRunnerSetList)
|
||||||
|
err = k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to list EphemeralRunnerSet")
|
||||||
|
Expect(len(runnerSetList.Items)).To(Equal(1), "There should be 1 EphemeralRunnerSet")
|
||||||
|
runnerSet = runnerSetList.Items[0]
|
||||||
|
|
||||||
|
listener = new(v1alpha1.AutoscalingListener)
|
||||||
|
err = k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, listener)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to get Listener")
|
||||||
|
|
||||||
|
patched = autoscalingRunnerSet.DeepCopy()
|
||||||
|
patched.ObjectMeta.Annotations[annotationKeyValuesHash] = "hash-changes"
|
||||||
|
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
||||||
|
|
||||||
// We should not re-create a new EphemeralRunnerSet
|
// We should not re-create a new EphemeralRunnerSet
|
||||||
Consistently(
|
Consistently(
|
||||||
func() (string, error) {
|
func() (string, error) {
|
||||||
@@ -493,6 +546,10 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() {
|
|||||||
// Patch the AutoScalingRunnerSet image which should trigger
|
// Patch the AutoScalingRunnerSet image which should trigger
|
||||||
// the recreation of the Listener and EphemeralRunnerSet
|
// the recreation of the Listener and EphemeralRunnerSet
|
||||||
patched := autoscalingRunnerSet.DeepCopy()
|
patched := autoscalingRunnerSet.DeepCopy()
|
||||||
|
if patched.ObjectMeta.Annotations == nil {
|
||||||
|
patched.ObjectMeta.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
patched.ObjectMeta.Annotations[annotationKeyValuesHash] = "testgroup2"
|
||||||
patched.Spec.Template.Spec = corev1.PodSpec{
|
patched.Spec.Template.Spec = corev1.PodSpec{
|
||||||
Containers: []corev1.Container{
|
Containers: []corev1.Container{
|
||||||
{
|
{
|
||||||
@@ -501,7 +558,6 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// patched.Spec.Template.Spec.PriorityClassName = "test-priority-class"
|
|
||||||
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
||||||
autoscalingRunnerSet = patched.DeepCopy()
|
autoscalingRunnerSet = patched.DeepCopy()
|
||||||
@@ -698,7 +754,7 @@ var _ = Describe("Test AutoScalingController updates", Ordered, func() {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := ars.Annotations[runnerScaleSetNameAnnotationKey]; ok {
|
if val, ok := ars.Annotations[AnnotationKeyGitHubRunnerScaleSetName]; ok {
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,7 +778,7 @@ var _ = Describe("Test AutoScalingController updates", Ordered, func() {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := ars.Annotations[runnerScaleSetNameAnnotationKey]; ok {
|
if val, ok := ars.Annotations[AnnotationKeyGitHubRunnerScaleSetName]; ok {
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,11 @@ const (
|
|||||||
// Finalizer used to protect resources from deletion while AutoscalingRunnerSet is running
|
// Finalizer used to protect resources from deletion while AutoscalingRunnerSet is running
|
||||||
const AutoscalingRunnerSetCleanupFinalizerName = "actions.github.com/cleanup-protection"
|
const AutoscalingRunnerSetCleanupFinalizerName = "actions.github.com/cleanup-protection"
|
||||||
|
|
||||||
const AnnotationKeyGitHubRunnerGroupName = "actions.github.com/runner-group-name"
|
const (
|
||||||
|
AnnotationKeyGitHubRunnerGroupName = "actions.github.com/runner-group-name"
|
||||||
|
AnnotationKeyGitHubRunnerScaleSetName = "actions.github.com/runner-scale-set-name"
|
||||||
|
AnnotationKeyPatchID = "actions.github.com/patch-id"
|
||||||
|
)
|
||||||
|
|
||||||
// Labels applied to listener roles
|
// Labels applied to listener roles
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||||
@@ -133,6 +132,23 @@ func (r *EphemeralRunnerReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
|||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ephemeralRunner.IsDone() {
|
||||||
|
log.Info("Cleaning up resources after after ephemeral runner termination", "phase", ephemeralRunner.Status.Phase)
|
||||||
|
done, err := r.cleanupResources(ctx, ephemeralRunner, log)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err, "Failed to clean up ephemeral runner owned resources")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
if !done {
|
||||||
|
log.Info("Waiting for ephemeral runner owned resources to be deleted")
|
||||||
|
return ctrl.Result{Requeue: true}, nil
|
||||||
|
}
|
||||||
|
// Stop reconciling on this object.
|
||||||
|
// The EphemeralRunnerSet is responsible for cleaning it up.
|
||||||
|
log.Info("EphemeralRunner has already finished. Stopping reconciliation and waiting for EphemeralRunnerSet to clean it up", "phase", ephemeralRunner.Status.Phase)
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
if !controllerutil.ContainsFinalizer(ephemeralRunner, ephemeralRunnerActionsFinalizerName) {
|
if !controllerutil.ContainsFinalizer(ephemeralRunner, ephemeralRunnerActionsFinalizerName) {
|
||||||
log.Info("Adding runner registration finalizer")
|
log.Info("Adding runner registration finalizer")
|
||||||
err := patch(ctx, r.Client, ephemeralRunner, func(obj *v1alpha1.EphemeralRunner) {
|
err := patch(ctx, r.Client, ephemeralRunner, func(obj *v1alpha1.EphemeralRunner) {
|
||||||
@@ -159,13 +175,6 @@ func (r *EphemeralRunnerReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
|||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ephemeralRunner.Status.Phase == corev1.PodSucceeded || ephemeralRunner.Status.Phase == corev1.PodFailed {
|
|
||||||
// Stop reconciling on this object.
|
|
||||||
// The EphemeralRunnerSet is responsible for cleaning it up.
|
|
||||||
log.Info("EphemeralRunner has already finished. Stopping reconciliation and waiting for EphemeralRunnerSet to clean it up", "phase", ephemeralRunner.Status.Phase)
|
|
||||||
return ctrl.Result{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if ephemeralRunner.Status.RunnerId == 0 {
|
if ephemeralRunner.Status.RunnerId == 0 {
|
||||||
log.Info("Creating new ephemeral runner registration and updating status with runner config")
|
log.Info("Creating new ephemeral runner registration and updating status with runner config")
|
||||||
return r.updateStatusWithRunnerConfig(ctx, ephemeralRunner, log)
|
return r.updateStatusWithRunnerConfig(ctx, ephemeralRunner, log)
|
||||||
@@ -285,14 +294,17 @@ func (r *EphemeralRunnerReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *EphemeralRunnerReconciler) cleanupRunnerFromService(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) (ctrl.Result, error) {
|
func (r *EphemeralRunnerReconciler) cleanupRunnerFromService(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) (ctrl.Result, error) {
|
||||||
actionsError := &actions.ActionsError{}
|
if err := r.deleteRunnerFromService(ctx, ephemeralRunner, log); err != nil {
|
||||||
err := r.deleteRunnerFromService(ctx, ephemeralRunner, log)
|
actionsError := &actions.ActionsError{}
|
||||||
if err != nil {
|
if !errors.As(err, &actionsError) {
|
||||||
if errors.As(err, &actionsError) &&
|
log.Error(err, "Failed to clean up runner from the service (not an ActionsError)")
|
||||||
actionsError.StatusCode == http.StatusBadRequest &&
|
return ctrl.Result{}, err
|
||||||
strings.Contains(actionsError.ExceptionName, "JobStillRunningException") {
|
}
|
||||||
|
|
||||||
|
if actionsError.StatusCode == http.StatusBadRequest && actionsError.IsException("JobStillRunningException") {
|
||||||
log.Info("Runner is still running the job. Re-queue in 30 seconds")
|
log.Info("Runner is still running the job. Re-queue in 30 seconds")
|
||||||
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
|
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Error(err, "Failed clean up runner from the service")
|
log.Error(err, "Failed clean up runner from the service")
|
||||||
@@ -300,10 +312,9 @@ func (r *EphemeralRunnerReconciler) cleanupRunnerFromService(ctx context.Context
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Successfully removed runner registration from service")
|
log.Info("Successfully removed runner registration from service")
|
||||||
err = patch(ctx, r.Client, ephemeralRunner, func(obj *v1alpha1.EphemeralRunner) {
|
if err := patch(ctx, r.Client, ephemeralRunner, func(obj *v1alpha1.EphemeralRunner) {
|
||||||
controllerutil.RemoveFinalizer(obj, ephemeralRunnerActionsFinalizerName)
|
controllerutil.RemoveFinalizer(obj, ephemeralRunnerActionsFinalizerName)
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,7 +335,7 @@ func (r *EphemeralRunnerReconciler) cleanupResources(ctx context.Context, epheme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
case err != nil && !kerrors.IsNotFound(err):
|
case !kerrors.IsNotFound(err):
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
log.Info("Pod is deleted")
|
log.Info("Pod is deleted")
|
||||||
@@ -341,7 +352,7 @@ func (r *EphemeralRunnerReconciler) cleanupResources(ctx context.Context, epheme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
case err != nil && !kerrors.IsNotFound(err):
|
case !kerrors.IsNotFound(err):
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
log.Info("Secret is deleted")
|
log.Info("Secret is deleted")
|
||||||
@@ -518,7 +529,7 @@ func (r *EphemeralRunnerReconciler) updateStatusWithRunnerConfig(ctx context.Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
if actionsError.StatusCode != http.StatusConflict ||
|
if actionsError.StatusCode != http.StatusConflict ||
|
||||||
!strings.Contains(actionsError.ExceptionName, "AgentExistsException") {
|
!actionsError.IsException("AgentExistsException") {
|
||||||
return ctrl.Result{}, fmt.Errorf("failed to generate JIT config with Actions service error: %v", err)
|
return ctrl.Result{}, fmt.Errorf("failed to generate JIT config with Actions service error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,7 +686,7 @@ func (r *EphemeralRunnerReconciler) updateRunStatusFromPod(ctx context.Context,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Updating ephemeral runner status with pod phase", "phase", pod.Status.Phase, "reason", pod.Status.Reason, "message", pod.Status.Message)
|
log.Info("Updating ephemeral runner status with pod phase", "statusPhase", pod.Status.Phase, "statusReason", pod.Status.Reason, "statusMessage", pod.Status.Message)
|
||||||
err := patchSubResource(ctx, r.Status(), ephemeralRunner, func(obj *v1alpha1.EphemeralRunner) {
|
err := patchSubResource(ctx, r.Status(), ephemeralRunner, func(obj *v1alpha1.EphemeralRunner) {
|
||||||
obj.Status.Phase = pod.Status.Phase
|
obj.Status.Phase = pod.Status.Phase
|
||||||
obj.Status.Ready = obj.Status.Ready || (pod.Status.Phase == corev1.PodRunning)
|
obj.Status.Ready = obj.Status.Ready || (pod.Status.Phase == corev1.PodRunning)
|
||||||
@@ -774,7 +785,7 @@ func (r EphemeralRunnerReconciler) runnerRegisteredWithService(ctx context.Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if actionsError.StatusCode != http.StatusNotFound ||
|
if actionsError.StatusCode != http.StatusNotFound ||
|
||||||
!strings.Contains(actionsError.ExceptionName, "AgentNotFoundException") {
|
!actionsError.IsException("AgentNotFoundException") {
|
||||||
return false, fmt.Errorf("failed to check if runner exists in GitHub service: %v", err)
|
return false, fmt.Errorf("failed to check if runner exists in GitHub service: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -804,7 +815,6 @@ func (r *EphemeralRunnerReconciler) deleteRunnerFromService(ctx context.Context,
|
|||||||
|
|
||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
func (r *EphemeralRunnerReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *EphemeralRunnerReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
// TODO(nikola-jokic): Add indexing and filtering fields on corev1.Pod{}
|
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
For(&v1alpha1.EphemeralRunner{}).
|
For(&v1alpha1.EphemeralRunner{}).
|
||||||
Owns(&corev1.Pod{}).
|
Owns(&corev1.Pod{}).
|
||||||
|
|||||||
@@ -671,8 +671,10 @@ var _ = Describe("EphemeralRunner", func() {
|
|||||||
fake.WithGetRunner(
|
fake.WithGetRunner(
|
||||||
nil,
|
nil,
|
||||||
&actions.ActionsError{
|
&actions.ActionsError{
|
||||||
StatusCode: http.StatusNotFound,
|
StatusCode: http.StatusNotFound,
|
||||||
ExceptionName: "AgentNotFoundException",
|
Err: &actions.ActionsExceptionError{
|
||||||
|
ExceptionName: "AgentNotFoundException",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strconv"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||||
"github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics"
|
"github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics"
|
||||||
@@ -156,14 +156,14 @@ func (r *EphemeralRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl.R
|
|||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingEphemeralRunners, runningEphemeralRunners, finishedEphemeralRunners, failedEphemeralRunners, deletingEphemeralRunners := categorizeEphemeralRunners(ephemeralRunnerList)
|
ephemeralRunnerState := newEphemeralRunnerState(ephemeralRunnerList)
|
||||||
|
|
||||||
log.Info("Ephemeral runner counts",
|
log.Info("Ephemeral runner counts",
|
||||||
"pending", len(pendingEphemeralRunners),
|
"pending", len(ephemeralRunnerState.pending),
|
||||||
"running", len(runningEphemeralRunners),
|
"running", len(ephemeralRunnerState.running),
|
||||||
"finished", len(finishedEphemeralRunners),
|
"finished", len(ephemeralRunnerState.finished),
|
||||||
"failed", len(failedEphemeralRunners),
|
"failed", len(ephemeralRunnerState.failed),
|
||||||
"deleting", len(deletingEphemeralRunners),
|
"deleting", len(ephemeralRunnerState.deleting),
|
||||||
)
|
)
|
||||||
|
|
||||||
if r.PublishMetrics {
|
if r.PublishMetrics {
|
||||||
@@ -183,54 +183,56 @@ func (r *EphemeralRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl.R
|
|||||||
Organization: parsedURL.Organization,
|
Organization: parsedURL.Organization,
|
||||||
Enterprise: parsedURL.Enterprise,
|
Enterprise: parsedURL.Enterprise,
|
||||||
},
|
},
|
||||||
len(pendingEphemeralRunners),
|
len(ephemeralRunnerState.pending),
|
||||||
len(runningEphemeralRunners),
|
len(ephemeralRunnerState.running),
|
||||||
len(failedEphemeralRunners),
|
len(ephemeralRunnerState.failed),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanup finished runners and proceed
|
total := ephemeralRunnerState.scaleTotal()
|
||||||
var errs []error
|
if ephemeralRunnerSet.Spec.PatchID == 0 || ephemeralRunnerSet.Spec.PatchID != ephemeralRunnerState.latestPatchID {
|
||||||
for i := range finishedEphemeralRunners {
|
defer func() {
|
||||||
log.Info("Deleting finished ephemeral runner", "name", finishedEphemeralRunners[i].Name)
|
if err := r.cleanupFinishedEphemeralRunners(ctx, ephemeralRunnerState.finished, log); err != nil {
|
||||||
if err := r.Delete(ctx, finishedEphemeralRunners[i]); err != nil {
|
log.Error(err, "failed to cleanup finished ephemeral runners")
|
||||||
if !kerrors.IsNotFound(err) {
|
}
|
||||||
errs = append(errs, err)
|
}()
|
||||||
|
log.Info("Scaling comparison", "current", total, "desired", ephemeralRunnerSet.Spec.Replicas)
|
||||||
|
switch {
|
||||||
|
case total < ephemeralRunnerSet.Spec.Replicas: // Handle scale up
|
||||||
|
count := ephemeralRunnerSet.Spec.Replicas - total
|
||||||
|
log.Info("Creating new ephemeral runners (scale up)", "count", count)
|
||||||
|
if err := r.createEphemeralRunners(ctx, ephemeralRunnerSet, count, log); err != nil {
|
||||||
|
log.Error(err, "failed to make ephemeral runner")
|
||||||
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) > 0 {
|
case ephemeralRunnerSet.Spec.PatchID > 0 && total >= ephemeralRunnerSet.Spec.Replicas: // Handle scale down scenario.
|
||||||
mergedErrs := multierr.Combine(errs...)
|
// If ephemeral runner did not yet update the phase to succeeded, but the scale down
|
||||||
log.Error(mergedErrs, "Failed to delete finished ephemeral runners")
|
// request is issued, we should ignore the scale down request.
|
||||||
return ctrl.Result{}, mergedErrs
|
// Eventually, the ephemeral runner will be cleaned up on the next patch request, which happens
|
||||||
}
|
// on the next batch
|
||||||
|
case ephemeralRunnerSet.Spec.PatchID == 0 && total > ephemeralRunnerSet.Spec.Replicas:
|
||||||
total := len(pendingEphemeralRunners) + len(runningEphemeralRunners) + len(failedEphemeralRunners)
|
count := total - ephemeralRunnerSet.Spec.Replicas
|
||||||
log.Info("Scaling comparison", "current", total, "desired", ephemeralRunnerSet.Spec.Replicas)
|
log.Info("Deleting ephemeral runners (scale down)", "count", count)
|
||||||
switch {
|
if err := r.deleteIdleEphemeralRunners(
|
||||||
case total < ephemeralRunnerSet.Spec.Replicas: // Handle scale up
|
ctx,
|
||||||
count := ephemeralRunnerSet.Spec.Replicas - total
|
ephemeralRunnerSet,
|
||||||
log.Info("Creating new ephemeral runners (scale up)", "count", count)
|
ephemeralRunnerState.pending,
|
||||||
if err := r.createEphemeralRunners(ctx, ephemeralRunnerSet, count, log); err != nil {
|
ephemeralRunnerState.running,
|
||||||
log.Error(err, "failed to make ephemeral runner")
|
count,
|
||||||
return ctrl.Result{}, err
|
log,
|
||||||
}
|
); err != nil {
|
||||||
|
log.Error(err, "failed to delete idle runners")
|
||||||
case total > ephemeralRunnerSet.Spec.Replicas: // Handle scale down scenario.
|
return ctrl.Result{}, err
|
||||||
count := total - ephemeralRunnerSet.Spec.Replicas
|
}
|
||||||
log.Info("Deleting ephemeral runners (scale down)", "count", count)
|
|
||||||
if err := r.deleteIdleEphemeralRunners(ctx, ephemeralRunnerSet, pendingEphemeralRunners, runningEphemeralRunners, count, log); err != nil {
|
|
||||||
log.Error(err, "failed to delete idle runners")
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
desiredStatus := v1alpha1.EphemeralRunnerSetStatus{
|
desiredStatus := v1alpha1.EphemeralRunnerSetStatus{
|
||||||
CurrentReplicas: total,
|
CurrentReplicas: total,
|
||||||
PendingEphemeralRunners: len(pendingEphemeralRunners),
|
PendingEphemeralRunners: len(ephemeralRunnerState.pending),
|
||||||
RunningEphemeralRunners: len(runningEphemeralRunners),
|
RunningEphemeralRunners: len(ephemeralRunnerState.running),
|
||||||
FailedEphemeralRunners: len(failedEphemeralRunners),
|
FailedEphemeralRunners: len(ephemeralRunnerState.failed),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the status if needed.
|
// Update the status if needed.
|
||||||
@@ -247,6 +249,21 @@ func (r *EphemeralRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl.R
|
|||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *EphemeralRunnerSetReconciler) cleanupFinishedEphemeralRunners(ctx context.Context, finishedEphemeralRunners []*v1alpha1.EphemeralRunner, log logr.Logger) error {
|
||||||
|
// cleanup finished runners and proceed
|
||||||
|
var errs []error
|
||||||
|
for i := range finishedEphemeralRunners {
|
||||||
|
log.Info("Deleting finished ephemeral runner", "name", finishedEphemeralRunners[i].Name)
|
||||||
|
if err := r.Delete(ctx, finishedEphemeralRunners[i]); err != nil {
|
||||||
|
if !kerrors.IsNotFound(err) {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return multierr.Combine(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *EphemeralRunnerSetReconciler) cleanUpProxySecret(ctx context.Context, ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet, log logr.Logger) error {
|
func (r *EphemeralRunnerSetReconciler) cleanUpProxySecret(ctx context.Context, ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet, log logr.Logger) error {
|
||||||
if ephemeralRunnerSet.Spec.EphemeralRunnerSpec.Proxy == nil {
|
if ephemeralRunnerSet.Spec.EphemeralRunnerSpec.Proxy == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -284,19 +301,19 @@ func (r *EphemeralRunnerSetReconciler) cleanUpEphemeralRunners(ctx context.Conte
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingEphemeralRunners, runningEphemeralRunners, finishedEphemeralRunners, failedEphemeralRunners, deletingEphemeralRunners := categorizeEphemeralRunners(ephemeralRunnerList)
|
ephemeralRunnerState := newEphemeralRunnerState(ephemeralRunnerList)
|
||||||
|
|
||||||
log.Info("Clean up runner counts",
|
log.Info("Clean up runner counts",
|
||||||
"pending", len(pendingEphemeralRunners),
|
"pending", len(ephemeralRunnerState.pending),
|
||||||
"running", len(runningEphemeralRunners),
|
"running", len(ephemeralRunnerState.running),
|
||||||
"finished", len(finishedEphemeralRunners),
|
"finished", len(ephemeralRunnerState.finished),
|
||||||
"failed", len(failedEphemeralRunners),
|
"failed", len(ephemeralRunnerState.failed),
|
||||||
"deleting", len(deletingEphemeralRunners),
|
"deleting", len(ephemeralRunnerState.deleting),
|
||||||
)
|
)
|
||||||
|
|
||||||
log.Info("Cleanup finished or failed ephemeral runners")
|
log.Info("Cleanup finished or failed ephemeral runners")
|
||||||
var errs []error
|
var errs []error
|
||||||
for _, ephemeralRunner := range append(finishedEphemeralRunners, failedEphemeralRunners...) {
|
for _, ephemeralRunner := range append(ephemeralRunnerState.finished, ephemeralRunnerState.failed...) {
|
||||||
log.Info("Deleting ephemeral runner", "name", ephemeralRunner.Name)
|
log.Info("Deleting ephemeral runner", "name", ephemeralRunner.Name)
|
||||||
if err := r.Delete(ctx, ephemeralRunner); err != nil && !kerrors.IsNotFound(err) {
|
if err := r.Delete(ctx, ephemeralRunner); err != nil && !kerrors.IsNotFound(err) {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
@@ -310,7 +327,7 @@ func (r *EphemeralRunnerSetReconciler) cleanUpEphemeralRunners(ctx context.Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// avoid fetching the client if we have nothing left to do
|
// avoid fetching the client if we have nothing left to do
|
||||||
if len(runningEphemeralRunners) == 0 && len(pendingEphemeralRunners) == 0 {
|
if len(ephemeralRunnerState.running) == 0 && len(ephemeralRunnerState.pending) == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +338,7 @@ func (r *EphemeralRunnerSetReconciler) cleanUpEphemeralRunners(ctx context.Conte
|
|||||||
|
|
||||||
log.Info("Cleanup pending or running ephemeral runners")
|
log.Info("Cleanup pending or running ephemeral runners")
|
||||||
errs = errs[0:0]
|
errs = errs[0:0]
|
||||||
for _, ephemeralRunner := range append(pendingEphemeralRunners, runningEphemeralRunners...) {
|
for _, ephemeralRunner := range append(ephemeralRunnerState.pending, ephemeralRunnerState.running...) {
|
||||||
log.Info("Removing the ephemeral runner from the service", "name", ephemeralRunner.Name)
|
log.Info("Removing the ephemeral runner from the service", "name", ephemeralRunner.Name)
|
||||||
_, err := r.deleteEphemeralRunnerWithActionsClient(ctx, ephemeralRunner, actionsClient, log)
|
_, err := r.deleteEphemeralRunnerWithActionsClient(ctx, ephemeralRunner, actionsClient, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -414,6 +431,9 @@ func (r *EphemeralRunnerSetReconciler) createProxySecret(ctx context.Context, ep
|
|||||||
// When this happens, the next reconcile loop will try to delete the remaining ephemeral runners
|
// When this happens, the next reconcile loop will try to delete the remaining ephemeral runners
|
||||||
// after we get notified by any of the `v1alpha1.EphemeralRunner.Status` updates.
|
// after we get notified by any of the `v1alpha1.EphemeralRunner.Status` updates.
|
||||||
func (r *EphemeralRunnerSetReconciler) deleteIdleEphemeralRunners(ctx context.Context, ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet, pendingEphemeralRunners, runningEphemeralRunners []*v1alpha1.EphemeralRunner, count int, log logr.Logger) error {
|
func (r *EphemeralRunnerSetReconciler) deleteIdleEphemeralRunners(ctx context.Context, ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet, pendingEphemeralRunners, runningEphemeralRunners []*v1alpha1.EphemeralRunner, count int, log logr.Logger) error {
|
||||||
|
if count <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
runners := newEphemeralRunnerStepper(pendingEphemeralRunners, runningEphemeralRunners)
|
runners := newEphemeralRunnerStepper(pendingEphemeralRunners, runningEphemeralRunners)
|
||||||
if runners.len() == 0 {
|
if runners.len() == 0 {
|
||||||
log.Info("No pending or running ephemeral runners running at this time for scale down")
|
log.Info("No pending or running ephemeral runners running at this time for scale down")
|
||||||
@@ -427,12 +447,13 @@ func (r *EphemeralRunnerSetReconciler) deleteIdleEphemeralRunners(ctx context.Co
|
|||||||
deletedCount := 0
|
deletedCount := 0
|
||||||
for runners.next() {
|
for runners.next() {
|
||||||
ephemeralRunner := runners.object()
|
ephemeralRunner := runners.object()
|
||||||
if ephemeralRunner.Status.RunnerId == 0 {
|
isDone := ephemeralRunner.IsDone()
|
||||||
|
if !isDone && ephemeralRunner.Status.RunnerId == 0 {
|
||||||
log.Info("Skipping ephemeral runner since it is not registered yet", "name", ephemeralRunner.Name)
|
log.Info("Skipping ephemeral runner since it is not registered yet", "name", ephemeralRunner.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ephemeralRunner.Status.JobRequestId > 0 {
|
if !isDone && ephemeralRunner.Status.JobRequestId > 0 {
|
||||||
log.Info("Skipping ephemeral runner since it is running a job", "name", ephemeralRunner.Name, "jobRequestId", ephemeralRunner.Status.JobRequestId)
|
log.Info("Skipping ephemeral runner since it is running a job", "name", ephemeralRunner.Name, "jobRequestId", ephemeralRunner.Status.JobRequestId)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -458,10 +479,14 @@ func (r *EphemeralRunnerSetReconciler) deleteIdleEphemeralRunners(ctx context.Co
|
|||||||
func (r *EphemeralRunnerSetReconciler) deleteEphemeralRunnerWithActionsClient(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, actionsClient actions.ActionsService, log logr.Logger) (bool, error) {
|
func (r *EphemeralRunnerSetReconciler) deleteEphemeralRunnerWithActionsClient(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, actionsClient actions.ActionsService, log logr.Logger) (bool, error) {
|
||||||
if err := actionsClient.RemoveRunner(ctx, int64(ephemeralRunner.Status.RunnerId)); err != nil {
|
if err := actionsClient.RemoveRunner(ctx, int64(ephemeralRunner.Status.RunnerId)); err != nil {
|
||||||
actionsError := &actions.ActionsError{}
|
actionsError := &actions.ActionsError{}
|
||||||
if errors.As(err, &actionsError) &&
|
if !errors.As(err, &actionsError) {
|
||||||
actionsError.StatusCode == http.StatusBadRequest &&
|
log.Error(err, "failed to remove runner from the service", "name", ephemeralRunner.Name, "runnerId", ephemeralRunner.Status.RunnerId)
|
||||||
strings.Contains(actionsError.ExceptionName, "JobStillRunningException") {
|
return false, err
|
||||||
// Runner is still running a job, proceed with the next one
|
}
|
||||||
|
|
||||||
|
if actionsError.StatusCode == http.StatusBadRequest &&
|
||||||
|
actionsError.IsException("JobStillRunningException") {
|
||||||
|
log.Info("Runner is still running a job, skipping deletion", "name", ephemeralRunner.Name, "runnerId", ephemeralRunner.Status.RunnerId)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,28 +571,6 @@ func (r *EphemeralRunnerSetReconciler) actionsClientOptionsFor(ctx context.Conte
|
|||||||
|
|
||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
func (r *EphemeralRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *EphemeralRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
// Index EphemeralRunner owned by EphemeralRunnerSet so we can perform faster look ups.
|
|
||||||
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &v1alpha1.EphemeralRunner{}, resourceOwnerKey, func(rawObj client.Object) []string {
|
|
||||||
groupVersion := v1alpha1.GroupVersion.String()
|
|
||||||
|
|
||||||
// grab the job object, extract the owner...
|
|
||||||
ephemeralRunner := rawObj.(*v1alpha1.EphemeralRunner)
|
|
||||||
owner := metav1.GetControllerOf(ephemeralRunner)
|
|
||||||
if owner == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...make sure it is owned by this controller
|
|
||||||
if owner.APIVersion != groupVersion || owner.Kind != "EphemeralRunnerSet" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...and if so, return it
|
|
||||||
return []string{owner.Name}
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
For(&v1alpha1.EphemeralRunnerSet{}).
|
For(&v1alpha1.EphemeralRunnerSet{}).
|
||||||
Owns(&v1alpha1.EphemeralRunner{}).
|
Owns(&v1alpha1.EphemeralRunner{}).
|
||||||
@@ -580,16 +583,22 @@ type ephemeralRunnerStepper struct {
|
|||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEphemeralRunnerStepper(pending, running []*v1alpha1.EphemeralRunner) *ephemeralRunnerStepper {
|
func newEphemeralRunnerStepper(primary []*v1alpha1.EphemeralRunner, othersOrdered ...[]*v1alpha1.EphemeralRunner) *ephemeralRunnerStepper {
|
||||||
sort.Slice(pending, func(i, j int) bool {
|
sort.Slice(primary, func(i, j int) bool {
|
||||||
return pending[i].GetCreationTimestamp().Time.Before(pending[j].GetCreationTimestamp().Time)
|
return primary[i].GetCreationTimestamp().Time.Before(primary[j].GetCreationTimestamp().Time)
|
||||||
})
|
|
||||||
sort.Slice(running, func(i, j int) bool {
|
|
||||||
return running[i].GetCreationTimestamp().Time.Before(running[j].GetCreationTimestamp().Time)
|
|
||||||
})
|
})
|
||||||
|
for _, bucket := range othersOrdered {
|
||||||
|
sort.Slice(bucket, func(i, j int) bool {
|
||||||
|
return bucket[i].GetCreationTimestamp().Time.Before(bucket[j].GetCreationTimestamp().Time)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bucket := range othersOrdered {
|
||||||
|
primary = append(primary, bucket...)
|
||||||
|
}
|
||||||
|
|
||||||
return &ephemeralRunnerStepper{
|
return &ephemeralRunnerStepper{
|
||||||
items: append(pending, running...),
|
items: primary,
|
||||||
index: -1,
|
index: -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -613,28 +622,48 @@ func (s *ephemeralRunnerStepper) len() int {
|
|||||||
return len(s.items)
|
return len(s.items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func categorizeEphemeralRunners(ephemeralRunnerList *v1alpha1.EphemeralRunnerList) (pendingEphemeralRunners, runningEphemeralRunners, finishedEphemeralRunners, failedEphemeralRunners, deletingEphemeralRunners []*v1alpha1.EphemeralRunner) {
|
type ephemeralRunnerState struct {
|
||||||
|
pending []*v1alpha1.EphemeralRunner
|
||||||
|
running []*v1alpha1.EphemeralRunner
|
||||||
|
finished []*v1alpha1.EphemeralRunner
|
||||||
|
failed []*v1alpha1.EphemeralRunner
|
||||||
|
deleting []*v1alpha1.EphemeralRunner
|
||||||
|
|
||||||
|
latestPatchID int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEphemeralRunnerState(ephemeralRunnerList *v1alpha1.EphemeralRunnerList) *ephemeralRunnerState {
|
||||||
|
var ephemeralRunnerState ephemeralRunnerState
|
||||||
|
|
||||||
for i := range ephemeralRunnerList.Items {
|
for i := range ephemeralRunnerList.Items {
|
||||||
r := &ephemeralRunnerList.Items[i]
|
r := &ephemeralRunnerList.Items[i]
|
||||||
|
patchID, err := strconv.Atoi(r.Annotations[AnnotationKeyPatchID])
|
||||||
|
if err == nil && patchID > ephemeralRunnerState.latestPatchID {
|
||||||
|
ephemeralRunnerState.latestPatchID = patchID
|
||||||
|
}
|
||||||
if !r.ObjectMeta.DeletionTimestamp.IsZero() {
|
if !r.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||||
deletingEphemeralRunners = append(deletingEphemeralRunners, r)
|
ephemeralRunnerState.deleting = append(ephemeralRunnerState.deleting, r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch r.Status.Phase {
|
switch r.Status.Phase {
|
||||||
case corev1.PodRunning:
|
case corev1.PodRunning:
|
||||||
runningEphemeralRunners = append(runningEphemeralRunners, r)
|
ephemeralRunnerState.running = append(ephemeralRunnerState.running, r)
|
||||||
case corev1.PodSucceeded:
|
case corev1.PodSucceeded:
|
||||||
finishedEphemeralRunners = append(finishedEphemeralRunners, r)
|
ephemeralRunnerState.finished = append(ephemeralRunnerState.finished, r)
|
||||||
case corev1.PodFailed:
|
case corev1.PodFailed:
|
||||||
failedEphemeralRunners = append(failedEphemeralRunners, r)
|
ephemeralRunnerState.failed = append(ephemeralRunnerState.failed, r)
|
||||||
default:
|
default:
|
||||||
// Pending or no phase should be considered as pending.
|
// Pending or no phase should be considered as pending.
|
||||||
//
|
//
|
||||||
// If field is not set, that means that the EphemeralRunner
|
// If field is not set, that means that the EphemeralRunner
|
||||||
// did not yet have chance to update the Status.Phase field.
|
// did not yet have chance to update the Status.Phase field.
|
||||||
pendingEphemeralRunners = append(pendingEphemeralRunners, r)
|
ephemeralRunnerState.pending = append(ephemeralRunnerState.pending, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return &ephemeralRunnerState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ephemeralRunnerState) scaleTotal() int {
|
||||||
|
return len(s.pending) + len(s.running) + len(s.failed)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,9 @@ const defaultGitHubToken = "gh_token"
|
|||||||
|
|
||||||
func startManagers(t ginkgo.GinkgoTInterface, first manager.Manager, others ...manager.Manager) {
|
func startManagers(t ginkgo.GinkgoTInterface, first manager.Manager, others ...manager.Manager) {
|
||||||
for _, mgr := range append([]manager.Manager{first}, others...) {
|
for _, mgr := range append([]manager.Manager{first}, others...) {
|
||||||
|
if err := SetupIndexers(mgr); err != nil {
|
||||||
|
t.Fatalf("failed to setup indexers: %v", err)
|
||||||
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
g, ctx := errgroup.WithContext(ctx)
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
|||||||
71
controllers/actions.github.com/indexer.go
Normal file
71
controllers/actions.github.com/indexer.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package actionsgithubcom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
v1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupIndexers(mgr ctrl.Manager) error {
|
||||||
|
if err := mgr.GetFieldIndexer().IndexField(
|
||||||
|
context.Background(),
|
||||||
|
&corev1.Pod{},
|
||||||
|
resourceOwnerKey,
|
||||||
|
newGroupVersionOwnerKindIndexer("AutoscalingListener", "EphemeralRunner"),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mgr.GetFieldIndexer().IndexField(
|
||||||
|
context.Background(),
|
||||||
|
&corev1.ServiceAccount{},
|
||||||
|
resourceOwnerKey,
|
||||||
|
newGroupVersionOwnerKindIndexer("AutoscalingListener"),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mgr.GetFieldIndexer().IndexField(
|
||||||
|
context.Background(),
|
||||||
|
&v1alpha1.EphemeralRunnerSet{},
|
||||||
|
resourceOwnerKey,
|
||||||
|
newGroupVersionOwnerKindIndexer("AutoscalingRunnerSet"),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mgr.GetFieldIndexer().IndexField(
|
||||||
|
context.Background(),
|
||||||
|
&v1alpha1.EphemeralRunner{},
|
||||||
|
resourceOwnerKey,
|
||||||
|
newGroupVersionOwnerKindIndexer("EphemeralRunnerSet"),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGroupVersionOwnerKindIndexer(ownerKind string, otherOwnerKinds ...string) client.IndexerFunc {
|
||||||
|
owners := append([]string{ownerKind}, otherOwnerKinds...)
|
||||||
|
return func(o client.Object) []string {
|
||||||
|
groupVersion := v1alpha1.GroupVersion.String()
|
||||||
|
owner := metav1.GetControllerOfNoCopy(o)
|
||||||
|
if owner == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...make sure it is owned by this controller
|
||||||
|
if owner.APIVersion != groupVersion || !slices.Contains(owners, owner.Kind) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...and if so, return it
|
||||||
|
return []string{owner.Name}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,13 +85,17 @@ func (b *resourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.
|
|||||||
effectiveMinRunners = *autoscalingRunnerSet.Spec.MinRunners
|
effectiveMinRunners = *autoscalingRunnerSet.Spec.MinRunners
|
||||||
}
|
}
|
||||||
|
|
||||||
labels := map[string]string{
|
labels := mergeLabels(autoscalingRunnerSet.Labels, map[string]string{
|
||||||
LabelKeyGitHubScaleSetNamespace: autoscalingRunnerSet.Namespace,
|
LabelKeyGitHubScaleSetNamespace: autoscalingRunnerSet.Namespace,
|
||||||
LabelKeyGitHubScaleSetName: autoscalingRunnerSet.Name,
|
LabelKeyGitHubScaleSetName: autoscalingRunnerSet.Name,
|
||||||
LabelKeyKubernetesPartOf: labelValueKubernetesPartOf,
|
LabelKeyKubernetesPartOf: labelValueKubernetesPartOf,
|
||||||
LabelKeyKubernetesComponent: "runner-scale-set-listener",
|
LabelKeyKubernetesComponent: "runner-scale-set-listener",
|
||||||
LabelKeyKubernetesVersion: autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion],
|
LabelKeyKubernetesVersion: autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion],
|
||||||
labelKeyRunnerSpecHash: autoscalingRunnerSet.ListenerSpecHash(),
|
})
|
||||||
|
|
||||||
|
annotations := map[string]string{
|
||||||
|
annotationKeyRunnerSpecHash: autoscalingRunnerSet.ListenerSpecHash(),
|
||||||
|
annotationKeyValuesHash: autoscalingRunnerSet.Annotations[annotationKeyValuesHash],
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := applyGitHubURLLabels(autoscalingRunnerSet.Spec.GitHubConfigUrl, labels); err != nil {
|
if err := applyGitHubURLLabels(autoscalingRunnerSet.Spec.GitHubConfigUrl, labels); err != nil {
|
||||||
@@ -100,9 +104,10 @@ func (b *resourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.
|
|||||||
|
|
||||||
autoscalingListener := &v1alpha1.AutoscalingListener{
|
autoscalingListener := &v1alpha1.AutoscalingListener{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: scaleSetListenerName(autoscalingRunnerSet),
|
Name: scaleSetListenerName(autoscalingRunnerSet),
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
|
Annotations: annotations,
|
||||||
},
|
},
|
||||||
Spec: v1alpha1.AutoscalingListenerSpec{
|
Spec: v1alpha1.AutoscalingListenerSpec{
|
||||||
GitHubConfigUrl: autoscalingRunnerSet.Spec.GitHubConfigUrl,
|
GitHubConfigUrl: autoscalingRunnerSet.Spec.GitHubConfigUrl,
|
||||||
@@ -406,10 +411,10 @@ func (b *resourceBuilder) newScaleSetListenerServiceAccount(autoscalingListener
|
|||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: scaleSetListenerServiceAccountName(autoscalingListener),
|
Name: scaleSetListenerServiceAccountName(autoscalingListener),
|
||||||
Namespace: autoscalingListener.Namespace,
|
Namespace: autoscalingListener.Namespace,
|
||||||
Labels: map[string]string{
|
Labels: mergeLabels(autoscalingListener.Labels, map[string]string{
|
||||||
LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
||||||
LabelKeyGitHubScaleSetName: autoscalingListener.Spec.AutoscalingRunnerSetName,
|
LabelKeyGitHubScaleSetName: autoscalingListener.Spec.AutoscalingRunnerSetName,
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,13 +426,13 @@ func (b *resourceBuilder) newScaleSetListenerRole(autoscalingListener *v1alpha1.
|
|||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: scaleSetListenerRoleName(autoscalingListener),
|
Name: scaleSetListenerRoleName(autoscalingListener),
|
||||||
Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
||||||
Labels: map[string]string{
|
Labels: mergeLabels(autoscalingListener.Labels, map[string]string{
|
||||||
LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
||||||
LabelKeyGitHubScaleSetName: autoscalingListener.Spec.AutoscalingRunnerSetName,
|
LabelKeyGitHubScaleSetName: autoscalingListener.Spec.AutoscalingRunnerSetName,
|
||||||
labelKeyListenerNamespace: autoscalingListener.Namespace,
|
labelKeyListenerNamespace: autoscalingListener.Namespace,
|
||||||
labelKeyListenerName: autoscalingListener.Name,
|
labelKeyListenerName: autoscalingListener.Name,
|
||||||
"role-policy-rules-hash": rulesHash,
|
"role-policy-rules-hash": rulesHash,
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
Rules: rules,
|
Rules: rules,
|
||||||
}
|
}
|
||||||
@@ -455,14 +460,14 @@ func (b *resourceBuilder) newScaleSetListenerRoleBinding(autoscalingListener *v1
|
|||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: scaleSetListenerRoleName(autoscalingListener),
|
Name: scaleSetListenerRoleName(autoscalingListener),
|
||||||
Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
||||||
Labels: map[string]string{
|
Labels: mergeLabels(autoscalingListener.Labels, map[string]string{
|
||||||
LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
||||||
LabelKeyGitHubScaleSetName: autoscalingListener.Spec.AutoscalingRunnerSetName,
|
LabelKeyGitHubScaleSetName: autoscalingListener.Spec.AutoscalingRunnerSetName,
|
||||||
labelKeyListenerNamespace: autoscalingListener.Namespace,
|
labelKeyListenerNamespace: autoscalingListener.Namespace,
|
||||||
labelKeyListenerName: autoscalingListener.Name,
|
labelKeyListenerName: autoscalingListener.Name,
|
||||||
"role-binding-role-ref-hash": roleRefHash,
|
"role-binding-role-ref-hash": roleRefHash,
|
||||||
"role-binding-subject-hash": subjectHash,
|
"role-binding-subject-hash": subjectHash,
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
RoleRef: roleRef,
|
RoleRef: roleRef,
|
||||||
Subjects: subjects,
|
Subjects: subjects,
|
||||||
@@ -478,11 +483,11 @@ func (b *resourceBuilder) newScaleSetListenerSecretMirror(autoscalingListener *v
|
|||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: scaleSetListenerSecretMirrorName(autoscalingListener),
|
Name: scaleSetListenerSecretMirrorName(autoscalingListener),
|
||||||
Namespace: autoscalingListener.Namespace,
|
Namespace: autoscalingListener.Namespace,
|
||||||
Labels: map[string]string{
|
Labels: mergeLabels(autoscalingListener.Labels, map[string]string{
|
||||||
LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
||||||
LabelKeyGitHubScaleSetName: autoscalingListener.Spec.AutoscalingRunnerSetName,
|
LabelKeyGitHubScaleSetName: autoscalingListener.Spec.AutoscalingRunnerSetName,
|
||||||
"secret-data-hash": dataHash,
|
"secret-data-hash": dataHash,
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
Data: secret.DeepCopy().Data,
|
Data: secret.DeepCopy().Data,
|
||||||
}
|
}
|
||||||
@@ -497,21 +502,23 @@ func (b *resourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.A
|
|||||||
}
|
}
|
||||||
runnerSpecHash := autoscalingRunnerSet.RunnerSetSpecHash()
|
runnerSpecHash := autoscalingRunnerSet.RunnerSetSpecHash()
|
||||||
|
|
||||||
labels := map[string]string{
|
labels := mergeLabels(autoscalingRunnerSet.Labels, map[string]string{
|
||||||
labelKeyRunnerSpecHash: runnerSpecHash,
|
|
||||||
LabelKeyKubernetesPartOf: labelValueKubernetesPartOf,
|
LabelKeyKubernetesPartOf: labelValueKubernetesPartOf,
|
||||||
LabelKeyKubernetesComponent: "runner-set",
|
LabelKeyKubernetesComponent: "runner-set",
|
||||||
LabelKeyKubernetesVersion: autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion],
|
LabelKeyKubernetesVersion: autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion],
|
||||||
LabelKeyGitHubScaleSetName: autoscalingRunnerSet.Name,
|
LabelKeyGitHubScaleSetName: autoscalingRunnerSet.Name,
|
||||||
LabelKeyGitHubScaleSetNamespace: autoscalingRunnerSet.Namespace,
|
LabelKeyGitHubScaleSetNamespace: autoscalingRunnerSet.Namespace,
|
||||||
}
|
})
|
||||||
|
|
||||||
if err := applyGitHubURLLabels(autoscalingRunnerSet.Spec.GitHubConfigUrl, labels); err != nil {
|
if err := applyGitHubURLLabels(autoscalingRunnerSet.Spec.GitHubConfigUrl, labels); err != nil {
|
||||||
return nil, fmt.Errorf("failed to apply GitHub URL labels: %v", err)
|
return nil, fmt.Errorf("failed to apply GitHub URL labels: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newAnnotations := map[string]string{
|
newAnnotations := map[string]string{
|
||||||
AnnotationKeyGitHubRunnerGroupName: autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerGroupName],
|
|
||||||
|
AnnotationKeyGitHubRunnerGroupName: autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerGroupName],
|
||||||
|
AnnotationKeyGitHubRunnerScaleSetName: autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerScaleSetName],
|
||||||
|
annotationKeyRunnerSpecHash: runnerSpecHash,
|
||||||
}
|
}
|
||||||
|
|
||||||
newEphemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{
|
newEphemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{
|
||||||
@@ -540,22 +547,19 @@ func (b *resourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.A
|
|||||||
|
|
||||||
func (b *resourceBuilder) newEphemeralRunner(ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet) *v1alpha1.EphemeralRunner {
|
func (b *resourceBuilder) newEphemeralRunner(ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet) *v1alpha1.EphemeralRunner {
|
||||||
labels := make(map[string]string)
|
labels := make(map[string]string)
|
||||||
for _, key := range commonLabelKeys {
|
for k, v := range ephemeralRunnerSet.Labels {
|
||||||
switch key {
|
if k == LabelKeyKubernetesComponent {
|
||||||
case LabelKeyKubernetesComponent:
|
labels[k] = "runner"
|
||||||
labels[key] = "runner"
|
} else {
|
||||||
default:
|
labels[k] = v
|
||||||
v, ok := ephemeralRunnerSet.Labels[key]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
labels[key] = v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
annotations := make(map[string]string)
|
annotations := make(map[string]string)
|
||||||
for key, val := range ephemeralRunnerSet.Annotations {
|
for key, val := range ephemeralRunnerSet.Annotations {
|
||||||
annotations[key] = val
|
annotations[key] = val
|
||||||
}
|
}
|
||||||
|
annotations[AnnotationKeyPatchID] = strconv.Itoa(ephemeralRunnerSet.Spec.PatchID)
|
||||||
return &v1alpha1.EphemeralRunner{
|
return &v1alpha1.EphemeralRunner{
|
||||||
TypeMeta: metav1.TypeMeta{},
|
TypeMeta: metav1.TypeMeta{},
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@@ -743,3 +747,17 @@ func trimLabelValue(val string) string {
|
|||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mergeLabels(base, overwrite map[string]string) map[string]string {
|
||||||
|
mergedLabels := map[string]string{}
|
||||||
|
|
||||||
|
for k, v := range base {
|
||||||
|
mergedLabels[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range overwrite {
|
||||||
|
mergedLabels[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedLabels
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ func TestLabelPropagation(t *testing.T) {
|
|||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
LabelKeyKubernetesPartOf: labelValueKubernetesPartOf,
|
LabelKeyKubernetesPartOf: labelValueKubernetesPartOf,
|
||||||
LabelKeyKubernetesVersion: "0.2.0",
|
LabelKeyKubernetesVersion: "0.2.0",
|
||||||
|
"arbitrary-label": "random-value",
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
runnerScaleSetIdAnnotationKey: "1",
|
runnerScaleSetIdAnnotationKey: "1",
|
||||||
AnnotationKeyGitHubRunnerGroupName: "test-group",
|
AnnotationKeyGitHubRunnerGroupName: "test-group",
|
||||||
|
AnnotationKeyGitHubRunnerScaleSetName: "test-scale-set",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
@@ -38,25 +40,28 @@ func TestLabelPropagation(t *testing.T) {
|
|||||||
assert.Equal(t, labelValueKubernetesPartOf, ephemeralRunnerSet.Labels[LabelKeyKubernetesPartOf])
|
assert.Equal(t, labelValueKubernetesPartOf, ephemeralRunnerSet.Labels[LabelKeyKubernetesPartOf])
|
||||||
assert.Equal(t, "runner-set", ephemeralRunnerSet.Labels[LabelKeyKubernetesComponent])
|
assert.Equal(t, "runner-set", ephemeralRunnerSet.Labels[LabelKeyKubernetesComponent])
|
||||||
assert.Equal(t, autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion], ephemeralRunnerSet.Labels[LabelKeyKubernetesVersion])
|
assert.Equal(t, autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion], ephemeralRunnerSet.Labels[LabelKeyKubernetesVersion])
|
||||||
assert.NotEmpty(t, ephemeralRunnerSet.Labels[labelKeyRunnerSpecHash])
|
assert.NotEmpty(t, ephemeralRunnerSet.Annotations[annotationKeyRunnerSpecHash])
|
||||||
assert.Equal(t, autoscalingRunnerSet.Name, ephemeralRunnerSet.Labels[LabelKeyGitHubScaleSetName])
|
assert.Equal(t, autoscalingRunnerSet.Name, ephemeralRunnerSet.Labels[LabelKeyGitHubScaleSetName])
|
||||||
assert.Equal(t, autoscalingRunnerSet.Namespace, ephemeralRunnerSet.Labels[LabelKeyGitHubScaleSetNamespace])
|
assert.Equal(t, autoscalingRunnerSet.Namespace, ephemeralRunnerSet.Labels[LabelKeyGitHubScaleSetNamespace])
|
||||||
assert.Equal(t, "", ephemeralRunnerSet.Labels[LabelKeyGitHubEnterprise])
|
assert.Equal(t, "", ephemeralRunnerSet.Labels[LabelKeyGitHubEnterprise])
|
||||||
assert.Equal(t, "org", ephemeralRunnerSet.Labels[LabelKeyGitHubOrganization])
|
assert.Equal(t, "org", ephemeralRunnerSet.Labels[LabelKeyGitHubOrganization])
|
||||||
assert.Equal(t, "repo", ephemeralRunnerSet.Labels[LabelKeyGitHubRepository])
|
assert.Equal(t, "repo", ephemeralRunnerSet.Labels[LabelKeyGitHubRepository])
|
||||||
assert.Equal(t, autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerGroupName], ephemeralRunnerSet.Annotations[AnnotationKeyGitHubRunnerGroupName])
|
assert.Equal(t, autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerGroupName], ephemeralRunnerSet.Annotations[AnnotationKeyGitHubRunnerGroupName])
|
||||||
|
assert.Equal(t, autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerScaleSetName], ephemeralRunnerSet.Annotations[AnnotationKeyGitHubRunnerScaleSetName])
|
||||||
|
assert.Equal(t, autoscalingRunnerSet.Labels["arbitrary-label"], ephemeralRunnerSet.Labels["arbitrary-label"])
|
||||||
|
|
||||||
listener, err := b.newAutoScalingListener(&autoscalingRunnerSet, ephemeralRunnerSet, autoscalingRunnerSet.Namespace, "test:latest", nil)
|
listener, err := b.newAutoScalingListener(&autoscalingRunnerSet, ephemeralRunnerSet, autoscalingRunnerSet.Namespace, "test:latest", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, labelValueKubernetesPartOf, listener.Labels[LabelKeyKubernetesPartOf])
|
assert.Equal(t, labelValueKubernetesPartOf, listener.Labels[LabelKeyKubernetesPartOf])
|
||||||
assert.Equal(t, "runner-scale-set-listener", listener.Labels[LabelKeyKubernetesComponent])
|
assert.Equal(t, "runner-scale-set-listener", listener.Labels[LabelKeyKubernetesComponent])
|
||||||
assert.Equal(t, autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion], listener.Labels[LabelKeyKubernetesVersion])
|
assert.Equal(t, autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion], listener.Labels[LabelKeyKubernetesVersion])
|
||||||
assert.NotEmpty(t, ephemeralRunnerSet.Labels[labelKeyRunnerSpecHash])
|
assert.NotEmpty(t, ephemeralRunnerSet.Annotations[annotationKeyRunnerSpecHash])
|
||||||
assert.Equal(t, autoscalingRunnerSet.Name, listener.Labels[LabelKeyGitHubScaleSetName])
|
assert.Equal(t, autoscalingRunnerSet.Name, listener.Labels[LabelKeyGitHubScaleSetName])
|
||||||
assert.Equal(t, autoscalingRunnerSet.Namespace, listener.Labels[LabelKeyGitHubScaleSetNamespace])
|
assert.Equal(t, autoscalingRunnerSet.Namespace, listener.Labels[LabelKeyGitHubScaleSetNamespace])
|
||||||
assert.Equal(t, "", listener.Labels[LabelKeyGitHubEnterprise])
|
assert.Equal(t, "", listener.Labels[LabelKeyGitHubEnterprise])
|
||||||
assert.Equal(t, "org", listener.Labels[LabelKeyGitHubOrganization])
|
assert.Equal(t, "org", listener.Labels[LabelKeyGitHubOrganization])
|
||||||
assert.Equal(t, "repo", listener.Labels[LabelKeyGitHubRepository])
|
assert.Equal(t, "repo", listener.Labels[LabelKeyGitHubRepository])
|
||||||
|
assert.Equal(t, autoscalingRunnerSet.Labels["arbitrary-label"], listener.Labels["arbitrary-label"])
|
||||||
|
|
||||||
listenerServiceAccount := &corev1.ServiceAccount{
|
listenerServiceAccount := &corev1.ServiceAccount{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@@ -83,6 +88,7 @@ func TestLabelPropagation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.Equal(t, "runner", ephemeralRunner.Labels[LabelKeyKubernetesComponent])
|
assert.Equal(t, "runner", ephemeralRunner.Labels[LabelKeyKubernetesComponent])
|
||||||
assert.Equal(t, autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerGroupName], ephemeralRunner.Annotations[AnnotationKeyGitHubRunnerGroupName])
|
assert.Equal(t, autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerGroupName], ephemeralRunner.Annotations[AnnotationKeyGitHubRunnerGroupName])
|
||||||
|
assert.Equal(t, autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerScaleSetName], ephemeralRunnerSet.Annotations[AnnotationKeyGitHubRunnerScaleSetName])
|
||||||
|
|
||||||
runnerSecret := &corev1.Secret{
|
runnerSecret := &corev1.Secret{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@@ -109,8 +115,9 @@ func TestGitHubURLTrimLabelValues(t *testing.T) {
|
|||||||
LabelKeyKubernetesVersion: "0.2.0",
|
LabelKeyKubernetesVersion: "0.2.0",
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
runnerScaleSetIdAnnotationKey: "1",
|
runnerScaleSetIdAnnotationKey: "1",
|
||||||
AnnotationKeyGitHubRunnerGroupName: "test-group",
|
AnnotationKeyGitHubRunnerGroupName: "test-group",
|
||||||
|
AnnotationKeyGitHubRunnerScaleSetName: "test-scale-set",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,50 @@ You can follow [this troubleshooting guide](https://docs.github.com/en/actions/h
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### v0.9.2
|
||||||
|
|
||||||
|
1. Refresh session if token expires during delete message [#3529](https://github.com/actions/actions-runner-controller/pull/3529)
|
||||||
|
1. Re-use the last desired patch on empty batch [#3453](https://github.com/actions/actions-runner-controller/pull/3453)
|
||||||
|
1. Extract single place to set up indexers [#3454](https://github.com/actions/actions-runner-controller/pull/3454)
|
||||||
|
1. Include controller version in logs [#3473](https://github.com/actions/actions-runner-controller/pull/3473)
|
||||||
|
1. Propogate arbitrary labels from runnersets to all created resources [#3157](https://github.com/actions/actions-runner-controller/pull/3157)
|
||||||
|
|
||||||
|
### v0.9.1
|
||||||
|
|
||||||
|
#### Major changes
|
||||||
|
|
||||||
|
1. Shutdown metrics server when listener exits [#3445](https://github.com/actions/actions-runner-controller/pull/3445)
|
||||||
|
1. Propagate max capacity information to the actions back-end [#3431](https://github.com/actions/actions-runner-controller/pull/3431)
|
||||||
|
1. Refactor actions client error to include request id [#3430](https://github.com/actions/actions-runner-controller/pull/3430)
|
||||||
|
1. Include self correction on empty batch and avoid removing pending runners when cluster is busy [#3426](https://github.com/actions/actions-runner-controller/pull/3426)
|
||||||
|
1. Add topologySpreadConstraint to gha-runner-scale-set-controller chart [#3405](https://github.com/actions/actions-runner-controller/pull/3405)
|
||||||
|
|
||||||
|
### v0.9.0
|
||||||
|
|
||||||
|
#### ⚠️ Warning
|
||||||
|
|
||||||
|
- This release contains CRD changes. During the upgrade, please remove the old CRDs before re-installing the new version. For more information, please read the [Upgrading ARC](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/deploying-runner-scale-sets-with-actions-runner-controller#upgrading-arc).
|
||||||
|
- This release contains changes in the [default docker socket path](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/deploying-runner-scale-sets-with-actions-runner-controller#upgrading-arc) expanded for container mode `dind`.
|
||||||
|
- Older version of the listener (`githubrunnerscalesetlistener`) is deprecated and will be removed in the future `0.10.0` release.
|
||||||
|
|
||||||
|
Please evaluate these changes carefully before upgrading.
|
||||||
|
|
||||||
|
#### Major changes
|
||||||
|
|
||||||
|
1. Change docker socket path to /var/run/docker.sock [#3337](https://github.com/actions/actions-runner-controller/pull/3337)
|
||||||
|
1. Update metrics to include repository on job-based label [#3310](https://github.com/actions/actions-runner-controller/pull/3310)
|
||||||
|
1. Bump Go version to 1.22.1 [#3290](https://github.com/actions/actions-runner-controller/pull/3290)
|
||||||
|
1. Propagate runner scale set name annotation to EphemeralRunner [#3098](https://github.com/actions/actions-runner-controller/pull/3098)
|
||||||
|
1. Add annotation with values hash to re-create listener [#3195](https://github.com/actions/actions-runner-controller/pull/3195)
|
||||||
|
1. Fix overscaling when the controller is much faster then the listener [#3371](https://github.com/actions/actions-runner-controller/pull/3371)
|
||||||
|
1. Add retry on 401 and 403 for runner-registration [#3377](https://github.com/actions/actions-runner-controller/pull/3377)
|
||||||
|
|
||||||
|
|
||||||
|
### v0.8.3
|
||||||
|
1. Expose volumeMounts and volumes in gha-runner-scale-set-controller [#3260](https://github.com/actions/actions-runner-controller/pull/3260)
|
||||||
|
1. Refer to the correct variable in discovery error message [#3296](https://github.com/actions/actions-runner-controller/pull/3296)
|
||||||
|
1. Fix acquire jobs after session refresh ghalistener [#3307](https://github.com/actions/actions-runner-controller/pull/3307)
|
||||||
|
|
||||||
### v0.8.2
|
### v0.8.2
|
||||||
1. Add listener graceful termination period and background context after the message is received [#3187](https://github.com/actions/actions-runner-controller/pull/3187)
|
1. Add listener graceful termination period and background context after the message is received [#3187](https://github.com/actions/actions-runner-controller/pull/3187)
|
||||||
1. Publish metrics in the new ghalistener [#3193](https://github.com/actions/actions-runner-controller/pull/3193)
|
1. Publish metrics in the new ghalistener [#3193](https://github.com/actions/actions-runner-controller/pull/3193)
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ const (
|
|||||||
apiVersionQueryParam = "api-version=6.0-preview"
|
apiVersionQueryParam = "api-version=6.0-preview"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Header used to propagate capacity information to the back-end
|
||||||
|
const HeaderScaleSetMaxCapacity = "X-ScaleSetMaxCapacity"
|
||||||
|
|
||||||
//go:generate mockery --inpackage --name=ActionsService
|
//go:generate mockery --inpackage --name=ActionsService
|
||||||
type ActionsService interface {
|
type ActionsService interface {
|
||||||
GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*RunnerScaleSet, error)
|
GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*RunnerScaleSet, error)
|
||||||
@@ -45,7 +48,7 @@ type ActionsService interface {
|
|||||||
AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error)
|
AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error)
|
||||||
GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (*AcquirableJobList, error)
|
GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (*AcquirableJobList, error)
|
||||||
|
|
||||||
GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64) (*RunnerScaleSetMessage, error)
|
GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error)
|
||||||
DeleteMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, messageId int64) error
|
DeleteMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, messageId int64) error
|
||||||
|
|
||||||
GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *RunnerScaleSetJitRunnerSetting, scaleSetId int) (*RunnerScaleSetJitRunnerConfig, error)
|
GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *RunnerScaleSetJitRunnerSetting, scaleSetId int) (*RunnerScaleSetJitRunnerConfig, error)
|
||||||
@@ -104,6 +107,8 @@ type Client struct {
|
|||||||
proxyFunc ProxyFunc
|
proxyFunc ProxyFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ ActionsService = &Client{}
|
||||||
|
|
||||||
type ProxyFunc func(req *http.Request) (*url.URL, error)
|
type ProxyFunc func(req *http.Request) (*url.URL, error)
|
||||||
|
|
||||||
type ClientOption func(*Client)
|
type ClientOption func(*Client)
|
||||||
@@ -355,15 +360,22 @@ func (c *Client) GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runne
|
|||||||
}
|
}
|
||||||
|
|
||||||
var runnerScaleSetList *runnerScaleSetsResponse
|
var runnerScaleSetList *runnerScaleSetsResponse
|
||||||
err = json.NewDecoder(resp.Body).Decode(&runnerScaleSetList)
|
if err := json.NewDecoder(resp.Body).Decode(&runnerScaleSetList); err != nil {
|
||||||
if err != nil {
|
return nil, &ActionsError{
|
||||||
return nil, err
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if runnerScaleSetList.Count == 0 {
|
if runnerScaleSetList.Count == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if runnerScaleSetList.Count > 1 {
|
if runnerScaleSetList.Count > 1 {
|
||||||
return nil, fmt.Errorf("multiple runner scale sets found with name %s", runnerScaleSetName)
|
return nil, &ActionsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: fmt.Errorf("multiple runner scale sets found with name %q", runnerScaleSetName),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &runnerScaleSetList.RunnerScaleSets[0], nil
|
return &runnerScaleSetList.RunnerScaleSets[0], nil
|
||||||
@@ -386,9 +398,12 @@ func (c *Client) GetRunnerScaleSetById(ctx context.Context, runnerScaleSetId int
|
|||||||
}
|
}
|
||||||
|
|
||||||
var runnerScaleSet *RunnerScaleSet
|
var runnerScaleSet *RunnerScaleSet
|
||||||
err = json.NewDecoder(resp.Body).Decode(&runnerScaleSet)
|
if err := json.NewDecoder(resp.Body).Decode(&runnerScaleSet); err != nil {
|
||||||
if err != nil {
|
return nil, &ActionsError{
|
||||||
return nil, err
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return runnerScaleSet, nil
|
return runnerScaleSet, nil
|
||||||
}
|
}
|
||||||
@@ -408,23 +423,43 @@ func (c *Client) GetRunnerGroupByName(ctx context.Context, runnerGroup string) (
|
|||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, &ActionsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unexpected status code: %d - body: %s", resp.StatusCode, string(body))
|
return nil, fmt.Errorf("unexpected status code: %w", &ActionsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: errors.New(string(body)),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var runnerGroupList *RunnerGroupList
|
var runnerGroupList *RunnerGroupList
|
||||||
err = json.NewDecoder(resp.Body).Decode(&runnerGroupList)
|
err = json.NewDecoder(resp.Body).Decode(&runnerGroupList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, &ActionsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if runnerGroupList.Count == 0 {
|
if runnerGroupList.Count == 0 {
|
||||||
return nil, fmt.Errorf("no runner group found with name '%s'", runnerGroup)
|
return nil, &ActionsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: fmt.Errorf("no runner group found with name %q", runnerGroup),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if runnerGroupList.Count > 1 {
|
if runnerGroupList.Count > 1 {
|
||||||
return nil, fmt.Errorf("multiple runner group found with name %s", runnerGroup)
|
return nil, &ActionsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: fmt.Errorf("multiple runner group found with name %q", runnerGroup),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &runnerGroupList.RunnerGroups[0], nil
|
return &runnerGroupList.RunnerGroups[0], nil
|
||||||
@@ -450,9 +485,12 @@ func (c *Client) CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *Runne
|
|||||||
return nil, ParseActionsErrorFromResponse(resp)
|
return nil, ParseActionsErrorFromResponse(resp)
|
||||||
}
|
}
|
||||||
var createdRunnerScaleSet *RunnerScaleSet
|
var createdRunnerScaleSet *RunnerScaleSet
|
||||||
err = json.NewDecoder(resp.Body).Decode(&createdRunnerScaleSet)
|
if err := json.NewDecoder(resp.Body).Decode(&createdRunnerScaleSet); err != nil {
|
||||||
if err != nil {
|
return nil, &ActionsError{
|
||||||
return nil, err
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return createdRunnerScaleSet, nil
|
return createdRunnerScaleSet, nil
|
||||||
}
|
}
|
||||||
@@ -480,9 +518,12 @@ func (c *Client) UpdateRunnerScaleSet(ctx context.Context, runnerScaleSetId int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var updatedRunnerScaleSet *RunnerScaleSet
|
var updatedRunnerScaleSet *RunnerScaleSet
|
||||||
err = json.NewDecoder(resp.Body).Decode(&updatedRunnerScaleSet)
|
if err := json.NewDecoder(resp.Body).Decode(&updatedRunnerScaleSet); err != nil {
|
||||||
if err != nil {
|
return nil, &ActionsError{
|
||||||
return nil, err
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return updatedRunnerScaleSet, nil
|
return updatedRunnerScaleSet, nil
|
||||||
}
|
}
|
||||||
@@ -507,7 +548,7 @@ func (c *Client) DeleteRunnerScaleSet(ctx context.Context, runnerScaleSetId int)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64) (*RunnerScaleSetMessage, error) {
|
func (c *Client) GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error) {
|
||||||
u, err := url.Parse(messageQueueUrl)
|
u, err := url.Parse(messageQueueUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -519,6 +560,10 @@ func (c *Client) GetMessage(ctx context.Context, messageQueueUrl, messageQueueAc
|
|||||||
u.RawQuery = q.Encode()
|
u.RawQuery = q.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if maxCapacity < 0 {
|
||||||
|
return nil, fmt.Errorf("maxCapacity must be greater than or equal to 0")
|
||||||
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -527,6 +572,7 @@ func (c *Client) GetMessage(ctx context.Context, messageQueueUrl, messageQueueAc
|
|||||||
req.Header.Set("Accept", "application/json; api-version=6.0-preview")
|
req.Header.Set("Accept", "application/json; api-version=6.0-preview")
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", messageQueueAccessToken))
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", messageQueueAccessToken))
|
||||||
req.Header.Set("User-Agent", c.userAgent.String())
|
req.Header.Set("User-Agent", c.userAgent.String())
|
||||||
|
req.Header.Set(HeaderScaleSetMaxCapacity, strconv.Itoa(maxCapacity))
|
||||||
|
|
||||||
resp, err := c.Do(req)
|
resp, err := c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -547,15 +593,26 @@ func (c *Client) GetMessage(ctx context.Context, messageQueueUrl, messageQueueAc
|
|||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
body = trimByteOrderMark(body)
|
body = trimByteOrderMark(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, &ActionsError{
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, &MessageQueueTokenExpiredError{
|
||||||
|
activityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
statusCode: resp.StatusCode,
|
||||||
|
msg: string(body),
|
||||||
}
|
}
|
||||||
return nil, &MessageQueueTokenExpiredError{msg: string(body)}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var message *RunnerScaleSetMessage
|
var message *RunnerScaleSetMessage
|
||||||
err = json.NewDecoder(resp.Body).Decode(&message)
|
if err := json.NewDecoder(resp.Body).Decode(&message); err != nil {
|
||||||
if err != nil {
|
return nil, &ActionsError{
|
||||||
return nil, err
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return message, nil
|
return message, nil
|
||||||
}
|
}
|
||||||
@@ -591,9 +648,17 @@ func (c *Client) DeleteMessage(ctx context.Context, messageQueueUrl, messageQueu
|
|||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
body = trimByteOrderMark(body)
|
body = trimByteOrderMark(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return &ActionsError{
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &MessageQueueTokenExpiredError{
|
||||||
|
activityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
statusCode: resp.StatusCode,
|
||||||
|
msg: string(body),
|
||||||
}
|
}
|
||||||
return &MessageQueueTokenExpiredError{msg: string(body)}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -641,9 +706,18 @@ func (c *Client) doSessionRequest(ctx context.Context, method, path string, requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode == expectedResponseStatusCode {
|
if resp.StatusCode == expectedResponseStatusCode {
|
||||||
if responseUnmarshalTarget != nil {
|
if responseUnmarshalTarget == nil {
|
||||||
return json.NewDecoder(resp.Body).Decode(responseUnmarshalTarget)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(responseUnmarshalTarget); err != nil {
|
||||||
|
return &ActionsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,10 +729,18 @@ func (c *Client) doSessionRequest(ctx context.Context, method, path string, requ
|
|||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
body = trimByteOrderMark(body)
|
body = trimByteOrderMark(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return &ActionsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("unexpected status code: %d - body: %s", resp.StatusCode, string(body))
|
return fmt.Errorf("unexpected status code: %w", &ActionsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: errors.New(string(body)),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error) {
|
func (c *Client) AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error) {
|
||||||
@@ -692,16 +774,28 @@ func (c *Client) AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQ
|
|||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
body = trimByteOrderMark(body)
|
body = trimByteOrderMark(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, &ActionsError{
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, &MessageQueueTokenExpiredError{msg: string(body)}
|
return nil, &MessageQueueTokenExpiredError{
|
||||||
|
activityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
statusCode: resp.StatusCode,
|
||||||
|
msg: string(body),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var acquiredJobs *Int64List
|
var acquiredJobs *Int64List
|
||||||
err = json.NewDecoder(resp.Body).Decode(&acquiredJobs)
|
err = json.NewDecoder(resp.Body).Decode(&acquiredJobs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, &ActionsError{
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return acquiredJobs.Value, nil
|
return acquiredJobs.Value, nil
|
||||||
@@ -732,7 +826,11 @@ func (c *Client) GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (*
|
|||||||
var acquirableJobList *AcquirableJobList
|
var acquirableJobList *AcquirableJobList
|
||||||
err = json.NewDecoder(resp.Body).Decode(&acquirableJobList)
|
err = json.NewDecoder(resp.Body).Decode(&acquirableJobList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, &ActionsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return acquirableJobList, nil
|
return acquirableJobList, nil
|
||||||
@@ -761,9 +859,12 @@ func (c *Client) GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *
|
|||||||
}
|
}
|
||||||
|
|
||||||
var runnerJitConfig *RunnerScaleSetJitRunnerConfig
|
var runnerJitConfig *RunnerScaleSetJitRunnerConfig
|
||||||
err = json.NewDecoder(resp.Body).Decode(&runnerJitConfig)
|
if err := json.NewDecoder(resp.Body).Decode(&runnerJitConfig); err != nil {
|
||||||
if err != nil {
|
return nil, &ActionsError{
|
||||||
return nil, err
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return runnerJitConfig, nil
|
return runnerJitConfig, nil
|
||||||
}
|
}
|
||||||
@@ -786,9 +887,12 @@ func (c *Client) GetRunner(ctx context.Context, runnerId int64) (*RunnerReferenc
|
|||||||
}
|
}
|
||||||
|
|
||||||
var runnerReference *RunnerReference
|
var runnerReference *RunnerReference
|
||||||
err = json.NewDecoder(resp.Body).Decode(&runnerReference)
|
if err := json.NewDecoder(resp.Body).Decode(&runnerReference); err != nil {
|
||||||
if err != nil {
|
return nil, &ActionsError{
|
||||||
return nil, err
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return runnerReference, nil
|
return runnerReference, nil
|
||||||
@@ -812,9 +916,12 @@ func (c *Client) GetRunnerByName(ctx context.Context, runnerName string) (*Runne
|
|||||||
}
|
}
|
||||||
|
|
||||||
var runnerList *RunnerReferenceList
|
var runnerList *RunnerReferenceList
|
||||||
err = json.NewDecoder(resp.Body).Decode(&runnerList)
|
if err := json.NewDecoder(resp.Body).Decode(&runnerList); err != nil {
|
||||||
if err != nil {
|
return nil, &ActionsError{
|
||||||
return nil, err
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if runnerList.Count == 0 {
|
if runnerList.Count == 0 {
|
||||||
@@ -822,7 +929,11 @@ func (c *Client) GetRunnerByName(ctx context.Context, runnerName string) (*Runne
|
|||||||
}
|
}
|
||||||
|
|
||||||
if runnerList.Count > 1 {
|
if runnerList.Count > 1 {
|
||||||
return nil, fmt.Errorf("multiple runner found with name %s", runnerName)
|
return nil, &ActionsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
ActivityID: resp.Header.Get(HeaderActionsActivityID),
|
||||||
|
Err: fmt.Errorf("multiple runner found with name %s", runnerName),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &runnerList.RunnerReferences[0], nil
|
return &runnerList.RunnerReferences[0], nil
|
||||||
@@ -895,12 +1006,20 @@ func (c *Client) getRunnerRegistrationToken(ctx context.Context) (*registrationT
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unexpected response from Actions service during registration token call: %v - %v", resp.StatusCode, string(body))
|
return nil, &GitHubAPIError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
RequestID: resp.Header.Get(HeaderGitHubRequestID),
|
||||||
|
Err: errors.New(string(body)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var registrationToken *registrationToken
|
var registrationToken *registrationToken
|
||||||
if err := json.NewDecoder(resp.Body).Decode(®istrationToken); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(®istrationToken); err != nil {
|
||||||
return nil, err
|
return nil, &GitHubAPIError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
RequestID: resp.Header.Get(HeaderGitHubRequestID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return registrationToken, nil
|
return registrationToken, nil
|
||||||
@@ -937,8 +1056,14 @@ func (c *Client) fetchAccessToken(ctx context.Context, gitHubConfigURL string, c
|
|||||||
|
|
||||||
// Format: https://docs.github.com/en/rest/apps/apps#create-an-installation-access-token-for-an-app
|
// Format: https://docs.github.com/en/rest/apps/apps#create-an-installation-access-token-for-an-app
|
||||||
var accessToken *accessToken
|
var accessToken *accessToken
|
||||||
err = json.NewDecoder(resp.Body).Decode(&accessToken)
|
if err = json.NewDecoder(resp.Body).Decode(&accessToken); err != nil {
|
||||||
return accessToken, err
|
return nil, &GitHubAPIError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
RequestID: resp.Header.Get(HeaderGitHubRequestID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accessToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionsServiceAdminConnection struct {
|
type ActionsServiceAdminConnection struct {
|
||||||
@@ -975,25 +1100,55 @@ func (c *Client) getActionsServiceAdminConnection(ctx context.Context, rt *regis
|
|||||||
|
|
||||||
c.logger.Info("getting Actions tenant URL and JWT", "registrationURL", req.URL.String())
|
c.logger.Info("getting Actions tenant URL and JWT", "registrationURL", req.URL.String())
|
||||||
|
|
||||||
resp, err := c.Do(req)
|
var resp *http.Response
|
||||||
if err != nil {
|
retry := 0
|
||||||
return nil, err
|
for {
|
||||||
}
|
var err error
|
||||||
defer resp.Body.Close()
|
resp, err = c.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
|
||||||
registrationErr := fmt.Errorf("unexpected response from Actions service during registration call: %v", resp.StatusCode)
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var innerErr error
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%v - %v", registrationErr, err)
|
innerErr = err
|
||||||
|
} else {
|
||||||
|
innerErr = errors.New(string(body))
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("%v - %v", registrationErr, string(body))
|
|
||||||
|
if resp.StatusCode != http.StatusUnauthorized && resp.StatusCode != http.StatusForbidden {
|
||||||
|
return nil, &GitHubAPIError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
RequestID: resp.Header.Get(HeaderGitHubRequestID),
|
||||||
|
Err: innerErr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
retry++
|
||||||
|
if retry > 3 {
|
||||||
|
return nil, fmt.Errorf("unable to register runner after 3 retries: %w", &GitHubAPIError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
RequestID: resp.Header.Get(HeaderGitHubRequestID),
|
||||||
|
Err: innerErr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(500 * int(time.Millisecond) * (retry + 1)))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionsServiceAdminConnection *ActionsServiceAdminConnection
|
var actionsServiceAdminConnection *ActionsServiceAdminConnection
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&actionsServiceAdminConnection); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&actionsServiceAdminConnection); err != nil {
|
||||||
return nil, err
|
return nil, &GitHubAPIError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
RequestID: resp.Header.Get(HeaderGitHubRequestID),
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return actionsServiceAdminConnection, nil
|
return actionsServiceAdminConnection, nil
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ func TestGetMessage(t *testing.T) {
|
|||||||
client, err := actions.NewClient(s.configURLForOrg("my-org"), auth)
|
client, err := actions.NewClient(s.configURLForOrg("my-org"), auth)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
got, err := client.GetMessage(ctx, s.URL, token, 0)
|
got, err := client.GetMessage(ctx, s.URL, token, 0, 10)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
})
|
})
|
||||||
@@ -52,7 +53,7 @@ func TestGetMessage(t *testing.T) {
|
|||||||
client, err := actions.NewClient(s.configURLForOrg("my-org"), auth)
|
client, err := actions.NewClient(s.configURLForOrg("my-org"), auth)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
got, err := client.GetMessage(ctx, s.URL, token, 1)
|
got, err := client.GetMessage(ctx, s.URL, token, 1, 10)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
})
|
})
|
||||||
@@ -76,7 +77,7 @@ func TestGetMessage(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = client.GetMessage(ctx, server.URL, token, 0)
|
_, err = client.GetMessage(ctx, server.URL, token, 0, 10)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||||
})
|
})
|
||||||
@@ -89,7 +90,7 @@ func TestGetMessage(t *testing.T) {
|
|||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = client.GetMessage(ctx, server.URL, token, 0)
|
_, err = client.GetMessage(ctx, server.URL, token, 0, 10)
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
|
|
||||||
var expectedErr *actions.MessageQueueTokenExpiredError
|
var expectedErr *actions.MessageQueueTokenExpiredError
|
||||||
@@ -98,7 +99,7 @@ func TestGetMessage(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Status code not found", func(t *testing.T) {
|
t.Run("Status code not found", func(t *testing.T) {
|
||||||
want := actions.ActionsError{
|
want := actions.ActionsError{
|
||||||
Message: "Request returned status: 404 Not Found",
|
Err: errors.New("unknown exception"),
|
||||||
StatusCode: 404,
|
StatusCode: 404,
|
||||||
}
|
}
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
@@ -108,7 +109,7 @@ func TestGetMessage(t *testing.T) {
|
|||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = client.GetMessage(ctx, server.URL, token, 0)
|
_, err = client.GetMessage(ctx, server.URL, token, 0, 10)
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
assert.Equal(t, want.Error(), err.Error())
|
assert.Equal(t, want.Error(), err.Error())
|
||||||
})
|
})
|
||||||
@@ -122,9 +123,35 @@ func TestGetMessage(t *testing.T) {
|
|||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = client.GetMessage(ctx, server.URL, token, 0)
|
_, err = client.GetMessage(ctx, server.URL, token, 0, 10)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Capacity error handling", func(t *testing.T) {
|
||||||
|
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
hc := r.Header.Get(actions.HeaderScaleSetMaxCapacity)
|
||||||
|
c, err := strconv.Atoi(hc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.GreaterOrEqual(t, c, 0)
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
}))
|
||||||
|
|
||||||
|
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = client.GetMessage(ctx, server.URL, token, 0, -1)
|
||||||
|
require.Error(t, err)
|
||||||
|
// Ensure we don't send requests with negative capacity
|
||||||
|
assert.False(t, errors.Is(err, &actions.ActionsError{}))
|
||||||
|
|
||||||
|
_, err = client.GetMessage(ctx, server.URL, token, 0, 0)
|
||||||
|
assert.Error(t, err)
|
||||||
|
var expectedErr *actions.ActionsError
|
||||||
|
assert.ErrorAs(t, err, &expectedErr)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, expectedErr.StatusCode)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteMessage(t *testing.T) {
|
func TestDeleteMessage(t *testing.T) {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const exampleRequestID = "5ddf2050-dae0-013c-9159-04421ad31b68"
|
||||||
|
|
||||||
func TestCreateMessageSession(t *testing.T) {
|
func TestCreateMessageSession(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
auth := &actions.ActionsAuth{
|
auth := &actions.ActionsAuth{
|
||||||
@@ -69,13 +71,17 @@ func TestCreateMessageSession(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
want := &actions.ActionsError{
|
want := &actions.ActionsError{
|
||||||
ExceptionName: "CSharpExceptionNameHere",
|
ActivityID: exampleRequestID,
|
||||||
Message: "could not do something",
|
StatusCode: http.StatusBadRequest,
|
||||||
StatusCode: http.StatusBadRequest,
|
Err: &actions.ActionsExceptionError{
|
||||||
|
ExceptionName: "CSharpExceptionNameHere",
|
||||||
|
Message: "could not do something",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Header().Set(actions.HeaderActionsActivityID, exampleRequestID)
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
resp := []byte(`{"typeName": "CSharpExceptionNameHere","message": "could not do something"}`)
|
resp := []byte(`{"typeName": "CSharpExceptionNameHere","message": "could not do something"}`)
|
||||||
w.Write(resp)
|
w.Write(resp)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -124,9 +125,15 @@ func TestGetRunnerScaleSet(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Multiple runner scale sets found", func(t *testing.T) {
|
t.Run("Multiple runner scale sets found", func(t *testing.T) {
|
||||||
wantErr := fmt.Errorf("multiple runner scale sets found with name %s", scaleSetName)
|
reqID := uuid.NewString()
|
||||||
|
wantErr := &actions.ActionsError{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
ActivityID: reqID,
|
||||||
|
Err: fmt.Errorf("multiple runner scale sets found with name %q", scaleSetName),
|
||||||
|
}
|
||||||
runnerScaleSetsResp := []byte(`{"count":2,"value":[{"id":1,"name":"ScaleSet"}]}`)
|
runnerScaleSetsResp := []byte(`{"count":2,"value":[{"id":1,"name":"ScaleSet"}]}`)
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.Header().Set(actions.HeaderActionsActivityID, reqID)
|
||||||
w.Write(runnerScaleSetsResp)
|
w.Write(runnerScaleSetsResp)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
@@ -2,63 +2,117 @@ package actions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Header names for request IDs
|
||||||
|
const (
|
||||||
|
HeaderActionsActivityID = "ActivityId"
|
||||||
|
HeaderGitHubRequestID = "X-GitHub-Request-Id"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GitHubAPIError struct {
|
||||||
|
StatusCode int
|
||||||
|
RequestID string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *GitHubAPIError) Error() string {
|
||||||
|
return fmt.Sprintf("github api error: StatusCode %d, RequestID %q: %v", e.StatusCode, e.RequestID, e.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *GitHubAPIError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
type ActionsError struct {
|
type ActionsError struct {
|
||||||
ExceptionName string `json:"typeName,omitempty"`
|
ActivityID string
|
||||||
Message string `json:"message,omitempty"`
|
StatusCode int
|
||||||
StatusCode int
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ActionsError) Error() string {
|
func (e *ActionsError) Error() string {
|
||||||
return fmt.Sprintf("%v - had issue communicating with Actions backend: %v", e.StatusCode, e.Message)
|
return fmt.Sprintf("actions error: StatusCode %d, AcivityId %q: %v", e.StatusCode, e.ActivityID, e.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ActionsError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ActionsError) IsException(target string) bool {
|
||||||
|
if ex, ok := e.Err.(*ActionsExceptionError); ok {
|
||||||
|
return strings.Contains(ex.ExceptionName, target)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionsExceptionError struct {
|
||||||
|
ExceptionName string `json:"typeName,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ActionsExceptionError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %s", e.ExceptionName, e.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseActionsErrorFromResponse(response *http.Response) error {
|
func ParseActionsErrorFromResponse(response *http.Response) error {
|
||||||
if response.ContentLength == 0 {
|
if response.ContentLength == 0 {
|
||||||
message := "Request returned status: " + response.Status
|
|
||||||
return &ActionsError{
|
return &ActionsError{
|
||||||
ExceptionName: "unknown",
|
ActivityID: response.Header.Get(HeaderActionsActivityID),
|
||||||
Message: message,
|
StatusCode: response.StatusCode,
|
||||||
StatusCode: response.StatusCode,
|
Err: errors.New("unknown exception"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
body, err := io.ReadAll(response.Body)
|
body, err := io.ReadAll(response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return &ActionsError{
|
||||||
|
ActivityID: response.Header.Get(HeaderActionsActivityID),
|
||||||
|
StatusCode: response.StatusCode,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body = trimByteOrderMark(body)
|
body = trimByteOrderMark(body)
|
||||||
contentType, ok := response.Header["Content-Type"]
|
contentType, ok := response.Header["Content-Type"]
|
||||||
if ok && len(contentType) > 0 && strings.Contains(contentType[0], "text/plain") {
|
if ok && len(contentType) > 0 && strings.Contains(contentType[0], "text/plain") {
|
||||||
message := string(body)
|
message := string(body)
|
||||||
statusCode := response.StatusCode
|
|
||||||
return &ActionsError{
|
return &ActionsError{
|
||||||
Message: message,
|
ActivityID: response.Header.Get(HeaderActionsActivityID),
|
||||||
StatusCode: statusCode,
|
StatusCode: response.StatusCode,
|
||||||
|
Err: errors.New(message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsError := &ActionsError{StatusCode: response.StatusCode}
|
var exception ActionsExceptionError
|
||||||
if err := json.Unmarshal(body, &actionsError); err != nil {
|
if err := json.Unmarshal(body, &exception); err != nil {
|
||||||
return err
|
return &ActionsError{
|
||||||
|
ActivityID: response.Header.Get(HeaderActionsActivityID),
|
||||||
|
StatusCode: response.StatusCode,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return actionsError
|
return &ActionsError{
|
||||||
|
ActivityID: response.Header.Get(HeaderActionsActivityID),
|
||||||
|
StatusCode: response.StatusCode,
|
||||||
|
Err: &exception,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageQueueTokenExpiredError struct {
|
type MessageQueueTokenExpiredError struct {
|
||||||
msg string
|
activityID string
|
||||||
|
statusCode int
|
||||||
|
msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *MessageQueueTokenExpiredError) Error() string {
|
func (e *MessageQueueTokenExpiredError) Error() string {
|
||||||
return e.msg
|
return fmt.Sprintf("MessageQueueTokenExpiredError: AcivityId %q, StatusCode %d: %s", e.activityID, e.statusCode, e.msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
type HttpClientSideError struct {
|
type HttpClientSideError struct {
|
||||||
|
|||||||
206
github/actions/errors_test.go
Normal file
206
github/actions/errors_test.go
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
package actions_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestActionsError(t *testing.T) {
|
||||||
|
t.Run("contains the status code, activity ID, and error", func(t *testing.T) {
|
||||||
|
err := &actions.ActionsError{
|
||||||
|
ActivityID: "activity-id",
|
||||||
|
StatusCode: 404,
|
||||||
|
Err: errors.New("example error description"),
|
||||||
|
}
|
||||||
|
|
||||||
|
s := err.Error()
|
||||||
|
assert.Contains(t, s, "StatusCode 404")
|
||||||
|
assert.Contains(t, s, "AcivityId \"activity-id\"")
|
||||||
|
assert.Contains(t, s, "example error description")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unwraps the error", func(t *testing.T) {
|
||||||
|
err := &actions.ActionsError{
|
||||||
|
ActivityID: "activity-id",
|
||||||
|
StatusCode: 404,
|
||||||
|
Err: &actions.ActionsExceptionError{
|
||||||
|
ExceptionName: "exception-name",
|
||||||
|
Message: "example error message",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, err.Unwrap(), err.Err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("is exception is ok", func(t *testing.T) {
|
||||||
|
err := &actions.ActionsError{
|
||||||
|
ActivityID: "activity-id",
|
||||||
|
StatusCode: 404,
|
||||||
|
Err: &actions.ActionsExceptionError{
|
||||||
|
ExceptionName: "exception-name",
|
||||||
|
Message: "example error message",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var exception *actions.ActionsExceptionError
|
||||||
|
assert.True(t, errors.As(err, &exception))
|
||||||
|
|
||||||
|
assert.True(t, err.IsException("exception-name"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("is exception is not ok", func(t *testing.T) {
|
||||||
|
tt := map[string]*actions.ActionsError{
|
||||||
|
"not an exception": {
|
||||||
|
ActivityID: "activity-id",
|
||||||
|
StatusCode: 404,
|
||||||
|
Err: errors.New("example error description"),
|
||||||
|
},
|
||||||
|
"not target exception": {
|
||||||
|
ActivityID: "activity-id",
|
||||||
|
StatusCode: 404,
|
||||||
|
Err: &actions.ActionsExceptionError{
|
||||||
|
ExceptionName: "exception-name",
|
||||||
|
Message: "example error message",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
targetException := "target-exception"
|
||||||
|
for name, err := range tt {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert.False(t, err.IsException(targetException))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActionsExceptionError(t *testing.T) {
|
||||||
|
t.Run("contains the exception name and message", func(t *testing.T) {
|
||||||
|
err := &actions.ActionsExceptionError{
|
||||||
|
ExceptionName: "exception-name",
|
||||||
|
Message: "example error message",
|
||||||
|
}
|
||||||
|
|
||||||
|
s := err.Error()
|
||||||
|
assert.Contains(t, s, "exception-name")
|
||||||
|
assert.Contains(t, s, "example error message")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitHubAPIError(t *testing.T) {
|
||||||
|
t.Run("contains the status code, request ID, and error", func(t *testing.T) {
|
||||||
|
err := &actions.GitHubAPIError{
|
||||||
|
StatusCode: 404,
|
||||||
|
RequestID: "request-id",
|
||||||
|
Err: errors.New("example error description"),
|
||||||
|
}
|
||||||
|
|
||||||
|
s := err.Error()
|
||||||
|
assert.Contains(t, s, "StatusCode 404")
|
||||||
|
assert.Contains(t, s, "RequestID \"request-id\"")
|
||||||
|
assert.Contains(t, s, "example error description")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unwraps the error", func(t *testing.T) {
|
||||||
|
err := &actions.GitHubAPIError{
|
||||||
|
StatusCode: 404,
|
||||||
|
RequestID: "request-id",
|
||||||
|
Err: errors.New("example error description"),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, err.Unwrap(), err.Err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseActionsErrorFromResponse(t *testing.T) {
|
||||||
|
t.Run("empty content length", func(t *testing.T) {
|
||||||
|
response := &http.Response{
|
||||||
|
ContentLength: 0,
|
||||||
|
Header: http.Header{
|
||||||
|
actions.HeaderActionsActivityID: []string{"activity-id"},
|
||||||
|
},
|
||||||
|
StatusCode: 404,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := actions.ParseActionsErrorFromResponse(response)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, err.(*actions.ActionsError).ActivityID, "activity-id")
|
||||||
|
assert.Equal(t, err.(*actions.ActionsError).StatusCode, 404)
|
||||||
|
assert.Equal(t, err.(*actions.ActionsError).Err.Error(), "unknown exception")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("contains text plain error", func(t *testing.T) {
|
||||||
|
errorMessage := "example error message"
|
||||||
|
response := &http.Response{
|
||||||
|
ContentLength: int64(len(errorMessage)),
|
||||||
|
Header: http.Header{
|
||||||
|
actions.HeaderActionsActivityID: []string{"activity-id"},
|
||||||
|
"Content-Type": []string{"text/plain"},
|
||||||
|
},
|
||||||
|
StatusCode: 404,
|
||||||
|
Body: io.NopCloser(strings.NewReader(errorMessage)),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := actions.ParseActionsErrorFromResponse(response)
|
||||||
|
require.Error(t, err)
|
||||||
|
var actionsError *actions.ActionsError
|
||||||
|
assert.ErrorAs(t, err, &actionsError)
|
||||||
|
assert.Equal(t, actionsError.ActivityID, "activity-id")
|
||||||
|
assert.Equal(t, actionsError.StatusCode, 404)
|
||||||
|
assert.Equal(t, actionsError.Err.Error(), errorMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("contains json error", func(t *testing.T) {
|
||||||
|
errorMessage := `{"typeName":"exception-name","message":"example error message"}`
|
||||||
|
response := &http.Response{
|
||||||
|
ContentLength: int64(len(errorMessage)),
|
||||||
|
Header: http.Header{
|
||||||
|
actions.HeaderActionsActivityID: []string{"activity-id"},
|
||||||
|
"Content-Type": []string{"application/json"},
|
||||||
|
},
|
||||||
|
StatusCode: 404,
|
||||||
|
Body: io.NopCloser(strings.NewReader(errorMessage)),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := actions.ParseActionsErrorFromResponse(response)
|
||||||
|
require.Error(t, err)
|
||||||
|
var actionsError *actions.ActionsError
|
||||||
|
assert.ErrorAs(t, err, &actionsError)
|
||||||
|
assert.Equal(t, actionsError.ActivityID, "activity-id")
|
||||||
|
assert.Equal(t, actionsError.StatusCode, 404)
|
||||||
|
|
||||||
|
inner, ok := actionsError.Err.(*actions.ActionsExceptionError)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, inner.ExceptionName, "exception-name")
|
||||||
|
assert.Equal(t, inner.Message, "example error message")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("wrapped exception error", func(t *testing.T) {
|
||||||
|
errorMessage := `{"typeName":"exception-name","message":"example error message"}`
|
||||||
|
response := &http.Response{
|
||||||
|
ContentLength: int64(len(errorMessage)),
|
||||||
|
Header: http.Header{
|
||||||
|
actions.HeaderActionsActivityID: []string{"activity-id"},
|
||||||
|
"Content-Type": []string{"application/json"},
|
||||||
|
},
|
||||||
|
StatusCode: 404,
|
||||||
|
Body: io.NopCloser(strings.NewReader(errorMessage)),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := actions.ParseActionsErrorFromResponse(response)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
var actionsExceptionError *actions.ActionsExceptionError
|
||||||
|
assert.ErrorAs(t, err, &actionsExceptionError)
|
||||||
|
|
||||||
|
assert.Equal(t, actionsExceptionError.ExceptionName, "exception-name")
|
||||||
|
assert.Equal(t, actionsExceptionError.Message, "example error message")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -259,7 +259,7 @@ func (f *FakeClient) GetAcquirableJobs(ctx context.Context, runnerScaleSetId int
|
|||||||
return f.getAcquirableJobsResult.AcquirableJobList, f.getAcquirableJobsResult.err
|
return f.getAcquirableJobsResult.AcquirableJobList, f.getAcquirableJobsResult.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeClient) GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64) (*actions.RunnerScaleSetMessage, error) {
|
func (f *FakeClient) GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*actions.RunnerScaleSetMessage, error) {
|
||||||
return f.getMessageResult.RunnerScaleSetMessage, f.getMessageResult.err
|
return f.getMessageResult.RunnerScaleSetMessage, f.getMessageResult.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package actions_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -138,7 +139,13 @@ func TestNewActionsServiceRequest(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
w.Write([]byte(errMessage))
|
w.Write([]byte(errMessage))
|
||||||
}
|
}
|
||||||
server := testserver.New(t, nil, testserver.WithActionsToken("random-token"), testserver.WithActionsToken(newToken), testserver.WithActionsRegistrationTokenHandler(unauthorizedHandler))
|
server := testserver.New(
|
||||||
|
t,
|
||||||
|
nil,
|
||||||
|
testserver.WithActionsToken("random-token"),
|
||||||
|
testserver.WithActionsToken(newToken),
|
||||||
|
testserver.WithActionsRegistrationTokenHandler(unauthorizedHandler),
|
||||||
|
)
|
||||||
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
expiringToken := "expiring-token"
|
expiringToken := "expiring-token"
|
||||||
@@ -152,6 +159,43 @@ func TestNewActionsServiceRequest(t *testing.T) {
|
|||||||
assert.Equal(t, client.ActionsServiceAdminTokenExpiresAt, expiresAt)
|
assert.Equal(t, client.ActionsServiceAdminTokenExpiresAt, expiresAt)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("admin token refresh retry", func(t *testing.T) {
|
||||||
|
newToken := defaultActionsToken(t)
|
||||||
|
errMessage := `{"message":"test"}`
|
||||||
|
|
||||||
|
srv := "http://github.com/my-org"
|
||||||
|
resp := &actions.ActionsServiceAdminConnection{
|
||||||
|
AdminToken: &newToken,
|
||||||
|
ActionsServiceUrl: &srv,
|
||||||
|
}
|
||||||
|
failures := 0
|
||||||
|
unauthorizedHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if failures < 2 {
|
||||||
|
failures++
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
w.Write([]byte(errMessage))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
_ = json.NewEncoder(w).Encode(resp)
|
||||||
|
}
|
||||||
|
server := testserver.New(t, nil, testserver.WithActionsToken("random-token"), testserver.WithActionsToken(newToken), testserver.WithActionsRegistrationTokenHandler(unauthorizedHandler))
|
||||||
|
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
||||||
|
require.NoError(t, err)
|
||||||
|
expiringToken := "expiring-token"
|
||||||
|
expiresAt := time.Now().Add(59 * time.Second)
|
||||||
|
client.ActionsServiceAdminToken = expiringToken
|
||||||
|
client.ActionsServiceAdminTokenExpiresAt = expiresAt
|
||||||
|
|
||||||
|
_, err = client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, client.ActionsServiceAdminToken, newToken)
|
||||||
|
assert.Equal(t, client.ActionsServiceURL, srv)
|
||||||
|
assert.NotEqual(t, client.ActionsServiceAdminTokenExpiresAt, expiresAt)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("token is currently valid", func(t *testing.T) {
|
t.Run("token is currently valid", func(t *testing.T) {
|
||||||
tokenThatShouldNotBeFetched := defaultActionsToken(t)
|
tokenThatShouldNotBeFetched := defaultActionsToken(t)
|
||||||
server := testserver.New(t, nil, testserver.WithActionsToken(tokenThatShouldNotBeFetched))
|
server := testserver.New(t, nil, testserver.WithActionsToken(tokenThatShouldNotBeFetched))
|
||||||
|
|||||||
@@ -186,25 +186,25 @@ func (_m *MockActionsService) GetAcquirableJobs(ctx context.Context, runnerScale
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMessage provides a mock function with given fields: ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId
|
// GetMessage provides a mock function with given fields: ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity
|
||||||
func (_m *MockActionsService) GetMessage(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, lastMessageId int64) (*RunnerScaleSetMessage, error) {
|
func (_m *MockActionsService) GetMessage(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error) {
|
||||||
ret := _m.Called(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId)
|
ret := _m.Called(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity)
|
||||||
|
|
||||||
var r0 *RunnerScaleSetMessage
|
var r0 *RunnerScaleSetMessage
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (*RunnerScaleSetMessage, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64, int) (*RunnerScaleSetMessage, error)); ok {
|
||||||
return rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId)
|
return rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) *RunnerScaleSetMessage); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64, int) *RunnerScaleSetMessage); ok {
|
||||||
r0 = rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId)
|
r0 = rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*RunnerScaleSetMessage)
|
r0 = ret.Get(0).(*RunnerScaleSetMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64) error); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64, int) error); ok {
|
||||||
r1 = rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId)
|
r1 = rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,25 +67,25 @@ func (_m *MockSessionService) DeleteMessage(ctx context.Context, messageId int64
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMessage provides a mock function with given fields: ctx, lastMessageId
|
// GetMessage provides a mock function with given fields: ctx, lastMessageId, maxCapacity
|
||||||
func (_m *MockSessionService) GetMessage(ctx context.Context, lastMessageId int64) (*RunnerScaleSetMessage, error) {
|
func (_m *MockSessionService) GetMessage(ctx context.Context, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error) {
|
||||||
ret := _m.Called(ctx, lastMessageId)
|
ret := _m.Called(ctx, lastMessageId, maxCapacity)
|
||||||
|
|
||||||
var r0 *RunnerScaleSetMessage
|
var r0 *RunnerScaleSetMessage
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, int64) (*RunnerScaleSetMessage, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, int64, int) (*RunnerScaleSetMessage, error)); ok {
|
||||||
return rf(ctx, lastMessageId)
|
return rf(ctx, lastMessageId, maxCapacity)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, int64) *RunnerScaleSetMessage); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, int64, int) *RunnerScaleSetMessage); ok {
|
||||||
r0 = rf(ctx, lastMessageId)
|
r0 = rf(ctx, lastMessageId, maxCapacity)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*RunnerScaleSetMessage)
|
r0 = ret.Get(0).(*RunnerScaleSetMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, int64, int) error); ok {
|
||||||
r1 = rf(ctx, lastMessageId)
|
r1 = rf(ctx, lastMessageId, maxCapacity)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
//go:generate mockery --inpackage --name=SessionService
|
//go:generate mockery --inpackage --name=SessionService
|
||||||
type SessionService interface {
|
type SessionService interface {
|
||||||
GetMessage(ctx context.Context, lastMessageId int64) (*RunnerScaleSetMessage, error)
|
GetMessage(ctx context.Context, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error)
|
||||||
DeleteMessage(ctx context.Context, messageId int64) error
|
DeleteMessage(ctx context.Context, messageId int64) error
|
||||||
AcquireJobs(ctx context.Context, requestIds []int64) ([]int64, error)
|
AcquireJobs(ctx context.Context, requestIds []int64) ([]int64, error)
|
||||||
io.Closer
|
io.Closer
|
||||||
|
|||||||
36
go.mod
36
go.mod
@@ -1,33 +1,33 @@
|
|||||||
module github.com/actions/actions-runner-controller
|
module github.com/actions/actions-runner-controller
|
||||||
|
|
||||||
go 1.21.3
|
go 1.22.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bradleyfalzon/ghinstallation/v2 v2.8.0
|
github.com/bradleyfalzon/ghinstallation/v2 v2.8.0
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/evanphx/json-patch v5.7.0+incompatible
|
github.com/evanphx/json-patch v5.9.0+incompatible
|
||||||
github.com/go-logr/logr v1.3.0
|
github.com/go-logr/logr v1.4.1
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/google/go-github/v52 v52.0.0
|
github.com/google/go-github/v52 v52.0.0
|
||||||
github.com/google/uuid v1.4.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
||||||
github.com/gruntwork-io/terratest v0.46.7
|
github.com/gruntwork-io/terratest v0.46.7
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5
|
github.com/hashicorp/go-retryablehttp v0.7.5
|
||||||
github.com/kelseyhightower/envconfig v1.4.0
|
github.com/kelseyhightower/envconfig v1.4.0
|
||||||
github.com/onsi/ginkgo v1.16.5
|
github.com/onsi/ginkgo v1.16.5
|
||||||
github.com/onsi/ginkgo/v2 v2.13.1
|
github.com/onsi/ginkgo/v2 v2.17.1
|
||||||
github.com/onsi/gomega v1.30.0
|
github.com/onsi/gomega v1.33.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.17.0
|
github.com/prometheus/client_golang v1.17.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/teambition/rrule-go v1.8.2
|
github.com/teambition/rrule-go v1.8.2
|
||||||
go.uber.org/multierr v1.11.0
|
go.uber.org/multierr v1.11.0
|
||||||
go.uber.org/zap v1.26.0
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/net v0.19.0
|
golang.org/x/net v0.24.0
|
||||||
golang.org/x/oauth2 v0.15.0
|
golang.org/x/oauth2 v0.19.0
|
||||||
golang.org/x/sync v0.5.0
|
golang.org/x/sync v0.7.0
|
||||||
gomodules.xyz/jsonpatch/v2 v2.4.0
|
gomodules.xyz/jsonpatch/v2 v2.4.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
k8s.io/api v0.28.4
|
k8s.io/api v0.28.4
|
||||||
@@ -43,7 +43,7 @@ require (
|
|||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1 // indirect
|
github.com/boombuler/barcode v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/cloudflare/circl v1.3.6 // indirect
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
|
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
|
||||||
@@ -87,17 +87,17 @@ require (
|
|||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/stretchr/objx v0.5.1 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/urfave/cli v1.22.2 // indirect
|
github.com/urfave/cli v1.22.2 // indirect
|
||||||
golang.org/x/crypto v0.16.0 // indirect
|
golang.org/x/crypto v0.22.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
||||||
golang.org/x/sys v0.15.0 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
golang.org/x/term v0.15.0 // indirect
|
golang.org/x/term v0.19.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/time v0.4.0 // indirect
|
golang.org/x/time v0.4.0 // indirect
|
||||||
golang.org/x/tools v0.15.0 // indirect
|
golang.org/x/tools v0.17.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/apiextensions-apiserver v0.28.3 // indirect
|
k8s.io/apiextensions-apiserver v0.28.3 // indirect
|
||||||
|
|||||||
74
go.sum
74
go.sum
@@ -19,8 +19,8 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
|
|||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
@@ -30,8 +30,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=
|
github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls=
|
||||||
github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=
|
github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=
|
||||||
github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
@@ -44,8 +44,9 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
|||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 h1:skJKxRtNmevLqnayafdLe2AsenqRupVmzZSqrvb5caU=
|
github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 h1:skJKxRtNmevLqnayafdLe2AsenqRupVmzZSqrvb5caU=
|
||||||
github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
|
||||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||||
|
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
||||||
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
||||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||||
@@ -99,8 +100,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
|||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
@@ -168,12 +169,12 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
|||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU=
|
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
|
||||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@@ -201,8 +202,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
@@ -211,9 +212,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/teambition/rrule-go v1.8.2 h1:lIjpjvWTj9fFUZCmuoVDrKVOtdiyzbzc93qTmRVe/J8=
|
github.com/teambition/rrule-go v1.8.2 h1:lIjpjvWTj9fFUZCmuoVDrKVOtdiyzbzc93qTmRVe/J8=
|
||||||
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
|
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
|
||||||
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
|
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
|
||||||
@@ -221,28 +221,26 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
|
|||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
|
||||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -255,18 +253,18 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
|
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||||
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
|
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -288,15 +286,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
@@ -315,8 +313,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
|||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||||
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -333,8 +331,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
|||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
14
main.go
14
main.go
@@ -239,6 +239,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if autoScalingRunnerSetOnly {
|
if autoScalingRunnerSetOnly {
|
||||||
|
if err := actionsgithubcom.SetupIndexers(mgr); err != nil {
|
||||||
|
log.Error(err, "unable to setup indexers")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
managerImage := os.Getenv("CONTROLLER_MANAGER_CONTAINER_IMAGE")
|
managerImage := os.Getenv("CONTROLLER_MANAGER_CONTAINER_IMAGE")
|
||||||
if managerImage == "" {
|
if managerImage == "" {
|
||||||
log.Error(err, "unable to obtain listener image")
|
log.Error(err, "unable to obtain listener image")
|
||||||
@@ -256,7 +260,7 @@ func main() {
|
|||||||
|
|
||||||
if err = (&actionsgithubcom.AutoscalingRunnerSetReconciler{
|
if err = (&actionsgithubcom.AutoscalingRunnerSetReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Log: log.WithName("AutoscalingRunnerSet"),
|
Log: log.WithName("AutoscalingRunnerSet").WithValues("version", build.Version),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
ControllerNamespace: managerNamespace,
|
ControllerNamespace: managerNamespace,
|
||||||
DefaultRunnerScaleSetListenerImage: managerImage,
|
DefaultRunnerScaleSetListenerImage: managerImage,
|
||||||
@@ -270,7 +274,7 @@ func main() {
|
|||||||
|
|
||||||
if err = (&actionsgithubcom.EphemeralRunnerReconciler{
|
if err = (&actionsgithubcom.EphemeralRunnerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Log: log.WithName("EphemeralRunner"),
|
Log: log.WithName("EphemeralRunner").WithValues("version", build.Version),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
ActionsClient: actionsMultiClient,
|
ActionsClient: actionsMultiClient,
|
||||||
}).SetupWithManager(mgr); err != nil {
|
}).SetupWithManager(mgr); err != nil {
|
||||||
@@ -280,7 +284,7 @@ func main() {
|
|||||||
|
|
||||||
if err = (&actionsgithubcom.EphemeralRunnerSetReconciler{
|
if err = (&actionsgithubcom.EphemeralRunnerSetReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Log: log.WithName("EphemeralRunnerSet"),
|
Log: log.WithName("EphemeralRunnerSet").WithValues("version", build.Version),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
ActionsClient: actionsMultiClient,
|
ActionsClient: actionsMultiClient,
|
||||||
PublishMetrics: metricsAddr != "0",
|
PublishMetrics: metricsAddr != "0",
|
||||||
@@ -291,7 +295,7 @@ func main() {
|
|||||||
|
|
||||||
if err = (&actionsgithubcom.AutoscalingListenerReconciler{
|
if err = (&actionsgithubcom.AutoscalingListenerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Log: log.WithName("AutoscalingListener"),
|
Log: log.WithName("AutoscalingListener").WithValues("version", build.Version),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
ListenerMetricsAddr: listenerMetricsAddr,
|
ListenerMetricsAddr: listenerMetricsAddr,
|
||||||
ListenerMetricsEndpoint: listenerMetricsEndpoint,
|
ListenerMetricsEndpoint: listenerMetricsEndpoint,
|
||||||
@@ -441,7 +445,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("starting manager")
|
log.Info("starting manager", "version", build.Version)
|
||||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||||
log.Error(err, "problem running manager")
|
log.Error(err, "problem running manager")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ DIND_ROOTLESS_RUNNER_NAME ?= ${DOCKER_USER}/actions-runner-dind-rootless
|
|||||||
OS_IMAGE ?= ubuntu-22.04
|
OS_IMAGE ?= ubuntu-22.04
|
||||||
TARGETPLATFORM ?= $(shell arch)
|
TARGETPLATFORM ?= $(shell arch)
|
||||||
|
|
||||||
RUNNER_VERSION ?= 2.312.0
|
RUNNER_VERSION ?= 2.316.1
|
||||||
RUNNER_CONTAINER_HOOKS_VERSION ?= 0.5.0
|
RUNNER_CONTAINER_HOOKS_VERSION ?= 0.6.0
|
||||||
DOCKER_VERSION ?= 24.0.7
|
DOCKER_VERSION ?= 24.0.7
|
||||||
|
|
||||||
# default list of platforms for which multiarch image is built
|
# default list of platforms for which multiarch image is built
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
RUNNER_VERSION=2.312.0
|
RUNNER_VERSION=2.316.1
|
||||||
RUNNER_CONTAINER_HOOKS_VERSION=0.5.0
|
RUNNER_CONTAINER_HOOKS_VERSION=0.6.0
|
||||||
@@ -36,8 +36,8 @@ var (
|
|||||||
|
|
||||||
testResultCMNamePrefix = "test-result-"
|
testResultCMNamePrefix = "test-result-"
|
||||||
|
|
||||||
RunnerVersion = "2.312.0"
|
RunnerVersion = "2.316.1"
|
||||||
RunnerContainerHooksVersion = "0.5.0"
|
RunnerContainerHooksVersion = "0.6.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// If you're willing to run this test via VS Code "run test" or "debug test",
|
// If you're willing to run this test via VS Code "run test" or "debug test",
|
||||||
@@ -93,7 +93,7 @@ func TestE2E(t *testing.T) {
|
|||||||
os.Getenv("UBUNTU_VERSION"),
|
os.Getenv("UBUNTU_VERSION"),
|
||||||
)
|
)
|
||||||
|
|
||||||
var testedVersions = []struct {
|
testedVersions := []struct {
|
||||||
label string
|
label string
|
||||||
controller, controllerVer string
|
controller, controllerVer string
|
||||||
chart, chartVer string
|
chart, chartVer string
|
||||||
@@ -154,9 +154,7 @@ func TestE2E(t *testing.T) {
|
|||||||
t.Skip("RunnerSets test has been skipped due to ARC_E2E_SKIP_RUNNERSETS")
|
t.Skip("RunnerSets test has been skipped due to ARC_E2E_SKIP_RUNNERSETS")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var testID string
|
||||||
testID string
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run("get or generate test ID", func(t *testing.T) {
|
t.Run("get or generate test ID", func(t *testing.T) {
|
||||||
testID = env.GetOrGenerateTestID(t)
|
testID = env.GetOrGenerateTestID(t)
|
||||||
@@ -268,9 +266,7 @@ func TestE2E(t *testing.T) {
|
|||||||
t.Skip("RunnerSets test has been skipped due to ARC_E2E_SKIP_RUNNERSETS")
|
t.Skip("RunnerSets test has been skipped due to ARC_E2E_SKIP_RUNNERSETS")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var testID string
|
||||||
testID string
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run("get or generate test ID", func(t *testing.T) {
|
t.Run("get or generate test ID", func(t *testing.T) {
|
||||||
testID = env.GetOrGenerateTestID(t)
|
testID = env.GetOrGenerateTestID(t)
|
||||||
@@ -1110,7 +1106,7 @@ func installActionsWorkflow(t *testing.T, testName, runnerLabel, testResultCMNam
|
|||||||
testing.Step{
|
testing.Step{
|
||||||
Uses: "actions/setup-go@v3",
|
Uses: "actions/setup-go@v3",
|
||||||
With: &testing.With{
|
With: &testing.With{
|
||||||
GoVersion: "1.21.3",
|
GoVersion: "1.22.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -1240,7 +1236,7 @@ func installActionsWorkflow(t *testing.T, testName, runnerLabel, testResultCMNam
|
|||||||
testing.Step{
|
testing.Step{
|
||||||
Uses: "azure/setup-kubectl@v1",
|
Uses: "azure/setup-kubectl@v1",
|
||||||
With: &testing.With{
|
With: &testing.With{
|
||||||
Version: "v1.21.3",
|
Version: "v1.22.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
testing.Step{
|
testing.Step{
|
||||||
|
|||||||
Reference in New Issue
Block a user