Skip to content

Fix betterleaks PR scan crash on Azure Pipelines, and gather CI platform logic in ci_providers - #8780

Merged
nvuillam merged 6 commits into
mainfrom
fix/betterleaks-azure-pr-scan-crash
Aug 22, 2026
Merged

Fix betterleaks PR scan crash on Azure Pipelines, and gather CI platform logic in ci_providers#8780
nvuillam merged 6 commits into
mainfrom
fix/betterleaks-azure-pr-scan-crash

Conversation

@nvuillam

@nvuillam nvuillam commented Aug 22, 2026

Copy link
Copy Markdown
Member

Fixes #8732, #8731.

The bug

With REPOSITORY_BETTERLEAKS_PR_COMMITS_SCAN: true on Azure Pipelines, MegaLinter crashes before running a single linter:

File "/megalinter/linters/BetterleaksLinter.py", line 62, in get_azure_devops_pr_target_sha
  return repo.commit(target_branch_name.replace("refs/heads", "origin"))
gitdb.exc.BadName: Ref 'origin/main' did not resolve to an object

The target branch SHA was resolved with a single unguarded GitPython lookup on origin/<branch>. The default Azure Pipelines checkout is shallow and creates no remote-tracking ref, so BadName propagates out of BetterleaksLinter.__init__ — called from linter_factory.build_descriptor_linters — and aborts the whole run, not just betterleaks.

The GitHub path had the same flaw (GITHUB_EVENT_PATH missing, unreadable, or not a PR payload), and GitLab / unrecognized platforms returned no SHAs with no feedback at all.

The fix

The target branch is now searched across several ref spellings (origin/<branch>, refs/remotes/origin/<branch>, the raw value, the bare name), so a branch present only locally is found too. Every lookup degrades to None with an actionable warning instead of raising, and betterleaks scans the whole repository — a superset, so nothing goes undetected:

WARNING [Azure Pipelines] Unable to resolve target branch refs/heads/main to a commit
        (tried origin/main, refs/remotes/origin/main, refs/heads/main, main)
WARNING [betterleaks] REPOSITORY_BETTERLEAKS_PR_COMMITS_SCAN is enabled but the Pull Request
        commit range could not be determined, so the whole repository is scanned. To scan only
        Pull Request commits, check out the repository with `fetchDepth: 0` and forward
        SYSTEM_PULLREQUEST_SOURCECOMMITID, SYSTEM_PULLREQUEST_TARGETBRANCH and BUILD_REASON to
        the MegaLinter container, or set REPOSITORY_BETTERLEAKS_PR_SOURCE_SHA and
        REPOSITORY_BETTERLEAKS_PR_TARGET_SHA yourself.

Documentation (#8731): restores the sections dropped from the descriptor when gitleaks was replaced by betterleaks in v10 — per-platform checkout depth (fetch-depth / fetchDepth / GIT_DEPTH), the Azure Pipelines docker run -e SYSTEM_PULLREQUEST_* passthrough, the git rev-list / git rev-parse snippets, and the GitLab merged-results-pipelines caveat.

New megalinter/ci_providers/ package

The SHA lookups were CI-platform concerns living on a linter. Rather than move them to yet another place, this PR gathers the platform knowledge that was spread across utils, utils_reporter, MegaLinter and the reporters, following the existing api_providers pattern.

Class Platform
CiProvider Base + neutral fallback, so callers never handle a missing provider
CiProviderAzurePipelines Source SHA from SYSTEM_PULLREQUEST_SOURCECOMMITID, target resolved from the branch name
CiProviderGithubActions Both SHAs from the event payload
CiProviderGitlab Premium/Ultimate merge request and external PR variables
CiProviderBitbucket (new)
CiProviderJenkins moved from reporters/jenkins_ci_vars.py

jenkins_ci_vars.py was never a reporter — it is called from Megalinter.__init__ and maps Jenkins variables onto the other platforms' native ones. It now sits with its peers.

What each provider exposes: is_current(), is_pr_context(), get_pr_commit_shas() + get_pr_commit_shas_hint(), get_repo_name(), get_branch_name(), get_job_url(), log_section_start/end(), set_output(), publish_job_summary(), markdown_supports_html_details.

Consolidated as a result:

  • utils.get_git_context_info() — a 95-line four-platform if/elif chain — delegates to the provider, keeping the git fallbacks and the GITHUB_JOB_URL / CI_JOB_URL overrides
  • utils_reporter.log_section_start/end() delegate too; the GitLab section-key sanitizing moves to CiProviderGitlab
  • MegaLinter.check_results() uses set_output(); MarkdownSummaryReporter uses publish_job_summary() and stops reading os.environ directly
  • Deduplicated: the GitHub run URL (built in 3 places), the Bitbucket step URL (2), the Azure BUILD_BUILDID/BUILD_BUILD_ID fallback (2)

Two detection gaps fixed on the way

utils.is_ci() and utils.is_pr() both omitted Bitbucket Pipelines. Consequences:

  • the is_bitbucket() branch of log_section_start was unreachable (harmless — it returned the same value as the fallthrough)
  • user-visible: the Pull Request optimizations in CheckovLinter and BetterleaksLinter never engaged on Bitbucket

Added utils.is_bitbucket_pr() and both missing cases.

The reporters now read their context from the providers

The four comment reporters and GithubStatusReporter each read the platform variables themselves, duplicating url construction and auth handling. They now ask their provider for the platform identity and keep only the comment transport and rendering — 238 lines lighter.

Provider Now owns
CiProviderAzurePipelines build_git_api_url(), get_api_headers(), get_artifacts_url(), and get_repository_id() — the SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI parsing, API lookup and BUILD_REPOSITORY_ID fallback
CiProviderGithubActions get_repo_slug(), get_commit_sha(), get_api_url(), get_pr_number() (the refs/pull/N/merge regex)
CiProviderGitlab get_project_id(), get_api_auth_options(), merge request iid resolution incl. CI_OPEN_MERGE_REQUESTS parsing and the CI_MERGE_REQUEST_IID retry
CiProviderBitbucket get_repo_slug(), get_pr_number(), get_api_headers()

One trap worth knowing about. Each reporter instantiates its own platform provider directly rather than calling get_ci_provider(). Under Jenkins the running platform is Jenkins, which maps its variables onto the other platforms' — so resolving through the factory (or an isinstance check in manage_activation) would have silently disabled every comment reporter on Jenkins. Reporter activation is deliberately left reading the mapped variables.

Deliberately not changed

GithubStatusReporter uses GITHUB_TOKEN while GithubCommentReporter prefers PAT. This looks like an inconsistency to unify behind one get_auth_token(), but it is not: MegaLinter documents fine-grained PATs scoped to Contents: Read/Write only, which lack statuses: write. Preferring PAT for commit statuses would give those users 403s. The providers therefore expose get_auth_token() (runner token) and get_user_auth_token() (PAT) as two distinct accessors, and each caller picks the right one.

Also left alone, being pure deduplication with no user benefit and real risk: the four get_comment_marker() implementations. Their output is persisted inside existing PR comments, so any drift would orphan them and post duplicates instead of updating.

Testing

megalinter/tests/test_megalinter/ci_providers_test.py — 56 tests, no Docker needed, including a direct regression test for the #8732 traceback:

  • Azure: resolution without an origin remote, from a bare branch name, unresolvable ref, non-git workspace, missing branch variable, full shallow-checkout scenario, BUILD_BUILD_ID fallback, URL-encoded project name
  • GitHub: valid payload, missing path, unreadable file, payload without pull_request, set_output / publish_job_summary including an unwritable path
  • GitLab: merge request, external PR, non-premium, section-key sanitizing and length cap
  • Bitbucket: repo/branch/job URL, no folding, no output support
  • Factory: per-platform detection and neutral fallback for both get_ci_provider() and get_pr_ci_provider()
  • Reporter context: Azure REST urls and Basic auth header, repository id resolution (all three branches), GitHub PR number and the two token accessors, GitLab auth options and iid resolution, Bitbucket slug/PR/headers

Beyond unit tests, the refactor was checked for behavioral equivalence against the previous implementations: the GitLab console folding output is byte-identical, the Azure REST urls and Basic auth header are byte-identical, and the repo/branch/job URL context is unchanged on GitHub, Azure, Bitbucket and outside any CI.

126 passed  (ci_providers, jenkins_ci_vars, linter, utils, utils_reporter, api_reporter_v2, filters)

black, isort (with TEMPLATES/.isort.cfg) and flake8 clean. Descriptor validated against megalinter-descriptor.jsonschema.json. linter_text feeds documentation generation only, so no build artifact changes.

REPOSITORY_BETTERLEAKS_PR_COMMITS_SCAN resolved the Azure Pipelines target
branch with a single unguarded GitPython lookup on origin/<branch>. The
default Azure checkout is shallow and creates no remote tracking ref, so
gitdb.exc.BadName escaped BetterleaksLinter.__init__ and aborted the whole
MegaLinter run before any linter started. The GitHub event payload lookup
had the same flaw, and GitLab/unknown platforms failed silently.

- New megalinter/ci_providers/ package (mirrors api_providers): CiProvider
  base class, CiProviderAzurePipelines, CiProviderGithubActions and
  CiProviderGitlab, exposing get_pr_commit_shas() and a platform specific
  get_pr_commit_shas_hint(). get_pr_ci_provider() falls back to the neutral
  base provider, so callers never handle a missing provider
- Azure target branch is now searched across several ref spellings
  (origin/<branch>, refs/remotes/origin/<branch>, raw value, bare name)
- Every lookup degrades to None with an actionable warning instead of
  raising, and betterleaks scans the whole repository
- BetterleaksLinter keeps only the orchestration
- Restore the Pull Request scan setup documentation lost when gitleaks was
  replaced by betterleaks: per platform checkout depth, Azure Pipelines
  variables to forward to the container, how to compute the SHAs manually
- Add ci_providers_test.py, covering the crash outside Docker

Fixes #8732
Fixes #8731
@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

⚠️MegaLinter analysis: Success with warnings

⚠️ PYTHON / bandit - 188 errors
-----------------
>> Issue: [B311:blacklist] Standard pseudo-random generators are not suitable for security/cryptographic purposes.
   Severity: Low   Confidence: High
   CWE: CWE-330 (https://cwe.mitre.org/data/definitions/330.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_calls.html#b311-random
   Location: ./megalinter/utils_sarif.py:156:61
155	                        rule["id"] = (
156	                            rule["id"] + "_DUPLICATE_" + str(random.randint(1, 99999))
157	                        )

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: ./megalinter/utilstest.py:122:4
121	    )
122	    assert os.path.isdir(config.get(request_id, "DEFAULT_WORKSPACE")), (
123	        "DEFAULT_WORKSPACE "
124	        + config.get(request_id, "DEFAULT_WORKSPACE")
125	        + " is not a valid folder"
126	    )
127	

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: ./megalinter/utilstest.py:167:4
166	    tmp_report_folder = tempfile.gettempdir() + os.path.sep + str(uuid.uuid4())
167	    assert os.path.isdir(workspace), f"Test folder {workspace} is not existing"
168	    linter_name = linter.linter_name

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: ./megalinter/utilstest.py:241:4
240	    tmp_report_folder = tempfile.gettempdir() + os.path.sep + str(uuid.uuid4())
241	    assert os.path.isdir(workspace), f"Test folder {workspace} is not existing"
242	    if os.path.isfile(workspace + os.path.sep + "no_test_failure"):

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: ./megalinter/utilstest.py:490:4
489	    )
490	    assert os.path.isdir(workspace), f"Test folder {workspace} is not existing"
491	    expected_file_name = ""

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: ./megalinter/utilstest.py:590:4
589	        workspace += os.path.sep + "bad"
590	    assert os.path.isdir(workspace), f"Test folder {workspace} is not existing"
591	    # Call linter

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: ./megalinter/utilstest.py:690:4
689	        workspace = workspace + os.path.sep + "fix"
690	    assert os.path.isdir(workspace), f"Test folder {workspace} is not existing"
691	

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: ./megalinter/utilstest.py:796:12
795	            ]
796	            assert (len(list(diffs))) > 0, f"No changes in the {file} file"
797	

--------------------------------------------------
>> Issue: [B108:hardcoded_tmp_directory] Probable insecure usage of temp file/directory.
   Severity: Medium   Confidence: Medium
   CWE: CWE-377 (https://cwe.mitre.org/data/definitions/377.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b108_hardcoded_tmp_directory.html
   Location: ./server/server.py:81:42
80	    if item.fileUploadId:
81	        uploaded_file_path = os.path.join("/tmp/server-files", item.fileUploadId)
82	        if not os.path.isdir(uploaded_file_path):

--------------------------------------------------
>> Issue: [B108:hardcoded_tmp_directory] Probable insecure usage of temp file/directory.
   Severity: Medium   Confidence: Medium
   CWE: CWE-377 (https://cwe.mitre.org/data/definitions/377.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b108_hardcoded_tmp_directory.html
   Location: ./server/server.py:103:38
102	    file_upload_id = "FILE_" + str(uuid1())
103	    uploaded_file_path = os.path.join("/tmp/server-files", file_upload_id)
104	    os.makedirs(uploaded_file_path)

--------------------------------------------------
>> Issue: [B108:hardcoded_tmp_directory] Probable insecure usage of temp file/directory.
   Severity: Medium   Confidence: Medium
   CWE: CWE-377 (https://cwe.mitre.org/data/definitions/377.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b108_hardcoded_tmp_directory.html
   Location: ./server/server_worker.py:102:34
101	        temp_dir = self.create_temp_dir()
102	        upload_dir = os.path.join("/tmp/server-files", file_upload_id)
103	        if os.path.exists(upload_dir):

--------------------------------------------------

Code scanned:
	Total lines of code: 29065
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 126
		Medium: 54
		High: 8
	Total issues (by confidence):
		Undefined: 0
		Low: 44
		Medium: 35
		High: 109
Files skipped (0):

(Truncated to last 6666 characters out of 130333)
⚠️ BASH / bash-exec - 1 error
Results of bash-exec linter (version 5.3.9)
See documentation on https://megalinter.io/beta/descriptors/bash_bash_exec/
-----------------------------------------------

✅ [SUCCESS] .automation/build_schemas_doc.sh
✅ [SUCCESS] .automation/format-tables.sh
✅ [SUCCESS] .vscode/testlinter.sh
✅ [SUCCESS] build.sh
✅ [SUCCESS] entrypoint.sh
❌ [ERROR] sh/megalinter_exec.sh
    Error: File:[sh/megalinter_exec.sh] is not executable

✅ [SUCCESS] sh/setup-runtime-user.sh
⚠️ SPELL / lychee - 54 errors
aw.githubusercontent.com/datreeio/CRDs-catalog/main/%7B%7B.Group%7D%7D/%7B%7B.ResourceKind%7D%7D_%7B%7B.ResourceAPIVersion%7D%7D.json (at 72:22) | Rejected status code: 404 Not Found

Errors in megalinter/descriptors/latex.megalinter-descriptor.yml
[TIMEOUT] https://www.nongnu.org/chktex (at 26:17) | Request timed out
[TIMEOUT] https://www.nongnu.org/chktex/ (at 29:23) | Request timed out
[TIMEOUT] https://www.nongnu.org/chktex/ (at 31:38) | Request timed out

Errors in megalinter/descriptors/markdown.megalinter-descriptor.yml
[404] https://github.com/rvben/rumdl/blob/main/docs/RULES.md (at 166:23) | Rejected status code: 404 Not Found
[403] https://www.npmjs.com/package/markdown-table-formatter (at 103:17) | Rejected status code: 403 Forbidden

Errors in megalinter/descriptors/repository.megalinter-descriptor.yml
[404] https://raw.githubusercontent.com/oxsecurity/megalinter/main/docs/assets/icons/linters/betterleaks.png (at 297:26) | Rejected status code: 404 Not Found

Errors in megalinter/descriptors/rst.megalinter-descriptor.yml
[403] https://docutils.sourceforge.io/docs/ref/rst/directives.html#raw-data-pass-through (at 34:38) | Rejected status code: 403 Forbidden

Errors in megalinter/descriptors/salesforce.megalinter-descriptor.yml
[403] https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/config.html (at 374:37) | Rejected status code: 403 Forbidden
[403] https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/engine-flow.html (at 371:17) | Rejected status code: 403 Forbidden
[403] https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/get-started.html (at 176:17) | Error (cached)
[403] https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/get-started.html (at 276:17) | Error (cached)
[403] https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/get-started.html (at 74:17) | Rejected status code: 403 Forbidden
[403] https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/rules-flow.html (at 373:23) | Rejected status code: 403 Forbidden

Errors in megalinter/descriptors/shared/biome.megalinter-linter.yml
[404] https://biomejs.dev/linter/rules/ (at 21:19) | Rejected status code: 404 Not Found

Errors in megalinter/descriptors/shared/cppcheck.megalinter-linter.yml
[403] https://cppcheck.sourceforge.io/ (at 3:13) | Rejected status code: 403 Forbidden
[403] https://cppcheck.sourceforge.io/ (at 4:14) | Rejected status code: 403 Forbidden
[403] https://cppcheck.sourceforge.io/manual.html#configuration (at 8:33) | Rejected status code: 403 Forbidden

Errors in megalinter/descriptors/spell.megalinter-descriptor.yml
[404] https://vale.sh/docs/topics/vocab/ (at 190:38) | Rejected status code: 404 Not Found | Followed 2 redirects. Redirects: https://vale.sh/docs/topics/vocab/ --[301]--> https://docs.vale.sh/topics/vocab/ --[302]--> https://docs.vale.sh/topics/vocab
[404] https://vale.sh/docs/vale-cli/structure/ (at 183:95) | Rejected status code: 404 Not Found | Followed 2 redirects. Redirects: https://vale.sh/docs/vale-cli/structure/ --[301]--> https://docs.vale.sh/vale-cli/structure/ --[302]--> https://docs.vale.sh/vale-cli/structure

Errors in megalinter/descriptors/tsx.megalinter-descriptor.yml
[404] https://eslint-react.xyz/docs/getting-started/installation (at 81:37) | Error (cached)

Errors in megalinter/descriptors/xml.megalinter-descriptor.yml
[406] https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home (at 38:17) | Rejected status code: 406 Not Acceptable

Errors in README.md
[ERROR] https://ampcode.com/ (at 247:1) | HTTP/2 protocol error. Server may not support HTTP/2 properly
[301] https://future-architect.github.io/authors/%E5%AE%AE%E6%B0%B8%E5%B4%87%E5%8F%B2 (at 1974:104) | Rejected status code: 301 Moved Permanently
[TIMEOUT] https://generated.at/ (at 1315:301) | Request timed out
[404] https://github.com/oxsecurity/megalinter/stargazers (at 2132:3) | Rejected status code: 404 Not Found
[404] https://github.com/oxsecurity/megalinter/stargazers/ (at 23:1) | Error (cached)
[403] https://javascript.plainenglish.io/node-js-coding-standard-tools-with-megalinter-on-gitlab-ci-a43b55915811 (at 1957:3) | Rejected status code: 403 Forbidden
[403] https://medium.com/@caodanju/30-seconds-to-setup-megalinter-your-go-to-tool-for-automated-code-quality-and-iac-security-969d90a5a99c (at 1942:3) | Rejected status code: 403 Forbidden
[403] https://medium.com/@RunningMattress (at 1951:255) | Rejected status code: 403 Forbidden
[403] https://medium.com/@RunningMattress/level-up-your-unity-packages-with-ci-cd-9498d2791211 (at 1951:3) | Rejected status code: 403 Forbidden
[403] https://medium.com/@SeasonedDeveloper (at 1938:255) | Rejected status code: 403 Forbidden
[403] https://medium.com/@SeasonedDeveloper/looking-for-the-best-ci-cd-pipeline-linting-tool-try-megalinter-d89c9eba850d (at 1938:3) | Rejected status code: 403 Forbidden
[403] https://medium.com/datamindedbe/integrating-megalinter-to-automate-linting-across-multiple-codebases-a-technical-description-a200bb235b71 (at 1939:3) | Rejected status code: 403 Forbidden
[403] https://nicolas.vuillamy.fr/improve-uniformize-and-secure-your-code-base-with-megalinter-62ebab422c1 (at 1960:3) | Rejected status code: 403 Forbidden
[403] https://nicolas.vuillamy.fr/megalinter-sells-his-soul-and-joins-ox-security-2a91a0027628 (at 1959:3) | Rejected status code: 403 Forbidden
[403] https://nklya.medium.com/ (at 1956:255) | Rejected status code: 403 Forbidden
[403] https://nklya.medium.com/hot-to-linter-basic-things-like-trailing-whitespaces-and-newlines-7b40da8f688d (at 1956:3) | Rejected status code: 403 Forbidden
[403] https://npmjs.org/package/mega-linter-runner (at 1230:1) | Error (cached)
[403] https://npmjs.org/package/mega-linter-runner (at 1231:1) | Error (cached)
[403] https://npmjs.org/package/mega-linter-runner (at 1232:1) | Error (cached)
[403] https://npmjs.org/package/mega-linter-runner (at 21:1) | Error (cached)
[403] https://openai.com/codex/ (at 239:1) | Rejected status code: 403 Forbidden
[403] https://pmd.sourceforge.io/pmd-6.55.0/pmd_userdocs_tools_ci.html (at 2041:3) | Rejected status code: 403 Forbidden
[403] https://www.npmjs.com/package/@downatthebottomofthemolehole/megalinter-mcp-server (at 1916:354) | Rejected status code: 403 Forbidden

Hint: Followed 784 redirects. You might want to consider replacing redirecting URLs with the resolved URLs. Use verbose mode (`-v`/`-vv`) to see redirection details.
Hint: Rejected redirectional status codes. This means some redirects were not followed. You might want to increase the limit for `-m`/`--max-redirects`.

(Truncated to last 6666 characters out of 32171)
⚠️ MARKDOWN / markdownlint - 344 errors
-Linter"]
docs/plugins.md:9 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Plugins"]
docs/quick-start.md:9 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Quick Start"]
docs/removed-linters.md:9 error MD024/no-duplicate-heading Multiple headings with the same content [Context: "Removed linters"]
docs/reporters.md:9 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Reporters"]
docs/reporters/AzureCommentReporter.md:6 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Azure Comment Reporter"]
docs/reporters/BitbucketCommentReporter.md:6 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Bitbucket Comment Reporter"]
docs/reporters/ConfigReporter.md:5 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "IDE Configuration Reporter"]
docs/reporters/ConsoleReporter.md:5 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Console Reporter"]
docs/reporters/EmailReporter.md:5 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "E-mail Reporter"]
docs/reporters/FileIoReporter.md:5 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "File.io Reporter"]
docs/reporters/GitHubCommentReporter.md:6 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "GitHub Comment Reporter"]
docs/reporters/GitHubCommentReporter.md:27:196 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data]
docs/reporters/GitHubCommentReporter.md:27:46 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubCommentReporter.md:27:174 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubCommentReporter.md:27:196 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubCommentReporter.md:28:179 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data]
docs/reporters/GitHubCommentReporter.md:28:46 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubCommentReporter.md:28:160 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubCommentReporter.md:28:179 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubCommentReporter.md:29:159 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data]
docs/reporters/GitHubCommentReporter.md:29:48 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubCommentReporter.md:29:143 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubCommentReporter.md:29:159 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubCommentReporter.md:30:171 error MD056/table-column-count Table column count [Expected: 4; Actual: 3; Too few cells, row will be missing data]
docs/reporters/GitHubCommentReporter.md:30:46 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubCommentReporter.md:30:152 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubCommentReporter.md:30:171 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
docs/reporters/GitHubStatusReporter.md:6 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "GitHub Status Reporter"]
docs/reporters/GitlabCommentReporter.md:6 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Gitlab Comment Reporter"]
docs/reporters/JsonReporter.md:5 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "JSON Reporter"]
docs/reporters/MarkdownSummaryReporter.md:6 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Markdown Summary Reporter"]
docs/reporters/SarifReporter.md:6 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "SARIF Reporter (beta)"]
docs/reporters/TapReporter.md:5 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "TAP Reporter"]
docs/reporters/TextReporter.md:5 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Text Reporter"]
docs/reporters/UpdatedSourcesReporter.md:5 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Updated Sources Reporter"]
docs/special-thanks.md:9 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Special thanks"]
docs/special-thanks.md:23:3 error MD045/no-alt-text Images should have alternate text (alt text)
docs/sponsor.md:5 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Sponsoring"]
docs/supported-linters.md:9 error MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "Supported Linters"]
mega-linter-runner/README.md:27:274 error MD051/link-fragments Link fragments should be valid [Context: "[**apply formatting and auto-fixes**](#apply-fixes)"]
mega-linter-runner/README.md:27:217 error MD051/link-fragments Link fragments should be valid [Context: "[**reports in several formats**](#reports)"]
README.md:219:127 error MD051/link-fragments Link fragments should be valid [Context: "[many additional features](#mega-linter-vs-super-linter)"]
README.md:2159:3 error MD045/no-alt-text Images should have alternate text (alt text)
skills/megalinter-check/performance.md:27:601 error MD013/line-length Line length [Expected: 600; Actual: 713]
skills/megalinter-setup/agents/megalinter-runner.md:33:601 error MD013/line-length Line length [Expected: 600; Actual: 620]

(Truncated to last 6666 characters out of 45733)
⚠️ YAML / prettier - 14 errors
(unchanged)
mega-linter-runner/generators/mega-linter-custom-flavor/templates/action.yml 5ms (unchanged)
mega-linter-runner/generators/mega-linter-custom-flavor/templates/check-new-megalinter-version.yml 20ms (unchanged)
mega-linter-runner/generators/mega-linter-custom-flavor/templates/megalinter-custom-flavor-builder.yml 9ms (unchanged)
[error] mega-linter-runner/generators/mega-linter-custom-flavor/templates/megalinter-custom-flavor.yml: SyntaxError: Implicit map keys need to be followed by map values (6:1)
[error]   4 | label: <%= CUSTOM_FLAVOR_LABEL %>
[error]   5 | linters:
[error] > 6 | <%= CUSTOM_FLAVOR_LINTERS %>
[error]     | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]   7 |
mega-linter-runner/generators/mega-linter-custom-flavor/templates/zizmor.yml 2ms (unchanged)
mega-linter-runner/generators/mega-linter/templates/.drone.yml 3ms (unchanged)
mega-linter-runner/generators/mega-linter/templates/.gitlab-ci.yml 5ms (unchanged)
mega-linter-runner/generators/mega-linter/templates/azure-pipelines.yml 4ms (unchanged)
mega-linter-runner/generators/mega-linter/templates/bitbucket-pipelines.yml 5ms (unchanged)
mega-linter-runner/generators/mega-linter/templates/concourse-task.yml 3ms (unchanged)
[error] mega-linter-runner/generators/mega-linter/templates/mega-linter.yml: SyntaxError: Implicit map keys need to be followed by map values (67:11)
[error]   65 |           # Only define `secrets.PAT` if you fully understand the trade-off.
[error]   66 |           token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
[error] > 67 |           <%- PERSIST_CREDENTIALS %>
[error]      |           ^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]   68 |
[error]   69 |           # If you use VALIDATE_ALL_CODEBASE = true, you can remove this line to
[error]   70 |           # improve performance
megalinter/descriptors/action.megalinter-descriptor.yml 14ms (unchanged)
megalinter/descriptors/ansible.megalinter-descriptor.yml 9ms (unchanged)
megalinter/descriptors/api.megalinter-descriptor.yml 10ms (unchanged)
megalinter/descriptors/arm.megalinter-descriptor.yml 6ms (unchanged)
megalinter/descriptors/bash.megalinter-descriptor.yml 14ms (unchanged)
megalinter/descriptors/bicep.megalinter-descriptor.yml 5ms (unchanged)
megalinter/descriptors/c.megalinter-descriptor.yml 3ms (unchanged)
megalinter/descriptors/clojure.megalinter-descriptor.yml 14ms (unchanged)
megalinter/descriptors/cloudformation.megalinter-descriptor.yml 10ms (unchanged)
megalinter/descriptors/coffee.megalinter-descriptor.yml 4ms (unchanged)
megalinter/descriptors/copypaste.megalinter-descriptor.yml 3ms (unchanged)
megalinter/descriptors/cpp.megalinter-descriptor.yml 3ms (unchanged)
megalinter/descriptors/csharp.megalinter-descriptor.yml 10ms (unchanged)
megalinter/descriptors/css.megalinter-descriptor.yml 7ms (unchanged)
megalinter/descriptors/dart.megalinter-descriptor.yml 7ms (unchanged)
megalinter/descriptors/dockerfile.megalinter-descriptor.yml 9ms (unchanged)
megalinter/descriptors/editorconfig.megalinter-descriptor.yml 4ms (unchanged)
megalinter/descriptors/env.megalinter-descriptor.yml 6ms (unchanged)
megalinter/descriptors/gherkin.megalinter-descriptor.yml 6ms (unchanged)
megalinter/descriptors/go.megalinter-descriptor.yml 8ms (unchanged)
megalinter/descriptors/graphql.megalinter-descriptor.yml 3ms (unchanged)
megalinter/descriptors/groovy.megalinter-descriptor.yml 4ms (unchanged)
megalinter/descriptors/html.megalinter-descriptor.yml 9ms (unchanged)
megalinter/descriptors/java.megalinter-descriptor.yml 14ms (unchanged)
megalinter/descriptors/javascript.megalinter-descriptor.yml 15ms (unchanged)
megalinter/descriptors/json.megalinter-descriptor.yml 12ms (unchanged)
megalinter/descriptors/jsx.megalinter-descriptor.yml 4ms (unchanged)
megalinter/descriptors/kotlin.megalinter-descriptor.yml 7ms (unchanged)
megalinter/descriptors/kubernetes.megalinter-descriptor.yml 12ms (unchanged)
megalinter/descriptors/latex.megalinter-descriptor.yml 8ms (unchanged)
megalinter/descriptors/lua.megalinter-descriptor.yml 7ms (unchanged)
megalinter/descriptors/markdown.megalinter-descriptor.yml 10ms (unchanged)
megalinter/descriptors/perl.megalinter-descriptor.yml 4ms (unchanged)
megalinter/descriptors/php.megalinter-descriptor.yml 35ms (unchanged)
megalinter/descriptors/powershell.megalinter-descriptor.yml 7ms (unchanged)
megalinter/descriptors/protobuf.megalinter-descriptor.yml 9ms (unchanged)
megalinter/descriptors/python.megalinter-descriptor.yml 74ms (unchanged)
megalinter/descriptors/r.megalinter-descriptor.yml 6ms (unchanged)
megalinter/descriptors/raku.megalinter-descriptor.yml 3ms (unchanged)
megalinter/descriptors/repository.megalinter-descriptor.yml 76ms (unchanged)
megalinter/descriptors/robotframework.megalinter-descriptor.yml 8ms (unchanged)
megalinter/descriptors/rst.megalinter-descriptor.yml 14ms (unchanged)
megalinter/descriptors/ruby.megalinter-descriptor.yml 6ms (unchanged)
megalinter/descriptors/rust.megalinter-descriptor.yml 6ms (unchanged)
megalinter/descriptors/salesforce.megalinter-descriptor.yml 28ms (unchanged)
megalinter/descriptors/scala.megalinter-descriptor.yml 4ms (unchanged)
megalinter/descriptors/shared/biome.megalinter-linter.yml 5ms (unchanged)
megalinter/descriptors/shared/clang-format.megalinter-linter.yml 2ms (unchanged)
megalinter/descriptors/shared/cppcheck.megalinter-linter.yml 6ms (unchanged)
megalinter/descriptors/shared/cpplint.megalinter-linter.yml 2ms (unchanged)
megalinter/descriptors/shared/dotnet-format.megalinter-linter.yml 3ms (unchanged)
megalinter/descriptors/shared/eslint.megalinter-linter.yml 5ms (unchanged)
megalinter/descriptors/shared/prettier.megalinter-linter.yml 6ms (unchanged)
megalinter/descriptors/shared/v8r.megalinter-linter.yml 3ms (unchanged)
megalinter/descriptors/snakemake.megalinter-descriptor.yml 5ms (unchanged)
megalinter/descriptors/spell.megalinter-descriptor.yml 17ms (unchanged)
megalinter/descriptors/sql.megalinter-descriptor.yml 7ms (unchanged)
megalinter/descriptors/swift.megalinter-descriptor.yml 9ms (unchanged)
megalinter/descriptors/tekton.megalinter-descriptor.yml 3ms (unchanged)
megalinter/descriptors/terraform.megalinter-descriptor.yml 11ms (unchanged)
megalinter/descriptors/tsx.megalinter-descriptor.yml 7ms (unchanged)
megalinter/descriptors/typescript.megalinter-descriptor.yml 9ms (unchanged)
megalinter/descriptors/vbdotnet.megalinter-descriptor.yml 2ms (unchanged)
megalinter/descriptors/xml.megalinter-descriptor.yml 4ms (unchanged)
megalinter/descriptors/yaml.megalinter-descriptor.yml 7ms (unchanged)
server/docker-compose-dev.yml 5ms (unchanged)
server/docker-compose.yml 5ms (unchanged)
trivy-secret.yaml 1ms (unchanged)
zizmor.yml 2ms (unchanged)

(Truncated to last 6666 characters out of 12551)
⚠️ YAML / yamllint - 42 errors
.grype.yaml
  6:1       warning  missing document start "---"  (document-start)

mega-linter-runner/.eslintrc.yml
  11:9      warning  too few spaces inside empty braces  (braces)

mega-linter-runner/generators/mega-linter-custom-flavor/templates/megalinter-custom-flavor-builder.yml
  48:15     warning  too few spaces inside empty braces  (braces)

mega-linter-runner/generators/mega-linter-custom-flavor/templates/megalinter-custom-flavor.yml
  7:1       error    syntax error: could not find expected ':' (syntax)

mega-linter-runner/generators/mega-linter/templates/mega-linter.yml
  38:15     warning  too few spaces inside empty braces  (braces)
  69:11     error    syntax error: could not find expected ':' (syntax)

megalinter/descriptors/copypaste.megalinter-descriptor.yml
  19:301    warning  line too long (313 > 300 characters)  (line-length)
  25:301    warning  line too long (384 > 300 characters)  (line-length)

megalinter/descriptors/javascript.megalinter-descriptor.yml
  52:301    warning  line too long (475 > 300 characters)  (line-length)
  328:301   warning  line too long (307 > 300 characters)  (line-length)
  354:301   warning  line too long (315 > 300 characters)  (line-length)

megalinter/descriptors/json.megalinter-descriptor.yml
  112:301   warning  line too long (315 > 300 characters)  (line-length)

megalinter/descriptors/jsx.megalinter-descriptor.yml
  29:301    warning  line too long (475 > 300 characters)  (line-length)

megalinter/descriptors/perl.megalinter-descriptor.yml
  25:301    warning  line too long (310 > 300 characters)  (line-length)

megalinter/descriptors/php.megalinter-descriptor.yml
  200:301   warning  line too long (389 > 300 characters)  (line-length)
  214:301   warning  line too long (302 > 300 characters)  (line-length)

megalinter/descriptors/repository.megalinter-descriptor.yml
  27:301    warning  line too long (666 > 300 characters)  (line-length)
  193:301   warning  line too long (408 > 300 characters)  (line-length)
  299:301   warning  line too long (345 > 300 characters)  (line-length)
  537:301   warning  line too long (306 > 300 characters)  (line-length)
  616:301   warning  line too long (374 > 300 characters)  (line-length)
  701:301   warning  line too long (316 > 300 characters)  (line-length)
  1038:301  warning  line too long (1263 > 300 characters)  (line-length)
  1135:301  warning  line too long (879 > 300 characters)  (line-length)
  1149:301  warning  line too long (358 > 300 characters)  (line-length)
  1212:301  warning  line too long (346 > 300 characters)  (line-length)
  1219:301  warning  line too long (307 > 300 characters)  (line-length)

megalinter/descriptors/salesforce.megalinter-descriptor.yml
  54:301    warning  line too long (359 > 300 characters)  (line-length)

megalinter/descriptors/spell.megalinter-descriptor.yml
  181:301   warning  line too long (315 > 300 characters)  (line-length)

megalinter/descriptors/sql.megalinter-descriptor.yml
  27:301    warning  line too long (403 > 300 characters)  (line-length)

megalinter/descriptors/terraform.megalinter-descriptor.yml
  28:301    warning  line too long (330 > 300 characters)  (line-length)
  88:301    warning  line too long (346 > 300 characters)  (line-length)
  155:301   warning  line too long (328 > 300 characters)  (line-length)

megalinter/descriptors/tsx.megalinter-descriptor.yml
  29:301    warning  line too long (475 > 300 characters)  (line-length)

megalinter/descriptors/typescript.megalinter-descriptor.yml
  39:301    warning  line too long (475 > 300 characters)  (line-length)
  318:301   warning  line too long (314 > 300 characters)  (line-length)
  344:301   warning  line too long (315 > 300 characters)  (line-length)

megalinter/descriptors/yaml.megalinter-descriptor.yml
  38:301    warning  line too long (315 > 300 characters)  (line-length)

mkdocs.yml
  8:301     warning  line too long (590 > 300 characters)  (line-length)
  72:5      warning  wrong indentation: expected 6 but found 4  (indentation)
  85:5      warning  wrong indentation: expected 6 but found 4  (indentation)

zizmor.yml
  1:1       warning  missing document start "---"  (document-start)

✅ Linters with no issues

actionlint, betterleaks, black, checkov, cspell, flake8, git_diff, grype, hadolint, isort, jscpd, jsonlint, markdown-table-formatter, mypy, npm-groovy-lint, osv-scanner, pylint, ruff, secretlint, shellcheck, shfmt, spectral, syft, trivy, trivy-sbom, trufflehog, v8r, v8r, xmllint, zizmor

See detailed reports in MegaLinter artifacts

MegaLinter is provided by OX Security
Show us your support by starring ⭐ the repository

The CI/CD platform specific logic was spread across utils, utils_reporter,
MegaLinter and the reporters. Concentrate it in megalinter/ci_providers/,
which now models the platform itself rather than only the Pull Request
commit range.

- jenkins_ci_vars moves from reporters/ to ci_providers/CiProviderJenkins:
  it was never a reporter, it is called from MegaLinter.__init__
- New CiProviderBitbucket, and every provider gains is_current(), so
  ci_providers.get_ci_provider() resolves the platform running the build
- CiProvider gains the shared platform surface: get_repo_name(),
  get_branch_name(), get_job_url(), log_section_start/end(), set_output(),
  publish_job_summary() and markdown_supports_html_details
- utils.get_git_context_info() delegates to the provider instead of its
  four-platform if/elif chain, keeping the git fallbacks and the
  GITHUB_JOB_URL / CI_JOB_URL overrides
- utils_reporter.log_section_start/end delegate too, moving the GitLab
  section key sanitizing to CiProviderGitlab
- MegaLinter.check_results uses set_output(), MarkdownSummaryReporter uses
  publish_job_summary() and stops reading os.environ directly
- Deduplicates the GitHub run URL (built in 3 places), the Bitbucket step
  URL (2 places) and the Azure BUILD_BUILDID/BUILD_BUILD_ID fallback

Fixes two detection gaps found on the way: is_ci() and is_pr() ignored
Bitbucket Pipelines, so the Bitbucket branch of log_section_start was
unreachable and the Pull Request optimizations of CheckovLinter and
BetterleaksLinter never engaged there. Adds is_bitbucket_pr().

Verified the refactored console folding is byte-identical to the previous
implementation on GitLab, and the repo/branch/job URL context is unchanged
on GitHub, Azure, Bitbucket and outside any CI.
@nvuillam nvuillam changed the title Fix MegaLinter crash on Azure Pipelines betterleaks PR commits scan Fix betterleaks PR scan crash on Azure Pipelines, and gather CI platform logic in ci_providers Aug 22, 2026
The four comment reporters and GithubStatusReporter each read the platform
environment variables themselves, duplicating url construction and auth
handling. They now ask their provider for the platform identity and keep
only the comment transport and rendering.

- CiProviderAzurePipelines gains get_team_project(), get_artifacts_url(),
  get_pr_number(), get_auth_token(), get_api_headers(), build_git_api_url()
  and get_repository_id(), which owns the SYSTEM_PULLREQUEST_SOURCEREPO
  URI parsing, the API lookup and the BUILD_REPOSITORY_ID fallback
- CiProviderGithubActions gains get_repo_slug(), get_commit_sha(),
  get_api_url(), get_pr_number() and the two token accessors
- CiProviderGitlab gains get_project_id(), get_api_auth_options() and the
  merge request iid resolution, including the CI_OPEN_MERGE_REQUESTS
  parsing and the CI_MERGE_REQUEST_IID retry
- CiProviderBitbucket gains get_repo_slug(), get_pr_number() and
  get_api_headers()

Each reporter instantiates its own platform provider directly instead of
calling get_ci_provider(). Under Jenkins the running platform is Jenkins,
which maps its variables onto the other platforms', so resolving through
the factory would have disabled every comment reporter there.

GitHub keeps get_auth_token() (GITHUB_TOKEN) and get_user_auth_token()
(PAT) separate on purpose: the comment reporter prefers PAT so the comment
is attributed to the user, while a commit status needs the statuses:write
scope that the documented fine-grained PAT does not carry. Unifying them
would have broken status reporting for those users.

Verified the Azure REST urls and the Basic auth header are byte-identical
to the previous implementation.
Add hexsha (GitPython commit attribute) to the cspell dictionary, and
assert the Azure Basic auth header by decoding it instead of hardcoding
the base64 literal, whose OnRvaw fragment cspell flagged.
The suite runs inside GitHub Actions, where GITHUB_ACTIONS, GITHUB_REPOSITORY
and GITHUB_RUN_ID are really set, so get_ci_provider() returned the GitHub
provider and GITHUB_REPOSITORY leaked into a test expecting no repository.

- A shared CiProviderTestCase clears every platform variable from the request
  configuration before each test
- The factory tests activate exactly one provider by patching is_current /
  is_pr_context on all of them, instead of mocking a single utils detector:
  the detectors read the global configuration, which the request-level
  cleanup can not reach

Verified green with the environment simulating each of GitHub Actions, Azure
Pipelines, GitLab, Bitbucket, Jenkins, and no CI at all.
@nvuillam
nvuillam merged commit 2e9c335 into main Aug 22, 2026
145 checks passed
@nvuillam
nvuillam deleted the fix/betterleaks-azure-pr-scan-crash branch August 22, 2026 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error using Betterleaks with Azure Pipelines

1 participant