diff --git a/.github/workflows/add-waiting-response-on-fail.yaml b/.github/workflows/add-waiting-response-on-fail.yaml
index 6bc57c24c952..762b7e3e1564 100644
--- a/.github/workflows/add-waiting-response-on-fail.yaml
+++ b/.github/workflows/add-waiting-response-on-fail.yaml
@@ -3,7 +3,7 @@ name: Add waiting-response on failures
on:
workflow_run:
- workflows: ['Check for new usages of deprecated functionality', 'GoLang Linting', 'Website Linting', 'Generation Check', 'Validate Examples', 'Unit Tests', 'Terraform Schema Linting', '32 Bit Build', 'Provider Tests', 'Vendor Dependencies Check']
+ workflows: ['Check for new usages of deprecated functionality', 'GoLang Linting', 'Website Linting', 'Generation Check', 'Validate Examples', 'Unit Tests', 'Terraform Schema Linting', 'Provider Tests', 'Vendor Dependencies Check']
types: [completed]
permissions:
diff --git a/.github/workflows/breaking-change-detection.yaml b/.github/workflows/breaking-change-detection.yaml
index 6d2e45a93a33..5cd0c965c10f 100644
--- a/.github/workflows/breaking-change-detection.yaml
+++ b/.github/workflows/breaking-change-detection.yaml
@@ -27,3 +27,12 @@ jobs:
with:
go-version-file: ./.go-version
- run: bash ./scripts/run-breaking-change-detection.sh
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::Breaking Schema Changes detected."
+ echo ""
+ echo "Your changes contain breaking schema changes (e.g. removing or renaming"
+ echo "a property, changing a property type, or making an optional property required)."
+ echo "Breaking changes must be gated behind the appropriate feature flag (e.g. features.FivePointOh())."
+ echo "Please review the breaking changes guide: contributing/topics/guide-breaking-changes.md"
diff --git a/.github/workflows/close-failing-ci-prs.yaml b/.github/workflows/close-failing-ci-prs.yaml
new file mode 100644
index 000000000000..b828679edf39
--- /dev/null
+++ b/.github/workflows/close-failing-ci-prs.yaml
@@ -0,0 +1,19 @@
+name: 'Close PRs With Failing CI'
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 6 * * MON,WED,FRI'
+
+permissions:
+ issues: write
+ pull-requests: write
+ checks: read
+ statuses: read
+
+jobs:
+ close-failing-ci-prs:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - name: Process PRs with failing CI
+ run: ./scripts/close-failing-ci-prs.sh -o "${{ github.repository_owner }}" -r "${{ github.event.repository.name }}" -t "${{ secrets.GITHUB_TOKEN }}" -l
diff --git a/.github/workflows/comment-failure.yaml b/.github/workflows/comment-failure.yaml
index f12efdeb385c..e3d45e9a34ee 100644
--- a/.github/workflows/comment-failure.yaml
+++ b/.github/workflows/comment-failure.yaml
@@ -19,9 +19,31 @@ jobs:
with:
result-encoding: string
script: |
+ const workflowName = '${{ github.workflow }}';
+
+ const guidance = {
+ 'Vendor Dependencies Check': 'Do not modify files in the `vendor/` directory directly. Instead, update dependencies in `go.mod` and run `go mod vendor` to sync the vendor directory. Then run `make depscheck` locally to verify.',
+ 'Website Linting': 'Run `make website-lint` and `make document-validate` locally to check your documentation files under `website/` for formatting or validation issues.',
+ 'Generation Check': 'Run `make generate` locally to regenerate any auto-generated code, then commit the resulting changes.',
+ 'GoLang Linting': 'Run the Go linter locally with `golangci-lint run -v ./internal/...` and fix any reported issues.',
+ 'Terraform Schema Linting': 'Run `make tflint` locally and fix any Terraform schema issues in your resource or data source definitions.',
+ 'Unit Tests': 'Run `make test` locally to reproduce and fix the failing unit tests.',
+ 'ShellCheck Scripts': 'Run `make shellcheck` locally to check shell scripts for issues and fix any warnings or errors.',
+ 'Validate Examples': 'Run `make validate-examples` locally to check that your example Terraform configurations under `examples/` are valid.',
+
+ };
+
+ let body = `Build failure: ${workflowName}\n\nThis pull request contains a build failure in the ${workflowName} check which needs to be addressed [here](${{ env.gha_url }}).`;
+
+ if (guidance[workflowName]) {
+ body += `\n\nHow to fix: ${guidance[workflowName]}`;
+ }
+
+ body += `\n\nFor more information, please refer to our [Contributing Guide](https://github.com/${{ github.repository }}/blob/main/contributing/README.md).`;
+
github.rest.issues.createComment({
- issue_number: ${{ github.event.number }},
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: 'Build failure \n\n This pull request contains a build failure which needs addressed [here](${{ env.gha_url}}) .'
- })
+ issue_number: ${{ github.event.number }},
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: body
+ })
diff --git a/.github/workflows/depscheck.yaml b/.github/workflows/depscheck.yaml
index e0a93a2144e0..8852870af942 100644
--- a/.github/workflows/depscheck.yaml
+++ b/.github/workflows/depscheck.yaml
@@ -23,6 +23,14 @@ jobs:
go-version-file: ./.go-version
- run: bash scripts/gogetcookie.sh
- run: make depscheck
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::Vendor Dependencies Check failed."
+ echo ""
+ echo "Do not modify files in the vendor/ directory directly."
+ echo "Instead, update dependencies in go.mod and run 'go mod tidy && go mod vendor' to sync the vendor directory."
+ echo "Then run 'make depscheck' locally to verify before pushing."
save-artifacts-on-fail:
needs: depscheck
if: ${{ failure() }}
diff --git a/.github/workflows/gencheck.yaml b/.github/workflows/gencheck.yaml
index d7574df077ec..0668e04bc889 100644
--- a/.github/workflows/gencheck.yaml
+++ b/.github/workflows/gencheck.yaml
@@ -26,6 +26,13 @@ jobs:
go-version-file: ./.go-version
- run: bash scripts/gogetcookie.sh
- run: make gencheck
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::Generation Check failed."
+ echo ""
+ echo "Run 'make generate' locally to regenerate auto-generated code, then commit the changes."
+ echo "This check ensures that generated files are up to date with their source definitions."
save-artifacts-on-fail:
needs: gencheck
if: ${{ failure() }}
diff --git a/.github/workflows/golint.yaml b/.github/workflows/golint.yaml
index 4286c8173a0b..db241cd630bf 100644
--- a/.github/workflows/golint.yaml
+++ b/.github/workflows/golint.yaml
@@ -29,6 +29,16 @@ jobs:
with:
version: 'v2.4.0'
args: -v ./internal/...
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::GoLang Linting failed."
+ echo ""
+ echo "Run the Go linter locally: golangci-lint run -v ./internal/..."
+ echo "Fix any reported issues before pushing. Common issues include:"
+ echo " - Unused variables or imports"
+ echo " - Error return values not checked"
+ echo " - Formatting issues (run 'gofmt -w .')"
save-artifacts-on-fail:
needs: golint
if: ${{ failure() }}
diff --git a/.github/workflows/gradually-deprecated.yaml b/.github/workflows/gradually-deprecated.yaml
index de58c5901dee..b2f9aaabad3d 100644
--- a/.github/workflows/gradually-deprecated.yaml
+++ b/.github/workflows/gradually-deprecated.yaml
@@ -24,6 +24,14 @@ jobs:
with:
go-version-file: ./.go-version
- run: bash ./scripts/run-gradually-deprecated.sh
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::New usage of deprecated functionality detected."
+ echo ""
+ echo "Your changes introduce new usages of deprecated functions or patterns."
+ echo "Please use the recommended replacement instead. Check the output above"
+ echo "for details on which deprecated items were referenced."
save-artifacts-on-fail:
needs: test
if: ${{ failure() }}
diff --git a/.github/workflows/label-ci-status.yaml b/.github/workflows/label-ci-status.yaml
new file mode 100644
index 000000000000..18c55cdb74c3
--- /dev/null
+++ b/.github/workflows/label-ci-status.yaml
@@ -0,0 +1,53 @@
+name: 'Label CI Status on PRs'
+on:
+ check_suite:
+ types: [completed]
+
+permissions:
+ pull-requests: write
+
+jobs:
+ label-ci-status:
+ runs-on: ubuntu-latest
+ # Only run for PRs, not pushes to branches
+ if: github.event.check_suite.pull_requests[0] != null
+ steps:
+ - name: Add or remove ci-failed label
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
+ with:
+ script: |
+ const pullRequests = context.payload.check_suite.pull_requests;
+ const conclusion = context.payload.check_suite.conclusion;
+ const label = 'ci-failed';
+
+ for (const pr of pullRequests) {
+ const prNumber = pr.number;
+
+ // Get current labels
+ const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ });
+ const hasLabel = currentLabels.some(l => l.name === label);
+
+ if (conclusion === 'failure' && !hasLabel) {
+ console.log(`PR #${prNumber}: CI failed → adding '${label}' label`);
+ await github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ labels: [label],
+ });
+ } else if (conclusion === 'success' && hasLabel) {
+ console.log(`PR #${prNumber}: CI passed → removing '${label}' label`);
+ await github.rest.issues.removeLabel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ name: label,
+ });
+ } else {
+ console.log(`PR #${prNumber}: conclusion=${conclusion}, hasLabel=${hasLabel} → no action`);
+ }
+ }
diff --git a/.github/workflows/preview-api-version-linter.yaml b/.github/workflows/preview-api-version-linter.yaml
index 909bcb25167a..192866e82a23 100644
--- a/.github/workflows/preview-api-version-linter.yaml
+++ b/.github/workflows/preview-api-version-linter.yaml
@@ -22,6 +22,14 @@ jobs:
with:
go-version-file: ./.go-version
- run: go run internal/tools/preview-api-version-linter/main.go
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::Preview API Version Linter failed."
+ echo ""
+ echo "Your changes reference Azure ARM API versions that are in preview."
+ echo "Preview API versions should not be used unless explicitly required and approved."
+ echo "Please update the API version references to use GA (stable) versions."
comment-on-fail:
needs: preview-api-version-linter
if: ${{ failure() }}
diff --git a/.github/workflows/shellcheck.yaml b/.github/workflows/shellcheck.yaml
index 8f4f72330af8..a30d0d016b09 100644
--- a/.github/workflows/shellcheck.yaml
+++ b/.github/workflows/shellcheck.yaml
@@ -23,6 +23,14 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run ShellCheck
run: make shellcheck
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::ShellCheck failed."
+ echo ""
+ echo "Run 'make shellcheck' locally to check shell scripts for issues."
+ echo "ShellCheck identifies common shell scripting pitfalls and bugs."
+ echo "See https://www.shellcheck.net/wiki/ for explanations of each warning code."
save-artifacts-on-fail:
needs: shellcheck
if: ${{ failure() }}
diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml
index 695022f77e56..2e9d0abf7846 100644
--- a/.github/workflows/static-analysis.yaml
+++ b/.github/workflows/static-analysis.yaml
@@ -26,3 +26,11 @@ jobs:
with:
go-version-file: ./.go-version
- run: bash ./scripts/run-static-analysis.sh
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::Static Analysis failed."
+ echo ""
+ echo "Run 'bash ./scripts/run-static-analysis.sh' locally to reproduce."
+ echo "This check looks for common code patterns that may indicate bugs or"
+ echo "deviations from project conventions."
diff --git a/.github/workflows/tflint.yaml b/.github/workflows/tflint.yaml
index 89f62ecd432f..b6cf8399bb5d 100644
--- a/.github/workflows/tflint.yaml
+++ b/.github/workflows/tflint.yaml
@@ -28,6 +28,15 @@ jobs:
- run: bash scripts/gogetcookie.sh
- run: make tools
- run: make tflint
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::Terraform Schema Linting failed."
+ echo ""
+ echo "Run 'make tflint' locally to reproduce and fix the errors."
+ echo "This check validates Terraform schema definitions in your resource and data source code."
+ echo "Common issues include: missing descriptions, incorrect type definitions,"
+ echo "or schema validation rules that don't match the API."
save-artifacts-on-fail:
needs: tflint
if: ${{ failure() }}
diff --git a/.github/workflows/thirty-two-bit.yaml b/.github/workflows/thirty-two-bit.yaml
deleted file mode 100644
index d432a82bd1c5..000000000000
--- a/.github/workflows/thirty-two-bit.yaml
+++ /dev/null
@@ -1,37 +0,0 @@
----
-name: 32 Bit Build
-
-permissions:
- contents: read
- pull-requests: write
-
-on:
- pull_request:
- types: ['opened', 'synchronize']
- paths:
- - '.github/workflows/thirty-two-bit.yaml'
- - 'vendor/**'
- - '**.go'
-
-concurrency:
- group: 'thirtytwo-${{ github.head_ref }}'
- cancel-in-progress: true
-
-jobs:
- compatibility-32bit-test:
- runs-on: custom-linux-small
- steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
- with:
- go-version-file: ./.go-version
- - run: bash scripts/gogetcookie.sh
- - run: GOARCH=386 GOOS=linux go build -o 32bitbuild .
- save-artifacts-on-fail:
- needs: compatibility-32bit-test
- if: ${{ failure() }}
- uses: ./.github/workflows/save-artifacts.yaml
- comment-on-fail:
- needs: compatibility-32bit-test
- if: ${{ failure() }}
- uses: ./.github/workflows/comment-failure.yaml
diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml
index 8d4a5efa9b0e..a86979ba1ffb 100644
--- a/.github/workflows/unit-test.yaml
+++ b/.github/workflows/unit-test.yaml
@@ -67,6 +67,15 @@ jobs:
-run='^Test[^A]|^TestA[^c]|^TestAc[^c]' \
./...
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::Unit Tests failed."
+ echo ""
+ echo "Run 'make test' locally to reproduce and fix the failing tests."
+ echo "If you have added a new resource or data source, ensure you have added"
+ echo "the corresponding unit tests. Check the test output above for details"
+ echo "on which tests failed and why."
save-artifacts-on-fail:
needs: test
if: ${{ failure() }}
diff --git a/.github/workflows/validate-examples.yaml b/.github/workflows/validate-examples.yaml
index 21e940f4214b..2acfe6e5abbb 100644
--- a/.github/workflows/validate-examples.yaml
+++ b/.github/workflows/validate-examples.yaml
@@ -27,6 +27,14 @@ jobs:
- run: bash scripts/gogetcookie.sh
- run: make tools
- run: make validate-examples
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::Example Validation failed."
+ echo ""
+ echo "Run 'make validate-examples' locally to check that your example"
+ echo "Terraform configurations under examples/ are valid."
+ echo "Ensure all examples use valid HCL syntax and reference existing resources."
save-artifacts-on-fail:
needs: website-lint
if: ${{ failure() }}
diff --git a/.github/workflows/website-lint.yaml b/.github/workflows/website-lint.yaml
index 15eaa8dcfe8a..94455aaba443 100644
--- a/.github/workflows/website-lint.yaml
+++ b/.github/workflows/website-lint.yaml
@@ -25,6 +25,15 @@ jobs:
- run: make tools
- run: make website-lint
- run: make document-validate
+ - name: Guidance on failure
+ if: failure()
+ run: |
+ echo "::error::Website Linting failed."
+ echo ""
+ echo "Run 'make website-lint' and 'make document-validate' locally."
+ echo "Check your documentation files under website/ for formatting or validation issues."
+ echo "Common issues include: missing required frontmatter fields, incorrect markdown formatting,"
+ echo "or mismatched resource documentation with the schema definition."
save-artifacts-on-fail:
needs: website-lint
if: ${{ failure() }}
diff --git a/GNUmakefile b/GNUmakefile
index fb2eea920910..09b0141c9490 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -57,7 +57,8 @@ lint:
shellcheck:
@command -v shellcheck >/dev/null || (echo "shellcheck not installed. Install via: brew install shellcheck (macOS) or apt install shellcheck (Linux)" && exit 1)
@echo "==> Checking shell scripts with shellcheck..."
- @shellcheck scripts/*.sh
+ @shellcheck scripts/*.sh || \
+ (echo; echo "ShellCheck found issues in shell scripts."; echo "Review the errors above and fix them. See https://www.shellcheck.net/ for detailed explanations of each rule."; exit 1)
depscheck:
@echo "==> Checking dependencies.."
@@ -65,16 +66,16 @@ depscheck:
@echo "==> Checking source code with go mod tidy..."
@go mod tidy
@git diff --exit-code -- go.mod go.sum || \
- (echo; echo "Unexpected difference in go.mod/go.sum files. Run 'go mod tidy' command or revert any go.mod/go.sum changes and commit."; exit 1)
+ (echo; echo "Unexpected difference in go.mod/go.sum files. Run 'go mod tidy' command or revert any go.mod/go.sum changes and commit."; echo "Do not modify files in the vendor/ directory directly."; exit 1)
@echo "==> Checking source code with go mod vendor..."
@go mod vendor
@git diff --compact-summary --exit-code -- vendor || \
- (echo; echo "Unexpected difference in vendor/ directory. Run 'go mod vendor' command or revert any go.mod/go.sum/vendor changes and commit."; exit 1)
+ (echo; echo "Unexpected difference in vendor/ directory. Run 'go mod vendor' command or revert any go.mod/go.sum/vendor changes and commit."; echo "Do not modify files in the vendor/ directory directly."; exit 1)
gencheck: generate
@echo "==> Comparing generated code to committed code..."
@git diff --compact-summary --exit-code -- ./ || \
- (echo; echo "Unexpected difference in generated code. Run 'make generate' to update the generated code and commit."; exit 1)
+ (echo; echo "Unexpected difference in generated code. Run 'make generate' to update the generated code and commit."; echo "If you added or modified a resource, ensure 'go generate' directives are up to date."; exit 1)
tflint:
./scripts/run-tflint.sh
@@ -118,13 +119,16 @@ website-lint:
@echo "==> Checking documentation for .html.markdown extension present"
@if ! find website/docs -type f -not -name "*.html.markdown" -print -exec false {} +; then \
echo "ERROR: file extension should be .html.markdown"; \
+ echo "All documentation files must use the .html.markdown extension."; \
exit 1; \
fi
@echo "==> Checking documentation spelling..."
- @misspell -error -source=text -i hdinsight,exportfs website/
+ @misspell -error -source=text -i hdinsight,exportfs website/ || \
+ (echo; echo "Spelling errors found in documentation. Install misspell: go install github.com/client9/misspell/cmd/misspell@latest"; exit 1)
@echo "==> Checking documentation for errors..."
@tfproviderdocs check -provider-name=azurerm -require-resource-subcategory \
- -allowed-resource-subcategories-file website/allowed-subcategories
+ -allowed-resource-subcategories-file website/allowed-subcategories || \
+ (echo; echo "Documentation validation failed. Check that your docs follow the provider documentation format."; echo "See: contributing/topics/guide-new-resource.md for documentation requirements."; exit 1)
@sh -c "'$(CURDIR)/scripts/terrafmt-website.sh'"
website:
diff --git a/scripts/close-failing-ci-prs.sh b/scripts/close-failing-ci-prs.sh
new file mode 100755
index 000000000000..90a06557db35
--- /dev/null
+++ b/scripts/close-failing-ci-prs.sh
@@ -0,0 +1,417 @@
+#!/usr/bin/env bash
+# Copyright IBM Corp. 2014, 2025
+# SPDX-License-Identifier: MPL-2.0
+
+# shellcheck disable=SC2086
+
+# Warns and closes PRs that have had failing CI for an extended period.
+# Checks CI status directly via the GitHub API (does not depend on labels).
+#
+# - After 7 days of failing CI: leaves a warning comment
+# - After 14 days of failing CI: closes the PR with a polite message
+# - PRs with "ci-ignore-failure" label are skipped
+
+set -euo pipefail
+
+DRY_RUN=true
+WARN_DAYS=7
+CLOSE_DAYS=14
+IGNORE_LABEL="ci-ignore-failure"
+WARNING_MARKER=""
+
+# Only these CI checks are considered when deciding whether to warn/close.
+# Process workflows (label bots, triage, comment actions, etc.) are excluded
+# so they cannot accidentally trigger PR closures.
+MONITORED_CHECKS=(
+ "detect" # breaking-change-detection + static-analysis job name
+ "depscheck" # Vendor Dependencies Check
+ "gencheck" # Generation Check
+ "golint" # GoLang Linting
+ "test" # Unit Tests + gradually-deprecated job name
+ "preview-api-version-linter" # Preview ARM API Version Linter
+ "shellcheck" # ShellCheck Scripts
+ "tflint" # Terraform Schema Linting
+ "website-lint" # Website Linting + Validate Examples job name
+ "provider-tests" # Provider Tests
+)
+
+# Returns 0 (true) if the check name is in MONITORED_CHECKS, 1 otherwise.
+is_monitored_check() {
+ local name=$1
+ for monitored in "${MONITORED_CHECKS[@]}"; do
+ if [[ "$name" == "$monitored" ]]; then
+ return 0
+ fi
+ done
+ return 1
+}
+
+while getopts o:r:t:l flag
+do
+ case "${flag}" in
+ o) owner=${OPTARG};;
+ r) repo=${OPTARG};;
+ t) token=${OPTARG};;
+ l) DRY_RUN=false;;
+ *) echo "Usage: $0 -o owner -r repo [-t token] [-l]"; exit 1;;
+ esac
+done
+
+# Use token from env if not provided via flag
+token="${token:-${GH_TOKEN:-${GITHUB_TOKEN:-}}}"
+if [[ -z "$token" ]]; then
+ echo "Error: No token provided. Use -t flag or set GH_TOKEN/GITHUB_TOKEN env var."
+ exit 1
+fi
+
+API_BASE="https://api.github.com/repos/${owner}/${repo}"
+
+echo "=== Close PRs With Failing CI ==="
+echo "Repository: ${owner}/${repo}"
+echo "Warn after: ${WARN_DAYS} days of failing CI"
+echo "Close after: ${CLOSE_DAYS} days of failing CI"
+echo "Ignore label: ${IGNORE_LABEL}"
+if [[ "$DRY_RUN" == "true" ]]; then
+ echo "Mode: DRY RUN (no changes will be made)"
+else
+ echo "Mode: LIVE"
+fi
+echo ""
+
+# Fetch all open PRs
+echo "Fetching open PRs..."
+all_prs=()
+page=1
+while :; do
+ prs_json=$(curl -s -L \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer $token" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "${API_BASE}/pulls?state=open&per_page=100&page=${page}")
+
+ count=$(echo "$prs_json" | jq length)
+ if [[ "$count" -eq 0 ]]; then
+ break
+ fi
+
+ while IFS= read -r pr; do
+ all_prs+=("$pr")
+ done < <(echo "$prs_json" | jq -c '.[]')
+
+ page=$((page + 1))
+done
+
+total_prs=${#all_prs[@]}
+echo "Found ${total_prs} open PR(s)"
+echo ""
+
+# Check CI status for a commit SHA.
+# Outputs "status|timestamp|failed_checks" where:
+# status = "failure" or "ok"
+# timestamp = earliest failure time (or PR updated_at as fallback)
+# failed_checks = comma-separated list of failed check/status names
+check_ci_status() {
+ local sha=$1
+ local pr_updated_at=$2
+
+ local has_failure=false
+ local earliest_failure=""
+ local failed_names=""
+
+ # Check combined commit status (legacy status API)
+ local status_json
+ status_json=$(curl -s -L \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer $token" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "${API_BASE}/commits/${sha}/status")
+
+ # Collect failure timestamps and names from legacy statuses, filtered to monitored checks
+ local failed_statuses
+ failed_statuses=$(echo "$status_json" | jq -r '.statuses[] | select(.state == "failure" or .state == "error") | .context' 2>/dev/null || true)
+ while IFS= read -r ctx; do
+ [[ -z "$ctx" ]] && continue
+ is_monitored_check "$ctx" || continue
+ has_failure=true
+ local ts
+ ts=$(echo "$status_json" | jq -r --arg c "$ctx" '[.statuses[] | select(.context == $c) | .updated_at] | first // empty')
+ if [[ -n "$ts" ]]; then
+ if [[ -z "$earliest_failure" ]] || [[ "$ts" < "$earliest_failure" ]]; then
+ earliest_failure="$ts"
+ fi
+ fi
+ if [[ -n "$failed_names" ]]; then
+ failed_names="${failed_names},${ctx}"
+ else
+ failed_names="$ctx"
+ fi
+ done <<< "$failed_statuses"
+
+ # Check check runs (checks API) with pagination, filtered to monitored checks
+ local check_page=1
+ while :; do
+ local check_runs_json
+ check_runs_json=$(curl -s -L \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer $token" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "${API_BASE}/commits/${sha}/check-runs?per_page=100&page=${check_page}")
+
+ local page_count
+ page_count=$(echo "$check_runs_json" | jq '.check_runs | length')
+ if [[ "$page_count" -eq 0 ]]; then
+ break
+ fi
+
+ # Iterate failed/cancelled check runs — only consider monitored ones
+ local failed_run_names
+ failed_run_names=$(echo "$check_runs_json" | jq -r '.check_runs[] | select(.conclusion == "failure" or .conclusion == "cancelled") | .name')
+ while IFS= read -r run_name; do
+ [[ -z "$run_name" ]] && continue
+ is_monitored_check "$run_name" || continue
+ has_failure=true
+
+ local run_time
+ run_time=$(echo "$check_runs_json" | jq -r --arg n "$run_name" '[.check_runs[] | select(.name == $n and (.conclusion == "failure" or .conclusion == "cancelled")) | .completed_at // empty] | map(select(. != "")) | first // empty')
+ if [[ -n "$run_time" ]]; then
+ if [[ -z "$earliest_failure" ]] || [[ "$run_time" < "$earliest_failure" ]]; then
+ earliest_failure="$run_time"
+ fi
+ fi
+
+ if [[ -n "$failed_names" ]]; then
+ failed_names="${failed_names},${run_name}"
+ else
+ failed_names="$run_name"
+ fi
+ done <<< "$failed_run_names"
+
+ if [[ "$page_count" -lt 100 ]]; then
+ break
+ fi
+ check_page=$((check_page + 1))
+ done
+
+ if [[ "$has_failure" == "true" ]]; then
+ echo "failure|${earliest_failure:-$pr_updated_at}|${failed_names}"
+ else
+ echo "ok||"
+ fi
+}
+
+# Map a failed check name to actionable guidance
+get_check_guidance() {
+ local check_name=$1
+ case "$check_name" in
+ depscheck|"Vendor Dependencies Check")
+ echo "Run \`make depscheck\`. Do not modify files in the \`vendor/\` directory directly - instead update dependencies in \`go.mod\` and run \`go mod vendor\`."
+ ;;
+ website-lint|"Website Linting")
+ echo "Run \`make website-lint\` and \`make document-validate\` locally. Check your documentation files under \`website/\` for formatting issues."
+ ;;
+ gencheck|"Generation Check")
+ echo "Run \`make generate\` to regenerate any auto-generated code, then commit the changes."
+ ;;
+ golint|"GoLang Linting")
+ echo "Run the Go linter locally with \`golangci-lint run ./internal/...\` and fix any reported issues."
+ ;;
+ tflint|"Terraform Schema Linting")
+ echo "Run \`make tflint\` locally and fix any Terraform schema issues in your resource/data source definitions."
+ ;;
+ detect|"Breaking Schema Changes")
+ echo "Your changes contain breaking schema changes. Please review the [breaking changes guide](contributing/topics/guide-breaking-changes.md) and ensure any breaking changes are behind the appropriate feature flag."
+ ;;
+ test|"Unit Tests")
+ echo "Run \`make test\` locally to reproduce and fix the failing unit tests."
+ ;;
+ "Static Analysis")
+ echo "Run \`bash ./scripts/run-static-analysis.sh\` locally and fix any reported issues."
+ ;;
+ shellcheck|"ShellCheck Scripts")
+ echo "Run \`make shellcheck\` to check shell scripts for issues."
+ ;;
+ "Validate Examples")
+ echo "Run \`make validate-examples\` to check that your example configurations are valid."
+ ;;
+ "Preview API Version Linter")
+ echo "Check that any API version references are not using preview versions unless explicitly required."
+ ;;
+ *)
+ echo "Check the CI logs for details on this failure."
+ ;;
+ esac
+}
+
+# Build a guidance section from a comma-separated list of failed check names
+build_guidance() {
+ local failed_checks=$1
+ if [[ -z "$failed_checks" ]]; then
+ echo ""
+ return
+ fi
+
+ local guidance="\\n\\n**Failing checks and how to fix them:**\\n"
+ local seen=""
+
+ IFS=',' read -ra checks <<< "$failed_checks"
+ for check in "${checks[@]}"; do
+ # Deduplicate
+ if echo "$seen" | grep -qF "|${check}|"; then
+ continue
+ fi
+ seen="${seen}|${check}|"
+
+ local fix
+ fix=$(get_check_guidance "$check")
+ guidance="${guidance}\\n- **${check}**: ${fix}"
+ done
+
+ echo "$guidance"
+}
+
+# Check if we already left a warning comment (uses a hidden HTML marker)
+has_warning_comment() {
+ local pr_number=$1
+
+ local comments_json
+ comments_json=$(curl -s -L \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer $token" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "${API_BASE}/issues/${pr_number}/comments?per_page=100")
+
+ echo "$comments_json" | jq -r '[.[] | select(.body | contains("'"${WARNING_MARKER}"'"))] | length'
+}
+
+# Process each PR
+warn_count=0
+close_count=0
+skip_count=0
+failing_count=0
+
+for pr in "${all_prs[@]}"; do
+ draft=$(echo "$pr" | jq -r '.draft')
+
+ # Skip draft PRs
+ if [[ "$draft" == "true" ]]; then
+ continue
+ fi
+
+ pr_number=$(echo "$pr" | jq -r '.number')
+ pr_title=$(echo "$pr" | jq -r '.title')
+ head_sha=$(echo "$pr" | jq -r '.head.sha')
+ updated_at=$(echo "$pr" | jq -r '.updated_at')
+ labels=$(echo "$pr" | jq -r '.labels[].name' 2>/dev/null || echo "")
+ pr_author=$(echo "$pr" | jq -r '.user.login')
+
+ # Skip PRs with ignore label
+ if echo "$labels" | grep -q "^${IGNORE_LABEL}$"; then
+ continue
+ fi
+
+ # Check CI status - returns "status|timestamp|failed_checks"
+ ci_result=$(check_ci_status "$head_sha" "$updated_at")
+ ci_status=$(echo "$ci_result" | cut -d'|' -f1)
+ ci_failed_since=$(echo "$ci_result" | cut -d'|' -f2)
+ ci_failed_checks=$(echo "$ci_result" | cut -d'|' -f3)
+
+ if [[ "$ci_status" != "failure" ]]; then
+ continue
+ fi
+
+ failing_count=$((failing_count + 1))
+ echo "PR #${pr_number} \"${pr_title}\""
+
+ if [[ -z "$ci_failed_since" ]]; then
+ echo " ↳ CI failing but could not determine since when, skipping"
+ skip_count=$((skip_count + 1))
+ continue
+ fi
+
+ # Calculate days since CI started failing
+ # Handle both GNU date (Linux) and BSD date (macOS)
+ failed_epoch=$(date -d "$ci_failed_since" +%s 2>/dev/null || date -jf "%Y-%m-%dT%H:%M:%SZ" "$ci_failed_since" +%s 2>/dev/null || date -jf "%Y-%m-%dT%T%z" "$ci_failed_since" +%s)
+ now_epoch=$(date -u +%s)
+ days_since=$(( (now_epoch - failed_epoch) / 86400 ))
+
+ echo " ↳ CI failing since: ${ci_failed_since} (${days_since} days)"
+ if [[ -n "$ci_failed_checks" ]]; then
+ echo " ↳ Failed checks: ${ci_failed_checks}"
+ fi
+
+ # Build guidance text for the comment
+ guidance=$(build_guidance "$ci_failed_checks")
+
+ # Close if past close threshold
+ if [[ "$days_since" -ge "$CLOSE_DAYS" ]]; then
+ close_count=$((close_count + 1))
+ echo " ↳ CI failing for ${days_since} days -> CLOSING"
+
+ if [[ "$DRY_RUN" == "false" ]]; then
+ comment_body="${WARNING_MARKER}\nThank you for your contribution @${pr_author}. Unfortunately, we are unable to review or merge this pull request as the CI checks have been failing for more than 14 days.\n\nPlease feel free to reopen this PR once the CI issues have been resolved.${guidance}\n\nThank you for your understanding!"
+
+ # Use jq to safely build JSON
+ json_payload=$(jq -n --arg body "$comment_body" '{"body": $body}')
+
+ curl -s -L -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer $token" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "${API_BASE}/issues/${pr_number}/comments" \
+ -d "$json_payload" > /dev/null
+
+ curl -s -L -X PATCH \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer $token" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "${API_BASE}/pulls/${pr_number}" \
+ -d '{"state":"closed"}' > /dev/null
+ echo " ↳ Closed and commented"
+ else
+ echo " ↳ (dry run - would close and comment)"
+ fi
+
+ # Warn if past warn threshold and not already warned
+ elif [[ "$days_since" -ge "$WARN_DAYS" ]]; then
+ existing_warnings=$(has_warning_comment "$pr_number")
+ if [[ "$existing_warnings" -gt 0 ]]; then
+ echo " ↳ Already warned, waiting for close threshold"
+ skip_count=$((skip_count + 1))
+ continue
+ fi
+
+ warn_count=$((warn_count + 1))
+ echo " ↳ CI failing for ${days_since} days -> WARNING"
+
+ if [[ "$DRY_RUN" == "false" ]]; then
+ comment_body="${WARNING_MARKER}\nHi @${pr_author}, we have noticed that the CI on this pull request has been failing for 7 days.\n\nIf the CI failures are not resolved within the next 7 days, we will close this pull request.${guidance}\n\nIf you need help, please leave a comment and we will do our best to assist. Thank you!"
+
+ json_payload=$(jq -n --arg body "$comment_body" '{"body": $body}')
+
+ curl -s -L -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer $token" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "${API_BASE}/issues/${pr_number}/comments" \
+ -d "$json_payload" > /dev/null
+ echo " ↳ Warning comment posted"
+ else
+ echo " ↳ (dry run - would post warning comment)"
+ fi
+ else
+ echo " ↳ Under threshold (${days_since}/${WARN_DAYS} days), skipping"
+ skip_count=$((skip_count + 1))
+ fi
+done
+
+echo ""
+echo "=== Summary ==="
+echo "Total PRs checked: ${total_prs}"
+echo "PRs with failing CI: ${failing_count}"
+echo "Warned: ${warn_count}"
+echo "Closed: ${close_count}"
+echo "Skipped: ${skip_count}"
+if [[ "$DRY_RUN" == "true" ]]; then
+ echo "(dry run - no actual changes made)"
+fi
+echo "Done."
diff --git a/scripts/run-breaking-change-detection.sh b/scripts/run-breaking-change-detection.sh
index a694b8071f24..5ef8c38b7b1a 100755
--- a/scripts/run-breaking-change-detection.sh
+++ b/scripts/run-breaking-change-detection.sh
@@ -3,12 +3,22 @@
# SPDX-License-Identifier: MPL-2.0
+function on_failure {
+ echo ""
+ echo "==> Breaking change detection failed!"
+ echo " Your changes modify the provider schema in a backwards-incompatible way."
+ echo " If this is intentional, the change must be gated behind features.FivePointOh()"
+ echo " and documented in the 5.0 upgrade guide."
+ echo " See: contributing/topics/guide-breaking-changes.md"
+ echo ""
+}
+
function runDetect {
go run internal/tools/schema-api/main.go -detect .release/provider-schema.json
}
function main {
- runDetect
+ runDetect || { on_failure; exit 1; }
}
main
\ No newline at end of file
diff --git a/scripts/run-lint.sh b/scripts/run-lint.sh
index e4c1cfc6ac35..8bbbde9e022e 100755
--- a/scripts/run-lint.sh
+++ b/scripts/run-lint.sh
@@ -3,13 +3,22 @@
# SPDX-License-Identifier: MPL-2.0
+function on_failure {
+ echo ""
+ echo "==> golangci-lint failed!"
+ echo " To auto-fix some issues run: make golangci-fix"
+ echo " Common issues: unused variables, formatting, error handling, naming conventions"
+ echo " Docs: https://golangci-lint.run/"
+ echo ""
+}
+
function runLinters {
echo "==> Checking source code against linters..."
golangci-lint run -v ./...
}
function main {
- runLinters
+ runLinters || { on_failure; exit 1; }
}
main
diff --git a/scripts/run-static-analysis.sh b/scripts/run-static-analysis.sh
index f32e0f04a87c..1c9030be9600 100755
--- a/scripts/run-static-analysis.sh
+++ b/scripts/run-static-analysis.sh
@@ -2,11 +2,13 @@
# Copyright IBM Corp. 2014, 2025
# SPDX-License-Identifier: MPL-2.0
-on_failure() {
+function on_failure {
+ echo ""
+ echo "==> Static analysis failed!"
+ echo " Check the output above for specific violations."
+ echo " Common issues: incorrect Go types in TypedSDK structs, missing required fields."
+ echo " Run locally: go run internal/tools/static-analysis/main.go -fail-on-error=false"
echo ""
- echo "Static analysis failed. Run 'make static-analysis' locally to reproduce."
- echo "This check validates project-specific code conventions that standard linters cannot enforce."
- echo "See the error output above for details on which rules failed and how to fix them."
}
function runStaticAnalysis {
@@ -15,7 +17,7 @@ function runStaticAnalysis {
}
function main {
- runStaticAnalysis
+ runStaticAnalysis || { on_failure; exit 1; }
}
main
\ No newline at end of file
diff --git a/scripts/run-tflint.sh b/scripts/run-tflint.sh
index ba62321db362..863304bd2a7e 100755
--- a/scripts/run-tflint.sh
+++ b/scripts/run-tflint.sh
@@ -3,7 +3,17 @@
# SPDX-License-Identifier: MPL-2.0
-set -e
+
+
+function on_failure {
+ echo ""
+ echo "==> tflint failed!"
+ echo " Common causes:"
+ echo " - Schema definition issues (e.g. missing Required/Optional, invalid types)"
+ echo " - Acceptance test formatting issues (run: terrafmt fmt -f )"
+ echo " - tfproviderlint rule violations (see https://github.com/bflad/tfproviderlint)"
+ echo ""
+}
function runTests {
echo "==> Checking source code against terraform provider linters..."
@@ -19,7 +29,8 @@ function runTests {
}
function main {
- runTests
+ runTests || { on_failure; exit 1; }
}
main
+