mirror of
https://github.com/actions/actions-runner-controller.git
synced 2025-12-10 11:41:27 +00:00
Compare commits
4 Commits
d0c3d38903
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82d5579696 | ||
|
|
540269880f | ||
|
|
9ebb97fe2e | ||
|
|
75c401f6c1 |
215
.github/actions/execute-assert-arc-e2e/action.yaml
vendored
215
.github/actions/execute-assert-arc-e2e/action.yaml
vendored
@@ -1,215 +0,0 @@
|
||||
name: 'Execute and Assert ARC E2E Test Action'
|
||||
description: 'Queue E2E test workflow and assert workflow run result to be succeed'
|
||||
|
||||
inputs:
|
||||
auth-token:
|
||||
description: 'GitHub access token to queue workflow run'
|
||||
required: true
|
||||
repo-owner:
|
||||
description: "The repository owner name that has the test workflow file, ex: actions"
|
||||
required: true
|
||||
repo-name:
|
||||
description: "The repository name that has the test workflow file, ex: test"
|
||||
required: true
|
||||
workflow-file:
|
||||
description: 'The file name of the workflow yaml, ex: test.yml'
|
||||
required: true
|
||||
arc-name:
|
||||
description: 'The name of the configured gha-runner-scale-set'
|
||||
required: true
|
||||
arc-namespace:
|
||||
description: 'The namespace of the configured gha-runner-scale-set'
|
||||
required: true
|
||||
arc-controller-namespace:
|
||||
description: 'The namespace of the configured gha-runner-scale-set-controller'
|
||||
required: true
|
||||
wait-to-finish:
|
||||
description: 'Wait for the workflow run to finish'
|
||||
required: true
|
||||
default: "true"
|
||||
wait-to-running:
|
||||
description: 'Wait for the workflow run to start running'
|
||||
required: true
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Queue test workflow
|
||||
shell: bash
|
||||
id: queue_workflow
|
||||
run: |
|
||||
queue_time=`date +%FT%TZ`
|
||||
echo "queue_time=$queue_time" >> $GITHUB_OUTPUT
|
||||
curl -X POST https://api.github.com/repos/${{inputs.repo-owner}}/${{inputs.repo-name}}/actions/workflows/${{inputs.workflow-file}}/dispatches \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token ${{inputs.auth-token}}" \
|
||||
-d '{"ref": "main", "inputs": { "arc_name": "${{inputs.arc-name}}" } }'
|
||||
|
||||
- name: Fetch workflow run & job ids
|
||||
uses: actions/github-script@v7
|
||||
id: query_workflow
|
||||
with:
|
||||
script: |
|
||||
// Try to find the workflow run triggered by the previous step using the workflow_dispatch event.
|
||||
// - Find recently create workflow runs in the test repository
|
||||
// - For each workflow run, list its workflow job and see if the job's labels contain `inputs.arc-name`
|
||||
// - Since the inputs.arc-name should be unique per e2e workflow run, once we find the job with the label, we find the workflow that we just triggered.
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
const owner = '${{inputs.repo-owner}}'
|
||||
const repo = '${{inputs.repo-name}}'
|
||||
const workflow_id = '${{inputs.workflow-file}}'
|
||||
let workflow_run_id = 0
|
||||
let workflow_job_id = 0
|
||||
let workflow_run_html_url = ""
|
||||
let count = 0
|
||||
while (count++<12) {
|
||||
await sleep(10 * 1000);
|
||||
let listRunResponse = await github.rest.actions.listWorkflowRuns({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
workflow_id: workflow_id,
|
||||
created: '>${{steps.queue_workflow.outputs.queue_time}}'
|
||||
})
|
||||
if (listRunResponse.data.total_count > 0) {
|
||||
console.log(`Found some new workflow runs for ${workflow_id}`)
|
||||
for (let i = 0; i<listRunResponse.data.total_count; i++) {
|
||||
let workflowRun = listRunResponse.data.workflow_runs[i]
|
||||
console.log(`Check if workflow run ${workflowRun.id} is triggered by us.`)
|
||||
let listJobResponse = await github.rest.actions.listJobsForWorkflowRun({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
run_id: workflowRun.id
|
||||
})
|
||||
console.log(`Workflow run ${workflowRun.id} has ${listJobResponse.data.total_count} jobs.`)
|
||||
if (listJobResponse.data.total_count > 0) {
|
||||
for (let j = 0; j<listJobResponse.data.total_count; j++) {
|
||||
let workflowJob = listJobResponse.data.jobs[j]
|
||||
console.log(`Check if workflow job ${workflowJob.id} is triggered by us.`)
|
||||
console.log(JSON.stringify(workflowJob.labels));
|
||||
if (workflowJob.labels.includes('${{inputs.arc-name}}')) {
|
||||
console.log(`Workflow job ${workflowJob.id} (Run id: ${workflowJob.run_id}) is triggered by us.`)
|
||||
workflow_run_id = workflowJob.run_id
|
||||
workflow_job_id = workflowJob.id
|
||||
workflow_run_html_url = workflowRun.html_url
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (workflow_job_id > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (workflow_job_id > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (workflow_job_id == 0) {
|
||||
core.setFailed(`Can't find workflow run and workflow job triggered to 'runs-on ${{inputs.arc-name}}'`)
|
||||
} else {
|
||||
core.setOutput('workflow_run', workflow_run_id);
|
||||
core.setOutput('workflow_job', workflow_job_id);
|
||||
core.setOutput('workflow_run_url', workflow_run_html_url);
|
||||
}
|
||||
|
||||
- name: Generate summary about the triggered workflow run
|
||||
shell: bash
|
||||
run: |
|
||||
cat <<-EOF > $GITHUB_STEP_SUMMARY
|
||||
| **Triggered workflow run** |
|
||||
|:--------------------------:|
|
||||
| ${{steps.query_workflow.outputs.workflow_run_url}} |
|
||||
EOF
|
||||
|
||||
- name: Wait for workflow to start running
|
||||
if: inputs.wait-to-running == 'true' && inputs.wait-to-finish == 'false'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
const owner = '${{inputs.repo-owner}}'
|
||||
const repo = '${{inputs.repo-name}}'
|
||||
const workflow_run_id = ${{steps.query_workflow.outputs.workflow_run}}
|
||||
const workflow_job_id = ${{steps.query_workflow.outputs.workflow_job}}
|
||||
let count = 0
|
||||
while (count++<10) {
|
||||
await sleep(30 * 1000);
|
||||
let getRunResponse = await github.rest.actions.getWorkflowRun({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
run_id: workflow_run_id
|
||||
})
|
||||
console.log(`${getRunResponse.data.html_url}: ${getRunResponse.data.status} (${getRunResponse.data.conclusion})`);
|
||||
if (getRunResponse.data.status == 'in_progress') {
|
||||
console.log(`Workflow run is in progress.`)
|
||||
return
|
||||
}
|
||||
}
|
||||
core.setFailed(`The triggered workflow run didn't start properly using ${{inputs.arc-name}}`)
|
||||
|
||||
- name: Wait for workflow to finish successfully
|
||||
if: inputs.wait-to-finish == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// Wait 5 minutes and make sure the workflow run we triggered completed with result 'success'
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
const owner = '${{inputs.repo-owner}}'
|
||||
const repo = '${{inputs.repo-name}}'
|
||||
const workflow_run_id = ${{steps.query_workflow.outputs.workflow_run}}
|
||||
const workflow_job_id = ${{steps.query_workflow.outputs.workflow_job}}
|
||||
let count = 0
|
||||
while (count++<10) {
|
||||
await sleep(30 * 1000);
|
||||
let getRunResponse = await github.rest.actions.getWorkflowRun({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
run_id: workflow_run_id
|
||||
})
|
||||
console.log(`${getRunResponse.data.html_url}: ${getRunResponse.data.status} (${getRunResponse.data.conclusion})`);
|
||||
if (getRunResponse.data.status == 'completed') {
|
||||
if ( getRunResponse.data.conclusion == 'success') {
|
||||
console.log(`Workflow run finished properly.`)
|
||||
return
|
||||
} else {
|
||||
core.setFailed(`The triggered workflow run finish with result ${getRunResponse.data.conclusion}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
core.setFailed(`The triggered workflow run didn't finish properly using ${{inputs.arc-name}}`)
|
||||
|
||||
- name: Gather listener logs
|
||||
shell: bash
|
||||
if: always()
|
||||
run: |
|
||||
LISTENER_POD="$(kubectl get autoscalinglisteners.actions.github.com -n arc-systems -o jsonpath='{.items[*].metadata.name}')"
|
||||
kubectl logs $LISTENER_POD -n ${{inputs.arc-controller-namespace}}
|
||||
|
||||
- name: Gather coredns logs
|
||||
shell: bash
|
||||
if: always()
|
||||
run: |
|
||||
kubectl logs deployments/coredns -n kube-system
|
||||
|
||||
- name: cleanup
|
||||
if: inputs.wait-to-finish == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
helm uninstall ${{ inputs.arc-name }} --namespace ${{inputs.arc-namespace}} --debug
|
||||
kubectl wait --timeout=30s --for=delete AutoScalingRunnerSet -n ${{inputs.arc-namespace}} -l app.kubernetes.io/instance=${{ inputs.arc-name }}
|
||||
|
||||
- name: Gather controller logs
|
||||
shell: bash
|
||||
if: always()
|
||||
run: |
|
||||
kubectl logs deployment/arc-gha-rs-controller -n ${{inputs.arc-controller-namespace}}
|
||||
65
.github/actions/setup-arc-e2e/action.yaml
vendored
65
.github/actions/setup-arc-e2e/action.yaml
vendored
@@ -1,65 +0,0 @@
|
||||
name: "Setup ARC E2E Test Action"
|
||||
description: "Build controller image, create kind cluster, load the image, and exchange ARC configure token."
|
||||
|
||||
inputs:
|
||||
app-id:
|
||||
description: "GitHub App Id for exchange access token"
|
||||
required: true
|
||||
app-pk:
|
||||
description: "GitHub App private key for exchange access token"
|
||||
required: true
|
||||
image-name:
|
||||
description: "Local docker image name for building"
|
||||
required: true
|
||||
image-tag:
|
||||
description: "Tag of ARC Docker image for building"
|
||||
required: true
|
||||
target-org:
|
||||
description: "The test organization for ARC e2e test"
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
token:
|
||||
description: "Token to use for configure ARC"
|
||||
value: ${{steps.config-token.outputs.token}}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2
|
||||
with:
|
||||
# Pinning v0.9.1 for Buildx and BuildKit v0.10.6
|
||||
# BuildKit v0.11 which has a bug causing intermittent
|
||||
# failures pushing images to GHCR
|
||||
version: v0.9.1
|
||||
driver-opts: image=moby/buildkit:v0.10.6
|
||||
|
||||
- name: Build controller image
|
||||
# https://github.com/docker/build-push-action/releases/tag/v6.18.0
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
|
||||
with:
|
||||
file: Dockerfile
|
||||
platforms: linux/amd64
|
||||
load: true
|
||||
build-args: |
|
||||
DOCKER_IMAGE_NAME=${{inputs.image-name}}
|
||||
VERSION=${{inputs.image-tag}}
|
||||
tags: |
|
||||
${{inputs.image-name}}:${{inputs.image-tag}}
|
||||
no-cache: true
|
||||
|
||||
- name: Create minikube cluster and load image
|
||||
shell: bash
|
||||
run: |
|
||||
minikube start
|
||||
minikube image load ${{inputs.image-name}}:${{inputs.image-tag}}
|
||||
|
||||
- name: Get configure token
|
||||
id: config-token
|
||||
# https://github.com/peter-murray/workflow-application-token-action/releases/tag/v3.0.0
|
||||
uses: peter-murray/workflow-application-token-action@dc0413987a085fa17d19df9e47d4677cf81ffef3
|
||||
with:
|
||||
application_id: ${{ inputs.app-id }}
|
||||
application_private_key: ${{ inputs.app-pk }}
|
||||
organization: ${{ inputs.target-org}}
|
||||
@@ -1,51 +0,0 @@
|
||||
name: "Setup Docker"
|
||||
|
||||
inputs:
|
||||
username:
|
||||
description: "Username"
|
||||
required: true
|
||||
password:
|
||||
description: "Password"
|
||||
required: true
|
||||
ghcr_username:
|
||||
description: "GHCR username. Usually set from the github.actor variable"
|
||||
required: true
|
||||
ghcr_password:
|
||||
description: "GHCR password. Usually set from the secrets.GITHUB_TOKEN variable"
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Get Short SHA
|
||||
id: vars
|
||||
run: |
|
||||
echo "sha_short=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Set up QEMU
|
||||
# https://github.com/docker/setup-qemu-action/releases/tag/v3.6.0
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
# https://github.com/docker/setup-buildx-action/releases/tag/v3.10.0
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: ${{ github.event_name == 'release' || github.event_name == 'push' && github.ref == 'refs/heads/master' && inputs.password != '' }}
|
||||
# https://github.com/docker/login-action/releases/tag/v3.4.0
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772
|
||||
with:
|
||||
username: ${{ inputs.username }}
|
||||
password: ${{ inputs.password }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: ${{ github.event_name == 'release' || github.event_name == 'push' && github.ref == 'refs/heads/master' && inputs.ghcr_password != '' }}
|
||||
# https://github.com/docker/login-action/releases/tag/v3.4.0
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ inputs.ghcr_username }}
|
||||
password: ${{ inputs.ghcr_password }}
|
||||
6
.github/workflows/arc-publish-chart.yaml
vendored
6
.github/workflows/arc-publish-chart.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
publish-chart: ${{ steps.publish-chart-step.outputs.publish }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -184,7 +184,7 @@ jobs:
|
||||
# this workaround is intended to move the index.yaml to the target repo
|
||||
# where the github pages are hosted
|
||||
- name: Checkout target repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.CHART_TARGET_ORG }}/${{ env.CHART_TARGET_REPO }}
|
||||
path: ${{ env.CHART_TARGET_REPO }}
|
||||
|
||||
2
.github/workflows/arc-publish.yaml
vendored
2
.github/workflows/arc-publish.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
if: ${{ !startsWith(github.event.inputs.release_tag_name, 'gha-runner-scale-set-') }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
|
||||
2
.github/workflows/arc-release-runners.yaml
vendored
2
.github/workflows/arc-release-runners.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
name: Trigger Build and Push of Runner Images
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get runner version
|
||||
id: versions
|
||||
run: |
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
container_hooks_current_version: ${{ steps.container_hooks_versions.outputs.container_hooks_current_version }}
|
||||
container_hooks_latest_version: ${{ steps.container_hooks_versions.outputs.container_hooks_latest_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Get runner current and latest versions
|
||||
id: runner_versions
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
echo "CONTAINER_HOOKS_CURRENT_VERSION=${{ needs.check_versions.outputs.container_hooks_current_version }}"
|
||||
echo "CONTAINER_HOOKS_LATEST_VERSION=${{ needs.check_versions.outputs.container_hooks_latest_version }}"
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: PR Name
|
||||
id: pr_name
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
PR_NAME: ${{ needs.check_pr.outputs.pr_name }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: New branch
|
||||
run: git checkout -b update-runner-"$(date +%Y-%m-%d)"
|
||||
|
||||
2
.github/workflows/arc-validate-chart.yaml
vendored
2
.github/workflows/arc-validate-chart.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
4
.github/workflows/arc-validate-runners.yaml
vendored
4
.github/workflows/arc-validate-runners.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
name: runner / shellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: "Run shellcheck"
|
||||
run: make shellcheck
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
|
||||
1020
.github/workflows/gha-e2e-tests.yaml
vendored
1020
.github/workflows/gha-e2e-tests.yaml
vendored
File diff suppressed because it is too large
Load Diff
6
.github/workflows/gha-publish-chart.yaml
vendored
6
.github/workflows/gha-publish-chart.yaml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# If inputs.ref is empty, it'll resolve to the default branch
|
||||
ref: ${{ inputs.ref }}
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# If inputs.ref is empty, it'll resolve to the default branch
|
||||
ref: ${{ inputs.ref }}
|
||||
@@ -166,7 +166,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# If inputs.ref is empty, it'll resolve to the default branch
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
4
.github/workflows/gha-validate-chart.yaml
vendored
4
.github/workflows/gha-validate-chart.yaml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
4
.github/workflows/global-publish-canary.yaml
vendored
4
.github/workflows/global-publish-canary.yaml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
TARGET_REPO: actions-runner-controller
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get Token
|
||||
id: get_workflow_token
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef
|
||||
|
||||
2
.github/workflows/global-run-codeql.yaml
vendored
2
.github/workflows/global-run-codeql.yaml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
check_for_first_interaction:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/first-interaction@v3
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
10
.github/workflows/go.yaml
vendored
10
.github/workflows/go.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
@@ -42,13 +42,13 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
cache: false
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@0a35821d5c230e903fcfe077583637dea1b27b47
|
||||
uses: golangci/golangci-lint-action@e7fa5ac41e1cf5b7d48e45e42232ce7ada589601
|
||||
with:
|
||||
only-new-issues: true
|
||||
version: v2.5.0
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
generate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
@@ -211,7 +211,14 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl.
|
||||
// TODO: make sure the role binding has the up-to-date role and service account
|
||||
|
||||
listenerPod := new(corev1.Pod)
|
||||
if err := r.Get(ctx, client.ObjectKey{Namespace: autoscalingListener.Namespace, Name: autoscalingListener.Name}, listenerPod); err != nil {
|
||||
if err := r.Get(
|
||||
ctx,
|
||||
client.ObjectKey{
|
||||
Namespace: autoscalingListener.Namespace,
|
||||
Name: autoscalingListener.Name,
|
||||
},
|
||||
listenerPod,
|
||||
); err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
log.Error(err, "Unable to get listener pod", "namespace", autoscalingListener.Namespace, "name", autoscalingListener.Name)
|
||||
return ctrl.Result{}, err
|
||||
@@ -229,37 +236,30 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl.
|
||||
|
||||
cs := listenerContainerStatus(listenerPod)
|
||||
switch {
|
||||
case listenerPod.Status.Reason == "Evicted":
|
||||
log.Info(
|
||||
"Listener pod is evicted",
|
||||
"phase", listenerPod.Status.Phase,
|
||||
"reason", listenerPod.Status.Reason,
|
||||
"message", listenerPod.Status.Message,
|
||||
)
|
||||
|
||||
return ctrl.Result{}, r.deleteListenerPod(ctx, autoscalingListener, listenerPod, log)
|
||||
|
||||
case cs == nil:
|
||||
log.Info("Listener pod is not ready", "namespace", listenerPod.Namespace, "name", listenerPod.Name)
|
||||
return ctrl.Result{}, nil
|
||||
case cs.State.Terminated != nil:
|
||||
log.Info("Listener pod is terminated", "namespace", listenerPod.Namespace, "name", listenerPod.Name, "reason", cs.State.Terminated.Reason, "message", cs.State.Terminated.Message)
|
||||
log.Info(
|
||||
"Listener pod is terminated",
|
||||
"namespace", listenerPod.Namespace,
|
||||
"name", listenerPod.Name,
|
||||
"reason", cs.State.Terminated.Reason,
|
||||
"message", cs.State.Terminated.Message,
|
||||
)
|
||||
|
||||
if err := r.publishRunningListener(autoscalingListener, false); err != nil {
|
||||
log.Error(err, "Unable to publish runner listener down metric", "namespace", listenerPod.Namespace, "name", listenerPod.Name)
|
||||
}
|
||||
return ctrl.Result{}, r.deleteListenerPod(ctx, autoscalingListener, listenerPod, log)
|
||||
|
||||
if listenerPod.DeletionTimestamp.IsZero() {
|
||||
log.Info("Deleting the listener pod", "namespace", listenerPod.Namespace, "name", listenerPod.Name)
|
||||
if err := r.Delete(ctx, listenerPod); err != nil && !kerrors.IsNotFound(err) {
|
||||
log.Error(err, "Unable to delete the listener pod", "namespace", listenerPod.Namespace, "name", listenerPod.Name)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// delete the listener config secret as well, so it gets recreated when the listener pod is recreated, with any new data if it exists
|
||||
var configSecret corev1.Secret
|
||||
err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: scaleSetListenerConfigName(autoscalingListener)}, &configSecret)
|
||||
switch {
|
||||
case err == nil && configSecret.DeletionTimestamp.IsZero():
|
||||
log.Info("Deleting the listener config secret")
|
||||
if err := r.Delete(ctx, &configSecret); err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("failed to delete listener config secret: %w", err)
|
||||
}
|
||||
case !kerrors.IsNotFound(err):
|
||||
return ctrl.Result{}, fmt.Errorf("failed to get the listener config secret: %w", err)
|
||||
}
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
case cs.State.Running != nil:
|
||||
if err := r.publishRunningListener(autoscalingListener, true); err != nil {
|
||||
log.Error(err, "Unable to publish running listener", "namespace", listenerPod.Namespace, "name", listenerPod.Name)
|
||||
@@ -269,10 +269,39 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl.
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *AutoscalingListenerReconciler) deleteListenerPod(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, listenerPod *corev1.Pod, log logr.Logger) error {
|
||||
if err := r.publishRunningListener(autoscalingListener, false); err != nil {
|
||||
log.Error(err, "Unable to publish runner listener down metric", "namespace", listenerPod.Namespace, "name", listenerPod.Name)
|
||||
}
|
||||
|
||||
if listenerPod.DeletionTimestamp.IsZero() {
|
||||
log.Info("Deleting the listener pod", "namespace", listenerPod.Namespace, "name", listenerPod.Name)
|
||||
if err := r.Delete(ctx, listenerPod); err != nil && !kerrors.IsNotFound(err) {
|
||||
log.Error(err, "Unable to delete the listener pod", "namespace", listenerPod.Namespace, "name", listenerPod.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
// delete the listener config secret as well, so it gets recreated when the listener pod is recreated, with any new data if it exists
|
||||
var configSecret corev1.Secret
|
||||
err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: scaleSetListenerConfigName(autoscalingListener)}, &configSecret)
|
||||
switch {
|
||||
case err == nil && configSecret.DeletionTimestamp.IsZero():
|
||||
log.Info("Deleting the listener config secret")
|
||||
if err := r.Delete(ctx, &configSecret); err != nil {
|
||||
return fmt.Errorf("failed to delete listener config secret: %w", err)
|
||||
}
|
||||
case !kerrors.IsNotFound(err):
|
||||
return fmt.Errorf("failed to get the listener config secret: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *AutoscalingListenerReconciler) cleanupResources(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (requeue bool, err error) {
|
||||
logger.Info("Cleaning up the listener pod")
|
||||
listenerPod := new(corev1.Pod)
|
||||
|
||||
@@ -671,6 +671,55 @@ var _ = Describe("Test AutoScalingListener customization", func() {
|
||||
autoscalingListenerTestInterval,
|
||||
).ShouldNot(BeEquivalentTo(oldPodUID), "Pod should be created")
|
||||
})
|
||||
|
||||
It("Should re-create pod when the listener pod is evicted", func() {
|
||||
pod := new(corev1.Pod)
|
||||
Eventually(
|
||||
func() (string, error) {
|
||||
err := k8sClient.Get(
|
||||
ctx,
|
||||
client.ObjectKey{
|
||||
Name: autoscalingListener.Name,
|
||||
Namespace: autoscalingListener.Namespace,
|
||||
},
|
||||
pod,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return pod.Name, nil
|
||||
},
|
||||
autoscalingListenerTestTimeout,
|
||||
autoscalingListenerTestInterval,
|
||||
).Should(
|
||||
BeEquivalentTo(autoscalingListener.Name),
|
||||
"Pod should be created",
|
||||
)
|
||||
|
||||
updated := pod.DeepCopy()
|
||||
oldPodUID := string(pod.UID)
|
||||
updated.Status.Reason = "Evicted"
|
||||
err := k8sClient.Status().Update(ctx, updated)
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to update pod status")
|
||||
|
||||
pod = new(corev1.Pod)
|
||||
Eventually(
|
||||
func() (string, error) {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingListener.Name, Namespace: autoscalingListener.Namespace}, pod)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(pod.UID), nil
|
||||
},
|
||||
autoscalingListenerTestTimeout,
|
||||
autoscalingListenerTestInterval,
|
||||
).ShouldNot(
|
||||
BeEquivalentTo(oldPodUID),
|
||||
"Pod should be created",
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -15,10 +15,12 @@ import (
|
||||
func TestGitHubConfig(t *testing.T) {
|
||||
t.Run("when given a valid URL", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
configURL string
|
||||
expected *actions.GitHubConfig
|
||||
}{
|
||||
{
|
||||
name: "repository URL",
|
||||
configURL: "https://github.com/org/repo",
|
||||
expected: &actions.GitHubConfig{
|
||||
Scope: actions.GitHubScopeRepository,
|
||||
@@ -29,6 +31,7 @@ func TestGitHubConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "repository URL with trailing slash",
|
||||
configURL: "https://github.com/org/repo/",
|
||||
expected: &actions.GitHubConfig{
|
||||
Scope: actions.GitHubScopeRepository,
|
||||
@@ -39,6 +42,7 @@ func TestGitHubConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "organization URL",
|
||||
configURL: "https://github.com/org",
|
||||
expected: &actions.GitHubConfig{
|
||||
Scope: actions.GitHubScopeOrganization,
|
||||
@@ -49,6 +53,7 @@ func TestGitHubConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enterprise URL",
|
||||
configURL: "https://github.com/enterprises/my-enterprise",
|
||||
expected: &actions.GitHubConfig{
|
||||
Scope: actions.GitHubScopeEnterprise,
|
||||
@@ -59,6 +64,7 @@ func TestGitHubConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enterprise URL with trailing slash",
|
||||
configURL: "https://github.com/enterprises/my-enterprise/",
|
||||
expected: &actions.GitHubConfig{
|
||||
Scope: actions.GitHubScopeEnterprise,
|
||||
@@ -69,6 +75,7 @@ func TestGitHubConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "organization URL with www",
|
||||
configURL: "https://www.github.com/org",
|
||||
expected: &actions.GitHubConfig{
|
||||
Scope: actions.GitHubScopeOrganization,
|
||||
@@ -79,6 +86,7 @@ func TestGitHubConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "organization URL with www and trailing slash",
|
||||
configURL: "https://www.github.com/org/",
|
||||
expected: &actions.GitHubConfig{
|
||||
Scope: actions.GitHubScopeOrganization,
|
||||
@@ -89,6 +97,7 @@ func TestGitHubConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "github local URL",
|
||||
configURL: "https://github.localhost/org",
|
||||
expected: &actions.GitHubConfig{
|
||||
Scope: actions.GitHubScopeOrganization,
|
||||
@@ -99,6 +108,7 @@ func TestGitHubConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "github local org URL",
|
||||
configURL: "https://my-ghes.com/org",
|
||||
expected: &actions.GitHubConfig{
|
||||
Scope: actions.GitHubScopeOrganization,
|
||||
@@ -109,6 +119,7 @@ func TestGitHubConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "github local URL with trailing slash",
|
||||
configURL: "https://my-ghes.com/org/",
|
||||
expected: &actions.GitHubConfig{
|
||||
Scope: actions.GitHubScopeOrganization,
|
||||
@@ -119,6 +130,7 @@ func TestGitHubConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "github local URL with ghe.com",
|
||||
configURL: "https://my-ghes.ghe.com/org/",
|
||||
expected: &actions.GitHubConfig{
|
||||
Scope: actions.GitHubScopeOrganization,
|
||||
@@ -131,7 +143,7 @@ func TestGitHubConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.configURL, func(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
parsedURL, err := url.Parse(strings.Trim(test.configURL, "/"))
|
||||
require.NoError(t, err)
|
||||
test.expected.ConfigURL = parsedURL
|
||||
|
||||
@@ -119,88 +119,84 @@ func TestGitHubAPIError(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func ParseActionsErrorFromResponse(t *testing.T) {
|
||||
func TestParseActionsErrorFromResponse(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"},
|
||||
},
|
||||
Header: http.Header{},
|
||||
StatusCode: 404,
|
||||
}
|
||||
response.Header.Add(actions.HeaderActionsActivityID, "activity-id")
|
||||
|
||||
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")
|
||||
assert.Equal(t, "activity-id", err.(*actions.ActionsError).ActivityID)
|
||||
assert.Equal(t, 404, err.(*actions.ActionsError).StatusCode)
|
||||
assert.Equal(t, "unknown exception", err.(*actions.ActionsError).Err.Error())
|
||||
})
|
||||
|
||||
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,
|
||||
Header: http.Header{},
|
||||
Body: io.NopCloser(strings.NewReader(errorMessage)),
|
||||
}
|
||||
response.Header.Add(actions.HeaderActionsActivityID, "activity-id")
|
||||
response.Header.Add("Content-Type", "text/plain")
|
||||
|
||||
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)
|
||||
require.ErrorAs(t, err, &actionsError)
|
||||
assert.Equal(t, "activity-id", actionsError.ActivityID)
|
||||
assert.Equal(t, 404, actionsError.StatusCode)
|
||||
assert.Equal(t, errorMessage, actionsError.Err.Error())
|
||||
})
|
||||
|
||||
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"},
|
||||
},
|
||||
Header: http.Header{},
|
||||
StatusCode: 404,
|
||||
Body: io.NopCloser(strings.NewReader(errorMessage)),
|
||||
}
|
||||
response.Header.Add(actions.HeaderActionsActivityID, "activity-id")
|
||||
response.Header.Add("Content-Type", "application/json")
|
||||
|
||||
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)
|
||||
require.ErrorAs(t, err, &actionsError)
|
||||
assert.Equal(t, "activity-id", actionsError.ActivityID)
|
||||
assert.Equal(t, 404, actionsError.StatusCode)
|
||||
|
||||
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")
|
||||
assert.Equal(t, "exception-name", inner.ExceptionName)
|
||||
assert.Equal(t, "example error message", inner.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"},
|
||||
},
|
||||
Header: http.Header{},
|
||||
StatusCode: 404,
|
||||
Body: io.NopCloser(strings.NewReader(errorMessage)),
|
||||
}
|
||||
response.Header.Add(actions.HeaderActionsActivityID, "activity-id")
|
||||
response.Header.Add("Content-Type", "application/json")
|
||||
|
||||
err := actions.ParseActionsErrorFromResponse(response)
|
||||
require.Error(t, err)
|
||||
|
||||
var actionsExceptionError *actions.ActionsExceptionError
|
||||
assert.ErrorAs(t, err, &actionsExceptionError)
|
||||
require.ErrorAs(t, err, &actionsExceptionError)
|
||||
|
||||
assert.Equal(t, actionsExceptionError.ExceptionName, "exception-name")
|
||||
assert.Equal(t, actionsExceptionError.Message, "example error message")
|
||||
assert.Equal(t, "exception-name", actionsExceptionError.ExceptionName)
|
||||
assert.Equal(t, "example error message", actionsExceptionError.Message)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user