From 4dfdbde238ada1e7e1b0dc17784c761da525aebf Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:56:52 -0800 Subject: [PATCH 01/34] ci(lint): add workflow and taskfiles Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .devcontainer/devcontainer.json | 4 +- .github/linters/.markdownlint-cli2.yaml | 2 + .github/linters/.markdownlint.yml | 2 +- .github/linters/.yamllint.yml | 28 ++ .github/workflows/ci.yml | 69 +--- .github/workflows/dependency-review.yml | 5 +- .github/workflows/e2e-tests.yaml | 2 +- .github/workflows/lint.yml | 86 +++++ .goreleaser.yml | 19 +- .node-version | 1 + .taskfiles/_internal.Taskfile.yml | 145 ++++++++ .taskfiles/azure.Taskfile.yml | 79 ++++ .taskfiles/golang.Taskfile.yml | 170 +++++++++ .taskfiles/markdown.Taskfile.yml | 122 +++++++ .taskfiles/runtime.Taskfile.yml | 86 +++++ .taskfiles/scripts/install_az.sh | 114 ++++++ .taskfiles/scripts/install_azd.sh | 87 +++++ .taskfiles/scripts/install_bicep.sh | 137 +++++++ .taskfiles/scripts/install_golangci-lint.sh | 96 +++++ .taskfiles/scripts/setup_fnm.sh | 145 ++++++++ .taskfiles/scripts/setup_golang.sh | 184 ++++++++++ .taskfiles/scripts/setup_pwsh.sh | 146 ++++++++ .taskfiles/scripts/setup_uv.sh | 98 +++++ .taskfiles/yaml.Taskfile.yml | 49 +++ .yamlignore | 5 + Taskfile.yml | 383 +++++--------------- 26 files changed, 1890 insertions(+), 374 deletions(-) create mode 100644 .github/linters/.yamllint.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .node-version create mode 100644 .taskfiles/_internal.Taskfile.yml create mode 100644 .taskfiles/azure.Taskfile.yml create mode 100644 .taskfiles/golang.Taskfile.yml create mode 100644 .taskfiles/markdown.Taskfile.yml create mode 100644 .taskfiles/runtime.Taskfile.yml create mode 100644 .taskfiles/scripts/install_az.sh create mode 100644 .taskfiles/scripts/install_azd.sh create mode 100644 .taskfiles/scripts/install_bicep.sh create mode 100644 .taskfiles/scripts/install_golangci-lint.sh create mode 100644 .taskfiles/scripts/setup_fnm.sh create mode 100644 .taskfiles/scripts/setup_golang.sh create mode 100644 .taskfiles/scripts/setup_pwsh.sh create mode 100644 .taskfiles/scripts/setup_uv.sh create mode 100644 .taskfiles/yaml.Taskfile.yml create mode 100644 .yamlignore diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4d87454..b1d74e4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,11 +7,11 @@ "configureZshAsDefaultShell": false }, "ghcr.io/devcontainers/features/azure-cli:1": { - "version": "2.82.0", + "version": "2.81.0", "installBicep": true }, "ghcr.io/devcontainers/features/terraform:1": { - "version": "1.10.4" + "version": "1.14.3" }, "ghcr.io/devcontainers/features/go:1": { "version": "1.25.5" diff --git a/.github/linters/.markdownlint-cli2.yaml b/.github/linters/.markdownlint-cli2.yaml index de7ecff..b5eff52 100644 --- a/.github/linters/.markdownlint-cli2.yaml +++ b/.github/linters/.markdownlint-cli2.yaml @@ -7,3 +7,5 @@ ignores: - .git - "**/node_modules/**" - .copilot-tracking/** + - venv/** + - .venv/** diff --git a/.github/linters/.markdownlint.yml b/.github/linters/.markdownlint.yml index af78481..91fe5ab 100644 --- a/.github/linters/.markdownlint.yml +++ b/.github/linters/.markdownlint.yml @@ -30,7 +30,7 @@ MD029: # MD033/no-inline-html - Inline HTML MD033: # Allowed elements - allowed_elements: [br, pre, a] + allowed_elements: [br, pre] # MD036/no-emphasis-as-heading - Emphasis used instead of a heading MD036: false diff --git a/.github/linters/.yamllint.yml b/.github/linters/.yamllint.yml new file mode 100644 index 0000000..9019ab1 --- /dev/null +++ b/.github/linters/.yamllint.yml @@ -0,0 +1,28 @@ +# yaml-language-server: $schema=https://www.schemastore.org/yamllint.json +# docs: https://yamllint.readthedocs.io/en/stable/configuration.html#extending-the-default-configuration +--- +extends: default + +locale: en_US.UTF-8 + +rules: + document-start: + level: warning + ignore: + - .cspell.yml + line-length: disable + quoted-strings: + level: error + quote-type: double + required: only-when-needed + comments: + min-spaces-from-content: 1 + braces: + level: warning + max-spaces-inside: 1 + truthy: + check-keys: false + +ignore-from-file: + - .gitignore + - .yamlignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5f9417..4f32280 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,9 @@ on: - main types: - opened + - reopened - synchronize + - ready_for_review merge_group: # schedule: # - cron: "0 2 * * *" @@ -30,73 +32,6 @@ env: permissions: {} jobs: - lint-go: - name: ๐Ÿงน Lint Go - runs-on: ubuntu-24.04 - timeout-minutes: 10 - permissions: - contents: read - pull-requests: read - steps: - - name: โคต๏ธ Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - - - name: ๐Ÿšง Setup Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 - with: - go-version-file: go.mod - cache: true - - - name: ๐Ÿšง Setup Task - uses: go-task/setup-task@0ab1b2a65bc55236a3bc64cde78f80e20e8885c2 # v1.0.0 - - - name: ๐Ÿ”€ Get dependencies - run: task deps - - - name: โœ”๏ธ Run GoVulnCheck - run: | - task install:govulncheck - task govulncheck || (echo "::warning::govulncheck found issues" && exit 0) - - - name: โœ”๏ธ Run Go linters - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 - with: - version: latest - only-new-issues: true - skip-cache: true - skip-save-cache: true - problem-matchers: true - - lint-markdown: - name: ๐Ÿงน Lint Markdown - runs-on: ubuntu-24.04 - timeout-minutes: 10 - permissions: - contents: read - steps: - - name: โคต๏ธ Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - - - name: ๐Ÿšง Setup Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 - with: - go-version-file: go.mod - cache: true - - - name: ๐Ÿšง Setup Task - uses: go-task/setup-task@0ab1b2a65bc55236a3bc64cde78f80e20e8885c2 # v1.0.0 - - - name: ๐Ÿ”จ Setup tools - run: | - task install:markdownlint - - - name: โœ”๏ธ Run Files linters - run: task lint:md - build: name: ๐Ÿ—๏ธ Build runs-on: ubuntu-24.04 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 1e51e75..a4928b6 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -27,7 +27,4 @@ jobs: - name: ๐Ÿ•ต๏ธ Run Dependency Review uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 with: - vulnerability-check: true - license-check: true - show-openssf-scorecard: true - comment-summary-in-pr: always + comment-summary-in-pr: on-failure diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index 5c7194c..b2b7b62 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -59,7 +59,7 @@ jobs: uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 with: terraform_wrapper: false - terraform_version: 1.10.4 + terraform_version: 1.14.3 - name: ๐Ÿ” Azure Login uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..b66c568 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,86 @@ +# yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json +--- +name: ๐Ÿงน Lint + +on: + push: + branches: + - main + pull_request: + branches: + - main + types: + - opened + - reopened + - synchronize + - ready_for_review + +permissions: {} + +jobs: + lint: + name: ๐Ÿงน Lint + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + task: + - go + - md + - yml + steps: + - name: ๐Ÿฉบ Debug + uses: raven-actions/debug@9dbdeb7eea607a7d73411895c65987e71d59a466 # v1.2.0 + + - name: โคต๏ธ Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - name: ๐Ÿšง Setup Node + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version-file: .node-version + + - name: ๐Ÿšง Setup Go + uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version-file: go.mod + + - name: ๐Ÿšง Setup Task + uses: go-task/setup-task@0ab1b2a65bc55236a3bc64cde78f80e20e8885c2 # v1.0.0 + with: + repo-token: ${{ github.token }} + + - name: ๐Ÿšง Setup runtime (UV) + run: task runtime:setup:uv + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: ๐Ÿ”จ Setup tools + run: task ${{ matrix.task }}:tools + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: โœ”๏ธ Run lint (${{ matrix.task }}) + run: task ${{ matrix.task }}:lint + + - name: ๐Ÿ”€ Check for differences + if: github.event.pull_request.user.login != 'dependabot[bot]' + run: task diff -- "${{ matrix.task }}:lint:fix" + + check-lint: + if: always() + name: ๐Ÿงน Check Lint + needs: lint + runs-on: ubuntu-24.04 + steps: + - name: โœ… OK + if: ${{ !(contains(needs.*.result, 'failure')) }} + run: exit 0 + - name: ๐Ÿ›‘ Failure + if: ${{ contains(needs.*.result, 'failure') }} + run: exit 1 diff --git a/.goreleaser.yml b/.goreleaser.yml index 38f40b8..2259b05 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -12,7 +12,7 @@ archives: - src: LICENSE dst: LICENSE.txt formats: [zip] - name_template: "az{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + name_template: az{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }} builds: - dir: cmd @@ -35,24 +35,23 @@ builds: ignore: - goos: darwin goarch: "386" - binary: "az{{ .ProjectName }}_v{{ .Version }}" + binary: az{{ .ProjectName }}_v{{ .Version }} checksum: algorithm: sha256 - name_template: "az{{ .ProjectName }}_{{ .Version }}_SHA256SUMS" - + name_template: az{{ .ProjectName }}_{{ .Version }}_SHA256SUMS signs: - artifacts: checksum args: # if you are using this in a GitHub action or some other automated pipeline, you # need to pass the batch flag to indicate its not interactive. - - "--batch" - - "--local-user" + - --batch + - --local-user - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key - - "--output" - - "${signature}" - - "--detach-sign" - - "${artifact}" + - --output + - ${signature} + - --detach-sign + - ${artifact} release: # If you want to manually examine the release before its live, uncomment this line: diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24 diff --git a/.taskfiles/_internal.Taskfile.yml b/.taskfiles/_internal.Taskfile.yml new file mode 100644 index 0000000..3db45b9 --- /dev/null +++ b/.taskfiles/_internal.Taskfile.yml @@ -0,0 +1,145 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json +# docs: https://taskfile.dev +--- +version: "3" + +vars: + PWSH: pwsh -NonInteractive -NoProfile -NoLogo -Command + PWSH_SCRIPT: pwsh -NonInteractive -NoProfile -NoLogo -ExecutionPolicy Bypass -File + APT_UPDATE: sudo apt-get -o DPkg::Lock::Timeout=-1 update + APT_INSTALL: sudo apt-get -o DPkg::Lock::Timeout=-1 install -y + PYTHON_VERSION: + sh: | + [ -f .python-version ] && cat .python-version || echo "3.14" + NODE_VERSION: + sh: | + [ -f .node-version ] && cat .node-version || echo "24" + +env: + DEBIAN_FRONTEND: noninteractive + +tasks: + command: + desc: Check if command exists (internal) + silent: true + run: once + preconditions: + - sh: | + {{empty .CLI_ARGS | not}} + msg: No command provided. Please provide a command to check. + cmds: + - cmd: command -v "{{.CLI_ARGS}}" + platforms: [linux, darwin] + - cmd: | + {{.PWSH}} 'Get-Command "{{.CLI_ARGS}}"' + platforms: [windows] + + command:version: + desc: Check if desired version of command is installed (internal) + silent: true + run: once + preconditions: + - sh: | + {{empty .CLI_ARGS | not}} + msg: No command and version provided. Please provide a command and version to check. + vars: + CMD: "{{index .CLI_ARGS_LIST 0}}" + VER: "{{index .CLI_ARGS_LIST 1}}" + cmds: + - cmd: | + {{if ne .VER "latest"}} + {{.CMD}} | grep -F "{{.VER}}" + {{end}} + platforms: [linux, darwin] + - cmd: | + {{if ne .VER "latest"}} + {{.PWSH}} ' + $match = (& {{.CMD}}) | Select-String -Pattern "{{.VER}}" -SimpleMatch + if (-not $match) { exit 1 } + ' + {{end}} + platforms: [windows] + + _install:go: + desc: go install + internal: true + preconditions: + - sh: task -g internal:command -- go + msg: "go is not installed. Please install go: https://go.dev/doc/install" + requires: + vars: [APP] + cmds: + - go install {{.APP}} + + _install:winget: + desc: winget install + internal: true + preconditions: + - sh: task -g internal:command -- winget + msg: "winget is not installed. Please install winget: https://learn.microsoft.com/windows/package-manager/winget/" + requires: + vars: [APP] + vars: + # yamllint disable-line rule:quoted-strings + VERSION: '{{if and .VERSION (ne .VERSION "latest")}}--version {{.VERSION}}{{end}}' + cmds: + - winget install --id "{{.APP}}" --exact --accept-source-agreements --accept-package-agreements --disable-interactivity {{.VERSION}} + ignore_error: true + platforms: [windows] + + _install:brew: + desc: brew install + internal: true + preconditions: + - sh: task -g internal:command -- brew + msg: brew is not installed. Please install brew. + requires: + vars: [APP] + cmds: + - brew install {{.APP}} + platforms: [darwin] + + _install:brew:tap: + desc: brew tap + internal: true + preconditions: + - sh: task -g internal:command -- brew + msg: brew is not installed. Please install brew. + requires: + vars: [APP] + cmds: + - brew tap {{.APP}} + platforms: [darwin] + + _install:pipx: + desc: pipx install + internal: true + preconditions: + - sh: task -g internal:command -- pipx + msg: "pipx is not installed. Please install pipx: https://pipx.pypa.io/" + requires: + vars: [APP] + cmds: + - pipx install --include-deps {{.APP}} + + _install:npm: + desc: npm install + internal: true + preconditions: + - sh: task -g internal:command -- npm + msg: "npm is not installed. Please install npm: https://docs.npmjs.com/cli/commands/npm" + requires: + vars: [APP] + cmds: + - npm install {{if .OPTS}}{{.OPTS}}{{else}}--global{{end}} {{.APP}} + + _install:uv: + desc: uv install + internal: true + preconditions: + - sh: task -g internal:command -- uv + msg: uv is not installed. Please install by running 'task -g runtime:setup:uv' + requires: + vars: [APP] + cmds: + - uv tool install --force --upgrade {{.APP}} diff --git a/.taskfiles/azure.Taskfile.yml b/.taskfiles/azure.Taskfile.yml new file mode 100644 index 0000000..dcb56c4 --- /dev/null +++ b/.taskfiles/azure.Taskfile.yml @@ -0,0 +1,79 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json +# docs: https://taskfile.dev +--- +version: "3" + +vars: + AZURE_VERSION: + map: + az: latest + azd: latest + bicep: latest + +tasks: + # * Install Azure CLI + install:az: + desc: Install Azure CLI + cmds: + - task: :internal:_install:winget + vars: + APP: Microsoft.AzureCLI + VERSION: "{{.AZURE_VERSION.az}}" + - task: :internal:_install:brew + vars: + APP: azure-cli@{{.AZURE_VERSION.az}} + - cmd: | + sudo ./scripts/install_az.sh "{{.AZURE_VERSION.az}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + + # * Install Azure Developer CLI + install:azd: + desc: Install Azure CLI + cmds: + - task: :internal:_install:winget + vars: + APP: Microsoft.Azd + VERSION: "{{.AZURE_VERSION.azd}}" + - task: :internal:_install:brew:tap + vars: + APP: azure/azd + - task: :internal:_install:brew + vars: + APP: azd@{{.AZURE_VERSION.azd}} + - cmd: | + ./scripts/install_azd.sh "{{.AZURE_VERSION.azd}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Install Bicep + install:bicep: + desc: Install Bicep + cmds: + - task: :internal:_install:winget + vars: + APP: Microsoft.Bicep + VERSION: "{{.AZURE_VERSION.bicep}}" + - task: :internal:_install:brew:tap + vars: + APP: azure/bicep + - task: :internal:_install:brew + vars: + APP: bicep@{{.AZURE_VERSION.bicep}} + - cmd: | + ./scripts/install_bicep.sh "{{.AZURE_VERSION.bicep}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Tools + tools: + desc: Install Azure tools + vars: + ITEMS: + - az + - azd + - bicep + cmds: + - for: { var: ITEMS } + task: install:{{.ITEM}} diff --git a/.taskfiles/golang.Taskfile.yml b/.taskfiles/golang.Taskfile.yml new file mode 100644 index 0000000..2a9e651 --- /dev/null +++ b/.taskfiles/golang.Taskfile.yml @@ -0,0 +1,170 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json +# docs: https://taskfile.dev +--- +version: "3" + +vars: + GO_VERSION: + map: + dlv: latest + deadcode: latest + golangciLint: latest + gotestsum: latest + govulncheck: latest + goimports: latest + gofumpt: latest + goTestCoverage: latest + gocoverCobertura: latest + goreleaser: latest + +tasks: + # * Install GoReleaser + install:goreleaser: + desc: Install GoReleaser + cmds: + - task: :internal:_install:winget + vars: + APP: goreleaser.goreleaser + VERSION: "{{.GO_VERSION.goreleaser}}" + - task: :internal:_install:brew + vars: + APP: goreleaser@{{.GO_VERSION.goreleaser}} + - cmd: | + # TODO + # ./scripts/install_goreleaser.sh "{{.GO_VERSION.goreleaser}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Install Dlv + install:dlv: + desc: Install Dlv + cmds: + - task: :internal:_install:go + vars: + APP: github.com/go-delve/delve/cmd/dlv@{{.GO_VERSION.dlv}} + + # * Install DeadCode + install:deadcode: + desc: Install DeadCode + cmds: + - task: :internal:_install:go + vars: + APP: golang.org/x/tools/cmd/deadcode@{{.GO_VERSION.deadcode}} + + # * Install GolangCI-Lint + install:golangci-lint: + desc: Install GolangCI-Lint + cmds: + - task: :internal:_install:winget + vars: + APP: GolangCI.golangci-lint + VERSION: "{{.GO_VERSION.golangciLint}}" + - task: :internal:_install:brew + vars: + APP: golangci-lint@{{.GO_VERSION.golangciLint}} + - cmd: | + ./scripts/install_golangci-lint.sh "{{.GO_VERSION.golangciLint}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Install GoTestSum + install:gotestsum: + desc: Install GoTestSum + cmds: + - task: :internal:_install:go + vars: + APP: gotest.tools/gotestsum@{{.GO_VERSION.gotestsum}} + + # * Install GoVulnCheck + install:govulncheck: + desc: Install GoVulnCheck + cmds: + - task: :internal:_install:go + vars: + APP: golang.org/x/vuln/cmd/govulncheck@{{.GO_VERSION.govulncheck}} + + # * Install GoImports + install:goimports: + desc: Install GoImports + cmds: + - task: :internal:_install:go + vars: + APP: golang.org/x/tools/cmd/goimports@{{.GO_VERSION.goimports}} + + # * Install GoFumpt + install:gofumpt: + desc: Install GoFumpt + cmds: + - task: :internal:_install:go + vars: + APP: mvdan.cc/gofumpt@{{.GO_VERSION.gofumpt}} + + # * Install Go Test Coverage + install:go-test-coverage: + desc: Install Go Test Coverage + cmds: + - task: :internal:_install:go + vars: + APP: github.com/vladopajic/go-test-coverage/v2@{{.GO_VERSION.goTestCoverage}} + + # * Install GoCover-Cobertura + install:gocover-cobertura: + desc: Install GoCover-Cobertura + cmds: + - task: :internal:_install:go + vars: + APP: github.com/boumenot/gocover-cobertura@{{.GO_VERSION.gocoverCobertura}} + + # * Tools + tools: + desc: Install Go tools + vars: + ITEMS: + - dlv + - deadcode + - golangci-lint + - gotestsum + - govulncheck + - goimports + - gofumpt + - go-test-coverage + - gocover-cobertura + - goreleaser + cmds: + - for: { var: ITEMS } + task: install:{{.ITEM}} + + # * Run GoVulnCheck + run:govulncheck: + desc: Run govulncheck + preconditions: + - sh: task -g internal:command -- govulncheck + msg: "โŒ Error: govulncheck is not installed. Please run 'task -g go:install:govulncheck'" + - sh: task -g internal:command:version -- "govulncheck --version" "{{.GO_VERSION.govulncheck}}" + msg: "โŒ Version Mismatch: You are not using {{.GO_VERSION.govulncheck}} version. Please run 'task -g go:install:govulncheck'" + cmds: + - govulncheck -test -show verbose ./... + + # * Run GolangCI-Lint + run:golangci-lint: + desc: Run GolangCI-Lint + preconditions: + - sh: task -g internal:command -- golangci-lint + msg: "โŒ Error: golangci-lint is not installed. Please run 'task -g go:install:golangci-lint'" + - sh: task -g internal:command:version -- "golangci-lint --version" "{{.GO_VERSION.golangciLint}}" + msg: "โŒ Version Mismatch: You are not using {{.GO_VERSION.golangciLint}} version. Please run 'task -g go:install:golangci-lint'" + sources: + - "**/*.go" + - "**/*.mod" + - "**/*.sum" + - "**/go.work" + cmds: + - golangci-lint run --fix + dir: "{{.USER_WORKING_DIR}}" + + # * Lint + lint: + desc: Lint Go files + cmds: + - task: run:golangci-lint + - task: run:govulncheck diff --git a/.taskfiles/markdown.Taskfile.yml b/.taskfiles/markdown.Taskfile.yml new file mode 100644 index 0000000..cce9691 --- /dev/null +++ b/.taskfiles/markdown.Taskfile.yml @@ -0,0 +1,122 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json +# docs: https://taskfile.dev +--- +version: "3" + +vars: + MARKDOWN_VERSION: + map: + markdownlintCli2: latest + markdownTableFormatter: latest + +tasks: + # * Install markdownlint-cli2 + install:markdownlint-cli2: + desc: Install markdownlint-cli2 + cmds: + - task: :internal:_install:npm + vars: + APP: markdownlint-cli2@{{.MARKDOWN_VERSION.markdownlintCli2}} + + # * Install markdown-table-formatter + install:markdown-table-formatter: + desc: Install markdown-table-formatter + cmds: + - task: :internal:_install:npm + vars: + APP: markdown-table-formatter@{{.MARKDOWN_VERSION.markdownTableFormatter}} + + # * Tools + tools: + desc: Install Markdown tools + vars: + ITEMS: + - markdownlint-cli2 + - markdown-table-formatter + cmds: + - for: { var: ITEMS } + task: install:{{.ITEM}} + + # * Run markdownlint-cli2 + _run:markdownlint-cli2: + internal: true + desc: Run markdownlint-cli2 + preconditions: + - sh: task -g internal:command -- markdownlint-cli2 + msg: "โŒ Error: markdownlint-cli2 is not installed. Please run 'task -g md:install:markdownlint-cli2'" + - sh: task -g internal:command:version -- "markdownlint-cli2 --help" "{{.MARKDOWN_VERSION.markdownlintCli2}}" + msg: "โŒ Version Mismatch: You are not using {{.MARKDOWN_VERSION.markdownlintCli2}} version. Please run 'task -g md:install:markdownlint-cli2'" + sources: + - "**/*.md" + vars: + # yamllint disable-line rule:quoted-strings + MODE: '{{default "" .MODE}}' + cmds: + - defer: | + {{if and .EXIT_CODE (eq .MODE "")}}echo "โš ๏ธ Markdown files are not formatted correctly. Please run 'task -g md:run:markdownlint-cli2:fix' to fix formatting issues."{{end}} + - markdownlint-cli2 "./**/*.md" --config ".github/linters/.markdownlint-cli2.yaml" {{.MODE}} + dir: "{{.USER_WORKING_DIR}}" + + # * Run markdownlint-cli2 Check + run:markdownlint-cli2:check: + desc: Run markdownlint-cli2 (check) + cmds: + - task: _run:markdownlint-cli2 + vars: { MODE: "" } + + # * Run markdownlint-cli2 Fix + run:markdownlint-cli2:fix: + desc: Run markdownlint-cli2 (fix) + cmds: + - task: _run:markdownlint-cli2 + vars: { MODE: --fix } + + # * Run markdown-table-formatter + _run:markdown-table-formatter: + internal: true + desc: Run markdown-table-formatter + preconditions: + - sh: task -g internal:command -- markdown-table-formatter + msg: "โŒ Error: markdown-table-formatter is not installed. Please run 'task -g md:install:markdown-table-formatter'" + - sh: task -g internal:command:version -- "markdown-table-formatter --version" "{{.MARKDOWN_VERSION.markdownTableFormatter}}" + msg: "โŒ Version Mismatch: You are not using {{.MARKDOWN_VERSION.markdownTableFormatter}} version. Please run 'task -g md:install:markdown-table-formatter'" + sources: + - "**/*.md" + vars: + # yamllint disable-line rule:quoted-strings + MODE: '{{default "--check" .MODE}}' + cmds: + - defer: | + {{if and .EXIT_CODE (eq .MODE "--check")}}echo "โš ๏ธ Markdown tables are not formatted correctly. Please run 'task -g md:run:markdown-table-formatter:fix' to fix formatting issues."{{end}} + - markdown-table-formatter "./**/*.md" {{.MODE}} + dir: "{{.USER_WORKING_DIR}}" + + # * Run markdown-table-formatter Check + run:markdown-table-formatter:check: + desc: Run markdown-table-formatter (check) + cmds: + - task: _run:markdown-table-formatter + vars: { MODE: --check } + + # * Run markdown-table-formatter Fix + run:markdown-table-formatter:fix: + desc: Run markdown-table-formatter (fix) + cmds: + - task: _run:markdown-table-formatter + vars: { MODE: "" } + + # * Lint Check + lint:check: + desc: Lint Markdown files (check) + cmds: + - task: run:markdownlint-cli2:check + - task: run:markdown-table-formatter:check + + # * Lint Fix + lint:fix: + aliases: + - lint + desc: Lint Markdown files (fix) + cmds: + - task: run:markdownlint-cli2:fix + - task: run:markdown-table-formatter:fix diff --git a/.taskfiles/runtime.Taskfile.yml b/.taskfiles/runtime.Taskfile.yml new file mode 100644 index 0000000..2be7c2b --- /dev/null +++ b/.taskfiles/runtime.Taskfile.yml @@ -0,0 +1,86 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json +# docs: https://taskfile.dev +--- +version: "3" + +vars: + RUNTIME_VERSION: + map: + uv: latest + fnm: latest + pwsh: latest + golang: latest + +tasks: + setup:uv: + desc: uv setup + cmds: + - cmd: | + ./scripts/setup_uv.sh "{{.RUNTIME_VERSION.uv}}" + platforms: [linux] + - task: :internal:_install:brew + vars: + APP: uv@{{.RUNTIME_VERSION.uv}} + - task: :internal:_install:winget + vars: + APP: astral-sh.uv + VERSION: "{{.RUNTIME_VERSION.uv}}" + dir: "{{.TASKFILE_DIR}}" + + setup:fnm: + desc: fnm setup + cmds: + - cmd: | + ./scripts/setup_fnm.sh "{{.RUNTIME_VERSION.fnm}}" + platforms: [linux] + - task: :internal:_install:brew + vars: + APP: fnm@{{.RUNTIME_VERSION.fnm}} + - task: :internal:_install:winget + vars: + APP: Schniz.fnm + VERSION: "{{.RUNTIME_VERSION.fnm}}" + dir: "{{.TASKFILE_DIR}}" + + setup:pwsh: + desc: pwsh setup + cmds: + - cmd: | + sudo ./scripts/setup_pwsh.sh "{{.RUNTIME_VERSION.pwsh}}" + platforms: [linux] + - cmd: | + ./scripts/setup_pwsh.sh "{{.RUNTIME_VERSION.pwsh}}" + platforms: [darwin] + - task: :internal:_install:winget + vars: + APP: Microsoft.PowerShell + VERSION: "{{.RUNTIME_VERSION.pwsh}}" + dir: "{{.TASKFILE_DIR}}" + + setup:golang: + desc: golang setup + cmds: + - cmd: | + ./scripts/setup_golang.sh "{{.RUNTIME_VERSION.golang}}" + platforms: [linux] + - task: :internal:_install:brew + vars: + APP: golang@{{.RUNTIME_VERSION.golang}} + - task: :internal:_install:winget + vars: + APP: GoLang.Go + VERSION: "{{.RUNTIME_VERSION.golang}}" + dir: "{{.TASKFILE_DIR}}" + + # * Setup + setup: + desc: Setup Runtime environments + vars: + ITEMS: + - golang + - uv + - fnm + - pwsh + cmds: + - for: { var: ITEMS } + task: setup:{{.ITEM}} diff --git a/.taskfiles/scripts/install_az.sh b/.taskfiles/scripts/install_az.sh new file mode 100644 index 0000000..7312a52 --- /dev/null +++ b/.taskfiles/scripts/install_az.sh @@ -0,0 +1,114 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly TOOL_NAME="az" +readonly KEYRING_URL="https://packages.microsoft.com/keys/microsoft.asc" +readonly KEYRING_PATH="/etc/apt/keyrings/microsoft.gpg" +readonly SOURCES_LIST="/etc/apt/sources.list.d/azure-cli.sources" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" + +log() { + echo "-> $*" >&2 +} + +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} + +usage() { + cat < "latest" +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +fi + +check_sudo + +log "Installing ${TOOL_NAME} (${VERSION}) via apt repository" + +# Check dependencies +log "Checking dependencies" +for dep in curl gpg lsb_release; do + command -v "${dep}" >/dev/null 2>&1 || die "Missing required dependency: ${dep}" +done + +# Install apt transport dependencies +log "Installing apt dependencies" +apt-get update || die "Failed to update apt cache" +apt-get install -y apt-transport-https ca-certificates gnupg || die "Failed to install apt dependencies" + +# Create keyrings directory +log "Setting up Microsoft GPG key" +# shellcheck disable=SC2174 +mkdir -p -m 755 /etc/apt/keyrings || die "Failed to create keyrings directory" + +# Download and install GPG key +if ! curl -sLS "${KEYRING_URL}" | gpg --batch --yes --dearmor -o "${KEYRING_PATH}"; then + die "Failed to download and install GPG keyring" +fi +chmod go+r "${KEYRING_PATH}" || die "Failed to set keyring permissions" + +# Add apt repository using DEB822 format +log "Adding Azure CLI apt repository" +arch="$(dpkg --print-architecture)" || die "Failed to detect architecture" +codename="$(lsb_release -cs)" || die "Failed to detect distribution codename" + +cat </dev/null || die "Failed to add apt repository" +Types: deb +URIs: https://packages.microsoft.com/repos/azure-cli/ +Suites: ${codename} +Components: main +Architectures: ${arch} +Signed-by: ${KEYRING_PATH} +EOF + +# Update apt cache and install +log "Updating apt cache" +apt-get update || die "Failed to update apt cache" + +log "Installing ${TOOL_NAME}" +if [[ "${VERSION}" == "latest" ]]; then + apt-get install -y azure-cli || die "Failed to install ${TOOL_NAME}" +else + # Install specific version (format: azure-cli=version-1~codename) + apt-get install -y "azure-cli=${VERSION}-1~${codename}" || die "Failed to install ${TOOL_NAME} version ${VERSION}" +fi + +log "โœ“ Successfully installed ${TOOL_NAME}" + +# Verify installation +"${TOOL_NAME}" --version || die "Installed binary failed to run" diff --git a/.taskfiles/scripts/install_azd.sh b/.taskfiles/scripts/install_azd.sh new file mode 100644 index 0000000..c6603e2 --- /dev/null +++ b/.taskfiles/scripts/install_azd.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly TOOL_NAME="azd" +readonly INSTALL_SCRIPT_URL="https://aka.ms/install-azd.sh" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +# Logging helper +log() { + echo "-> $*" >&2 +} + +# Error handling helper +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} + +# Help message +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +command -v curl >/dev/null 2>&1 || die "Missing required dependency: curl" + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" + +# Execute remote installation script +log "Fetching and executing official installation script" +if ! curl -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | /bin/bash -s -- --version "${VERSION}" --install-folder "${INSTALL_DIR}" --symlink-folder "${INSTALL_DIR}"; then + die "Installation failed. Check version or network connection." +fi + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" + +# Run tool version to verify +"${INSTALL_DIR}/${TOOL_NAME}" version || die "Installed binary failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/install_bicep.sh b/.taskfiles/scripts/install_bicep.sh new file mode 100644 index 0000000..b1be097 --- /dev/null +++ b/.taskfiles/scripts/install_bicep.sh @@ -0,0 +1,137 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly GITHUB_OWNER="azure" +readonly GITHUB_REPO="bicep" +readonly TOOL_NAME="bicep" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +tempDir="" + +# Logging helper +log() { + echo "-> $*" >&2 +} + +# Error handling helper +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} +# Help message +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +for dep in curl jq; do + command -v "${dep}" >/dev/null 2>&1 || die "Missing required dependency: ${dep}" +done + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +# Detect architecture +archRaw="$(uname -m)" +case "${archRaw}" in + x86_64 | amd64) arch="x64" ;; + arm64 | aarch64) arch="arm64" ;; + *) die "Unsupported architecture: ${archRaw}" ;; +esac + +log "Installing ${TOOL_NAME} (${VERSION}) for ${arch} to ${INSTALL_DIR}" + +# GitHub API authentication +ghAuthHeader=(-H "Accept: application/vnd.github+json") +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + ghAuthHeader+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +fi + +# Create temp directory +tempDir="$(mktemp -d)" || die "Failed to create temp directory" + +# Fetch release info and download URL +log "Fetching release information" +if [[ "${VERSION}" == "latest" ]]; then + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest" +else + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/tags/${VERSION}" +fi + +releaseJson="${tempDir}/release.json" +if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then + die "Failed to fetch release information. Check version or network connection." +fi + +# Extract download URL +downloadUrl="$(jq -r --arg arch "${arch}" \ + '.assets[] | select(.browser_download_url | endswith("-linux-\($arch)")) | .browser_download_url' \ + "${releaseJson}")" + +[[ -n "${downloadUrl}" ]] || die "No asset found for ${arch}" + +log "Downloading ${downloadUrl}" +binaryPath="${tempDir}/${TOOL_NAME}" +curl -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${binaryPath}" || die "Download failed" + +# Install binary +log "Installing binary" +install -Dm0755 "${binaryPath}" "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to install binary" + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" + +# Run tool version to verify +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/install_golangci-lint.sh b/.taskfiles/scripts/install_golangci-lint.sh new file mode 100644 index 0000000..7fe6209 --- /dev/null +++ b/.taskfiles/scripts/install_golangci-lint.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly GITHUB_OWNER="golangci" +readonly GITHUB_REPO="golangci-lint" +readonly TOOL_NAME="golangci-lint" +readonly INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/master/install.sh" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +# Logging helper +log() { + echo "-> $*" >&2 +} + +# Error handling helper +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} + +# Help message +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +command -v curl >/dev/null 2>&1 || die "Missing required dependency: curl" + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" + +# GitHub API authentication +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + ghAuthHeader=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +else + ghAuthHeader=() +fi + +# Execute remote installation script +log "Fetching and executing official installation script" +if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | sh -s -- -b "${INSTALL_DIR}" "${VERSION}"; then + die "Installation failed. Check version or network connection." +fi + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" + +# Run tool version to verify +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed binary failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/setup_fnm.sh b/.taskfiles/scripts/setup_fnm.sh new file mode 100644 index 0000000..55abd19 --- /dev/null +++ b/.taskfiles/scripts/setup_fnm.sh @@ -0,0 +1,145 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly TOOL_NAME="fnm" +readonly INSTALL_SCRIPT_URL="https://fnm.vercel.app/install" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +# Logging helper +log() { + echo "-> $*" >&2 +} + +# Error handling helper +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} + +# Help message +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + INSTALL_DIR="${HOME}/.local/share/fnm" +fi + +# Check dependencies +command -v curl >/dev/null 2>&1 || die "Missing required dependency: curl" + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" + +# GitHub API authentication +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + ghAuthHeader=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +else + ghAuthHeader=() +fi + +# Execute remote installation script +log "Fetching and executing official installation script" +if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | bash -s -- --skip-shell --install-dir "${INSTALL_DIR}" --release "${VERSION}"; then + die "Installation failed. Check version or network connection." +fi + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}" + +# Run tool version to verify +"${INSTALL_DIR}/fnm" --version || die "Installed binary failed to run (${INSTALL_DIR}/fnm)" + +# Create symlink in ~/.local/bin +SYMLINK_DIR="${HOME}/.local/bin" +SYMLINK_PATH="${SYMLINK_DIR}/${TOOL_NAME}" + +if [[ ! -d "${SYMLINK_DIR}" ]]; then + mkdir -p "${SYMLINK_DIR}" || die "Cannot create symlink directory ${SYMLINK_DIR}" +fi + +if [[ -L "${SYMLINK_PATH}" ]]; then + log "Removing existing symlink at ${SYMLINK_PATH}" + rm -f "${SYMLINK_PATH}" +elif [[ -e "${SYMLINK_PATH}" ]]; then + die "Cannot create symlink: ${SYMLINK_PATH} already exists and is not a symlink" +fi + +ln -s "${INSTALL_DIR}/${TOOL_NAME}" "${SYMLINK_PATH}" || die "Failed to create symlink ${SYMLINK_PATH}" +log "โœ“ Created symlink: ${SYMLINK_PATH} -> ${INSTALL_DIR}/${TOOL_NAME}" + +# Function to add environment variables to shell config +add_to_shell() { + local shell_config="$1" + local shell_type="$2" + + if [[ -f "${shell_config}" ]]; then + # Array of environment variables to add + local env_vars=( + "export PATH=\$PATH:${INSTALL_DIR}" + ) + + # Add each variable if not already present + for str in "${env_vars[@]}"; do + if ! grep -qF "${str}" "${shell_config}"; then + echo "${str}" >>"${shell_config}" + fi + done + + # Add fnm env command if not already present + if ! grep -q "fnm env" "${shell_config}"; then + echo "eval \"\$(fnm env --use-on-cd --shell ${shell_type})\"" >>"${shell_config}" + fi + fi +} + +# Configure both bash and zsh if present +add_to_shell ~/.bashrc bash +add_to_shell ~/.zshrc zsh +add_to_shell ~/.config/fish/config.fish fish + +# Verify installation +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed binary failed to run" + +# Since this script runs in bash, only evaluate fnm env for bash +log "Setting up environment for current session" +eval "$("${INSTALL_DIR}/${TOOL_NAME}" env --use-on-cd --shell bash)" diff --git a/.taskfiles/scripts/setup_golang.sh b/.taskfiles/scripts/setup_golang.sh new file mode 100644 index 0000000..f847d4e --- /dev/null +++ b/.taskfiles/scripts/setup_golang.sh @@ -0,0 +1,184 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly TOOL_NAME="go" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +tempFile="" + +# Logging helper +log() { + echo "-> $*" >&2 +} + +# Error handling helper +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} +# Help message +usage() { + cat < "latest" +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +fi + +# Check dependencies +for dep in curl jq wget; do + command -v "${dep}" >/dev/null 2>&1 || die "Missing required dependency: ${dep}" +done + +# Determine Go version to install +if [[ "${VERSION}" != "latest" ]]; then + # Strip 'go' prefix if present and add it back + VERSION="${VERSION#go}" + goVersion="go${VERSION}" +else + log "Fetching latest Go version" + goVersion=$(curl -fsSL --proto '=https' --tlsv1.3 "https://go.dev/dl/?mode=json" | jq -r '.[0].version') +fi + +# Detect architecture +archRaw="$(uname -m)" +case "${archRaw}" in + x86_64 | amd64) arch="amd64" ;; + arm64 | aarch64) arch="arm64" ;; + armv7l | armv6l) arch="armv6l" ;; + i386 | i686) arch="386" ;; + *) die "Unsupported architecture: ${archRaw}" ;; +esac + +goFile="${goVersion}.linux-${arch}.tar.gz" + +# Determine install directory and installation mode +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local" + installMode="system" + else + INSTALL_DIR="${HOME}/.local" + installMode="user" + fi +else + # Custom install dir - determine mode based on permissions + if [[ "${EUID}" -eq 0 ]]; then + installMode="system" + else + installMode="user" + fi +fi + +goInstallPath="${INSTALL_DIR}/go" + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +log "Installing ${TOOL_NAME} (${goVersion}) for ${arch} to ${goInstallPath}" + +# Check if Go is already installed and matches the desired version +if [[ -x "${goInstallPath}/bin/go" ]]; then + installedVersion=$("${goInstallPath}/bin/go" version | awk '{print $3}') + if [[ "${installedVersion}" == "${goVersion}" ]]; then + log "โœ“ Go ${goVersion} is already installed. Skipping installation." + exit 0 + else + log "Go ${installedVersion} is installed, but ${goVersion} is required. Updating..." + fi +fi + +# Download Go +log "Downloading ${goFile}" +tempFile="${goFile}" +if ! wget -q "https://golang.org/dl/${goFile}"; then + die "Failed to download Go ${goVersion}" +fi + +# Remove existing installation and extract new one +log "Extracting Go to ${INSTALL_DIR}" +rm -rf "${goInstallPath}" +tar -C "${INSTALL_DIR}" -xzf "${goFile}" || die "Failed to extract Go archive" + +log "โœ“ Successfully installed ${TOOL_NAME} to ${goInstallPath}" + +# Configure environment based on installation mode +if [[ "${installMode}" == "system" ]]; then + # System-wide installation - create profile.d script + if [[ ! -f /etc/profile.d/golang.sh ]]; then + log "Setting up system-wide environment variables" + # shellcheck disable=SC2016 + echo "export PATH=\"\$PATH:${goInstallPath}/bin\"" | tee /etc/profile.d/golang.sh >/dev/null + fi +else + # User installation - add to shell configs + log "Setting up user environment variables" + + # Function to add environment variables to shell config + add_to_shell() { + local shell_config="$1" + + if [[ -f "${shell_config}" ]]; then + # Array of environment variables to add + # shellcheck disable=SC2016 + local env_vars=( + "export PATH=\$PATH:${goInstallPath}/bin" + 'export GOPATH=$HOME/go' + 'export PATH=$PATH:$GOPATH/bin' + ) + + # Add each variable if not already present + for str in "${env_vars[@]}"; do + if ! grep -qF "${str}" "${shell_config}"; then + echo "${str}" >>"${shell_config}" + fi + done + fi + } + + # Configure both bash and zsh if present + add_to_shell ~/.bashrc + add_to_shell ~/.zshrc + + log "Note: Restart your shell or run 'source ~/.bashrc' to use Go" +fi + +# Verify installation +"${goInstallPath}/bin/go" version || die "Installed binary failed to run" diff --git a/.taskfiles/scripts/setup_pwsh.sh b/.taskfiles/scripts/setup_pwsh.sh new file mode 100644 index 0000000..cdaabd5 --- /dev/null +++ b/.taskfiles/scripts/setup_pwsh.sh @@ -0,0 +1,146 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly TOOL_NAME="pwsh" +readonly KEYRING_URL_BASE="https://packages.microsoft.com/config" +readonly PACKAGES_DEB="packages-microsoft-prod.deb" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" + +tempFile="" + +log() { + echo "-> $*" >&2 +} + +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} +usage() { + cat < "latest" +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +fi + +os="$(uname -s | tr '[:upper:]' '[:lower:]')" + +if [[ "${os}" == "darwin" ]]; then + log "Installing ${TOOL_NAME} (${VERSION}) using Homebrew" + + command -v brew >/dev/null 2>&1 || die "brew is not installed. Install it from https://brew.sh/" + + if [[ "${VERSION}" != "latest" ]]; then + log "Note: version pinning isn't supported by this Homebrew installer path; installing latest stable." + fi + + brew install --cask powershell || die "Failed to install PowerShell via Homebrew" + + log "โœ“ Successfully installed ${TOOL_NAME}" + pwsh --version >/dev/null 2>&1 || die "Installed binary failed to run" + exit 0 +fi + +if [[ "${os}" != "linux" ]]; then + die "Unsupported OS: ${os}" +fi + +check_sudo + +if [[ "${VERSION}" != "latest" ]]; then + die "This installer uses the Microsoft package repository method and installs the latest available version. For specific versions, use the official direct-download method." +fi + +log "Installing ${TOOL_NAME} (${VERSION}) via Microsoft package repository" + +log "Checking dependencies" +for dep in apt-get wget dpkg; do + command -v "${dep}" >/dev/null 2>&1 || die "Missing required dependency: ${dep}" +done + +log "Detecting distribution" +[[ -f /etc/os-release ]] || die "Missing /etc/os-release" +# shellcheck disable=SC1091 +source /etc/os-release + +case "${ID:-}" in + debian) + distro="debian" + ;; + ubuntu) + distro="ubuntu" + ;; + *) + die "Unsupported Linux distribution: ${ID:-unknown}" + ;; +esac + +[[ -n "${VERSION_ID:-}" ]] || die "Unable to determine VERSION_ID from /etc/os-release" + +log "Updating package lists" +apt-get update || die "Failed to update apt cache" + +log "Installing prerequisites" +apt-get install -y wget || die "Failed to install prerequisites" + +log "Setting up Microsoft repository" +tempFile="${PACKAGES_DEB}" +repoUrl="${KEYRING_URL_BASE}/${distro}/${VERSION_ID}/${PACKAGES_DEB}" + +if ! wget -q "${repoUrl}"; then + die "Failed to download Microsoft repository configuration: ${repoUrl}" +fi + +dpkg -i "${PACKAGES_DEB}" || die "Failed to register Microsoft repository keys" +rm -f "${PACKAGES_DEB}" || true +tempFile="" + +log "Updating package lists (post-repo)" +apt-get update || die "Failed to update apt cache" + +log "Installing PowerShell" +apt-get install -y powershell || die "Failed to install ${TOOL_NAME}" + +log "โœ“ Successfully installed ${TOOL_NAME}" +"${TOOL_NAME}" -Version >/dev/null 2>&1 || die "Installed binary failed to run" diff --git a/.taskfiles/scripts/setup_uv.sh b/.taskfiles/scripts/setup_uv.sh new file mode 100644 index 0000000..6a7a58f --- /dev/null +++ b/.taskfiles/scripts/setup_uv.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly TOOL_NAME="uv" +readonly INSTALL_SCRIPT_URL="https://astral.sh/uv/install.sh" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +# Logging helper +log() { + echo "-> $*" >&2 +} + +# Error handling helper +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} + +# Help message +usage() { + cat < "latest", strip "v" prefix if present +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" ]]; then + # uv installer expects version without 'v' prefix + VERSION="${VERSION#v}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +command -v curl >/dev/null 2>&1 || die "Missing required dependency: curl" + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" + +# GitHub API authentication +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + ghAuthHeader=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +else + ghAuthHeader=() +fi + +log "INSTALL_DIR=${INSTALL_DIR}" +log "VERSION=${VERSION}" + +# Execute remote installation script +log "Fetching and executing official installation script" +if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | env UV_INSTALL_DIR="${INSTALL_DIR}" UV_VERSION="${VERSION}" UV_GITHUB_TOKEN="${GITHUB_TOKEN:-}" sh; then + die "Installation failed. Check version or network connection." +fi + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/uv" + +# Run tool version to verify +"${INSTALL_DIR}/uv" --version || die "Installed binary failed to run (${INSTALL_DIR}/uv)" diff --git a/.taskfiles/yaml.Taskfile.yml b/.taskfiles/yaml.Taskfile.yml new file mode 100644 index 0000000..a984620 --- /dev/null +++ b/.taskfiles/yaml.Taskfile.yml @@ -0,0 +1,49 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json +# docs: https://taskfile.dev +--- +version: "3" + +vars: + YAML_VERSION: + map: + yamllint: latest + +tasks: + # * Install YAMLlint + install:yamllint: + desc: Install YAMLlint + cmds: + - task: :internal:_install:uv + vars: + APP: yamllint@{{.YAML_VERSION.yamllint}} + + # * Tools + tools: + desc: Install YAML tools + vars: + ITEMS: + - yamllint + cmds: + - for: { var: ITEMS } + task: install:{{.ITEM}} + + # * Run YAMLlint + run:yamllint: + desc: Run YAMLlint + preconditions: + - sh: task -g internal:command -- yamllint + msg: "โŒ Error: yamllint is not installed. Please run 'task -g yml:install:yamllint'" + - sh: task -g internal:command:version -- "yamllint --version" "{{.YAML_VERSION.yamllint}}" + msg: "โŒ Version Mismatch: You are not using {{.YAML_VERSION.yamllint}} version. Please run 'task -g yml:install:yamllint'" + sources: + - "**/*.yml" + - "**/*.yaml" + cmds: + - yamllint --config-file .github/linters/.yamllint.yml --format auto --strict . + dir: "{{.USER_WORKING_DIR}}" + + # * Lint + lint: + desc: Lint YAML files + cmds: + - task: run:yamllint diff --git a/.yamlignore b/.yamlignore new file mode 100644 index 0000000..1c3da07 --- /dev/null +++ b/.yamlignore @@ -0,0 +1,5 @@ + **/.terraform/** + **/.venv/** + **/venv/** + **/.git/** + **/node_modules/** diff --git a/Taskfile.yml b/Taskfile.yml index 5dc1767..ed3471e 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -3,16 +3,65 @@ --- version: "3" -dotenv: [dev.env] +set: [pipefail] +shopt: [globstar] + +dotenv: [.env, dev.env, ".{{.ENV}}/.env", "{{.ENV}}/.env"] vars: PROJECT_NAME: azmpf - BUILD_DEV_OUTPUT_DIR: "bin/{{OS}}-{{ARCH}}" + BUILD_DEV_OUTPUT_DIR: bin/{{OS}}-{{ARCH}} BUILD_DEV_ARTIFACT: "{{.BUILD_DEV_OUTPUT_DIR}}/{{.PROJECT_NAME}}{{exeExt}}" - PWSH: pwsh -NoProfile -NonInteractive -NoLogo -Command - PWSH_SCRIPT: pwsh -NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass -File + +includes: + internal: .taskfiles/_internal.Taskfile.yml + runtime: .taskfiles/runtime.Taskfile.yml + az: .taskfiles/azure.Taskfile.yml + go: .taskfiles/golang.Taskfile.yml + md: .taskfiles/markdown.Taskfile.yml + yml: .taskfiles/yaml.Taskfile.yml tasks: + default: + desc: List available tasks + silent: true + cmds: + - task --list + + sysinfo: + desc: Display system information + silent: true + cmds: + # yamllint disable-line rule:quoted-strings + - 'echo "OS: {{OS}}"' # windows, linux, darwin + # yamllint disable-line rule:quoted-strings + - 'echo "Architecture: {{ARCH}}"' # amd64, arm64, etc. + # yamllint disable-line rule:quoted-strings + - 'echo "CPU cores: {{numCPU}}"' # Number of CPU cores + + tools: + desc: Install tools + vars: + ITEMS: + - az + - go + - md + - yml + cmds: + - for: { var: ITEMS } + task: "{{.ITEM}}:tools" + + lint: + desc: Lint files + vars: + ITEMS: + - go + - md + - yml + deps: + - for: { var: ITEMS } + task: "{{.ITEM}}:lint" + deps: desc: Check if dependencies are up to date cmds: @@ -36,81 +85,6 @@ tasks: - defer: task: deps - lint: - desc: Run linters - cmds: - - task: lint:files - - task: lint:go - - task: lint:tf - - task: lint:md - - lint:files: - desc: Run linters for various file types - cmds: - - copywrite headers - - copywrite license - - lint:go: - desc: Run Go linters - cmds: - - task: govulncheck - - task: golangci-lint - - govulncheck: - desc: Run govulncheck - cmds: - - govulncheck -test -show verbose ./... - - golangci-lint: - desc: Run golangci-lint - cmds: - - golangci-lint run --fix ./... - - lint:tf: - desc: Run Terraform linters - cmds: - - terraform fmt -recursive - - tflint --recursive - - tfsec . - - checkov --directory . - - lint:md: - desc: Run Markdown linters - cmds: - - markdownlint-cli2 "./**/*.md" --config "./.github/linters/.markdownlint-cli2.yaml" --fix - - lint:links: - desc: Run link checkers - cmds: - - lychee --config ./.github/linters/.lychee.toml --format markdown . - - tools: - desc: Install required tools - cmds: - - for: - [ - semver, - copywrite, - changie, - dlv, - goimports, - golangci-lint, - gofumpt, - goreleaser, - govulncheck, - yamllint, - markdownlint, - ] - task: install:{{.ITEM}} - - task: test:tools - - task: lint:tf-tools - - lint:tf-tools: - desc: Install Terraform lint tools - cmds: - - for: [tflint, tfsec, checkov] - task: install:{{.ITEM}} - # ---------------------- # Build # ---------------------- @@ -212,6 +186,7 @@ tasks: _teste2e:run: internal: true vars: + # yamllint disable-line rule:quoted-strings FORMAT: '{{if eq .GITHUB_ACTIONS "true"}}github-actions{{else}}pkgname-and-test-fails{{end}}' env: MPF_TFPATH: "{{.MPF_TFPATH}}" @@ -223,28 +198,32 @@ tasks: testunit: desc: Run unit tests cmds: + # yamllint disable-line rule:quoted-strings - 'gotestsum --format-hivis --format {{.FORMAT}} --jsonfile "testresults.json" ./pkg/domain ./pkg/infrastructure/ARMTemplateShared ./pkg/infrastructure/mpfSharedUtils ./pkg/infrastructure/authorizationCheckers/terraform -p {{numCPU}} -timeout 5m -ldflags="{{.LDFLAGS}}" -coverprofile="coverage.out" -covermode atomic' - task: _test:getcover vars: TEST_NAME: "{{if gt (len (splitArgs .CLI_ARGS)) 0}}{{index (splitArgs .CLI_ARGS) 0}}{{end}}" TEST_PATH: "{{if gt (len (splitArgs .CLI_ARGS)) 1}}{{index (splitArgs .CLI_ARGS) 1}}{{else}}./...{{end}}" + # yamllint disable-line rule:quoted-strings FORMAT: '{{if eq .GITHUB_ACTIONS "true"}}github-actions{{else}}pkgname-and-test-fails{{end}}' - LDFLAGS: "-s -w -X main.version=testUnit" + LDFLAGS: -s -w -X main.version=testUnit testcli:arm: desc: Run CLI tests for ARM vars: + # yamllint disable-line rule:quoted-strings EXPECTED: '{{if eq OS "windows"}}13{{else}}14{{end}}' cmds: - task: _testcli:validate vars: - CMD: "go run ./cmd arm --templateFilePath ./samples/templates/aks-private-subnet.json --parametersFilePath ./samples/templates/aks-private-subnet-parameters.json" + CMD: go run ./cmd arm --templateFilePath ./samples/templates/aks-private-subnet.json --parametersFilePath ./samples/templates/aks-private-subnet-parameters.json EXPECTED: "{{.EXPECTED}}" PRE_CMD: "" testcli:bicep: desc: Run CLI tests for Bicep vars: + # yamllint disable-line rule:quoted-strings EXPECTED: '{{if eq OS "windows"}}13{{else}}14{{end}}' MPF_BICEPEXECPATH: sh: | @@ -258,13 +237,14 @@ tasks: cmds: - task: _testcli:validate vars: - CMD: "go run ./cmd bicep --bicepFilePath ./samples/bicep/aks-private-subnet.bicep --parametersFilePath ./samples/bicep/aks-private-subnet-params.json --bicepExecPath \"{{.MPF_BICEPEXECPATH}}\"" + CMD: go run ./cmd bicep --bicepFilePath ./samples/bicep/aks-private-subnet.bicep --parametersFilePath ./samples/bicep/aks-private-subnet-params.json --bicepExecPath \"{{.MPF_BICEPEXECPATH}}\" EXPECTED: "{{.EXPECTED}}" PRE_CMD: "" testcli:terraform: desc: Run CLI tests for Terraform vars: + # yamllint disable-line rule:quoted-strings EXPECTED: '{{if eq OS "windows"}}12{{else}}13{{end}}' MPF_TFPATH: sh: | @@ -278,17 +258,10 @@ tasks: cmds: - task: _testcli:validate vars: - CMD: "go run ./cmd terraform --workingDir ./samples/terraform/aci --varFilePath ./samples/terraform/aci/dev.vars.tfvars --tfPath \"{{.MPF_TFPATH}}\"" + CMD: go run ./cmd terraform --workingDir ./samples/terraform/aci --varFilePath ./samples/terraform/aci/dev.vars.tfvars --tfPath \"{{.MPF_TFPATH}}\" EXPECTED: "{{.EXPECTED}}" PRE_CMD: | - {{if eq OS "windows"}} - if (-not $env:USERPROFILE) { $env:USERPROFILE = "C:\Users\runneradmin" } - if (-not $env:HOMEDRIVE) { $env:HOMEDRIVE = "C:" } - if (-not $env:HOMEPATH) { $env:HOMEPATH = "\Users\runneradmin" } - Write-Host "Terraform path: $env:MPF_TFPATH" - {{else}} echo "Terraform path: $MPF_TFPATH" - {{end}} teste2e:arm: desc: Run E2E tests for ARM @@ -332,27 +305,31 @@ tasks: desc: Run acceptance tests cmds: - go clean -testcache + # yamllint disable-line rule:quoted-strings - 'gotestsum --format-hivis --format {{.FORMAT}} --jsonfile "testresults.json" -- {{.TEST_PATH}} -run "^TestAcc_{{.TEST_NAME}}" -p {{numCPU}} -timeout 30m -ldflags="{{.LDFLAGS}}" -coverprofile="coverage.out" -covermode atomic' - task: _test:getcover vars: TEST_NAME: "{{if gt (len (splitArgs .CLI_ARGS)) 0}}{{index (splitArgs .CLI_ARGS) 0}}{{end}}" TEST_PATH: "{{if gt (len (splitArgs .CLI_ARGS)) 1}}{{index (splitArgs .CLI_ARGS) 1}}{{else}}./...{{end}}" + # yamllint disable-line rule:quoted-strings FORMAT: '{{if eq .GITHUB_ACTIONS "true"}}github-actions{{else}}pkgname-and-test-fails{{end}}' - LDFLAGS: "-s -w -X main.version=testAcc" + LDFLAGS: -s -w -X main.version=testAcc test: desc: Run tests cmds: - go clean -testcache - go test -failfast -run ^TestDevEnv_WellKnown$ ./internal/testhelp + # yamllint disable-line rule:quoted-strings - 'gotestsum --format-hivis --format {{.FORMAT}} --jsonfile "testresults.json" -- {{.TEST_PATH}} -run "^Test(Acc|Unit)_{{.TEST_NAME}}" -p {{numCPU}} -timeout 30m -ldflags="{{.LDFLAGS}}" -coverprofile="coverage.out" -covermode atomic -coverpkg={{.GO_PKGS}}' - task: _test:getcover vars: TEST_NAME: "{{if gt (len (splitArgs .CLI_ARGS)) 0}}{{index (splitArgs .CLI_ARGS) 0}}{{end}}" TEST_PATH: "{{if gt (len (splitArgs .CLI_ARGS)) 1}}{{index (splitArgs .CLI_ARGS) 1}}{{else}}./...{{end}}" + # yamllint disable-line rule:quoted-strings FORMAT: '{{if eq .GITHUB_ACTIONS "true"}}github-actions{{else}}pkgname-and-test-fails{{end}}' - LDFLAGS: "-s -w -X main.version=testAcc" - GO_PKGS_EXCLUDE: "/testhelp|/fakes|/terraform-provider-fabric" + LDFLAGS: -s -w -X main.version=testAcc + GO_PKGS_EXCLUDE: /testhelp|/fakes|/terraform-provider-fabric GO_PKGS: sh: | {{if eq OS "windows"}} @@ -364,25 +341,6 @@ tasks: TF_LOG: error TF_ACC: 1 - testacc:setup: - desc: Setup acceptence/development test environment - dotenv: ["wellknown.env"] - preconditions: - - sh: | - {{if ne OS "windows"}} - command -v pwsh &>/dev/null || exit 1 - {{end}} - msg: "First install PowerShell: https://learn.microsoft.com/powershell/scripting/install/installing-powershell" - cmds: - - cmd: | - {{.PWSH_SCRIPT}} ./tools/scripts/Set-WellKnown.ps1 - - test:tools: - desc: Install test tools - cmds: - - for: [gotestsum, gocover-cobertura] - task: install:{{.ITEM}} - _test:getcover: desc: Get coverage results internal: true @@ -404,7 +362,7 @@ tasks: sh: semver up beta env: - CHANGIE_GITHUB_REPOSITORY: "microsoft/terraform-provider-fabric" + CHANGIE_GITHUB_REPOSITORY: microsoft/terraform-provider-fabric cmds: - echo "{{.SEMVER}}" - cmd: | @@ -419,182 +377,6 @@ tasks: - cmd: | gh pr create --title "feat(release): {{.SEMVER}}" --body-file ".changes/{{.SEMVER}}.md" --label "skip-changelog" - # ---------------------- - # Install Helpers - # ---------------------- - install:bicep: - desc: Install Bicep - cmds: - - cmd: | - curl -Lo bicep https://github.com/Azure/bicep/releases/latest/download/bicep-linux-x64 - chmod +x ./bicep - sudo mv ./bicep /usr/local/bin/bicep - platforms: [linux] - - cmd: | - curl -Lo bicep https://github.com/Azure/bicep/releases/latest/download/bicep-osx-x64 - chmod +x ./bicep - sudo spctl --add ./bicep - sudo mv ./bicep /usr/local/bin/bicep - platforms: [darwin] - - cmd: | - {{.PWSH}} ' - $installPath = "$env:USERPROFILE\.bicep" - $installDir = New-Item -ItemType Directory -Path $installPath -Force - $installDir.Attributes += "Hidden" - (New-Object Net.WebClient).DownloadFile("https://github.com/Azure/bicep/releases/latest/download/bicep-win-x64.exe", "$installPath\bicep.exe") - $currentPath = (Get-Item -path "HKCU:\Environment" ).GetValue("Path", "", "DoNotExpandEnvironmentNames") - if (-not $currentPath.Contains("%USERPROFILE%\.bicep")) { setx PATH ($currentPath + ";%USERPROFILE%\.bicep") } - if (-not $env:path.Contains($installPath)) { $env:path += ";$installPath" } - if ($env:GITHUB_PATH) { - echo "$installPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - } - ' - platforms: [windows] - - install:semver: - desc: Install semver - cmds: - - go install github.com/maykonlsf/semver-cli/cmd/semver@latest - - install:copywrite: - desc: Install copywrite - cmds: - - go install github.com/hashicorp/copywrite@latest - - install:changie: - desc: Install changie - cmds: - - go install github.com/miniscruff/changie@latest - - install:gotestsum: - desc: Install gotestsum - cmds: - - go install gotest.tools/gotestsum@latest - - install:dlv: - desc: Install dlv - cmds: - - go install github.com/go-delve/delve/cmd/dlv@latest - - install:gocover-cobertura: - desc: Install gocover-cobertura - cmds: - - go install github.com/boumenot/gocover-cobertura@latest - - install:tfplugindocs: - desc: Install tfplugindocs - cmds: - - go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@latest - - install:goimports: - desc: Install goimports - cmds: - - go install golang.org/x/tools/cmd/goimports@latest - - install:golangci-lint: - desc: Install golangci-lint - cmds: - - cmd: winget install GolangCI.golangci-lint - ignore_error: true - platforms: [windows] - - cmd: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)/bin" latest - platforms: [linux] - - cmd: brew install golangci-lint - platforms: [darwin] - - install:gofumpt: - desc: Install gofumpt - cmds: - - go install mvdan.cc/gofumpt@latest - - install:goreleaser: - desc: Install goreleaser - cmds: - - cmd: | - echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list - sudo apt update - sudo apt install goreleaser - platforms: [linux] - - cmd: brew install goreleaser - platforms: [darwin] - - cmd: winget install goreleaser.goreleaser - platforms: [windows] - - install:tfproviderlintx: - desc: Install tfproviderlintx - cmds: - - go install github.com/bflad/tfproviderlint/cmd/tfproviderlintx@latest - - install:markdownlint: - desc: Install markdownlint - cmds: - - npm install -g markdownlint-cli2 - - install:tflint: - desc: Install tflint - cmds: - - go install github.com/terraform-linters/tflint@latest - - install:tfsec: - desc: Install tfsec - cmds: - - go install github.com/aquasecurity/tfsec/cmd/tfsec@latest - - install:govulncheck: - desc: Install govulncheck - cmds: - - go install golang.org/x/vuln/cmd/govulncheck@latest - - install:checkov: - desc: Install checkov - preconditions: - - sh: | - {{if eq OS "windows"}} - {{.PWSH}} 'if (-not (Get-Command pipx -ErrorAction SilentlyContinue)) { exit 1 }' - {{else}} - command -v pipx &>/dev/null || exit 1 - {{end}} - msg: "First install pipx: https://pipx.pypa.io/" - cmds: - - pipx install --force checkov - - install:mkdocs: - desc: Install mkdocs - preconditions: - - sh: | - {{if eq OS "windows"}} - {{.PWSH}} 'if (-not (Get-Command pipx -ErrorAction SilentlyContinue)) { exit 1 }' - {{else}} - command -v pipx &>/dev/null || exit 1 - {{end}} - msg: "First install pipx: https://pipx.pypa.io/" - cmds: - - pipx install --force mkdocs - - pipx inject --include-deps --force mkdocs $(mkdocs get-deps) - - install:yamllint: - desc: Install yamllint - preconditions: - - sh: | - {{if eq OS "windows"}} - {{.PWSH}} 'if (-not (Get-Command pipx -ErrorAction SilentlyContinue)) { exit 1 }' - {{else}} - command -v pipx &>/dev/null || exit 1 - {{end}} - msg: "First install pipx: https://pipx.pypa.io/" - cmds: - - pipx install --force yamllint - - install:lychee: - desc: Install lychee - cmds: - - cmd: winget install lycheeverse.lychee - platforms: [windows] - - cmd: cargo install lychee - ignore_error: true - platforms: [linux] - - cmd: brew install lychee - platforms: [darwin] tf:clean: desc: Clean up Terraform files @@ -606,3 +388,26 @@ tasks: {{.PWSH}} 'Get-ChildItem -Path ./ -Include ".external_modules",".terraform" -Directory -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force; Get-ChildItem -Path ./ -Include "*.terraform.lock.*","*.tfstate*","terraform.log" -File -Recurse -ErrorAction SilentlyContinue | Remove-Item -Force' platforms: [windows] dir: "{{.ROOT_DIR}}" + + diff: + desc: Check for differences + silent: true + status: + - git diff --shortstat --exit-code + cmds: + - cmd: | + echo "โš ๏ธโš ๏ธโš ๏ธ Unexpected difference. Run 'task -g {{.CLI_ARGS}}' command and commit. โš ๏ธโš ๏ธโš ๏ธ" + echo + echo "๐Ÿ›‘๐Ÿ›‘๐Ÿ›‘ Uncommitted changes ๐Ÿ›‘๐Ÿ›‘๐Ÿ›‘" + echo + git status --short + echo + echo "๐Ÿ“๐Ÿ“๐Ÿ“ Summary ๐Ÿ“๐Ÿ“๐Ÿ“" + echo + git diff --compact-summary + echo + echo "๐Ÿ”๐Ÿ”๐Ÿ” Differences ๐Ÿ”๐Ÿ”๐Ÿ”" + echo + git --no-pager diff + echo + exit 1 From 91d36948e1ebf64fa965729a764264c82201128f Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:58:33 -0800 Subject: [PATCH 02/34] fix: add sudo to uv setup command for proper execution Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .taskfiles/runtime.Taskfile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.taskfiles/runtime.Taskfile.yml b/.taskfiles/runtime.Taskfile.yml index 2be7c2b..2f24bc1 100644 --- a/.taskfiles/runtime.Taskfile.yml +++ b/.taskfiles/runtime.Taskfile.yml @@ -16,7 +16,7 @@ tasks: desc: uv setup cmds: - cmd: | - ./scripts/setup_uv.sh "{{.RUNTIME_VERSION.uv}}" + sudo ./scripts/setup_uv.sh "{{.RUNTIME_VERSION.uv}}" platforms: [linux] - task: :internal:_install:brew vars: From 48f422977536b8d0fc7fb6136a3e4c709e31ccb5 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:04:57 -0800 Subject: [PATCH 03/34] ci: update task commands for consistency and improve script permissions Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/e2e-tests.yaml | 4 +-- .taskfiles/_internal.Taskfile.yml | 16 ++++++------ .taskfiles/golang.Taskfile.yml | 29 +++++++++++++++------ .taskfiles/markdown.Taskfile.yml | 20 +++++++------- .taskfiles/scripts/install_az.sh | 0 .taskfiles/scripts/install_azd.sh | 0 .taskfiles/scripts/install_bicep.sh | 0 .taskfiles/scripts/install_golangci-lint.sh | 0 .taskfiles/scripts/setup_fnm.sh | 0 .taskfiles/scripts/setup_golang.sh | 0 .taskfiles/scripts/setup_pwsh.sh | 0 .taskfiles/scripts/setup_uv.sh | 0 .taskfiles/yaml.Taskfile.yml | 8 +++--- Taskfile.yml | 2 +- 15 files changed, 47 insertions(+), 34 deletions(-) mode change 100644 => 100755 .taskfiles/scripts/install_az.sh mode change 100644 => 100755 .taskfiles/scripts/install_azd.sh mode change 100644 => 100755 .taskfiles/scripts/install_bicep.sh mode change 100644 => 100755 .taskfiles/scripts/install_golangci-lint.sh mode change 100644 => 100755 .taskfiles/scripts/setup_fnm.sh mode change 100644 => 100755 .taskfiles/scripts/setup_golang.sh mode change 100644 => 100755 .taskfiles/scripts/setup_pwsh.sh mode change 100644 => 100755 .taskfiles/scripts/setup_uv.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f32280..a92cc17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: run: task deps:download - name: ๐Ÿ”จ Setup Build tools - run: task install:goreleaser + run: task go:install:goreleaser - name: ๐Ÿ—๏ธ Build run: task build diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index b2b7b62..f088020 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -48,11 +48,11 @@ jobs: run: task deps:download - name: ๐Ÿ”จ Setup Test tools - run: task test:tools + run: task go:tools:test - name: ๐Ÿ“ฆ Install Bicep if: matrix.type == 'bicep' - run: task install:bicep + run: task az:install:bicep - name: ๐Ÿšง Setup Terraform if: matrix.type == 'terraform' diff --git a/.taskfiles/_internal.Taskfile.yml b/.taskfiles/_internal.Taskfile.yml index 3db45b9..b2fb365 100644 --- a/.taskfiles/_internal.Taskfile.yml +++ b/.taskfiles/_internal.Taskfile.yml @@ -64,7 +64,7 @@ tasks: desc: go install internal: true preconditions: - - sh: task -g internal:command -- go + - sh: task internal:command -- go msg: "go is not installed. Please install go: https://go.dev/doc/install" requires: vars: [APP] @@ -75,7 +75,7 @@ tasks: desc: winget install internal: true preconditions: - - sh: task -g internal:command -- winget + - sh: task internal:command -- winget msg: "winget is not installed. Please install winget: https://learn.microsoft.com/windows/package-manager/winget/" requires: vars: [APP] @@ -91,7 +91,7 @@ tasks: desc: brew install internal: true preconditions: - - sh: task -g internal:command -- brew + - sh: task internal:command -- brew msg: brew is not installed. Please install brew. requires: vars: [APP] @@ -103,7 +103,7 @@ tasks: desc: brew tap internal: true preconditions: - - sh: task -g internal:command -- brew + - sh: task internal:command -- brew msg: brew is not installed. Please install brew. requires: vars: [APP] @@ -115,7 +115,7 @@ tasks: desc: pipx install internal: true preconditions: - - sh: task -g internal:command -- pipx + - sh: task internal:command -- pipx msg: "pipx is not installed. Please install pipx: https://pipx.pypa.io/" requires: vars: [APP] @@ -126,7 +126,7 @@ tasks: desc: npm install internal: true preconditions: - - sh: task -g internal:command -- npm + - sh: task internal:command -- npm msg: "npm is not installed. Please install npm: https://docs.npmjs.com/cli/commands/npm" requires: vars: [APP] @@ -137,8 +137,8 @@ tasks: desc: uv install internal: true preconditions: - - sh: task -g internal:command -- uv - msg: uv is not installed. Please install by running 'task -g runtime:setup:uv' + - sh: task internal:command -- uv + msg: uv is not installed. Please install by running 'task runtime:setup:uv' requires: vars: [APP] cmds: diff --git a/.taskfiles/golang.Taskfile.yml b/.taskfiles/golang.Taskfile.yml index 2a9e651..f9d4a77 100644 --- a/.taskfiles/golang.Taskfile.yml +++ b/.taskfiles/golang.Taskfile.yml @@ -134,14 +134,27 @@ tasks: - for: { var: ITEMS } task: install:{{.ITEM}} + # * Tools + tools:test: + desc: Install Go tools (test) + vars: + ITEMS: + - gotestsum + - go-test-coverage + - gocover-cobertura + - goreleaser + cmds: + - for: { var: ITEMS } + task: install:{{.ITEM}} + # * Run GoVulnCheck run:govulncheck: desc: Run govulncheck preconditions: - - sh: task -g internal:command -- govulncheck - msg: "โŒ Error: govulncheck is not installed. Please run 'task -g go:install:govulncheck'" - - sh: task -g internal:command:version -- "govulncheck --version" "{{.GO_VERSION.govulncheck}}" - msg: "โŒ Version Mismatch: You are not using {{.GO_VERSION.govulncheck}} version. Please run 'task -g go:install:govulncheck'" + - sh: task internal:command -- govulncheck + msg: "โŒ Error: govulncheck is not installed. Please run 'task go:install:govulncheck'" + - sh: task internal:command:version -- "govulncheck --version" "{{.GO_VERSION.govulncheck}}" + msg: "โŒ Version Mismatch: You are not using {{.GO_VERSION.govulncheck}} version. Please run 'task go:install:govulncheck'" cmds: - govulncheck -test -show verbose ./... @@ -149,10 +162,10 @@ tasks: run:golangci-lint: desc: Run GolangCI-Lint preconditions: - - sh: task -g internal:command -- golangci-lint - msg: "โŒ Error: golangci-lint is not installed. Please run 'task -g go:install:golangci-lint'" - - sh: task -g internal:command:version -- "golangci-lint --version" "{{.GO_VERSION.golangciLint}}" - msg: "โŒ Version Mismatch: You are not using {{.GO_VERSION.golangciLint}} version. Please run 'task -g go:install:golangci-lint'" + - sh: task internal:command -- golangci-lint + msg: "โŒ Error: golangci-lint is not installed. Please run 'task go:install:golangci-lint'" + - sh: task internal:command:version -- "golangci-lint --version" "{{.GO_VERSION.golangciLint}}" + msg: "โŒ Version Mismatch: You are not using {{.GO_VERSION.golangciLint}} version. Please run 'task go:install:golangci-lint'" sources: - "**/*.go" - "**/*.mod" diff --git a/.taskfiles/markdown.Taskfile.yml b/.taskfiles/markdown.Taskfile.yml index cce9691..1ffb6ec 100644 --- a/.taskfiles/markdown.Taskfile.yml +++ b/.taskfiles/markdown.Taskfile.yml @@ -42,10 +42,10 @@ tasks: internal: true desc: Run markdownlint-cli2 preconditions: - - sh: task -g internal:command -- markdownlint-cli2 - msg: "โŒ Error: markdownlint-cli2 is not installed. Please run 'task -g md:install:markdownlint-cli2'" - - sh: task -g internal:command:version -- "markdownlint-cli2 --help" "{{.MARKDOWN_VERSION.markdownlintCli2}}" - msg: "โŒ Version Mismatch: You are not using {{.MARKDOWN_VERSION.markdownlintCli2}} version. Please run 'task -g md:install:markdownlint-cli2'" + - sh: task internal:command -- markdownlint-cli2 + msg: "โŒ Error: markdownlint-cli2 is not installed. Please run 'task md:install:markdownlint-cli2'" + - sh: task internal:command:version -- "markdownlint-cli2 --help" "{{.MARKDOWN_VERSION.markdownlintCli2}}" + msg: "โŒ Version Mismatch: You are not using {{.MARKDOWN_VERSION.markdownlintCli2}} version. Please run 'task md:install:markdownlint-cli2'" sources: - "**/*.md" vars: @@ -53,7 +53,7 @@ tasks: MODE: '{{default "" .MODE}}' cmds: - defer: | - {{if and .EXIT_CODE (eq .MODE "")}}echo "โš ๏ธ Markdown files are not formatted correctly. Please run 'task -g md:run:markdownlint-cli2:fix' to fix formatting issues."{{end}} + {{if and .EXIT_CODE (eq .MODE "")}}echo "โš ๏ธ Markdown files are not formatted correctly. Please run 'task md:run:markdownlint-cli2:fix' to fix formatting issues."{{end}} - markdownlint-cli2 "./**/*.md" --config ".github/linters/.markdownlint-cli2.yaml" {{.MODE}} dir: "{{.USER_WORKING_DIR}}" @@ -76,10 +76,10 @@ tasks: internal: true desc: Run markdown-table-formatter preconditions: - - sh: task -g internal:command -- markdown-table-formatter - msg: "โŒ Error: markdown-table-formatter is not installed. Please run 'task -g md:install:markdown-table-formatter'" - - sh: task -g internal:command:version -- "markdown-table-formatter --version" "{{.MARKDOWN_VERSION.markdownTableFormatter}}" - msg: "โŒ Version Mismatch: You are not using {{.MARKDOWN_VERSION.markdownTableFormatter}} version. Please run 'task -g md:install:markdown-table-formatter'" + - sh: task internal:command -- markdown-table-formatter + msg: "โŒ Error: markdown-table-formatter is not installed. Please run 'task md:install:markdown-table-formatter'" + - sh: task internal:command:version -- "markdown-table-formatter --version" "{{.MARKDOWN_VERSION.markdownTableFormatter}}" + msg: "โŒ Version Mismatch: You are not using {{.MARKDOWN_VERSION.markdownTableFormatter}} version. Please run 'task md:install:markdown-table-formatter'" sources: - "**/*.md" vars: @@ -87,7 +87,7 @@ tasks: MODE: '{{default "--check" .MODE}}' cmds: - defer: | - {{if and .EXIT_CODE (eq .MODE "--check")}}echo "โš ๏ธ Markdown tables are not formatted correctly. Please run 'task -g md:run:markdown-table-formatter:fix' to fix formatting issues."{{end}} + {{if and .EXIT_CODE (eq .MODE "--check")}}echo "โš ๏ธ Markdown tables are not formatted correctly. Please run 'task md:run:markdown-table-formatter:fix' to fix formatting issues."{{end}} - markdown-table-formatter "./**/*.md" {{.MODE}} dir: "{{.USER_WORKING_DIR}}" diff --git a/.taskfiles/scripts/install_az.sh b/.taskfiles/scripts/install_az.sh old mode 100644 new mode 100755 diff --git a/.taskfiles/scripts/install_azd.sh b/.taskfiles/scripts/install_azd.sh old mode 100644 new mode 100755 diff --git a/.taskfiles/scripts/install_bicep.sh b/.taskfiles/scripts/install_bicep.sh old mode 100644 new mode 100755 diff --git a/.taskfiles/scripts/install_golangci-lint.sh b/.taskfiles/scripts/install_golangci-lint.sh old mode 100644 new mode 100755 diff --git a/.taskfiles/scripts/setup_fnm.sh b/.taskfiles/scripts/setup_fnm.sh old mode 100644 new mode 100755 diff --git a/.taskfiles/scripts/setup_golang.sh b/.taskfiles/scripts/setup_golang.sh old mode 100644 new mode 100755 diff --git a/.taskfiles/scripts/setup_pwsh.sh b/.taskfiles/scripts/setup_pwsh.sh old mode 100644 new mode 100755 diff --git a/.taskfiles/scripts/setup_uv.sh b/.taskfiles/scripts/setup_uv.sh old mode 100644 new mode 100755 diff --git a/.taskfiles/yaml.Taskfile.yml b/.taskfiles/yaml.Taskfile.yml index a984620..43368de 100644 --- a/.taskfiles/yaml.Taskfile.yml +++ b/.taskfiles/yaml.Taskfile.yml @@ -31,10 +31,10 @@ tasks: run:yamllint: desc: Run YAMLlint preconditions: - - sh: task -g internal:command -- yamllint - msg: "โŒ Error: yamllint is not installed. Please run 'task -g yml:install:yamllint'" - - sh: task -g internal:command:version -- "yamllint --version" "{{.YAML_VERSION.yamllint}}" - msg: "โŒ Version Mismatch: You are not using {{.YAML_VERSION.yamllint}} version. Please run 'task -g yml:install:yamllint'" + - sh: task internal:command -- yamllint + msg: "โŒ Error: yamllint is not installed. Please run 'task yml:install:yamllint'" + - sh: task internal:command:version -- "yamllint --version" "{{.YAML_VERSION.yamllint}}" + msg: "โŒ Version Mismatch: You are not using {{.YAML_VERSION.yamllint}} version. Please run 'task yml:install:yamllint'" sources: - "**/*.yml" - "**/*.yaml" diff --git a/Taskfile.yml b/Taskfile.yml index ed3471e..63ebdc6 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -396,7 +396,7 @@ tasks: - git diff --shortstat --exit-code cmds: - cmd: | - echo "โš ๏ธโš ๏ธโš ๏ธ Unexpected difference. Run 'task -g {{.CLI_ARGS}}' command and commit. โš ๏ธโš ๏ธโš ๏ธ" + echo "โš ๏ธโš ๏ธโš ๏ธ Unexpected difference. Run 'task {{.CLI_ARGS}}' command and commit. โš ๏ธโš ๏ธโš ๏ธ" echo echo "๐Ÿ›‘๐Ÿ›‘๐Ÿ›‘ Uncommitted changes ๐Ÿ›‘๐Ÿ›‘๐Ÿ›‘" echo From 88e61e66682e70db212d945a41ca41e661032b26 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:12:48 -0800 Subject: [PATCH 04/34] ci: remove sudo from uv setup command and add install_goreleaser script Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .taskfiles/runtime.Taskfile.yml | 2 +- .taskfiles/scripts/install_goreleaser.sh | 160 +++++++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100755 .taskfiles/scripts/install_goreleaser.sh diff --git a/.taskfiles/runtime.Taskfile.yml b/.taskfiles/runtime.Taskfile.yml index 2f24bc1..2be7c2b 100644 --- a/.taskfiles/runtime.Taskfile.yml +++ b/.taskfiles/runtime.Taskfile.yml @@ -16,7 +16,7 @@ tasks: desc: uv setup cmds: - cmd: | - sudo ./scripts/setup_uv.sh "{{.RUNTIME_VERSION.uv}}" + ./scripts/setup_uv.sh "{{.RUNTIME_VERSION.uv}}" platforms: [linux] - task: :internal:_install:brew vars: diff --git a/.taskfiles/scripts/install_goreleaser.sh b/.taskfiles/scripts/install_goreleaser.sh new file mode 100755 index 0000000..dfdf414 --- /dev/null +++ b/.taskfiles/scripts/install_goreleaser.sh @@ -0,0 +1,160 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly GITHUB_OWNER="goreleaser" +readonly GITHUB_REPO="goreleaser" +readonly TOOL_NAME="goreleaser" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +tempDir="" + +# Logging helper +log() { + echo "-> $*" >&2 +} + +# Error handling helper +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} + +# Help message +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +for dep in curl jq tar; do + command -v "${dep}" >/dev/null 2>&1 || die "Missing required dependency: ${dep}" +done + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +# Detect OS +osRaw="$(uname -s)" +case "${osRaw}" in + Linux) os="Linux" ;; + Darwin) os="Darwin" ;; + *) die "Unsupported operating system: ${osRaw}" ;; +esac + +# Detect architecture +archRaw="$(uname -m)" +case "${archRaw}" in + x86_64 | amd64) arch="x86_64" ;; + arm64 | aarch64) arch="arm64" ;; + armv7l | armv6l) arch="armv7" ;; + i386 | i686) arch="i386" ;; + *) die "Unsupported architecture: ${archRaw}" ;; +esac + +log "Installing ${TOOL_NAME} (${VERSION}) for ${os}/${arch} to ${INSTALL_DIR}" + +# GitHub API authentication +ghAuthHeader=(-H "Accept: application/vnd.github+json") +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + ghAuthHeader+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +fi + +# Create temp directory +tempDir="$(mktemp -d)" || die "Failed to create temp directory" + +# Fetch release info and download URL +log "Fetching release information" +if [[ "${VERSION}" == "latest" ]]; then + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest" +else + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/tags/${VERSION}" +fi + +releaseJson="${tempDir}/release.json" +if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then + die "Failed to fetch release information. Check version or network connection." +fi + +# Extract download URL (pattern: goreleaser__.tar.gz) +downloadUrl="$(jq -r --arg os "${os}" --arg arch "${arch}" \ + '.assets[] | select(.browser_download_url | test("goreleaser_\($os)_\($arch)\\.tar\\.gz$")) | .browser_download_url' \ + "${releaseJson}")" + +[[ -n "${downloadUrl}" ]] || die "No asset found for ${os}/${arch}" + +log "Downloading ${downloadUrl}" +archivePath="${tempDir}/${TOOL_NAME}.tar.gz" +curl -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" + +# Extract binary +log "Extracting archive" +tar -xf "${archivePath}" -C "${tempDir}" "${TOOL_NAME}" || die "Extraction failed" +[[ -f "${tempDir}/${TOOL_NAME}" ]] || die "Binary not found in archive" + +# Install binary +log "Installing binary" +if [[ "${os}" == "Darwin" ]]; then + # macOS install command may not support -D flag in all versions + mkdir -p "${INSTALL_DIR}" || die "Failed to create install directory" + install -m 0755 "${tempDir}/${TOOL_NAME}" "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to install binary" +else + # Linux install with -D flag + install -Dm0755 "${tempDir}/${TOOL_NAME}" "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to install binary" +fi + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" + +# Run tool version to verify +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" From dd7dfb2af64f23d082674fb8092e65e9d4caff34 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:13:35 -0800 Subject: [PATCH 05/34] Update .taskfiles/azure.Taskfile.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .taskfiles/azure.Taskfile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.taskfiles/azure.Taskfile.yml b/.taskfiles/azure.Taskfile.yml index dcb56c4..08c8b8c 100644 --- a/.taskfiles/azure.Taskfile.yml +++ b/.taskfiles/azure.Taskfile.yml @@ -30,7 +30,7 @@ tasks: # * Install Azure Developer CLI install:azd: - desc: Install Azure CLI + desc: Install Azure Developer CLI cmds: - task: :internal:_install:winget vars: From b39805c3540069b1dac1dad12a6e77243a5c49d3 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:13:41 -0800 Subject: [PATCH 06/34] Update .taskfiles/scripts/install_bicep.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .taskfiles/scripts/install_bicep.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.taskfiles/scripts/install_bicep.sh b/.taskfiles/scripts/install_bicep.sh index b1be097..7028ac1 100755 --- a/.taskfiles/scripts/install_bicep.sh +++ b/.taskfiles/scripts/install_bicep.sh @@ -134,4 +134,4 @@ install -Dm0755 "${binaryPath}" "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" # Run tool version to verify -"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed binary failed to run (${INSTALL_DIR}/${TOOL_NAME})" From d2fc78bde166a59ea2aa7eb37bf23b75fd172260 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:15:03 -0800 Subject: [PATCH 07/34] fix: uncomment installation command for GoReleaser in golang.Taskfile.yml Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .taskfiles/golang.Taskfile.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.taskfiles/golang.Taskfile.yml b/.taskfiles/golang.Taskfile.yml index f9d4a77..ce4da4a 100644 --- a/.taskfiles/golang.Taskfile.yml +++ b/.taskfiles/golang.Taskfile.yml @@ -30,8 +30,7 @@ tasks: vars: APP: goreleaser@{{.GO_VERSION.goreleaser}} - cmd: | - # TODO - # ./scripts/install_goreleaser.sh "{{.GO_VERSION.goreleaser}}" + ./scripts/install_goreleaser.sh "{{.GO_VERSION.goreleaser}}" platforms: [linux] dir: "{{.TASKFILE_DIR}}" From 4b129c48479ae4955b1adc05dcc5af6bc92d79ea Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:16:14 -0800 Subject: [PATCH 08/34] ci: update test tools setup command for consistency Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a92cc17..69aab30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,7 +90,7 @@ jobs: run: task deps:download - name: ๐Ÿ”จ Setup Test tools - run: task test:tools + run: task go:tools:test - name: ๐Ÿงช Run Tests run: task testunit From 568df2629703d89006460bd7f8fcda7acdd47c31 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:20:32 -0800 Subject: [PATCH 09/34] ci: enhance linting tasks for Go, Markdown, and YAML tools Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/workflows/lint.yml | 4 ++-- .taskfiles/golang.Taskfile.yml | 13 ++++++++++++- .taskfiles/markdown.Taskfile.yml | 4 +++- .taskfiles/yaml.Taskfile.yml | 6 ++++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b66c568..ce3e2a9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -61,7 +61,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} - name: ๐Ÿ”จ Setup tools - run: task ${{ matrix.task }}:tools + run: task ${{ matrix.task }}:tools:lint env: GITHUB_TOKEN: ${{ github.token }} @@ -70,7 +70,7 @@ jobs: - name: ๐Ÿ”€ Check for differences if: github.event.pull_request.user.login != 'dependabot[bot]' - run: task diff -- "${{ matrix.task }}:lint:fix" + run: task diff -- "${{ matrix.task }}:lint" check-lint: if: always() diff --git a/.taskfiles/golang.Taskfile.yml b/.taskfiles/golang.Taskfile.yml index ce4da4a..55e63d6 100644 --- a/.taskfiles/golang.Taskfile.yml +++ b/.taskfiles/golang.Taskfile.yml @@ -133,7 +133,7 @@ tasks: - for: { var: ITEMS } task: install:{{.ITEM}} - # * Tools + # * Tools (test) tools:test: desc: Install Go tools (test) vars: @@ -146,6 +146,17 @@ tasks: - for: { var: ITEMS } task: install:{{.ITEM}} + # * Tools (lint) + tools:lint: + desc: Install Go tools (lint) + vars: + ITEMS: + - golangci-lint + - govulncheck + cmds: + - for: { var: ITEMS } + task: install:{{.ITEM}} + # * Run GoVulnCheck run:govulncheck: desc: Run govulncheck diff --git a/.taskfiles/markdown.Taskfile.yml b/.taskfiles/markdown.Taskfile.yml index 1ffb6ec..a1cbd69 100644 --- a/.taskfiles/markdown.Taskfile.yml +++ b/.taskfiles/markdown.Taskfile.yml @@ -27,8 +27,10 @@ tasks: APP: markdown-table-formatter@{{.MARKDOWN_VERSION.markdownTableFormatter}} # * Tools - tools: + tools:lint: desc: Install Markdown tools + aliases: + - tools vars: ITEMS: - markdownlint-cli2 diff --git a/.taskfiles/yaml.Taskfile.yml b/.taskfiles/yaml.Taskfile.yml index 43368de..2562fbd 100644 --- a/.taskfiles/yaml.Taskfile.yml +++ b/.taskfiles/yaml.Taskfile.yml @@ -18,8 +18,10 @@ tasks: APP: yamllint@{{.YAML_VERSION.yamllint}} # * Tools - tools: - desc: Install YAML tools + tools:lint: + desc: Install YAML tools (lint) + aliases: + - tools vars: ITEMS: - yamllint From f1b659c2095f194c7266e12990752935eb70b253 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:26:42 -0800 Subject: [PATCH 10/34] ci: add GitHub CLI and linting tools installation scripts Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/workflows/lint.yml | 1 + .taskfiles/github.Taskfile.yml | 199 +++++++++++++++++++++++ .taskfiles/scripts/install_act.sh | 96 +++++++++++ .taskfiles/scripts/install_actionlint.sh | 96 +++++++++++ .taskfiles/scripts/install_ghalint.ps1 | 148 +++++++++++++++++ .taskfiles/scripts/install_ghalint.sh | 145 +++++++++++++++++ .taskfiles/scripts/install_pinact.ps1 | 144 ++++++++++++++++ .taskfiles/scripts/install_pinact.sh | 141 ++++++++++++++++ Taskfile.yml | 3 + 9 files changed, 973 insertions(+) create mode 100644 .taskfiles/github.Taskfile.yml create mode 100755 .taskfiles/scripts/install_act.sh create mode 100755 .taskfiles/scripts/install_actionlint.sh create mode 100755 .taskfiles/scripts/install_ghalint.ps1 create mode 100755 .taskfiles/scripts/install_ghalint.sh create mode 100755 .taskfiles/scripts/install_pinact.ps1 create mode 100755 .taskfiles/scripts/install_pinact.sh diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ce3e2a9..4ee53a6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,6 +28,7 @@ jobs: fail-fast: false matrix: task: + - gh - go - md - yml diff --git a/.taskfiles/github.Taskfile.yml b/.taskfiles/github.Taskfile.yml new file mode 100644 index 0000000..e36d758 --- /dev/null +++ b/.taskfiles/github.Taskfile.yml @@ -0,0 +1,199 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json +# docs: https://taskfile.dev +--- +version: "3" + +vars: + GITHUB_VERSION: + map: + actionlint: latest + act: latest + pinact: latest + ghalint: latest + gh: latest + +tasks: + # * Install Actionlint + install:actionlint: + desc: Install Actionlint + cmds: + - task: :internal:_install:winget + vars: + APP: rhysd.actionlint + VERSION: "{{.GITHUB_VERSION.actionlint}}" + - task: :internal:_install:brew + vars: + APP: actionlint@{{.GITHUB_VERSION.actionlint}} + - cmd: | + ./scripts/install_actionlint.sh "{{.GITHUB_VERSION.actionlint}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Install ACT + install:act: + desc: Install ACT + cmds: + - task: :internal:_install:winget + vars: + APP: nektos.act + VERSION: "{{.GITHUB_VERSION.act}}" + - task: :internal:_install:brew + vars: + APP: act@{{.GITHUB_VERSION.act}} + - cmd: | + ./scripts/install_act.sh "{{.GITHUB_VERSION.act}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Install PinAct + install:pinact: + desc: Install PinAct + summary: Install a pinned version of Act + cmds: + - cmd: | + {{.PWSH_SCRIPT}} ./scripts/install_pinact.ps1 "{{.GITHUB_VERSION.pinact}}" + platforms: [windows] + - task: :internal:_install:brew + vars: + APP: pinact@{{.GITHUB_VERSION.pinact}} + - cmd: | + ./scripts/install_pinact.sh "{{.GITHUB_VERSION.pinact}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Install GHAlint + install:ghalint: + desc: Install GHAlint + cmds: + - cmd: | + {{.PWSH_SCRIPT}} ./scripts/install_ghalint.ps1 "{{.GITHUB_VERSION.ghalint}}" + platforms: [windows] + - task: :internal:_install:brew + vars: + APP: ghalint@{{.GITHUB_VERSION.ghalint}} + - cmd: | + ./scripts/install_ghalint.sh "{{.GITHUB_VERSION.ghalint}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Install GitHub CLI + install:gh: + desc: Install GitHub CLI + cmds: + - task: :internal:_install:winget + vars: + APP: GitHub.cli + VERSION: "{{.GITHUB_VERSION.gh}}" + - task: :internal:_install:brew + vars: + APP: gh@{{.GITHUB_VERSION.gh}} + - cmd: | + sudo ./scripts/install_gh.sh "{{.GITHUB_VERSION.gh}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Tools + tools: + desc: Install GitHub tools + vars: + ITEMS: + - act + - gh + cmds: + - for: { var: ITEMS } + task: install:{{.ITEM}} + - task: tools:lint + + # * Tools (lint) + tools:lint: + desc: Install GitHub tools (lint) + vars: + ITEMS: + - actionlint + - pinact + - ghalint + cmds: + - for: { var: ITEMS } + task: install:{{.ITEM}} + + # * PinAct Runner + _run:pinact: + internal: true + desc: Run PinAct + preconditions: + - sh: task -g internal:command -- pinact + msg: "โŒ Error: pinact is not installed. Please run 'task -g gh:install:pinact'" + - sh: task -g internal:command:version -- "pinact version" "{{.GITHUB_VERSION.pinact}}" + msg: "โŒ Version Mismatch: You are not using {{.GITHUB_VERSION.pinact}} version. Please run 'task -g gh:install:pinact'" + sources: + - .github/workflows/*.yml + - .github/workflows/*.yaml + - "**/action.yml" + - "**/action.yaml" + vars: + # yamllint disable-line rule:quoted-strings + MODE: '{{default "--check --diff" .MODE}}' + cmds: + - defer: | + {{if and .EXIT_CODE (eq .MODE "--check")}}echo "โš ๏ธ GitHub Workflows/Actions files are not formatted correctly. Please run 'task -g gh:run:pinact:fix' to fix formatting issues."{{end}} + - pinact run {{.MODE}} + dir: "{{.USER_WORKING_DIR}}" + + # * Run PinAct Check + run:pinact:check: + desc: Run PinAct Check + cmds: + - task: _run:pinact + vars: { MODE: --check --diff } + + # * Run PinAct Fix + run:pinact:fix: + desc: Run PinAct fix + cmds: + - task: _run:pinact + vars: { MODE: --fix --diff } + + # * Run Actionlint + run:actionlint: + desc: Run Actionlint + preconditions: + - sh: task -g internal:command -- actionlint + msg: "โŒ Error: actionlint is not installed. Please run 'task -g gh:install:actionlint'" + - sh: task -g internal:command:version -- "actionlint --version" "{{.GITHUB_VERSION.actionlint}}" + msg: "โŒ Version Mismatch: You are not using {{.GITHUB_VERSION.actionlint}} version. Please run 'task -g gh:install:actionlint'" + sources: + - .github/workflows/*.yml + - .github/workflows/*.yaml + - "**/action.yml" + - "**/action.yaml" + cmds: + - actionlint -config-file ~/.linters/actionlint.yaml + dir: "{{.USER_WORKING_DIR}}" + + # * Run GHALint + run:ghalint: + desc: Run GHALint + preconditions: + - sh: task -g internal:command -- ghalint + msg: "โŒ Error: ghalint is not installed. Please run 'task -g gh:install:ghalint'" + - sh: task -g internal:command:version -- "ghalint --version" "{{.GITHUB_VERSION.ghalint}}" + msg: "โŒ Version Mismatch: You are not using {{.GITHUB_VERSION.ghalint}} version. Please run 'task -g gh:install:ghalint'" + sources: + - .github/workflows/*.yml + - .github/workflows/*.yaml + - "**/action.yml" + - "**/action.yaml" + env: + GHALINT_LOG_COLOR: always + cmds: + - ghalint run + - ghalint run-action + dir: "{{.USER_WORKING_DIR}}" + + # * Lint + lint: + desc: Lint YAML files + cmds: + - task: run:ghalint + - task: run:actionlint + - task: run:pinact:fix diff --git a/.taskfiles/scripts/install_act.sh b/.taskfiles/scripts/install_act.sh new file mode 100755 index 0000000..04a6ab0 --- /dev/null +++ b/.taskfiles/scripts/install_act.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly GITHUB_OWNER="nektos" +readonly GITHUB_REPO="act" +readonly TOOL_NAME="act" +readonly INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/master/install.sh" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +# Logging helper +log() { + echo "-> $*" >&2 +} + +# Error handling helper +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} + +# Help message +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +command -v curl >/dev/null 2>&1 || die "Missing required dependency: curl" + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" + +# GitHub API authentication +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + ghAuthHeader=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +else + ghAuthHeader=() +fi + +# Execute remote installation script +log "Fetching and executing official installation script" +if ! curl "${ghAuthHeader[@]}" --proto '=https' --tlsv1.3 -fsSL "${INSTALL_SCRIPT_URL}" | /bin/bash -s -- -b "${INSTALL_DIR}" "${VERSION}"; then + die "Installation failed. Check version or network connection." +fi + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" + +# Run tool version to verify +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed binary failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/install_actionlint.sh b/.taskfiles/scripts/install_actionlint.sh new file mode 100755 index 0000000..f485b20 --- /dev/null +++ b/.taskfiles/scripts/install_actionlint.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly GITHUB_OWNER="rhysd" +readonly GITHUB_REPO="actionlint" +readonly TOOL_NAME="actionlint" +readonly INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/main/scripts/download-actionlint.bash" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +# Logging helper +log() { + echo "-> $*" >&2 +} + +# Error handling helper +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} + +# Help message +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +command -v curl >/dev/null 2>&1 || die "Missing required dependency: curl" + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" + +# GitHub API authentication +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + ghAuthHeader=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +else + ghAuthHeader=() +fi + +# Execute remote installation script +log "Fetching and executing official installation script" +if ! curl "${ghAuthHeader[@]}" --proto '=https' --tlsv1.3 -fsSL "${INSTALL_SCRIPT_URL}" | /bin/bash -s -- "${VERSION}" "${INSTALL_DIR}"; then + die "Installation failed. Check version or network connection." +fi + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" + +# Run tool version to verify +"${INSTALL_DIR}/${TOOL_NAME}" -version || die "Installed binary failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/install_ghalint.ps1 b/.taskfiles/scripts/install_ghalint.ps1 new file mode 100755 index 0000000..dfd727b --- /dev/null +++ b/.taskfiles/scripts/install_ghalint.ps1 @@ -0,0 +1,148 @@ +[CmdletBinding()] +param( + [Parameter(Position = 0)] + [string]$Version = $env:VERSION, + + [Parameter(Position = 1)] + [string]$InstallDir = $env:INSTALL_DIR +) + +$ErrorActionPreference = 'Stop' + +# Constants +$GitHubOwner = 'suzuki-shunsuke' +$GitHubRepo = 'ghalint' +$ToolName = 'ghalint' + +function Write-Log { + param([Parameter(Mandatory)] [string]$Message) + Write-Host "-> $Message" +} + +function Show-Usage { + @" +Usage: install_ghalint.ps1 [VERSION] [INSTALL_DIR] + +Positional arguments: + VERSION Version to install (default: latest) + INSTALL_DIR Custom install directory + +Environment variables: + VERSION Desired version (default: latest) + INSTALL_DIR Install directory override + GITHUB_TOKEN GitHub token for API authentication + +Examples: + ./install_ghalint.ps1 # Install latest + ./install_ghalint.ps1 1.5.4 # Install 1.5.4 + ./install_ghalint.ps1 1.5.4 "$HOME\.local\bin" # Install 1.5.4 to a custom dir +"@ +} + +# Help +if ($args -contains '-h' -or $args -contains '--help') { + Show-Usage + exit 0 +} + +# Normalize version +if ([string]::IsNullOrWhiteSpace($Version)) { + $Version = 'latest' +} + +$versionTag = if ($Version -eq 'latest') { + 'latest' +} +elseif ($Version -match '^v') { + $Version +} +else { + "v$Version" +} + +# Determine install directory (simple, user-first default) +if ([string]::IsNullOrWhiteSpace($InstallDir)) { + $InstallDir = Join-Path $HOME '.local\bin' +} + +if (-not (Test-Path -LiteralPath $InstallDir)) { + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null +} + +# Detect architecture +$arch = switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) { + 'X64' { 'amd64' } + 'Arm64' { 'arm64' } + default { throw "Unsupported architecture: $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)" } +} + +Write-Log "Installing $ToolName ($versionTag) to $InstallDir" + +# GitHub API +$headers = @{ 'Accept' = 'application/vnd.github+json' } +if (-not [string]::IsNullOrWhiteSpace($env:GITHUB_TOKEN)) { + $headers['Authorization'] = "Bearer $($env:GITHUB_TOKEN)" +} + +$apiUrl = if ($versionTag -eq 'latest') { + "https://api.github.com/repos/$GitHubOwner/$GitHubRepo/releases/latest" +} +else { + "https://api.github.com/repos/$GitHubOwner/$GitHubRepo/releases/tags/$versionTag" +} + +$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString('n')) +New-Item -ItemType Directory -Path $tempDir -Force | Out-Null + +try { + Write-Log 'Fetching release information' + $release = Invoke-RestMethod -Headers $headers -Uri $apiUrl + + # Asset pattern (example): ghalint_1.5.4_windows_amd64.zip + $verNoV = if ($versionTag -eq 'latest') { + # use tag_name to compute asset name + ($release.tag_name -replace '^v', '') + } + else { + ($versionTag -replace '^v', '') + } + + $assetName = "${ToolName}_${verNoV}_windows_${arch}.zip" + $asset = $release.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1 + if (-not $asset) { + throw "No asset found for architecture $arch (expected $assetName)" + } + + $zipPath = Join-Path $tempDir $assetName + Write-Log "Downloading $($asset.browser_download_url)" + Invoke-WebRequest -Headers $headers -Uri $asset.browser_download_url -OutFile $zipPath + + Write-Log 'Extracting archive' + Expand-Archive -Path $zipPath -DestinationPath $tempDir -Force + + $exeSource = Join-Path $tempDir "$ToolName.exe" + if (-not (Test-Path -LiteralPath $exeSource)) { + $found = Get-ChildItem -Path $tempDir -Recurse -Filter "$ToolName.exe" -File -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($found) { + $exeSource = $found.FullName + } + } + + if (-not (Test-Path -LiteralPath $exeSource)) { + throw "Binary not found in archive ($ToolName.exe)" + } + + $exeDest = Join-Path $InstallDir "$ToolName.exe" + + Write-Log 'Installing binary' + Copy-Item -LiteralPath $exeSource -Destination $exeDest -Force + + Write-Log "โœ“ Successfully installed $ToolName to $exeDest" + + & $exeDest --version | Out-Null +} +finally { + if (Test-Path -LiteralPath $tempDir) { + Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue + } +} diff --git a/.taskfiles/scripts/install_ghalint.sh b/.taskfiles/scripts/install_ghalint.sh new file mode 100755 index 0000000..94a66a7 --- /dev/null +++ b/.taskfiles/scripts/install_ghalint.sh @@ -0,0 +1,145 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly GITHUB_OWNER="suzuki-shunsuke" +readonly GITHUB_REPO="ghalint" +readonly TOOL_NAME="ghalint" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +tempDir="" + +# Logging helper +log() { + echo "-> $*" >&2 +} + +# Error handling helper +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} +# Help message +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +for dep in curl jq tar; do + command -v "${dep}" >/dev/null 2>&1 || die "Missing required dependency: ${dep}" +done + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +# Detect architecture +archRaw="$(uname -m)" +case "${archRaw}" in + x86_64 | amd64) arch="amd64" ;; + arm64 | aarch64) arch="arm64" ;; + # armv7l | armv6l) arch="arm" ;; + # i386 | i686) arch="386" ;; + *) die "Unsupported architecture: ${archRaw}" ;; +esac + +log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" + +# GitHub API authentication +ghAuthHeader=(-H "Accept: application/vnd.github+json") +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + ghAuthHeader+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +fi + +# Create temp directory +tempDir="$(mktemp -d)" || die "Failed to create temp directory" + +# Fetch release info and download URL +log "Fetching release information" +if [[ "${VERSION}" == "latest" ]]; then + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest" +else + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/tags/${VERSION}" +fi + +releaseJson="${tempDir}/release.json" +if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then + die "Failed to fetch release information. Check version or network connection." +fi + +# Extract download URL +downloadUrl="$(jq -r --arg arch "${arch}" \ + '.assets[] | select(.browser_download_url | endswith("_linux_\($arch).tar.gz")) | .browser_download_url' \ + "${releaseJson}")" + +[[ -n "${downloadUrl}" ]] || die "No asset found for architecture ${arch}" + +log "Downloading ${downloadUrl}" +archivePath="${tempDir}/${TOOL_NAME}.tar.gz" +curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" + +# Extract binary +log "Extracting archive" +tar -xf "${archivePath}" -C "${tempDir}" "${TOOL_NAME}" || die "Extraction failed" +[[ -f "${tempDir}/${TOOL_NAME}" ]] || die "Binary not found in archive" + +# Install binary +log "Installing binary" +mv "${tempDir}/${TOOL_NAME}" "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to install binary" +chmod 0755 "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to set permissions" + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" + +# Run tool version to verify +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/install_pinact.ps1 b/.taskfiles/scripts/install_pinact.ps1 new file mode 100755 index 0000000..00e1105 --- /dev/null +++ b/.taskfiles/scripts/install_pinact.ps1 @@ -0,0 +1,144 @@ +[CmdletBinding()] +param( + [Parameter(Position = 0)] + [string]$Version = $env:VERSION, + + [Parameter(Position = 1)] + [string]$InstallDir = $env:INSTALL_DIR +) + +$ErrorActionPreference = 'Stop' + +# Constants +$GitHubOwner = 'suzuki-shunsuke' +$GitHubRepo = 'pinact' +$ToolName = 'pinact' + +function Write-Log { + param([Parameter(Mandatory)] [string]$Message) + Write-Host "-> $Message" +} + +function Die { + param( + [Parameter(Mandatory)] [string]$Message, + [int]$ExitCode = 1 + ) + Write-Error "X Error: $Message" + exit $ExitCode +} + +function Show-Usage { + @" +Usage: install_pinact.ps1 [VERSION] [INSTALL_DIR] + +Positional arguments: + VERSION Version to install (default: latest) + INSTALL_DIR Custom install directory + +Environment variables: + VERSION Desired version (default: latest) + INSTALL_DIR Install directory override + GITHUB_TOKEN GitHub token for API authentication + +Examples: + ./install_pinact.ps1 # Install latest + ./install_pinact.ps1 3.6.0 # Install 3.6.0 + ./install_pinact.ps1 3.6.0 "$HOME\.local\bin" # Install 3.6.0 to a custom dir +"@ +} + +# Help +if ($args -contains '-h' -or $args -contains '--help' -or $PSBoundParameters.ContainsKey('Help')) { + Show-Usage + exit 0 +} + +# Normalize version +if ([string]::IsNullOrWhiteSpace($Version)) { + $Version = 'latest' +} +elseif ($Version -match '^[0-9]') { + $Version = "v$Version" +} + +# Determine install directory (simple, user-first default) +if ([string]::IsNullOrWhiteSpace($InstallDir)) { + $InstallDir = Join-Path $HOME '.local\bin' +} + +# Ensure install directory exists +if (-not (Test-Path -LiteralPath $InstallDir)) { + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null +} + +# Detect architecture +$arch = switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) { + 'X64' { 'amd64' } + 'Arm64' { 'arm64' } + default { Die "Unsupported architecture: $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)" } +} + +Write-Log "Installing $ToolName ($Version) to $InstallDir" + +# GitHub API +$headers = @{ 'Accept' = 'application/vnd.github+json' } +if (-not [string]::IsNullOrWhiteSpace($env:GITHUB_TOKEN)) { + $headers['Authorization'] = "Bearer $($env:GITHUB_TOKEN)" +} + +$apiUrl = if ($Version -eq 'latest') { + "https://api.github.com/repos/$GitHubOwner/$GitHubRepo/releases/latest" +} +else { + "https://api.github.com/repos/$GitHubOwner/$GitHubRepo/releases/tags/$Version" +} + +$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString('n')) +New-Item -ItemType Directory -Path $tempDir -Force | Out-Null + +try { + Write-Log 'Fetching release information' + $release = Invoke-RestMethod -Headers $headers -Uri $apiUrl + + $assetName = "${ToolName}_windows_${arch}.zip" + $asset = $release.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1 + if (-not $asset) { + Die "No asset found for architecture $arch (expected $assetName)" + } + + $zipPath = Join-Path $tempDir $assetName + Write-Log "Downloading $($asset.browser_download_url)" + Invoke-WebRequest -Headers $headers -Uri $asset.browser_download_url -OutFile $zipPath + + Write-Log 'Extracting archive' + Expand-Archive -Path $zipPath -DestinationPath $tempDir -Force + + $exeSource = Join-Path $tempDir "$ToolName.exe" + if (-not (Test-Path -LiteralPath $exeSource)) { + # Fallback: search anywhere in extracted tree + $found = Get-ChildItem -Path $tempDir -Recurse -Filter "$ToolName.exe" -File -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($found) { + $exeSource = $found.FullName + } + } + + if (-not (Test-Path -LiteralPath $exeSource)) { + Die "Binary not found in archive ($ToolName.exe)" + } + + $exeDest = Join-Path $InstallDir "$ToolName.exe" + + Write-Log 'Installing binary' + Copy-Item -LiteralPath $exeSource -Destination $exeDest -Force + + Write-Log "โœ“ Successfully installed $ToolName to $exeDest" + + # Verify + & $exeDest version | Out-Null +} +finally { + if (Test-Path -LiteralPath $tempDir) { + Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue + } +} diff --git a/.taskfiles/scripts/install_pinact.sh b/.taskfiles/scripts/install_pinact.sh new file mode 100755 index 0000000..4be8abd --- /dev/null +++ b/.taskfiles/scripts/install_pinact.sh @@ -0,0 +1,141 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly GITHUB_OWNER="suzuki-shunsuke" +readonly GITHUB_REPO="pinact" +readonly TOOL_NAME="pinact" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +tempDir="" + +log() { + echo "-> $*" >&2 +} + +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +for dep in curl jq tar; do + command -v "${dep}" >/dev/null 2>&1 || die "Missing required dependency: ${dep}" +done + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +# Detect architecture +archRaw="$(uname -m)" +case "${archRaw}" in + x86_64 | amd64) arch="amd64" ;; + arm64 | aarch64) arch="arm64" ;; + # armv7l | armv6l) arch="arm" ;; + # i386 | i686) arch="386" ;; + *) die "Unsupported architecture: ${archRaw}" ;; +esac + +log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" + +# GitHub API authentication +ghAuthHeader=(-H "Accept: application/vnd.github+json") +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + ghAuthHeader+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +fi + +# Create temp directory +tempDir="$(mktemp -d)" || die "Failed to create temp directory" + +# Fetch release info and download URL +log "Fetching release information" +if [[ "${VERSION}" == "latest" ]]; then + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest" +else + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/tags/${VERSION}" +fi + +releaseJson="${tempDir}/release.json" +if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then + die "Failed to fetch release information. Check version or network connection." +fi + +# Extract download URL +downloadUrl="$(jq -r --arg arch "${arch}" \ + '.assets[] | select(.browser_download_url | endswith("_linux_\($arch).tar.gz")) | .browser_download_url' \ + "${releaseJson}")" + +[[ -n "${downloadUrl}" ]] || die "No asset found for architecture ${arch}" + +log "Downloading ${downloadUrl}" +archivePath="${tempDir}/${TOOL_NAME}.tar.gz" +curl -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" + +# Extract binary +log "Extracting archive" +tar -xf "${archivePath}" -C "${tempDir}" "${TOOL_NAME}" || die "Extraction failed" +[[ -f "${tempDir}/${TOOL_NAME}" ]] || die "Binary not found in archive" + +# Install binary +log "Installing binary" +mv "${tempDir}/${TOOL_NAME}" "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to install binary" +chmod 0755 "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to set permissions" + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" + +# Run tool version to verify +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/Taskfile.yml b/Taskfile.yml index 63ebdc6..63025ec 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -17,6 +17,7 @@ includes: internal: .taskfiles/_internal.Taskfile.yml runtime: .taskfiles/runtime.Taskfile.yml az: .taskfiles/azure.Taskfile.yml + gh: .taskfiles/github.Taskfile.yml go: .taskfiles/golang.Taskfile.yml md: .taskfiles/markdown.Taskfile.yml yml: .taskfiles/yaml.Taskfile.yml @@ -44,6 +45,7 @@ tasks: vars: ITEMS: - az + - gh - go - md - yml @@ -55,6 +57,7 @@ tasks: desc: Lint files vars: ITEMS: + - gh - go - md - yml From e65d5bfb07d3a29498816e91f71c14ecac77cb00 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:29:24 -0800 Subject: [PATCH 11/34] ci: add bin path setup for linting tasks and update error messages for tool installation checks Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/workflows/lint.yml | 4 ++++ .taskfiles/github.Taskfile.yml | 26 +++++++++++++------------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4ee53a6..c4895df 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -66,6 +66,10 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} + - name: ๐ŸŒŒ Ensure bin path + run: | + export PATH="$HOME/.local/bin:$PATH" + - name: โœ”๏ธ Run lint (${{ matrix.task }}) run: task ${{ matrix.task }}:lint diff --git a/.taskfiles/github.Taskfile.yml b/.taskfiles/github.Taskfile.yml index e36d758..08e461d 100644 --- a/.taskfiles/github.Taskfile.yml +++ b/.taskfiles/github.Taskfile.yml @@ -121,10 +121,10 @@ tasks: internal: true desc: Run PinAct preconditions: - - sh: task -g internal:command -- pinact - msg: "โŒ Error: pinact is not installed. Please run 'task -g gh:install:pinact'" - - sh: task -g internal:command:version -- "pinact version" "{{.GITHUB_VERSION.pinact}}" - msg: "โŒ Version Mismatch: You are not using {{.GITHUB_VERSION.pinact}} version. Please run 'task -g gh:install:pinact'" + - sh: task internal:command -- pinact + msg: "โŒ Error: pinact is not installed. Please run 'task gh:install:pinact'" + - sh: task internal:command:version -- "pinact version" "{{.GITHUB_VERSION.pinact}}" + msg: "โŒ Version Mismatch: You are not using {{.GITHUB_VERSION.pinact}} version. Please run 'task gh:install:pinact'" sources: - .github/workflows/*.yml - .github/workflows/*.yaml @@ -135,7 +135,7 @@ tasks: MODE: '{{default "--check --diff" .MODE}}' cmds: - defer: | - {{if and .EXIT_CODE (eq .MODE "--check")}}echo "โš ๏ธ GitHub Workflows/Actions files are not formatted correctly. Please run 'task -g gh:run:pinact:fix' to fix formatting issues."{{end}} + {{if and .EXIT_CODE (eq .MODE "--check")}}echo "โš ๏ธ GitHub Workflows/Actions files are not formatted correctly. Please run 'task gh:run:pinact:fix' to fix formatting issues."{{end}} - pinact run {{.MODE}} dir: "{{.USER_WORKING_DIR}}" @@ -157,10 +157,10 @@ tasks: run:actionlint: desc: Run Actionlint preconditions: - - sh: task -g internal:command -- actionlint - msg: "โŒ Error: actionlint is not installed. Please run 'task -g gh:install:actionlint'" - - sh: task -g internal:command:version -- "actionlint --version" "{{.GITHUB_VERSION.actionlint}}" - msg: "โŒ Version Mismatch: You are not using {{.GITHUB_VERSION.actionlint}} version. Please run 'task -g gh:install:actionlint'" + - sh: task internal:command -- actionlint + msg: "โŒ Error: actionlint is not installed. Please run 'task gh:install:actionlint'" + - sh: task internal:command:version -- "actionlint --version" "{{.GITHUB_VERSION.actionlint}}" + msg: "โŒ Version Mismatch: You are not using {{.GITHUB_VERSION.actionlint}} version. Please run 'task gh:install:actionlint'" sources: - .github/workflows/*.yml - .github/workflows/*.yaml @@ -174,10 +174,10 @@ tasks: run:ghalint: desc: Run GHALint preconditions: - - sh: task -g internal:command -- ghalint - msg: "โŒ Error: ghalint is not installed. Please run 'task -g gh:install:ghalint'" - - sh: task -g internal:command:version -- "ghalint --version" "{{.GITHUB_VERSION.ghalint}}" - msg: "โŒ Version Mismatch: You are not using {{.GITHUB_VERSION.ghalint}} version. Please run 'task -g gh:install:ghalint'" + - sh: task internal:command -- ghalint + msg: "โŒ Error: ghalint is not installed. Please run 'task gh:install:ghalint'" + - sh: task internal:command:version -- "ghalint --version" "{{.GITHUB_VERSION.ghalint}}" + msg: "โŒ Version Mismatch: You are not using {{.GITHUB_VERSION.ghalint}} version. Please run 'task gh:install:ghalint'" sources: - .github/workflows/*.yml - .github/workflows/*.yaml From e1484f074cffc7229b9d63f79606e8d0b5964aad Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:38:48 -0800 Subject: [PATCH 12/34] ci: add shell linting tools and configuration files Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/linters/.lychee.toml | 60 --------- .github/linters/.shellcheckrc | 3 + .github/linters/actionlint.yaml | 20 +++ .github/workflows/e2e-tests.yaml | 1 + .github/workflows/lint.yml | 2 + .taskfiles/github.Taskfile.yml | 9 +- .taskfiles/golang.Taskfile.yml | 2 +- .taskfiles/markdown.Taskfile.yml | 6 +- .taskfiles/scripts/install_shellcheck.sh | 143 +++++++++++++++++++++ .taskfiles/scripts/install_shfmt.sh | 150 +++++++++++++++++++++++ .taskfiles/shell.Taskfile.yml | 102 +++++++++++++++ .taskfiles/yaml.Taskfile.yml | 4 +- Taskfile.yml | 3 + 13 files changed, 435 insertions(+), 70 deletions(-) delete mode 100644 .github/linters/.lychee.toml create mode 100644 .github/linters/.shellcheckrc create mode 100644 .github/linters/actionlint.yaml create mode 100755 .taskfiles/scripts/install_shellcheck.sh create mode 100755 .taskfiles/scripts/install_shfmt.sh create mode 100644 .taskfiles/shell.Taskfile.yml diff --git a/.github/linters/.lychee.toml b/.github/linters/.lychee.toml deleted file mode 100644 index e27d79c..0000000 --- a/.github/linters/.lychee.toml +++ /dev/null @@ -1,60 +0,0 @@ -# https://lychee.cli.rs/#/usage/config -# Example config: https://github.com/lycheeverse/lychee/blob/master/lychee.example.toml - - -############################# Cache ############################### - -# Enable link caching. This can be helpful to avoid checking the same links on multiple runs. -cache = true - -# Discard all cached requests older than this duration. -max_cache_age = "1d" - -############################# Runtime ############################# - -# Maximum number of allowed redirects. -max_redirects = 6 - -# Maximum number of allowed retries before a link is declared dead. -max_retries = 2 - -# Maximum number of concurrent link checks. -# max_concurrency = 2 - -############################# Requests ############################ - -# User agent to send with each request. -user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0" - -# Website timeout from connect to response finished. -timeout = 45 - -# Minimum wait time in seconds between retries of failed requests. -retry_wait_time = 2 - -# Comma-separated list of accepted status codes for valid links. -accept = ["200", "206", "301", "429"] - -# Only test links with the given schemes (e.g. https). -# Omit to check links with any scheme. -scheme = ["https", "http", "file"] - -# Custom request headers -headers = ['Accept-Encoding: deflate, compress, gzip, br, zstd'] - -############################# Exclusions ########################## - -# Ignore case of paths when matching glob patterns. -glob_ignore_case = true - -# Exclude all private IPs from checking. -exclude_all_private = true - -# Exclude private IP address ranges from checking. -exclude_private = true - -# Exclude link-local IP address range from checking. -exclude_link_local = true - -# Exclude loopback IP address range and localhost from checking. -exclude_loopback = true diff --git a/.github/linters/.shellcheckrc b/.github/linters/.shellcheckrc new file mode 100644 index 0000000..fe8e96e --- /dev/null +++ b/.github/linters/.shellcheckrc @@ -0,0 +1,3 @@ +# .shellcheckrc + +disable=SC3037,SC2086,SC2155 diff --git a/.github/linters/actionlint.yaml b/.github/linters/actionlint.yaml new file mode 100644 index 0000000..09a68ad --- /dev/null +++ b/.github/linters/actionlint.yaml @@ -0,0 +1,20 @@ +--- +self-hosted-runner: + # Labels of self-hosted runner in array of strings. + labels: [] + +# Configuration variables in array of strings defined in your repository or +# organization. `null` means disabling configuration variables check. +# Empty array means no configuration variable is allowed. +config-variables: null + +# Configuration for file paths. The keys are glob patterns to match to file +# paths relative to the repository root. The values are the configurations for +# the file paths. Note that the path separator is always '/'. +# The following configurations are available. +# +# "ignore" is an array of regular expression patterns. Matched error messages +# are ignored. This is similar to the "-ignore" command line option. +paths: +# .github/workflows/**/*.yml: +# ignore: [] diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index f088020..f1bb155 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -92,6 +92,7 @@ jobs: name: ๐Ÿงช Check E2E Tests needs: e2e-tests runs-on: ubuntu-24.04 + timeout-minutes: 5 steps: - name: โœ… OK if: ${{ !(contains(needs.*.result, 'failure')) }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c4895df..17ca323 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,6 +29,7 @@ jobs: matrix: task: - gh + - sh - go - md - yml @@ -82,6 +83,7 @@ jobs: name: ๐Ÿงน Check Lint needs: lint runs-on: ubuntu-24.04 + timeout-minutes: 5 steps: - name: โœ… OK if: ${{ !(contains(needs.*.result, 'failure')) }} diff --git a/.taskfiles/github.Taskfile.yml b/.taskfiles/github.Taskfile.yml index 08e461d..94545b6 100644 --- a/.taskfiles/github.Taskfile.yml +++ b/.taskfiles/github.Taskfile.yml @@ -115,6 +115,7 @@ tasks: cmds: - for: { var: ITEMS } task: install:{{.ITEM}} + - task sh:tools:lint # * PinAct Runner _run:pinact: @@ -137,7 +138,7 @@ tasks: - defer: | {{if and .EXIT_CODE (eq .MODE "--check")}}echo "โš ๏ธ GitHub Workflows/Actions files are not formatted correctly. Please run 'task gh:run:pinact:fix' to fix formatting issues."{{end}} - pinact run {{.MODE}} - dir: "{{.USER_WORKING_DIR}}" + dir: "{{.ROOT_DIR}}" # * Run PinAct Check run:pinact:check: @@ -167,8 +168,8 @@ tasks: - "**/action.yml" - "**/action.yaml" cmds: - - actionlint -config-file ~/.linters/actionlint.yaml - dir: "{{.USER_WORKING_DIR}}" + - actionlint -config-file ./.github/linters/actionlint.yaml + dir: "{{.ROOT_DIR}}" # * Run GHALint run:ghalint: @@ -188,7 +189,7 @@ tasks: cmds: - ghalint run - ghalint run-action - dir: "{{.USER_WORKING_DIR}}" + dir: "{{.ROOT_DIR}}" # * Lint lint: diff --git a/.taskfiles/golang.Taskfile.yml b/.taskfiles/golang.Taskfile.yml index 55e63d6..d18280a 100644 --- a/.taskfiles/golang.Taskfile.yml +++ b/.taskfiles/golang.Taskfile.yml @@ -183,7 +183,7 @@ tasks: - "**/go.work" cmds: - golangci-lint run --fix - dir: "{{.USER_WORKING_DIR}}" + dir: "{{.ROOT_DIR}}" # * Lint lint: diff --git a/.taskfiles/markdown.Taskfile.yml b/.taskfiles/markdown.Taskfile.yml index a1cbd69..c41463a 100644 --- a/.taskfiles/markdown.Taskfile.yml +++ b/.taskfiles/markdown.Taskfile.yml @@ -56,8 +56,8 @@ tasks: cmds: - defer: | {{if and .EXIT_CODE (eq .MODE "")}}echo "โš ๏ธ Markdown files are not formatted correctly. Please run 'task md:run:markdownlint-cli2:fix' to fix formatting issues."{{end}} - - markdownlint-cli2 "./**/*.md" --config ".github/linters/.markdownlint-cli2.yaml" {{.MODE}} - dir: "{{.USER_WORKING_DIR}}" + - markdownlint-cli2 "./**/*.md" --config "./.github/linters/.markdownlint-cli2.yaml" {{.MODE}} + dir: "{{.ROOT_DIR}}" # * Run markdownlint-cli2 Check run:markdownlint-cli2:check: @@ -91,7 +91,7 @@ tasks: - defer: | {{if and .EXIT_CODE (eq .MODE "--check")}}echo "โš ๏ธ Markdown tables are not formatted correctly. Please run 'task md:run:markdown-table-formatter:fix' to fix formatting issues."{{end}} - markdown-table-formatter "./**/*.md" {{.MODE}} - dir: "{{.USER_WORKING_DIR}}" + dir: "{{.ROOT_DIR}}" # * Run markdown-table-formatter Check run:markdown-table-formatter:check: diff --git a/.taskfiles/scripts/install_shellcheck.sh b/.taskfiles/scripts/install_shellcheck.sh new file mode 100755 index 0000000..4779e0e --- /dev/null +++ b/.taskfiles/scripts/install_shellcheck.sh @@ -0,0 +1,143 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly GITHUB_OWNER="koalaman" +readonly GITHUB_REPO="shellcheck" +readonly TOOL_NAME="shellcheck" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +tempDir="" + +log() { + echo "-> $*" >&2 +} + +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +for dep in curl jq tar xz; do + command -v "${dep}" >/dev/null 2>&1 || die "Missing required dependency: ${dep}" +done + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +# Detect architecture +archRaw="$(uname -m)" +case "${archRaw}" in + x86_64 | amd64) arch="x86_64" ;; + arm64 | aarch64) arch="aarch64" ;; + *) die "Unsupported architecture: ${archRaw}" ;; +esac + +log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" + +# GitHub API authentication +ghAuthHeader=(-H "Accept: application/vnd.github+json") +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + ghAuthHeader+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +fi + +# Create temp directory +tempDir="$(mktemp -d)" || die "Failed to create temp directory" + +# Fetch release info and download URL +log "Fetching release information" +if [[ "${VERSION}" == "latest" ]]; then + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest" +else + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/tags/${VERSION}" +fi + +releaseJson="${tempDir}/release.json" +if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then + die "Failed to fetch release information. Check version or network connection." +fi + +# Extract download URL +downloadUrl="$(jq -r --arg arch "${arch}" \ + '.assets[] | select(.browser_download_url | endswith("linux.\($arch).tar.xz")) | .browser_download_url' \ + "${releaseJson}")" + +[[ -n "${downloadUrl}" ]] || die "No asset found for architecture ${arch}" + +log "Downloading ${downloadUrl}" +archivePath="${tempDir}/${TOOL_NAME}.tar.xz" +curl -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" + +# Extract binary +log "Extracting archive" +tar -xf "${archivePath}" -C "${tempDir}" || die "Extraction failed" +# Find and move the binary (it's in a versioned subdirectory) +binaryPath="$(find "${tempDir}" -name "${TOOL_NAME}" -type f)" || die "Binary not found in archive" +[[ -n "${binaryPath}" ]] || die "Binary not found in archive" +mv "${binaryPath}" "${tempDir}/${TOOL_NAME}" || die "Failed to move binary" +[[ -f "${tempDir}/${TOOL_NAME}" ]] || die "Binary not found in archive" + +# Install binary +log "Installing binary" +mv "${tempDir}/${TOOL_NAME}" "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to install binary" +chmod 0755 "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to set permissions" + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" + +# Run tool version to verify +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/install_shfmt.sh b/.taskfiles/scripts/install_shfmt.sh new file mode 100755 index 0000000..7ecab82 --- /dev/null +++ b/.taskfiles/scripts/install_shfmt.sh @@ -0,0 +1,150 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly GITHUB_OWNER="mvdan" +readonly GITHUB_REPO="sh" +readonly TOOL_NAME="shfmt" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +tempDir="" + +log() { + echo "-> $*" >&2 +} + +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +for dep in curl jq tar; do + command -v "${dep}" >/dev/null 2>&1 || die "Missing required dependency: ${dep}" +done + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +# Detect OS +osRaw="$(uname -s)" +case "${osRaw}" in + Linux) os="linux" ;; + Darwin) os="darwin" ;; + *) die "Unsupported operating system: ${osRaw}" ;; +esac + +# Detect architecture +archRaw="$(uname -m)" +case "${archRaw}" in + x86_64 | amd64) arch="amd64" ;; + arm64 | aarch64) arch="arm64" ;; + armv7l | armv6l) arch="arm" ;; + i386 | i686) arch="386" ;; + *) die "Unsupported architecture: ${archRaw}" ;; +esac + +log "Installing ${TOOL_NAME} (${VERSION}) for ${os}/${arch} to ${INSTALL_DIR}" + +# GitHub API authentication +ghAuthHeader=(-H "Accept: application/vnd.github+json") +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + ghAuthHeader+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +fi + +# Create temp directory +tempDir="$(mktemp -d)" || die "Failed to create temp directory" + +# Fetch release info and download URL +log "Fetching release information" +if [[ "${VERSION}" == "latest" ]]; then + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest" +else + apiUrl="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/tags/${VERSION}" +fi + +releaseJson="${tempDir}/release.json" +if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then + die "Failed to fetch release information. Check version or network connection." +fi + +# Extract download URL +downloadUrl="$(jq -r --arg os "${os}" --arg arch "${arch}" \ + '.assets[] | select(.browser_download_url | endswith("_\($os)_\($arch)")) | .browser_download_url' \ + "${releaseJson}")" + +[[ -n "${downloadUrl}" ]] || die "No asset found for ${os}/${arch}" + +log "Downloading ${downloadUrl}" +binaryPath="${tempDir}/${TOOL_NAME}" +curl -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${binaryPath}" || die "Download failed" + +# Install binary +log "Installing binary" +if [[ "${os}" == "darwin" ]]; then + # macOS install command may not support -D flag in all versions + mkdir -p "${INSTALL_DIR}" || die "Failed to create install directory" + install -m 0755 "${binaryPath}" "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to install binary" +else + # Linux install with -D flag + install -Dm0755 "${binaryPath}" "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to install binary" +fi + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" + +# Run tool version to verify +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/shell.Taskfile.yml b/.taskfiles/shell.Taskfile.yml new file mode 100644 index 0000000..238de84 --- /dev/null +++ b/.taskfiles/shell.Taskfile.yml @@ -0,0 +1,102 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json +# docs: https://taskfile.dev +--- +version: "3" + +vars: + SHELL_VERSION: + map: + shellcheck: latest + shfmt: latest + +tasks: + # * Install shellcheck + install:shellcheck: + desc: Install shellcheck + cmds: + - task: :internal:_install:winget + vars: + APP: koalaman.shellcheck + VERSION: "{{.SHELL_VERSION.shellcheck}}" + - task: :internal:_install:brew + vars: + APP: shellcheck@{{.SHELL_VERSION.shellcheck}} + - cmd: | + ./scripts/install_shellcheck.sh "{{.SHELL_VERSION.shellcheck}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Install shfmt + install:shfmt: + desc: Install shfmt + cmds: + - task: :internal:_install:winget + vars: + APP: mvdan.shfmt + VERSION: "{{.SHELL_VERSION.shfmt}}" + - cmd: | + ./scripts/install_shfmt.sh "{{.SHELL_VERSION.shfmt}}" + platforms: [linux, darwin] + dir: "{{.TASKFILE_DIR}}" + + # * Tools + tools:lint: + desc: Install Shell tools + aliases: + - tools + vars: + ITEMS: + - shellcheck + - shfmt + cmds: + - for: { var: ITEMS } + task: install:{{.ITEM}} + + # * Run shfmt + run:shfmt: + desc: Run shfmt + preconditions: + - sh: task internal:command -- shfmt + msg: "โŒ Error: shfmt is not installed. Please run 'task sh:install:shfmt" + - sh: task internal:command:version -- "shfmt --version" "{{.SHELL_VERSION.shfmt}}" + msg: "โŒ Version Mismatch: You are not using {{.SHELL_VERSION.shfmt}} version. Please run 'task sh:install:shfmt'" + vars: + SHELL_FILES: + sh: | + {{if eq OS "windows"}} + {{.PWSH}} 'shfmt --find . | Where-Object { $_ -notmatch "\\(.git|node_modules|\\.terraform|\\.venv|\\.dev)\\" }' + {{else}} + shfmt --find . | grep -v -E '\./\.git/|\./node_modules/|/\.terraform/|\.terraform/|\./\.venv/|\./\.dev/' + {{end}} + cmds: + - for: { var: SHELL_FILES } + cmd: shfmt -w -i 2 -ci -bn "{{osClean .ITEM}}" + dir: "{{.USER_WORKING_DIR}}" + + # * Run shellcheck + run:shellcheck: + desc: Run shellcheck + preconditions: + - sh: task internal:command -- shellcheck + msg: "โŒ Error: shellcheck is not installed. Please run 'task sh:install:shellcheck'" + - sh: task internal:command:version -- "shellcheck --version" "{{.SHELL_VERSION.shellcheck}}" + msg: "โŒ Version Mismatch: You are not using {{.SHELL_VERSION.shellcheck}} version. Please run 'task sh:install:shellcheck'" + vars: + ITEMS: + sh: | + {{if eq OS "windows"}} + {{.PWSH}} 'Get-ChildItem -Path . -Filter "*.sh" -Recurse | Where-Object { $_.FullName -notmatch "\\(.git|node_modules|.terraform|.venv|.dev)\\" } | ForEach-Object { $_.FullName | Resolve-Path -Relative }' + {{else}} + find . -type f -name "*.sh" -not -path "./.git/*" -not -path "./node_modules/*" -not -path "*/.venv/*" -not -path "*/.dev/*" -not -path "*/.terraform/*" -not -path ".terraform/*" + {{end}} + cmds: + - for: { var: ITEMS } + cmd: shellcheck "{{osClean .ITEM}}" + dir: "{{.USER_WORKING_DIR}}" + + # * Lint + lint: + desc: Lint Shell files + cmds: + - task: run:shfmt + - task: run:shellcheck diff --git a/.taskfiles/yaml.Taskfile.yml b/.taskfiles/yaml.Taskfile.yml index 2562fbd..cd6c983 100644 --- a/.taskfiles/yaml.Taskfile.yml +++ b/.taskfiles/yaml.Taskfile.yml @@ -41,8 +41,8 @@ tasks: - "**/*.yml" - "**/*.yaml" cmds: - - yamllint --config-file .github/linters/.yamllint.yml --format auto --strict . - dir: "{{.USER_WORKING_DIR}}" + - yamllint --config-file ./.github/linters/.yamllint.yml --format auto --strict . + dir: "{{.ROOT_DIR}}" # * Lint lint: diff --git a/Taskfile.yml b/Taskfile.yml index 63025ec..4b56dc1 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -18,6 +18,7 @@ includes: runtime: .taskfiles/runtime.Taskfile.yml az: .taskfiles/azure.Taskfile.yml gh: .taskfiles/github.Taskfile.yml + sh: .taskfiles/shell.Taskfile.yml go: .taskfiles/golang.Taskfile.yml md: .taskfiles/markdown.Taskfile.yml yml: .taskfiles/yaml.Taskfile.yml @@ -46,6 +47,7 @@ tasks: ITEMS: - az - gh + - sh - go - md - yml @@ -58,6 +60,7 @@ tasks: vars: ITEMS: - gh + - sh - go - md - yml From 3da071a79e7cdb88e864c2bbd064ca73b1ca7e4a Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:44:23 -0800 Subject: [PATCH 13/34] ci: fix argument parsing formatting in create-e2e-service-principals.sh Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .env.sh | 10 ---------- scripts/create-e2e-service-principals.sh | 6 +++--- 2 files changed, 3 insertions(+), 13 deletions(-) delete mode 100644 .env.sh diff --git a/.env.sh b/.env.sh deleted file mode 100644 index 8635594..0000000 --- a/.env.sh +++ /dev/null @@ -1,10 +0,0 @@ -SUBSCRIPTION_ID="YOUR_SUBSCRIPTION_ID" -TENANT_ID="YOUR_TENANT_ID" -# TEST_DEPLOYMENT_NAME_PFX="testDeploy" -# TEST_DEPLOYMENT_RESOURCE_GROUP_NAME_PFX="testdeployrg" -SP_CLIENT_ID="YOUR_SP_CLIENT_ID" -SP_CLIENT_SECRET="YOUR_SP_CLIENT_SECRET" -SP_OBJECT_ID="YOUR_SP_OBJECT_ID" -TEMPLATE_FILE="./templates/samples/aks.json" -PARAMETERS_FILE="./templates/samples/aks-parameters.json" - diff --git a/scripts/create-e2e-service-principals.sh b/scripts/create-e2e-service-principals.sh index 49f1dcc..1737561 100755 --- a/scripts/create-e2e-service-principals.sh +++ b/scripts/create-e2e-service-principals.sh @@ -59,7 +59,7 @@ EOF parse_args() { while [[ $# -gt 0 ]]; do case "$1" in - -y|--yes) + -y | --yes) YES=true shift ;; @@ -67,11 +67,11 @@ parse_args() { ADD_TO_GITHUB=true shift ;; - -o|--output-file) + -o | --output-file) OUTPUT_FILE="$2" shift 2 ;; - -h|--help) + -h | --help) usage exit 0 ;; From 3a89c17edc9b74431eebee6333ea77612cb68a6a Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:08:32 -0800 Subject: [PATCH 14/34] ci: update devcontainer and taskfiles for Terraform and OpenTofu installation, add Prettier configuration, and enhance VSCode settings Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .devcontainer/devcontainer.json | 52 +++++++--- .github/linters/.prettierrc.yml | 38 ++++++++ .github/workflows/e2e-tests.yaml | 8 +- .taskfiles/scripts/install_opentofu.sh | 87 +++++++++++++++++ .taskfiles/scripts/install_terraform.sh | 94 +++++++++++++++++++ .taskfiles/terraform.Taskfile.yml | 81 ++++++++++++++++ .vscode/settings.json | 55 +++++++++++ Taskfile.yml | 11 +++ .../rg-invalid-tfvars/dev.vars.tfvars | 2 +- 9 files changed, 410 insertions(+), 18 deletions(-) create mode 100644 .github/linters/.prettierrc.yml create mode 100755 .taskfiles/scripts/install_opentofu.sh create mode 100755 .taskfiles/scripts/install_terraform.sh create mode 100644 .taskfiles/terraform.Taskfile.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b1d74e4..b890b3d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,17 +6,13 @@ "ghcr.io/devcontainers/features/common-utils:2": { "configureZshAsDefaultShell": false }, - "ghcr.io/devcontainers/features/azure-cli:1": { - "version": "2.81.0", - "installBicep": true + "ghcr.io/eitsupi/devcontainer-features/jq-likes:2": { + // ? move to Taskfile? + "jqVersion": "latest", + "yqVersion": "latest", + "gojqVersion": "latest", + "jaqVersion": "latest" }, - "ghcr.io/devcontainers/features/terraform:1": { - "version": "1.14.3" - }, - "ghcr.io/devcontainers/features/go:1": { - "version": "1.25.5" - }, - "ghcr.io/devcontainers/features/powershell:1": {}, "ghcr.io/eitsupi/devcontainer-features/go-task:1": {} }, "workspaceMount": "source=${localWorkspaceFolder},target=${containerWorkspaceFolder},type=bind,consistency=cached", @@ -24,14 +20,48 @@ "remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" }, + "onCreateCommand": { + "init": "task init" + }, "postCreateCommand": { "git-safe-dir": "git config --global --add safe.directory ${containerWorkspaceFolder}" }, "customizations": { "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "terminal.integrated.defaultProfile.osx": "zsh", + "terminal.integrated.defaultProfile.windows": "PowerShell", + "powershell.powerShellAdditionalExePaths": { + "pwsh": "/usr/bin/pwsh" + }, + "powershell.powerShellDefaultVersion": "pwsh", + "shellformat.path": "/home/${remoteUser}/.local/bin/shfmt" + }, "extensions": [ + "redhat.vscode-yaml", + "golang.go", + "ms-vscode.powershell", "ms-azuretools.vscode-bicep", - "hashicorp.terraform" + "edwinhuish.better-comments-next", + "timonwong.shellcheck", + "task.vscode-task", + "esbenp.prettier-vscode", + "fnando.linter", + "ms-azuretools.vscode-azure-github-copilot", + "ms-azuretools.vscode-docker", + "davidanson.vscode-markdownlint", + "hashicorp.terraform", + "github.copilot", + "github.copilot-chat", + "github.vscode-github-actions", + "github.vscode-pull-request-github", + "github.codespaces", + "github.remotehub", + "bierner.github-markdown-preview", + "usernamehw.errorlens", + "lumirelle.shell-format-rev", + "editorconfig.editorconfig" ] } } diff --git a/.github/linters/.prettierrc.yml b/.github/linters/.prettierrc.yml new file mode 100644 index 0000000..fe8877d --- /dev/null +++ b/.github/linters/.prettierrc.yml @@ -0,0 +1,38 @@ +# yaml-language-server: $schema=https://www.schemastore.org/prettierrc.json +--- +arrowParens: always +bracketSpacing: true +endOfLine: lf +htmlWhitespaceSensitivity: css +insertPragma: false # consider true +singleAttributePerLine: false +bracketSameLine: false +jsxSingleQuote: true +printWidth: 120 +proseWrap: preserve +quoteProps: as-needed +requirePragma: false # consider true +semi: true +singleQuote: true +tabWidth: 2 +trailingComma: none +useTabs: false +vueIndentScriptAndStyle: true +embeddedLanguageFormatting: auto +experimentalTernaries: true +experimentalOperatorPosition: end +# multilineArraysWrapThreshold: 1 +# plugins: +# - prettier-plugin-multiline-arrays + +# Language-specific overrides +overrides: + - files: "*.md" + options: + proseWrap: always + - files: "*.yml" + options: + singleQuote: false + - files: "*.yaml" + options: + singleQuote: false diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index f1bb155..7304a63 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -8,7 +8,6 @@ on: pull_request: merge_group: workflow_dispatch: - schedule: - cron: 0 21 * * * @@ -54,12 +53,9 @@ jobs: if: matrix.type == 'bicep' run: task az:install:bicep - - name: ๐Ÿšง Setup Terraform + - name: ๐Ÿ“ฆ Install Terraform if: matrix.type == 'terraform' - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 - with: - terraform_wrapper: false - terraform_version: 1.14.3 + run: task tf:install:terraform - name: ๐Ÿ” Azure Login uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 diff --git a/.taskfiles/scripts/install_opentofu.sh b/.taskfiles/scripts/install_opentofu.sh new file mode 100755 index 0000000..70f4a4e --- /dev/null +++ b/.taskfiles/scripts/install_opentofu.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly TOOL_NAME="tofu" +readonly INSTALL_SCRIPT_URL="https://get.opentofu.org/install-opentofu.sh" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" +INSTALL_DIR="${2:-${INSTALL_DIR:-}}" + +# Logging helper +log() { + echo "-> $*" >&2 +} + +# Error handling helper +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} + +# Help message +usage() { + cat < "latest", numeric -> add "v" prefix +if [[ -z "${VERSION//[[:space:]]/}" ]]; then + VERSION="latest" +elif [[ "${VERSION}" != "latest" && "${VERSION}" =~ ^[0-9] ]]; then + VERSION="v${VERSION}" +fi + +# Determine install directory +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ "${EUID}" -eq 0 ]]; then + INSTALL_DIR="/usr/local/bin" + else + INSTALL_DIR="${HOME}/.local/bin" + fi +fi + +# Check dependencies +command -v curl >/dev/null 2>&1 || die "Missing required dependency: curl" + +# Create install directory if it doesn't exist +if [[ ! -d "${INSTALL_DIR}" ]]; then + mkdir -p "${INSTALL_DIR}" || die "Cannot create install directory ${INSTALL_DIR}" +fi + +log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" + +# Execute remote installation script +log "Fetching and executing official installation script" +if ! curl -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | sh -s -- --install-method standalone --opentofu-version "${VERSION}" --install-path "${INSTALL_DIR}"; then + die "Installation failed. Check version or network connection." +fi + +log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" + +# Run tool version to verify +"${INSTALL_DIR}/${TOOL_NAME}" -version || die "Installed binary failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/install_terraform.sh b/.taskfiles/scripts/install_terraform.sh new file mode 100755 index 0000000..470eb35 --- /dev/null +++ b/.taskfiles/scripts/install_terraform.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +set -euo pipefail + +# Constants +readonly TOOL_NAME="terraform" +readonly KEYRING_URL="https://apt.releases.hashicorp.com/gpg" +readonly KEYRING_PATH="/usr/share/keyrings/hashicorp-archive-keyring.gpg" +readonly SOURCES_LIST="/etc/apt/sources.list.d/hashicorp.list" + +# Configuration (can be overridden by env) +VERSION="${1:-${VERSION:-latest}}" + +log() { + echo "-> $*" >&2 +} + +die() { + echo "X Error: $*" >&2 + exit "${2:-1}" +} + +usage() { + cat </dev/null 2>&1; then + die "Missing required dependency: ${dep}" + fi +done + +# Download and install GPG key +log "Setting up HashiCorp GPG key" +if ! wget -O - "${KEYRING_URL}" | gpg --batch --yes --dearmor -o "${KEYRING_PATH}"; then + die "Failed to download and install GPG keyring" +fi + +# Add apt repository +log "Adding HashiCorp apt repository" +arch="$(dpkg --print-architecture)" || die "Failed to detect architecture" +codename="$(lsb_release -cs)" || die "Failed to detect distribution codename" + +echo "deb [arch=${arch} signed-by=${KEYRING_PATH}] https://apt.releases.hashicorp.com ${codename} main" \ + | tee "${SOURCES_LIST}" >/dev/null || die "Failed to add apt repository" + +# Update apt cache and install +log "Updating apt cache" +apt-get update || die "Failed to update apt cache" + +log "Installing ${TOOL_NAME}" +if [[ "${VERSION}" == "latest" ]]; then + apt-get install -y terraform || die "Failed to install ${TOOL_NAME}" +else + # Try to install specific version (format: terraform=version) + apt-get install -y "terraform=${VERSION}" || die "Failed to install ${TOOL_NAME} version ${VERSION}" +fi + +log "โœ“ Successfully installed ${TOOL_NAME}" + +# Verify installation +"${TOOL_NAME}" --version || die "Installed binary failed to run" diff --git a/.taskfiles/terraform.Taskfile.yml b/.taskfiles/terraform.Taskfile.yml new file mode 100644 index 0000000..b5e2ffe --- /dev/null +++ b/.taskfiles/terraform.Taskfile.yml @@ -0,0 +1,81 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json +# docs: https://taskfile.dev +--- +version: "3" + +vars: + TERRAFORM_VERSION: + map: + terraform: latest + opentofu: latest + +tasks: + # * Install Terraform + install:terraform: + desc: Install Terraform + cmds: + - task: :internal:_install:winget + vars: + APP: HashiCorp.Terraform + VERSION: "{{.TERRAFORM_VERSION.terraform}}" + - task: :internal:_install:brew:tap + vars: + APP: hashicorp/tap + - task: :internal:_install:brew + vars: + APP: hashicorp/tap/terraform@{{.TERRAFORM_VERSION.terraform}} + - cmd: | + sudo ./scripts/install_terraform.sh "{{.TERRAFORM_VERSION.terraform}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Install OpenTofu + install:opentofu: + desc: Install OpenTofu + cmds: + - task: :internal:_install:winget + vars: + APP: OpenTofu.Tofu + VERSION: "{{.TERRAFORM_VERSION.opentofu}}" + - task: :internal:_install:brew + vars: + APP: opentofu@{{.TERRAFORM_VERSION.opentofu}} + - cmd: | + ./scripts/install_opentofu.sh "{{.TERRAFORM_VERSION.opentofu}}" + platforms: [linux] + dir: "{{.TASKFILE_DIR}}" + + # * Tools + tools:lint: + desc: Install Terraform tools + aliases: + - tools + vars: + ITEMS: + - terraform + - opentofu + cmds: + - for: { var: ITEMS } + task: install:{{.ITEM}} + + # * Run Terraform fmt + run:terraform-fmt: + desc: Run terraform fmt + preconditions: + - sh: task internal:command -- terraform + msg: "โŒ Error: terraform is not installed. Please run 'task tf:install:terraform'" + - sh: task internal:command:version -- "terraform --version" "{{.TERRAFORM_VERSION.terraform}}" + msg: "โŒ Version Mismatch: You are not using {{.TERRAFORM_VERSION.terraform}} version. Please run 'task tf:install:terraform'" + sources: + - "**/*.tf" + - "**/*.tfvars" + - "**/*.hcl" + cmds: + - terraform fmt -recursive + dir: "{{.ROOT_DIR}}" + + # * Lint + lint: + desc: Lint Terraform files + cmds: + - task: run:terraform-fmt diff --git a/.vscode/settings.json b/.vscode/settings.json index b31e31b..e8b40d2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,58 @@ { + // github + "github.branchProtection": true, + "githubPullRequests.showPullRequestNumberInTree": true, + // javascript + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + // typescript + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + // json + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "prettier.configPath": "./.github/linters/.prettierrc.yml", + // yaml + "[yaml]": { + "editor.defaultFormatter": "redhat.vscode-yaml" + }, + "yaml.format.singleQuote": false, + "yaml.schemaStore.enable": true, + // markdown + "[markdown]": { + "editor.defaultFormatter": "DavidAnson.vscode-markdownlint", + "editor.formatOnSave": true, + "editor.formatOnPaste": true + }, + "markdownlint.configFile": "./.github/linters/.markdownlint.yml", + "markdown.extension.toc.levels": "2..6", + "markdown.extension.toc.updateOnSave": true, + "markdown.extension.tableFormatter.normalizeIndentation": true, + "markdown.extension.tableFormatter.delimiterRowNoPadding": true, + // shell + "shellcheck.customArgs": [ + "--rcfile", + "./.github/linters/.shellcheckrc" + ], + "shellcheck.enable": true, + "shellcheck.enableQuickFix": true, + "shellcheck.useWorkspaceRootAsCwd": true, + "shellformat.useEditorConfig": true, + // general lint + "linter.linters": { + "yamllint": { + "configFiles": ["./.github/linters/.yamllint.yml"] + }, + "markdownlint": { + "configFiles": ["./.github/linters/.markdownlint.yml"] + } + }, + // golang "go.testEnvFile": "${workspaceFolder}/dev.env" } diff --git a/Taskfile.yml b/Taskfile.yml index 4b56dc1..f1e552f 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -20,6 +20,7 @@ includes: gh: .taskfiles/github.Taskfile.yml sh: .taskfiles/shell.Taskfile.yml go: .taskfiles/golang.Taskfile.yml + tf: .taskfiles/terraform.Taskfile.yml md: .taskfiles/markdown.Taskfile.yml yml: .taskfiles/yaml.Taskfile.yml @@ -49,6 +50,7 @@ tasks: - gh - sh - go + - tf - md - yml cmds: @@ -62,12 +64,21 @@ tasks: - gh - sh - go + - tf - md - yml deps: - for: { var: ITEMS } task: "{{.ITEM}}:lint" + init: + desc: Initialize development environment + silent: true + cmds: + - task: runtime:setup + - task: tools + dir: "{{.USER_WORKING_DIR}}" + deps: desc: Check if dependencies are up to date cmds: diff --git a/samples/terraform/rg-invalid-tfvars/dev.vars.tfvars b/samples/terraform/rg-invalid-tfvars/dev.vars.tfvars index 3c56856..1778e7c 100644 --- a/samples/terraform/rg-invalid-tfvars/dev.vars.tfvars +++ b/samples/terraform/rg-invalid-tfvars/dev.vars.tfvars @@ -1 +1 @@ -location = +location = "" From dc497f7fff474d07112346648b74a141958bfa28 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:11:37 -0800 Subject: [PATCH 15/34] ci: improve error messages for tool installation verification scripts Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .taskfiles/scripts/install_ghalint.sh | 2 +- .taskfiles/scripts/install_goreleaser.sh | 4 ++-- .taskfiles/scripts/install_pinact.sh | 4 ++-- .taskfiles/scripts/install_shellcheck.sh | 2 +- .taskfiles/scripts/install_shfmt.sh | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.taskfiles/scripts/install_ghalint.sh b/.taskfiles/scripts/install_ghalint.sh index 94a66a7..bb9d7ad 100755 --- a/.taskfiles/scripts/install_ghalint.sh +++ b/.taskfiles/scripts/install_ghalint.sh @@ -142,4 +142,4 @@ chmod 0755 "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to set permissions" log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" # Run tool version to verify -"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed binary failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/install_goreleaser.sh b/.taskfiles/scripts/install_goreleaser.sh index dfdf414..e0b4699 100755 --- a/.taskfiles/scripts/install_goreleaser.sh +++ b/.taskfiles/scripts/install_goreleaser.sh @@ -136,7 +136,7 @@ downloadUrl="$(jq -r --arg os "${os}" --arg arch "${arch}" \ log "Downloading ${downloadUrl}" archivePath="${tempDir}/${TOOL_NAME}.tar.gz" -curl -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" +curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" # Extract binary log "Extracting archive" @@ -157,4 +157,4 @@ fi log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" # Run tool version to verify -"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed binary failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/install_pinact.sh b/.taskfiles/scripts/install_pinact.sh index 4be8abd..dbfe0b5 100755 --- a/.taskfiles/scripts/install_pinact.sh +++ b/.taskfiles/scripts/install_pinact.sh @@ -123,7 +123,7 @@ downloadUrl="$(jq -r --arg arch "${arch}" \ log "Downloading ${downloadUrl}" archivePath="${tempDir}/${TOOL_NAME}.tar.gz" -curl -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" +curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" # Extract binary log "Extracting archive" @@ -138,4 +138,4 @@ chmod 0755 "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to set permissions" log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" # Run tool version to verify -"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed binary failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/install_shellcheck.sh b/.taskfiles/scripts/install_shellcheck.sh index 4779e0e..d188fd0 100755 --- a/.taskfiles/scripts/install_shellcheck.sh +++ b/.taskfiles/scripts/install_shellcheck.sh @@ -140,4 +140,4 @@ chmod 0755 "${INSTALL_DIR}/${TOOL_NAME}" || die "Failed to set permissions" log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" # Run tool version to verify -"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed binary failed to run (${INSTALL_DIR}/${TOOL_NAME})" diff --git a/.taskfiles/scripts/install_shfmt.sh b/.taskfiles/scripts/install_shfmt.sh index 7ecab82..5818748 100755 --- a/.taskfiles/scripts/install_shfmt.sh +++ b/.taskfiles/scripts/install_shfmt.sh @@ -147,4 +147,4 @@ fi log "โœ“ Successfully installed ${TOOL_NAME} to ${INSTALL_DIR}/${TOOL_NAME}" # Run tool version to verify -"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed failed to run (${INSTALL_DIR}/${TOOL_NAME})" +"${INSTALL_DIR}/${TOOL_NAME}" --version || die "Installed binary failed to run (${INSTALL_DIR}/${TOOL_NAME})" From eb48c0888f8e1235fe5332859be606e77287070c Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:19:48 -0800 Subject: [PATCH 16/34] ci: add merge_group trigger to lint workflow Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/workflows/lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 17ca323..4888138 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,6 +14,7 @@ on: - reopened - synchronize - ready_for_review + merge_group: permissions: {} From 0d68867b4010f22f42521d4c3c40a16e3c8f5c6f Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:25:46 -0800 Subject: [PATCH 17/34] ci: add step to ensure bin path in e2e tests workflow Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/workflows/e2e-tests.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index 7304a63..bd8d611 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -57,6 +57,10 @@ jobs: if: matrix.type == 'terraform' run: task tf:install:terraform + - name: ๐ŸŒŒ Ensure bin path + run: | + export PATH="$HOME/.local/bin:$PATH" + - name: ๐Ÿ” Azure Login uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 with: From c8d2c340ab54af965c5bf5ea70150cc57039a0f2 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:35:08 -0800 Subject: [PATCH 18/34] ci: update bin path setup for Linux and Windows in workflows Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/workflows/e2e-tests.yaml | 8 +++++++- .github/workflows/lint.yml | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index bd8d611..89a4f8d 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -58,8 +58,14 @@ jobs: run: task tf:install:terraform - name: ๐ŸŒŒ Ensure bin path + if: runner.os == 'Linux' + run: | + echo "::add-path::$HOME/.local/bin" + + - name: ๐ŸŒŒ Ensure bin path + if: runner.os == 'Windows' run: | - export PATH="$HOME/.local/bin:$PATH" + echo "::add-path::%USERPROFILE%\.local\bin" - name: ๐Ÿ” Azure Login uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4888138..eba489c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -70,7 +70,7 @@ jobs: - name: ๐ŸŒŒ Ensure bin path run: | - export PATH="$HOME/.local/bin:$PATH" + echo "::add-path::$HOME/.local/bin" - name: โœ”๏ธ Run lint (${{ matrix.task }}) run: task ${{ matrix.task }}:lint From 8185af4d1941e4fae0092c256b625d78e0649fde Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:39:39 -0800 Subject: [PATCH 19/34] ci: fix command syntax in Bicep validation task Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- Taskfile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Taskfile.yml b/Taskfile.yml index f1e552f..dd0d3c6 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -254,7 +254,7 @@ tasks: cmds: - task: _testcli:validate vars: - CMD: go run ./cmd bicep --bicepFilePath ./samples/bicep/aks-private-subnet.bicep --parametersFilePath ./samples/bicep/aks-private-subnet-params.json --bicepExecPath \"{{.MPF_BICEPEXECPATH}}\" + CMD: go run ./cmd bicep --bicepFilePath ./samples/bicep/aks-private-subnet.bicep --parametersFilePath ./samples/bicep/aks-private-subnet-params.json --bicepExecPath "{{.MPF_BICEPEXECPATH}}" EXPECTED: "{{.EXPECTED}}" PRE_CMD: "" From 82625085b0181ade6c6e2faf7300d321dfe62dce Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:45:08 -0800 Subject: [PATCH 20/34] ci: update bin path setup for Linux and Windows in lint and e2e tests workflows Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/workflows/e2e-tests.yaml | 12 ++++++++++-- .github/workflows/lint.yml | 16 +++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index 89a4f8d..df004a7 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -60,12 +60,20 @@ jobs: - name: ๐ŸŒŒ Ensure bin path if: runner.os == 'Linux' run: | - echo "::add-path::$HOME/.local/bin" + INSTALL_PATH="$HOME/.local/bin" + + echo "$INSTALL_PATH" >> "${GITHUB_PATH}" + export PATH="$INSTALL_PATH:$PATH" + shell: bash - name: ๐ŸŒŒ Ensure bin path if: runner.os == 'Windows' run: | - echo "::add-path::%USERPROFILE%\.local\bin" + $INSTALL_PATH = "$env:USERPROFILE\.local\bin" + + $INSTALL_PATH | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + $env:PATH = "$INSTALL_PATH;$env:PATH" + shell: pwsh - name: ๐Ÿ” Azure Login uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index eba489c..e8ab407 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -69,8 +69,22 @@ jobs: GITHUB_TOKEN: ${{ github.token }} - name: ๐ŸŒŒ Ensure bin path + if: runner.os == 'Linux' run: | - echo "::add-path::$HOME/.local/bin" + INSTALL_PATH="$HOME/.local/bin" + + echo "$INSTALL_PATH" >> "${GITHUB_PATH}" + export PATH="$INSTALL_PATH:$PATH" + shell: bash + + - name: ๐ŸŒŒ Ensure bin path + if: runner.os == 'Windows' + run: | + $INSTALL_PATH = "$env:USERPROFILE\.local\bin" + + $INSTALL_PATH | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + $env:PATH = "$INSTALL_PATH;$env:PATH" + shell: pwsh - name: โœ”๏ธ Run lint (${{ matrix.task }}) run: task ${{ matrix.task }}:lint From 30efced6b6dced9d1a7781cc282a4f477a48f5a9 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:53:58 -0800 Subject: [PATCH 21/34] ci: remove unnecessary --exact flag from winget install command Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .taskfiles/_internal.Taskfile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.taskfiles/_internal.Taskfile.yml b/.taskfiles/_internal.Taskfile.yml index b2fb365..6f74ec0 100644 --- a/.taskfiles/_internal.Taskfile.yml +++ b/.taskfiles/_internal.Taskfile.yml @@ -83,7 +83,7 @@ tasks: # yamllint disable-line rule:quoted-strings VERSION: '{{if and .VERSION (ne .VERSION "latest")}}--version {{.VERSION}}{{end}}' cmds: - - winget install --id "{{.APP}}" --exact --accept-source-agreements --accept-package-agreements --disable-interactivity {{.VERSION}} + - winget install --id "{{.APP}}" --accept-source-agreements --accept-package-agreements --disable-interactivity {{.VERSION}} ignore_error: true platforms: [windows] From 67693a965b369027f70bfb154d53a9afc142569f Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 23:09:22 -0800 Subject: [PATCH 22/34] ci: update Windows installation paths in lint and e2e tests workflows Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/workflows/e2e-tests.yaml | 7 +++++++ .github/workflows/lint.yml | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index df004a7..e70747c 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -70,7 +70,14 @@ jobs: if: runner.os == 'Windows' run: | $INSTALL_PATH = "$env:USERPROFILE\.local\bin" + $INSTALL_PATH | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + $env:PATH = "$INSTALL_PATH;$env:PATH" + + $INSTALL_PATH = "$env:LOCALAPPDATA\Microsoft\WinGet\Links" + $INSTALL_PATH | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + $env:PATH = "$INSTALL_PATH;$env:PATH" + $INSTALL_PATH = "$env:LOCALAPPDATA\Microsoft\WindowsApps" $INSTALL_PATH | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append $env:PATH = "$INSTALL_PATH;$env:PATH" shell: pwsh diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e8ab407..a52def1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -81,7 +81,14 @@ jobs: if: runner.os == 'Windows' run: | $INSTALL_PATH = "$env:USERPROFILE\.local\bin" + $INSTALL_PATH | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + $env:PATH = "$INSTALL_PATH;$env:PATH" + + $INSTALL_PATH = "$env:LOCALAPPDATA\Microsoft\WinGet\Links" + $INSTALL_PATH | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + $env:PATH = "$INSTALL_PATH;$env:PATH" + $INSTALL_PATH = "$env:LOCALAPPDATA\Microsoft\WindowsApps" $INSTALL_PATH | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append $env:PATH = "$INSTALL_PATH;$env:PATH" shell: pwsh From 812bc42dfba87ab33d0f493dccb1e7db59662555 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 23:15:35 -0800 Subject: [PATCH 23/34] ci: fix command syntax in Terraform validation task Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- Taskfile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Taskfile.yml b/Taskfile.yml index dd0d3c6..ebaea9f 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -275,7 +275,7 @@ tasks: cmds: - task: _testcli:validate vars: - CMD: go run ./cmd terraform --workingDir ./samples/terraform/aci --varFilePath ./samples/terraform/aci/dev.vars.tfvars --tfPath \"{{.MPF_TFPATH}}\" + CMD: go run ./cmd terraform --workingDir ./samples/terraform/aci --varFilePath ./samples/terraform/aci/dev.vars.tfvars --tfPath "{{.MPF_TFPATH}}" EXPECTED: "{{.EXPECTED}}" PRE_CMD: | echo "Terraform path: $MPF_TFPATH" From b101f979084d171e63aa267a02e6d546b0fade2c Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Wed, 17 Dec 2025 23:37:30 -0800 Subject: [PATCH 24/34] ci: add concurrency settings to CI workflows Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- .github/workflows/e2e-tests.yaml | 7 +++++++ .github/workflows/lint.yml | 4 ++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69aab30..ea072f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,8 @@ on: # - cron: "0 2 * * *" workflow_dispatch: +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -29,8 +31,6 @@ concurrency: env: CLI_VERSION: ${{ github.sha }} -permissions: {} - jobs: build: name: ๐Ÿ—๏ธ Build diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index e70747c..65c1ce2 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -13,6 +13,10 @@ on: permissions: {} +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + jobs: e2e-tests: if: github.event_name != 'pull_request' @@ -28,6 +32,9 @@ jobs: contents: read id-token: write steps: + - name: ๐Ÿฉบ Debug + uses: raven-actions/debug@9dbdeb7eea607a7d73411895c65987e71d59a466 # v1.2.0 + - name: โคต๏ธ Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a52def1..e4098e9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,6 +18,10 @@ on: permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint: name: ๐Ÿงน Lint From 5fd6942c8042c6c8bca4021e86619668a9148901 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Thu, 18 Dec 2025 00:15:37 -0800 Subject: [PATCH 25/34] ci: update YAML linting rules and Goreleaser configuration Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .github/linters/.yamllint.yml | 1 + .goreleaser.yml | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/linters/.yamllint.yml b/.github/linters/.yamllint.yml index 9019ab1..2202ca8 100644 --- a/.github/linters/.yamllint.yml +++ b/.github/linters/.yamllint.yml @@ -19,6 +19,7 @@ rules: min-spaces-from-content: 1 braces: level: warning + min-spaces-inside: 0 max-spaces-inside: 1 truthy: check-keys: false diff --git a/.goreleaser.yml b/.goreleaser.yml index 2259b05..907f650 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -3,6 +3,10 @@ --- version: 2 +project_name: azmpf + +report_sizes: true + before: hooks: - go mod download @@ -11,8 +15,11 @@ archives: - files: - src: LICENSE dst: LICENSE.txt - formats: [zip] - name_template: az{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }} + format_overrides: + - goos: windows + formats: + - zip + name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" builds: - dir: cmd @@ -22,7 +29,7 @@ builds: flags: - -trimpath ldflags: - - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{ .CommitDate }} + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{ .Date }} goos: - windows - linux @@ -35,11 +42,16 @@ builds: ignore: - goos: darwin goarch: "386" - binary: az{{ .ProjectName }}_v{{ .Version }} + binary: "{{ .ProjectName }}" + +sboms: + - id: default + disable: false checksum: algorithm: sha256 - name_template: az{{ .ProjectName }}_{{ .Version }}_SHA256SUMS + name_template: "{{ .ProjectName }}_SHA256SUMS" + signs: - artifacts: checksum args: From 4db4a76993180d6f7987535287f1b2ada7d10d58 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Thu, 18 Dec 2025 00:25:33 -0800 Subject: [PATCH 26/34] ci: update curl commands to include GitHub API authentication headers Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .taskfiles/scripts/install_bicep.sh | 2 +- .taskfiles/scripts/install_shellcheck.sh | 2 +- .taskfiles/scripts/install_shfmt.sh | 2 +- .taskfiles/scripts/setup_fnm.sh | 9 +-------- .taskfiles/scripts/setup_uv.sh | 12 +----------- 5 files changed, 5 insertions(+), 22 deletions(-) diff --git a/.taskfiles/scripts/install_bicep.sh b/.taskfiles/scripts/install_bicep.sh index 7028ac1..fcd9a58 100755 --- a/.taskfiles/scripts/install_bicep.sh +++ b/.taskfiles/scripts/install_bicep.sh @@ -125,7 +125,7 @@ downloadUrl="$(jq -r --arg arch "${arch}" \ log "Downloading ${downloadUrl}" binaryPath="${tempDir}/${TOOL_NAME}" -curl -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${binaryPath}" || die "Download failed" +curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${binaryPath}" || die "Download failed" # Install binary log "Installing binary" diff --git a/.taskfiles/scripts/install_shellcheck.sh b/.taskfiles/scripts/install_shellcheck.sh index d188fd0..5ea59d9 100755 --- a/.taskfiles/scripts/install_shellcheck.sh +++ b/.taskfiles/scripts/install_shellcheck.sh @@ -121,7 +121,7 @@ downloadUrl="$(jq -r --arg arch "${arch}" \ log "Downloading ${downloadUrl}" archivePath="${tempDir}/${TOOL_NAME}.tar.xz" -curl -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" +curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" # Extract binary log "Extracting archive" diff --git a/.taskfiles/scripts/install_shfmt.sh b/.taskfiles/scripts/install_shfmt.sh index 5818748..3de1619 100755 --- a/.taskfiles/scripts/install_shfmt.sh +++ b/.taskfiles/scripts/install_shfmt.sh @@ -131,7 +131,7 @@ downloadUrl="$(jq -r --arg os "${os}" --arg arch "${arch}" \ log "Downloading ${downloadUrl}" binaryPath="${tempDir}/${TOOL_NAME}" -curl -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${binaryPath}" || die "Download failed" +curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${binaryPath}" || die "Download failed" # Install binary log "Installing binary" diff --git a/.taskfiles/scripts/setup_fnm.sh b/.taskfiles/scripts/setup_fnm.sh index 55abd19..620e8ba 100755 --- a/.taskfiles/scripts/setup_fnm.sh +++ b/.taskfiles/scripts/setup_fnm.sh @@ -71,16 +71,9 @@ fi log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" -# GitHub API authentication -if [[ -n "${GITHUB_TOKEN:-}" ]]; then - ghAuthHeader=(-H "Authorization: Bearer ${GITHUB_TOKEN}") -else - ghAuthHeader=() -fi - # Execute remote installation script log "Fetching and executing official installation script" -if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | bash -s -- --skip-shell --install-dir "${INSTALL_DIR}" --release "${VERSION}"; then +if ! curl -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | bash -s -- --skip-shell --install-dir "${INSTALL_DIR}" --release "${VERSION}"; then die "Installation failed. Check version or network connection." fi diff --git a/.taskfiles/scripts/setup_uv.sh b/.taskfiles/scripts/setup_uv.sh index 6a7a58f..6e34463 100755 --- a/.taskfiles/scripts/setup_uv.sh +++ b/.taskfiles/scripts/setup_uv.sh @@ -76,19 +76,9 @@ fi log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" -# GitHub API authentication -if [[ -n "${GITHUB_TOKEN:-}" ]]; then - ghAuthHeader=(-H "Authorization: Bearer ${GITHUB_TOKEN}") -else - ghAuthHeader=() -fi - -log "INSTALL_DIR=${INSTALL_DIR}" -log "VERSION=${VERSION}" - # Execute remote installation script log "Fetching and executing official installation script" -if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | env UV_INSTALL_DIR="${INSTALL_DIR}" UV_VERSION="${VERSION}" UV_GITHUB_TOKEN="${GITHUB_TOKEN:-}" sh; then +if ! curl -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | env UV_INSTALL_DIR="${INSTALL_DIR}" UV_VERSION="${VERSION}" UV_GITHUB_TOKEN="${GITHUB_TOKEN:-}" sh; then die "Installation failed. Check version or network connection." fi From 91e2f041eee1ab51cfdb52ef9abf33d83bd99156 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Thu, 18 Dec 2025 09:55:09 -0800 Subject: [PATCH 27/34] ci: remove unnecessary TLS version specification in curl commands Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .taskfiles/scripts/install_act.sh | 2 +- .taskfiles/scripts/install_actionlint.sh | 2 +- .taskfiles/scripts/install_azd.sh | 2 +- .taskfiles/scripts/install_bicep.sh | 4 ++-- .taskfiles/scripts/install_ghalint.sh | 4 ++-- .taskfiles/scripts/install_golangci-lint.sh | 2 +- .taskfiles/scripts/install_goreleaser.sh | 5 +++-- .taskfiles/scripts/install_opentofu.sh | 2 +- .taskfiles/scripts/install_pinact.sh | 4 ++-- .taskfiles/scripts/install_shellcheck.sh | 4 ++-- .taskfiles/scripts/install_shfmt.sh | 4 ++-- .taskfiles/scripts/setup_fnm.sh | 2 +- .taskfiles/scripts/setup_golang.sh | 2 +- .taskfiles/scripts/setup_uv.sh | 2 +- 14 files changed, 21 insertions(+), 20 deletions(-) diff --git a/.taskfiles/scripts/install_act.sh b/.taskfiles/scripts/install_act.sh index 04a6ab0..14187b9 100755 --- a/.taskfiles/scripts/install_act.sh +++ b/.taskfiles/scripts/install_act.sh @@ -86,7 +86,7 @@ fi # Execute remote installation script log "Fetching and executing official installation script" -if ! curl "${ghAuthHeader[@]}" --proto '=https' --tlsv1.3 -fsSL "${INSTALL_SCRIPT_URL}" | /bin/bash -s -- -b "${INSTALL_DIR}" "${VERSION}"; then +if ! curl "${ghAuthHeader[@]}" -fsSL "${INSTALL_SCRIPT_URL}" | /bin/bash -s -- -b "${INSTALL_DIR}" "${VERSION}"; then die "Installation failed. Check version or network connection." fi diff --git a/.taskfiles/scripts/install_actionlint.sh b/.taskfiles/scripts/install_actionlint.sh index f485b20..4d1bf76 100755 --- a/.taskfiles/scripts/install_actionlint.sh +++ b/.taskfiles/scripts/install_actionlint.sh @@ -86,7 +86,7 @@ fi # Execute remote installation script log "Fetching and executing official installation script" -if ! curl "${ghAuthHeader[@]}" --proto '=https' --tlsv1.3 -fsSL "${INSTALL_SCRIPT_URL}" | /bin/bash -s -- "${VERSION}" "${INSTALL_DIR}"; then +if ! curl "${ghAuthHeader[@]}" -fsSL "${INSTALL_SCRIPT_URL}" | /bin/bash -s -- "${VERSION}" "${INSTALL_DIR}"; then die "Installation failed. Check version or network connection." fi diff --git a/.taskfiles/scripts/install_azd.sh b/.taskfiles/scripts/install_azd.sh index c6603e2..7d929fd 100755 --- a/.taskfiles/scripts/install_azd.sh +++ b/.taskfiles/scripts/install_azd.sh @@ -77,7 +77,7 @@ log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" # Execute remote installation script log "Fetching and executing official installation script" -if ! curl -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | /bin/bash -s -- --version "${VERSION}" --install-folder "${INSTALL_DIR}" --symlink-folder "${INSTALL_DIR}"; then +if ! curl -fsSL "${INSTALL_SCRIPT_URL}" | /bin/bash -s -- --version "${VERSION}" --install-folder "${INSTALL_DIR}" --symlink-folder "${INSTALL_DIR}"; then die "Installation failed. Check version or network connection." fi diff --git a/.taskfiles/scripts/install_bicep.sh b/.taskfiles/scripts/install_bicep.sh index fcd9a58..6ef084b 100755 --- a/.taskfiles/scripts/install_bicep.sh +++ b/.taskfiles/scripts/install_bicep.sh @@ -112,7 +112,7 @@ else fi releaseJson="${tempDir}/release.json" -if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then +if ! curl "${ghAuthHeader[@]}" -fsSL "${apiUrl}" -o "${releaseJson}"; then die "Failed to fetch release information. Check version or network connection." fi @@ -125,7 +125,7 @@ downloadUrl="$(jq -r --arg arch "${arch}" \ log "Downloading ${downloadUrl}" binaryPath="${tempDir}/${TOOL_NAME}" -curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${binaryPath}" || die "Download failed" +curl "${ghAuthHeader[@]}" -fsSL "${downloadUrl}" -o "${binaryPath}" || die "Download failed" # Install binary log "Installing binary" diff --git a/.taskfiles/scripts/install_ghalint.sh b/.taskfiles/scripts/install_ghalint.sh index bb9d7ad..ac60d38 100755 --- a/.taskfiles/scripts/install_ghalint.sh +++ b/.taskfiles/scripts/install_ghalint.sh @@ -114,7 +114,7 @@ else fi releaseJson="${tempDir}/release.json" -if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then +if ! curl "${ghAuthHeader[@]}" -fsSL "${apiUrl}" -o "${releaseJson}"; then die "Failed to fetch release information. Check version or network connection." fi @@ -127,7 +127,7 @@ downloadUrl="$(jq -r --arg arch "${arch}" \ log "Downloading ${downloadUrl}" archivePath="${tempDir}/${TOOL_NAME}.tar.gz" -curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" +curl "${ghAuthHeader[@]}" -fsSL "${downloadUrl}" -o "${archivePath}" || die "Download failed" # Extract binary log "Extracting archive" diff --git a/.taskfiles/scripts/install_golangci-lint.sh b/.taskfiles/scripts/install_golangci-lint.sh index 7fe6209..6f658a5 100755 --- a/.taskfiles/scripts/install_golangci-lint.sh +++ b/.taskfiles/scripts/install_golangci-lint.sh @@ -86,7 +86,7 @@ fi # Execute remote installation script log "Fetching and executing official installation script" -if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | sh -s -- -b "${INSTALL_DIR}" "${VERSION}"; then +if ! curl "${ghAuthHeader[@]}" -fsSL "${INSTALL_SCRIPT_URL}" | sh -s -- -b "${INSTALL_DIR}" "${VERSION}"; then die "Installation failed. Check version or network connection." fi diff --git a/.taskfiles/scripts/install_goreleaser.sh b/.taskfiles/scripts/install_goreleaser.sh index e0b4699..a84efcc 100755 --- a/.taskfiles/scripts/install_goreleaser.sh +++ b/.taskfiles/scripts/install_goreleaser.sh @@ -107,6 +107,7 @@ log "Installing ${TOOL_NAME} (${VERSION}) for ${os}/${arch} to ${INSTALL_DIR}" # GitHub API authentication ghAuthHeader=(-H "Accept: application/vnd.github+json") +ghAuthHeader+=(-H "User-Agent: ${TOOL_NAME}-installer") if [[ -n "${GITHUB_TOKEN:-}" ]]; then ghAuthHeader+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") fi @@ -123,7 +124,7 @@ else fi releaseJson="${tempDir}/release.json" -if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then +if ! curl "${ghAuthHeader[@]}" -fsSL "${apiUrl}" -o "${releaseJson}"; then die "Failed to fetch release information. Check version or network connection." fi @@ -136,7 +137,7 @@ downloadUrl="$(jq -r --arg os "${os}" --arg arch "${arch}" \ log "Downloading ${downloadUrl}" archivePath="${tempDir}/${TOOL_NAME}.tar.gz" -curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" +curl "${ghAuthHeader[@]}" -fsSL "${downloadUrl}" -o "${archivePath}" || die "Download failed" # Extract binary log "Extracting archive" diff --git a/.taskfiles/scripts/install_opentofu.sh b/.taskfiles/scripts/install_opentofu.sh index 70f4a4e..9f835e2 100755 --- a/.taskfiles/scripts/install_opentofu.sh +++ b/.taskfiles/scripts/install_opentofu.sh @@ -77,7 +77,7 @@ log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" # Execute remote installation script log "Fetching and executing official installation script" -if ! curl -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | sh -s -- --install-method standalone --opentofu-version "${VERSION}" --install-path "${INSTALL_DIR}"; then +if ! curl -fsSL "${INSTALL_SCRIPT_URL}" | sh -s -- --install-method standalone --opentofu-version "${VERSION}" --install-path "${INSTALL_DIR}"; then die "Installation failed. Check version or network connection." fi diff --git a/.taskfiles/scripts/install_pinact.sh b/.taskfiles/scripts/install_pinact.sh index dbfe0b5..c9b8656 100755 --- a/.taskfiles/scripts/install_pinact.sh +++ b/.taskfiles/scripts/install_pinact.sh @@ -110,7 +110,7 @@ else fi releaseJson="${tempDir}/release.json" -if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then +if ! curl "${ghAuthHeader[@]}" -fsSL "${apiUrl}" -o "${releaseJson}"; then die "Failed to fetch release information. Check version or network connection." fi @@ -123,7 +123,7 @@ downloadUrl="$(jq -r --arg arch "${arch}" \ log "Downloading ${downloadUrl}" archivePath="${tempDir}/${TOOL_NAME}.tar.gz" -curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" +curl "${ghAuthHeader[@]}" -fsSL "${downloadUrl}" -o "${archivePath}" || die "Download failed" # Extract binary log "Extracting archive" diff --git a/.taskfiles/scripts/install_shellcheck.sh b/.taskfiles/scripts/install_shellcheck.sh index 5ea59d9..77fc0f1 100755 --- a/.taskfiles/scripts/install_shellcheck.sh +++ b/.taskfiles/scripts/install_shellcheck.sh @@ -108,7 +108,7 @@ else fi releaseJson="${tempDir}/release.json" -if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then +if ! curl "${ghAuthHeader[@]}" -fsSL "${apiUrl}" -o "${releaseJson}"; then die "Failed to fetch release information. Check version or network connection." fi @@ -121,7 +121,7 @@ downloadUrl="$(jq -r --arg arch "${arch}" \ log "Downloading ${downloadUrl}" archivePath="${tempDir}/${TOOL_NAME}.tar.xz" -curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${archivePath}" || die "Download failed" +curl "${ghAuthHeader[@]}" -fsSL "${downloadUrl}" -o "${archivePath}" || die "Download failed" # Extract binary log "Extracting archive" diff --git a/.taskfiles/scripts/install_shfmt.sh b/.taskfiles/scripts/install_shfmt.sh index 3de1619..2d57f07 100755 --- a/.taskfiles/scripts/install_shfmt.sh +++ b/.taskfiles/scripts/install_shfmt.sh @@ -118,7 +118,7 @@ else fi releaseJson="${tempDir}/release.json" -if ! curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${apiUrl}" -o "${releaseJson}"; then +if ! curl "${ghAuthHeader[@]}" -fsSL "${apiUrl}" -o "${releaseJson}"; then die "Failed to fetch release information. Check version or network connection." fi @@ -131,7 +131,7 @@ downloadUrl="$(jq -r --arg os "${os}" --arg arch "${arch}" \ log "Downloading ${downloadUrl}" binaryPath="${tempDir}/${TOOL_NAME}" -curl "${ghAuthHeader[@]}" -fsSL --proto '=https' --tlsv1.3 "${downloadUrl}" -o "${binaryPath}" || die "Download failed" +curl "${ghAuthHeader[@]}" -fsSL "${downloadUrl}" -o "${binaryPath}" || die "Download failed" # Install binary log "Installing binary" diff --git a/.taskfiles/scripts/setup_fnm.sh b/.taskfiles/scripts/setup_fnm.sh index 620e8ba..8293c6f 100755 --- a/.taskfiles/scripts/setup_fnm.sh +++ b/.taskfiles/scripts/setup_fnm.sh @@ -73,7 +73,7 @@ log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" # Execute remote installation script log "Fetching and executing official installation script" -if ! curl -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | bash -s -- --skip-shell --install-dir "${INSTALL_DIR}" --release "${VERSION}"; then +if ! curl -fsSL "${INSTALL_SCRIPT_URL}" | bash -s -- --skip-shell --install-dir "${INSTALL_DIR}" --release "${VERSION}"; then die "Installation failed. Check version or network connection." fi diff --git a/.taskfiles/scripts/setup_golang.sh b/.taskfiles/scripts/setup_golang.sh index f847d4e..7c08cc9 100755 --- a/.taskfiles/scripts/setup_golang.sh +++ b/.taskfiles/scripts/setup_golang.sh @@ -72,7 +72,7 @@ if [[ "${VERSION}" != "latest" ]]; then goVersion="go${VERSION}" else log "Fetching latest Go version" - goVersion=$(curl -fsSL --proto '=https' --tlsv1.3 "https://go.dev/dl/?mode=json" | jq -r '.[0].version') + goVersion=$(curl -fsSL "https://go.dev/dl/?mode=json" | jq -r '.[0].version') fi # Detect architecture diff --git a/.taskfiles/scripts/setup_uv.sh b/.taskfiles/scripts/setup_uv.sh index 6e34463..7d42ecb 100755 --- a/.taskfiles/scripts/setup_uv.sh +++ b/.taskfiles/scripts/setup_uv.sh @@ -78,7 +78,7 @@ log "Installing ${TOOL_NAME} (${VERSION}) to ${INSTALL_DIR}" # Execute remote installation script log "Fetching and executing official installation script" -if ! curl -fsSL --proto '=https' --tlsv1.3 "${INSTALL_SCRIPT_URL}" | env UV_INSTALL_DIR="${INSTALL_DIR}" UV_VERSION="${VERSION}" UV_GITHUB_TOKEN="${GITHUB_TOKEN:-}" sh; then +if ! curl -fsSL "${INSTALL_SCRIPT_URL}" | env UV_INSTALL_DIR="${INSTALL_DIR}" UV_VERSION="${VERSION}" UV_GITHUB_TOKEN="${GITHUB_TOKEN:-}" sh; then die "Installation failed. Check version or network connection." fi From 51514f89b5275ce4c7a525a19783a3cee56b4cf3 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:23:01 -0800 Subject: [PATCH 28/34] ci: add delays for Azure RBAC propagation after role assignments Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- pkg/usecase/mpfService.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/usecase/mpfService.go b/pkg/usecase/mpfService.go index a373535..33a2c29 100644 --- a/pkg/usecase/mpfService.go +++ b/pkg/usecase/mpfService.go @@ -25,6 +25,7 @@ package usecase import ( "context" "strings" + "time" "github.com/Azure/mpf/pkg/domain" log "github.com/sirupsen/logrus" @@ -128,6 +129,11 @@ func (s *MPFService) GetMinimumPermissionsRequired() (domain.MPFResult, error) { } log.Infoln("New Custom Role assigned to service principal successfully") + // Wait for Azure RBAC propagation after initial role assignment + // Azure role assignments can take a few seconds to propagate across all authorization endpoints + log.Infoln("Waiting for Azure RBAC propagation after initial role assignment...") + time.Sleep(5 * time.Second) + // Add initial permissions to requiredPermissions map log.Infoln("Adding initial permissions to requiredPermissions map") s.requiredPermissions[s.mpfConfig.SubscriptionID] = append(s.requiredPermissions[s.mpfConfig.SubscriptionID], s.permissionsToAddToResult...) @@ -206,6 +212,11 @@ func (s *MPFService) GetMinimumPermissionsRequired() (domain.MPFResult, error) { } log.Infoln("Permission/scope added to role successfully") + // Wait for Azure RBAC propagation before retrying deployment + // Azure role definition updates can take a few seconds to propagate across all authorization endpoints + log.Infoln("Waiting for Azure RBAC propagation...") + time.Sleep(5 * time.Second) + iterCount++ if iterCount == maxIterations { log.Warnln("max iterations for fetching authorization errors reached, exiting...") From d2e6d946fbb2b530c80f2a59578abe69f2f6ba5d Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:14:55 -0800 Subject: [PATCH 29/34] ci: remove deprecated PowerShell script for creating E2E service principals Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- scripts/cleanup-tmp-roles.ps1 | 59 +++++++++++++++++ scripts/cleanup-tmp-roles.sh | 64 +++++++++++++++++++ ....ps1 => create-e2e-service-principals.ps1} | 0 3 files changed, 123 insertions(+) create mode 100644 scripts/cleanup-tmp-roles.ps1 create mode 100644 scripts/cleanup-tmp-roles.sh rename scripts/{New-E2EServicePrincipals.ps1 => create-e2e-service-principals.ps1} (100%) diff --git a/scripts/cleanup-tmp-roles.ps1 b/scripts/cleanup-tmp-roles.ps1 new file mode 100644 index 0000000..35a4dd1 --- /dev/null +++ b/scripts/cleanup-tmp-roles.ps1 @@ -0,0 +1,59 @@ +Param( + [string]$SubscriptionId = $env:SUBSCRIPTION_ID, + [string]$RolePrefix = $(if ($env:TMP_ROLE_PREFIX) { $env:TMP_ROLE_PREFIX } else { 'tmp-rol-' }) +) + +$ErrorActionPreference = 'Stop' + +if (-not (Get-Command az -ErrorAction SilentlyContinue)) { + Write-Error 'Azure CLI (az) is required' + exit 1 +} + +if ([string]::IsNullOrWhiteSpace($SubscriptionId)) { + try { + $SubscriptionId = az account show --query id -o tsv 2>$null + } + catch { + $SubscriptionId = '' + } +} + +if ([string]::IsNullOrWhiteSpace($SubscriptionId)) { + Write-Error 'Subscription not provided and no default found. Set SUBSCRIPTION_ID or login.' + exit 1 +} + +az account set --subscription $SubscriptionId | Out-Null + +$query = "[?starts_with(roleName, '$RolePrefix')].{Id:id,Name:name,RoleName:roleName}" +$defs = az role definition list --custom-role-only true --subscription $SubscriptionId --query $query -o json | ConvertFrom-Json + +if (-not $defs -or $defs.Count -eq 0) { + Write-Host 'No tmp-rol* custom roles found' + exit 0 +} + +foreach ($def in $defs) { + $roleId = $def.Id + $roleName = $def.Name + $roleDisplay = $def.RoleName + + Write-Host "Processing role $roleDisplay ($roleName)" + + $assignments = az role assignment list --all --subscription $SubscriptionId --role $roleId --query '[].id' -o tsv + if ([string]::IsNullOrWhiteSpace($assignments)) { + Write-Host "No assignments for $roleDisplay" + } + else { + $assignments -split "`n" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { + Write-Host "Deleting assignment $_" + az role assignment delete --ids $_ + } + } + + Write-Host "Deleting role definition $roleName" + az role definition delete --name $roleName --subscription $SubscriptionId + Write-Host "Done $roleDisplay" + Write-Host +} diff --git a/scripts/cleanup-tmp-roles.sh b/scripts/cleanup-tmp-roles.sh new file mode 100644 index 0000000..dbd2f94 --- /dev/null +++ b/scripts/cleanup-tmp-roles.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Cleans up custom roles and role assignments created by MPF (names prefixed tmp-rol-). +# Usage: SUBSCRIPTION_ID= ./cleanup-tmp-roles.sh +# Optional: TMP_ROLE_PREFIX (default: tmp-rol-) + +if ! command -v az >/dev/null 2>&1; then + echo "Azure CLI (az) is required" >&2 + exit 1 +fi +if ! command -v jq >/dev/null 2>&1; then + echo "jq is required" >&2 + exit 1 +fi + +SUBSCRIPTION_ID="${SUBSCRIPTION_ID:-${1:-}}" +ROLE_PREFIX="${TMP_ROLE_PREFIX:-tmp-rol-}" + +if [[ -z "${SUBSCRIPTION_ID}" ]]; then + SUBSCRIPTION_ID=$(az account show --query id -o tsv 2>/dev/null || true) +fi + +if [[ -z "${SUBSCRIPTION_ID}" ]]; then + echo "Subscription not provided and no default found. Set SUBSCRIPTION_ID or login." >&2 + exit 1 +fi + +az account set --subscription "${SUBSCRIPTION_ID}" + +query="[?starts_with(roleName, '$ROLE_PREFIX')].[id,name,roleName]" +defs_json=$(az role definition list --custom-role-only true --subscription "${SUBSCRIPTION_ID}" \ + --query "${query}" -o json) + +if [[ "$(echo "${defs_json}" | jq 'length')" -eq 0 ]]; then + echo "No tmp-rol* custom roles found" + exit 0 +fi + +echo "${defs_json}" | jq -c '.[]' | while read -r def; do + role_id=$(echo "${def}" | jq -r '.[0]') + role_name=$(echo "${def}" | jq -r '.[1]') + role_display=$(echo "${def}" | jq -r '.[2]') + + echo "Processing role ${role_display} (${role_name})" + + assignments=$(az role assignment list --all --subscription "${SUBSCRIPTION_ID}" --role "${role_id}" --query '[].id' -o tsv) + if [[ -n "${assignments}" ]]; then + echo "${assignments}" | while read -r assign_id; do + [[ -z "${assign_id}" ]] && continue + echo "Deleting assignment ${assign_id}" + az role assignment delete --ids "${assign_id}" + done + else + echo "No assignments for ${role_display}" + fi + + echo "Deleting role definition ${role_name}" + az role definition delete --name "${role_name}" --subscription "${SUBSCRIPTION_ID}" + echo "Done ${role_display}" + echo +done + diff --git a/scripts/New-E2EServicePrincipals.ps1 b/scripts/create-e2e-service-principals.ps1 similarity index 100% rename from scripts/New-E2EServicePrincipals.ps1 rename to scripts/create-e2e-service-principals.ps1 From e66f217e0684bef6a6013c4528147c33b123c386 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:58:48 -0800 Subject: [PATCH 30/34] ci: remove trailing whitespace in cleanup-tmp-roles.sh Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- scripts/cleanup-tmp-roles.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/cleanup-tmp-roles.sh b/scripts/cleanup-tmp-roles.sh index dbd2f94..8f06421 100644 --- a/scripts/cleanup-tmp-roles.sh +++ b/scripts/cleanup-tmp-roles.sh @@ -61,4 +61,3 @@ echo "${defs_json}" | jq -c '.[]' | while read -r def; do echo "Done ${role_display}" echo done - From d8048fe327eaa0d21d2ab088917dbc062106a2a2 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:07:55 -0800 Subject: [PATCH 31/34] ci: ensure response body is closed and handle non-success status codes in DeleteCustomRole Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .../defaultSPRoleAssignmentManager.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/infrastructure/spRoleAssignmentManager/defaultSPRoleAssignmentManager.go b/pkg/infrastructure/spRoleAssignmentManager/defaultSPRoleAssignmentManager.go index 0bd3777..3677202 100644 --- a/pkg/infrastructure/spRoleAssignmentManager/defaultSPRoleAssignmentManager.go +++ b/pkg/infrastructure/spRoleAssignmentManager/defaultSPRoleAssignmentManager.go @@ -357,6 +357,7 @@ func (r *SPRoleAssignmentManager) DeleteCustomRole(subscription string, role dom if err != nil { return err } + defer resp.Body.Close() // read response body body, err := io.ReadAll(resp.Body) @@ -364,6 +365,10 @@ func (r *SPRoleAssignmentManager) DeleteCustomRole(subscription string, role dom log.Warnf("Could not delete role definition: %s\n", err) } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("failed to delete role definition. Status: %s, Body: %s", resp.Status, string(body)) + } + log.Debugln(string(body)) log.Infoln("Role definition deleted successfully") From 90c9daafcb8713cdaa7849c93e8d719e73bf1a31 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:13:54 -0800 Subject: [PATCH 32/34] ci: ignore error check for deferred response body closure in DeleteCustomRole Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .../spRoleAssignmentManager/defaultSPRoleAssignmentManager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/infrastructure/spRoleAssignmentManager/defaultSPRoleAssignmentManager.go b/pkg/infrastructure/spRoleAssignmentManager/defaultSPRoleAssignmentManager.go index 3677202..b2c58d2 100644 --- a/pkg/infrastructure/spRoleAssignmentManager/defaultSPRoleAssignmentManager.go +++ b/pkg/infrastructure/spRoleAssignmentManager/defaultSPRoleAssignmentManager.go @@ -357,7 +357,7 @@ func (r *SPRoleAssignmentManager) DeleteCustomRole(subscription string, role dom if err != nil { return err } - defer resp.Body.Close() + defer resp.Body.Close() //nolint:errcheck // read response body body, err := io.ReadAll(resp.Body) From b4938a44747756b3aad919980cff373d563de415 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:55:44 -0800 Subject: [PATCH 33/34] ci: add clean task for removing Terraform files and directories Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- .taskfiles/terraform.Taskfile.yml | 12 ++++++++++++ Taskfile.yml | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.taskfiles/terraform.Taskfile.yml b/.taskfiles/terraform.Taskfile.yml index b5e2ffe..699df31 100644 --- a/.taskfiles/terraform.Taskfile.yml +++ b/.taskfiles/terraform.Taskfile.yml @@ -79,3 +79,15 @@ tasks: desc: Lint Terraform files cmds: - task: run:terraform-fmt + + # * Clean + clean: + desc: Clean up Terraform files + cmds: + - cmd: | + find ./ -type d \( -name ".external_modules" -o -name ".terraform" \) -exec rm -rf {} + 2>/dev/null; find ./ -type f \( -name "*.terraform.lock.*" -o -name "*.tfstate*" -o -name "terraform.log" \) -delete 2>/dev/null; true + platforms: [linux, darwin] + - cmd: | + {{.PWSH}} 'Get-ChildItem -Path ./ -Include ".external_modules",".terraform" -Directory -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force; Get-ChildItem -Path ./ -Include "*.terraform.lock.*","*.tfstate*","terraform.log" -File -Recurse -ErrorAction SilentlyContinue | Remove-Item -Force' + platforms: [windows] + dir: "{{.ROOT_DIR}}" diff --git a/Taskfile.yml b/Taskfile.yml index ebaea9f..a256393 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -394,18 +394,6 @@ tasks: - cmd: | gh pr create --title "feat(release): {{.SEMVER}}" --body-file ".changes/{{.SEMVER}}.md" --label "skip-changelog" - - tf:clean: - desc: Clean up Terraform files - cmds: - - cmd: | - find ./ -type d \( -name ".external_modules" -o -name ".terraform" \) -exec rm -rf {} + 2>/dev/null; find ./ -type f \( -name "*.terraform.lock.*" -o -name "*.tfstate*" -o -name "terraform.log" \) -delete 2>/dev/null; true - platforms: [linux, darwin] - - cmd: | - {{.PWSH}} 'Get-ChildItem -Path ./ -Include ".external_modules",".terraform" -Directory -Recurse -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force; Get-ChildItem -Path ./ -Include "*.terraform.lock.*","*.tfstate*","terraform.log" -File -Recurse -ErrorAction SilentlyContinue | Remove-Item -Force' - platforms: [windows] - dir: "{{.ROOT_DIR}}" - diff: desc: Check for differences silent: true From b265283a261d2fffa8c8820888692175a32f1e13 Mon Sep 17 00:00:00 2001 From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Date: Thu, 18 Dec 2025 17:33:44 -0800 Subject: [PATCH 34/34] ci: add logging for Azure RBAC propagation and required permissions in tests Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> --- e2eTests/e2eTerraform_test.go | 13 ++++++++++++- pkg/usecase/mpfService.go | 5 +++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/e2eTests/e2eTerraform_test.go b/e2eTests/e2eTerraform_test.go index 1984a93..ea1fc12 100644 --- a/e2eTests/e2eTerraform_test.go +++ b/e2eTests/e2eTerraform_test.go @@ -170,8 +170,19 @@ func TestTerraformModuleTest(t *testing.T) { t.Error(err) } + // Microsoft.OperationalInsights/workspaces/delete + // Microsoft.OperationalInsights/workspaces/read + // Microsoft.OperationalInsights/workspaces/write + // Microsoft.Resources/deployments/read + // Microsoft.Resources/deployments/write + // Microsoft.Resources/subscriptions/resourcegroups/delete + // Microsoft.Resources/subscriptions/resourcegroups/read + // Microsoft.Resources/subscriptions/resourcegroups/write + assert.NotEmpty(t, mpfResult.RequiredPermissions) - assert.Equal(t, 8, len(mpfResult.RequiredPermissions[mpfConfig.SubscriptionID])) + perms := mpfResult.RequiredPermissions[mpfConfig.SubscriptionID] + log.Infof("Found %d permissions: %v", len(perms), perms) + assert.Equal(t, 8, len(perms)) } // diff --git a/pkg/usecase/mpfService.go b/pkg/usecase/mpfService.go index 33a2c29..c5229b6 100644 --- a/pkg/usecase/mpfService.go +++ b/pkg/usecase/mpfService.go @@ -105,6 +105,11 @@ func (s *MPFService) GetMinimumPermissionsRequired() (domain.MPFResult, error) { } log.Info("Deleted all existing role assignments for service principal \n") + // Wait for Azure RBAC propagation after deleting role assignments + // This ensures that any previous permissions are fully revoked before starting the new test + log.Infoln("Waiting for Azure RBAC propagation after deleting role assignments...") + time.Sleep(15 * time.Second) + // Initialize new custom role log.Infoln("Initializing Custom Role") // err = mpf.CreateUpdateCustomRole([]string{})