# ------------------------------------------------------------ # Copyright 2021 The Dapr 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. # ------------------------------------------------------------ name: Components Conformance Tests on: repository_dispatch: types: [conformance-test] workflow_dispatch: schedule: - cron: '0 */8 * * *' push: branches: - 'release-*' - 'gh-readonly-queue/main/*' pull_request: branches: - 'main' - 'release-*' - 'gh-readonly-queue/main/*' merge_group: jobs: # Based on whether this is a PR or a scheduled run, we will run a different # subset of the conformance tests. This allows all the tests not requiring # secrets to be executed on pull requests. generate-matrix: runs-on: ubuntu-22.04 steps: - name: Parse repository_dispatch payload if: github.event_name == 'repository_dispatch' working-directory: ${{ github.workspace }} run: | if [ ${{ github.event.client_payload.command }} = "ok-to-test" ]; then echo "CHECKOUT_REF=${{ github.event.client_payload.pull_head_ref }}" >> $GITHUB_ENV echo "PR_NUMBER=${{ github.event.client_payload.issue.number }}" >> $GITHUB_ENV fi - name: Check out code uses: actions/checkout@v4 with: repository: ${{ env.CHECKOUT_REPO }} ref: ${{ env.CHECKOUT_REF }} - name: Generate test matrix id: generate-matrix env: VAULT_NAME: ${{ secrets.AZURE_KEYVAULT }} run: | if [ -z "$VAULT_NAME" ]; then # Do not include cloud tests when credentials are not available node .github/scripts/test-info.mjs conformance false else # Include cloud tests node .github/scripts/test-info.mjs conformance true fi - name: Create PR comment if: env.PR_NUMBER != '' uses: artursouza/sticky-pull-request-comment@da9e86aa2a80e4ae3b854d251add33bd6baabcba with: header: ${{ github.run_id }} number: ${{ env.PR_NUMBER }} GITHUB_TOKEN: ${{ secrets.DAPR_BOT_TOKEN }} message: | # Components conformance test 🔗 **[Link to Action run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})** Commit ref: ${{ env.CHECKOUT_REF }} outputs: test-matrix: ${{ steps.generate-matrix.outputs.test-matrix }} conformance: name: ${{ matrix.component }} conformance # Add "id-token" with the intended permissions. # Needed by the 'Authenticate to Google Cloud' step. permissions: contents: 'read' id-token: 'write' runs-on: ubuntu-22.04 env: UNIQUE_ID: ${{github.run_id}}-${{github.run_attempt}} GOCOV_VER: "v1.1.0" GOTESTSUM_VER: "v1.9.0" defaults: run: shell: bash needs: - generate-matrix strategy: fail-fast: false # Keep running even if one component fails matrix: include: ${{ fromJson(needs.generate-matrix.outputs.test-matrix) }} steps: - name: Set default payload repo and ref working-directory: ${{ github.workspace }} run: | echo "CHECKOUT_REPO=${{ github.repository }}" >> $GITHUB_ENV echo "CHECKOUT_REF=${{ github.ref }}" >> $GITHUB_ENV - name: Parse repository_dispatch payload if: github.event_name == 'repository_dispatch' working-directory: ${{ github.workspace }} run: | if [ ${{ github.event.client_payload.command }} = "ok-to-test" ]; then echo "CHECKOUT_REPO=${{ github.event.client_payload.pull_head_repo }}" >> $GITHUB_ENV echo "CHECKOUT_REF=${{ github.event.client_payload.pull_head_ref }}" >> $GITHUB_ENV echo "PR_NUMBER=${{ github.event.client_payload.issue.number }}" >> $GITHUB_ENV fi - name: Check out code uses: actions/checkout@v4 with: repository: ${{ env.CHECKOUT_REPO }} ref: ${{ env.CHECKOUT_REF }} - name: Setup test environment run: | # Output file echo "TEST_OUTPUT_FILE_PREFIX=$GITHUB_WORKSPACE/test_report" >> $GITHUB_ENV # Current time (used by Terraform) echo "CURRENT_TIME=$(date --rfc-3339=date)" >> ${GITHUB_ENV} - name: Configure conformance test and source path run: | TEST_COMPONENT=$(echo ${{ matrix.component }} | sed -E 's/\./\//g') export SOURCE_PATH="github.com/dapr/components-contrib/${TEST_COMPONENT}" echo "SOURCE_PATH=$SOURCE_PATH" >> $GITHUB_ENV # converts slashes to dots in this string, so that it doesn't consider them sub-folders export SOURCE_PATH_LINEAR=$(echo "$SOURCE_PATH" |sed 's#/#\.#g') echo "SOURCE_PATH_LINEAR=$SOURCE_PATH_LINEAR" >> $GITHUB_ENV - uses: Azure/login@v1 if: matrix.required-secrets != '' with: creds: ${{ secrets.AZURE_CREDENTIALS }} # Set this GitHub secret to your KeyVault, and grant the KeyVault policy to your Service Principal: # az keyvault set-policy -n $AZURE_KEYVAULT --secret-permissions get list --spn $SPN_CLIENT_ID # Using az cli to query keyvault as Azure/get-keyvault-secrets@v1 is deprecated - name: Setup secrets if: matrix.required-secrets != '' env: VAULT_NAME: ${{ secrets.AZURE_KEYVAULT }} run: | secrets="${{ matrix.required-secrets }}" for secretName in $(echo -n $secrets | tr ',' ' '); do value=$(az keyvault secret show \ --name $secretName \ --vault-name $VAULT_NAME \ --query value \ --output tsv) echo "::add-mask::$value" echo "$secretName=$value" >> $GITHUB_OUTPUT echo "$secretName=$value" >> $GITHUB_ENV done # Authenticate with GCP Workload Identity Pool # Exports GCP ENV Vars: # - GCP_PROJECT # - GOOGLE_APPLICATION_CREDENTIALS - id: 'auth' if: matrix.require-gcp-credentials == 'true' name: 'Authenticate to Google Cloud' uses: 'google-github-actions/auth@v1' with: token_format: 'access_token' workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER_NAME }} service_account: ${{ secrets.GCP_WIF_SA_EMAIL }} create_credentials_file: true export_environment_variables: true cleanup_credentials: true # Download the required certificates into files, and set env var pointing to their names - name: Setup certs if: matrix.required-certs != '' run: | for CERT_NAME in $(echo "${{ matrix.required-certs }}" | sed 's/,/ /g'); do CERT_FILE=$(mktemp --suffix .pfx) echo "Downloading cert $CERT_NAME into file $CERT_FILE" rm $CERT_FILE && \ az keyvault secret download --vault-name ${{ secrets.AZURE_KEYVAULT }} --name $CERT_NAME --encoding base64 --file $CERT_FILE echo 'Setting $CERT_NAME to' "$CERT_FILE" echo "$CERT_NAME=$CERT_FILE" >> $GITHUB_ENV done - name: Setup Terraform if: matrix.require-terraform == 'true' uses: hashicorp/setup-terraform@v2.0.3 - name: Set Cloudflare env vars if: matrix.require-cloudflare-credentials == 'true' run: | echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV - name: Set AWS env vars if: matrix.require-aws-credentials == 'true' run: | echo "AWS_ACCESS_KEY=${{ secrets.AWS_ACCESS_KEY }}" >> $GITHUB_ENV echo "AWS_SECRET_KEY=${{ secrets.AWS_SECRET_KEY }}" >> $GITHUB_ENV - name: Configure AWS Credentials if: matrix.require-aws-credentials == 'true' uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} aws-region: us-west-1 - name: Start MongoDB if: matrix.mongodb-version != '' uses: supercharge/mongodb-github-action@1.8.0 with: mongodb-version: ${{ matrix.mongodb-version }} mongodb-replica-set: test-rs - name: Set up Go uses: actions/setup-go@v4 with: go-version-file: 'go.mod' cache: 'false' - name: Install Node.js ${{ matrix.nodejs-version }} if: matrix.nodejs-version != '' uses: actions/setup-node@v3 with: node-version: ${{ matrix.nodejs-version }} - name: Start KinD uses: helm/kind-action@v1.5.0 if: matrix.require-kind == 'true' - name: Download Go dependencies run: | go mod download go install github.com/axw/gocov/gocov@${{ env.GOCOV_VER }} go install gotest.tools/gotestsum@${{ env.GOTESTSUM_VER }} - name: Run setup script if: matrix.setup-script != '' run: .github/scripts/components-scripts/${{ matrix.setup-script }} - name: Catch setup failures if: failure() run: | echo "CONFORMANCE_FAILURE=true" >> $GITHUB_ENV - name: Run tests continue-on-error: true run: | set -e KIND=$(echo ${{ matrix.component }} | cut -d. -f1) NAME=$(echo ${{ matrix.component }} | cut -d. -f2-) KIND_UPPER="$(tr '[:lower:]' '[:upper:]' <<< ${KIND:0:1})${KIND:1}" if [ "${KIND}" = "secretstores" ]; then KIND_UPPER=SecretStore fi echo "Running tests for Test${KIND_UPPER}Conformance/${KIND}/${NAME} ... " echo "Source Pacakge: " ${{ matrix.source-pkg }} set +e gotestsum --jsonfile ${{ env.TEST_OUTPUT_FILE_PREFIX }}_conformance.json \ --junitfile ${{ env.TEST_OUTPUT_FILE_PREFIX }}_conformance.xml --format standard-verbose -- \ -p 2 -count=1 -timeout=15m -tags=conftests ./tests/conformance --run="Test${KIND_UPPER}Conformance/${NAME}" -coverprofile=cover.out \ -covermode=set -coverpkg=${{ matrix.source-pkg }} status=$? echo "Completed tests for Test${KIND_UPPER}Conformance/${KIND}/${NAME} ... " if test $status -ne 0; then echo "Setting CONFORMANCE_FAILURE" echo "CONFORMANCE_FAILURE=true" >> $GITHUB_ENV fi set -e # Fail the step if we found no test to run if grep -q "warning: no tests to run" ${{ env.TEST_OUTPUT_FILE_PREFIX }}_conformance.json ; then echo "::error:: No test was found for component ${{ matrix.component }}" exit -1 fi - name: Delete downloaded up certs if: always() && matrix.required-certs != '' run: | for CERT_NAME in $(echo "${{ matrix.required-certs }}" | sed 's/,/ /g'); do CERT_FILE=$(printenv $CERT_NAME) echo "Cleaning up the certificate file $CERT_FILE..." rm $CERT_FILE || true done - name: Check conformance test passed continue-on-error: false run: | echo "CONFORMANCE_FAILURE=$CONFORMANCE_FAILURE" if [[ -v CONFORMANCE_FAILURE ]]; then exit 1 fi - name: Prepare test result info if: always() run: | mkdir -p tmp/result_files echo "Writing to tmp/result_files/${{ matrix.component }}.txt" if [[ "${{ env.CONFORMANCE_FAILURE }}" == "true" ]]; then echo "0" >> "tmp/result_files/${{ matrix.component }}.txt" else echo "1" >> "tmp/result_files/${{ matrix.component }}.txt" fi - name: Upload result files uses: actions/upload-artifact@v3 if: always() with: name: result_files path: tmp/result_files retention-days: 1 - name: Prepare coverage report file to upload if: github.event_name == 'schedule' run: | mkdir -p tmp/conf_code_cov cp cover.out tmp/conf_code_cov/${{ env.SOURCE_PATH_LINEAR }}.out - name: Upload coverage report file uses: actions/upload-artifact@v3 if: github.event_name == 'schedule' with: name: conf_code_cov path: tmp/conf_code_cov retention-days: 7 # Upload logs for test analytics to consume - name: Upload test results if: always() uses: actions/upload-artifact@main with: name: ${{ matrix.component }}_conformance_test path: ${{ env.TEST_OUTPUT_FILE_PREFIX }}_conformance.* - name: Run destroy script if: always() && matrix.destroy-script != '' run: .github/scripts/components-scripts/${{ matrix.destroy-script }} post_job: name: Post-completion runs-on: ubuntu-22.04 if: always() needs: - conformance - generate-matrix steps: - name: Parse repository_dispatch payload if: github.event_name == 'repository_dispatch' working-directory: ${{ github.workspace }} run: | if [ ${{ github.event.client_payload.command }} = "ok-to-test" ]; then echo "CHECKOUT_REF=${{ github.event.client_payload.pull_head_ref }}" >> $GITHUB_ENV echo "PR_NUMBER=${{ github.event.client_payload.issue.number }}" >> $GITHUB_ENV fi - name: Download test result artifact if: always() && env.PR_NUMBER != '' uses: actions/download-artifact@v3 continue-on-error: true id: testresults with: name: result_files path: tmp/result_files - name: Build message if: always() && env.PR_NUMBER != '' # Abusing of the github-script action to be able to write this in JS uses: actions/github-script@v6 with: script: | const allComponents = JSON.parse('${{ needs.generate-matrix.outputs.test-matrix }}') const basePath = '${{ steps.testresults.outputs.download-path }}' const testType = 'conformance' const fs = require('fs') const path = require('path') let message = `# Components ${testType} test 🔗 **[Link to Action run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})** Commit ref: ${{ env.CHECKOUT_REF }}` let allSuccess = true let allFound = true let notSuccess = [] let notFound = [] for (let i = 0; i < allComponents.length; i++) { let component = allComponents[i] if (!component) { continue } if (typeof component == 'object') { component = component.component } let found = false let success = false try { let read = fs.readFileSync(path.join(basePath, component + '.txt'), 'utf8') read = read.split('\n')[0] switch (read) { case '1': found = true success = true break case '0': found = true success = false } } catch (e) { // ignore errors, leave found = false } if (!found) { allFound = false notFound.push(component) } if (!success) { allSuccess = false notSuccess.push(component) } } if (allSuccess) { if (allFound) { message += '\n\n' + `# ✅ All ${testType} tests passed All tests have reported a successful status` + '\n\n' } else { message += '\n\n' + `# ⚠️ Some ${testType} tests did not report status Although there were no failures reported, some tests did not report a status:` + '\n\n' for (let i = 0; i < notFound.length; i++) { message += '- ' + notFound[i] + '\n' } message += '\n' } } else { message += '\n\n' + `# ❌ Some ${testType} tests failed These tests failed:` + '\n\n' for (let i = 0; i < notSuccess.length; i++) { message += '- ' + notSuccess[i] + '\n' } message += '\n' if (!allFound) { message += 'Additionally, some tests did not report a status:\n\n' for (let i = 0; i < notFound.length; i++) { message += '- ' + notFound[i] + '\n' } message += '\n' } } fs.writeFileSync('message.txt', message) - name: Replace PR comment if: env.PR_NUMBER != '' uses: artursouza/sticky-pull-request-comment@da9e86aa2a80e4ae3b854d251add33bd6baabcba with: header: ${{ github.run_id }} number: ${{ env.PR_NUMBER }} GITHUB_TOKEN: ${{ secrets.DAPR_BOT_TOKEN }} path: message.txt