diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c6ee553 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +--- +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: / + schedule: + interval: daily + time: "11:00" + commit-message: + prefix: "gh-actions:" diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index f83e11e..c7223f0 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -9,13 +9,13 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Get changed Dockerfile id: changed-files-specific - uses: tj-actions/changed-files@2c85495a7bb72f2734cb5181e29b2ee5e08e61f7 # v13.1 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | Dockerfile @@ -52,7 +52,7 @@ jobs: - name: Dive - check image for waste files if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: MaxymVlasov/dive-action@0035999cae50d4ef657ac94be84f01812aa192a5 # v0.1.0 + uses: MaxymVlasov/dive-action@fafb796951b322cc4926b8a5eafda89ab9de8edf # v1.5.1 with: image: ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} config-file: ${{ github.workspace }}/.github/.dive-ci.yaml diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 2d0c497..a79e218 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5.0.1 - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -22,7 +22,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Login to ghcr.io - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -66,7 +66,7 @@ jobs: "github_token=${{ secrets.GITHUB_TOKEN }}" - name: Login to DockerHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: registry.hub.docker.com username: ${{ secrets.DOCKERHUB_USER }} @@ -98,4 +98,3 @@ jobs: tags: | registry.hub.docker.com/tofuutils/pre-commit-opentofu:nightly provenance: false - diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 7a8b642..3d86d51 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -14,7 +14,7 @@ jobs: steps: # Please look up the latest version from # https://github.com/amannn/action-semantic-pull-request/releases - - uses: amannn/action-semantic-pull-request@e9fabac35e210fea40ca5b14c0da95a099eff26f # v5.4.0 + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 1085384..b0566cf 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -6,7 +6,7 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - run: | git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* @@ -27,20 +27,24 @@ jobs: sudo apt update && sudo apt install shellcheck - name: Install hadolint + env: + GH_TOKEN: ${{ github.token }} run: | - curl -L "$(curl -s https://api.github.com/repos/hadolint/hadolint/releases/latest | grep -o -E -m 1 "https://.+?/hadolint-Linux-x86_64")" > hadolint \ - && chmod +x hadolint && sudo mv hadolint /usr/bin/ + gh release download v2.14.0 --repo hadolint/hadolint --pattern "hadolint-linux-x86_64" + mv hadolint-linux-x86_64 hadolint + chmod +x hadolint + sudo mv hadolint /usr/bin/ # Need to success pre-commit fix push - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} # Skip tofu_tflint which interferes to commit pre-commit auto-fixes - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: '3.9' - name: Execute pre-commit - uses: pre-commit/action@9b88afc9cd57fd75b655d5c71bd38146d07135fe # v2.0.3 + uses: pre-commit/action@576ff52938d158a24ac7e009dfa94b1455e7df99 env: SKIP: no-commit-to-branch,hadolint with: @@ -49,7 +53,7 @@ jobs: # Run only skipped checks - name: Execute pre-commit check that have no auto-fixes if: always() - uses: pre-commit/action@9b88afc9cd57fd75b655d5c71bd38146d07135fe # v2.0.3 + uses: pre-commit/action@576ff52938d158a24ac7e009dfa94b1455e7df99 env: SKIP: check-added-large-files,check-merge-conflict,check-vcs-permalinks,forbid-new-submodules,no-commit-to-branch,end-of-file-fixer,trailing-whitespace,check-yaml,check-merge-conflict,check-executables-have-shebangs,check-case-conflict,mixed-line-ending,detect-aws-credentials,detect-private-key,shfmt,shellcheck with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4aa0df1..35b8f62 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,13 +18,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false fetch-depth: 0 - name: Release - uses: cycjimmy/semantic-release-action@61680d0e9b02ff86f5648ade99e01be17f0260a4 # v4.0.0 + uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6.0.0 with: semantic_version: 18.0.0 extra_plugins: | diff --git a/.github/workflows/stale-actions.yaml b/.github/workflows/stale-actions.yaml index 32f08e7..d797c08 100644 --- a/.github/workflows/stale-actions.yaml +++ b/.github/workflows/stale-actions.yaml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 + - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} # Staling issues and PR's diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 17fc5a6..e801df2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v6.0.0 hooks: # Git style - id: check-added-large-files diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 0dbc918..d60a3e5 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -4,7 +4,7 @@ entry: hooks/infracost_breakdown.sh language: script require_serial: true - files: \.(tf(vars)?|hcl)$ + files: \.(tf|tofu|tfvars|hcl)$ exclude: \.terraform\/.*$ - id: tofu_fmt @@ -12,25 +12,29 @@ description: Rewrites all OpenTofu configuration files to a canonical format. entry: hooks/tofu_fmt.sh language: script - files: (\.tf|\.tfvars)$ + files: \.(tf|tofu|tfvars|(tftest|tofutest|tfmock|tfquery)\.hcl)$ exclude: \.terraform\/.*$ - id: tofu_docs name: OpenTofu docs - description: Inserts input and output documentation into README.md (using terraform-docs). + description: + Inserts input and output documentation into README.md (using + terraform-docs). require_serial: true entry: hooks/tofu_docs.sh language: script - files: (\.tf|\.terraform\.lock\.hcl)$ + files: \.(tf|tofu|terraform\.lock\.hcl)$ exclude: \.terraform\/.*$ - id: tofu_docs_without_aggregate_type_defaults name: OpenTofu docs (without aggregate type defaults) - description: Inserts input and output documentation into README.md (using terraform-docs). Identical to terraform_docs. + description: + Inserts input and output documentation into README.md (using + terraform-docs). Identical to terraform_docs. require_serial: true entry: hooks/tofu_docs.sh language: script - files: (\.tf)$ + files: \.(tf|tofu)$ exclude: \.terraform\/.*$ - id: tofu_docs_replace @@ -39,7 +43,7 @@ require_serial: true entry: hooks/tofu_docs_replace.py language: python - files: (\.tf)$ + files: \.(tf|tofu)$ exclude: \.terraform\/.*$ - id: tofu_validate @@ -48,7 +52,7 @@ require_serial: true entry: hooks/tofu_validate.sh language: script - files: (\.tf|\.tfvars)$ + files: \.(tf|tofu|tfvars|terraform\.lock\.hcl)$ exclude: \.terraform\/.*$ - id: tofu_providers_lock @@ -66,12 +70,13 @@ require_serial: true entry: hooks/tofu_tflint.sh language: script - files: (\.tf|\.tfvars)$ + files: \.(tf|tofu|tfvars)$ exclude: \.terraform\/.*$ - id: terragrunt_fmt name: Terragrunt fmt - description: Rewrites all Terragrunt configuration files to a canonical format. + description: + Rewrites all Terragrunt configuration files to a canonical format. entry: hooks/terragrunt_fmt.sh language: script files: (\.hcl)$ @@ -85,20 +90,30 @@ files: (\.hcl)$ exclude: \.terraform\/.*$ +- id: terragrunt_validate_inputs + name: Terragrunt validate inputs + description: Validates Terragrunt unused and undefined inputs. + entry: hooks/terragrunt_validate_inputs.sh + language: script + files: (\.hcl)$ + exclude: \.terraform\/.*$ + - id: tofu_tfsec name: OpenTofu validate with tfsec (deprecated, use "tofu_trivy") - description: Static analysis of OpenTofu templates to spot potential security issues. + description: + Static analysis of OpenTofu templates to spot potential security issues. require_serial: true entry: hooks/tofu_tfsec.sh - files: \.tf(vars)?$ + files: \.(tf|tofu|tfvars)$ language: script - id: tofu_trivy name: OpenTofu validate with trivy - description: Static analysis of OpenTofu templates to spot potential security issues. + description: + Static analysis of OpenTofu templates to spot potential security issues. require_serial: true entry: hooks/tofu_trivy.sh - files: \.tf(vars)?$ + files: \.(tf|tofu|tfvars)$ language: script - id: checkov @@ -108,7 +123,7 @@ language: python pass_filenames: false always_run: false - files: \.tf$ + files: \.(tf|tofu)$ exclude: \.terraform\/.*$ require_serial: true @@ -118,7 +133,7 @@ entry: hooks/tofu_checkov.sh language: script always_run: false - files: \.tf$ + files: \.(tf|tofu)$ exclude: \.terraform\/.*$ require_serial: true @@ -130,7 +145,7 @@ pass_filenames: false always_run: false require_serial: true - files: \.tf$ + files: \.(tf|tofu)$ exclude: \.terraform\/.*$ - id: terrascan @@ -138,7 +153,7 @@ description: Runs terrascan on OpenTofu templates. language: script entry: hooks/terrascan.sh - files: \.tf$ + files: \.(tf|tofu)$ exclude: \.terraform\/.*$ require_serial: true @@ -149,5 +164,5 @@ entry: hooks/tfupdate.sh args: - --args=terraform - files: \.tf$ + files: \.(tf|tofu)$ require_serial: true diff --git a/CHANGELOG.md b/CHANGELOG.md index e02f1ca..2112ef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,54 @@ All notable changes to this project will be documented in this file. +# [2.4.0](https://github.com/tofuutils/pre-commit-opentofu/compare/v2.3.0...v2.4.0) (2026-05-25) + + +### Bug Fixes + +* expand file extension patterns for tofu hooks ([451aaa5](https://github.com/tofuutils/pre-commit-opentofu/commit/451aaa59b552eb3913629c835af8b6b568aab120)) + + +### Features + +* support .tofu file extension and update hooks/tools ([9624cc8](https://github.com/tofuutils/pre-commit-opentofu/commit/9624cc8f24177378449203194b4f0ee71bc6c1a0)) + +# [2.3.0](https://github.com/tofuutils/pre-commit-opentofu/compare/v2.2.2...v2.3.0) (2026-04-21) + + +### Features + +* add terragrunt_validate_inputs hook ([0ca9eca](https://github.com/tofuutils/pre-commit-opentofu/commit/0ca9eca3823420f31d09c62bf1672bea03ca3e07)) + +## [2.2.2](https://github.com/tofuutils/pre-commit-opentofu/compare/v2.2.1...v2.2.2) (2025-10-22) + + +### Bug Fixes + +* replace deprecated hclfmt with hcl format command ([f1a589b](https://github.com/tofuutils/pre-commit-opentofu/commit/f1a589bd124b277cc02fcbf04ee05017fb8822c0)) + +## [2.2.1](https://github.com/tofuutils/pre-commit-opentofu/compare/v2.2.0...v2.2.1) (2025-06-04) + + +### Bug Fixes + +* make infracost_breakdown.sh compatible with bash 3.2 (macOS) ([df886fa](https://github.com/tofuutils/pre-commit-opentofu/commit/df886fa772e7d1eedf5603327c0cf02968e7d779)) +* Update pre-commit/action version ([#30](https://github.com/tofuutils/pre-commit-opentofu/issues/30)) ([44c7b5d](https://github.com/tofuutils/pre-commit-opentofu/commit/44c7b5dec9362d2fe7ed5e8786f4d95956791d3d)) + +# [2.2.0](https://github.com/tofuutils/pre-commit-opentofu/compare/v2.1.0...v2.2.0) (2025-03-29) + + +### Features + +* make release ([e625db1](https://github.com/tofuutils/pre-commit-opentofu/commit/e625db13ec285e132f43cdf6e5aa3f3272e45451)) + +# [2.1.0](https://github.com/tofuutils/pre-commit-opentofu/compare/v2.0.0...v2.1.0) (2024-10-16) + + +### Features + +* support .tofu files ([#6](https://github.com/tofuutils/pre-commit-opentofu/issues/6)) ([e059c58](https://github.com/tofuutils/pre-commit-opentofu/commit/e059c5859bceddf1ca018f55851f6940ad51f1c2)) + # [2.0.0](https://github.com/tofuutils/pre-commit-opentofu/compare/v1.0.4...v2.0.0) (2024-09-25) diff --git a/Dockerfile b/Dockerfile index 39255fc..6699cb2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,22 +10,22 @@ RUN apk add --no-cache \ curl=~8 && \ # Upgrade packages for be able get latest Checkov python3 -m pip install --no-cache-dir --upgrade \ - pip \ - setuptools + pip \ + setuptools ARG PRE_COMMIT_VERSION=${PRE_COMMIT_VERSION:-latest} -ARG TOFU_VERSION=${TOFU_VERSION:-1.6.1} +ARG TOFU_VERSION=${TOFU_VERSION:-1.9.0} # Install pre-commit RUN [ ${PRE_COMMIT_VERSION} = "latest" ] && pip3 install --no-cache-dir pre-commit \ || pip3 install --no-cache-dir pre-commit==${PRE_COMMIT_VERSION} RUN curl -LO https://github.com/opentofu/opentofu/releases/download/v${TOFU_VERSION}/tofu_${TOFU_VERSION}_${TARGETOS}_${TARGETARCH}.zip \ - && curl -LO https://github.com/opentofu/opentofu/releases/download/v${TOFU_VERSION}/tofu_${TOFU_VERSION}_SHA256SUMS \ - && [ $(sha256sum "tofu_${TOFU_VERSION}_${TARGETOS}_${TARGETARCH}.zip" | cut -f 1 -d ' ') = "$(grep "tofu_${TOFU_VERSION}_${TARGETOS}_${TARGETARCH}.zip" tofu_*_SHA256SUMS | cut -f 1 -d ' ')" ] \ - && unzip tofu_${TOFU_VERSION}_${TARGETOS}_${TARGETARCH}.zip -d /usr/bin/ \ - && rm "tofu_${TOFU_VERSION}_${TARGETOS}_${TARGETARCH}.zip" \ - && rm "tofu_${TOFU_VERSION}_SHA256SUMS" + && curl -LO https://github.com/opentofu/opentofu/releases/download/v${TOFU_VERSION}/tofu_${TOFU_VERSION}_SHA256SUMS \ + && [ "$(sha256sum "tofu_${TOFU_VERSION}_${TARGETOS}_${TARGETARCH}.zip" | cut -f 1 -d ' ')" = "$(grep "tofu_${TOFU_VERSION}_${TARGETOS}_${TARGETARCH}.zip" tofu_*_SHA256SUMS | cut -f 1 -d ' ')" ] \ + && unzip tofu_${TOFU_VERSION}_${TARGETOS}_${TARGETARCH}.zip -d /usr/bin/ \ + && rm "tofu_${TOFU_VERSION}_${TARGETOS}_${TARGETARCH}.zip" \ + && rm "tofu_${TOFU_VERSION}_SHA256SUMS" # # Install tools @@ -47,18 +47,18 @@ ARG HCLEDIT_VERSION=${HCLEDIT_VERSION:-false} # specified in step below ARG INSTALL_ALL=${INSTALL_ALL:-false} RUN if [ "$INSTALL_ALL" != "false" ]; then \ - echo "export CHECKOV_VERSION=latest" >> /.env && \ - echo "export INFRACOST_VERSION=latest" >> /.env && \ - echo "export TERRAFORM_DOCS_VERSION=latest" >> /.env && \ - echo "export TERRAGRUNT_VERSION=latest" >> /.env && \ - echo "export TERRASCAN_VERSION=latest" >> /.env && \ - echo "export TFLINT_VERSION=latest" >> /.env && \ - echo "export TFSEC_VERSION=latest" >> /.env && \ - echo "export TRIVY_VERSION=latest" >> /.env && \ - echo "export TFUPDATE_VERSION=latest" >> /.env && \ - echo "export HCLEDIT_VERSION=latest" >> /.env \ + echo "export CHECKOV_VERSION=latest" >> /.env && \ + echo "export INFRACOST_VERSION=latest" >> /.env && \ + echo "export TERRAFORM_DOCS_VERSION=latest" >> /.env && \ + echo "export TERRAGRUNT_VERSION=latest" >> /.env && \ + echo "export TERRASCAN_VERSION=latest" >> /.env && \ + echo "export TFLINT_VERSION=latest" >> /.env && \ + echo "export TFSEC_VERSION=latest" >> /.env && \ + echo "export TRIVY_VERSION=latest" >> /.env && \ + echo "export TFUPDATE_VERSION=latest" >> /.env && \ + echo "export HCLEDIT_VERSION=latest" >> /.env \ ; else \ - touch /.env \ + touch /.env \ ; fi @@ -66,10 +66,10 @@ RUN if [ "$INSTALL_ALL" != "false" ]; then \ RUN . /.env && \ if [ "$CHECKOV_VERSION" != "false" ]; then \ ( \ - apk add --no-cache gcc=~12 libffi-dev=~3 musl-dev=~1; \ - [ "$CHECKOV_VERSION" = "latest" ] && pip3 install --no-cache-dir checkov \ - || pip3 install --no-cache-dir checkov==${CHECKOV_VERSION}; \ - apk del gcc libffi-dev musl-dev \ + apk add --no-cache gcc=~12 libffi-dev=~3 musl-dev=~1; \ + [ "$CHECKOV_VERSION" = "latest" ] && pip3 install --no-cache-dir checkov \ + || pip3 install --no-cache-dir checkov==${CHECKOV_VERSION}; \ + apk del gcc libffi-dev musl-dev \ ) \ ; fi @@ -77,9 +77,9 @@ RUN . /.env && \ RUN . /.env && \ if [ "$INFRACOST_VERSION" != "false" ]; then \ ( \ - INFRACOST_RELEASES="https://api.github.com/repos/infracost/infracost/releases" && \ - [ "$INFRACOST_VERSION" = "latest" ] && curl -L "$(curl -s ${INFRACOST_RELEASES}/latest | grep -o -E -m 1 "https://.+?-${TARGETOS}-${TARGETARCH}.tar.gz")" > infracost.tgz \ - || curl -L "$(curl -s ${INFRACOST_RELEASES} | grep -o -E "https://.+?v${INFRACOST_VERSION}/infracost-${TARGETOS}-${TARGETARCH}.tar.gz")" > infracost.tgz \ + INFRACOST_RELEASES="https://api.github.com/repos/infracost/infracost/releases" && \ + [ "$INFRACOST_VERSION" = "latest" ] && curl -L "$(curl -s ${INFRACOST_RELEASES}/latest | grep -o -E -m 1 "https://.+?-${TARGETOS}-${TARGETARCH}.tar.gz")" > infracost.tgz \ + || curl -L "$(curl -s ${INFRACOST_RELEASES} | grep -o -E "https://.+?v${INFRACOST_VERSION}/infracost-${TARGETOS}-${TARGETARCH}.tar.gz")" > infracost.tgz \ ) && tar -xzf infracost.tgz && rm infracost.tgz && mv infracost-${TARGETOS}-${TARGETARCH} infracost \ ; fi @@ -87,9 +87,9 @@ RUN . /.env && \ RUN . /.env && \ if [ "$TERRAFORM_DOCS_VERSION" != "false" ]; then \ ( \ - TERRAFORM_DOCS_RELEASES="https://api.github.com/repos/terraform-docs/terraform-docs/releases" && \ - [ "$TERRAFORM_DOCS_VERSION" = "latest" ] && curl -L "$(curl -s ${TERRAFORM_DOCS_RELEASES}/latest | grep -o -E -m 1 "https://.+?-${TARGETOS}-${TARGETARCH}.tar.gz")" > terraform-docs.tgz \ - || curl -L "$(curl -s ${TERRAFORM_DOCS_RELEASES} | grep -o -E "https://.+?v${TERRAFORM_DOCS_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz")" > terraform-docs.tgz \ + TERRAFORM_DOCS_RELEASES="https://api.github.com/repos/terraform-docs/terraform-docs/releases" && \ + [ "$TERRAFORM_DOCS_VERSION" = "latest" ] && curl -L "$(curl -s ${TERRAFORM_DOCS_RELEASES}/latest | grep -o -E -m 1 "https://.+?-${TARGETOS}-${TARGETARCH}.tar.gz")" > terraform-docs.tgz \ + || curl -L "$(curl -s ${TERRAFORM_DOCS_RELEASES} | grep -o -E "https://.+?v${TERRAFORM_DOCS_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz")" > terraform-docs.tgz \ ) && tar -xzf terraform-docs.tgz terraform-docs && rm terraform-docs.tgz && chmod +x terraform-docs \ ; fi @@ -97,9 +97,9 @@ RUN . /.env && \ RUN . /.env \ && if [ "$TERRAGRUNT_VERSION" != "false" ]; then \ ( \ - TERRAGRUNT_RELEASES="https://api.github.com/repos/gruntwork-io/terragrunt/releases" && \ - [ "$TERRAGRUNT_VERSION" = "latest" ] && curl -L "$(curl -s ${TERRAGRUNT_RELEASES}/latest | grep -o -E -m 1 "https://.+?/terragrunt_${TARGETOS}_${TARGETARCH}")" > terragrunt \ - || curl -L "$(curl -s ${TERRAGRUNT_RELEASES} | grep -o -E -m 1 "https://.+?v${TERRAGRUNT_VERSION}/terragrunt_${TARGETOS}_${TARGETARCH}")" > terragrunt \ + TERRAGRUNT_RELEASES="https://api.github.com/repos/gruntwork-io/terragrunt/releases" && \ + [ "$TERRAGRUNT_VERSION" = "latest" ] && curl -L "$(curl -s ${TERRAGRUNT_RELEASES}/latest | grep -o -E -m 1 "https://.+?/terragrunt_${TARGETOS}_${TARGETARCH}")" > terragrunt \ + || curl -L "$(curl -s ${TERRAGRUNT_RELEASES} | grep -o -E -m 1 "https://.+?v${TERRAGRUNT_VERSION}/terragrunt_${TARGETOS}_${TARGETARCH}")" > terragrunt \ ) && chmod +x terragrunt \ ; fi @@ -111,9 +111,9 @@ RUN . /.env && \ # Convert the first letter to Uppercase OS="$(echo ${TARGETOS} | cut -c1 | tr '[:lower:]' '[:upper:]' | xargs echo -n; echo ${TARGETOS} | cut -c2-)"; \ ( \ - TERRASCAN_RELEASES="https://api.github.com/repos/tenable/terrascan/releases" && \ - [ "$TERRASCAN_VERSION" = "latest" ] && curl -L "$(curl -s ${TERRASCAN_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${OS}_${ARCH}.tar.gz")" > terrascan.tar.gz \ - || curl -L "$(curl -s ${TERRASCAN_RELEASES} | grep -o -E "https://.+?${TERRASCAN_VERSION}_${OS}_${ARCH}.tar.gz")" > terrascan.tar.gz \ + TERRASCAN_RELEASES="https://api.github.com/repos/tenable/terrascan/releases" && \ + [ "$TERRASCAN_VERSION" = "latest" ] && curl -L "$(curl -s ${TERRASCAN_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${OS}_${ARCH}.tar.gz")" > terrascan.tar.gz \ + || curl -L "$(curl -s ${TERRASCAN_RELEASES} | grep -o -E "https://.+?${TERRASCAN_VERSION}_${OS}_${ARCH}.tar.gz")" > terrascan.tar.gz \ ) && tar -xzf terrascan.tar.gz terrascan && rm terrascan.tar.gz && \ ./terrascan init \ ; fi @@ -122,9 +122,9 @@ RUN . /.env && \ RUN . /.env && \ if [ "$TFLINT_VERSION" != "false" ]; then \ ( \ - TFLINT_RELEASES="https://api.github.com/repos/terraform-linters/tflint/releases" && \ - [ "$TFLINT_VERSION" = "latest" ] && curl -L "$(curl -s ${TFLINT_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.zip")" > tflint.zip \ - || curl -L "$(curl -s ${TFLINT_RELEASES} | grep -o -E "https://.+?/v${TFLINT_VERSION}/tflint_${TARGETOS}_${TARGETARCH}.zip")" > tflint.zip \ + TFLINT_RELEASES="https://api.github.com/repos/terraform-linters/tflint/releases" && \ + [ "$TFLINT_VERSION" = "latest" ] && curl -L "$(curl -s ${TFLINT_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.zip")" > tflint.zip \ + || curl -L "$(curl -s ${TFLINT_RELEASES} | grep -o -E "https://.+?/v${TFLINT_VERSION}/tflint_${TARGETOS}_${TARGETARCH}.zip")" > tflint.zip \ ) && unzip tflint.zip && rm tflint.zip \ ; fi @@ -132,9 +132,9 @@ RUN . /.env && \ RUN . /.env && \ if [ "$TFSEC_VERSION" != "false" ]; then \ ( \ - TFSEC_RELEASES="https://api.github.com/repos/aquasecurity/tfsec/releases" && \ - [ "$TFSEC_VERSION" = "latest" ] && curl -L "$(curl -s ${TFSEC_RELEASES}/latest | grep -o -E -m 1 "https://.+?/tfsec-${TARGETOS}-${TARGETARCH}")" > tfsec \ - || curl -L "$(curl -s ${TFSEC_RELEASES} | grep -o -E -m 1 "https://.+?v${TFSEC_VERSION}/tfsec-${TARGETOS}-${TARGETARCH}")" > tfsec \ + TFSEC_RELEASES="https://api.github.com/repos/aquasecurity/tfsec/releases" && \ + [ "$TFSEC_VERSION" = "latest" ] && curl -L "$(curl -s ${TFSEC_RELEASES}/latest | grep -o -E -m 1 "https://.+?/tfsec-${TARGETOS}-${TARGETARCH}")" > tfsec \ + || curl -L "$(curl -s ${TFSEC_RELEASES} | grep -o -E -m 1 "https://.+?v${TFSEC_VERSION}/tfsec-${TARGETOS}-${TARGETARCH}")" > tfsec \ ) && chmod +x tfsec \ ; fi @@ -143,9 +143,9 @@ RUN . /.env && \ if [ "$TRIVY_VERSION" != "false" ]; then \ if [ "$TARGETARCH" != "amd64" ]; then ARCH="$TARGETARCH"; else ARCH="64bit"; fi; \ ( \ - TRIVY_RELEASES="https://api.github.com/repos/aquasecurity/trivy/releases" && \ - [ "$TRIVY_VERSION" = "latest" ] && curl -L "$(curl -s ${TRIVY_RELEASES}/latest | grep -o -E -i -m 1 "https://.+?/trivy_.+?_${TARGETOS}-${ARCH}.tar.gz")" > trivy.tar.gz \ - || curl -L "$(curl -s ${TRIVY_RELEASES} | grep -o -E -i -m 1 "https://.+?/v${TRIVY_VERSION}/trivy_.+?_${TARGETOS}-${ARCH}.tar.gz")" > trivy.tar.gz \ + TRIVY_RELEASES="https://api.github.com/repos/aquasecurity/trivy/releases" && \ + [ "$TRIVY_VERSION" = "latest" ] && curl -L "$(curl -s ${TRIVY_RELEASES}/latest | grep -o -E -i -m 1 "https://.+?/trivy_.+?_${TARGETOS}-${ARCH}.tar.gz")" > trivy.tar.gz \ + || curl -L "$(curl -s ${TRIVY_RELEASES} | grep -o -E -i -m 1 "https://.+?/v${TRIVY_VERSION}/trivy_.+?_${TARGETOS}-${ARCH}.tar.gz")" > trivy.tar.gz \ ) && tar -xzf trivy.tar.gz trivy && rm trivy.tar.gz \ ; fi @@ -153,9 +153,9 @@ RUN . /.env && \ RUN . /.env && \ if [ "$TFUPDATE_VERSION" != "false" ]; then \ ( \ - TFUPDATE_RELEASES="https://api.github.com/repos/minamijoyo/tfupdate/releases" && \ - [ "$TFUPDATE_VERSION" = "latest" ] && curl -L "$(curl -s ${TFUPDATE_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.tar.gz")" > tfupdate.tgz \ - || curl -L "$(curl -s ${TFUPDATE_RELEASES} | grep -o -E -m 1 "https://.+?${TFUPDATE_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz")" > tfupdate.tgz \ + TFUPDATE_RELEASES="https://api.github.com/repos/minamijoyo/tfupdate/releases" && \ + [ "$TFUPDATE_VERSION" = "latest" ] && curl -L "$(curl -s ${TFUPDATE_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.tar.gz")" > tfupdate.tgz \ + || curl -L "$(curl -s ${TFUPDATE_RELEASES} | grep -o -E -m 1 "https://.+?${TFUPDATE_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz")" > tfupdate.tgz \ ) && tar -xzf tfupdate.tgz tfupdate && rm tfupdate.tgz \ ; fi @@ -163,9 +163,9 @@ RUN . /.env && \ RUN . /.env && \ if [ "$HCLEDIT_VERSION" != "false" ]; then \ ( \ - HCLEDIT_RELEASES="https://api.github.com/repos/minamijoyo/hcledit/releases" && \ - [ "$HCLEDIT_VERSION" = "latest" ] && curl -L "$(curl -s ${HCLEDIT_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.tar.gz")" > hcledit.tgz \ - || curl -L "$(curl -s ${HCLEDIT_RELEASES} | grep -o -E -m 1 "https://.+?${HCLEDIT_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz")" > hcledit.tgz \ + HCLEDIT_RELEASES="https://api.github.com/repos/minamijoyo/hcledit/releases" && \ + [ "$HCLEDIT_VERSION" = "latest" ] && curl -L "$(curl -s ${HCLEDIT_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.tar.gz")" > hcledit.tgz \ + || curl -L "$(curl -s ${HCLEDIT_RELEASES} | grep -o -E -m 1 "https://.+?${HCLEDIT_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz")" > hcledit.tgz \ ) && tar -xzf hcledit.tgz hcledit && rm hcledit.tgz \ ; fi @@ -210,7 +210,7 @@ COPY --from=builder \ /bin_dir/ \ /usr/bin/tofu \ /usr/local/bin/checkov* \ - /usr/bin/ + /usr/bin/ # Copy pre-commit packages COPY --from=builder /usr/local/lib/python3.12/site-packages/ /usr/local/lib/python3.12/site-packages/ # Copy terrascan policies @@ -218,10 +218,10 @@ COPY --from=builder /root/ /root/ # Install hooks extra deps RUN if [ "$(grep -o '^terraform-docs SKIPPED$' /usr/bin/tools_versions_info)" = "" ]; then \ - apk add --no-cache perl=~5 \ + apk add --no-cache perl=~5 \ ; fi && \ if [ "$(grep -o '^infracost SKIPPED$' /usr/bin/tools_versions_info)" = "" ]; then \ - apk add --no-cache jq=~1 \ + apk add --no-cache jq=~1 \ ; fi && \ # Fix git runtime fatal: # unsafe repository ('/lint' is owned by someone else) diff --git a/README.md b/README.md index 73c4bb0..c8434c9 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ If you are using `pre-commit-opentofu` already or want to support its developmen

* [`checkov`](https://github.com/bridgecrewio/checkov) required for `tofu_checkov` hook. * [`terraform-docs`](https://github.com/terraform-docs/terraform-docs) required for `tofu_docs` hook. -* [`terragrunt`](https://terragrunt.gruntwork.io/docs/getting-started/install/) required for `terragrunt_validate` hook. +* [`terragrunt`](https://terragrunt.gruntwork.io/docs/getting-started/install/) required for `terragrunt_validate` and `terragrunt_validate_inputs` hooks. * [`terrascan`](https://github.com/tenable/terrascan) required for `terrascan` hook. * [`TFLint`](https://github.com/terraform-linters/tflint) required for `tofu_tflint` hook. * [`TFSec`](https://github.com/liamg/tfsec) required for `tofu_tfsec` hook. @@ -266,9 +266,35 @@ TAG=latest docker run --rm --entrypoint cat tofuutils/pre-commit-opentofu:$TAG /usr/bin/tools_versions_info ``` +### Example: Terragrunt Input Validation + +Use `terragrunt_validate_inputs` to check that Terragrunt inputs line up with the module variables they are passed into: + +```yaml +repos: +- repo: https://github.com/tofuutils/pre-commit-opentofu + rev: # Get the latest from: https://github.com/tofuutils/pre-commit-opentofu/releases + hooks: + - id: terragrunt_fmt + - id: terragrunt_validate_inputs + args: + - --args=--terragrunt-strict-validate +``` + +> **Note**: This hook automatically uses `terragrunt validate-inputs` for older Terragrunt releases and `terragrunt hcl validate --inputs` for newer releases. +> +> If Terragrunt reports intermittent `.terragrunt-cache` download or `file exists` errors in your repository, run this hook serially in your consumer configuration: +> +> ```yaml +> - id: terragrunt_validate_inputs +> require_serial: true +> args: +> - --args=--terragrunt-strict-validate +> ``` + ## Available Hooks -There are several [pre-commit](https://pre-commit.com/) hooks to keep OpenTofu configurations (both `*.tf` and `*.tfvars`) and Terragrunt configurations (`*.hcl`) in a good shape: +There are several [pre-commit](https://pre-commit.com/) hooks to keep OpenTofu configurations (`*.tf`, `*.tofu`, and `*.tfvars`) and Terragrunt configurations (`*.hcl`) in a good shape: | Hook name | Description | Dependencies
[Install instructions here](#1-install-dependencies) | @@ -286,6 +312,7 @@ There are several [pre-commit](https://pre-commit.com/) hooks to keep OpenTofu c | `tofu_validate` | Validates all Terraform configuration files. [Hook notes](#tofu_validate) | `jq`, only for `--retry-once-with-cleanup` flag | | `terragrunt_fmt` | Reformat all [Terragrunt](https://github.com/gruntwork-io/terragrunt) configuration files (`*.hcl`) to a canonical format. | `terragrunt` | | `terragrunt_validate` | Validates all [Terragrunt](https://github.com/gruntwork-io/terragrunt) configuration files (`*.hcl`) | `terragrunt` | +| `terragrunt_validate_inputs` | Validates Terragrunt unused and undefined inputs. | `terragrunt` | | `tofu_wrapper_module_for_each` | Generates OpenTofu wrappers with `for_each` in module. [Hook notes](#terraform_wrapper_module_for_each) | `hcledit` | | `terrascan` | [terrascan](https://github.com/tenable/terrascan) Detect compliance and security violations. [Hook notes](#terrascan) | `terrascan` | | `tfupdate` | [tfupdate](https://github.com/minamijoyo/tfupdate) Update version constraints of OpenTofu core, providers, and modules. [Hook notes](#tfupdate) | `tfupdate` | @@ -299,6 +326,8 @@ Check the [source file](https://github.com/tofuutils/pre-commit-opentofu/blob/ma OpenTofu operates on a per-dir basis, while `pre-commit` framework only supports files and files that exist. This means if you only remove the TF-related file without any other changes in the same dir, checks will be skipped. Example and details [here](https://github.com/pre-commit/pre-commit/issues/3048). +Hooks match `*.tofu` files where OpenTofu configuration files are supported, but some wrapped third-party tools may lag behind OpenTofu's native `*.tofu` parsing. If a hook runs `terraform-docs`, `tflint`, `tfsec`, `trivy`, `checkov`, `infracost`, or `tfupdate`, make sure the installed tool version supports the file extensions used in your repository. + ### All hooks: Usage of environment variables in `--args` > All, except deprecated hooks: `checkov`, `tofu_docs_replace` @@ -552,7 +581,7 @@ To replicate functionality in `tofu_docs` hook: - --args=--config=.terraform-docs.yml ``` -### terraftofu_fmtorm_fmt +### tofu_fmt 1. `tofu_fmt` supports custom arguments so you can pass [supported flags](https://www.terraform.io/docs/cli/commands/fmt.html#usage). Eg: @@ -689,7 +718,7 @@ To replicate functionality in `tofu_docs` hook: - --args=--config=__GIT_WORKING_DIR__/.tflint.hcl ``` -3. By default, pre-commit-opentofu performs directory switching into the OpenTofu modules for you. If you want to delgate the directory changing to the binary - this will allow tflint to determine the full paths for error/warning messages, rather than just module relative paths. *Note: this requires `tflint>=0.44.0`.* For example: +3. By default, pre-commit-opentofu performs directory switching into the OpenTofu modules for you. If you want to delegate the directory changing to the binary - this will allow tflint to determine the full paths for error/warning messages, rather than just module relative paths. *Note: this requires `tflint>=0.44.0`.* For example: ```yaml - id: tofu_tflint @@ -903,7 +932,7 @@ To replicate functionality in `tofu_docs` hook: require_serial: true entry: .generate-providers.sh language: script - files: \.tf(vars)?$ + files: \.(tf|tofu|tfvars)$ pass_filenames: false - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/hooks/_common.sh b/hooks/_common.sh index 133e457..5808c19 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -112,7 +112,7 @@ function common::parse_and_export_env_vars { while true; do # Check if at least 1 env var exists in `$arg` # shellcheck disable=SC2016 # '${' should not be expanded - if [[ "$arg" =~ .*'${'[A-Z_][A-Z0-9_]+?'}'.* ]]; then + if [[ "$arg" =~ .*'${'[A-Z_][A-Z0-9_]*'}'.* ]]; then # Get `ENV_VAR` from `.*${ENV_VAR}.*` local env_var_name=${arg#*$\{} env_var_name=${env_var_name%%\}*} @@ -123,7 +123,7 @@ function common::parse_and_export_env_vars { # `$arg` will be checked in `if` conditional, `$ARGS` will be used in the next functions. # shellcheck disable=SC2016 # '${' should not be expanded arg=${arg/'${'$env_var_name'}'/$env_var_value} - ARGS[$arg_idx]=$arg + ARGS[arg_idx]=$arg # shellcheck disable=SC2016 # '${' should not be expanded common::colorify "green" 'After ${'"$env_var_name"'} expansion: '"'$arg'\n" continue diff --git a/hooks/infracost_breakdown.sh b/hooks/infracost_breakdown.sh index 5515791..121f49b 100755 --- a/hooks/infracost_breakdown.sh +++ b/hooks/infracost_breakdown.sh @@ -70,19 +70,24 @@ function infracost_breakdown_ { # -h .totalHourlyCost > 0.1 # --hook-config=.currency == "USD" first_char=${check:0:1} - last_char=${check: -1} + last_char=${check:$((${#check} - 1)):1} if [ "$first_char" == "$last_char" ] && { [ "$first_char" == '"' ] || [ "$first_char" == "'" ] }; then - check="${check:1:-1}" + check="${check:1:$((${#check} - 2))}" fi - mapfile -t operations < <(echo "$check" | grep -oE '[!<>=]{1,2}') + # Replace mapfile with while read loop for bash 3.2 compatibility + operations=() + while IFS= read -r line; do + operations+=("$line") + done < <(echo "$check" | grep -oE '[!<>=]{1,2}') + # Get the very last operator, that is used in comparison inside `jq` query. # From the example below we need to pick the `>` which is in between `add` and `1000`, # but not the `!=`, which goes earlier in the `jq` expression # [.projects[].diff.totalMonthlyCost | select (.!=null) | tonumber] | add > 1000 - operation=${operations[-1]} + operation=${operations[$((${#operations[@]} - 1))]} IFS="$operation" read -r -a jq_check <<< "$check" real_value="$(jq "${jq_check[0]}" <<< "$RESULTS")" diff --git a/hooks/terragrunt_fmt.sh b/hooks/terragrunt_fmt.sh index 7c78b92..0b4165a 100755 --- a/hooks/terragrunt_fmt.sh +++ b/hooks/terragrunt_fmt.sh @@ -12,7 +12,7 @@ function main { common::parse_cmdline "$@" common::export_provided_env_vars "${ENV_VARS[@]}" common::parse_and_export_env_vars - # JFYI: terragrunt hclfmt color already suppressed via PRE_COMMIT_COLOR=never + # JFYI: terragrunt hcl format color already suppressed via PRE_COMMIT_COLOR=never # shellcheck disable=SC2153 # False positive common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" @@ -40,7 +40,7 @@ function per_dir_hook_unique_part { local -a -r args=("$@") # pass the arguments to hook - terragrunt hclfmt "${args[@]}" + terragrunt hcl format "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? @@ -57,7 +57,7 @@ function run_hook_on_whole_repo { local -a -r args=("$@") # pass the arguments to hook - terragrunt hclfmt "$(pwd)" "${args[@]}" + terragrunt hcl format "$(pwd)" "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? diff --git a/hooks/terragrunt_validate_inputs.sh b/hooks/terragrunt_validate_inputs.sh new file mode 100755 index 0000000..a406324 --- /dev/null +++ b/hooks/terragrunt_validate_inputs.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash +set -eo pipefail + +# globals variables +# shellcheck disable=SC2155 # No way to assign to readonly variable in separate lines +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +function main { + common::initialize "$SCRIPT_DIR" + common::parse_cmdline "$@" + common::export_provided_env_vars "${ENV_VARS[@]}" + common::parse_and_export_env_vars + # JFYI: terragrunt validate color already suppressed via PRE_COMMIT_COLOR=never + + if terragrunt_version_ge_0_78; then + normalize_validate_args_for_modern_terragrunt + readonly SUBCOMMAND=("hcl" "validate" "--inputs") + readonly RUN_ALL_SUBCOMMAND=("run" "--all" "hcl" "validate" "--inputs") + + # shellcheck disable=SC2153 # False positive + common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" + return + fi + + run_legacy_validate_inputs +} + +function normalize_validate_args_for_modern_terragrunt { + local arg_idx + + for arg_idx in "${!ARGS[@]}"; do + case "${ARGS[$arg_idx]}" in + --terragrunt-strict-validate | --strict-validate) + ARGS[arg_idx]="--strict" + ;; + esac + done +} + +function terragrunt_version_ge_0_78 { + local version_raw + local version + local major + local minor + + version_raw=$(terragrunt --version 2> /dev/null || true) + version=$(echo "$version_raw" | sed -E 's/.*v?([0-9]+)\.([0-9]+)\.([0-9]+).*/\1.\2.\3/') + + if [[ ! $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + return 1 + fi + + IFS=. read -r major minor _ <<< "$version" + + if ((major > 0)); then + return 0 + fi + + if ((minor >= 78)); then + return 0 + fi + + return 1 +} + +function run_legacy_validate_inputs { + local -a unit_dirs=() + local final_exit_code=0 + local dir_path + + while read -r dir_path; do + if [[ -n $dir_path ]]; then + unit_dirs+=("$dir_path") + fi + done < <(legacy_unit_dirs_from_files) + + if [[ ${#unit_dirs[@]} -eq 0 ]]; then + return 0 + fi + + # preserve errexit status + shopt -qo errexit && ERREXIT_IS_SET=true + set +e + + for dir_path in "${unit_dirs[@]}"; do + pushd "$dir_path" > /dev/null || continue + terragrunt validate-inputs "${ARGS[@]}" + + local exit_code=$? + if [ $exit_code -ne 0 ]; then + final_exit_code=$exit_code + fi + + popd > /dev/null + done + + [[ $ERREXIT_IS_SET ]] && set -e + exit $final_exit_code +} + +function legacy_unit_dirs_from_files { + local -a unit_files=() + local file_with_path + local file_dir + local file_name + + if common::is_hook_run_on_whole_repo "$HOOK_ID" "${FILES[@]}"; then + find . -type f -name terragrunt.hcl \ + -not -path '*/.terragrunt-cache/*' \ + -not -path '*/.terraform/*' | + sort -u | while read -r unit_file; do + dirname "$unit_file" + done + return + fi + + for file_with_path in "${FILES[@]}"; do + file_dir=$(dirname "$file_with_path") + file_name=$(basename "$file_with_path") + + if [[ $file_name == terragrunt.hcl ]]; then + unit_files+=("$file_with_path") + continue + fi + + while read -r unit_file; do + if [[ -n $unit_file ]]; then + unit_files+=("$unit_file") + fi + done < <(find "$file_dir" -type f -name terragrunt.hcl \ + -not -path '*/.terragrunt-cache/*' \ + -not -path '*/.terraform/*' | sort -u) + done + + if [[ ${#unit_files[@]} -eq 0 ]]; then + find . -type f -name terragrunt.hcl \ + -not -path '*/.terragrunt-cache/*' \ + -not -path '*/.terraform/*' | + sort -u | while read -r unit_file; do + dirname "$unit_file" + done + return + fi + + printf '%s\n' "${unit_files[@]}" | sort -u | while read -r unit_file; do + dirname "$unit_file" + done +} + +####################################################################### +# Unique part of `common::per_dir_hook`. The function is executed in loop +# on each provided dir path. Run wrapped tool with specified arguments +# Arguments: +# dir_path (string) PATH to dir relative to git repo root. +# Can be used in error logging +# change_dir_in_unique_part (string/false) Modifier which creates +# possibilities to use non-common chdir strategies. +# Availability depends on hook. +# args (array) arguments that configure wrapped tool behavior +# Outputs: +# If failed - print out hook checks status +####################################################################### +function per_dir_hook_unique_part { + # shellcheck disable=SC2034 # Unused var. + local -r dir_path="$1" + # shellcheck disable=SC2034 # Unused var. + local -r change_dir_in_unique_part="$2" + shift 2 + local -a -r args=("$@") + + # pass the arguments to hook + terragrunt "${SUBCOMMAND[@]}" "${args[@]}" + + # return exit code to common::per_dir_hook + local exit_code=$? + return $exit_code +} + +####################################################################### +# Unique part of `common::per_dir_hook`. The function is executed one time +# in the root git repo +# Arguments: +# args (array) arguments that configure wrapped tool behavior +####################################################################### +function run_hook_on_whole_repo { + local -a -r args=("$@") + + # pass the arguments to hook + terragrunt "${RUN_ALL_SUBCOMMAND[@]}" "${args[@]}" + + # return exit code to common::per_dir_hook + local exit_code=$? + return $exit_code +} + +[ "${BASH_SOURCE[0]}" != "$0" ] || main "$@" diff --git a/hooks/tofu_docs.sh b/hooks/tofu_docs.sh index b7c9e30..8b8d136 100755 --- a/hooks/tofu_docs.sh +++ b/hooks/tofu_docs.sh @@ -155,7 +155,7 @@ function tofu_docs { # if $create_if_not_exist && [[ ! -f "$text_file" ]]; then dir_have_tf_files="$( - find . -maxdepth 1 -type f | sed 's|.*\.||' | sort -u | grep -oE '^tf$|^tfvars$' || + find . -maxdepth 1 -type f | sed 's|.*\.||' | sort -u | grep -oE '^tofu|^tf$|^tfvars$' || exit 0 )" diff --git a/hooks/tofu_docs_replace.py b/hooks/tofu_docs_replace.py index a9cf6c9..26f1d06 100644 --- a/hooks/tofu_docs_replace.py +++ b/hooks/tofu_docs_replace.py @@ -7,30 +7,41 @@ import sys def main(argv=None): parser = argparse.ArgumentParser( description="""Run terraform-docs on a set of files. Follows the standard convention of - pulling the documentation from main.tf in order to replace the entire + pulling the documentation from main.(tf|tofu) in order to replace the entire README.md file each time.""" ) parser.add_argument( - '--dest', dest='dest', default='README.md', + "--dest", + dest="dest", + default="README.md", ) parser.add_argument( - '--sort-inputs-by-required', dest='sort', action='store_true', - help='[deprecated] use --sort-by-required instead', + "--sort-inputs-by-required", + dest="sort", + action="store_true", + help="[deprecated] use --sort-by-required instead", ) parser.add_argument( - '--sort-by-required', dest='sort', action='store_true', + "--sort-by-required", + dest="sort", + action="store_true", ) parser.add_argument( - '--with-aggregate-type-defaults', dest='aggregate', action='store_true', - help='[deprecated]', + "--with-aggregate-type-defaults", + dest="aggregate", + action="store_true", + help="[deprecated]", ) - parser.add_argument('filenames', nargs='*', help='Filenames to check.') + parser.add_argument("filenames", nargs="*", help="Filenames to check.") args = parser.parse_args(argv) dirs = [] for filename in args.filenames: - if (os.path.realpath(filename) not in dirs and - (filename.endswith(".tf") or filename.endswith(".tfvars"))): + if os.path.realpath(filename) not in dirs and ( + filename.endswith(".tf") + or filename.endswith(".tofu") + or filename.endswith(".tfvars") + ): dirs.append(os.path.dirname(filename)) retval = 0 @@ -38,12 +49,12 @@ def main(argv=None): for dir in dirs: try: procArgs = [] - procArgs.append('terraform-docs') + procArgs.append("terraform-docs") if args.sort: - procArgs.append('--sort-by-required') - procArgs.append('md') + procArgs.append("--sort-by-required") + procArgs.append("md") procArgs.append("./{dir}".format(dir=dir)) - procArgs.append('>') + procArgs.append(">") procArgs.append("./{dir}/{dest}".format(dir=dir, dest=args.dest)) subprocess.check_call(" ".join(procArgs), shell=True) except subprocess.CalledProcessError as e: @@ -52,5 +63,5 @@ def main(argv=None): return retval -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) diff --git a/hooks/tofu_wrapper_module_for_each.sh b/hooks/tofu_wrapper_module_for_each.sh index f1a70ef..4572b58 100755 --- a/hooks/tofu_wrapper_module_for_each.sh +++ b/hooks/tofu_wrapper_module_for_each.sh @@ -312,10 +312,10 @@ EOF # Read content of all OpenTofu files # shellcheck disable=SC2207 - all_tf_content=$(find "${full_module_dir}" -name '*.tf' -maxdepth 1 -type f -exec cat {} +) + all_tf_content=$(find "${full_module_dir}" -regex '.*\.(tf|tofu)' -maxdepth 1 -type f -exec cat {} +) if [[ ! $all_tf_content ]]; then - common::colorify "yellow" "Skipping ${full_module_dir} because there are no *.tf files." + common::colorify "yellow" "Skipping ${full_module_dir} because there are no .tf or .tofu files." continue fi diff --git a/lib_getopt b/lib_getopt index c4b21fa..74e72f1 100644 --- a/lib_getopt +++ b/lib_getopt @@ -360,7 +360,7 @@ getopt() { } _getopt_resolve_abbrev() { - # Resolves an abbrevation from a list of possibilities. + # Resolves an abbreviation from a list of possibilities. # If the abbreviation is unambiguous, echoes the expansion on stdout # and returns 0. If the abbreviation is ambiguous, prints a message on # stderr and returns 1. (For first parse this should convert to exit