diff --git a/.github/workflows/docs-check-azure-arm-template.yml b/.github/workflows/docs-check-azure-arm-template.yml new file mode 100644 index 0000000000..db5bc25ba6 --- /dev/null +++ b/.github/workflows/docs-check-azure-arm-template.yml @@ -0,0 +1,69 @@ +name: 'Docs: Azure ARM Template' + +on: + push: + branches: + - 'master' + - 'v5.*' + paths: + - '.github/workflows/docs-check-azure-arm-template.yml' + - 'docs/assets/templates/azure/prowler-scan.json' + - 'docs/snippets/azure-prowler-scan-template.mdx' + - 'permissions/templates/azure/bicep/Makefile' + - 'permissions/templates/azure/bicep/prowler-scan.bicep' + - 'permissions/templates/azure/bicep/prowler-scan.json' + - 'permissions/templates/azure/bicep/sync_docs_template.py' + - 'permissions/templates/azure/bicep/sync_docs_template_test.py' + pull_request: + branches: + - 'master' + - 'v5.*' + paths: + - '.github/workflows/docs-check-azure-arm-template.yml' + - 'docs/assets/templates/azure/prowler-scan.json' + - 'docs/snippets/azure-prowler-scan-template.mdx' + - 'permissions/templates/azure/bicep/Makefile' + - 'permissions/templates/azure/bicep/prowler-scan.bicep' + - 'permissions/templates/azure/bicep/prowler-scan.json' + - 'permissions/templates/azure/bicep/sync_docs_template.py' + - 'permissions/templates/azure/bicep/sync_docs_template_test.py' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + azure-arm-template: + if: github.repository == 'prowler-cloud/prowler' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: block + allowed-endpoints: > + api.github.com:443 + github.com:443 + release-assets.githubusercontent.com:443 + + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Bicep + uses: anthony-c-martin/setup-bicep@faf8170f496c10a2ea75977d2e77024131331404 # v1 + with: + version: v0.46.1 + + - name: Test Azure ARM documentation synchronization + run: python3 -m unittest permissions/templates/azure/bicep/sync_docs_template_test.py + + - name: Check Azure ARM template synchronization + run: make --directory permissions/templates/azure/bicep check diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml index 0475925d25..abd42ded07 100644 --- a/.github/workflows/markdown-lint.yml +++ b/.github/workflows/markdown-lint.yml @@ -62,6 +62,3 @@ jobs: env: pnpm_config_ignore_scripts: 'true' run: git ls-files -z '*.md' | xargs -0 -r pnpm dlx markdownlint-cli@0.45.0 -- - - - name: Check Azure ARM documentation assets - run: python3 permissions/templates/azure/bicep/sync_docs_template.py --check diff --git a/docs/snippets/azure-prowler-scan-template.mdx b/docs/snippets/azure-prowler-scan-template.mdx index e77ef09b3a..24af8e4ddb 100644 --- a/docs/snippets/azure-prowler-scan-template.mdx +++ b/docs/snippets/azure-prowler-scan-template.mdx @@ -101,5 +101,4 @@ } } } - ``` diff --git a/permissions/templates/azure/bicep/Makefile b/permissions/templates/azure/bicep/Makefile index 53b5a55d72..10da390676 100644 --- a/permissions/templates/azure/bicep/Makefile +++ b/permissions/templates/azure/bicep/Makefile @@ -26,10 +26,13 @@ sync-docs: prowler-scan.json # CI hook: fail if the JSON is out of sync with the Bicep source. Run # `make build` locally and commit both files before opening the PR. check: prowler-scan.bicep - $(BICEP_BUILD) prowler-scan.bicep --outfile /tmp/prowler-scan.check.json - diff -q prowler-scan.json /tmp/prowler-scan.check.json + @set -eu; \ + tmp=$$(mktemp); \ + trap 'rm -f "$$tmp"' EXIT; \ + $(BICEP_BUILD) prowler-scan.bicep --outfile "$$tmp"; \ + python3 -c 'from pathlib import Path; import sys; path = Path(sys.argv[1]); path.write_bytes(path.read_bytes().rstrip(b"\r\n") + b"\n")' "$$tmp"; \ + diff -q prowler-scan.json "$$tmp"; \ python3 sync_docs_template.py --check - rm /tmp/prowler-scan.check.json clean: rm -f prowler-scan.json diff --git a/permissions/templates/azure/bicep/sync_docs_template.py b/permissions/templates/azure/bicep/sync_docs_template.py index 30c649d117..87c14bba1b 100644 --- a/permissions/templates/azure/bicep/sync_docs_template.py +++ b/permissions/templates/azure/bicep/sync_docs_template.py @@ -11,7 +11,7 @@ SNIPPET_PREFIX = ( b"{/* AUTO-GENERATED from permissions/templates/azure/bicep/" b"prowler-scan.json. Do not edit manually. */}\n\n```json\n" ) -SNIPPET_SUFFIX = b"\n```\n" +SNIPPET_SUFFIX = b"```\n" def parse_args(): @@ -33,18 +33,22 @@ def expected_snippet(source): def main(): args = parse_args() - source = args.source.read_bytes() + source_bytes = args.source.read_bytes() + source = source_bytes.rstrip(b"\r\n") + b"\n" json.loads(source) snippet = expected_snippet(source) if args.sync: args.asset.parent.mkdir(parents=True, exist_ok=True) args.snippet.parent.mkdir(parents=True, exist_ok=True) + args.source.write_bytes(source) args.asset.write_bytes(source) args.snippet.write_bytes(snippet) return 0 drifted = [] + if source_bytes != source: + drifted.append(args.source) if not args.asset.exists() or args.asset.read_bytes() != source: drifted.append(args.asset) if not args.snippet.exists() or args.snippet.read_bytes() != snippet: diff --git a/permissions/templates/azure/bicep/sync_docs_template_test.py b/permissions/templates/azure/bicep/sync_docs_template_test.py index a9b76bdce9..974a8714c2 100644 --- a/permissions/templates/azure/bicep/sync_docs_template_test.py +++ b/permissions/templates/azure/bicep/sync_docs_template_test.py @@ -39,13 +39,29 @@ class SyncDocsTemplateTest(unittest.TestCase): check=False, ) - def test_sync_copies_asset_and_embeds_exact_canonical_bytes(self): - result = self.run_script("--sync") + def test_sync_normalizes_canonical_and_asset_to_one_trailing_lf(self): + for terminal_newlines in (b"", b"\n", b"\n\n", b"\r\n\r\n"): + with self.subTest(terminal_newlines=terminal_newlines): + self.source.write_bytes(self.canonical + terminal_newlines) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(self.asset.read_bytes(), self.canonical) - snippet = self.snippet.read_bytes() - self.assertIn(b"```json\n" + self.canonical + b"\n```", snippet) + result = self.run_script("--sync") + + self.assertEqual(result.returncode, 0, result.stderr) + expected = self.canonical + b"\n" + self.assertEqual(self.source.read_bytes(), expected) + self.assertEqual(self.asset.read_bytes(), expected) + + def test_sync_normalizes_snippet_fence_and_final_lf(self): + for terminal_newlines in (b"", b"\n", b"\n\n", b"\r\n\r\n"): + with self.subTest(terminal_newlines=terminal_newlines): + self.source.write_bytes(self.canonical + terminal_newlines) + + result = self.run_script("--sync") + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertTrue( + self.snippet.read_bytes().endswith(self.canonical + b"\n```\n") + ) def test_check_fails_when_generated_output_drifts(self): self.assertEqual(self.run_script("--sync").returncode, 0)