tests #1670
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
| name: tests | |
| on: | |
| push: | |
| branches: | |
| - master | |
| pull_request: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: '0 3 * * *' | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| # Checkout must precede setup-go so its cache can be keyed on go.sum. | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| - name: Install Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: '1.23.x' | |
| - name: Install Terraform | |
| uses: hashicorp/setup-terraform@v4 | |
| with: | |
| terraform_version: 'latest' | |
| # Disable the wrapper: the acceptance-test driver must exec the real | |
| # terraform binary, not the wrapper script shadowing it on PATH. | |
| terraform_wrapper: false | |
| - name: Test | |
| env: | |
| # Skip terraform's checkpoint version-check call in every init the tests run. | |
| CHECKPOINT_DISABLE: "1" | |
| run: go test -timeout 10m ./... | |
| e2e_test: | |
| strategy: | |
| matrix: | |
| terraform-version: ["0.14", "1.0", "1.8", "latest"] | |
| config: [examples] | |
| fail-fast: false | |
| runs-on: ubuntu-latest | |
| steps: | |
| # Checkout must precede setup-go so its cache can be keyed on go.sum. | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| - name: Install Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: '1.23.x' | |
| - name: Install Terraform | |
| uses: hashicorp/setup-terraform@v4 | |
| with: | |
| terraform_version: ${{ matrix.terraform-version }} | |
| - name: Add resources via Terraform | |
| run: make terraform CONFIGURATION="${{ matrix.config }}" ARGS="apply --auto-approve --input=false" | |
| env: | |
| BETTERUPTIME_API_TOKEN: ${{ secrets.UPTIME_E2E_TEAM_TOKEN }} | |
| # A data-source lookup that finds nothing yields a null output - and Terraform | |
| # drops null outputs from state entirely, so also check every output declared | |
| # in the configuration is actually present. Empty strings and collections | |
| # (e.g. nobody on the default on-call calendar) hide a data problem too. | |
| - name: Verify no output is missing, null, or empty | |
| run: | | |
| make terraform CONFIGURATION="${{ matrix.config }}" ARGS="output -json > outputs.json" | |
| echo "Terraform outputs:" | |
| jq 'map_values(.value)' "${{ matrix.config }}/outputs.json" | |
| DECLARED="$(grep -hoE '^output "[^"]+"' "${{ matrix.config }}"/*.tf | sed -E 's/^output "([^"]+)"/\1/' | sort -u)" | |
| MISSING="$(comm -23 <(echo "$DECLARED") <(jq -r 'keys[]' "${{ matrix.config }}/outputs.json" | sort -u))" | |
| EMPTY="$(jq -r 'to_entries[] | select(.value.value == null or .value.value == "" or .value.value == [] or .value.value == {}) | .key' "${{ matrix.config }}/outputs.json")" | |
| if [ -n "$MISSING" ] || [ -n "$EMPTY" ]; then | |
| if [ -n "$MISSING" ]; then | |
| echo "Declared outputs missing a value:" | |
| echo "$MISSING" | |
| fi | |
| if [ -n "$EMPTY" ]; then | |
| echo "Outputs with null or empty values:" | |
| echo "$EMPTY" | |
| fi | |
| exit 1 | |
| fi | |
| echo "All $(echo "$DECLARED" | wc -l) declared outputs have values." | |
| env: | |
| BETTERUPTIME_API_TOKEN: ${{ secrets.UPTIME_E2E_TEAM_TOKEN }} | |
| - name: Plan resources via Terraform - must be empty | |
| run: | | |
| make terraform CONFIGURATION="${{ matrix.config }}" ARGS="plan --input=false --out=tfplan" | |
| make terraform CONFIGURATION="${{ matrix.config }}" ARGS="show --json tfplan > tfplan.json" | |
| CHANGES="$(jq "[.resource_changes[]? | select(.change.actions != [\"no-op\"])] | length" "${{ matrix.config }}/tfplan.json")" | |
| if [ "$CHANGES" == "0" ]; then | |
| echo "No planned changes detected after first apply. Success!" | |
| else | |
| echo "$CHANGES planned changes detected after first apply. Failure!" | |
| exit 1 | |
| fi | |
| env: | |
| BETTERUPTIME_API_TOKEN: ${{ secrets.UPTIME_E2E_TEAM_TOKEN }} | |
| - name: Destroy resources via Terraform | |
| if: always() | |
| run: make terraform CONFIGURATION="${{ matrix.config }}" ARGS="destroy --auto-approve --input=false" | |
| env: | |
| BETTERUPTIME_API_TOKEN: ${{ secrets.UPTIME_E2E_TEAM_TOKEN }} | |
| # Assembles every documented example into one configuration - the basic example's | |
| # scaffolding (provider/versions/variables/random) without its own main.tf/outputs.tf, | |
| # plus every resources/* and data-sources/* example - and runs it as a single | |
| # apply/plan/destroy, keeping the registry docs' snippets verified. | |
| e2e_combined: | |
| strategy: | |
| matrix: | |
| terraform-version: ["0.14", "1.0", "1.8", "latest"] | |
| fail-fast: false | |
| runs-on: ubuntu-latest | |
| steps: | |
| # Checkout must precede setup-go so its cache can be keyed on go.sum. | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| - name: Install Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: '1.23.x' | |
| - name: Install Terraform | |
| uses: hashicorp/setup-terraform@v4 | |
| with: | |
| terraform_version: ${{ matrix.terraform-version }} | |
| - name: Assemble combined examples | |
| run: | | |
| mkdir -p combined-e2e | |
| # Reuse the basic example's scaffolding (provider/versions/variables/random), | |
| # but not its own resources/outputs. | |
| for f in examples/*.tf; do | |
| case "$(basename "$f")" in main.tf | outputs.tf) continue ;; esac | |
| cp "$f" "combined-e2e/$(basename "$f")" | |
| done | |
| # Every resource and data-source example runs in the combined config. | |
| # Data sources look up pre-existing "My Existing ..." fixtures on the team. | |
| for f in examples/resources/*/*.tf; do | |
| dir="$(basename "$(dirname "$f")")" | |
| cp "$f" "combined-e2e/res__${dir}__$(basename "$f")" | |
| done | |
| for f in examples/data-sources/*/*.tf; do | |
| dir="$(basename "$(dirname "$f")")" | |
| cp "$f" "combined-e2e/ds__${dir}__$(basename "$f")" | |
| done | |
| # Invitation e-mails must be unique across the concurrent matrix jobs; the | |
| # docs keep clean addresses, the combined run suffixes them with the pet. | |
| sed -i -E 's/(alice|bob|dylan)@betterstack\.com/\1+${random_pet.unique.id}@betterstack.com/' combined-e2e/res__betteruptime_team_member__resource.tf | |
| - name: Add resources via Terraform | |
| run: make terraform CONFIGURATION="combined-e2e" ARGS="apply --auto-approve --input=false" | |
| env: | |
| BETTERUPTIME_API_TOKEN: ${{ secrets.UPTIME_E2E_TEAM_TOKEN }} | |
| # A data-source lookup that finds nothing yields a null output - and Terraform | |
| # drops null outputs from state entirely, so also check every output declared | |
| # in the configuration is actually present. Empty strings and collections | |
| # (e.g. nobody on the default on-call calendar) hide a data problem too. | |
| - name: Verify no output is missing, null, or empty | |
| run: | | |
| make terraform CONFIGURATION="combined-e2e" ARGS="output -json > outputs.json" | |
| echo "Terraform outputs:" | |
| jq 'map_values(.value)' "combined-e2e/outputs.json" | |
| DECLARED="$(grep -hoE '^output "[^"]+"' combined-e2e/*.tf | sed -E 's/^output "([^"]+)"/\1/' | sort -u)" | |
| MISSING="$(comm -23 <(echo "$DECLARED") <(jq -r 'keys[]' "combined-e2e/outputs.json" | sort -u))" | |
| EMPTY="$(jq -r 'to_entries[] | select(.value.value == null or .value.value == "" or .value.value == [] or .value.value == {}) | .key' "combined-e2e/outputs.json")" | |
| if [ -n "$MISSING" ] || [ -n "$EMPTY" ]; then | |
| if [ -n "$MISSING" ]; then | |
| echo "Declared outputs missing a value:" | |
| echo "$MISSING" | |
| fi | |
| if [ -n "$EMPTY" ]; then | |
| echo "Outputs with null or empty values:" | |
| echo "$EMPTY" | |
| fi | |
| exit 1 | |
| fi | |
| echo "All $(echo "$DECLARED" | wc -l) declared outputs have values." | |
| env: | |
| BETTERUPTIME_API_TOKEN: ${{ secrets.UPTIME_E2E_TEAM_TOKEN }} | |
| - name: Plan resources via Terraform - must be empty | |
| run: | | |
| make terraform CONFIGURATION="combined-e2e" ARGS="plan --input=false --out=tfplan" | |
| make terraform CONFIGURATION="combined-e2e" ARGS="show --json tfplan > tfplan.json" | |
| CHANGES="$(jq "[.resource_changes[]? | select(.change.actions != [\"no-op\"])] | length" "combined-e2e/tfplan.json")" | |
| if [ "$CHANGES" == "0" ]; then | |
| echo "No planned changes detected after first apply. Success!" | |
| else | |
| echo "$CHANGES planned changes detected after first apply. Failure!" | |
| exit 1 | |
| fi | |
| env: | |
| BETTERUPTIME_API_TOKEN: ${{ secrets.UPTIME_E2E_TEAM_TOKEN }} | |
| - name: Destroy resources via Terraform | |
| if: always() | |
| run: make terraform CONFIGURATION="combined-e2e" ARGS="destroy --auto-approve --input=false" | |
| env: | |
| BETTERUPTIME_API_TOKEN: ${{ secrets.UPTIME_E2E_TEAM_TOKEN }} | |
| notify-linear: | |
| if: ${{ failure() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.event_name == 'schedule') }} | |
| needs: [e2e_test, e2e_combined] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| actions: read | |
| steps: | |
| - name: "Gather failure context" | |
| id: ctx | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| FAILED=$(gh api \ | |
| "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}/jobs" \ | |
| --paginate \ | |
| --jq '[.jobs[] | |
| | select(.conclusion == "failure") | |
| | "- **\(.name)** — failed at _\([.steps[] | select(.conclusion == "failure") | .name] | join(", "))_"] | |
| | join("\n")') | |
| if [ -z "$FAILED" ]; then | |
| FAILED="_(no failed jobs reported by the API yet)_" | |
| fi | |
| { | |
| echo "failed_jobs<<CTXEOF" | |
| echo "$FAILED" | |
| echo "CTXEOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: "Create Linear issue" | |
| env: | |
| LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }} | |
| LINEAR_TEAM_ID: ${{ secrets.LINEAR_TEAM_ID }} | |
| LINEAR_STATUS_ID: ${{ secrets.LINEAR_STATUS_PLAN_ID }} | |
| LINEAR_PROJECT_ID: ${{ secrets.LINEAR_PROJECT_ID }} | |
| LINEAR_ASSIGNEE_ID: ${{ secrets.LINEAR_ASSIGNEE_ID }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| RUN_ATTEMPT: ${{ github.run_attempt }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| SHA: ${{ github.sha }} | |
| COMMIT_MESSAGE: ${{ github.event.head_commit.message }} | |
| FAILED_JOBS: ${{ steps.ctx.outputs.failed_jobs }} | |
| run: | | |
| SHORT_SHA="${SHA:0:7}" | |
| # head_commit is only populated on push events; skip the line otherwise | |
| COMMIT_LINE="**Commit:** \`$SHORT_SHA\`" | |
| if [ -n "$COMMIT_MESSAGE" ]; then | |
| COMMIT_SUBJECT="$(printf '%s' "$COMMIT_MESSAGE" | head -n1)" | |
| COMMIT_LINE="**Commit:** \`$SHORT_SHA\` — $COMMIT_SUBJECT" | |
| fi | |
| ATTEMPT_NOTE="" | |
| if [ "$RUN_ATTEMPT" != "1" ]; then | |
| ATTEMPT_NOTE=" (attempt #$RUN_ATTEMPT)" | |
| fi | |
| TITLE="E2E failure: ${{ github.workflow }} (${{ github.repository }})" | |
| DESCRIPTION=$(cat <<EOF | |
| **${{ github.workflow }}** failed on \`${{ github.ref_name }}\` in \`${{ github.repository }}\` via \`$EVENT_NAME\`$ATTEMPT_NOTE | |
| **Failing jobs:** | |
| $FAILED_JOBS | |
| $COMMIT_LINE | |
| [View failing run]($RUN_URL) | |
| EOF | |
| ) | |
| PAYLOAD=$(jq -n \ | |
| --arg query 'mutation IssueCreate($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { identifier url } } }' \ | |
| --arg title "$TITLE" \ | |
| --arg description "$DESCRIPTION" \ | |
| --arg teamId "$LINEAR_TEAM_ID" \ | |
| --arg assigneeId "$LINEAR_ASSIGNEE_ID" \ | |
| --arg stateId "$LINEAR_STATUS_ID" \ | |
| --arg projectId "$LINEAR_PROJECT_ID" \ | |
| '{ | |
| query: $query, | |
| variables: { | |
| input: { | |
| title: $title, | |
| description: $description, | |
| teamId: $teamId, | |
| assigneeId: $assigneeId, | |
| stateId: $stateId, | |
| projectId: $projectId, | |
| priority: 2 | |
| } | |
| } | |
| }') | |
| curl -sSf -X POST https://api.linear.app/graphql \ | |
| -H "Authorization: $LINEAR_API_KEY" \ | |
| -H 'Content-Type: application/json' \ | |
| -d "$PAYLOAD" |