Add Repo Radius deploy workflow #10653
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ------------------------------------------------------------ | |
| # Copyright 2023 The Radius Authors. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # ------------------------------------------------------------ | |
| # yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json | |
| --- | |
| name: Functional Tests (with Cloud Resources) | |
| on: | |
| # Enable manual trigger | |
| workflow_dispatch: | |
| inputs: | |
| branch: | |
| description: Branch to run the workflow on | |
| required: true | |
| default: main | |
| schedule: | |
| # Run three times a day at 6 AM PDT (1 PM UTC), 12 PM PDT (7 PM UTC), and 6 PM PDT (1 AM UTC next day) | |
| - cron: 0 1,13,19 * * * | |
| # Dispatch on external events | |
| repository_dispatch: | |
| types: [deployment-engine.run-functional-tests] | |
| # Expected client_payload: | |
| # src_image: Source image in ACR (e.g. radiusdeploymentengine.azurecr.io/deployment-engine) | |
| # dest_image: Destination image in GHCR (e.g. ghcr.io/radius-project/deployment-engine) | |
| # tag: Tag for the image (e.g. latest, 0.1, 0.1.0-rc1, pr-123) | |
| # pull_request_target runs in the context of the base branch, providing access to secrets. | |
| # For external contributors, the approval-gate job requires manual approval before tests run. | |
| # SECURITY: We use pull_request_target but do NOT run any code from the PR until after approval. | |
| pull_request_target: | |
| branches: | |
| - main | |
| - features/* | |
| - release/* | |
| # Revalidate the combined changes when a PR is queued in the merge queue. | |
| # Scoped to main only so the release branch flow is never affected. In the | |
| # merge queue the code already lives in the base repo (trusted context), so | |
| # the check-trust / approval-gate jobs are skipped and setup proceeds. | |
| merge_group: | |
| branches: | |
| - main | |
| permissions: {} | |
| # Reduce self-contention on the shared Azure ACI 'StandardCores' quota (the | |
| # bottleneck behind Test_ACI's ContainerGroupQuotaReached flakes): serialize cloud | |
| # functional-test runs per pull request so a single PR pushing rapid commits does | |
| # not run several ACI-provisioning runs at once. Keyed by PR number because under | |
| # pull_request_target github.ref is the base ref, which would otherwise collapse | |
| # every PR into one group and cancel unrelated runs. cancel-in-progress is limited | |
| # to pull_request_target so the merge queue and scheduled runs are never cancelled. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request_target' }} | |
| env: | |
| GOPROXY: https://proxy.golang.org | |
| # yq version | |
| YQ_VERSION: v4.44.3 | |
| YQ_LINUX_AMD64_SHA256: a2c097180dd884a8d50c956ee16a9cec070f30a7947cf4ebf87d5f36213e9ed7 | |
| # Helm version | |
| HELM_VER: v4.2.2 | |
| # KinD cluster version | |
| KIND_VER: v0.29.0 | |
| # Kubectl version | |
| KUBECTL_VER: v1.25.0 | |
| # Azure Keyvault CSI driver chart version | |
| AZURE_KEYVAULT_CSI_DRIVER_VER: 1.4.2 | |
| # Azure workload identity webhook chart version | |
| AZURE_WORKLOAD_IDENTITY_WEBHOOK_VER: 1.3.0 | |
| # Container registry for storing container images | |
| CONTAINER_REGISTRY: ${{ vars.FUNCTIONAL_TEST_CONTAINER_REGISTRY }} | |
| # Container registry for storing Bicep recipe artifacts | |
| BICEP_RECIPE_REGISTRY: ${{ vars.FUNCTIONAL_TEST_BICEP_RECIPE_REGISTRY }} | |
| # The radius functional test timeout | |
| FUNCTIONALTEST_TIMEOUT: 60m | |
| # The Azure Location to store test resources | |
| AZURE_LOCATION: ${{ vars.AZURE_LOCATION }} | |
| # The base directory for storing test logs | |
| RADIUS_CONTAINER_LOG_BASE: dist/container_logs | |
| # The Radius helm chart location. | |
| RADIUS_CHART_LOCATION: deploy/Chart/ | |
| # The region for AWS resources | |
| AWS_REGION: ${{ vars.AWS_REGION }} | |
| # The current GitHub action link | |
| ACTION_LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| # Server where terraform test modules are deployed | |
| TF_RECIPE_MODULE_SERVER_URL: http://tf-module-server.radius-test-tf-module-server.svc.cluster.local | |
| # Private Git repository where terraform module for testing is stored. | |
| TF_RECIPE_PRIVATE_GIT_SOURCE: git::https://github.com/radius-project/terraform-private-modules//kubernetes-redis | |
| # bicep-types ACR url for pulling latest Radius Bicep types (AWS) | |
| BICEP_TYPES_REGISTRY: biceptypes.azurecr.io | |
| # bicep-types ACR url for uploading test Radius Bicep types | |
| TEST_BICEP_TYPES_REGISTRY: ${{ vars.TEST_BICEP_TYPES_REGISTRY }} | |
| # Kubernetes client QPS and Burst settings for high-concurrency CI environments | |
| RADIUS_QPS_AND_BURST: "800" | |
| jobs: | |
| # Trust check for pull_request_target events. Determines whether the PR author | |
| # is a trusted contributor (org member or same-repo push) or an external contributor. | |
| # | |
| # Trust is determined by: | |
| # 1. Same-repo PR (head repo == base repo): trusted (only users with write access | |
| # can push branches to the repo). | |
| # 2. Fork PR + org member: trusted (checked via GitHub API using app token). | |
| # 3. Fork PR + non-member: external (requires approval). | |
| # | |
| # NOTE: We do NOT rely on github.event.pull_request.author_association because | |
| # webhook payloads report incorrect values for org members with private | |
| # membership visibility (returns CONTRIBUTOR instead of MEMBER). | |
| check-trust: | |
| name: Check Trust | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| if: github.event_name == 'pull_request_target' | |
| outputs: | |
| is-external: ${{ steps.check.outputs.is-external }} | |
| permissions: {} | |
| steps: | |
| - name: Generate App Token | |
| id: app-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| client-id: ${{ secrets.FUNCTIONAL_TEST_CLIENT_ID }} | |
| private-key: ${{ secrets.FUNCTIONAL_TEST_APP_PRIVATE_KEY }} | |
| owner: ${{ github.repository_owner }} | |
| repositories: radius | |
| permission-members: read | |
| - name: Determine trust level | |
| id: check | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} | |
| BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }} | |
| PR_AUTHOR: ${{ github.event.pull_request.user.login }} | |
| ORG: ${{ github.repository_owner }} | |
| run: | | |
| # Same-repo PRs are always trusted (requires write access to push branches) | |
| if [ "${HEAD_REPO}" = "${BASE_REPO}" ]; then | |
| echo "Same-repo PR from ${PR_AUTHOR} — trusted" | |
| echo "is-external=false" >> "${GITHUB_OUTPUT}" | |
| exit 0 | |
| fi | |
| # Fork PR: check if the author is an org member via GitHub API. | |
| # Uses app token which can read org membership regardless of visibility settings. | |
| # gh api returns exit code 0 for 204 (member) and non-zero for 404/302 (not a member). | |
| if gh api "orgs/${ORG}/members/${PR_AUTHOR}" --silent 2>/dev/null; then | |
| echo "Fork PR from org member ${PR_AUTHOR} — trusted" | |
| echo "is-external=false" >> "${GITHUB_OUTPUT}" | |
| else | |
| echo "Fork PR from ${PR_AUTHOR} — external" | |
| echo "is-external=true" >> "${GITHUB_OUTPUT}" | |
| fi | |
| # Approval gate for external contributors. Uses GitHub Environment protection | |
| # to require manual approval before running tests on PRs from non-members. | |
| approval-gate: | |
| name: Approval Gate | |
| needs: [check-trust] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| if: | | |
| needs.check-trust.outputs.is-external == 'true' | |
| environment: external-contributor-approval | |
| permissions: {} | |
| steps: | |
| - name: Approved | |
| run: echo "Tests approved to run" | |
| setup: | |
| name: Setup | |
| needs: [check-trust, approval-gate] | |
| # Run for all events. For PRs: | |
| # - check-trust determines if the author is external | |
| # - approval-gate runs only for external contributors and requires manual approval | |
| # - If check-trust or approval-gate are skipped (non-PR events), setup proceeds | |
| # For pull_request_target, require approval-gate to be 'success' or 'skipped' — block | |
| # on 'cancelled' (rejected approval) to prevent running PR code with secrets. | |
| if: | | |
| !cancelled() && | |
| (needs.check-trust.result == 'success' || needs.check-trust.result == 'skipped') && | |
| (needs.approval-gate.result == 'success' || needs.approval-gate.result == 'skipped') && | |
| (github.event_name != 'schedule' || github.repository == vars.RADIUS_REPOSITORY) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read # Required for listing the commits | |
| pull-requests: read # Required for fetching PR details | |
| env: | |
| DE_IMAGE: ghcr.io/radius-project/deployment-engine | |
| DE_TAG: latest | |
| outputs: | |
| REL_VERSION: ${{ steps.gen-id.outputs.REL_VERSION }} | |
| UNIQUE_ID: ${{ steps.gen-id.outputs.UNIQUE_ID }} | |
| PR_NUMBER: ${{ steps.gen-id.outputs.PR_NUMBER }} | |
| CHECKOUT_REPO: ${{ steps.gen-id.outputs.CHECKOUT_REPO }} | |
| CHECKOUT_REF: ${{ steps.gen-id.outputs.CHECKOUT_REF }} | |
| RAD_CLI_ARTIFACT_NAME: ${{ steps.gen-id.outputs.RAD_CLI_ARTIFACT_NAME }} | |
| DE_IMAGE: ${{ steps.gen-id.outputs.DE_IMAGE }} | |
| DE_TAG: ${{ steps.gen-id.outputs.DE_TAG }} | |
| BASE_SHA: ${{ steps.gen-id.outputs.BASE_SHA }} | |
| steps: | |
| - name: Log Event Information | |
| env: | |
| PR_AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }} | |
| PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} | |
| PR_BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }} | |
| IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} | |
| run: | | |
| echo "Event Name: ${GITHUB_EVENT_NAME}" | |
| echo "Actor: ${GITHUB_ACTOR}" | |
| echo "Author Association: ${PR_AUTHOR_ASSOCIATION}" | |
| echo "PR Head Repo: ${PR_HEAD_REPO}" | |
| echo "PR Base Repo: ${PR_BASE_REPO}" | |
| echo "Is Fork: ${IS_FORK}" | |
| - name: Set up checkout target (scheduled) | |
| if: github.event_name == 'schedule' | |
| run: | | |
| echo "CHECKOUT_REPO=${GITHUB_REPOSITORY}" >> $GITHUB_ENV | |
| echo "CHECKOUT_REF=refs/heads/main" >> $GITHUB_ENV | |
| - name: Set up checkout target (repository_dispatch) | |
| if: github.event_name == 'repository_dispatch' | |
| run: | | |
| echo "CHECKOUT_REPO=${GITHUB_REPOSITORY}" >> $GITHUB_ENV | |
| echo "CHECKOUT_REF=refs/heads/main" >> $GITHUB_ENV | |
| - name: Set up checkout target (pull_request_target) | |
| if: github.event_name == 'pull_request_target' | |
| env: | |
| PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} | |
| PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| run: | | |
| { | |
| echo "CHECKOUT_REPO=${PR_HEAD_REPO}" | |
| echo "CHECKOUT_REF=${PR_HEAD_SHA}" | |
| echo "PR_NUMBER=${PR_NUMBER}" | |
| echo "BASE_SHA=${PR_BASE_SHA}" | |
| } >> "${GITHUB_ENV}" | |
| - name: Set up checkout target (workflow_dispatch) | |
| if: github.event_name == 'workflow_dispatch' | |
| env: | |
| INPUT_BRANCH: ${{ github.event.inputs.branch }} | |
| run: | | |
| { | |
| echo "CHECKOUT_REPO=${GITHUB_REPOSITORY}" | |
| echo "CHECKOUT_REF=refs/heads/${INPUT_BRANCH}" | |
| } >> "${GITHUB_ENV}" | |
| - name: Set up checkout target (merge_group) | |
| if: github.event_name == 'merge_group' | |
| run: | | |
| # GITHUB_SHA is the merge queue's combined commit (base + queued PRs). | |
| # Using it as the checkout ref and the status-check sha ensures the | |
| # required check is reported on the commit the merge queue validates. | |
| { | |
| echo "CHECKOUT_REPO=${GITHUB_REPOSITORY}" | |
| echo "CHECKOUT_REF=${GITHUB_SHA}" | |
| } >> "${GITHUB_ENV}" | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Set DE image and tag (repository_dispatch from deployment-engine.run-functional-tests) | |
| if: github.event_name == 'repository_dispatch' | |
| shell: bash | |
| env: | |
| DEST_IMAGE: ${{ github.event.client_payload.dest_image }} | |
| TAG: ${{ github.event.client_payload.tag }} | |
| run: | | |
| { | |
| echo "DE_IMAGE=${DEST_IMAGE}" | |
| echo "DE_TAG=${TAG}" | |
| } >> "$GITHUB_ENV" | |
| - name: Generate ID for release | |
| id: gen-id | |
| run: | | |
| BASE_STR="RADIUS|${GITHUB_SHA}|${GITHUB_SERVER_URL}|${GITHUB_REPOSITORY}|${GITHUB_RUN_ID}|${GITHUB_RUN_ATTEMPT}" | |
| if [ "$GITHUB_EVENT_NAME" == "schedule" ]; then | |
| # Add run number to randomize unique id for scheduled runs. | |
| BASE_STR="${GITHUB_RUN_NUMBER}|${BASE_STR}" | |
| fi | |
| UNIQUE_ID=func$(echo $BASE_STR | sha1sum | head -c 10) | |
| echo "REL_VERSION=pr-${UNIQUE_ID}" >> $GITHUB_ENV | |
| # Set output variables to be used in the other jobs | |
| { | |
| echo "REL_VERSION=pr-${UNIQUE_ID}" | |
| echo "UNIQUE_ID=${UNIQUE_ID}" | |
| echo "CHECKOUT_REPO=${CHECKOUT_REPO}" | |
| echo "CHECKOUT_REF=${CHECKOUT_REF}" | |
| echo "RAD_CLI_ARTIFACT_NAME=rad_cli_linux_amd64" | |
| echo "PR_NUMBER=${PR_NUMBER}" | |
| echo "DE_IMAGE=${DE_IMAGE}" | |
| echo "DE_TAG=${DE_TAG}" | |
| echo "BASE_SHA=${BASE_SHA}" | |
| } >> "${GITHUB_OUTPUT}" | |
| changes: | |
| name: Changes | |
| needs: setup | |
| if: always() && needs.setup.result == 'success' | |
| uses: ./.github/workflows/__changes.yml | |
| with: | |
| ref: ${{ needs.setup.outputs.CHECKOUT_REF }} | |
| repository: ${{ needs.setup.outputs.CHECKOUT_REPO }} | |
| base_sha: ${{ needs.setup.outputs.BASE_SHA }} | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| build: | |
| name: Build Radius for test | |
| needs: [setup, changes] | |
| # Skip if only docs/markdown changed | |
| if: always() && needs.changes.outputs.only_changed != 'true' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read # Required for listing the commits | |
| packages: write # Required for uploading the package | |
| pull-requests: write # Required for updating pull requests | |
| id-token: write # Required for azure/login | |
| env: | |
| REL_VERSION: ${{ needs.setup.outputs.REL_VERSION }} | |
| UNIQUE_ID: ${{ needs.setup.outputs.UNIQUE_ID }} | |
| PR_NUMBER: ${{ needs.setup.outputs.PR_NUMBER }} | |
| CHECKOUT_REPO: ${{ needs.setup.outputs.CHECKOUT_REPO }} | |
| CHECKOUT_REF: ${{ needs.setup.outputs.CHECKOUT_REF }} | |
| RAD_CLI_ARTIFACT_NAME: ${{ needs.setup.outputs.RAD_CLI_ARTIFACT_NAME }} | |
| DE_IMAGE: ${{ needs.setup.outputs.DE_IMAGE }} | |
| DE_TAG: ${{ needs.setup.outputs.DE_TAG }} | |
| DAPR_VER: 1.14.4 | |
| ACTION_LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| steps: | |
| - name: Get GitHub app token | |
| if: github.repository == vars.RADIUS_REPOSITORY | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| id: get_installation_token | |
| with: | |
| client-id: ${{ secrets.FUNCTIONAL_TEST_CLIENT_ID }} | |
| private-key: ${{ secrets.FUNCTIONAL_TEST_APP_PRIVATE_KEY }} | |
| permission-pull-requests: write | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| repository: ${{ env.CHECKOUT_REPO }} | |
| ref: ${{ env.CHECKOUT_REF }} | |
| persist-credentials: false | |
| - name: Setup Go | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version-file: go.mod | |
| cache-dependency-path: go.sum | |
| cache: true | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| recreate: true | |
| message: | | |
| ## Radius functional test overview | |
| :mag: **[Go to test action run](${{ env.ACTION_LINK }})** | |
| <details> | |
| <summary> Click here to see the test run details</summary> | |
| | Name | Value | | |
| |------|-------| | |
| |**Repository** | ${{ env.CHECKOUT_REPO }} | | |
| |**Commit ref** | ${{ env.CHECKOUT_REF }} | | |
| |**Unique ID** | ${{ env.UNIQUE_ID }} | | |
| |**Image tag** | ${{ env.REL_VERSION }} | | |
| * KinD: ${{ env.KIND_VER }} | |
| * Dapr: ${{ env.DAPR_VER }} | |
| * Azure KeyVault CSI driver: ${{ env.AZURE_KEYVAULT_CSI_DRIVER_VER }} | |
| * Azure Workload identity webhook: ${{ env.AZURE_WORKLOAD_IDENTITY_WEBHOOK_VER }} | |
| * Bicep recipe location `${{ env.BICEP_RECIPE_REGISTRY }}/test/testrecipes/test-bicep-recipes/<name>:${{ env.REL_VERSION }}` | |
| * Terraform recipe location `${{ env.TF_RECIPE_MODULE_SERVER_URL }}/<name>.zip` (in cluster) | |
| * applications-rp test image location: `${{ env.CONTAINER_REGISTRY }}/applications-rp:${{ env.REL_VERSION }}` | |
| * dynamic-rp test image location: `${{ env.CONTAINER_REGISTRY }}/dynamic-rp:${{ env.REL_VERSION }}` | |
| * controller test image location: `${{ env.CONTAINER_REGISTRY }}/controller:${{ env.REL_VERSION }}` | |
| * ucp test image location: `${{ env.CONTAINER_REGISTRY }}/ucpd:${{ env.REL_VERSION }}` | |
| * deployment-engine test image location: `${{ env.DE_IMAGE }}:${{ env.DE_TAG }}` | |
| </details> | |
| ## Test Status | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ github.token }} | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :hourglass: Building Radius and pushing container images for functional tests... | |
| - name: Build and Push container images | |
| run: | | |
| make build && make docker-build && make docker-push | |
| env: | |
| DOCKER_REGISTRY: ${{ env.CONTAINER_REGISTRY }} | |
| DOCKER_TAG_VERSION: ${{ env.REL_VERSION }} | |
| - name: Upload CLI binary | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ${{ env.RAD_CLI_ARTIFACT_NAME }} | |
| path: | | |
| ./dist/linux_amd64/release/rad | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: success() && env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :white_check_mark: Container images build succeeded | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: failure() && env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :x: Container images build failed | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :hourglass: Publishing Bicep Recipes for functional tests... | |
| - name: Setup Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version-file: .node-version | |
| - name: Install yq | |
| # Required by make generate-bicep-types-contrib to parse defaults.yaml. | |
| run: | | |
| mkdir -p "${RUNNER_TEMP}/bin" | |
| curl -fsSL "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" -o "${RUNNER_TEMP}/bin/yq" | |
| echo "${YQ_LINUX_AMD64_SHA256} ${RUNNER_TEMP}/bin/yq" | sha256sum -c - | |
| chmod +x "${RUNNER_TEMP}/bin/yq" | |
| echo "${RUNNER_TEMP}/bin" >> "${GITHUB_PATH}" | |
| - name: Generate Bicep extensibility types from OpenAPI specs | |
| env: | |
| BICEP_TYPES_VERSION: ${{ env.REL_VERSION == 'edge' && 'latest' || env.REL_VERSION }} | |
| run: | | |
| make generate-bicep-types VERSION="${BICEP_TYPES_VERSION}" | |
| - name: Setup and verify bicep CLI | |
| run: | | |
| curl -Lo bicep https://github.com/Azure/bicep/releases/latest/download/bicep-linux-x64 | |
| chmod +x ./bicep | |
| sudo mv ./bicep /usr/local/bin/bicep | |
| bicep --version | |
| - name: Login to Azure (for private test bicep-types ACR) | |
| uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 | |
| with: | |
| client-id: ${{ secrets.AZURE_SP_TESTS_APPID }} | |
| tenant-id: ${{ secrets.AZURE_SP_TESTS_TENANTID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTIONID_TESTS }} | |
| - name: Publish Radius test bicep types | |
| env: | |
| BICEP_TYPES_VERSION: ${{ env.REL_VERSION == 'edge' && 'latest' || env.REL_VERSION }} | |
| run: | | |
| bicep publish-extension ./hack/bicep-types-radius/generated/index.json --target "br:${TEST_BICEP_TYPES_REGISTRY}/test/radius:${BICEP_TYPES_VERSION}" --force | |
| - name: Generate test bicepconfig.json | |
| run: | | |
| if [[ "${REL_VERSION}" == "edge" ]]; then | |
| RADIUS_VERSION="latest" | |
| else | |
| RADIUS_VERSION="${REL_VERSION}" | |
| fi | |
| cat <<EOF > ./test/bicepconfig.json | |
| { | |
| "extensions": { | |
| "radius": "br:${TEST_BICEP_TYPES_REGISTRY}/test/radius:$RADIUS_VERSION", | |
| "aws": "br:${BICEP_TYPES_REGISTRY}/aws:latest" | |
| } | |
| } | |
| EOF | |
| - name: Publish Bicep Test Recipes | |
| run: | | |
| mkdir ./bin | |
| cp ./dist/linux_amd64/release/rad ./bin/rad | |
| chmod +x ./bin/rad | |
| export PATH=$GITHUB_WORKSPACE/bin:$PATH | |
| which rad || { echo "cannot find rad"; exit 1; } | |
| rad bicep download | |
| rad version | |
| make publish-test-bicep-recipes | |
| env: | |
| BICEP_RECIPE_REGISTRY: ${{ env.BICEP_RECIPE_REGISTRY }} | |
| BICEP_RECIPE_TAG_VERSION: ${{ env.REL_VERSION }} | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: success() && env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :white_check_mark: Recipe publishing succeeded | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: failure() && env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :x: Test recipe publishing failed | |
| skip-tests: | |
| name: Skip Functional Tests | |
| needs: [setup, changes] | |
| if: always() && needs.changes.outputs.only_changed == 'true' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read # Required for listing the commits | |
| steps: | |
| - name: Get GitHub app token | |
| if: github.repository == vars.RADIUS_REPOSITORY | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| id: get_installation_token | |
| with: | |
| client-id: ${{ secrets.FUNCTIONAL_TEST_CLIENT_ID }} | |
| private-key: ${{ secrets.FUNCTIONAL_TEST_APP_PRIVATE_KEY }} | |
| permission-checks: write | |
| - uses: LouisBrunner/checks-action@937cbbcde3259005b50746dc91cde29098aac2ff # v3.1.0 | |
| with: | |
| token: ${{ steps.get_installation_token.outputs.token }} | |
| name: Functional Test Run | |
| status: completed | |
| conclusion: success | |
| repo: ${{ github.repository }} | |
| sha: ${{ needs.setup.outputs.CHECKOUT_REF }} | |
| output: | | |
| {"summary":"Skipped functional tests"} | |
| tests: | |
| name: Run ${{ matrix.name }} functional tests | |
| needs: [setup, build] | |
| if: always() && needs.setup.result == 'success' && needs.build.result == 'success' | |
| # Approval gate (via environment protection) ensures external contributors are approved before reaching here | |
| strategy: | |
| # Keep matrix legs independent. With fail-fast: true, a single failing | |
| # (often flaky) leg cancels all in-progress siblings. GitHub's | |
| # "Re-run failed jobs" reliably re-runs only failure-concluded jobs, so | |
| # cancelled siblings can stay stale on the PR and the checks never fully | |
| # update to green even after a successful re-run. | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-24.04] | |
| name: [corerp-cloud, ucp-cloud] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 60 | |
| permissions: | |
| id-token: write # Required for requesting the JWT | |
| contents: read # Required for listing the commits | |
| checks: write # Required for publishing test results | |
| packages: read # Required for pulling images from ghcr.io inside KinD | |
| pull-requests: write # Required for posting a PR comment | |
| env: | |
| UNIQUE_ID: ${{ needs.setup.outputs.UNIQUE_ID }} | |
| REL_VERSION: ${{ needs.setup.outputs.REL_VERSION }} | |
| CHECKOUT_REPO: ${{ needs.setup.outputs.CHECKOUT_REPO }} | |
| CHECKOUT_REF: ${{ needs.setup.outputs.CHECKOUT_REF }} | |
| PR_NUMBER: ${{ needs.setup.outputs.PR_NUMBER }} | |
| # Include run_attempt so each re-run uses a distinct resource group. | |
| # UNIQUE_ID is generated in the separate `setup` job, which is NOT | |
| # re-executed by "Re-run failed jobs", so it stays constant across | |
| # attempts. The delete step uses `az group delete --no-wait`, so a | |
| # re-run would otherwise try to recreate the same resource group while | |
| # the previous attempt's deletion is still in progress, failing with | |
| # "ResourceGroupBeingDeleted". The radtest- prefix is preserved so the | |
| # purge workflow still cleans up any orphaned groups. | |
| AZURE_TEST_RESOURCE_GROUP: radtest-${{ needs.setup.outputs.UNIQUE_ID }}-${{ matrix.name }}-${{ github.run_attempt }} | |
| RAD_CLI_ARTIFACT_NAME: ${{ needs.setup.outputs.RAD_CLI_ARTIFACT_NAME }} | |
| BICEP_RECIPE_TAG_VERSION: ${{ needs.setup.outputs.REL_VERSION }} | |
| DE_IMAGE: ${{ needs.setup.outputs.DE_IMAGE }} | |
| DE_TAG: ${{ needs.setup.outputs.DE_TAG }} | |
| steps: | |
| - name: Get GitHub app token | |
| if: github.repository == vars.RADIUS_REPOSITORY | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| id: get_installation_token | |
| with: | |
| client-id: ${{ secrets.FUNCTIONAL_TEST_CLIENT_ID }} | |
| private-key: ${{ secrets.FUNCTIONAL_TEST_APP_PRIVATE_KEY }} | |
| owner: ${{ github.repository_owner }} | |
| repositories: | | |
| radius | |
| terraform-private-modules | |
| permission-checks: write | |
| permission-pull-requests: write | |
| permission-contents: read | |
| - uses: LouisBrunner/checks-action@937cbbcde3259005b50746dc91cde29098aac2ff # v3.1.0 | |
| if: always() | |
| with: | |
| token: ${{ steps.get_installation_token.outputs.token }} | |
| name: Functional Test Run | |
| status: in_progress | |
| repo: ${{ github.repository }} | |
| sha: ${{ env.CHECKOUT_REF }} | |
| details_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| - name: Checkout Radius repository | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| repository: ${{ env.CHECKOUT_REPO }} | |
| ref: ${{ env.CHECKOUT_REF }} | |
| persist-credentials: false | |
| - name: Checkout Samples repository | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| if: matrix.name == 'samples' | |
| with: | |
| repository: radius-project/samples | |
| ref: refs/heads/edge | |
| path: samples | |
| persist-credentials: false | |
| - name: Setup Go | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version-file: go.mod | |
| cache-dependency-path: go.sum | |
| cache: true | |
| - name: Download rad CLI | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: ${{ env.RAD_CLI_ARTIFACT_NAME }} | |
| path: ${{ runner.temp }}/rad-cli-artifact | |
| - name: Copy rad CLI to bin | |
| run: | | |
| mkdir -p ./bin | |
| cp "${RUNNER_TEMP}/rad-cli-artifact/rad" ./bin/rad | |
| chmod +x ./bin/rad | |
| # Verify it is the expected binary | |
| file ./bin/rad | grep -q "ELF" || { echo "Downloaded rad binary is not a valid ELF executable"; exit 1; } | |
| # Verify it is the expected rad CLI binary by running a harmless command | |
| ./bin/rad version >/dev/null 2>&1 || { echo "Downloaded rad binary is not a working rad CLI executable"; exit 1; } | |
| - name: Login to Azure | |
| uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 | |
| with: | |
| client-id: ${{ secrets.AZURE_SP_TESTS_APPID }} | |
| tenant-id: ${{ secrets.AZURE_SP_TESTS_TENANTID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTIONID_TESTS }} | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :hourglass: Starting ${{ matrix.name }} functional tests... | |
| - name: Create azure resource group - ${{ env.AZURE_TEST_RESOURCE_GROUP }} | |
| run: | | |
| current_time=$(date +%s) | |
| az group create \ | |
| --location "${AZURE_LOCATION}" \ | |
| --name "${RESOURCE_GROUP}" \ | |
| --subscription "${AZURE_SUBSCRIPTIONID_TESTS}" \ | |
| --tags creationTime=$current_time | |
| while [ $(az group exists --name "${RESOURCE_GROUP}") = false ]; do sleep 2; done | |
| env: | |
| RESOURCE_GROUP: ${{ env.AZURE_TEST_RESOURCE_GROUP }} | |
| AZURE_SUBSCRIPTIONID_TESTS: ${{ secrets.AZURE_SUBSCRIPTIONID_TESTS }} | |
| - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 | |
| with: | |
| version: ${{ env.HELM_VER }} | |
| # The role-to-assume is the role that the github action will assume to execute aws commands and | |
| # construct cloud control client in test code. | |
| - name: configure aws credentials using assumed role | |
| uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0 | |
| with: | |
| role-to-assume: ${{ secrets.AWS_GH_ACTIONS_ROLE }} | |
| role-session-name: GitHub_to_AWS_via_FederatedOIDC | |
| aws-region: ${{ env.AWS_REGION }} | |
| # create kind cluster with OIDC provider. | |
| - name: Create KinD cluster | |
| env: | |
| FUNCTEST_AZURE_OIDC_JSON: ${{ secrets.FUNCTEST_AZURE_OIDC_JSON }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| curl -sSLo "kind" "https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VER}/kind-linux-amd64" | |
| chmod +x ./kind | |
| # Parse and validate Azure workload identity values from secret JSON. | |
| OIDC_JSON="${FUNCTEST_AZURE_OIDC_JSON}" | |
| if ! echo "${OIDC_JSON}" | jq -e . >/dev/null; then | |
| echo "FUNCTEST_AZURE_OIDC_JSON is not valid JSON." | |
| echo "Expected keys: AZURE_OIDC_ISSUER, AZURE_OIDC_ISSUER_PUBLIC_KEY, AZURE_OIDC_ISSUER_PRIVATE_KEY" | |
| exit 1 | |
| fi | |
| AZURE_OIDC_ISSUER="$(echo "${OIDC_JSON}" | jq -er '.AZURE_OIDC_ISSUER')" | |
| AZURE_OIDC_ISSUER_PUBLIC_KEY="$(echo "${OIDC_JSON}" | jq -er '.AZURE_OIDC_ISSUER_PUBLIC_KEY')" | |
| AZURE_OIDC_ISSUER_PRIVATE_KEY="$(echo "${OIDC_JSON}" | jq -er '.AZURE_OIDC_ISSUER_PRIVATE_KEY')" | |
| AUTHKEY=$(printf '%s' "${GITHUB_ACTOR}:${GITHUB_TOKEN}" | base64) | |
| echo "{\"auths\":{\"ghcr.io\":{\"auth\":\"${AUTHKEY}\"}}}" > "./ghcr_secret.json" | |
| # Create KinD cluster with OIDC Issuer keys | |
| printf '%s' "${AZURE_OIDC_ISSUER_PUBLIC_KEY}" | base64 -d > sa.pub | |
| printf '%s' "${AZURE_OIDC_ISSUER_PRIVATE_KEY}" | base64 -d > sa.key | |
| openssl pkey -pubin -in sa.pub -noout >/dev/null | |
| openssl pkey -in sa.key -check -noout >/dev/null | |
| cat <<EOF | ./kind create cluster --name radius --config=- | |
| kind: Cluster | |
| apiVersion: kind.x-k8s.io/v1alpha4 | |
| nodes: | |
| - role: control-plane | |
| extraMounts: | |
| - hostPath: ./sa.pub | |
| containerPath: /etc/kubernetes/pki/sa.pub | |
| - hostPath: ./sa.key | |
| containerPath: /etc/kubernetes/pki/sa.key | |
| - hostPath: ./ghcr_secret.json | |
| containerPath: /var/lib/kubelet/config.json | |
| kubeadmConfigPatches: | |
| - | | |
| kind: ClusterConfiguration | |
| apiServer: | |
| extraArgs: | |
| service-account-issuer: $AZURE_OIDC_ISSUER | |
| service-account-key-file: /etc/kubernetes/pki/sa.pub | |
| service-account-signing-key-file: /etc/kubernetes/pki/sa.key | |
| controllerManager: | |
| extraArgs: | |
| service-account-private-key-file: /etc/kubernetes/pki/sa.key | |
| EOF | |
| - name: Install Azure Keyvault CSI driver chart | |
| run: | | |
| helm repo add csi-secrets-store-provider-azure https://azure.github.io/secrets-store-csi-driver-provider-azure/charts | |
| helm install csi csi-secrets-store-provider-azure/csi-secrets-store-provider-azure --version "${AZURE_KEYVAULT_CSI_DRIVER_VER}" | |
| - name: Install azure workload identity webhook chart | |
| env: | |
| AZURE_SP_TESTS_TENANTID: ${{ secrets.AZURE_SP_TESTS_TENANTID }} | |
| run: | | |
| helm repo add azure-workload-identity https://azure.github.io/azure-workload-identity/charts | |
| helm install workload-identity-webhook azure-workload-identity/workload-identity-webhook --namespace radius-default --create-namespace --version "${AZURE_WORKLOAD_IDENTITY_WEBHOOK_VER}" --set "azureTenantID=${AZURE_SP_TESTS_TENANTID}" | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ github.token }} | |
| - name: Download Bicep | |
| run: | | |
| export PATH=$GITHUB_WORKSPACE/bin:$PATH | |
| which rad || { echo "cannot find rad"; exit 1; } | |
| rad bicep download | |
| rad version | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: failure() && env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :x: Test tool installation for ${{ matrix.name }} failed. Please check [the logs](${{ env.ACTION_LINK }}) for more details | |
| - name: Install Radius | |
| env: | |
| AZURE_SUBSCRIPTIONID_TESTS: ${{ secrets.AZURE_SUBSCRIPTIONID_TESTS }} | |
| AZURE_SP_TESTS_APPID: ${{ secrets.AZURE_SP_TESTS_APPID }} | |
| AZURE_SP_TESTS_TENANTID: ${{ secrets.AZURE_SP_TESTS_TENANTID }} | |
| FUNCTEST_AWS_ACCOUNT_ID: ${{ secrets.FUNCTEST_AWS_ACCOUNT_ID }} | |
| FUNC_TEST_RAD_IRSA_ROLE: ${{ secrets.FUNC_TEST_RAD_IRSA_ROLE }} | |
| run: | | |
| export PATH=$GITHUB_WORKSPACE/bin:$PATH | |
| which rad || { echo "cannot find rad"; exit 1; } | |
| echo "*** Installing Radius to Kubernetes ***" | |
| rad install kubernetes \ | |
| --chart "${RADIUS_CHART_LOCATION}" \ | |
| --set "rp.image=${CONTAINER_REGISTRY}/applications-rp,rp.tag=${REL_VERSION}" \ | |
| --set "dynamicrp.image=${CONTAINER_REGISTRY}/dynamic-rp,dynamicrp.tag=${REL_VERSION}" \ | |
| --set "controller.image=${CONTAINER_REGISTRY}/controller,controller.tag=${REL_VERSION}" \ | |
| --set "ucp.image=${CONTAINER_REGISTRY}/ucpd,ucp.tag=${REL_VERSION}" \ | |
| --set "de.image=${DE_IMAGE},de.tag=${DE_TAG}" \ | |
| --set "bicep.image=${CONTAINER_REGISTRY}/bicep,bicep.tag=${REL_VERSION}" \ | |
| --set global.azureWorkloadIdentity.enabled=true \ | |
| --set global.aws.irsa.enabled=true | |
| echo "*** Verify manifests are registered ***" | |
| rm -f registermanifest_logs.txt | |
| # Find the pod with container "ucp" | |
| POD_NAME=$( | |
| kubectl get pods -n radius-system \ | |
| -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.containers[*].name}{"\n"}{end}' \ | |
| | grep "ucp" \ | |
| | head -n1 \ | |
| | cut -d" " -f1 | |
| ) | |
| echo "Found ucp pod: $POD_NAME" | |
| if [ -z "$POD_NAME" ]; then | |
| echo "No pod with container 'ucp' found in namespace radius-system." | |
| exit 1 | |
| fi | |
| # Poll logs for up to iterations, 30 seconds each (upto 3 minutes total) | |
| for i in {1..6}; do | |
| kubectl logs "$POD_NAME" -n radius-system | tee registermanifest_logs.txt > /dev/null | |
| # Exit on error | |
| if grep -qi "Service initializer terminated with error" registermanifest_logs.txt; then | |
| echo "Error found in ucp logs:" | |
| grep -i "Service initializer terminated with error" registermanifest_logs.txt | |
| exit 1 | |
| fi | |
| # Check for success | |
| if grep -q "Successfully registered manifests" registermanifest_logs.txt; then | |
| echo "Successfully registered manifests - message found." | |
| break | |
| fi | |
| echo "Logs not ready, waiting 30 seconds..." | |
| sleep 30 | |
| done | |
| # Final check to ensure success message was found | |
| if ! grep -q "Successfully registered manifests" registermanifest_logs.txt; then | |
| echo "Manifests not registered after 3 minutes." | |
| exit 1 | |
| fi | |
| echo "*** Create workspace, group and environment for test ***" | |
| rad workspace create kubernetes | |
| rad group create kind-radius | |
| rad group switch kind-radius | |
| # The functional test is designed to use default namespace. So you must create the environment for default namespace. | |
| rad env create kind-radius --namespace default | |
| rad env switch kind-radius | |
| echo "*** Configuring Azure provider ***" | |
| rad env update kind-radius --azure-subscription-id "${AZURE_SUBSCRIPTIONID_TESTS}" \ | |
| --azure-resource-group "${AZURE_TEST_RESOURCE_GROUP}" | |
| rad credential register azure wi \ | |
| --client-id "${AZURE_SP_TESTS_APPID}" \ | |
| --tenant-id "${AZURE_SP_TESTS_TENANTID}" | |
| echo "*** Configuring AWS provider ***" | |
| rad env update kind-radius --aws-region "${AWS_REGION}" --aws-account-id "${FUNCTEST_AWS_ACCOUNT_ID}" | |
| rad credential register aws irsa \ | |
| --iam-role "${FUNC_TEST_RAD_IRSA_ROLE}" | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: failure() && env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :x: Failed to install Radius for ${{ matrix.name }} functional test. Please check [the logs](${{ env.ACTION_LINK }}) for more details | |
| - name: Publish Terraform test recipes | |
| run: | | |
| make publish-test-terraform-recipes | |
| - name: Generate test bicepconfig.json | |
| run: | | |
| if [[ "${REL_VERSION}" == "edge" ]]; then | |
| RADIUS_VERSION="latest" | |
| else | |
| RADIUS_VERSION="${REL_VERSION}" | |
| fi | |
| cat <<EOF > ./test/bicepconfig.json | |
| { | |
| "extensions": { | |
| "radius": "br:${TEST_BICEP_TYPES_REGISTRY}/test/radius:$RADIUS_VERSION", | |
| "aws": "br:${BICEP_TYPES_REGISTRY}/aws:latest" | |
| }, | |
| "cloud": { | |
| "credentialPrecedence": ["AzureCLI", "Environment"] | |
| } | |
| } | |
| EOF | |
| - name: Restore Bicep artifacts before running functional tests | |
| # The exact files chosen to run the command can be changed, but we need 1 that uses the Radius extension and 1 that uses the AWS extension so we can restore both Bicep artifacts before the tests start | |
| run: | | |
| # Restore Radius Bicep types | |
| bicep restore ./test/functional-portable/corerp/cloud/resources/testdata/corerp-azure-connection-database-service.bicep --force | |
| # Restore AWS Bicep types | |
| bicep restore ./test/functional-portable/corerp/cloud/resources/testdata/aws-logs-loggroup.bicep --force | |
| - name: Run functional tests | |
| run: | | |
| set -euo pipefail | |
| # Ensure rad cli is in path before running tests. | |
| export PATH=$GITHUB_WORKSPACE/bin:$PATH | |
| # Make directory to capture functional test results | |
| mkdir -p ./dist/functional_test | |
| cd $GITHUB_WORKSPACE | |
| which rad || { echo "cannot find rad"; exit 1; } | |
| # Populate the following test environment variables from JSON secret. | |
| # AZURE_COSMOS_MONGODB_ACCOUNT_ID | |
| # AZURE_MSSQL_RESOURCE_ID | |
| # AZURE_MSSQL_USERNAME | |
| # AZURE_MSSQL_PASSWORD | |
| PREPROVISIONED_JSON="${FUNCTEST_PREPROVISIONED_RESOURCE_JSON}" | |
| if ! echo "${PREPROVISIONED_JSON}" | jq -e . >/dev/null; then | |
| echo "FUNCTEST_PREPROVISIONED_RESOURCE_JSON is not valid JSON." | |
| echo "Expected keys: AZURE_COSMOS_MONGODB_ACCOUNT_ID, AZURE_MSSQL_RESOURCE_ID, AZURE_MSSQL_USERNAME, AZURE_MSSQL_PASSWORD" | |
| exit 1 | |
| fi | |
| export AZURE_COSMOS_MONGODB_ACCOUNT_ID="$(echo "${PREPROVISIONED_JSON}" | jq -er '.AZURE_COSMOS_MONGODB_ACCOUNT_ID')" | |
| export AZURE_MSSQL_RESOURCE_ID="$(echo "${PREPROVISIONED_JSON}" | jq -er '.AZURE_MSSQL_RESOURCE_ID')" | |
| export AZURE_MSSQL_USERNAME="$(echo "${PREPROVISIONED_JSON}" | jq -er '.AZURE_MSSQL_USERNAME')" | |
| export AZURE_MSSQL_PASSWORD="$(echo "${PREPROVISIONED_JSON}" | jq -er '.AZURE_MSSQL_PASSWORD')" | |
| make "test-functional-${MATRIX_NAME}" | |
| env: | |
| DOCKER_REGISTRY: ${{ env.CONTAINER_REGISTRY }} | |
| TEST_TIMEOUT: ${{ env.FUNCTIONALTEST_TIMEOUT }} | |
| RADIUS_CONTAINER_LOG_PATH: ${{ github.workspace }}/${{ env.RADIUS_CONTAINER_LOG_BASE }} | |
| AWS_REGION: ${{ env.AWS_REGION }} | |
| AWS_ACCOUNT_ID: ${{ secrets.FUNCTEST_AWS_ACCOUNT_ID }} | |
| RADIUS_SAMPLES_REPO_ROOT: ${{ github.workspace }}/samples | |
| # Test_MongoDB_Recipe_Parameters is using the following environment variable. | |
| INTEGRATION_TEST_RESOURCE_GROUP_NAME: ${{ env.AZURE_TEST_RESOURCE_GROUP }} | |
| # Enable Azure resource location verification in tests | |
| AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTIONID_TESTS }} | |
| BICEP_RECIPE_REGISTRY: ${{ env.BICEP_RECIPE_REGISTRY }} | |
| BICEP_RECIPE_TAG_VERSION: ${{ env.BICEP_RECIPE_TAG_VERSION }} | |
| GH_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| GOTESTSUM_OPTS: --junitfile ./dist/functional_test/results.xml | |
| # Initiate resource deletion without blocking on completion (matches the | |
| # non-cloud suite). Test assertions for deploy/create still run in full; | |
| # only the synchronous teardown wait is skipped, and the per-run resource | |
| # group is force-deleted afterward. This removes the slow Test_ACI teardown | |
| # (~9 min) from the critical path without reducing validation scope. | |
| RADIUS_TEST_FAST_CLEANUP: true | |
| MATRIX_NAME: ${{ matrix.name }} | |
| FUNCTEST_PREPROVISIONED_RESOURCE_JSON: ${{ secrets.FUNCTEST_PREPROVISIONED_RESOURCE_JSON }} | |
| - name: Process Functional Test Results | |
| uses: ./.github/actions/process-test-results | |
| # Run on success and failure so a passing "Re-run failed jobs" refreshes the | |
| # "Functional Tests - <name>" check published by EnricoMi. Gating this on | |
| # failure() left that check stuck red after a green re-run, because the step | |
| # was skipped and the previous attempt's failed check was never updated. | |
| # comment_mode: failures keeps PR comments limited to actual failures, and | |
| # the failure artifacts are still uploaded for post-mortem. | |
| if: always() && github.repository == vars.RADIUS_REPOSITORY | |
| with: | |
| test_group_name: Functional Tests - ${{ matrix.name }} | |
| artifact_name: functional_test_results_${{ matrix.name }} | |
| result_directory: dist/functional_test/ | |
| comment_mode: failures | |
| - name: Collect Pod details | |
| if: always() | |
| # Diagnostic-only snapshot of cluster-wide pod state for post-mortem. A | |
| # `kubectl describe pods -A` can race pod teardown and return a transient | |
| # NotFound, so never let it fail the job. | |
| continue-on-error: true | |
| env: | |
| MATRIX_NAME: ${{ matrix.name }} | |
| run: | | |
| POD_STATE_LOG_FILENAME="${RADIUS_CONTAINER_LOG_BASE}/${MATRIX_NAME}-tests-pod-states.log" | |
| mkdir -p "$(dirname "${POD_STATE_LOG_FILENAME}")" | |
| { | |
| echo "kubectl get pods -A" | |
| kubectl get pods -A | |
| echo "kubectl describe pods -A" | |
| kubectl describe pods -A | |
| } >> "${POD_STATE_LOG_FILENAME}" | |
| - name: Upload container logs | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ${{ matrix.name }}_container_logs | |
| path: ./${{ env.RADIUS_CONTAINER_LOG_BASE }} | |
| - name: Get Terraform recipe publishing logs | |
| if: always() | |
| run: | | |
| # Create pod-logs directory | |
| mkdir -p recipes/pod-logs | |
| # Get pod logs and save to file | |
| namespace="radius-test-tf-module-server" | |
| label="app.kubernetes.io/name=tf-module-server" | |
| pod_names=($(kubectl get pods -l $label -n $namespace -o jsonpath='{.items[*].metadata.name}')) | |
| for pod_name in "${pod_names[@]}"; do | |
| kubectl logs $pod_name -n $namespace > recipes/pod-logs/${pod_name}.txt | |
| done | |
| echo "Pod logs saved to recipes/pod-logs/" | |
| # Get kubernetes events and save to file | |
| kubectl get events -n $namespace > recipes/pod-logs/events.txt | |
| - name: Upload Terraform recipe publishing logs | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| if: always() | |
| with: | |
| name: ${{ matrix.name }}_recipes-pod-logs | |
| path: recipes/pod-logs | |
| if-no-files-found: error | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: success() && env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :white_check_mark: ${{ matrix.name }} functional tests succeeded | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: failure() && env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :x: ${{ matrix.name }} functional test failed. Please check [the logs](${{ env.ACTION_LINK }}) for more details | |
| - uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 | |
| if: cancelled() && env.PR_NUMBER != '' | |
| continue-on-error: true | |
| with: | |
| GITHUB_TOKEN: ${{ steps.get_installation_token.outputs.token }} | |
| header: teststatus | |
| number: ${{ env.PR_NUMBER }} | |
| append: true | |
| message: | | |
| :x: ${{ matrix.name }} functional test cancelled. Please check [the logs](${{ env.ACTION_LINK }}) for more details | |
| - name: Release ACI quota before async resource group delete | |
| if: always() | |
| env: | |
| AZURE_SUBSCRIPTIONID_TESTS: ${{ secrets.AZURE_SUBSCRIPTIONID_TESTS }} | |
| run: | | |
| # The regional Azure Container Instances 'StandardCores' quota is shared | |
| # across all concurrent cloud functional-test runs and is the bottleneck | |
| # behind Test_ACI (deploys fail with ContainerGroupQuotaReached). The | |
| # resource group is deleted with --no-wait below because the full delete is | |
| # slow for gateways/VNets (see #12044), so its ACI resources would otherwise | |
| # linger for minutes and keep holding quota that other runs need. Delete the | |
| # ACI resources synchronously here to release the quota promptly. Best-effort: | |
| # the resource group delete and the purge workflow still clean up the rest. | |
| set -uo pipefail | |
| sub="${AZURE_SUBSCRIPTIONID_TESTS}" | |
| rg="${AZURE_TEST_RESOURCE_GROUP}" | |
| # Radius creates the ACI nGroups/containerGroupProfiles with this API version | |
| # (pkg/sdk/v20241101preview). Delete with it explicitly: a plain delete lets | |
| # ARM pick the provider's latest advertised version (e.g. 2026-06-01-preview), | |
| # which the RP rejects ("api-version not supported"), leaving the nGroups - | |
| # and therefore the whole resource group - undeletable. | |
| aci_api_version="2024-11-01-preview" | |
| if ! az group show --subscription "${sub}" --name "${rg}" >/dev/null 2>&1; then | |
| echo "Resource group ${rg} not found; nothing to release." | |
| exit 0 | |
| fi | |
| aci_ids=$(az resource list \ | |
| --subscription "${sub}" \ | |
| --resource-group "${rg}" \ | |
| --query "[?starts_with(type, 'Microsoft.ContainerInstance/')].id" \ | |
| --output tsv 2>/dev/null || true) | |
| if [ -z "${aci_ids}" ]; then | |
| echo "No Azure Container Instances resources in ${rg}." | |
| exit 0 | |
| fi | |
| echo "Releasing ACI quota by deleting:" | |
| echo "${aci_ids}" | |
| # nGroups (container scale sets) back the quota-holding container groups, so | |
| # delete them first (in parallel) and wait, then delete the remaining ACI | |
| # resources (profiles). Parallelizing keeps the synchronous teardown short | |
| # while preserving the nGroups-before-profiles order (nGroups reference the | |
| # profiles). | |
| for id in $(echo "${aci_ids}" | grep -i '/nGroups/' || true); do | |
| az resource delete --subscription "${sub}" --ids "${id}" --api-version "${aci_api_version}" --verbose || true & | |
| done | |
| wait | |
| for id in $(echo "${aci_ids}" | grep -iv '/nGroups/' || true); do | |
| az resource delete --subscription "${sub}" --ids "${id}" --api-version "${aci_api_version}" --verbose || true & | |
| done | |
| wait | |
| - name: Delete azure resource group - ${{ env.AZURE_TEST_RESOURCE_GROUP }} | |
| if: always() | |
| env: | |
| AZURE_SUBSCRIPTIONID_TESTS: ${{ secrets.AZURE_SUBSCRIPTIONID_TESTS }} | |
| run: | | |
| # if deletion fails, purge workflow will purge the resource group and its resources later. | |
| az group delete \ | |
| --subscription "${AZURE_SUBSCRIPTIONID_TESTS}" \ | |
| --name "${AZURE_TEST_RESOURCE_GROUP}" \ | |
| --yes \ | |
| --verbose \ | |
| --no-wait | |
| report-test-results: | |
| # Report final test status. Runs after all tests complete (or are skipped). | |
| if: always() && github.repository == vars.RADIUS_REPOSITORY | |
| name: Report test results | |
| needs: [setup, build, tests] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| env: | |
| CHECKOUT_REF: ${{ needs.setup.outputs.CHECKOUT_REF }} | |
| steps: | |
| - name: Get GitHub app token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| id: get_installation_token | |
| with: | |
| client-id: ${{ secrets.FUNCTIONAL_TEST_CLIENT_ID }} | |
| private-key: ${{ secrets.FUNCTIONAL_TEST_APP_PRIVATE_KEY }} | |
| permission-checks: write | |
| - name: Aggregate functional test status | |
| id: get_test_status | |
| run: | | |
| # Derive the overall status from the needs context instead of querying the | |
| # Actions API. `tests` is a matrix job, so needs.tests.result already collapses | |
| # all legs (success only if every leg passed). A docs-only run skips build+tests | |
| # with setup successful, which yields success and matches the skip-tests job. | |
| # failure takes precedence over cancelled. | |
| echo "setup=${SETUP_RESULT} build=${BUILD_RESULT} tests=${TESTS_RESULT}" | |
| TEST_STATUS="success" | |
| for result in "${SETUP_RESULT}" "${BUILD_RESULT}" "${TESTS_RESULT}"; do | |
| if [[ "${result}" == "failure" ]]; then | |
| TEST_STATUS="failure" | |
| break | |
| elif [[ "${result}" == "cancelled" ]]; then | |
| TEST_STATUS="cancelled" | |
| fi | |
| done | |
| echo "Functional Test Run status: ${TEST_STATUS}" | |
| echo "test_status=${TEST_STATUS}" >> "${GITHUB_OUTPUT}" | |
| env: | |
| SETUP_RESULT: ${{ needs.setup.result }} | |
| BUILD_RESULT: ${{ needs.build.result }} | |
| TESTS_RESULT: ${{ needs.tests.result }} | |
| - uses: LouisBrunner/checks-action@937cbbcde3259005b50746dc91cde29098aac2ff # v3.1.0 | |
| if: always() | |
| with: | |
| token: ${{ steps.get_installation_token.outputs.token }} | |
| name: Functional Test Run | |
| repo: ${{ github.repository }} | |
| sha: ${{ env.CHECKOUT_REF }} | |
| status: completed | |
| conclusion: ${{ steps.get_test_status.outputs.test_status }} | |
| output: | | |
| {"summary":"Functional Test run completed. See links for more information.","title":"Functional Test Run"} | |
| details_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| report-failure: | |
| name: Report test failure | |
| needs: [build, tests] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| if: failure() && github.event_name == 'schedule' && github.repository == vars.RADIUS_REPOSITORY | |
| permissions: | |
| issues: write # Required to create an issue when the scheduled run fails | |
| steps: | |
| - name: Create failure issue for failing scheduled run | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| github-token: ${{ github.token }} | |
| script: | | |
| github.rest.issues.create({ | |
| ...context.repo, | |
| title: `Scheduled functional test failed - Run ID: ${context.runId}`, | |
| labels: ['test-failure'], | |
| body: `## Bug information \n\nThis issue is automatically generated if the scheduled functional test fails. The Radius functional test operates on a schedule of every 4 hours during weekdays and every 12 hours over the weekend. It's important to understand that the test may fail due to workflow infrastructure issues, like network problems, rather than the flakiness of the test itself. For the further investigation, please visit [here](${process.env.ACTION_LINK}).` | |
| }) |