From 454eb413d4b1a504af13bae2b30c0cf388bedba5 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 12 Jan 2024 20:40:39 +0200 Subject: [PATCH 001/293] chore: Fix conflict with Renovate issue (#611) --- .github/workflows/stale-actions.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale-actions.yaml b/.github/workflows/stale-actions.yaml index f43e10db8..32f08e781 100644 --- a/.github/workflows/stale-actions.yaml +++ b/.github/workflows/stale-actions.yaml @@ -21,7 +21,7 @@ jobs: This PR has been automatically marked as stale because it has been open 30 days with no activity. Remove stale label or comment or this PR will be closed in 10 days # Not stale if have this labels or part of milestone - exempt-issue-labels: bug,wip,on-hold + exempt-issue-labels: bug,wip,on-hold,auto-update exempt-pr-labels: bug,wip,on-hold exempt-all-milestones: true # Close issue operations From aa2c9b24ede6c5e4cec790fe236808e232e27d68 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 23 Jan 2024 17:27:01 +0200 Subject: [PATCH 002/293] chore: Bump maintenance bagde (#614) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b4eef7d66..aac9f8eba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Collection of git hooks for Terraform to be used with [pre-commit framework](http://pre-commit.com/) -[![Github tag](https://img.shields.io/github/tag/antonbabenko/pre-commit-terraform.svg)](https://github.com/antonbabenko/pre-commit-terraform/releases) ![maintenance status](https://img.shields.io/maintenance/yes/2023.svg) [![Help Contribute to Open Source](https://www.codetriage.com/antonbabenko/pre-commit-terraform/badges/users.svg)](https://www.codetriage.com/antonbabenko/pre-commit-terraform) +[![Github tag](https://img.shields.io/github/tag/antonbabenko/pre-commit-terraform.svg)](https://github.com/antonbabenko/pre-commit-terraform/releases) ![maintenance status](https://img.shields.io/maintenance/yes/2024.svg) [![Help Contribute to Open Source](https://www.codetriage.com/antonbabenko/pre-commit-terraform/badges/users.svg)](https://www.codetriage.com/antonbabenko/pre-commit-terraform) [![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) From 995a59d4c30c7710a640aba2bb430265677894c5 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 23 Jan 2024 19:05:19 +0200 Subject: [PATCH 003/293] docs: Update installation instructions (#615) ### Description of your changes * Tested out that it works on Ubuntu 22.04.3 * Add needed link to python3 * Update infracost command as it was changed --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aac9f8eba..7d7be2dea 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ curl -L "$(curl -s https://api.github.com/repos/aquasecurity/tfsec/releases/late curl -L "$(curl -s https://api.github.com/repos/aquasecurity/trivy/releases/latest | grep -o -E -i -m 1 "https://.+?/trivy_.+?_Linux-64bit.tar.gz")" > trivy.tar.gz && tar -xzf trivy.tar.gz trivy && rm trivy.tar.gz && sudo mv trivy /usr/bin curl -L "$(curl -s https://api.github.com/repos/tenable/terrascan/releases/latest | grep -o -E -m 1 "https://.+?_Linux_x86_64.tar.gz")" > terrascan.tar.gz && tar -xzf terrascan.tar.gz terrascan && rm terrascan.tar.gz && sudo mv terrascan /usr/bin/ && terrascan init sudo apt install -y jq && \ -curl -L "$(curl -s https://api.github.com/repos/infracost/infracost/releases/latest | grep -o -E -m 1 "https://.+?-linux-amd64.tar.gz")" > infracost.tgz && tar -xzf infracost.tgz && rm infracost.tgz && sudo mv infracost-linux-amd64 /usr/bin/infracost && infracost register +curl -L "$(curl -s https://api.github.com/repos/infracost/infracost/releases/latest | grep -o -E -m 1 "https://.+?-linux-amd64.tar.gz")" > infracost.tgz && tar -xzf infracost.tgz && rm infracost.tgz && sudo mv infracost-linux-amd64 /usr/bin/infracost && infracost auth login curl -L "$(curl -s https://api.github.com/repos/minamijoyo/tfupdate/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > tfupdate.tar.gz && tar -xzf tfupdate.tar.gz tfupdate && rm tfupdate.tar.gz && sudo mv tfupdate /usr/bin/ curl -L "$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > hcledit.tar.gz && tar -xzf hcledit.tar.gz hcledit && rm hcledit.tar.gz && sudo mv hcledit /usr/bin/ ``` @@ -170,11 +170,11 @@ curl -L "$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/late -
Ubuntu 20.04
+
Ubuntu 20.04+
```bash sudo apt update -sudo apt install -y unzip software-properties-common python3 python3-pip +sudo apt install -y unzip software-properties-common python3 python3-pip python-is-python3 python3 -m pip install --upgrade pip pip3 install --no-cache-dir pre-commit pip3 install --no-cache-dir checkov @@ -184,7 +184,7 @@ curl -L "$(curl -s https://api.github.com/repos/terraform-linters/tflint/release curl -L "$(curl -s https://api.github.com/repos/aquasecurity/tfsec/releases/latest | grep -o -E -m 1 "https://.+?tfsec-linux-amd64")" > tfsec && chmod +x tfsec && sudo mv tfsec /usr/bin/ curl -L "$(curl -s https://api.github.com/repos/aquasecurity/trivy/releases/latest | grep -o -E -i -m 1 "https://.+?/trivy_.+?_Linux-64bit.tar.gz")" > trivy.tar.gz && tar -xzf trivy.tar.gz trivy && rm trivy.tar.gz && sudo mv trivy /usr/bin sudo apt install -y jq && \ -curl -L "$(curl -s https://api.github.com/repos/infracost/infracost/releases/latest | grep -o -E -m 1 "https://.+?-linux-amd64.tar.gz")" > infracost.tgz && tar -xzf infracost.tgz && rm infracost.tgz && sudo mv infracost-linux-amd64 /usr/bin/infracost && infracost register +curl -L "$(curl -s https://api.github.com/repos/infracost/infracost/releases/latest | grep -o -E -m 1 "https://.+?-linux-amd64.tar.gz")" > infracost.tgz && tar -xzf infracost.tgz && rm infracost.tgz && sudo mv infracost-linux-amd64 /usr/bin/infracost && infracost auth login curl -L "$(curl -s https://api.github.com/repos/minamijoyo/tfupdate/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > tfupdate.tar.gz && tar -xzf tfupdate.tar.gz tfupdate && rm tfupdate.tar.gz && sudo mv tfupdate /usr/bin/ curl -L "$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > hcledit.tar.gz && tar -xzf hcledit.tar.gz hcledit && rm hcledit.tar.gz && sudo mv hcledit /usr/bin/ ``` From eee8fa1d55d404d87688bf44ef81bd0b2a8bac2d Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Sat, 10 Feb 2024 00:38:26 +0200 Subject: [PATCH 004/293] docs: Revisit docs constructions to Github markdown extension (#619) And partially revert syntax as GH Markdown is buggy: https://github.com/orgs/community/discussions/16925#discussioncomment-7574895 --- .../bug_report_local_install.md | 2 + Dockerfile | 1 + README.md | 79 ++++++++++--------- 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report_local_install.md b/.github/ISSUE_TEMPLATE/bug_report_local_install.md index 63a1ce915..544dc03ed 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_local_install.md +++ b/.github/ISSUE_TEMPLATE/bug_report_local_install.md @@ -82,11 +82,13 @@ terraform --version | head -n 1 2>/dev/null || echo "terraform SKIPPED python --version 2>/dev/null || echo "python SKIPPED" python3 --version 2>/dev/null || echo "python3 SKIPPED" echo -n "checkov " && checkov --version 2>/dev/null || echo "checkov SKIPPED" +infracost --version 2>/dev/null || echo "infracost SKIPPED" terraform-docs --version 2>/dev/null || echo "terraform-docs SKIPPED" terragrunt --version 2>/dev/null || echo "terragrunt SKIPPED" echo -n "terrascan " && terrascan version 2>/dev/null || echo "terrascan SKIPPED" tflint --version 2>/dev/null || echo "tflint SKIPPED" echo -n "tfsec " && tfsec --version 2>/dev/null || echo "tfsec SKIPPED" +echo -n "trivy " && trivy --version 2>/dev/null || echo "tfsec SKIPPED" echo -n "tfupdate " && tfupdate --version 2>/dev/null || echo "tfupdate SKIPPED" echo -n "hcledit " && hcledit version 2>/dev/null || echo "hcledit SKIPPED" EOF diff --git a/Dockerfile b/Dockerfile index fcf33f2a9..83d0bc05b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -181,6 +181,7 @@ RUN . /.env && \ (if [ "$TERRASCAN_VERSION" != "false" ]; then echo "terrascan $(./terrascan version)" >> $F; else echo "terrascan SKIPPED" >> $F ; fi) && \ (if [ "$TFLINT_VERSION" != "false" ]; then ./tflint --version >> $F; else echo "tflint SKIPPED" >> $F ; fi) && \ (if [ "$TFSEC_VERSION" != "false" ]; then echo "tfsec $(./tfsec --version)" >> $F; else echo "tfsec SKIPPED" >> $F ; fi) && \ + (if [ "$TRIVY_VERSION" != "false" ]; then echo "trivy $(./trivy --version)" >> $F; else echo "trivy SKIPPED" >> $F ; fi) && \ (if [ "$TFUPDATE_VERSION" != "false" ]; then echo "tfupdate $(./tfupdate --version)" >> $F; else echo "tfupdate SKIPPED" >> $F ; fi) && \ (if [ "$HCLEDIT_VERSION" != "false" ]; then echo "hcledit $(./hcledit version)" >> $F; else echo "hcledit SKIPPED" >> $F ; fi) && \ echo -e "\n\n" && cat $F && echo -e "\n\n" diff --git a/README.md b/README.md index 7d7be2dea..dcafd39e1 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,6 @@ Automated provisioning of Terraform workflows and Infrastructure as Code.
infracost - - Cloud cost estimates for Terraform. If you are using `pre-commit-terraform` already or want to support its development and [many other open-source projects](https://github.com/antonbabenko/terraform-aws-devops), please become a [GitHub Sponsor](https://github.com/sponsors/antonbabenko)! @@ -64,8 +62,6 @@ If you are using `pre-commit-terraform` already or want to support its developme ### 1. Install dependencies - - * [`pre-commit`](https://pre-commit.com/#install), [`terraform`](https://www.terraform.io/downloads.html), [`git`](https://git-scm.com/downloads), @@ -102,7 +98,8 @@ All available tags [here](https://github.com/antonbabenko/pre-commit-terraform/p **Build from scratch**: -> **Note**: To build image you need to have [`docker buildx`](https://docs.docker.com/build/install-buildx/) enabled as default builder. +> [!IMPORTANT] +> To build image you need to have [`docker buildx`](https://docs.docker.com/build/install-buildx/) enabled as default builder. > Otherwise - provide `TARGETOS` and `TARGETARCH` as additional `--build-arg`'s to `docker build`. When hooks-related `--build-arg`s are not specified, only the latest version of `pre-commit` and `terraform` will be installed. @@ -195,7 +192,8 @@ curl -L "$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/late We highly recommend using [WSL/WSL2](https://docs.microsoft.com/en-us/windows/wsl/install) with Ubuntu and following the Ubuntu installation guide. Or use Docker. -> **Note**: We won't be able to help with issues that can't be reproduced in Linux/Mac. +> [!IMPORTANT] +> We won't be able to help with issues that can't be reproduced in Linux/Mac. > So, try to find a working solution and send PR before open an issue. Otherwise, you can follow [this gist](https://gist.github.com/etiennejeanneaurevolve/1ed387dc73c5d4cb53ab313049587d09): @@ -211,11 +209,10 @@ E.g. `C:\Users\USERNAME\AppData\Local\Programs\Python\Python39\Lib\site-packages
- - ### 2. Install the pre-commit hook globally -> **Note**: not needed if you use the Docker image +> [!NOTE] +> Not needed if you use the Docker image ```bash DIR=~/.git-template @@ -249,7 +246,8 @@ pre-commit run -a Or, using Docker ([available tags](https://github.com/antonbabenko/pre-commit-terraform/pkgs/container/pre-commit-terraform/versions)): -> **Note**: This command uses your user id and group id for the docker container to use to access the local files. If the files are owned by another user, update the `USERID` environment variable. See [File Permissions section](#file-permissions) for more information. +> [!TIP] +> This command uses your user id and group id for the docker container to use to access the local files. If the files are owned by another user, update the `USERID` environment variable. See [File Permissions section](#file-permissions) for more information. ```bash TAG=latest @@ -267,7 +265,6 @@ docker run --rm --entrypoint cat ghcr.io/antonbabenko/pre-commit-terraform:$TAG There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform configurations (both `*.tf` and `*.tfvars`) and Terragrunt configurations (`*.hcl`) in a good shape: - | Hook name | Description | Dependencies
[Install instructions here](#1-install-dependencies) | | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | `checkov` and `terraform_checkov` | [checkov](https://github.com/bridgecrewio/checkov) static analysis of terraform templates to spot potential security issues. [Hook notes](#checkov-deprecated-and-terraform_checkov) | `checkov`
Ubuntu deps: `python3`, `python3-pip` | @@ -278,7 +275,7 @@ There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform | `terraform_fmt` | Reformat all Terraform configuration files to a canonical format. [Hook notes](#terraform_fmt) | - | | `terraform_providers_lock` | Updates provider signatures in [dependency lock files](https://www.terraform.io/docs/cli/commands/providers/lock.html). [Hook notes](#terraform_providers_lock) | - | | `terraform_tflint` | Validates all Terraform configuration files with [TFLint](https://github.com/terraform-linters/tflint). [Available TFLint rules](https://github.com/terraform-linters/tflint/tree/master/docs/rules#rules). [Hook notes](#terraform_tflint). | `tflint` | -| `terraform_tfsec` | [TFSec](https://github.com/aquasecurity/tfsec) static analysis of terraform templates to spot potential security issues. **DEPRECATED**, use `terraform_trivy`. [Hook notes](#terraform_tfsec-deprecated) | `tfsec` | +| `terraform_tfsec` | [TFSec](https://github.com/aquasecurity/tfsec) static analysis of terraform templates to spot potential security issues. **DEPRECATED**, use `terraform_trivy`. [Hook notes](#terraform_tfsec-deprecated) | `tfsec` | | `terraform_trivy` | [Trivy](https://github.com/aquasecurity/trivy) static analysis of terraform templates to spot potential security issues. [Hook notes](#terraform_trivy) | `trivy` | | `terraform_validate` | Validates all Terraform configuration files. [Hook notes](#terraform_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` | @@ -286,7 +283,6 @@ There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform | `terraform_wrapper_module_for_each` | Generates Terraform 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 Terraform core, providers, and modules. [Hook notes](#tfupdate) | `tfupdate` | - Check the [source file](https://github.com/antonbabenko/pre-commit-terraform/blob/master/.pre-commit-hooks.yaml) to know arguments used for each hook. @@ -302,7 +298,8 @@ Terraform operates on a per-dir basis, while `pre-commit` framework only support You can use environment variables for the `--args` section. -> **Warning**: You _must_ use the `${ENV_VAR}` definition, `$ENV_VAR` will not expand. +> [!IMPORTANT] +> You _must_ use the `${ENV_VAR}` definition, `$ENV_VAR` will not expand. Config example: @@ -390,7 +387,7 @@ Unlike most other hooks, this hook triggers once if there are any changed files - --args=--path=./env/dev verbose: true # Always show costs ``` - +
Output ```bash @@ -405,7 +402,7 @@ Unlike most other hooks, this hook triggers once if there are any changed files Total Monthly Cost: 86.83 USD Total Monthly Cost (diff): 86.83 USD ``` - +
2. Note that spaces are not allowed in `--args`, so you need to split it, like this: @@ -429,7 +426,7 @@ Unlike most other hooks, this hook triggers once if there are any changed files - --hook-config='.projects[].diff.totalMonthlyCost|tonumber != 10000' - --hook-config='.currency == "USD"' ``` - +
Output ```bash @@ -448,7 +445,7 @@ Unlike most other hooks, this hook triggers once if there are any changed files Total Monthly Cost: 86.83 USD Total Monthly Cost (diff): 86.83 USD ``` - +
* Only one path per one hook (`- id: infracost_breakdown`) is allowed. @@ -511,7 +508,8 @@ Unlike most other hooks, this hook triggers once if there are any changed files - --args=--config=.terraform-docs.yml ``` - > **Warning**: Avoid use `recursive.enabled: true` in config file, that can cause unexpected behavior. + > **Warning** + > Avoid use `recursive.enabled: true` in config file, that can cause unexpected behavior. 5. If you need some exotic settings, it can be done too. I.e. this one generates HCL files: @@ -563,12 +561,16 @@ To replicate functionality in `terraform_docs` hook: ### terraform_providers_lock -> **Note**: The hook requires Terraform 0.14 or later. +> [!NOTE] +> The hook requires Terraform 0.14 or later. -> **Note**: The hook can invoke `terraform providers lock` that can be really slow and requires fetching metadata from remote Terraform registries - not all of that metadata is currently being cached by Terraform. +> [!NOTE] +> The hook can invoke `terraform providers lock` that can be really slow and requires fetching metadata from remote Terraform registries - not all of that metadata is currently being cached by Terraform. ->
Note: Read this if you used this hook before v1.80.0 | Planned breaking changes in v2.0 -> We introduced '--mode' flag for this hook. If you'd like to continue using this hook as before, please: +> [!NOTE] +>
Read this if you used this hook before v1.80.0 | Planned breaking changes in v2.0 +>
+> We introduced `--mode` flag for this hook. If you'd like to continue using this hook as before, please: > > * Specify `--hook-config=--mode=always-regenerate-lockfile` in `args:` > * Before `terraform_providers_lock`, add `terraform_validate` hook with `--hook-config=--retry-once-with-cleanup=true` @@ -605,7 +607,8 @@ To replicate functionality in `terraform_docs` hook: * `only-check-is-current-lockfile-cross-platform` with [terraform_validate hook](#terraform_validate) - make up-to-date lockfile by adding/removing providers and only then check that lockfile has all required SHAs. - > **Note**: Next `terraform_validate` flag requires additional dependency to be installed: `jq`. Also, it could run another slow and time consuming command - `terraform init` + > **Important** + > Next `terraform_validate` flag requires additional dependency to be installed: `jq`. Also, it could run another slow and time consuming command - `terraform init` ```yaml - id: terraform_validate @@ -654,9 +657,10 @@ To replicate functionality in `terraform_docs` hook: `terraform_providers_lock` hook will try to reinitialize directories before running the `terraform providers lock` command. -5. `terraform_providers_lock` support passing custom arguments to its `terraform init`: +3. `terraform_providers_lock` support passing custom arguments to its `terraform init`: - > **Warning** - DEPRECATION NOTICE: This is available only in `no-mode` mode, which will be removed in v2.0. Please provide this keys to [`terraform_validate`](#terraform_validate) hook, which, to take effect, should be called before `terraform_providers_lock` + > **Warning** + > DEPRECATION NOTICE: This is available only in `no-mode` mode, which will be removed in v2.0. Please provide this keys to [`terraform_validate`](#terraform_validate) hook, which, to take effect, should be called before `terraform_providers_lock` ```yaml - id: terraform_providers_lock @@ -826,9 +830,11 @@ To replicate functionality in `terraform_docs` hook: - --hook-config=--retry-once-with-cleanup=true # Boolean. true or false ``` - > **Note**: The flag requires additional dependency to be installed: `jq`. + > **Important** + > The flag requires additional dependency to be installed: `jq`. - > **Note**: Reinit can be very slow and require downloading data from remote Terraform registries, and not all of that downloaded data or meta-data is currently being cached by Terraform. + > **Note** + > Reinit can be very slow and require downloading data from remote Terraform registries, and not all of that downloaded data or meta-data is currently being cached by Terraform. When `--retry-once-with-cleanup=true`, in each failed directory the cached modules and providers from the `.terraform` directory will be deleted, before retrying once more. To avoid unnecessary deletion of this directory, the cleanup and retry will only happen if Terraform produces any of the following error messages: @@ -838,7 +844,8 @@ To replicate functionality in `terraform_docs` hook: * "Module not installed" * "Could not load plugin" - **Warning**: When using `--retry-once-with-cleanup=true`, problematic `.terraform/modules/` and `.terraform/providers/` directories will be recursively deleted without prompting for consent. Other files and directories will not be affected, such as the `.terraform/environment` file. + > **Warning** + > When using `--retry-once-with-cleanup=true`, problematic `.terraform/modules/` and `.terraform/providers/` directories will be recursively deleted without prompting for consent. Other files and directories will not be affected, such as the `.terraform/environment` file. **Option 2** @@ -856,9 +863,10 @@ To replicate functionality in `terraform_docs` hook: `terraform_validate` hook will try to reinitialize them before running the `terraform validate` command. - **Warning**: If you use Terraform workspaces, DO NOT use this option ([details](https://github.com/antonbabenko/pre-commit-terraform/issues/203#issuecomment-918791847)). Consider the first option, or wait for [`force-init`](https://github.com/antonbabenko/pre-commit-terraform/issues/224) option implementation. + > **Caution** + > If you use Terraform workspaces, DO NOT use this option ([details](https://github.com/antonbabenko/pre-commit-terraform/issues/203#issuecomment-918791847)). Consider the first option, or wait for [`force-init`](https://github.com/antonbabenko/pre-commit-terraform/issues/224) option implementation. -4. `terraform_validate` in a repo with Terraform module, written using Terraform 0.15+ and which uses provider `configuration_aliases` ([Provider Aliases Within Modules](https://www.terraform.io/language/modules/develop/providers#provider-aliases-within-modules)), errors out. +1. `terraform_validate` in a repo with Terraform module, written using Terraform 0.15+ and which uses provider `configuration_aliases` ([Provider Aliases Within Modules](https://www.terraform.io/language/modules/develop/providers#provider-aliases-within-modules)), errors out. When running the hook against Terraform code where you have provider `configuration_aliases` defined in a `required_providers` configuration block, terraform will throw an error like: @@ -907,7 +915,8 @@ To replicate functionality in `terraform_docs` hook: [...] ``` - > Note: The latter method will leave an "aliased-providers.tf.json" file in your repo. You will either want to automate a way to clean this up or add it to your `.gitignore` or both. + > **Tip** + > The latter method will leave an "aliased-providers.tf.json" file in your repo. You will either want to automate a way to clean this up or add it to your `.gitignore` or both. ### terraform_wrapper_module_for_each @@ -1030,7 +1039,8 @@ machine github.com login ghp_bl481aBlabl481aBla ``` -> **Note**: The value of `GITHUB_SERVER_HOSTNAME` can also refer to a GitHub Enterprise server (i.e. `github.my-enterprise.com`). +> [!TIP] +> The value of `GITHUB_SERVER_HOSTNAME` can also refer to a GitHub Enterprise server (i.e. `github.my-enterprise.com`). Finally, you can execute `docker run` with an additional volume mount so that the `~/.netrc` is accessible within the container @@ -1045,7 +1055,6 @@ docker run --rm -e "USERID=$(id -u):$(id -g)" -v ~/.netrc:/root/.netrc -v $(pwd) This repository is managed by [Anton Babenko](https://github.com/antonbabenko) with help from these awesome contributors: - @@ -1059,8 +1068,6 @@ This repository is managed by [Anton Babenko](https://github.com/antonbabenko) w - - ## License MIT licensed. See [LICENSE](LICENSE) for full details. From 8bcd633aaeaff836ac5cffa3a020c8ab678a6b54 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Sat, 10 Feb 2024 00:39:12 +0200 Subject: [PATCH 005/293] chore(deps): Update tj-actions/changed-files to fix dependabot alert (#621) --- .github/workflows/build-image-test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index f83e11ebf..1b263ce46 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -13,9 +13,9 @@ jobs: with: fetch-depth: 0 - - name: Get changed Dockerfile + - name: Get changed Docker related files id: changed-files-specific - uses: tj-actions/changed-files@2c85495a7bb72f2734cb5181e29b2ee5e08e61f7 # v13.1 + uses: tj-actions/changed-files@90a06d6ba9543371ab4df8eeca0be07ca6054959 # v42.0.2 with: files: | Dockerfile From e1a93b26b29eda144fd0f53e3d84a99c07b15070 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Sat, 17 Feb 2024 00:26:40 +0200 Subject: [PATCH 006/293] fix: `grep: warning: stray \ before /` which pop-up in `grep 3.8` (#625) --- .pre-commit-hooks.yaml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index e8115b487..40c2cb072 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -5,7 +5,7 @@ language: script require_serial: true files: \.(tf(vars)?|hcl)$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ - id: terraform_fmt name: Terraform fmt @@ -13,7 +13,7 @@ entry: hooks/terraform_fmt.sh language: script files: (\.tf|\.tfvars)$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ - id: terraform_docs name: Terraform docs @@ -22,7 +22,7 @@ entry: hooks/terraform_docs.sh language: script files: (\.tf|\.terraform\.lock\.hcl)$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ - id: terraform_docs_without_aggregate_type_defaults name: Terraform docs (without aggregate type defaults) @@ -31,7 +31,7 @@ entry: hooks/terraform_docs.sh language: script files: (\.tf)$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ - id: terraform_docs_replace name: Terraform docs (overwrite README.md) @@ -40,7 +40,7 @@ entry: terraform_docs_replace language: python files: (\.tf)$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ - id: terraform_validate name: Terraform validate @@ -49,7 +49,7 @@ entry: hooks/terraform_validate.sh language: script files: (\.tf|\.tfvars)$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ - id: terraform_providers_lock name: Lock terraform provider versions @@ -58,7 +58,7 @@ entry: hooks/terraform_providers_lock.sh language: script files: (\.terraform\.lock\.hcl)$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ - id: terraform_tflint name: Terraform validate with tflint @@ -67,7 +67,7 @@ entry: hooks/terraform_tflint.sh language: script files: (\.tf|\.tfvars)$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ - id: terragrunt_fmt name: Terragrunt fmt @@ -75,7 +75,7 @@ entry: hooks/terragrunt_fmt.sh language: script files: (\.hcl)$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ - id: terragrunt_validate name: Terragrunt validate @@ -83,7 +83,7 @@ entry: hooks/terragrunt_validate.sh language: script files: (\.hcl)$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ - id: terraform_tfsec name: Terraform validate with tfsec (deprecated, use "terraform_trivy") @@ -109,7 +109,7 @@ pass_filenames: false always_run: false files: \.tf$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ require_serial: true - id: terraform_checkov @@ -119,7 +119,7 @@ language: script always_run: false files: \.tf$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ require_serial: true - id: terraform_wrapper_module_for_each @@ -131,7 +131,7 @@ always_run: false require_serial: true files: \.tf$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ - id: terrascan name: terrascan @@ -139,7 +139,7 @@ language: script entry: hooks/terrascan.sh files: \.tf$ - exclude: \.terraform\/.*$ + exclude: \.terraform/.*$ require_serial: true - id: tfupdate From d7ec9331606a8363f444d13c1ef1fb61a73d3887 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 16 Feb 2024 22:27:10 +0000 Subject: [PATCH 007/293] chore(release): version 1.86.1 [skip ci] ## [1.86.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.86.0...v1.86.1) (2024-02-16) ### Bug Fixes * `grep: warning: stray \ before /` which pop-up in `grep 3.8` ([#625](https://github.com/antonbabenko/pre-commit-terraform/issues/625)) ([e1a93b2](https://github.com/antonbabenko/pre-commit-terraform/commit/e1a93b26b29eda144fd0f53e3d84a99c07b15070)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd283b13b..29e544a99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.86.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.86.0...v1.86.1) (2024-02-16) + + +### Bug Fixes + +* `grep: warning: stray \ before /` which pop-up in `grep 3.8` ([#625](https://github.com/antonbabenko/pre-commit-terraform/issues/625)) ([e1a93b2](https://github.com/antonbabenko/pre-commit-terraform/commit/e1a93b26b29eda144fd0f53e3d84a99c07b15070)) + # [1.86.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.85.0...v1.86.0) (2023-12-21) From 6c6eca463a74fa2608cb3de3e03873765d46252f Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Sat, 17 Feb 2024 22:41:40 +0200 Subject: [PATCH 008/293] feat: Add parallelism to major chunk of hooks. Check `Parallelism` section in README (#620) ### Reasoning We have a GH workflow that runs lockflies updates every week (implementation and reasoning [here](https://grem1.in/post/terraform-lockfiles-maxymvlasov/)). It usually takes from 2h 30min to 3h 15min. That was fine for us, till we found that our GH runners, based on AWS EC2s, started silently failing after 30min "without recent logs", and that was fixed by crutch which sends a dummy log every 10min. However, during the debugging, I spent some time describing why hooks were not utilizing all the provided resources. And that means a waste of time and money, not only for that corner case but for every huge commit, which can cause opting out by `git commit -n` of using hooks locally for changes that affect many directories. ### Description of your changes * Add per-hook `--parallelism-limit` setting to `--hook-config`. Defaults to `number of logical CPUs - 1` * As quick tests show, ~5% of stacks face race condition problem, no matter if any locking mechanism exists or dirs try to init in parallel. I suppose the lock failed as it uses disk when hooks run in memory, so the creation of the lock can take some time as there bunch of caches between Mem and Disk. These milliseconds are enough to allow running a few `t init` in parallel. * Final implementation uses a retry mechanism for cases when race condition failed to `t init` directory. In quick tests, I can say that on big changes: * Up to 2000% speed increase for `terraform_validate`, and up to 500% - for other affected hooks. * When `--parallelism-limit=1` I observed an insignificant increase in time (about 5-10%) compared to v1.86.0 which has no parallelism at all. This may be the cost of maintaining parallelism or the result of external factors since the tests were not conducted in a vacuum. For small changes, improvements are less significant. ----- Other significant findings/solutions included to this PR: * feat: Investigate and fix issue with wrong CPU count for containers (#623) So, I found that `nproc` always shows how many CPUs available is. K8s "limits" and docker `--cpus` are throttling mechanisms, which do not hide the visibility of all cores. There are a few workarounds, but IMO, it is better to implement checks for that than do them >Workaround for docker - set `--cpuset-cpus` >Workaraund for K8s - somehow deal with [kubelet static CPU management policy](https://kubernetes.io/docs/tasks/administer-cluster/cpu-management-policies/#cpu-management-policies), as [recommend in Reddit](https://news.ycombinator.com/item?id=25224714) * Send all "colorify" logs through stderr, as make able to add user-facing-logs in functions that also need to return same value to the function-caller. Needed for `common::get_cpu_num` err_msg show up ------ * Count --parallelism-ci-cpu-cores only in edge-cases Details: https://github.com/antonbabenko/pre-commit-terraform/pull/620#pullrequestreview-1883528728 --------- Co-authored-by: George L. Yermulnik --- .../bug_report_local_install.md | 13 +- Dockerfile | 1 + README.md | 65 ++++++ hooks/_common.sh | 195 ++++++++++++++++-- hooks/terraform_checkov.sh | 5 +- hooks/terraform_fmt.sh | 5 +- hooks/terraform_providers_lock.sh | 6 +- hooks/terraform_tflint.sh | 5 +- hooks/terraform_tfsec.sh | 5 +- hooks/terraform_trivy.sh | 5 +- hooks/terraform_validate.sh | 8 +- hooks/terragrunt_fmt.sh | 5 +- hooks/terragrunt_validate.sh | 5 +- hooks/terrascan.sh | 5 +- hooks/tfupdate.sh | 5 +- 15 files changed, 292 insertions(+), 41 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report_local_install.md b/.github/ISSUE_TEMPLATE/bug_report_local_install.md index 544dc03ed..bfccad8ad 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_local_install.md +++ b/.github/ISSUE_TEMPLATE/bug_report_local_install.md @@ -81,16 +81,17 @@ pre-commit --version 2>/dev/null || echo "pre-commit SKIPPE terraform --version | head -n 1 2>/dev/null || echo "terraform SKIPPED" python --version 2>/dev/null || echo "python SKIPPED" python3 --version 2>/dev/null || echo "python3 SKIPPED" -echo -n "checkov " && checkov --version 2>/dev/null || echo "checkov SKIPPED" +echo -n "checkov " && checkov --version 2>/dev/null || echo "SKIPPED" infracost --version 2>/dev/null || echo "infracost SKIPPED" terraform-docs --version 2>/dev/null || echo "terraform-docs SKIPPED" terragrunt --version 2>/dev/null || echo "terragrunt SKIPPED" -echo -n "terrascan " && terrascan version 2>/dev/null || echo "terrascan SKIPPED" +echo -n "terrascan " && terrascan version 2>/dev/null || echo "SKIPPED" tflint --version 2>/dev/null || echo "tflint SKIPPED" -echo -n "tfsec " && tfsec --version 2>/dev/null || echo "tfsec SKIPPED" -echo -n "trivy " && trivy --version 2>/dev/null || echo "tfsec SKIPPED" -echo -n "tfupdate " && tfupdate --version 2>/dev/null || echo "tfupdate SKIPPED" -echo -n "hcledit " && hcledit version 2>/dev/null || echo "hcledit SKIPPED" +echo -n "tfsec " && tfsec --version 2>/dev/null || echo "SKIPPED" +echo -n "trivy " && trivy --version 2>/dev/null || echo "SKIPPED" +echo -n "tfupdate " && tfupdate --version 2>/dev/null || echo "SKIPPED" +echo -n "hcledit " && hcledit version 2>/dev/null || echo "SKIPPED" +echo -n "flock " && flock --version 2>/dev/null || echo "SKIPPED" EOF --> diff --git a/Dockerfile b/Dockerfile index 83d0bc05b..7809f9f9e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -173,6 +173,7 @@ RUN . /.env && \ RUN . /.env && \ F=tools_versions_info && \ pre-commit --version >> $F && \ + echo "flock $(flock 2>&1 | head -n 1)" >> $F && \ ./terraform --version | head -n 1 >> $F && \ (if [ "$CHECKOV_VERSION" != "false" ]; then echo "checkov $(checkov --version)" >> $F; else echo "checkov SKIPPED" >> $F ; fi) && \ (if [ "$INFRACOST_VERSION" != "false" ]; then echo "$(./infracost --version)" >> $F; else echo "infracost SKIPPED" >> $F ; fi) && \ diff --git a/README.md b/README.md index dcafd39e1..df8c4d0b5 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ If you are using `pre-commit-terraform` already or want to support its developme * [All hooks: Usage of environment variables in `--args`](#all-hooks-usage-of-environment-variables-in---args) * [All hooks: Set env vars inside hook at runtime](#all-hooks-set-env-vars-inside-hook-at-runtime) * [All hooks: Disable color output](#all-hooks-disable-color-output) + * [Many hooks: Parallelism](#many-hooks-parallelism) * [checkov (deprecated) and terraform\_checkov](#checkov-deprecated-and-terraform_checkov) * [infracost\_breakdown](#infracost_breakdown) * [terraform\_docs](#terraform_docs) @@ -338,6 +339,70 @@ To disable color output for all hooks, set `PRE_COMMIT_COLOR=never` var. Eg: PRE_COMMIT_COLOR=never pre-commit run ``` +### Many hooks: Parallelism + +> All, except deprecated hooks: `checkov`, `terraform_docs_replace` and hooks which can't be paralleled this way: `infracost_breakdown`, `terraform_wrapper_module_for_each`. +> Also, there's a chance that parallelism have no effect on `terragrunt_fmt` and `terragrunt_validate` hooks + +By default, parallelism is set to `number of logical CPUs - 1`. +If you'd like to disable parallelism, set it to `1` + +```yaml +- id: terragrunt_validate + args: + - --hook-config=--parallelism-limit=1 +``` + +In the same way you can set it to any positive integer. + +If you'd like to set parallelism value relative to number of CPU logical cores - provide valid Bash arithmetic expression and use `CPU` as a reference to the number of CPU logical cores + + +```yaml +- id: terraform_providers_lock + args: + - --hook-config=--parallelism-limit=CPU*4 +``` + +> [!TIP] +>
Info useful for parallelism fine-tunning +> +>
+> Tests below were run on repo with 45 Terraform dirs on laptop with 16 CPUs, SSD and 1Gbit/s network. Laptop was slightly used in the process. +> +> Observed results may vary greatly depending on your repo structure, machine characteristics and their usage. +> +> If during fine-tuning you'll find that your results are very different from provided below and you think that this data could help someone else - feel free to send PR. +> +> +> | Hook | Most used resource | Comparison of optimization results / Notes | +> | ------------------------------------------------------------------------------ | ---------------------------------- | --------------------------------------------------------------- | +> | terraform_checkov | CPU heavy | - | +> | terraform_fmt | CPU heavy | - | +> | terraform_providers_lock (3 platforms,
`--mode=always-regenerate-lockfile`) | Network & Disk heavy | `defaults (CPU-1)` - 3m 39s; `CPU*2` - 3m 19s; `CPU*4` - 2m 56s | +> | terraform_tflint | CPU heavy | - | +> | terraform_tfsec | CPU heavy | - | +> | terraform_trivy | CPU moderate | `defaults (CPU-1)` - 32s; `CPU*2` - 30s; `CPU*4` - 31s | +> | terraform_validate (t validate only) | CPU heavy | - | +> | terraform_validate (t init + t validate) | Network & Disk heavy, CPU moderate | `defaults (CPU-1)` - 1m 30s; `CPU*2` - 1m 25s; `CPU*4` - 1m 41s | +> | terragrunt_fmt | CPU heavy | N/A? need more info from TG users | +> | terragrunt_validate | CPU heavy | N/A? need more info from TG users | +> | terrascan | CPU moderate-heavy | `defaults (CPU-1)` - 8s; `CPU*2` - 6s | +> | tfupdate | Disk/Network? | too quick in any settings. More info needed | +> +> +>
+ + + +```yaml +args: + - --hook-config=--parallelism-ci-cpu-cores=N +``` + +If you don't see code above in your `pre-commit-config.yaml` or logs - you don't need it. +`--parallelism-ci-cpu-cores` used only in edge cases and is ignored in other situations. Check out its usage in [hooks/_common.sh](hooks/_common.sh) + ### checkov (deprecated) and terraform_checkov > `checkov` hook is deprecated, please use `terraform_checkov`. diff --git a/hooks/_common.sh b/hooks/_common.sh index 31be98100..730848997 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -170,6 +170,75 @@ function common::is_hook_run_on_whole_repo { fi } +####################################################################### +# Get the number of CPU logical cores available for pre-commit to use +# Arguments: +# parallelism_ci_cpu_cores (string) Used in edge cases when number of +# CPU cores can't be derived automatically +# Outputs: +# Returns number of CPU logical cores, rounded down to nearest integer +####################################################################### +function common::get_cpu_num { + local -r parallelism_ci_cpu_cores=$1 + + local millicpu + + if [[ -f /sys/fs/cgroup/cpu/cpu.cfs_quota_us ]]; then + # Inside K8s pod or DinD in K8s + millicpu=$(< /sys/fs/cgroup/cpu/cpu.cfs_quota_us) + + if [[ $millicpu -eq -1 ]]; then + # K8s no limits or in DinD + if [[ -n $parallelism_ci_cpu_cores ]]; then + if [[ ! $parallelism_ci_cpu_cores =~ ^[[:digit:]]+$ ]]; then + common::colorify "yellow" "--parallelism-ci-cpu-cores set to" \ + "'$parallelism_ci_cpu_cores' which is not a positive integer.\n" \ + "To avoid possible harm, parallelism is disabled.\n" \ + "To re-enable it, change corresponding value in config to positive integer" + + echo 1 + return + fi + + echo "$parallelism_ci_cpu_cores" + return + fi + + common::colorify "yellow" "Unable to derive number of available CPU cores.\n" \ + "Running inside K8s pod without limits or inside DinD without limits propagation.\n" \ + "To avoid possible harm, parallelism is disabled.\n" \ + "To re-enable it, set corresponding limits, or set the following for the current hook:\n" \ + " args:\n" \ + " - --hook-config=--parallelism-ci-cpu-cores=N\n" \ + "where N is the number of CPU cores to allocate to pre-commit." + + echo 1 + return + fi + + echo $((millicpu / 1000)) + return + fi + + if [[ -f /sys/fs/cgroup/cpu.max ]]; then + # Inside Linux (Docker?) container + millicpu=$(cut -d' ' -f1 /sys/fs/cgroup/cpu.max) + + if [[ $millicpu == max ]]; then + # No limits + nproc 2> /dev/null || echo 1 + return + fi + + echo $((millicpu / 1000)) + return + fi + + # On host machine or any other case + # `nproc` - Linux/FreeBSD, `sysctl -n hw.ncpu` - macOS/BSD, `echo 1` - fallback + nproc 2> /dev/null || sysctl -n hw.ncpu 2> /dev/null || echo 1 +} + ####################################################################### # Hook execution boilerplate logic which is common to hooks, that run # on per dir basis. @@ -219,10 +288,16 @@ function common::per_dir_hook { # Lookup hook-config for modifiers that impact common behavior local change_dir_in_unique_part=false + + local parallelism_limit IFS=";" read -r -a configs <<< "${HOOK_CONFIG[*]}" for c in "${configs[@]}"; do IFS="=" read -r -a config <<< "$c" - key=${config[0]} + + # $hook_config receives string like '--foo=bar; --baz=4;' etc. + # It gets split by `;` into array, which we're parsing here ('--foo=bar' ' --baz=4') + # Next line removes leading spaces, to support >1 `--hook-config` args + key="${config[0]## }" value=${config[1]} case $key in @@ -233,32 +308,82 @@ function common::per_dir_hook { change_dir_in_unique_part="delegate_chdir" fi ;; + --parallelism-limit) + # this flag will limit the number of parallel processes + parallelism_limit="$value" + ;; + --parallelism-ci-cpu-cores) + # Used in edge cases when number of CPU cores can't be derived automatically + parallelism_ci_cpu_cores="$value" + ;; esac done + CPU=$(common::get_cpu_num "$parallelism_ci_cpu_cores") + # parallelism_limit can include reference to 'CPU' variable + local parallelism_disabled=false + + if [[ ! $parallelism_limit ]]; then + # Could evaluate to 0 + parallelism_limit=$((CPU - 1)) + elif [[ $parallelism_limit -eq 1 ]]; then + parallelism_disabled=true + else + # Could evaluate to <1 + parallelism_limit=$((parallelism_limit)) + fi + + if [[ $parallelism_limit -lt 1 ]]; then + # Suppress warning for edge cases when only 1 CPU available or + # when `--parallelism-ci-cpu-cores=1` and `--parallelism_limit` unset + if [[ $CPU -ne 1 ]]; then + + common::colorify "yellow" "Observed Parallelism limit '$parallelism_limit'." \ + "To avoid possible harm, parallelism set to '1'" + fi + + parallelism_limit=1 + parallelism_disabled=true + fi + + local pids=() + + mapfile -t dir_paths_unique < <(echo "${dir_paths[@]}" | tr ' ' '\n' | sort -u) + local length=${#dir_paths_unique[@]} + local last_index=$((${#dir_paths_unique[@]} - 1)) + + local final_exit_code=0 # preserve errexit status shopt -qo errexit && ERREXIT_IS_SET=true # allow hook to continue if exit_code is greater than 0 set +e - local final_exit_code=0 - - # run hook for each path - for dir_path in $(echo "${dir_paths[*]}" | tr ' ' '\n' | sort -u); do - dir_path="${dir_path//__REPLACED__SPACE__/ }" + # run hook for each path in parallel + for ((i = 0; i < length; i++)); do + dir_path="${dir_paths_unique[$i]//__REPLACED__SPACE__/ }" + { + if [[ $change_dir_in_unique_part == false ]]; then + pushd "$dir_path" > /dev/null + fi - if [[ $change_dir_in_unique_part == false ]]; then - pushd "$dir_path" > /dev/null || continue - fi + per_dir_hook_unique_part "$dir_path" "$change_dir_in_unique_part" "$parallelism_disabled" "${args[@]}" + } & + pids+=("$!") - per_dir_hook_unique_part "$dir_path" "$change_dir_in_unique_part" "${args[@]}" + if [[ $parallelism_disabled == true ]] || + [[ $i -ne 0 && $((i % parallelism_limit)) -eq 0 ]] || # don't stop on first iteration when parallelism_limit>1 + [[ $i -eq $last_index ]]; then - local exit_code=$? - if [ $exit_code -ne 0 ]; then - final_exit_code=$exit_code - fi + for pid in "${pids[@]}"; do + # Get the exit code from the background process + local exit_code=0 + wait "$pid" || exit_code=$? - if [[ $change_dir_in_unique_part == false ]]; then - popd > /dev/null + if [ $exit_code -ne 0 ]; then + final_exit_code=$exit_code + fi + done + # Reset pids for next iteration + unset pids fi done @@ -291,14 +416,15 @@ function common::colorify { # Params start # local COLOR="${!1}" - local -r TEXT=$2 + shift + local -r TEXT="$*" # Params end # if [ "$PRE_COMMIT_COLOR" = "never" ]; then COLOR=$RESET fi - echo -e "${COLOR}${TEXT}${RESET}" + echo -e "${COLOR}${TEXT}${RESET}" >&2 } ####################################################################### @@ -307,8 +433,11 @@ function common::colorify { # command_name (string) command that will tun after successful init # dir_path (string) PATH to dir relative to git repo root. # Can be used in error logging +# parallelism_disabled (bool) if true - skip lock mechanism # Globals (init and populate): # TF_INIT_ARGS (array) arguments for `terraform init` command +# TF_PLUGIN_CACHE_DIR (string) user defined env var with name of the directory +# which can't be R/W concurrently # Outputs: # If failed - print out terraform init output ####################################################################### @@ -316,6 +445,7 @@ function common::colorify { function common::terraform_init { local -r command_name=$1 local -r dir_path=$2 + local -r parallelism_disabled=$3 local exit_code=0 local init_output @@ -325,9 +455,32 @@ function common::terraform_init { TF_INIT_ARGS+=("-no-color") fi - if [ ! -d .terraform/modules ] || [ ! -d .terraform/providers ]; then - init_output=$(terraform init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) - exit_code=$? + recreate_modules=$([[ ! -d .terraform/modules ]] && echo true || echo false) + recreate_providers=$([[ ! -d .terraform/providers ]] && echo true || echo false) + + if [[ $recreate_modules == true || $recreate_providers == true ]]; then + # Plugin cache dir can't be written concurrently or read during write + # https://github.com/hashicorp/terraform/issues/31964 + if [[ -z $TF_PLUGIN_CACHE_DIR || $parallelism_disabled == true ]]; then + init_output=$(terraform init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) + exit_code=$? + else + # Locking just doesn't work, and the below works quicker instead. Details: + # https://github.com/hashicorp/terraform/issues/31964#issuecomment-1939869453 + for i in {1..10}; do + init_output=$(terraform init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) + exit_code=$? + + if [ $exit_code -eq 0 ]; then + break + fi + sleep 1 + + common::colorify "green" "Race condition detected. Retrying 'terraform init' command [retry $i]: $dir_path." + [[ $recreate_modules == true ]] && rm -rf .terraform/modules + [[ $recreate_providers == true ]] && rm -rf .terraform/providers + done + fi if [ $exit_code -ne 0 ]; then common::colorify "red" "'terraform init' failed, '$command_name' skipped: $dir_path" diff --git a/hooks/terraform_checkov.sh b/hooks/terraform_checkov.sh index 03ad208c6..a9603afd9 100755 --- a/hooks/terraform_checkov.sh +++ b/hooks/terraform_checkov.sh @@ -34,6 +34,7 @@ function main { # change_dir_in_unique_part (string/false) Modifier which creates # possibilities to use non-common chdir strategies. # Availability depends on hook. +# parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior # Outputs: # If failed - print out hook checks status @@ -43,7 +44,9 @@ function per_dir_hook_unique_part { local -r dir_path="$1" # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" - shift 2 + # shellcheck disable=SC2034 # Unused var. + local -r parallelism_disabled="$3" + shift 3 local -a -r args=("$@") checkov -d . "${args[@]}" diff --git a/hooks/terraform_fmt.sh b/hooks/terraform_fmt.sh index bb0b327d1..727635caa 100755 --- a/hooks/terraform_fmt.sh +++ b/hooks/terraform_fmt.sh @@ -31,6 +31,7 @@ function main { # change_dir_in_unique_part (string/false) Modifier which creates # possibilities to use non-common chdir strategies. # Availability depends on hook. +# parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior # Outputs: # If failed - print out hook checks status @@ -40,7 +41,9 @@ function per_dir_hook_unique_part { local -r dir_path="$1" # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" - shift 2 + # shellcheck disable=SC2034 # Unused var. + local -r parallelism_disabled="$3" + shift 3 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/terraform_providers_lock.sh b/hooks/terraform_providers_lock.sh index d85a794f8..bf2aa2084 100755 --- a/hooks/terraform_providers_lock.sh +++ b/hooks/terraform_providers_lock.sh @@ -85,6 +85,7 @@ function lockfile_contains_all_needed_sha { # change_dir_in_unique_part (string/false) Modifier which creates # possibilities to use non-common chdir strategies. # Availability depends on hook. +# parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior # Outputs: # If failed - print out hook checks status @@ -93,7 +94,8 @@ function per_dir_hook_unique_part { local -r dir_path="$1" # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" - shift 2 + local -r parallelism_disabled="$3" + shift 3 local -a -r args=("$@") local platforms_count=0 @@ -136,7 +138,7 @@ function per_dir_hook_unique_part { common::colorify "yellow" "DEPRECATION NOTICE: We introduced '--mode' flag for this hook. Check migration instructions at https://github.com/antonbabenko/pre-commit-terraform#terraform_providers_lock " - common::terraform_init 'terraform providers lock' "$dir_path" || { + common::terraform_init 'terraform providers lock' "$dir_path" "$parallelism_disabled" || { exit_code=$? return $exit_code } diff --git a/hooks/terraform_tflint.sh b/hooks/terraform_tflint.sh index f26201a3c..9c8373676 100755 --- a/hooks/terraform_tflint.sh +++ b/hooks/terraform_tflint.sh @@ -44,6 +44,7 @@ function main { # change_dir_in_unique_part (string/false) Modifier which creates # possibilities to use non-common chdir strategies. # Availability depends on hook. +# parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior # Outputs: # If failed - print out hook checks status @@ -51,7 +52,9 @@ function main { function per_dir_hook_unique_part { local -r dir_path="$1" local -r change_dir_in_unique_part="$2" - shift 2 + # shellcheck disable=SC2034 # Unused var. + local -r parallelism_disabled="$3" + shift 3 local -a -r args=("$@") if [ "$change_dir_in_unique_part" == "delegate_chdir" ]; then diff --git a/hooks/terraform_tfsec.sh b/hooks/terraform_tfsec.sh index d05ed4241..75966bf8c 100755 --- a/hooks/terraform_tfsec.sh +++ b/hooks/terraform_tfsec.sh @@ -37,6 +37,7 @@ function main { # change_dir_in_unique_part (string/false) Modifier which creates # possibilities to use non-common chdir strategies. # Availability depends on hook. +# parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior # Outputs: # If failed - print out hook checks status @@ -46,7 +47,9 @@ function per_dir_hook_unique_part { local -r dir_path="$1" # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" - shift 2 + # shellcheck disable=SC2034 # Unused var. + local -r parallelism_disabled="$3" + shift 3 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/terraform_trivy.sh b/hooks/terraform_trivy.sh index dc205601a..fd9a3203a 100755 --- a/hooks/terraform_trivy.sh +++ b/hooks/terraform_trivy.sh @@ -29,6 +29,7 @@ function main { # change_dir_in_unique_part (string/false) Modifier which creates # possibilities to use non-common chdir strategies. # Availability depends on hook. +# parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior # Outputs: # If failed - print out hook checks status @@ -38,7 +39,9 @@ function per_dir_hook_unique_part { local -r dir_path="$1" # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" - shift 2 + # shellcheck disable=SC2034 # Unused var. + local -r parallelism_disabled="$3" + shift 3 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/terraform_validate.sh b/hooks/terraform_validate.sh index 2f3795a72..9a6c50fce 100755 --- a/hooks/terraform_validate.sh +++ b/hooks/terraform_validate.sh @@ -75,6 +75,7 @@ function match_validate_errors { # change_dir_in_unique_part (string/false) Modifier which creates # possibilities to use non-common chdir strategies. # Availability depends on hook. +# parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior # Outputs: # If failed - print out hook checks status @@ -83,7 +84,8 @@ function per_dir_hook_unique_part { local -r dir_path="$1" # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" - shift 2 + local -r parallelism_disabled="$3" + shift 3 local -a -r args=("$@") local exit_code @@ -121,7 +123,7 @@ function per_dir_hook_unique_part { # In case `terraform validate` failed to execute # - check is simple `terraform init` will help - common::terraform_init 'terraform validate' "$dir_path" || { + common::terraform_init 'terraform validate' "$dir_path" "$parallelism_disabled" || { exit_code=$? return $exit_code } @@ -150,7 +152,7 @@ function per_dir_hook_unique_part { common::colorify "yellow" "Re-validating: $dir_path" - common::terraform_init 'terraform validate' "$dir_path" || { + common::terraform_init 'terraform validate' "$dir_path" "$parallelism_disabled" || { exit_code=$? return $exit_code } diff --git a/hooks/terragrunt_fmt.sh b/hooks/terragrunt_fmt.sh index 7c78b9233..1effbf47b 100755 --- a/hooks/terragrunt_fmt.sh +++ b/hooks/terragrunt_fmt.sh @@ -27,6 +27,7 @@ function main { # change_dir_in_unique_part (string/false) Modifier which creates # possibilities to use non-common chdir strategies. # Availability depends on hook. +# parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior # Outputs: # If failed - print out hook checks status @@ -36,7 +37,9 @@ function per_dir_hook_unique_part { local -r dir_path="$1" # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" - shift 2 + # shellcheck disable=SC2034 # Unused var. + local -r parallelism_disabled="$3" + shift 3 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/terragrunt_validate.sh b/hooks/terragrunt_validate.sh index 15e203be7..8fafada0a 100755 --- a/hooks/terragrunt_validate.sh +++ b/hooks/terragrunt_validate.sh @@ -27,6 +27,7 @@ function main { # change_dir_in_unique_part (string/false) Modifier which creates # possibilities to use non-common chdir strategies. # Availability depends on hook. +# parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior # Outputs: # If failed - print out hook checks status @@ -36,7 +37,9 @@ function per_dir_hook_unique_part { local -r dir_path="$1" # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" - shift 2 + # shellcheck disable=SC2034 # Unused var. + local -r parallelism_disabled="$3" + shift 3 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/terrascan.sh b/hooks/terrascan.sh index ac040b93e..0c1447bd4 100755 --- a/hooks/terrascan.sh +++ b/hooks/terrascan.sh @@ -27,6 +27,7 @@ function main { # change_dir_in_unique_part (string/false) Modifier which creates # possibilities to use non-common chdir strategies. # Availability depends on hook. +# parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior # Outputs: # If failed - print out hook checks status @@ -36,7 +37,9 @@ function per_dir_hook_unique_part { local -r dir_path="$1" # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" - shift 2 + # shellcheck disable=SC2034 # Unused var. + local -r parallelism_disabled="$3" + shift 3 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/tfupdate.sh b/hooks/tfupdate.sh index d9a482917..9397cfe94 100755 --- a/hooks/tfupdate.sh +++ b/hooks/tfupdate.sh @@ -37,6 +37,7 @@ function main { # change_dir_in_unique_part (string/false) Modifier which creates # possibilities to use non-common chdir strategies. # Availability depends on hook. +# parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior # Outputs: # If failed - print out hook checks status @@ -46,7 +47,9 @@ function per_dir_hook_unique_part { local -r dir_path="$1" # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" - shift 2 + # shellcheck disable=SC2034 # Unused var. + local -r parallelism_disabled="$3" + shift 3 local -a -r args=("$@") # pass the arguments to hook From 8056513029dfeac44d57a2c49872423109173f53 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 17 Feb 2024 20:42:10 +0000 Subject: [PATCH 009/293] chore(release): version 1.87.0 [skip ci] # [1.87.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.86.1...v1.87.0) (2024-02-17) ### Features * Add parallelism to major chunk of hooks. Check `Parallelism` section in README ([#620](https://github.com/antonbabenko/pre-commit-terraform/issues/620)) ([6c6eca4](https://github.com/antonbabenko/pre-commit-terraform/commit/6c6eca463a74fa2608cb3de3e03873765d46252f)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29e544a99..28e78c416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.87.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.86.1...v1.87.0) (2024-02-17) + + +### Features + +* Add parallelism to major chunk of hooks. Check `Parallelism` section in README ([#620](https://github.com/antonbabenko/pre-commit-terraform/issues/620)) ([6c6eca4](https://github.com/antonbabenko/pre-commit-terraform/commit/6c6eca463a74fa2608cb3de3e03873765d46252f)) + ## [1.86.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.86.0...v1.86.1) (2024-02-16) From 5deea8c9f40f348eebd250a6fb96fcef3426b671 Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Mon, 19 Feb 2024 14:46:55 +0200 Subject: [PATCH 010/293] [`hooks/_common.sh`] Replace `mapfile` to support older Bash --- hooks/_common.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index 730848997..576013e07 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -348,7 +348,9 @@ function common::per_dir_hook { local pids=() - mapfile -t dir_paths_unique < <(echo "${dir_paths[@]}" | tr ' ' '\n' | sort -u) + while IFS= read -r _line; do + dir_paths_unique+=("$_line") + done < <(printf '%s\n' "${dir_paths[@]}" | sort -u) local length=${#dir_paths_unique[@]} local last_index=$((${#dir_paths_unique[@]} - 1)) From bf354a2b11870d84ab147928c483eb4e967fe5d1 Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Mon, 19 Feb 2024 15:05:18 +0200 Subject: [PATCH 011/293] Declare `dir_paths_unique` as local and as array --- hooks/_common.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/hooks/_common.sh b/hooks/_common.sh index 576013e07..c9e1390f8 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -347,6 +347,7 @@ function common::per_dir_hook { fi local pids=() + local dir_paths_unique=() while IFS= read -r _line; do dir_paths_unique+=("$_line") From a272c3373e388da02ce7a9e7bf8def9f025b99f3 Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Mon, 19 Feb 2024 15:20:44 +0200 Subject: [PATCH 012/293] Address PR discussion by creating array in a one-liner --- hooks/_common.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index c9e1390f8..3b4bd79d0 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -347,11 +347,10 @@ function common::per_dir_hook { fi local pids=() - local dir_paths_unique=() - while IFS= read -r _line; do - dir_paths_unique+=("$_line") - done < <(printf '%s\n' "${dir_paths[@]}" | sort -u) + # shellcheck disable=SC2207 + local -a dir_paths_unique=("$(printf '%s\n' "${dir_paths[@]}" | sort -u)") + local length=${#dir_paths_unique[@]} local last_index=$((${#dir_paths_unique[@]} - 1)) From 310cf281de64ddd33fae339d41853ebdb7373dcb Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 19 Feb 2024 15:31:06 +0200 Subject: [PATCH 013/293] Apply suggestions from code review --- hooks/_common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index 3b4bd79d0..20555bca9 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -349,7 +349,7 @@ function common::per_dir_hook { local pids=() # shellcheck disable=SC2207 - local -a dir_paths_unique=("$(printf '%s\n' "${dir_paths[@]}" | sort -u)") + local -a dir_paths_unique=( $(printf '%s\n' "${dir_paths[@]}" | sort -u) ) local length=${#dir_paths_unique[@]} local last_index=$((${#dir_paths_unique[@]} - 1)) From 2f9dfe984078451b814be8a98ced2727fc128634 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 19 Feb 2024 15:31:36 +0200 Subject: [PATCH 014/293] Apply suggestions from code review --- hooks/_common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index 20555bca9..5f5a345be 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -348,7 +348,7 @@ function common::per_dir_hook { local pids=() - # shellcheck disable=SC2207 + # shellcheck disable=SC2207 # More readable way local -a dir_paths_unique=( $(printf '%s\n' "${dir_paths[@]}" | sort -u) ) local length=${#dir_paths_unique[@]} From ebd70793b15d81271081d59169606581a5d3c525 Mon Sep 17 00:00:00 2001 From: pre-commit Date: Mon, 19 Feb 2024 13:32:21 +0000 Subject: [PATCH 015/293] pre-commit fixes --- hooks/_common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index 5f5a345be..d6dbbb272 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -349,7 +349,7 @@ function common::per_dir_hook { local pids=() # shellcheck disable=SC2207 # More readable way - local -a dir_paths_unique=( $(printf '%s\n' "${dir_paths[@]}" | sort -u) ) + local -a dir_paths_unique=($(printf '%s\n' "${dir_paths[@]}" | sort -u)) local length=${#dir_paths_unique[@]} local last_index=$((${#dir_paths_unique[@]} - 1)) From 24ac3cf8b89a47b5d0cc38d50b73cfcd0f0426e1 Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Mon, 19 Feb 2024 15:38:20 +0200 Subject: [PATCH 016/293] Address remark about array elements splitting --- hooks/_common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index 3b4bd79d0..8f75a1b43 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -349,7 +349,7 @@ function common::per_dir_hook { local pids=() # shellcheck disable=SC2207 - local -a dir_paths_unique=("$(printf '%s\n' "${dir_paths[@]}" | sort -u)") + local -a dir_paths_unique=($(printf '%s\n' "${dir_paths[@]}" | sort -u)) local length=${#dir_paths_unique[@]} local last_index=$((${#dir_paths_unique[@]} - 1)) From 01ab3f0c68abda9f5799647f783c91c3d1fa3a90 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 19 Feb 2024 15:56:06 +0200 Subject: [PATCH 017/293] fix: Replace `mapfile` to support Bash 3.2.57 pre-installed in macOS (#628) Follow-up release fix for https://github.com/antonbabenko/pre-commit-terraform/pull/627 --- .github/ISSUE_TEMPLATE/bug_report_local_install.md | 1 - Dockerfile | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report_local_install.md b/.github/ISSUE_TEMPLATE/bug_report_local_install.md index bfccad8ad..c58591c28 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_local_install.md +++ b/.github/ISSUE_TEMPLATE/bug_report_local_install.md @@ -91,7 +91,6 @@ echo -n "tfsec " && tfsec --version 2>/dev/null || echo "SKIPPED" echo -n "trivy " && trivy --version 2>/dev/null || echo "SKIPPED" echo -n "tfupdate " && tfupdate --version 2>/dev/null || echo "SKIPPED" echo -n "hcledit " && hcledit version 2>/dev/null || echo "SKIPPED" -echo -n "flock " && flock --version 2>/dev/null || echo "SKIPPED" EOF --> diff --git a/Dockerfile b/Dockerfile index 7809f9f9e..83d0bc05b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -173,7 +173,6 @@ RUN . /.env && \ RUN . /.env && \ F=tools_versions_info && \ pre-commit --version >> $F && \ - echo "flock $(flock 2>&1 | head -n 1)" >> $F && \ ./terraform --version | head -n 1 >> $F && \ (if [ "$CHECKOV_VERSION" != "false" ]; then echo "checkov $(checkov --version)" >> $F; else echo "checkov SKIPPED" >> $F ; fi) && \ (if [ "$INFRACOST_VERSION" != "false" ]; then echo "$(./infracost --version)" >> $F; else echo "infracost SKIPPED" >> $F ; fi) && \ From 385b5422c8e1102401696f1a1e8c5fbbea6f75f2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 19 Feb 2024 13:56:44 +0000 Subject: [PATCH 018/293] chore(release): version 1.87.1 [skip ci] ## [1.87.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.87.0...v1.87.1) (2024-02-19) ### Bug Fixes * Replace `mapfile` to support Bash 3.2.57 pre-installed in macOS ([#628](https://github.com/antonbabenko/pre-commit-terraform/issues/628)) ([01ab3f0](https://github.com/antonbabenko/pre-commit-terraform/commit/01ab3f0c68abda9f5799647f783c91c3d1fa3a90)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e78c416..54eb1179c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.87.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.87.0...v1.87.1) (2024-02-19) + + +### Bug Fixes + +* Replace `mapfile` to support Bash 3.2.57 pre-installed in macOS ([#628](https://github.com/antonbabenko/pre-commit-terraform/issues/628)) ([01ab3f0](https://github.com/antonbabenko/pre-commit-terraform/commit/01ab3f0c68abda9f5799647f783c91c3d1fa3a90)) + # [1.87.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.86.1...v1.87.0) (2024-02-17) From 77940fd1fbbe9d3ea70306f396e1d8a13534d51d Mon Sep 17 00:00:00 2001 From: Andrii Veklychev Date: Thu, 22 Feb 2024 22:47:05 +0100 Subject: [PATCH 019/293] feat: Add `terragrunt_providers_lock` hook (#632) --- .pre-commit-hooks.yaml | 8 ++++ README.md | 24 ++++++++++ hooks/terragrunt_providers_lock.sh | 70 ++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100755 hooks/terragrunt_providers_lock.sh diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 40c2cb072..0942ddfa7 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -85,6 +85,14 @@ files: (\.hcl)$ exclude: \.terraform/.*$ +- id: terragrunt_providers_lock + name: Terragrunt providers lock + description: Updates provider signatures in dependency lock files using terragrunt. + entry: hooks/terragrunt_providers_lock.sh + language: script + files: (terragrunt|\.terraform\.lock)\.hcl$ + exclude: \.(terraform/.*|terragrunt-cache)$ + - id: terraform_tfsec name: Terraform validate with tfsec (deprecated, use "terraform_trivy") description: Static analysis of Terraform templates to spot potential security issues. diff --git a/README.md b/README.md index df8c4d0b5..2c1c46163 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ If you are using `pre-commit-terraform` already or want to support its developme * [terraform\_wrapper\_module\_for\_each](#terraform_wrapper_module_for_each) * [terrascan](#terrascan) * [tfupdate](#tfupdate) + * [terragrunt\_providers\_lock](#terragrunt_providers_lock) * [Docker Usage](#docker-usage) * [File Permissions](#file-permissions) * [Download Terraform modules from private GitHub repositories](#download-terraform-modules-from-private-github-repositories) @@ -281,6 +282,7 @@ There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform | `terraform_validate` | Validates all Terraform configuration files. [Hook notes](#terraform_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_providers_lock` | Generates `.terraform.lock.hcl` files using [Terragrunt](https://github.com/gruntwork-io/terragrunt). | `terragrunt` | | `terraform_wrapper_module_for_each` | Generates Terraform 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 Terraform core, providers, and modules. [Hook notes](#tfupdate) | `tfupdate` | @@ -1058,6 +1060,28 @@ If the generated name is incorrect, set them by providing the `module-repo-short Check [`tfupdate` usage instructions](https://github.com/minamijoyo/tfupdate#usage) for other available options and usage examples. No need to pass `--recursive .` as it is added automatically. +### terragrunt_providers_lock + +> [!TIP] +> Use this hook only in infrastructure repos managed solely by `terragrunt` and do not mix with [`terraform_providers_lock`](#terraform_providers_lock) to avoid conflicts. + +> [!WARNING] +> Hook _may_ be very slow, because terragrunt invokes `t init` under the hood. + +Hook produces same results as [`terraform_providers_lock`](#terraform_providers_lock), but for terragrunt root modules. + +It invokes `terragrunt providers lock` under the hood and terragrunt [does its' own magic](https://terragrunt.gruntwork.io/docs/features/lock-file-handling/) for handling lock files. + + +```yaml +- id: terragrunt_providers_lock + name: Terragrunt providers lock + args: + - --args=-platform=darwin_arm64 + - --args=-platform=darwin_amd64 + - --args=-platform=linux_amd64 +``` + ## Docker Usage ### File Permissions diff --git a/hooks/terragrunt_providers_lock.sh b/hooks/terragrunt_providers_lock.sh new file mode 100755 index 000000000..f05a571c4 --- /dev/null +++ b/hooks/terragrunt_providers_lock.sh @@ -0,0 +1,70 @@ +#!/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 providers lock color already suppressed via PRE_COMMIT_COLOR=never + + # shellcheck disable=SC2153 # False positive + common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" +} + +####################################################################### +# 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. +# parallelism_disabled (bool) if true - skip lock mechanism +# 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" + # shellcheck disable=SC2034 # Unused var. + local -r parallelism_disabled="$3" + shift 3 + local -a -r args=("$@") + + # pass the arguments to hook + terragrunt providers lock "${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 providers lock "${args[@]}" + + # return exit code to common::per_dir_hook + local exit_code=$? + return $exit_code +} + +[ "${BASH_SOURCE[0]}" != "$0" ] || main "$@" From 4bf25029a7a45d7d69592250dd3c327a74d77631 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 22 Feb 2024 21:47:34 +0000 Subject: [PATCH 020/293] chore(release): version 1.88.0 [skip ci] # [1.88.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.87.1...v1.88.0) (2024-02-22) ### Features * Add `terragrunt_providers_lock` hook ([#632](https://github.com/antonbabenko/pre-commit-terraform/issues/632)) ([77940fd](https://github.com/antonbabenko/pre-commit-terraform/commit/77940fd1fbbe9d3ea70306f396e1d8a13534d51d)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54eb1179c..9f5a59da7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.88.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.87.1...v1.88.0) (2024-02-22) + + +### Features + +* Add `terragrunt_providers_lock` hook ([#632](https://github.com/antonbabenko/pre-commit-terraform/issues/632)) ([77940fd](https://github.com/antonbabenko/pre-commit-terraform/commit/77940fd1fbbe9d3ea70306f396e1d8a13534d51d)) + ## [1.87.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.87.0...v1.87.1) (2024-02-19) From dc65c4bae7963f7a65fc30e2137fb58093064024 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 26 Feb 2024 18:52:14 +0200 Subject: [PATCH 021/293] ci(docker): Add tests for arm64 (#637) --- .github/workflows/build-image-test.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 1b263ce46..9dfbda3c6 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -7,7 +7,12 @@ env: jobs: build: - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest] + arch: [amd64, arm64] + + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: @@ -61,7 +66,7 @@ jobs: # Can't build both platforms and use --load at the same time # https://github.com/docker/buildx/issues/59#issuecomment-1433097926 - name: Build Multi-arch docker-image - if: steps.changed-files-specific.outputs.any_changed == 'true' + if: steps.changed-files-specific.outputs.any_changed == 'true' && matrix.os == 'ubuntu-latest' && matrix.arch == 'amd64' uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 with: context: . From c29bdb12779331c9fc9c3e61846d914db4219126 Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Wed, 28 Feb 2024 19:35:29 +0530 Subject: [PATCH 022/293] docs: Way we can skip directories (#631) --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2c1c46163..2a0ffb9c9 100644 --- a/README.md +++ b/README.md @@ -861,10 +861,9 @@ To replicate functionality in `terraform_docs` hook: ```yaml - id: terraform_trivy - args: - - > - --args=--format json - --skip-dirs="**/.terragrunt-cache" + args: + - --args=--format=json + - --args=--skip-dirs="**/.terraform" ``` ### terraform_validate From cfe4477432b0f2ef7888382b1ffd7ac2de382e35 Mon Sep 17 00:00:00 2001 From: Maxime Brunet Date: Fri, 8 Mar 2024 00:42:36 +0000 Subject: [PATCH 023/293] docs(terraform_validate): Add note about cache and parallelism (#642) --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a0ffb9c9..2b32d3f8f 100644 --- a/README.md +++ b/README.md @@ -674,8 +674,8 @@ To replicate functionality in `terraform_docs` hook: * `only-check-is-current-lockfile-cross-platform` with [terraform_validate hook](#terraform_validate) - make up-to-date lockfile by adding/removing providers and only then check that lockfile has all required SHAs. - > **Important** - > Next `terraform_validate` flag requires additional dependency to be installed: `jq`. Also, it could run another slow and time consuming command - `terraform init` + > **Important** + > Next `terraform_validate` flag requires additional dependency to be installed: `jq`. Also, it could run another slow and time consuming command - `terraform init` ```yaml - id: terraform_validate @@ -868,6 +868,9 @@ To replicate functionality in `terraform_docs` hook: ### terraform_validate +> [!IMPORTANT] +> If you use [`TF_PLUGIN_CACHE_DIR`](https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache), we recommend enabling `--hook-config=--retry-once-with-cleanup=true` or disabling parallelism (`--hook-config=--parallelism-limit=1`) to avoid [race conditions when `terraform init` writes to it](https://github.com/hashicorp/terraform/issues/31964). + 1. `terraform_validate` supports custom arguments so you can pass supported `-no-color` or `-json` flags: ```yaml From f255b05feaace02f38822e3b53cf38c38e069115 Mon Sep 17 00:00:00 2001 From: antm-pp <132563905+antm-pp@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:30:40 +0100 Subject: [PATCH 024/293] fix(docker): Checkov installation silently fails on `docker build` in arm64. Workaround till issue will be fixed in `checkov` itself (#635) --------- Co-authored-by: George L. Yermulnik Co-authored-by: MaxymVlasov --- .github/.container-structure-test-config.yaml | 5 ++ Dockerfile | 74 +++++++++++++------ 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/.github/.container-structure-test-config.yaml b/.github/.container-structure-test-config.yaml index 60107655e..8cbf7e4de 100644 --- a/.github/.container-structure-test-config.yaml +++ b/.github/.container-structure-test-config.yaml @@ -15,6 +15,11 @@ commandTests: args: ["-version"] expectedOutput: ["^Terraform v([0-9]+\\.){2}[0-9]+\\non linux_amd64\\n$"] + - name: "gcc" + command: "gcc" + args: ["--version"] + expectedOutput: ["^gcc \\(Alpine 12\\."] + - name: "checkov" command: "checkov" args: ["--version"] diff --git a/Dockerfile b/Dockerfile index 83d0bc05b..242a45405 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,8 +17,10 @@ ARG PRE_COMMIT_VERSION=${PRE_COMMIT_VERSION:-latest} ARG TERRAFORM_VERSION=${TERRAFORM_VERSION:-latest} # 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 if [ ${PRE_COMMIT_VERSION} = "latest" ]; \ + then pip3 install --no-cache-dir pre-commit; \ + else pip3 install --no-cache-dir pre-commit==${PRE_COMMIT_VERSION}; \ + fi # Install terraform because pre-commit needs it RUN if [ "${TERRAFORM_VERSION}" = "latest" ]; then \ @@ -66,10 +68,16 @@ 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 \ + # cargo, gcc, git, musl-dev, rust and CARGO envvar required for compilation of rustworkx@0.13.2, no longer required once checkov version depends on rustworkx >0.14.0 + # https://github.com/bridgecrewio/checkov/pull/6045 + # gcc libffi-dev musl-dev required for compilation of cffi, until it contains musl aarch64 + export CARGO_NET_GIT_FETCH_WITH_CLI=true && \ + apk add --no-cache cargo=~1 gcc=~12 git=~2 libffi-dev=~3 libgcc=~12 musl-dev=~1 rust=~1 ; \ + if [ "$CHECKOV_VERSION" = "latest" ]; \ + then pip3 install --no-cache-dir checkov || exit 1; \ + else pip3 install --no-cache-dir checkov==${CHECKOV_VERSION} || exit 1; \ + fi; \ + apk del cargo gcc git libffi-dev musl-dev rust \ ) \ ; fi @@ -78,8 +86,10 @@ 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 \ + if [ "$INFRACOST_VERSION" = "latest" ]; \ + then curl -L "$(curl -s ${INFRACOST_RELEASES}/latest | grep -o -E -m 1 "https://.+?-${TARGETOS}-${TARGETARCH}.tar.gz")" > infracost.tgz; \ + else curl -L "$(curl -s ${INFRACOST_RELEASES} | grep -o -E "https://.+?v${INFRACOST_VERSION}/infracost-${TARGETOS}-${TARGETARCH}.tar.gz")" > infracost.tgz; \ + fi; \ ) && tar -xzf infracost.tgz && rm infracost.tgz && mv infracost-${TARGETOS}-${TARGETARCH} infracost \ ; fi @@ -88,8 +98,10 @@ 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 \ + if [ "$TERRAFORM_DOCS_VERSION" = "latest" ]; \ + then curl -L "$(curl -s ${TERRAFORM_DOCS_RELEASES}/latest | grep -o -E -m 1 "https://.+?-${TARGETOS}-${TARGETARCH}.tar.gz")" > terraform-docs.tgz; \ + else curl -L "$(curl -s ${TERRAFORM_DOCS_RELEASES} | grep -o -E "https://.+?v${TERRAFORM_DOCS_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz")" > terraform-docs.tgz; \ + fi; \ ) && tar -xzf terraform-docs.tgz terraform-docs && rm terraform-docs.tgz && chmod +x terraform-docs \ ; fi @@ -98,8 +110,10 @@ 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 \ + if [ "$TERRAGRUNT_VERSION" = "latest" ]; \ + then curl -L "$(curl -s ${TERRAGRUNT_RELEASES}/latest | grep -o -E -m 1 "https://.+?/terragrunt_${TARGETOS}_${TARGETARCH}")" > terragrunt; \ + else curl -L "$(curl -s ${TERRAGRUNT_RELEASES} | grep -o -E -m 1 "https://.+?v${TERRAGRUNT_VERSION}/terragrunt_${TARGETOS}_${TARGETARCH}")" > terragrunt; \ + fi; \ ) && chmod +x terragrunt \ ; fi @@ -112,8 +126,10 @@ RUN . /.env && \ 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 \ + if [ "$TERRASCAN_VERSION" = "latest" ]; \ + then curl -L "$(curl -s ${TERRASCAN_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${OS}_${ARCH}.tar.gz")" > terrascan.tar.gz; \ + else curl -L "$(curl -s ${TERRASCAN_RELEASES} | grep -o -E "https://.+?${TERRASCAN_VERSION}_${OS}_${ARCH}.tar.gz")" > terrascan.tar.gz; \ + fi; \ ) && tar -xzf terrascan.tar.gz terrascan && rm terrascan.tar.gz && \ ./terrascan init \ ; fi @@ -123,8 +139,10 @@ 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 \ + if [ "$TFLINT_VERSION" = "latest" ]; \ + then curl -L "$(curl -s ${TFLINT_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.zip")" > tflint.zip; \ + else curl -L "$(curl -s ${TFLINT_RELEASES} | grep -o -E "https://.+?/v${TFLINT_VERSION}/tflint_${TARGETOS}_${TARGETARCH}.zip")" > tflint.zip; \ + fi; \ ) && unzip tflint.zip && rm tflint.zip \ ; fi @@ -133,8 +151,10 @@ 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 \ + if [ "$TFSEC_VERSION" = "latest" ]; then \ + curl -L "$(curl -s ${TFSEC_RELEASES}/latest | grep -o -E -m 1 "https://.+?/tfsec-${TARGETOS}-${TARGETARCH}")" > tfsec; \ + else curl -L "$(curl -s ${TFSEC_RELEASES} | grep -o -E -m 1 "https://.+?v${TFSEC_VERSION}/tfsec-${TARGETOS}-${TARGETARCH}")" > tfsec; \ + fi; \ ) && chmod +x tfsec \ ; fi @@ -144,8 +164,10 @@ RUN . /.env && \ 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 \ + if [ "$TRIVY_VERSION" = "latest" ]; \ + then curl -L "$(curl -s ${TRIVY_RELEASES}/latest | grep -o -E -i -m 1 "https://.+?/trivy_.+?_${TARGETOS}-${ARCH}.tar.gz")" > trivy.tar.gz; \ + else curl -L "$(curl -s ${TRIVY_RELEASES} | grep -o -E -i -m 1 "https://.+?/v${TRIVY_VERSION}/trivy_.+?_${TARGETOS}-${ARCH}.tar.gz")" > trivy.tar.gz; \ + fi; \ ) && tar -xzf trivy.tar.gz trivy && rm trivy.tar.gz \ ; fi @@ -154,8 +176,10 @@ 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 \ + if [ "$TFUPDATE_VERSION" = "latest" ]; \ + then curl -L "$(curl -s ${TFUPDATE_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.tar.gz")" > tfupdate.tgz; \ + else curl -L "$(curl -s ${TFUPDATE_RELEASES} | grep -o -E -m 1 "https://.+?${TFUPDATE_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz")" > tfupdate.tgz; \ + fi; \ ) && tar -xzf tfupdate.tgz tfupdate && rm tfupdate.tgz \ ; fi @@ -164,8 +188,10 @@ 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 \ + if [ "$HCLEDIT_VERSION" = "latest" ]; \ + then curl -L "$(curl -s ${HCLEDIT_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.tar.gz")" > hcledit.tgz; \ + else curl -L "$(curl -s ${HCLEDIT_RELEASES} | grep -o -E -m 1 "https://.+?${HCLEDIT_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz")" > hcledit.tgz; \ + fi; \ ) && tar -xzf hcledit.tgz hcledit && rm hcledit.tgz \ ; fi From 051dcd50cfca896e0907fc0b85f35ddc96799be7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 11 Mar 2024 20:31:08 +0000 Subject: [PATCH 025/293] chore(release): version 1.88.1 [skip ci] ## [1.88.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.0...v1.88.1) (2024-03-11) ### Bug Fixes * **docker:** Checkov installation silently fails on `docker build` in arm64. Workaround till issue will be fixed in `checkov` itself ([#635](https://github.com/antonbabenko/pre-commit-terraform/issues/635)) ([f255b05](https://github.com/antonbabenko/pre-commit-terraform/commit/f255b05feaace02f38822e3b53cf38c38e069115)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f5a59da7..e910db027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.88.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.0...v1.88.1) (2024-03-11) + + +### Bug Fixes + +* **docker:** Checkov installation silently fails on `docker build` in arm64. Workaround till issue will be fixed in `checkov` itself ([#635](https://github.com/antonbabenko/pre-commit-terraform/issues/635)) ([f255b05](https://github.com/antonbabenko/pre-commit-terraform/commit/f255b05feaace02f38822e3b53cf38c38e069115)) + # [1.88.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.87.1...v1.88.0) (2024-02-22) From a2a2990ca42f93e2c1d61507d8c75e493d29dee6 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 13 Mar 2024 22:47:41 +0200 Subject: [PATCH 026/293] fix(non-linux): Bash environment variables in arguments not expanded + Add `trace` log level (#645) --------- Co-authored-by: George L. Yermulnik --- README.md | 13 +++++++++++++ hooks/_common.sh | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b32d3f8f..9c7b4128c 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ If you are using `pre-commit-terraform` already or want to support its developme * [All hooks: Usage of environment variables in `--args`](#all-hooks-usage-of-environment-variables-in---args) * [All hooks: Set env vars inside hook at runtime](#all-hooks-set-env-vars-inside-hook-at-runtime) * [All hooks: Disable color output](#all-hooks-disable-color-output) + * [All hooks: Log levels](#all-hooks-log-levels) * [Many hooks: Parallelism](#many-hooks-parallelism) * [checkov (deprecated) and terraform\_checkov](#checkov-deprecated-and-terraform_checkov) * [infracost\_breakdown](#infracost_breakdown) @@ -341,6 +342,18 @@ To disable color output for all hooks, set `PRE_COMMIT_COLOR=never` var. Eg: PRE_COMMIT_COLOR=never pre-commit run ``` +### All hooks: Log levels + +In case you need to debug hooks, you can set `PCT_LOG=trace`. + +For example: + +```bash +PCT_LOG=trace pre-commit run -a +``` + +Less verbose log levels will be implemented in [#562](https://github.com/antonbabenko/pre-commit-terraform/issues/562). + ### Many hooks: Parallelism > All, except deprecated hooks: `checkov`, `terraform_docs_replace` and hooks which can't be paralleled this way: `infracost_breakdown`, `terraform_wrapper_module_for_each`. diff --git a/hooks/_common.sh b/hooks/_common.sh index d6dbbb272..e6d610af3 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -1,6 +1,22 @@ #!/usr/bin/env bash set -eo pipefail +if [[ $PCT_LOG == trace ]]; then + + echo "BASH path: '$BASH'" + echo "BASH_VERSION: $BASH_VERSION" + echo "BASHOPTS: $BASHOPTS" + echo "OSTYPE: $OSTYPE" + + # ${FUNCNAME[*]} - function calls in reversed order. Each new function call is appended to the beginning + # ${BASH_SOURCE##*/} - get filename + # $LINENO - get line number + export PS4='\e[2m +trace: ${FUNCNAME[*]} + ${BASH_SOURCE##*/}:$LINENO: \e[0m' + + set -x +fi # Hook ID, based on hook filename. # Hook filename MUST BE same with `- id` in .pre-commit-hooks.yaml file # shellcheck disable=SC2034 # Unused var. @@ -112,7 +128,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%%\}*} From 65098137c6d1fc0cc3e1abc8f853c6e7b0daf076 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 13 Mar 2024 20:48:14 +0000 Subject: [PATCH 027/293] chore(release): version 1.88.2 [skip ci] ## [1.88.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.1...v1.88.2) (2024-03-13) ### Bug Fixes * **non-linux:** Bash environment variables in arguments not expanded + Add `trace` log level ([#645](https://github.com/antonbabenko/pre-commit-terraform/issues/645)) ([a2a2990](https://github.com/antonbabenko/pre-commit-terraform/commit/a2a2990ca42f93e2c1d61507d8c75e493d29dee6)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e910db027..2560a7849 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.88.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.1...v1.88.2) (2024-03-13) + + +### Bug Fixes + +* **non-linux:** Bash environment variables in arguments not expanded + Add `trace` log level ([#645](https://github.com/antonbabenko/pre-commit-terraform/issues/645)) ([a2a2990](https://github.com/antonbabenko/pre-commit-terraform/commit/a2a2990ca42f93e2c1d61507d8c75e493d29dee6)) + ## [1.88.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.0...v1.88.1) (2024-03-11) From 495357e0f06dcde5e0d75cc75a2f0ff08dee5bbb Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 15 Mar 2024 00:14:02 +0200 Subject: [PATCH 028/293] docs: Clarify deps (#647) --------- Co-authored-by: George L. Yermulnik --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c7b4128c..87143321f 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ If you are using `pre-commit-terraform` already or want to support its developme * [`pre-commit`](https://pre-commit.com/#install), [`terraform`](https://www.terraform.io/downloads.html), [`git`](https://git-scm.com/downloads), - POSIX compatible shell, + [BASH `3.2.57` or newer](https://www.gnu.org/software/bash/#download), Internet connection (on first run), x86_64 or arm64 compatible operation system, Some hardware where this OS will run, From 02c1935a12c889a029bc0a571410f19eb39bbab1 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 22 Mar 2024 15:34:42 +0200 Subject: [PATCH 029/293] fix(`terraform_providers_lock`): Require `terraform init` (and `terraform_validate` hook) run when only lockfile changed (#649) ### Description of your changes ![image](https://github.com/antonbabenko/pre-commit-terraform/assets/11096782/50022937-7360-4c3b-8919-a488730c493a) When the terraform root module contains any child module it requires `terraform init` to run to be able to successfully run the provider lock operation. Previously, `terraform init` was in `terraform_providers_lock` hook too, but moved out to be only in `terraform_validate`, because there was little sense in trying to lock anything that was code invalid. If we return `terraform init` back to `terraform_providers_lock` - major changes in providers that remove resources etc. can be successfully written to lockfile without any checks, as such checks done in `terraform_validate` ## Workaround till the release of that stuff Add `files: (\.tf|\.tfvars|\.terraform\.lock\.hcl)$` to `terraform_validate` hook call --- Co-authored-by: George L. Yermulnik --- .pre-commit-hooks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 0942ddfa7..cbe506dd3 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -48,7 +48,7 @@ require_serial: true entry: hooks/terraform_validate.sh language: script - files: (\.tf|\.tfvars)$ + files: \.(tf(vars)?|terraform\.lock\.hcl)$ exclude: \.terraform/.*$ - id: terraform_providers_lock From 1bdc82be7850b9b0ccada524cd5dc37249501159 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 22 Mar 2024 13:35:12 +0000 Subject: [PATCH 030/293] chore(release): version 1.88.3 [skip ci] ## [1.88.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.2...v1.88.3) (2024-03-22) ### Bug Fixes * **`terraform_providers_lock`:** Require `terraform init` (and `terraform_validate` hook) run when only lockfile changed ([#649](https://github.com/antonbabenko/pre-commit-terraform/issues/649)) ([02c1935](https://github.com/antonbabenko/pre-commit-terraform/commit/02c1935a12c889a029bc0a571410f19eb39bbab1)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2560a7849..72431f955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.88.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.2...v1.88.3) (2024-03-22) + + +### Bug Fixes + +* **`terraform_providers_lock`:** Require `terraform init` (and `terraform_validate` hook) run when only lockfile changed ([#649](https://github.com/antonbabenko/pre-commit-terraform/issues/649)) ([02c1935](https://github.com/antonbabenko/pre-commit-terraform/commit/02c1935a12c889a029bc0a571410f19eb39bbab1)) + ## [1.88.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.1...v1.88.2) (2024-03-13) From daec6823f980ef0e9ac8675ed93b6861fcbe58cc Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Mon, 25 Mar 2024 22:26:19 +0200 Subject: [PATCH 031/293] fix: Improve README and drop quotes from hook env vars (#651) --- README.md | 8 ++++++-- hooks/_common.sh | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 87143321f..346203435 100644 --- a/README.md +++ b/README.md @@ -322,14 +322,18 @@ If for config above set up `export CONFIG_NAME=.tflint; export CONFIG_EXT=hcl` b You can specify environment variables that will be passed to the hook at runtime. +> [!IMPORTANT] +> Variable values are exported _verbatim_: +> - No interpolation or expansion are applied +> - The enclosing double quotes are removed if they are provided + Config example: ```yaml - id: terraform_validate args: - --env-vars=AWS_DEFAULT_REGION="us-west-2" - - --env-vars=AWS_ACCESS_KEY_ID="anaccesskey" - - --env-vars=AWS_SECRET_ACCESS_KEY="asecretkey" + - --env-vars=AWS_PROFILE="my-aws-cli-profile" ``` ### All hooks: Disable color output diff --git a/hooks/_common.sh b/hooks/_common.sh index e6d610af3..16c070c6c 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -527,6 +527,11 @@ function common::export_provided_env_vars { for var in "${env_vars[@]}"; do var_name="${var%%=*}" var_value="${var#*=}" + # Drop enclosing double quotes + if [[ $var_value =~ ^\" && $var_value =~ \"$ ]]; then + var_value="${var_value#\"}" + var_value="${var_value%\"}" + fi # shellcheck disable=SC2086 export $var_name="$var_value" done From d9addbeb3614a69d4f96a60d3a090bfddfbb1596 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 25 Mar 2024 20:26:49 +0000 Subject: [PATCH 032/293] chore(release): version 1.88.4 [skip ci] ## [1.88.4](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.3...v1.88.4) (2024-03-25) ### Bug Fixes * Improve README and drop quotes from hook env vars ([#651](https://github.com/antonbabenko/pre-commit-terraform/issues/651)) ([daec682](https://github.com/antonbabenko/pre-commit-terraform/commit/daec6823f980ef0e9ac8675ed93b6861fcbe58cc)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72431f955..e2125f916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.88.4](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.3...v1.88.4) (2024-03-25) + + +### Bug Fixes + +* Improve README and drop quotes from hook env vars ([#651](https://github.com/antonbabenko/pre-commit-terraform/issues/651)) ([daec682](https://github.com/antonbabenko/pre-commit-terraform/commit/daec6823f980ef0e9ac8675ed93b6861fcbe58cc)) + ## [1.88.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.2...v1.88.3) (2024-03-22) From 42919f3926f26453e44e11990c08f038edc521b7 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 5 Apr 2024 22:05:35 +0300 Subject: [PATCH 033/293] docs: Document that `__GIT_WORKING_DIR__` available for `terraform_trivy` (#654) --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 346203435..96b987a60 100644 --- a/README.md +++ b/README.md @@ -883,6 +883,14 @@ To replicate functionality in `terraform_docs` hook: - --args=--skip-dirs="**/.terraform" ``` +4. When you have multiple directories and want to run `trivy` in all of them and share a single config file - use the `__GIT_WORKING_DIR__` placeholder. It will be replaced by `terraform_trivy` hooks with Git working directory (repo root) at run time. For example: + + ```yaml + - id: terraform_trivy + args: + - --args=--ignorefile=__GIT_WORKING_DIR__/.trivyignore + ``` + ### terraform_validate > [!IMPORTANT] From b127601a0b3d5af3dcc9f91a6d74e16f37d66a60 Mon Sep 17 00:00:00 2001 From: Nicholas Henry Date: Mon, 15 Apr 2024 12:35:03 -0600 Subject: [PATCH 034/293] feat: Hook terraform_wrapper_module_for_each should use versions.tf from the module if it exists (#657) Co-authored-by: Nick Henry --- hooks/terraform_wrapper_module_for_each.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hooks/terraform_wrapper_module_for_each.sh b/hooks/terraform_wrapper_module_for_each.sh index b01fe4601..e9a98e2a3 100755 --- a/hooks/terraform_wrapper_module_for_each.sh +++ b/hooks/terraform_wrapper_module_for_each.sh @@ -393,7 +393,13 @@ EOF mv "$tmp_file_tf" "${output_dir}/main.tf" echo "$CONTENT_VARIABLES_TF" > "${output_dir}/variables.tf" - echo "$CONTENT_VERSIONS_TF" > "${output_dir}/versions.tf" + + # If the root module has a versions.tf, use that; otherwise, create it + if [[ -f "${full_module_dir}/versions.tf" ]]; then + cp "${full_module_dir}/versions.tf" "${output_dir}/versions.tf" + else + echo "$CONTENT_VERSIONS_TF" > "${output_dir}/versions.tf" + fi echo "$CONTENT_OUTPUTS_TF" > "${output_dir}/outputs.tf" sed -i.bak "s|WRAPPER_OUTPUT_SENSITIVE|${wrapper_output_sensitive}|g" "${output_dir}/outputs.tf" From ff2f0ca1e3ccdc2b7b8bb0752e3c9e7292893535 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 15 Apr 2024 18:35:36 +0000 Subject: [PATCH 035/293] chore(release): version 1.89.0 [skip ci] # [1.89.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.4...v1.89.0) (2024-04-15) ### Features * Hook terraform_wrapper_module_for_each should use versions.tf from the module if it exists ([#657](https://github.com/antonbabenko/pre-commit-terraform/issues/657)) ([b127601](https://github.com/antonbabenko/pre-commit-terraform/commit/b127601a0b3d5af3dcc9f91a6d74e16f37d66a60)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2125f916..b632e46d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.89.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.4...v1.89.0) (2024-04-15) + + +### Features + +* Hook terraform_wrapper_module_for_each should use versions.tf from the module if it exists ([#657](https://github.com/antonbabenko/pre-commit-terraform/issues/657)) ([b127601](https://github.com/antonbabenko/pre-commit-terraform/commit/b127601a0b3d5af3dcc9f91a6d74e16f37d66a60)) + ## [1.88.4](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.3...v1.88.4) (2024-03-25) From 0340c8d00fe3ba39829b66fd7890d828697a7878 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 25 Apr 2024 18:02:52 +0300 Subject: [PATCH 036/293] fix(docker): Prevent all possible "silent errors" during `docker build` (#644) --------- Co-authored-by: George L. Yermulnik --- .dockerignore | 1 + .github/.container-structure-test-config.yaml | 6 +- .github/CONTRIBUTING.md | 3 + .github/workflows/build-image-test.yaml | 9 +- .pre-commit-config.yaml | 5 +- Dockerfile | 192 ++++-------------- hooks/infracost_breakdown.sh | 4 +- hooks/terraform_checkov.sh | 4 +- hooks/terraform_docs.sh | 4 +- hooks/terraform_fmt.sh | 4 +- hooks/terraform_providers_lock.sh | 4 +- hooks/terraform_tflint.sh | 4 +- hooks/terraform_tfsec.sh | 4 +- hooks/terraform_trivy.sh | 4 +- hooks/terraform_validate.sh | 4 +- hooks/terraform_wrapper_module_for_each.sh | 4 +- hooks/terragrunt_fmt.sh | 4 +- hooks/terragrunt_providers_lock.sh | 4 +- hooks/terragrunt_validate.sh | 4 +- hooks/terrascan.sh | 4 +- hooks/tfupdate.sh | 4 +- tools/install/_common.sh | 86 ++++++++ tools/install/checkov.sh | 36 ++++ tools/install/hcledit.sh | 17 ++ tools/install/infracost.sh | 19 ++ tools/install/pre-commit.sh | 16 ++ tools/install/terraform-docs.sh | 17 ++ tools/install/terraform.sh | 20 ++ tools/install/terragrunt.sh | 17 ++ tools/install/terrascan.sh | 26 +++ tools/install/tflint.sh | 17 ++ tools/install/tfsec.sh | 17 ++ tools/install/tfupdate.sh | 17 ++ tools/install/trivy.sh | 20 ++ 34 files changed, 409 insertions(+), 192 deletions(-) create mode 100755 tools/install/_common.sh create mode 100755 tools/install/checkov.sh create mode 100755 tools/install/hcledit.sh create mode 100755 tools/install/infracost.sh create mode 100755 tools/install/pre-commit.sh create mode 100755 tools/install/terraform-docs.sh create mode 100755 tools/install/terraform.sh create mode 100755 tools/install/terragrunt.sh create mode 100755 tools/install/terrascan.sh create mode 100755 tools/install/tflint.sh create mode 100755 tools/install/tfsec.sh create mode 100755 tools/install/tfupdate.sh create mode 100755 tools/install/trivy.sh diff --git a/.dockerignore b/.dockerignore index a78e675d2..e57569568 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,4 @@ !.dockerignore !Dockerfile !tools/entrypoint.sh +!tools/install/*.sh diff --git a/.github/.container-structure-test-config.yaml b/.github/.container-structure-test-config.yaml index 8cbf7e4de..c746d83fc 100644 --- a/.github/.container-structure-test-config.yaml +++ b/.github/.container-structure-test-config.yaml @@ -13,7 +13,7 @@ commandTests: - name: "terraform" command: "terraform" args: ["-version"] - expectedOutput: ["^Terraform v([0-9]+\\.){2}[0-9]+\\non linux_amd64\\n$"] + expectedOutput: ["^Terraform v([0-9]+\\.){2}[0-9]+\\n"] - name: "gcc" command: "gcc" @@ -28,12 +28,12 @@ commandTests: - name: "infracost" command: "infracost" args: ["--version"] - expectedOutput: ["^Infracost v([0-9]+\\.){2}[0-9]+\\n$"] + expectedOutput: ["^Infracost v([0-9]+\\.){2}[0-9]+"] - name: "terraform-docs" command: "terraform-docs" args: ["--version"] - expectedOutput: ["^terraform-docs version v([0-9]+\\.){2}[0-9]+ [a-z0-9]+ linux/amd64\\n$"] + expectedOutput: ["^terraform-docs version v([0-9]+\\.){2}[0-9]+ [a-z0-9]+"] - name: "terragrunt" command: "terragrunt" diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 388bf3751..1d1568e51 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -113,6 +113,9 @@ You can use [this PR](https://github.com/antonbabenko/pre-commit-terraform/pull/ ### Add code +> [!TIP] +> Here is a screencast of [how to add new dependency in `tools/install/`](https://github.com/antonbabenko/pre-commit-terraform/assets/11096782/8fc461e9-f163-4592-9497-4a18fa89c0e8) - used in Dockerfile + 1. Based on prev. block, add hook dependencies installation to [Dockerfile](../Dockerfile). Check that works: * `docker build -t pre-commit --build-arg INSTALL_ALL=true .` diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 9dfbda3c6..05fbc37b0 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -27,6 +27,13 @@ jobs: .dockerignore tools/entrypoint.sh .github/workflows/build-image-test.yaml + tools/*.sh + + - name: Set up QEMU + if: matrix.os != 'ubuntu-latest' || matrix.arch != 'amd64' + uses: docker/setup-qemu-action@v2 + with: + platforms: 'arm64' - name: Set up Docker Buildx uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 @@ -38,7 +45,7 @@ jobs: context: . build-args: | INSTALL_ALL=true - platforms: linux/amd64 # Only one allowed here, see https://github.com/docker/buildx/issues/59#issuecomment-1433097926 + platforms: linux/${{ matrix.arch }} # Only one allowed here, see https://github.com/docker/buildx/issues/59#issuecomment-1433097926 push: false load: true tags: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 17fc5a6fc..dee8870ae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,13 +42,14 @@ repos: hooks: - id: hadolint args: [ - '--ignore', 'DL3027', # Do not use apt '--ignore', 'DL3007', # Using latest + '--ignore', 'DL3013', # Pin versions in pip + '--ignore', 'DL3027', # Do not use apt + '--ignore', 'DL3059', # Docker `RUN`s shouldn't be consolidated here '--ignore', 'DL4006', # Not related to alpine '--ignore', 'SC1091', # Useless check '--ignore', 'SC2015', # Useless check '--ignore', 'SC3037', # Not related to alpine - '--ignore', 'DL3013', # Pin versions in pip ] # JSON5 Linter diff --git a/Dockerfile b/Dockerfile index 242a45405..3822194e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,41 +7,43 @@ WORKDIR /bin_dir RUN apk add --no-cache \ # Builder deps + bash=~5 \ curl=~8 && \ # Upgrade packages for be able get latest Checkov python3 -m pip install --no-cache-dir --upgrade \ pip \ setuptools +COPY tools/install/ /install/ + +# +# Install required tools +# ARG PRE_COMMIT_VERSION=${PRE_COMMIT_VERSION:-latest} ARG TERRAFORM_VERSION=${TERRAFORM_VERSION:-latest} -# Install pre-commit -RUN if [ ${PRE_COMMIT_VERSION} = "latest" ]; \ - then pip3 install --no-cache-dir pre-commit; \ - else pip3 install --no-cache-dir pre-commit==${PRE_COMMIT_VERSION}; \ +RUN touch /.env && \ + if [ "$PRE_COMMIT_VERSION" = "false" ] || [ "$TERRAFORM_VERSION" = "false" ]; then \ + echo "Vital software can't be skipped" && exit 1; \ fi -# Install terraform because pre-commit needs it -RUN if [ "${TERRAFORM_VERSION}" = "latest" ]; then \ - TERRAFORM_VERSION="$(curl -s https://api.github.com/repos/hashicorp/terraform/releases/latest | grep tag_name | grep -o -E -m 1 "[0-9.]+")" \ - ; fi && \ - curl -L "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_${TARGETOS}_${TARGETARCH}.zip" > terraform.zip && \ - unzip terraform.zip terraform && rm terraform.zip + +RUN /install/pre-commit.sh +RUN /install/terraform.sh # # Install tools # ARG CHECKOV_VERSION=${CHECKOV_VERSION:-false} +ARG HCLEDIT_VERSION=${HCLEDIT_VERSION:-false} ARG INFRACOST_VERSION=${INFRACOST_VERSION:-false} ARG TERRAFORM_DOCS_VERSION=${TERRAFORM_DOCS_VERSION:-false} ARG TERRAGRUNT_VERSION=${TERRAGRUNT_VERSION:-false} ARG TERRASCAN_VERSION=${TERRASCAN_VERSION:-false} ARG TFLINT_VERSION=${TFLINT_VERSION:-false} ARG TFSEC_VERSION=${TFSEC_VERSION:-false} -ARG TRIVY_VERSION=${TRIVY_VERSION:-false} ARG TFUPDATE_VERSION=${TFUPDATE_VERSION:-false} -ARG HCLEDIT_VERSION=${HCLEDIT_VERSION:-false} +ARG TRIVY_VERSION=${TRIVY_VERSION:-false} # Tricky thing to install all tools by set only one arg. @@ -49,167 +51,45 @@ 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 \ - ; else \ - touch /.env \ + echo "CHECKOV_VERSION=latest" >> /.env && \ + echo "HCLEDIT_VERSION=latest" >> /.env && \ + echo "INFRACOST_VERSION=latest" >> /.env && \ + echo "TERRAFORM_DOCS_VERSION=latest" >> /.env && \ + echo "TERRAGRUNT_VERSION=latest" >> /.env && \ + echo "TERRASCAN_VERSION=latest" >> /.env && \ + echo "TFLINT_VERSION=latest" >> /.env && \ + echo "TFSEC_VERSION=latest" >> /.env && \ + echo "TFUPDATE_VERSION=latest" >> /.env && \ + echo "TRIVY_VERSION=latest" >> /.env \ ; fi - -# Checkov -RUN . /.env && \ - if [ "$CHECKOV_VERSION" != "false" ]; then \ - ( \ - # cargo, gcc, git, musl-dev, rust and CARGO envvar required for compilation of rustworkx@0.13.2, no longer required once checkov version depends on rustworkx >0.14.0 - # https://github.com/bridgecrewio/checkov/pull/6045 - # gcc libffi-dev musl-dev required for compilation of cffi, until it contains musl aarch64 - export CARGO_NET_GIT_FETCH_WITH_CLI=true && \ - apk add --no-cache cargo=~1 gcc=~12 git=~2 libffi-dev=~3 libgcc=~12 musl-dev=~1 rust=~1 ; \ - if [ "$CHECKOV_VERSION" = "latest" ]; \ - then pip3 install --no-cache-dir checkov || exit 1; \ - else pip3 install --no-cache-dir checkov==${CHECKOV_VERSION} || exit 1; \ - fi; \ - apk del cargo gcc git libffi-dev musl-dev rust \ - ) \ - ; fi - -# infracost -RUN . /.env && \ - if [ "$INFRACOST_VERSION" != "false" ]; then \ - ( \ - INFRACOST_RELEASES="https://api.github.com/repos/infracost/infracost/releases" && \ - if [ "$INFRACOST_VERSION" = "latest" ]; \ - then curl -L "$(curl -s ${INFRACOST_RELEASES}/latest | grep -o -E -m 1 "https://.+?-${TARGETOS}-${TARGETARCH}.tar.gz")" > infracost.tgz; \ - else curl -L "$(curl -s ${INFRACOST_RELEASES} | grep -o -E "https://.+?v${INFRACOST_VERSION}/infracost-${TARGETOS}-${TARGETARCH}.tar.gz")" > infracost.tgz; \ - fi; \ - ) && tar -xzf infracost.tgz && rm infracost.tgz && mv infracost-${TARGETOS}-${TARGETARCH} infracost \ - ; fi - -# Terraform docs -RUN . /.env && \ - if [ "$TERRAFORM_DOCS_VERSION" != "false" ]; then \ - ( \ - TERRAFORM_DOCS_RELEASES="https://api.github.com/repos/terraform-docs/terraform-docs/releases" && \ - if [ "$TERRAFORM_DOCS_VERSION" = "latest" ]; \ - then curl -L "$(curl -s ${TERRAFORM_DOCS_RELEASES}/latest | grep -o -E -m 1 "https://.+?-${TARGETOS}-${TARGETARCH}.tar.gz")" > terraform-docs.tgz; \ - else curl -L "$(curl -s ${TERRAFORM_DOCS_RELEASES} | grep -o -E "https://.+?v${TERRAFORM_DOCS_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz")" > terraform-docs.tgz; \ - fi; \ - ) && tar -xzf terraform-docs.tgz terraform-docs && rm terraform-docs.tgz && chmod +x terraform-docs \ - ; fi - -# Terragrunt -RUN . /.env \ - && if [ "$TERRAGRUNT_VERSION" != "false" ]; then \ - ( \ - TERRAGRUNT_RELEASES="https://api.github.com/repos/gruntwork-io/terragrunt/releases" && \ - if [ "$TERRAGRUNT_VERSION" = "latest" ]; \ - then curl -L "$(curl -s ${TERRAGRUNT_RELEASES}/latest | grep -o -E -m 1 "https://.+?/terragrunt_${TARGETOS}_${TARGETARCH}")" > terragrunt; \ - else curl -L "$(curl -s ${TERRAGRUNT_RELEASES} | grep -o -E -m 1 "https://.+?v${TERRAGRUNT_VERSION}/terragrunt_${TARGETOS}_${TARGETARCH}")" > terragrunt; \ - fi; \ - ) && chmod +x terragrunt \ - ; fi +RUN /install/checkov.sh +RUN /install/hcledit.sh +RUN /install/infracost.sh +RUN /install/terraform-docs.sh +RUN /install/terragrunt.sh +RUN /install/terrascan.sh +RUN /install/tflint.sh +RUN /install/tfsec.sh +RUN /install/tfupdate.sh +RUN /install/trivy.sh -# Terrascan -RUN . /.env && \ - if [ "$TERRASCAN_VERSION" != "false" ]; then \ - if [ "$TARGETARCH" != "amd64" ]; then ARCH="$TARGETARCH"; else ARCH="x86_64"; fi; \ - # 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" && \ - if [ "$TERRASCAN_VERSION" = "latest" ]; \ - then curl -L "$(curl -s ${TERRASCAN_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${OS}_${ARCH}.tar.gz")" > terrascan.tar.gz; \ - else curl -L "$(curl -s ${TERRASCAN_RELEASES} | grep -o -E "https://.+?${TERRASCAN_VERSION}_${OS}_${ARCH}.tar.gz")" > terrascan.tar.gz; \ - fi; \ - ) && tar -xzf terrascan.tar.gz terrascan && rm terrascan.tar.gz && \ - ./terrascan init \ - ; fi - -# TFLint -RUN . /.env && \ - if [ "$TFLINT_VERSION" != "false" ]; then \ - ( \ - TFLINT_RELEASES="https://api.github.com/repos/terraform-linters/tflint/releases" && \ - if [ "$TFLINT_VERSION" = "latest" ]; \ - then curl -L "$(curl -s ${TFLINT_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.zip")" > tflint.zip; \ - else curl -L "$(curl -s ${TFLINT_RELEASES} | grep -o -E "https://.+?/v${TFLINT_VERSION}/tflint_${TARGETOS}_${TARGETARCH}.zip")" > tflint.zip; \ - fi; \ - ) && unzip tflint.zip && rm tflint.zip \ - ; fi - -# TFSec -RUN . /.env && \ - if [ "$TFSEC_VERSION" != "false" ]; then \ - ( \ - TFSEC_RELEASES="https://api.github.com/repos/aquasecurity/tfsec/releases" && \ - if [ "$TFSEC_VERSION" = "latest" ]; then \ - curl -L "$(curl -s ${TFSEC_RELEASES}/latest | grep -o -E -m 1 "https://.+?/tfsec-${TARGETOS}-${TARGETARCH}")" > tfsec; \ - else curl -L "$(curl -s ${TFSEC_RELEASES} | grep -o -E -m 1 "https://.+?v${TFSEC_VERSION}/tfsec-${TARGETOS}-${TARGETARCH}")" > tfsec; \ - fi; \ - ) && chmod +x tfsec \ - ; fi - -# Trivy -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" && \ - if [ "$TRIVY_VERSION" = "latest" ]; \ - then curl -L "$(curl -s ${TRIVY_RELEASES}/latest | grep -o -E -i -m 1 "https://.+?/trivy_.+?_${TARGETOS}-${ARCH}.tar.gz")" > trivy.tar.gz; \ - else curl -L "$(curl -s ${TRIVY_RELEASES} | grep -o -E -i -m 1 "https://.+?/v${TRIVY_VERSION}/trivy_.+?_${TARGETOS}-${ARCH}.tar.gz")" > trivy.tar.gz; \ - fi; \ - ) && tar -xzf trivy.tar.gz trivy && rm trivy.tar.gz \ - ; fi - -# TFUpdate -RUN . /.env && \ - if [ "$TFUPDATE_VERSION" != "false" ]; then \ - ( \ - TFUPDATE_RELEASES="https://api.github.com/repos/minamijoyo/tfupdate/releases" && \ - if [ "$TFUPDATE_VERSION" = "latest" ]; \ - then curl -L "$(curl -s ${TFUPDATE_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.tar.gz")" > tfupdate.tgz; \ - else curl -L "$(curl -s ${TFUPDATE_RELEASES} | grep -o -E -m 1 "https://.+?${TFUPDATE_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz")" > tfupdate.tgz; \ - fi; \ - ) && tar -xzf tfupdate.tgz tfupdate && rm tfupdate.tgz \ - ; fi - -# hcledit -RUN . /.env && \ - if [ "$HCLEDIT_VERSION" != "false" ]; then \ - ( \ - HCLEDIT_RELEASES="https://api.github.com/repos/minamijoyo/hcledit/releases" && \ - if [ "$HCLEDIT_VERSION" = "latest" ]; \ - then curl -L "$(curl -s ${HCLEDIT_RELEASES}/latest | grep -o -E -m 1 "https://.+?_${TARGETOS}_${TARGETARCH}.tar.gz")" > hcledit.tgz; \ - else curl -L "$(curl -s ${HCLEDIT_RELEASES} | grep -o -E -m 1 "https://.+?${HCLEDIT_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz")" > hcledit.tgz; \ - fi; \ - ) && tar -xzf hcledit.tgz hcledit && rm hcledit.tgz \ - ; fi - # Checking binaries versions and write it to debug file RUN . /.env && \ F=tools_versions_info && \ pre-commit --version >> $F && \ ./terraform --version | head -n 1 >> $F && \ (if [ "$CHECKOV_VERSION" != "false" ]; then echo "checkov $(checkov --version)" >> $F; else echo "checkov SKIPPED" >> $F ; fi) && \ + (if [ "$HCLEDIT_VERSION" != "false" ]; then echo "hcledit $(./hcledit version)" >> $F; else echo "hcledit SKIPPED" >> $F ; fi) && \ (if [ "$INFRACOST_VERSION" != "false" ]; then echo "$(./infracost --version)" >> $F; else echo "infracost SKIPPED" >> $F ; fi) && \ (if [ "$TERRAFORM_DOCS_VERSION" != "false" ]; then ./terraform-docs --version >> $F; else echo "terraform-docs SKIPPED" >> $F ; fi) && \ (if [ "$TERRAGRUNT_VERSION" != "false" ]; then ./terragrunt --version >> $F; else echo "terragrunt SKIPPED" >> $F ; fi) && \ (if [ "$TERRASCAN_VERSION" != "false" ]; then echo "terrascan $(./terrascan version)" >> $F; else echo "terrascan SKIPPED" >> $F ; fi) && \ (if [ "$TFLINT_VERSION" != "false" ]; then ./tflint --version >> $F; else echo "tflint SKIPPED" >> $F ; fi) && \ (if [ "$TFSEC_VERSION" != "false" ]; then echo "tfsec $(./tfsec --version)" >> $F; else echo "tfsec SKIPPED" >> $F ; fi) && \ - (if [ "$TRIVY_VERSION" != "false" ]; then echo "trivy $(./trivy --version)" >> $F; else echo "trivy SKIPPED" >> $F ; fi) && \ (if [ "$TFUPDATE_VERSION" != "false" ]; then echo "tfupdate $(./tfupdate --version)" >> $F; else echo "tfupdate SKIPPED" >> $F ; fi) && \ - (if [ "$HCLEDIT_VERSION" != "false" ]; then echo "hcledit $(./hcledit version)" >> $F; else echo "hcledit SKIPPED" >> $F ; fi) && \ + (if [ "$TRIVY_VERSION" != "false" ]; then echo "trivy $(./trivy --version)" >> $F; else echo "trivy SKIPPED" >> $F ; fi) && \ echo -e "\n\n" && cat $F && echo -e "\n\n" diff --git a/hooks/infracost_breakdown.sh b/hooks/infracost_breakdown.sh index 551579112..d5351b9bf 100755 --- a/hooks/infracost_breakdown.sh +++ b/hooks/infracost_breakdown.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terraform_checkov.sh b/hooks/terraform_checkov.sh index a9603afd9..dbbdc463b 100755 --- a/hooks/terraform_checkov.sh +++ b/hooks/terraform_checkov.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index c597730b0..472d6487c 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terraform_fmt.sh b/hooks/terraform_fmt.sh index 727635caa..949a066a8 100755 --- a/hooks/terraform_fmt.sh +++ b/hooks/terraform_fmt.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terraform_providers_lock.sh b/hooks/terraform_providers_lock.sh index bf2aa2084..34bce58b2 100755 --- a/hooks/terraform_providers_lock.sh +++ b/hooks/terraform_providers_lock.sh @@ -3,8 +3,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terraform_tflint.sh b/hooks/terraform_tflint.sh index 9c8373676..211e648bc 100755 --- a/hooks/terraform_tflint.sh +++ b/hooks/terraform_tflint.sh @@ -3,8 +3,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terraform_tfsec.sh b/hooks/terraform_tfsec.sh index 75966bf8c..52cab2c71 100755 --- a/hooks/terraform_tfsec.sh +++ b/hooks/terraform_tfsec.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terraform_trivy.sh b/hooks/terraform_trivy.sh index fd9a3203a..7de40188b 100755 --- a/hooks/terraform_trivy.sh +++ b/hooks/terraform_trivy.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terraform_validate.sh b/hooks/terraform_validate.sh index 9a6c50fce..a56e8c6db 100755 --- a/hooks/terraform_validate.sh +++ b/hooks/terraform_validate.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terraform_wrapper_module_for_each.sh b/hooks/terraform_wrapper_module_for_each.sh index e9a98e2a3..945bf9a8a 100755 --- a/hooks/terraform_wrapper_module_for_each.sh +++ b/hooks/terraform_wrapper_module_for_each.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terragrunt_fmt.sh b/hooks/terragrunt_fmt.sh index 1effbf47b..a39c78a79 100755 --- a/hooks/terragrunt_fmt.sh +++ b/hooks/terragrunt_fmt.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terragrunt_providers_lock.sh b/hooks/terragrunt_providers_lock.sh index f05a571c4..059f8d1f5 100755 --- a/hooks/terragrunt_providers_lock.sh +++ b/hooks/terragrunt_providers_lock.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terragrunt_validate.sh b/hooks/terragrunt_validate.sh index 8fafada0a..e595329b6 100755 --- a/hooks/terragrunt_validate.sh +++ b/hooks/terragrunt_validate.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/terrascan.sh b/hooks/terrascan.sh index 0c1447bd4..d7dc5f4a5 100755 --- a/hooks/terrascan.sh +++ b/hooks/terrascan.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/hooks/tfupdate.sh b/hooks/tfupdate.sh index 9397cfe94..5c9979a47 100755 --- a/hooks/tfupdate.sh +++ b/hooks/tfupdate.sh @@ -2,8 +2,8 @@ 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)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR # shellcheck source=_common.sh . "$SCRIPT_DIR/_common.sh" diff --git a/tools/install/_common.sh b/tools/install/_common.sh new file mode 100755 index 000000000..70297f297 --- /dev/null +++ b/tools/install/_common.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash + +set -eo pipefail + +# Tool name, based on filename. +# Tool filename MUST BE same as in package manager/binary name +TOOL=${0##*/} +readonly TOOL=${TOOL%%.*} + +# Get "TOOL_VERSION" +# shellcheck disable=SC1091 # Created in Dockerfile before execution of this script +source /.env +env_var_name="${TOOL//-/_}" +env_var_name="${env_var_name^^}_VERSION" +# shellcheck disable=SC2034 # Used in other scripts +readonly VERSION="${!env_var_name}" + +# Skip tool installation if the version is set to "false" +if [[ $VERSION == false ]]; then + echo "'$TOOL' skipped" + exit 0 +fi + +####################################################################### +# Install the latest or specific version of the tool from GitHub release +# Globals: +# TOOL - Name of the tool +# VERSION - Version of the tool +# Arguments: +# GH_ORG - GitHub organization name where the tool is hosted +# DISTRIBUTED_AS - How the tool is distributed. +# Can be: 'tar.gz', 'zip' or 'binary' +# GH_RELEASE_REGEX_LATEST - Regular expression to match the latest +# release URL +# GH_RELEASE_REGEX_SPECIFIC_VERSION - Regular expression to match the +# specific version release URL +# UNUSUAL_TOOL_NAME_IN_PKG - If the tool in the tar.gz package is +# not in the root or named differently than the tool name itself, +# For example, includes the version number or is in a subdirectory +####################################################################### +function common::install_from_gh_release { + local -r GH_ORG=$1 + local -r DISTRIBUTED_AS=$2 + local -r GH_RELEASE_REGEX_LATEST=$3 + local -r GH_RELEASE_REGEX_SPECIFIC_VERSION=$4 + local -r UNUSUAL_TOOL_NAME_IN_PKG=$5 + + case $DISTRIBUTED_AS in + tar.gz | zip) + local -r PKG="${TOOL}.${DISTRIBUTED_AS}" + ;; + binary) + local -r PKG="$TOOL" + ;; + *) + echo "Unknown DISTRIBUTED_AS: '$DISTRIBUTED_AS'. Should be one of: 'tar.gz', 'zip' or 'binary'." >&2 + exit 1 + ;; + esac + + # Download tool + local -r RELEASES="https://api.github.com/repos/${GH_ORG}/${TOOL}/releases" + + if [[ $VERSION == latest ]]; then + curl -L "$(curl -s "${RELEASES}/latest" | grep -o -E -i -m 1 "$GH_RELEASE_REGEX_LATEST")" > "$PKG" + else + curl -L "$(curl -s "$RELEASES" | grep -o -E -i -m 1 "$GH_RELEASE_REGEX_SPECIFIC_VERSION")" > "$PKG" + fi + + # Make tool ready to use + if [[ $DISTRIBUTED_AS == tar.gz ]]; then + if [[ -z $UNUSUAL_TOOL_NAME_IN_PKG ]]; then + tar -xzf "$PKG" "$TOOL" + else + tar -xzf "$PKG" "$UNUSUAL_TOOL_NAME_IN_PKG" + mv "$UNUSUAL_TOOL_NAME_IN_PKG" "$TOOL" + fi + rm "$PKG" + + elif [[ $DISTRIBUTED_AS == zip ]]; then + unzip "$PKG" + rm "$PKG" + else + chmod +x "$PKG" + fi +} diff --git a/tools/install/checkov.sh b/tools/install/checkov.sh new file mode 100755 index 000000000..8be8c649e --- /dev/null +++ b/tools/install/checkov.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# + +apk add --no-cache \ + gcc=~12 \ + libffi-dev=~3 \ + musl-dev=~1 + +# cargo, gcc, git, musl-dev, rust and CARGO envvar required for compilation of rustworkx@0.13.2 +# no longer required once checkov version depends on rustworkx >0.14.0 +# https://github.com/bridgecrewio/checkov/pull/6045 +# gcc libffi-dev musl-dev required for compilation of cffi, until it contains musl aarch64 +export CARGO_NET_GIT_FETCH_WITH_CLI=true +apk add --no-cache \ + cargo=~1 \ + git=~2 \ + libgcc=~12 \ + rust=~1 + +if [[ $VERSION == latest ]]; then + pip3 install --no-cache-dir "${TOOL}" +else + pip3 install --no-cache-dir "${TOOL}==${VERSION}" +fi + +apk del gcc libffi-dev musl-dev +# no longer required once checkov version depends on rustworkx >0.14.0 +# https://github.com/bridgecrewio/checkov/pull/6045 +apk del cargo git rust diff --git a/tools/install/hcledit.sh b/tools/install/hcledit.sh new file mode 100755 index 000000000..498e4fb6f --- /dev/null +++ b/tools/install/hcledit.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# + +GH_ORG="minamijoyo" +GH_RELEASE_REGEX_SPECIFIC_VERSION="https://.+?${VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz" +GH_RELEASE_REGEX_LATEST="https://.+?_${TARGETOS}_${TARGETARCH}.tar.gz" +DISTRIBUTED_AS="tar.gz" + +common::install_from_gh_release "$GH_ORG" "$DISTRIBUTED_AS" \ + "$GH_RELEASE_REGEX_LATEST" "$GH_RELEASE_REGEX_SPECIFIC_VERSION" diff --git a/tools/install/infracost.sh b/tools/install/infracost.sh new file mode 100755 index 000000000..9974ca1d1 --- /dev/null +++ b/tools/install/infracost.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# + +GH_ORG="infracost" +GH_RELEASE_REGEX_SPECIFIC_VERSION="https://.+?v${VERSION}/${TOOL}-${TARGETOS}-${TARGETARCH}.tar.gz" +GH_RELEASE_REGEX_LATEST="https://.+?-${TARGETOS}-${TARGETARCH}.tar.gz" +DISTRIBUTED_AS="tar.gz" +UNUSUAL_TOOL_NAME_IN_PKG="${TOOL}-${TARGETOS}-${TARGETARCH}" + +common::install_from_gh_release "$GH_ORG" "$DISTRIBUTED_AS" \ + "$GH_RELEASE_REGEX_LATEST" "$GH_RELEASE_REGEX_SPECIFIC_VERSION" \ + "$UNUSUAL_TOOL_NAME_IN_PKG" diff --git a/tools/install/pre-commit.sh b/tools/install/pre-commit.sh new file mode 100755 index 000000000..9f3bdfb24 --- /dev/null +++ b/tools/install/pre-commit.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR + +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# + +if [[ $VERSION == latest ]]; then + pip3 install --no-cache-dir "$TOOL" +else + pip3 install --no-cache-dir "${TOOL}==${VERSION}" +fi diff --git a/tools/install/terraform-docs.sh b/tools/install/terraform-docs.sh new file mode 100755 index 000000000..9eec05394 --- /dev/null +++ b/tools/install/terraform-docs.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# + +GH_ORG="terraform-docs" +GH_RELEASE_REGEX_SPECIFIC_VERSION="https://.+?v${VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" +GH_RELEASE_REGEX_LATEST="https://.+?-${TARGETOS}-${TARGETARCH}.tar.gz" +DISTRIBUTED_AS="tar.gz" + +common::install_from_gh_release "$GH_ORG" "$DISTRIBUTED_AS" \ + "$GH_RELEASE_REGEX_LATEST" "$GH_RELEASE_REGEX_SPECIFIC_VERSION" diff --git a/tools/install/terraform.sh b/tools/install/terraform.sh new file mode 100755 index 000000000..65ec21c2b --- /dev/null +++ b/tools/install/terraform.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# +# shellcheck disable=SC2153 # We are using the variable from _common.sh +if [[ $VERSION == latest ]]; then + version="$(curl -s https://api.github.com/repos/hashicorp/terraform/releases/latest | grep tag_name | grep -o -E -m 1 "[0-9.]+")" +else + version=$VERSION +fi +readonly version + +curl -L "https://releases.hashicorp.com/terraform/${version}/${TOOL}_${version}_${TARGETOS}_${TARGETARCH}.zip" > "${TOOL}.zip" +unzip "${TOOL}.zip" "$TOOL" +rm "${TOOL}.zip" diff --git a/tools/install/terragrunt.sh b/tools/install/terragrunt.sh new file mode 100755 index 000000000..20cc60ff7 --- /dev/null +++ b/tools/install/terragrunt.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# + +GH_ORG="gruntwork-io" +GH_RELEASE_REGEX_SPECIFIC_VERSION="https://.+?v${VERSION}/${TOOL}_${TARGETOS}_${TARGETARCH}" +GH_RELEASE_REGEX_LATEST="https://.+?/${TOOL}_${TARGETOS}_${TARGETARCH}" +DISTRIBUTED_AS="binary" + +common::install_from_gh_release "$GH_ORG" "$DISTRIBUTED_AS" \ + "$GH_RELEASE_REGEX_LATEST" "$GH_RELEASE_REGEX_SPECIFIC_VERSION" diff --git a/tools/install/terrascan.sh b/tools/install/terrascan.sh new file mode 100755 index 000000000..4393159d3 --- /dev/null +++ b/tools/install/terrascan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# + +[[ $TARGETARCH == amd64 ]] && ARCH="x86_64" || ARCH="$TARGETARCH" +readonly ARCH +# Convert the first letter to Uppercase +OS="${TARGETOS^}" + +GH_ORG="tenable" +GH_RELEASE_REGEX_SPECIFIC_VERSION="https://.+?${VERSION}_${OS}_${ARCH}.tar.gz" +GH_RELEASE_REGEX_LATEST="https://.+?_${OS}_${ARCH}.tar.gz" +DISTRIBUTED_AS="tar.gz" + +common::install_from_gh_release "$GH_ORG" "$DISTRIBUTED_AS" \ + "$GH_RELEASE_REGEX_LATEST" "$GH_RELEASE_REGEX_SPECIFIC_VERSION" + +# Download (caching) terrascan rego policies to save time during terrascan run +# https://runterrascan.io/docs/usage/_print/#pg-2cba380a2ef14e4ae3c674e02c5f9f53 +./"$TOOL" init diff --git a/tools/install/tflint.sh b/tools/install/tflint.sh new file mode 100755 index 000000000..ac2556b81 --- /dev/null +++ b/tools/install/tflint.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# + +GH_ORG="terraform-linters" +GH_RELEASE_REGEX_SPECIFIC_VERSION="https://.+?/v${VERSION}/${TOOL}_${TARGETOS}_${TARGETARCH}.zip" +GH_RELEASE_REGEX_LATEST="https://.+?_${TARGETOS}_${TARGETARCH}.zip" +DISTRIBUTED_AS="zip" + +common::install_from_gh_release "$GH_ORG" "$DISTRIBUTED_AS" \ + "$GH_RELEASE_REGEX_LATEST" "$GH_RELEASE_REGEX_SPECIFIC_VERSION" diff --git a/tools/install/tfsec.sh b/tools/install/tfsec.sh new file mode 100755 index 000000000..3c9c2430d --- /dev/null +++ b/tools/install/tfsec.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# + +GH_ORG="aquasecurity" +GH_RELEASE_REGEX_SPECIFIC_VERSION="https://.+?v${VERSION}/${TOOL}-${TARGETOS}-${TARGETARCH}" +GH_RELEASE_REGEX_LATEST="https://.+?/${TOOL}-${TARGETOS}-${TARGETARCH}" +DISTRIBUTED_AS="binary" + +common::install_from_gh_release "$GH_ORG" "$DISTRIBUTED_AS" \ + "$GH_RELEASE_REGEX_LATEST" "$GH_RELEASE_REGEX_SPECIFIC_VERSION" diff --git a/tools/install/tfupdate.sh b/tools/install/tfupdate.sh new file mode 100755 index 000000000..498e4fb6f --- /dev/null +++ b/tools/install/tfupdate.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# + +GH_ORG="minamijoyo" +GH_RELEASE_REGEX_SPECIFIC_VERSION="https://.+?${VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz" +GH_RELEASE_REGEX_LATEST="https://.+?_${TARGETOS}_${TARGETARCH}.tar.gz" +DISTRIBUTED_AS="tar.gz" + +common::install_from_gh_release "$GH_ORG" "$DISTRIBUTED_AS" \ + "$GH_RELEASE_REGEX_LATEST" "$GH_RELEASE_REGEX_SPECIFIC_VERSION" diff --git a/tools/install/trivy.sh b/tools/install/trivy.sh new file mode 100755 index 000000000..c07625b53 --- /dev/null +++ b/tools/install/trivy.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# + +[[ $TARGETARCH == amd64 ]] && ARCH="64bit" || ARCH="$TARGETARCH" +readonly ARCH + +GH_ORG="aquasecurity" +GH_RELEASE_REGEX_SPECIFIC_VERSION="https://.+?/v${VERSION}/${TOOL}_.+?_${TARGETOS}-${ARCH}.tar.gz" +GH_RELEASE_REGEX_LATEST="https://.+?/${TOOL}_.+?_${TARGETOS}-${ARCH}.tar.gz" +DISTRIBUTED_AS="tar.gz" + +common::install_from_gh_release "$GH_ORG" "$DISTRIBUTED_AS" \ + "$GH_RELEASE_REGEX_LATEST" "$GH_RELEASE_REGEX_SPECIFIC_VERSION" From c9646748faf2914cbac342e9c06a7b5865d06d2f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 25 Apr 2024 15:03:27 +0000 Subject: [PATCH 037/293] chore(release): version 1.89.1 [skip ci] ## [1.89.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.89.0...v1.89.1) (2024-04-25) ### Bug Fixes * **docker:** Prevent all possible "silent errors" during `docker build` ([#644](https://github.com/antonbabenko/pre-commit-terraform/issues/644)) ([0340c8d](https://github.com/antonbabenko/pre-commit-terraform/commit/0340c8d00fe3ba39829b66fd7890d828697a7878)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b632e46d2..8c9132d04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.89.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.89.0...v1.89.1) (2024-04-25) + + +### Bug Fixes + +* **docker:** Prevent all possible "silent errors" during `docker build` ([#644](https://github.com/antonbabenko/pre-commit-terraform/issues/644)) ([0340c8d](https://github.com/antonbabenko/pre-commit-terraform/commit/0340c8d00fe3ba39829b66fd7890d828697a7878)) + # [1.89.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.88.4...v1.89.0) (2024-04-15) From 88d2b66ab660f13adb336e837f4b912943665a77 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:02:24 +0300 Subject: [PATCH 038/293] chore(deps): update pre-commit hook pre-commit/pre-commit-hooks to v4.6.0 (#666) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dee8870ae..6944a0adb 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: v4.6.0 hooks: # Git style - id: check-added-large-files From 0e96290117702f8ac2dd466eb46cf39849615851 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:02:51 +0300 Subject: [PATCH 039/293] chore(deps): update cycjimmy/semantic-release-action action to v4.1.0 (#662) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99ed765bd..fe0453713 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: fetch-depth: 0 - name: Release - uses: cycjimmy/semantic-release-action@61680d0e9b02ff86f5648ade99e01be17f0260a4 # v4.0.0 + uses: cycjimmy/semantic-release-action@cb425203a562475bca039ba4dbf90c7f9ac790f4 # v4.1.0 with: semantic_version: 18.0.0 extra_plugins: | From 13d0de856f2318d44ceea59bfb6ce764c601a49a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:06:30 +0300 Subject: [PATCH 040/293] chore(deps): update actions/setup-python action to v5.1.0 (#661) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pre-commit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 577295042..4f5081344 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -36,7 +36,7 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} # Skip terraform_tflint which interferes to commit pre-commit auto-fixes - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.9' - name: Execute pre-commit From 862192fd61f3787fe5fb4ae9957c6c9e1c068c40 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:00:53 +0300 Subject: [PATCH 041/293] chore(deps): update tj-actions/changed-files action to v44 (#667) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 05fbc37b0..63ce64a6d 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -20,7 +20,7 @@ jobs: - name: Get changed Docker related files id: changed-files-specific - uses: tj-actions/changed-files@90a06d6ba9543371ab4df8eeca0be07ca6054959 # v42.0.2 + uses: tj-actions/changed-files@0874344d6ebbaa00a27da73276ae7162fadcaf69 # v44.3.0 with: files: | Dockerfile From 62bf2d131741311f3dc041cc636b51ad1182e2f4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:01:39 +0300 Subject: [PATCH 042/293] chore(deps): update docker/setup-buildx-action action to v3.3.0 (#665) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 63ce64a6d..18e8a50e6 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -36,7 +36,7 @@ jobs: platforms: 'arm64' - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 3f79fb205..24c37ac0d 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -17,7 +17,7 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Login to GitHub Container Registry uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 with: @@ -29,7 +29,7 @@ jobs: echo IMAGE_TAG=$([ ${{ github.ref_type }} == 'tag' ] && echo ${{ github.ref_name }} || echo 'latest') >> $GITHUB_ENV - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Build and Push release if: github.event_name != 'schedule' From 4545b6cfa30c5e29f94965677a79b8773f39935c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:58:03 +0300 Subject: [PATCH 043/293] chore(deps): update docker/build-push-action action to v5.3.0 (#663) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 4 ++-- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 18e8a50e6..abf0caa06 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -40,7 +40,7 @@ jobs: - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 with: context: . build-args: | @@ -74,7 +74,7 @@ jobs: # https://github.com/docker/buildx/issues/59#issuecomment-1433097926 - name: Build Multi-arch docker-image if: steps.changed-files-specific.outputs.any_changed == 'true' && matrix.os == 'ubuntu-latest' && matrix.arch == 'amd64' - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 with: context: . build-args: | diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 24c37ac0d..1cd40349e 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -33,7 +33,7 @@ jobs: - name: Build and Push release if: github.event_name != 'schedule' - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 with: context: . build-args: | @@ -50,7 +50,7 @@ jobs: - name: Build and Push nightly if: github.event_name == 'schedule' - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 with: context: . build-args: | From 5bb464e2453836d9eb22cf6681d55917961898f0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:58:27 +0300 Subject: [PATCH 044/293] chore(deps): update docker/login-action action to v3.1.0 (#664) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 1cd40349e..5f88d097c 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -19,7 +19,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Login to GitHub Container Registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From a7697a2cf8c0b7df939e0eda38af3dc8ee882841 Mon Sep 17 00:00:00 2001 From: Steffen Tautenhahn Date: Tue, 30 Apr 2024 18:30:45 +0200 Subject: [PATCH 045/293] docs: How to use pre-commit-terraform image to run pre-commit in CI (#656) --------- Signed-off-by: Steffen Tautenhahn --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/README.md b/README.md index 96b987a60..1c58c24dc 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ If you are using `pre-commit-terraform` already or want to support its developme * [Docker Usage](#docker-usage) * [File Permissions](#file-permissions) * [Download Terraform modules from private GitHub repositories](#download-terraform-modules-from-private-github-repositories) +* [Github Actions](#github-actions) * [Authors](#authors) * [License](#license) * [Additional information for users from Russia and Belarus](#additional-information-for-users-from-russia-and-belarus) @@ -1167,6 +1168,62 @@ Finally, you can execute `docker run` with an additional volume mount so that th docker run --rm -e "USERID=$(id -u):$(id -g)" -v ~/.netrc:/root/.netrc -v $(pwd):/lint -w /lint ghcr.io/antonbabenko/pre-commit-terraform:latest run -a ``` +## Github Actions + +You can use this hook in your GitHub Actions workflow togehther with [pre-commit](https://pre-commit.com). To easy up dependency management, you can use the managed [docker image](#docker-usage) within your workflow. Make sure to set the image tag to the version you want to use. + +In this repository's pre-commit [workflow file](.github/workflows/pre-commit.yml) we run pre-commit without the container image. + +Here is an example that use the container image, includes caching of pre-commit dependencies and uses the `pre-commit` command to run the checks (but fixes will be not automatically push back to your branch, when it possible): + +```yaml +name: pre-commit-terraform + +on: + pull_request: + +jobs: + pre-commit: + runs-on: ubuntu-latest + container: + image: ghcr.io/antonbabenko/pre-commit-terraform:latest # latest used here for simplicity, not recommended + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - run: | + git config --global --add safe.directory $GITHUB_WORKSPACE + git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* + + - name: Get changed files + id: file_changes + run: | + export DIFF=$(git diff --name-only origin/${{ github.base_ref }} ${{ github.sha }}) + echo "Diff between ${{ github.base_ref }} and ${{ github.sha }}" + echo "files=$( echo "$DIFF" | xargs echo )" >> $GITHUB_OUTPUT + + - name: fix tar dependency in alpine container image + run: | + apk --no-cache add tar + # check python modules installed versions + python -m pip freeze --local + + - name: Cache pre-commit since we use pre-commit from container + uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: pre-commit-3|${{ hashFiles('.pre-commit-config.yaml') }} + + - name: Execute pre-commit + run: | + pre-commit run --color=always --show-diff-on-failure --files ${{ steps.file_changes.outputs.files }} +``` + ## Authors This repository is managed by [Anton Babenko](https://github.com/antonbabenko) with help from these awesome contributors: From 1c6b55efef0be055f4dabde54d2fd32c9a46575c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 16:40:52 +0300 Subject: [PATCH 046/293] chore(deps): update actions/checkout action to v4.1.6 (#660) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- .github/workflows/build-image.yaml | 2 +- .github/workflows/pre-commit.yaml | 4 ++-- .github/workflows/release.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index abf0caa06..e4a1be11f 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 0 diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 5f88d097c..764e04b77 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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Set up Docker Buildx diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 4f5081344..6f73f9f61 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - run: | git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* @@ -31,7 +31,7 @@ jobs: 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/ # Need to success pre-commit fix push - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe0453713..54fabc423 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: persist-credentials: false fetch-depth: 0 From 1ac4f2d16814e0d4fed1175562161ff9a20bacea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 18:37:47 +0300 Subject: [PATCH 047/293] chore(deps): pin docker/setup-qemu-action action to 2b82ce8 (#672) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index e4a1be11f..18133154c 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -31,7 +31,7 @@ jobs: - name: Set up QEMU if: matrix.os != 'ubuntu-latest' || matrix.arch != 'amd64' - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2 with: platforms: 'arm64' From c7011c06b84fc96c9a5f2f4508d5ced83ddd2af0 Mon Sep 17 00:00:00 2001 From: Denis Iskandarov Date: Thu, 23 May 2024 07:07:19 +0400 Subject: [PATCH 048/293] feat: Support set custom TF/OpenTofu binary. | If you use a custom Docker image build, please note that `TERRAFORM_VERSION` now must be provided (#670) --------- Co-authored-by: George L. Yermulnik Co-authored-by: Maksym Vlasov --- .github/.container-structure-test-config.yaml | 15 +++-- .../bug_report_local_install.md | 1 + Dockerfile | 20 +++--- README.md | 46 +++++++++----- hooks/_common.sh | 62 ++++++++++++++++++- hooks/terraform_checkov.sh | 5 +- hooks/terraform_fmt.sh | 6 +- hooks/terraform_providers_lock.sh | 8 ++- hooks/terraform_tflint.sh | 5 +- hooks/terraform_tfsec.sh | 5 +- hooks/terraform_trivy.sh | 5 +- hooks/terraform_validate.sh | 16 ++--- hooks/terragrunt_fmt.sh | 5 +- hooks/terragrunt_providers_lock.sh | 5 +- hooks/terragrunt_validate.sh | 5 +- hooks/terrascan.sh | 5 +- hooks/tfupdate.sh | 5 +- tools/install/opentofu.sh | 21 +++++++ 18 files changed, 189 insertions(+), 51 deletions(-) create mode 100755 tools/install/opentofu.sh diff --git a/.github/.container-structure-test-config.yaml b/.github/.container-structure-test-config.yaml index c746d83fc..33beae533 100644 --- a/.github/.container-structure-test-config.yaml +++ b/.github/.container-structure-test-config.yaml @@ -10,11 +10,6 @@ commandTests: args: ["-V"] expectedOutput: ["^pre-commit ([0-9]+\\.){2}[0-9]+\\n$"] - - name: "terraform" - command: "terraform" - args: ["-version"] - expectedOutput: ["^Terraform v([0-9]+\\.){2}[0-9]+\\n"] - - name: "gcc" command: "gcc" args: ["--version"] @@ -30,6 +25,16 @@ commandTests: args: ["--version"] expectedOutput: ["^Infracost v([0-9]+\\.){2}[0-9]+"] + - name: "opentofu" + command: "tofu" + args: ["-version"] + expectedOutput: ["^OpenTofu v([0-9]+\\.){2}[0-9]+\\n"] + + - name: "terraform" + command: "terraform" + args: ["-version"] + expectedOutput: ["^Terraform v([0-9]+\\.){2}[0-9]+\\n"] + - name: "terraform-docs" command: "terraform-docs" args: ["--version"] diff --git a/.github/ISSUE_TEMPLATE/bug_report_local_install.md b/.github/ISSUE_TEMPLATE/bug_report_local_install.md index c58591c28..0ac1fb405 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_local_install.md +++ b/.github/ISSUE_TEMPLATE/bug_report_local_install.md @@ -78,6 +78,7 @@ Linux DESKTOP-C7315EF 5.4.72-microsoft-standard-WSL2 #1 SMP Wed Oct 28 23:40:43 bash << EOF bash --version | head -n 1 2>/dev/null || echo "bash SKIPPED" pre-commit --version 2>/dev/null || echo "pre-commit SKIPPED" +tofu --version | head -n 1 2>/dev/null || echo "opentofu SKIPPED" terraform --version | head -n 1 2>/dev/null || echo "terraform SKIPPED" python --version 2>/dev/null || echo "python SKIPPED" python3 --version 2>/dev/null || echo "python3 SKIPPED" diff --git a/Dockerfile b/Dockerfile index 3822194e5..9fa4f2557 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,20 +20,18 @@ COPY tools/install/ /install/ # Install required tools # ARG PRE_COMMIT_VERSION=${PRE_COMMIT_VERSION:-latest} -ARG TERRAFORM_VERSION=${TERRAFORM_VERSION:-latest} - RUN touch /.env && \ - if [ "$PRE_COMMIT_VERSION" = "false" ] || [ "$TERRAFORM_VERSION" = "false" ]; then \ + if [ "$PRE_COMMIT_VERSION" = "false" ]; then \ echo "Vital software can't be skipped" && exit 1; \ fi - - RUN /install/pre-commit.sh -RUN /install/terraform.sh # # Install tools # +ARG OPENTOFU_VERSION=${OPENTOFU_VERSION:-false} +ARG TERRAFORM_VERSION=${TERRAFORM_VERSION:-false} + ARG CHECKOV_VERSION=${CHECKOV_VERSION:-false} ARG HCLEDIT_VERSION=${HCLEDIT_VERSION:-false} ARG INFRACOST_VERSION=${INFRACOST_VERSION:-false} @@ -51,6 +49,9 @@ ARG TRIVY_VERSION=${TRIVY_VERSION:-false} # specified in step below ARG INSTALL_ALL=${INSTALL_ALL:-false} RUN if [ "$INSTALL_ALL" != "false" ]; then \ + echo "OPENTOFU_VERSION=latest" >> /.env && \ + echo "TERRAFORM_VERSION=latest" >> /.env && \ + \ echo "CHECKOV_VERSION=latest" >> /.env && \ echo "HCLEDIT_VERSION=latest" >> /.env && \ echo "INFRACOST_VERSION=latest" >> /.env && \ @@ -63,6 +64,9 @@ RUN if [ "$INSTALL_ALL" != "false" ]; then \ echo "TRIVY_VERSION=latest" >> /.env \ ; fi +RUN /install/opentofu.sh +RUN /install/terraform.sh + RUN /install/checkov.sh RUN /install/hcledit.sh RUN /install/infracost.sh @@ -79,7 +83,9 @@ RUN /install/trivy.sh RUN . /.env && \ F=tools_versions_info && \ pre-commit --version >> $F && \ - ./terraform --version | head -n 1 >> $F && \ + (if [ "$OPENTOFU_VERSION" != "false" ]; then echo "./tofu --version | head -n 1" >> $F; else echo "opentofu SKIPPED" >> $F ; fi) && \ + (if [ "$TERRAFORM_VERSION" != "false" ]; then echo "./terraform --version | head -n 1" >> $F; else echo "terraform SKIPPED" >> $F ; fi) && \ + \ (if [ "$CHECKOV_VERSION" != "false" ]; then echo "checkov $(checkov --version)" >> $F; else echo "checkov SKIPPED" >> $F ; fi) && \ (if [ "$HCLEDIT_VERSION" != "false" ]; then echo "hcledit $(./hcledit version)" >> $F; else echo "hcledit SKIPPED" >> $F ; fi) && \ (if [ "$INFRACOST_VERSION" != "false" ]; then echo "$(./infracost --version)" >> $F; else echo "infracost SKIPPED" >> $F ; fi) && \ diff --git a/README.md b/README.md index 1c58c24dc..173e525a0 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ If you are using `pre-commit-terraform` already or want to support its developme * [Table of content](#table-of-content) * [How to install](#how-to-install) * [1. Install dependencies](#1-install-dependencies) + * [1.1 Custom Terraform binaries and OpenTofu support](#11-custom-terraform-binaries-and-opentofu-support) * [2. Install the pre-commit hook globally](#2-install-the-pre-commit-hook-globally) * [3. Add configs and hooks](#3-add-configs-and-hooks) * [4. Run](#4-run) @@ -67,7 +68,7 @@ If you are using `pre-commit-terraform` already or want to support its developme ### 1. Install dependencies * [`pre-commit`](https://pre-commit.com/#install), - [`terraform`](https://www.terraform.io/downloads.html), + [`terraform`](https://www.terraform.io/downloads.html) or [`opentofu`](https://opentofu.org/docs/intro/install/), [`git`](https://git-scm.com/downloads), [BASH `3.2.57` or newer](https://www.gnu.org/software/bash/#download), Internet connection (on first run), @@ -77,17 +78,31 @@ If you are using `pre-commit-terraform` already or want to support its developme Some basic physical laws, Hope that it all will work.

-* [`checkov`](https://github.com/bridgecrewio/checkov) required for `terraform_checkov` hook. -* [`terraform-docs`](https://github.com/terraform-docs/terraform-docs) required for `terraform_docs` hook. -* [`terragrunt`](https://terragrunt.gruntwork.io/docs/getting-started/install/) required for `terragrunt_validate` hook. -* [`terrascan`](https://github.com/tenable/terrascan) required for `terrascan` hook. -* [`TFLint`](https://github.com/terraform-linters/tflint) required for `terraform_tflint` hook. -* [`TFSec`](https://github.com/liamg/tfsec) required for `terraform_tfsec` hook. -* [`Trivy`](https://github.com/aquasecurity/trivy) required for `terraform_trivy` hook. -* [`infracost`](https://github.com/infracost/infracost) required for `infracost_breakdown` hook. -* [`jq`](https://github.com/stedolan/jq) required for `terraform_validate` with `--retry-once-with-cleanup` flag, and for `infracost_breakdown` hook. -* [`tfupdate`](https://github.com/minamijoyo/tfupdate) required for `tfupdate` hook. -* [`hcledit`](https://github.com/minamijoyo/hcledit) required for `terraform_wrapper_module_for_each` hook. +* [`checkov`](https://github.com/bridgecrewio/checkov) required for `terraform_checkov` hook +* [`terraform-docs`](https://github.com/terraform-docs/terraform-docs) required for `terraform_docs` hook +* [`terragrunt`](https://terragrunt.gruntwork.io/docs/getting-started/install/) required for `terragrunt_validate` hook +* [`terrascan`](https://github.com/tenable/terrascan) required for `terrascan` hook +* [`TFLint`](https://github.com/terraform-linters/tflint) required for `terraform_tflint` hook +* [`TFSec`](https://github.com/liamg/tfsec) required for `terraform_tfsec` hook +* [`Trivy`](https://github.com/aquasecurity/trivy) required for `terraform_trivy` hook +* [`infracost`](https://github.com/infracost/infracost) required for `infracost_breakdown` hook +* [`jq`](https://github.com/stedolan/jq) required for `terraform_validate` with `--retry-once-with-cleanup` flag, and for `infracost_breakdown` hook +* [`tfupdate`](https://github.com/minamijoyo/tfupdate) required for `tfupdate` hook +* [`hcledit`](https://github.com/minamijoyo/hcledit) required for `terraform_wrapper_module_for_each` hook + + +#### 1.1 Custom Terraform binaries and OpenTofu support + +It is possible to set custom path to `terraform` binary. +This makes it possible to use [OpenTofu](https://opentofu.org) binary `tofu` instead of `terraform`. + +How binary discovery works and how you can redefine it (first matched takes precedence): + +1. Check if per hook configuration `--hook-config=--tf-path=` is set +2. Check if `PCT_TFPATH=` environment variable is set +3. Check if `TERRAGRUNT_TFPATH=` environment variable is set +4. Check if `terraform` binary can be found in the user's $PATH +5. Check if `tofu` binary can be found in the user's $PATH
Docker
@@ -120,17 +135,18 @@ To install a specific version of individual tools, define it using `--build-arg` ```bash docker build -t pre-commit-terraform \ --build-arg PRE_COMMIT_VERSION=latest \ - --build-arg TERRAFORM_VERSION=latest \ + --build-arg OPENTOFU_VERSION=latest \ + --build-arg TERRAFORM_VERSION=1.5.7 \ --build-arg CHECKOV_VERSION=2.0.405 \ + --build-arg HCLEDIT_VERSION=latest \ --build-arg INFRACOST_VERSION=latest \ --build-arg TERRAFORM_DOCS_VERSION=0.15.0 \ --build-arg TERRAGRUNT_VERSION=latest \ --build-arg TERRASCAN_VERSION=1.10.0 \ --build-arg TFLINT_VERSION=0.31.0 \ --build-arg TFSEC_VERSION=latest \ - --build-arg TRIVY_VERSION=latest \ --build-arg TFUPDATE_VERSION=latest \ - --build-arg HCLEDIT_VERSION=latest \ + --build-arg TRIVY_VERSION=latest \ . ``` diff --git a/hooks/_common.sh b/hooks/_common.sh index 16c070c6c..c278c3a7a 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -283,6 +283,8 @@ function common::per_dir_hook { # despite there's only one positional ARG left local -a -r files=("$@") + local -r tf_path=$(common::get_tf_binary_path) + # check is (optional) function defined if [ "$(type -t run_hook_on_whole_repo)" == function ] && # check is hook run via `pre-commit run --all` @@ -383,7 +385,7 @@ function common::per_dir_hook { pushd "$dir_path" > /dev/null fi - per_dir_hook_unique_part "$dir_path" "$change_dir_in_unique_part" "$parallelism_disabled" "${args[@]}" + per_dir_hook_unique_part "$dir_path" "$change_dir_in_unique_part" "$parallelism_disabled" "$tf_path" "${args[@]}" } & pids+=("$!") @@ -445,6 +447,58 @@ function common::colorify { echo -e "${COLOR}${TEXT}${RESET}" >&2 } +####################################################################### +# Get Terraform/OpenTofu binary path +# Allows user to set the path to custom Terraform or OpenTofu binary +# Globals (init and populate): +# HOOK_CONFIG (array) arguments that configure hook behavior +# PCT_TFPATH (string) user defined env var with path to Terraform/OpenTofu binary +# TERRAGRUNT_TFPATH (string) user defined env var with path to Terraform/OpenTofu binary +# Outputs: +# If failed - exit 1 with error message about missing Terraform/OpenTofu binary +####################################################################### +function common::get_tf_binary_path { + local hook_config_tf_path + + for config in "${HOOK_CONFIG[@]}"; do + if [[ $config == --tf-path=* ]]; then + hook_config_tf_path=${config#*=} + hook_config_tf_path=${hook_config_tf_path%;} + break + fi + done + + # direct hook config, has the highest precedence + if [[ $hook_config_tf_path ]]; then + echo "$hook_config_tf_path" + return + + # environment variable + elif [[ $PCT_TFPATH ]]; then + echo "$PCT_TFPATH" + return + + # Maybe there is a similar setting for Terragrunt already + elif [[ $TERRAGRUNT_TFPATH ]]; then + echo "$TERRAGRUNT_TFPATH" + return + + # check if Terraform binary is available + elif command -v terraform &> /dev/null; then + command -v terraform + return + + # finally, check if Tofu binary is available + elif command -v tofu &> /dev/null; then + command -v tofu + return + + else + common::colorify "red" "Neither Terraform nor OpenTofu binary could be found. Please either set the \"--tf-path\" hook configuration argument, or set the \"PCT_TFPATH\" environment variable, or set the \"TERRAGRUNT_TFPATH\" environment variable, or install Terraform or OpenTofu globally." + exit 1 + fi +} + ####################################################################### # Run terraform init command # Arguments: @@ -452,6 +506,7 @@ function common::colorify { # dir_path (string) PATH to dir relative to git repo root. # Can be used in error logging # parallelism_disabled (bool) if true - skip lock mechanism +# tf_path (string) PATH to Terraform/OpenTofu binary # Globals (init and populate): # TF_INIT_ARGS (array) arguments for `terraform init` command # TF_PLUGIN_CACHE_DIR (string) user defined env var with name of the directory @@ -464,6 +519,7 @@ function common::terraform_init { local -r command_name=$1 local -r dir_path=$2 local -r parallelism_disabled=$3 + local -r tf_path=$4 local exit_code=0 local init_output @@ -480,13 +536,13 @@ function common::terraform_init { # Plugin cache dir can't be written concurrently or read during write # https://github.com/hashicorp/terraform/issues/31964 if [[ -z $TF_PLUGIN_CACHE_DIR || $parallelism_disabled == true ]]; then - init_output=$(terraform init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) + init_output=$($tf_path init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) exit_code=$? else # Locking just doesn't work, and the below works quicker instead. Details: # https://github.com/hashicorp/terraform/issues/31964#issuecomment-1939869453 for i in {1..10}; do - init_output=$(terraform init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) + init_output=$($tf_path init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) exit_code=$? if [ $exit_code -eq 0 ]; then diff --git a/hooks/terraform_checkov.sh b/hooks/terraform_checkov.sh index dbbdc463b..01e2f2454 100755 --- a/hooks/terraform_checkov.sh +++ b/hooks/terraform_checkov.sh @@ -36,6 +36,7 @@ function main { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -46,7 +47,9 @@ function per_dir_hook_unique_part { local -r change_dir_in_unique_part="$2" # shellcheck disable=SC2034 # Unused var. local -r parallelism_disabled="$3" - shift 3 + # shellcheck disable=SC2034 # Unused var. + local -r tf_path="$4" + shift 4 local -a -r args=("$@") checkov -d . "${args[@]}" diff --git a/hooks/terraform_fmt.sh b/hooks/terraform_fmt.sh index 949a066a8..a39c27fc5 100755 --- a/hooks/terraform_fmt.sh +++ b/hooks/terraform_fmt.sh @@ -33,6 +33,7 @@ function main { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -43,11 +44,12 @@ function per_dir_hook_unique_part { local -r change_dir_in_unique_part="$2" # shellcheck disable=SC2034 # Unused var. local -r parallelism_disabled="$3" - shift 3 + local -r tf_path="$4" + shift 4 local -a -r args=("$@") # pass the arguments to hook - terraform fmt "${args[@]}" + $tf_path fmt "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? diff --git a/hooks/terraform_providers_lock.sh b/hooks/terraform_providers_lock.sh index 34bce58b2..1cecd1a02 100755 --- a/hooks/terraform_providers_lock.sh +++ b/hooks/terraform_providers_lock.sh @@ -87,6 +87,7 @@ function lockfile_contains_all_needed_sha { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -95,7 +96,8 @@ function per_dir_hook_unique_part { # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" local -r parallelism_disabled="$3" - shift 3 + local -r tf_path="$4" + shift 4 local -a -r args=("$@") local platforms_count=0 @@ -138,7 +140,7 @@ function per_dir_hook_unique_part { common::colorify "yellow" "DEPRECATION NOTICE: We introduced '--mode' flag for this hook. Check migration instructions at https://github.com/antonbabenko/pre-commit-terraform#terraform_providers_lock " - common::terraform_init 'terraform providers lock' "$dir_path" "$parallelism_disabled" || { + common::terraform_init 'terraform providers lock' "$dir_path" "$parallelism_disabled" "$tf_path" || { exit_code=$? return $exit_code } @@ -153,7 +155,7 @@ Check migration instructions at https://github.com/antonbabenko/pre-commit-terra #? Don't require `tf init` for providers, but required `tf init` for modules #? Mitigated by `function match_validate_errors` from terraform_validate hook # pass the arguments to hook - terraform providers lock "${args[@]}" + $tf_path providers lock "${args[@]}" # return exit code to common::per_dir_hook exit_code=$? diff --git a/hooks/terraform_tflint.sh b/hooks/terraform_tflint.sh index 211e648bc..54bcebe38 100755 --- a/hooks/terraform_tflint.sh +++ b/hooks/terraform_tflint.sh @@ -46,6 +46,7 @@ function main { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -54,7 +55,9 @@ function per_dir_hook_unique_part { local -r change_dir_in_unique_part="$2" # shellcheck disable=SC2034 # Unused var. local -r parallelism_disabled="$3" - shift 3 + # shellcheck disable=SC2034 # Unused var. + local -r tf_path="$4" + shift 4 local -a -r args=("$@") if [ "$change_dir_in_unique_part" == "delegate_chdir" ]; then diff --git a/hooks/terraform_tfsec.sh b/hooks/terraform_tfsec.sh index 52cab2c71..dddad49a6 100755 --- a/hooks/terraform_tfsec.sh +++ b/hooks/terraform_tfsec.sh @@ -39,6 +39,7 @@ function main { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -49,7 +50,9 @@ function per_dir_hook_unique_part { local -r change_dir_in_unique_part="$2" # shellcheck disable=SC2034 # Unused var. local -r parallelism_disabled="$3" - shift 3 + # shellcheck disable=SC2034 # Unused var. + local -r tf_path="$4" + shift 4 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/terraform_trivy.sh b/hooks/terraform_trivy.sh index 7de40188b..fb4a529a0 100755 --- a/hooks/terraform_trivy.sh +++ b/hooks/terraform_trivy.sh @@ -31,6 +31,7 @@ function main { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -41,7 +42,9 @@ function per_dir_hook_unique_part { local -r change_dir_in_unique_part="$2" # shellcheck disable=SC2034 # Unused var. local -r parallelism_disabled="$3" - shift 3 + # shellcheck disable=SC2034 # Unused var. + local -r tf_path="$4" + shift 4 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/terraform_validate.sh b/hooks/terraform_validate.sh index a56e8c6db..e51b5c714 100755 --- a/hooks/terraform_validate.sh +++ b/hooks/terraform_validate.sh @@ -77,6 +77,7 @@ function match_validate_errors { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -85,7 +86,8 @@ function per_dir_hook_unique_part { # shellcheck disable=SC2034 # Unused var. local -r change_dir_in_unique_part="$2" local -r parallelism_disabled="$3" - shift 3 + local -r tf_path="$4" + shift 4 local -a -r args=("$@") local exit_code @@ -116,25 +118,25 @@ function per_dir_hook_unique_part { # First try `terraform validate` with the hope that all deps are # pre-installed. That is needed for cases when `.terraform/modules` # or `.terraform/providers` missed AND that is expected. - terraform validate "${args[@]}" &> /dev/null && { + $tf_path validate "${args[@]}" &> /dev/null && { exit_code=$? return $exit_code } # In case `terraform validate` failed to execute # - check is simple `terraform init` will help - common::terraform_init 'terraform validate' "$dir_path" "$parallelism_disabled" || { + common::terraform_init "$tf_path validate" "$dir_path" "$parallelism_disabled" "$tf_path" || { exit_code=$? return $exit_code } if [ "$retry_once_with_cleanup" != "true" ]; then # terraform validate only - validate_output=$(terraform validate "${args[@]}" 2>&1) + validate_output=$($tf_path validate "${args[@]}" 2>&1) exit_code=$? else # terraform validate, plus capture possible errors - validate_output=$(terraform validate -json "${args[@]}" 2>&1) + validate_output=$($tf_path validate -json "${args[@]}" 2>&1) exit_code=$? # Match specific validation errors @@ -152,12 +154,12 @@ function per_dir_hook_unique_part { common::colorify "yellow" "Re-validating: $dir_path" - common::terraform_init 'terraform validate' "$dir_path" "$parallelism_disabled" || { + common::terraform_init "$tf_path validate" "$dir_path" "$parallelism_disabled" "$tf_path" || { exit_code=$? return $exit_code } - validate_output=$(terraform validate "${args[@]}" 2>&1) + validate_output=$($tf_path validate "${args[@]}" 2>&1) exit_code=$? fi fi diff --git a/hooks/terragrunt_fmt.sh b/hooks/terragrunt_fmt.sh index a39c78a79..698b3843b 100755 --- a/hooks/terragrunt_fmt.sh +++ b/hooks/terragrunt_fmt.sh @@ -29,6 +29,7 @@ function main { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -39,7 +40,9 @@ function per_dir_hook_unique_part { local -r change_dir_in_unique_part="$2" # shellcheck disable=SC2034 # Unused var. local -r parallelism_disabled="$3" - shift 3 + # shellcheck disable=SC2034 # Unused var. + local -r tf_path="$4" + shift 4 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/terragrunt_providers_lock.sh b/hooks/terragrunt_providers_lock.sh index 059f8d1f5..9e3557aeb 100755 --- a/hooks/terragrunt_providers_lock.sh +++ b/hooks/terragrunt_providers_lock.sh @@ -29,6 +29,7 @@ function main { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -39,7 +40,9 @@ function per_dir_hook_unique_part { local -r change_dir_in_unique_part="$2" # shellcheck disable=SC2034 # Unused var. local -r parallelism_disabled="$3" - shift 3 + # shellcheck disable=SC2034 # Unused var. + local -r tf_path="$4" + shift 4 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/terragrunt_validate.sh b/hooks/terragrunt_validate.sh index e595329b6..5a62c3913 100755 --- a/hooks/terragrunt_validate.sh +++ b/hooks/terragrunt_validate.sh @@ -29,6 +29,7 @@ function main { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -39,7 +40,9 @@ function per_dir_hook_unique_part { local -r change_dir_in_unique_part="$2" # shellcheck disable=SC2034 # Unused var. local -r parallelism_disabled="$3" - shift 3 + # shellcheck disable=SC2034 # Unused var. + local -r tf_path="$4" + shift 4 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/terrascan.sh b/hooks/terrascan.sh index d7dc5f4a5..f6586ceae 100755 --- a/hooks/terrascan.sh +++ b/hooks/terrascan.sh @@ -29,6 +29,7 @@ function main { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -39,7 +40,9 @@ function per_dir_hook_unique_part { local -r change_dir_in_unique_part="$2" # shellcheck disable=SC2034 # Unused var. local -r parallelism_disabled="$3" - shift 3 + # shellcheck disable=SC2034 # Unused var. + local -r tf_path="$4" + shift 4 local -a -r args=("$@") # pass the arguments to hook diff --git a/hooks/tfupdate.sh b/hooks/tfupdate.sh index 5c9979a47..1d474318b 100755 --- a/hooks/tfupdate.sh +++ b/hooks/tfupdate.sh @@ -39,6 +39,7 @@ function main { # Availability depends on hook. # parallelism_disabled (bool) if true - skip lock mechanism # args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary # Outputs: # If failed - print out hook checks status ####################################################################### @@ -49,7 +50,9 @@ function per_dir_hook_unique_part { local -r change_dir_in_unique_part="$2" # shellcheck disable=SC2034 # Unused var. local -r parallelism_disabled="$3" - shift 3 + # shellcheck disable=SC2034 # Unused var. + local -r tf_path="$4" + shift 4 local -a -r args=("$@") # pass the arguments to hook diff --git a/tools/install/opentofu.sh b/tools/install/opentofu.sh new file mode 100755 index 000000000..bda0fe399 --- /dev/null +++ b/tools/install/opentofu.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +# +# Unique part +# +GH_ORG="opentofu" +GH_RELEASE_REGEX_SPECIFIC_VERSION="https://.+?v${VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz" +GH_RELEASE_REGEX_LATEST="https://.+?_${TARGETOS}_${TARGETARCH}.tar.gz" +DISTRIBUTED_AS="tar.gz" +UNUSUAL_TOOL_NAME_IN_PKG="tofu" + +common::install_from_gh_release "$GH_ORG" "$DISTRIBUTED_AS" \ + "$GH_RELEASE_REGEX_LATEST" "$GH_RELEASE_REGEX_SPECIFIC_VERSION" \ + "$UNUSUAL_TOOL_NAME_IN_PKG" + +# restore original binary name +mv "$TOOL" "$UNUSUAL_TOOL_NAME_IN_PKG" From 29ae38e3db29ee7ca6ef38dde9151466bcbe2226 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 23 May 2024 03:07:49 +0000 Subject: [PATCH 049/293] chore(release): version 1.90.0 [skip ci] # [1.90.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.89.1...v1.90.0) (2024-05-23) ### Features * Support set custom TF/OpenTofu binary. | If you use a custom Docker image build, please note that `TERRAFORM_VERSION` now must be provided ([#670](https://github.com/antonbabenko/pre-commit-terraform/issues/670)) ([c7011c0](https://github.com/antonbabenko/pre-commit-terraform/commit/c7011c06b84fc96c9a5f2f4508d5ced83ddd2af0)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c9132d04..57c02a728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.90.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.89.1...v1.90.0) (2024-05-23) + + +### Features + +* Support set custom TF/OpenTofu binary. | If you use a custom Docker image build, please note that `TERRAFORM_VERSION` now must be provided ([#670](https://github.com/antonbabenko/pre-commit-terraform/issues/670)) ([c7011c0](https://github.com/antonbabenko/pre-commit-terraform/commit/c7011c06b84fc96c9a5f2f4508d5ced83ddd2af0)) + ## [1.89.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.89.0...v1.89.1) (2024-04-25) From e4e705310fd94d53efecce36c2d1bcfe3a07be9e Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Thu, 30 May 2024 15:14:26 +0300 Subject: [PATCH 050/293] chore: Fix YAML highlighting in issue templates (#674) --- .github/ISSUE_TEMPLATE/bug_report_docker.md | 2 +- .github/ISSUE_TEMPLATE/bug_report_local_install.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report_docker.md b/.github/ISSUE_TEMPLATE/bug_report_docker.md index a47e30657..0847388be 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_docker.md +++ b/.github/ISSUE_TEMPLATE/bug_report_docker.md @@ -74,7 +74,7 @@ INSERT_OUTPUT_HERE
file content -```bash +```yaml INSERT_FILE_CONTENT_HERE ``` diff --git a/.github/ISSUE_TEMPLATE/bug_report_local_install.md b/.github/ISSUE_TEMPLATE/bug_report_local_install.md index 0ac1fb405..3bef0dee0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_local_install.md +++ b/.github/ISSUE_TEMPLATE/bug_report_local_install.md @@ -105,7 +105,7 @@ INSERT_TOOLS_VERSIONS_HERE
file content -```bash +```yaml INSERT_FILE_CONTENT_HERE ``` From dae1a483b429506863c3c7203932fef4fa74f86a Mon Sep 17 00:00:00 2001 From: Anton Babenko Date: Fri, 7 Jun 2024 19:58:47 +0200 Subject: [PATCH 051/293] feat: Added Terramate as sponsor (#676) --- README.md | 11 +++-------- assets/terramate.png | Bin 0 -> 9372 bytes 2 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 assets/terramate.png diff --git a/README.md b/README.md index 173e525a0..f10d92660 100644 --- a/README.md +++ b/README.md @@ -11,16 +11,11 @@ Want to contribute? Check [open issues](https://github.com/antonbabenko/pre-comm
-env0 +Terramate: Automate, Orchestrate and Observe Terraform -Automated provisioning of Terraform workflows and Infrastructure as Code. +Terramate is an IaC collaboration, visibility and observability platform that empowers your team to manage Terraform and OpenTofu faster and more confidently than ever before. -
-infracost - -Cloud cost estimates for Terraform. - -If you are using `pre-commit-terraform` already or want to support its development and [many other open-source projects](https://github.com/antonbabenko/terraform-aws-devops), please become a [GitHub Sponsor](https://github.com/sponsors/antonbabenko)! +If you want to support the development of `pre-commit-terraform` and [many other open-source projects](https://github.com/antonbabenko/terraform-aws-devops), please become a [GitHub Sponsor](https://github.com/sponsors/antonbabenko)! ## Table of content diff --git a/assets/terramate.png b/assets/terramate.png new file mode 100644 index 0000000000000000000000000000000000000000..314813b0001f4c8f756964ce23760c000f55e907 GIT binary patch literal 9372 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 X{1&rAy^{W)A1CrMD$*5_#zFrFT(iK( literal 0 HcmV?d00001 From a4ad28b4420f7479756741d13afa8bfdafe7588a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 7 Jun 2024 18:05:35 +0000 Subject: [PATCH 052/293] chore(release): version 1.91.0 [skip ci] # [1.91.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.90.0...v1.91.0) (2024-06-07) ### Features * Added Terramate as sponsor ([#676](https://github.com/antonbabenko/pre-commit-terraform/issues/676)) ([dae1a48](https://github.com/antonbabenko/pre-commit-terraform/commit/dae1a483b429506863c3c7203932fef4fa74f86a)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57c02a728..ce9173587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.91.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.90.0...v1.91.0) (2024-06-07) + + +### Features + +* Added Terramate as sponsor ([#676](https://github.com/antonbabenko/pre-commit-terraform/issues/676)) ([dae1a48](https://github.com/antonbabenko/pre-commit-terraform/commit/dae1a483b429506863c3c7203932fef4fa74f86a)) + # [1.90.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.89.1...v1.90.0) (2024-05-23) From bc7a9187ef5fba2c456849f10bfbbe87e0b34820 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 10 Jun 2024 21:09:56 +0300 Subject: [PATCH 053/293] docs: Describe which tools should be installed to avoid common errors (#678) --- .github/CONTRIBUTING.md | 10 ++++++++++ .vscode/extensions.json | 26 ++++++++++++++++++++++++++ .vscode/settings.json | 10 ++++++++++ 3 files changed, 46 insertions(+) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 1d1568e51..8066f6f30 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -12,6 +12,7 @@ Enjoy the clean, valid, and documented code! * [Run via Docker](#run-via-docker) * [Check results](#check-results) * [Cleanup](#cleanup) +* [Required tools and plugins to simplify review process](#required-tools-and-plugins-to-simplify-review-process) * [Add new hook](#add-new-hook) * [Before write code](#before-write-code) * [Prepare basic documentation](#prepare-basic-documentation) @@ -98,6 +99,13 @@ Results will be located at `./test/results` dir. sudo rm -rf tests/results ``` +## Required tools and plugins to simplify review process + +1. [editorconfig.org](https://editorconfig.org/) (preinstalled in some IDE) +2. [pre-commit](https://pre-commit.com/#install) +3. (Optional) If you use VS Code - feel free to install all recommended extensions + + ## Add new hook You can use [this PR](https://github.com/antonbabenko/pre-commit-terraform/pull/252) as an example. @@ -106,6 +114,8 @@ You can use [this PR](https://github.com/antonbabenko/pre-commit-terraform/pull/ 1. Try to figure out future hook usage. 2. Confirm the concept with [Anton Babenko](https://github.com/antonbabenko). +3. Install [required tools and plugins](#required-tools-and-plugins-to-simplify-review-process) + ### Prepare basic documentation diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..25c4c5f1f --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,26 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + // Global + "aaron-bond.better-comments", + "gruntfuggly.todo-tree", + "shardulm94.trailing-spaces", + "glenbuktenica.unicode-substitutions", + "editorconfig.editorconfig", + + // Grammar + "streetsidesoftware.code-spell-checker", + "znck.grammarly", + + // Documentation + "bierner.markdown-preview-github-styles", + "yzhang.markdown-all-in-one", + "DavidAnson.vscode-markdownlint", + + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..c1dc51901 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "editor.guides.bracketPairs": "active", + "markdown.extension.toc.unorderedList.marker": "*", + "markdown.extension.toc.levels": "2..6", + "cSpell.language": "en", + "markdownlint.config": { + "code-block-style": false + }, + "markdown.validate.enabled": true, +} From 5ed533aaa6c07a882aaaf76bca9becf11720d6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20PAR=C3=88S?= Date: Tue, 11 Jun 2024 16:00:34 +0200 Subject: [PATCH 054/293] docs: Fix TFLint rules link (#679) --- README.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index f10d92660..b2b7fd3ca 100644 --- a/README.md +++ b/README.md @@ -280,25 +280,25 @@ docker run --rm --entrypoint cat ghcr.io/antonbabenko/pre-commit-terraform:$TAG There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform configurations (both `*.tf` and `*.tfvars`) and Terragrunt configurations (`*.hcl`) in a good shape: -| Hook name | Description | Dependencies
[Install instructions here](#1-install-dependencies) | -| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -| `checkov` and `terraform_checkov` | [checkov](https://github.com/bridgecrewio/checkov) static analysis of terraform templates to spot potential security issues. [Hook notes](#checkov-deprecated-and-terraform_checkov) | `checkov`
Ubuntu deps: `python3`, `python3-pip` | -| `infracost_breakdown` | Check how much your infra costs with [infracost](https://github.com/infracost/infracost). [Hook notes](#infracost_breakdown) | `infracost`, `jq`, [Infracost API key](https://www.infracost.io/docs/#2-get-api-key) | -| `terraform_docs` | Inserts input and output documentation into `README.md`. Recommended. [Hook notes](#terraform_docs) | `terraform-docs` | -| `terraform_docs_replace` | Runs `terraform-docs` and pipes the output directly to README.md. **DEPRECATED**, see [#248](https://github.com/antonbabenko/pre-commit-terraform/issues/248). [Hook notes](#terraform_docs_replace-deprecated) | `python3`, `terraform-docs` | -| `terraform_docs_without_`
`aggregate_type_defaults` | Inserts input and output documentation into `README.md` without aggregate type defaults. Hook notes same as for [terraform_docs](#terraform_docs) | `terraform-docs` | -| `terraform_fmt` | Reformat all Terraform configuration files to a canonical format. [Hook notes](#terraform_fmt) | - | -| `terraform_providers_lock` | Updates provider signatures in [dependency lock files](https://www.terraform.io/docs/cli/commands/providers/lock.html). [Hook notes](#terraform_providers_lock) | - | -| `terraform_tflint` | Validates all Terraform configuration files with [TFLint](https://github.com/terraform-linters/tflint). [Available TFLint rules](https://github.com/terraform-linters/tflint/tree/master/docs/rules#rules). [Hook notes](#terraform_tflint). | `tflint` | -| `terraform_tfsec` | [TFSec](https://github.com/aquasecurity/tfsec) static analysis of terraform templates to spot potential security issues. **DEPRECATED**, use `terraform_trivy`. [Hook notes](#terraform_tfsec-deprecated) | `tfsec` | -| `terraform_trivy` | [Trivy](https://github.com/aquasecurity/trivy) static analysis of terraform templates to spot potential security issues. [Hook notes](#terraform_trivy) | `trivy` | -| `terraform_validate` | Validates all Terraform configuration files. [Hook notes](#terraform_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_providers_lock` | Generates `.terraform.lock.hcl` files using [Terragrunt](https://github.com/gruntwork-io/terragrunt). | `terragrunt` | -| `terraform_wrapper_module_for_each` | Generates Terraform 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 Terraform core, providers, and modules. [Hook notes](#tfupdate) | `tfupdate` | +| Hook name | Description | Dependencies
[Install instructions here](#1-install-dependencies) | +| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| `checkov` and `terraform_checkov` | [checkov](https://github.com/bridgecrewio/checkov) static analysis of terraform templates to spot potential security issues. [Hook notes](#checkov-deprecated-and-terraform_checkov) | `checkov`
Ubuntu deps: `python3`, `python3-pip` | +| `infracost_breakdown` | Check how much your infra costs with [infracost](https://github.com/infracost/infracost). [Hook notes](#infracost_breakdown) | `infracost`, `jq`, [Infracost API key](https://www.infracost.io/docs/#2-get-api-key) | +| `terraform_docs` | Inserts input and output documentation into `README.md`. Recommended. [Hook notes](#terraform_docs) | `terraform-docs` | +| `terraform_docs_replace` | Runs `terraform-docs` and pipes the output directly to README.md. **DEPRECATED**, see [#248](https://github.com/antonbabenko/pre-commit-terraform/issues/248). [Hook notes](#terraform_docs_replace-deprecated) | `python3`, `terraform-docs` | +| `terraform_docs_without_`
`aggregate_type_defaults` | Inserts input and output documentation into `README.md` without aggregate type defaults. Hook notes same as for [terraform_docs](#terraform_docs) | `terraform-docs` | +| `terraform_fmt` | Reformat all Terraform configuration files to a canonical format. [Hook notes](#terraform_fmt) | - | +| `terraform_providers_lock` | Updates provider signatures in [dependency lock files](https://www.terraform.io/docs/cli/commands/providers/lock.html). [Hook notes](#terraform_providers_lock) | - | +| `terraform_tflint` | Validates all Terraform configuration files with [TFLint](https://github.com/terraform-linters/tflint). [Available TFLint rules](https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/README.md). [Hook notes](#terraform_tflint). | `tflint` | +| `terraform_tfsec` | [TFSec](https://github.com/aquasecurity/tfsec) static analysis of terraform templates to spot potential security issues. **DEPRECATED**, use `terraform_trivy`. [Hook notes](#terraform_tfsec-deprecated) | `tfsec` | +| `terraform_trivy` | [Trivy](https://github.com/aquasecurity/trivy) static analysis of terraform templates to spot potential security issues. [Hook notes](#terraform_trivy) | `trivy` | +| `terraform_validate` | Validates all Terraform configuration files. [Hook notes](#terraform_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_providers_lock` | Generates `.terraform.lock.hcl` files using [Terragrunt](https://github.com/gruntwork-io/terragrunt). | `terragrunt` | +| `terraform_wrapper_module_for_each` | Generates Terraform 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 Terraform core, providers, and modules. [Hook notes](#tfupdate) | `tfupdate` | Check the [source file](https://github.com/antonbabenko/pre-commit-terraform/blob/master/.pre-commit-hooks.yaml) to know arguments used for each hook. From a139b71bc722ac1d2d5ed89caeb74d66a882bb94 Mon Sep 17 00:00:00 2001 From: Josh Reichardt Date: Wed, 19 Jun 2024 08:23:03 -0500 Subject: [PATCH 055/293] feat: Add `terragrunt_validate_inputs` hook to check unused and undefined inputs (#677) --- .pre-commit-hooks.yaml | 8 ++++ README.md | 26 +++++++++- hooks/terragrunt_validate_inputs.sh | 73 +++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100755 hooks/terragrunt_validate_inputs.sh diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index cbe506dd3..520c3f08a 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -85,6 +85,14 @@ 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: terragrunt_providers_lock name: Terragrunt providers lock description: Updates provider signatures in dependency lock files using terragrunt. diff --git a/README.md b/README.md index b2b7fd3ca..11267ed34 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ If you want to support the development of `pre-commit-terraform` and [many other * [terrascan](#terrascan) * [tfupdate](#tfupdate) * [terragrunt\_providers\_lock](#terragrunt_providers_lock) + * [terragrunt\_validate\_inputs](#terragrunt_validate_inputs) * [Docker Usage](#docker-usage) * [File Permissions](#file-permissions) * [Download Terraform modules from private GitHub repositories](#download-terraform-modules-from-private-github-repositories) @@ -75,7 +76,7 @@ If you want to support the development of `pre-commit-terraform` and [many other

* [`checkov`](https://github.com/bridgecrewio/checkov) required for `terraform_checkov` hook * [`terraform-docs`](https://github.com/terraform-docs/terraform-docs) required for `terraform_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_valid_inputs` hooks * [`terrascan`](https://github.com/tenable/terrascan) required for `terrascan` hook * [`TFLint`](https://github.com/terraform-linters/tflint) required for `terraform_tflint` hook * [`TFSec`](https://github.com/liamg/tfsec) required for `terraform_tfsec` hook @@ -295,6 +296,7 @@ There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform | `terraform_validate` | Validates all Terraform configuration files. [Hook notes](#terraform_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](https://github.com/gruntwork-io/terragrunt) unused and undefined inputs (`*.hcl`) | `terragrunt_providers_lock` | Generates `.terraform.lock.hcl` files using [Terragrunt](https://github.com/gruntwork-io/terragrunt). | `terragrunt` | | `terraform_wrapper_module_for_each` | Generates Terraform 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` | @@ -1121,6 +1123,28 @@ It invokes `terragrunt providers lock` under the hood and terragrunt [does its' - --args=-platform=linux_amd64 ``` +### terragrunt_validate_inputs + +Validates Terragrunt unused and undefined inputs. This is useful for keeping +configs clean when module versions change or if configs are copied. + +See the [Terragrunt docs](https://terragrunt.gruntwork.io/docs/reference/cli-options/#validate-inputs) for more details. + +Example: + +```yaml +- id: terragrunt_validate_inputs + name: Terragrunt validate inputs + args: + # Optionally check for unused inputs + - --args=--terragrunt-strict-validate +``` + +> [!NOTE] +> This hook requires authentication to a given account if defined by config to work properly. For example, if you use a third-party tool to store AWS credentials like `aws-vault` you must be authenticated first. +> +> See docs for the [iam_role](https://terragrunt.gruntwork.io/docs/reference/config-blocks-and-attributes/#iam_role) attribute and [--terragrunt-iam-role](https://terragrunt.gruntwork.io/docs/reference/cli-options/#terragrunt-iam-role) flag for more. + ## Docker Usage ### File Permissions diff --git a/hooks/terragrunt_validate_inputs.sh b/hooks/terragrunt_validate_inputs.sh new file mode 100755 index 000000000..0c8d79e2d --- /dev/null +++ b/hooks/terragrunt_validate_inputs.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -eo pipefail + +# globals variables +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCRIPT_DIR +# 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 + + # shellcheck disable=SC2153 # False positive + common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" +} + +####################################################################### +# 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. +# parallelism_disabled (bool) if true - skip lock mechanism +# args (array) arguments that configure wrapped tool behavior +# tf_path (string) PATH to Terraform/OpenTofu binary +# 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" + # shellcheck disable=SC2034 # Unused var. + local -r parallelism_disabled="$3" + # shellcheck disable=SC2034 # Unused var. + local -r tf_path="$4" + shift 4 + local -a -r args=("$@") + + # pass the arguments to hook + terragrunt validate-inputs "${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 validate-inputs "${args[@]}" + + # return exit code to common::per_dir_hook + local exit_code=$? + return $exit_code +} + +[ "${BASH_SOURCE[0]}" != "$0" ] || main "$@" From f86126395b31dd359ef9bcd8436d63feef7191d7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 19 Jun 2024 13:23:39 +0000 Subject: [PATCH 056/293] chore(release): version 1.92.0 [skip ci] # [1.92.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.91.0...v1.92.0) (2024-06-19) ### Features * Add `terragrunt_validate_inputs` hook to check unused and undefined inputs ([#677](https://github.com/antonbabenko/pre-commit-terraform/issues/677)) ([a139b71](https://github.com/antonbabenko/pre-commit-terraform/commit/a139b71bc722ac1d2d5ed89caeb74d66a882bb94)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce9173587..4dac95e2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.92.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.91.0...v1.92.0) (2024-06-19) + + +### Features + +* Add `terragrunt_validate_inputs` hook to check unused and undefined inputs ([#677](https://github.com/antonbabenko/pre-commit-terraform/issues/677)) ([a139b71](https://github.com/antonbabenko/pre-commit-terraform/commit/a139b71bc722ac1d2d5ed89caeb74d66a882bb94)) + # [1.91.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.90.0...v1.91.0) (2024-06-07) From c379356298cfa89c1af9d77f74936eab0c0a966c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:44:32 +0300 Subject: [PATCH 057/293] chore(deps): update actions/checkout action to v4.1.7 (#681) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- .github/workflows/build-image.yaml | 2 +- .github/workflows/pre-commit.yaml | 4 ++-- .github/workflows/release.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 18133154c..4b690f3a4 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 764e04b77..99ee14627 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Set up Docker Buildx diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 6f73f9f61..99e246818 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - run: | git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* @@ -31,7 +31,7 @@ jobs: 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/ # Need to success pre-commit fix push - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54fabc423..c6e8f431f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: persist-credentials: false fetch-depth: 0 From b035f8a400c79726846945c8a0e6f8dec24f943f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:44:59 +0300 Subject: [PATCH 058/293] chore(deps): update amannn/action-semantic-pull-request action to v5.5.2 (#682) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pr-title.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 7a8b64289..52568beef 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@cfb60706e18bc85e8aec535e3c577abe8f70378e # v5.5.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: From 2608dfb039886f4831454e71e11954f957e00d78 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:46:36 +0300 Subject: [PATCH 059/293] chore(deps): update docker/login-action action to v3.2.0 (#684) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 99ee14627..f0672fcdc 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -19,7 +19,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Login to GitHub Container Registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From 7b74a62002d83b61655f2c4bb59d7c4edbe5ff5b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:47:17 +0300 Subject: [PATCH 060/293] chore(deps): update docker/setup-qemu-action action to v2.2.0 (#685) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 4b690f3a4..1f22769ab 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -31,7 +31,7 @@ jobs: - name: Set up QEMU if: matrix.os != 'ubuntu-latest' || matrix.arch != 'amd64' - uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2 + uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 with: platforms: 'arm64' From a2c106d7709b9f1cc6d126ac5feac420d9e3a585 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:52:27 +0300 Subject: [PATCH 061/293] chore(deps): update tj-actions/changed-files action to v44.5.2 (#686) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 1f22769ab..a208e8e1d 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -20,7 +20,7 @@ jobs: - name: Get changed Docker related files id: changed-files-specific - uses: tj-actions/changed-files@0874344d6ebbaa00a27da73276ae7162fadcaf69 # v44.3.0 + uses: tj-actions/changed-files@d6babd6899969df1a11d14c368283ea4436bca78 # v44.5.2 with: files: | Dockerfile From 3077272e27f5beb503f89c7e6c611b280122b421 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:53:52 +0300 Subject: [PATCH 062/293] chore(deps): update docker/setup-qemu-action action to v3 (#687) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index a208e8e1d..ba8dc81b6 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -31,7 +31,7 @@ jobs: - name: Set up QEMU if: matrix.os != 'ubuntu-latest' || matrix.arch != 'amd64' - uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 with: platforms: 'arm64' From 2bdd384971c6d70461a6b395d697413766200e09 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:54:11 +0300 Subject: [PATCH 063/293] chore(deps): update docker/build-push-action action to v5.4.0 (#683) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 4 ++-- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index ba8dc81b6..517ecddd2 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -40,7 +40,7 @@ jobs: - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 + uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 with: context: . build-args: | @@ -74,7 +74,7 @@ jobs: # https://github.com/docker/buildx/issues/59#issuecomment-1433097926 - name: Build Multi-arch docker-image if: steps.changed-files-specific.outputs.any_changed == 'true' && matrix.os == 'ubuntu-latest' && matrix.arch == 'amd64' - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 + uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 with: context: . build-args: | diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index f0672fcdc..347e0bb55 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -33,7 +33,7 @@ jobs: - name: Build and Push release if: github.event_name != 'schedule' - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 + uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 with: context: . build-args: | @@ -50,7 +50,7 @@ jobs: - name: Build and Push nightly if: github.event_name == 'schedule' - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 + uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 with: context: . build-args: | From 59953d67e8504c36440f5ca6dc86f50492f169e9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 21:29:40 +0300 Subject: [PATCH 064/293] chore(deps): update docker/build-push-action action to v6 (#689) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 4 ++-- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 517ecddd2..bba8da1a9 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -40,7 +40,7 @@ jobs: - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 + uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 with: context: . build-args: | @@ -74,7 +74,7 @@ jobs: # https://github.com/docker/buildx/issues/59#issuecomment-1433097926 - name: Build Multi-arch docker-image if: steps.changed-files-specific.outputs.any_changed == 'true' && matrix.os == 'ubuntu-latest' && matrix.arch == 'amd64' - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 + uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 with: context: . build-args: | diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 347e0bb55..34d535c7e 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -33,7 +33,7 @@ jobs: - name: Build and Push release if: github.event_name != 'schedule' - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 + uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 with: context: . build-args: | @@ -50,7 +50,7 @@ jobs: - name: Build and Push nightly if: github.event_name == 'schedule' - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 + uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 with: context: . build-args: | From a63e5a73ed028226bf72c6eac01c31193988c995 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 21:44:38 +0300 Subject: [PATCH 065/293] chore(deps): update maxymvlasov/dive-action action to v1 (#690) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index bba8da1a9..60cc8edef 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -64,7 +64,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@379af3fc636888ada5899c997e8b52db6ad45023 # v1.0.1 with: image: ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} config-file: ${{ github.workspace }}/.github/.dive-ci.yaml From 2d7a2fb25636448b1131e623553e72c615c6030d Mon Sep 17 00:00:00 2001 From: Andriy Nazaruk <42734108+anazaruk2907@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:31:19 +0300 Subject: [PATCH 066/293] docs: Fix typo and check all development flow (#694) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 11267ed34..b41292ad8 100644 --- a/README.md +++ b/README.md @@ -788,7 +788,7 @@ To replicate functionality in `terraform_docs` hook: - --args=--config=__GIT_WORKING_DIR__/.tflint.hcl ``` -3. By default, pre-commit-terraform performs directory switching into the terraform 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-terraform performs directory switching into the terraform 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: terraform_tflint From 6ff3572afb0a70c6fe4c6a0524d1f332a4f8fb6c Mon Sep 17 00:00:00 2001 From: Lucas Ponce Date: Thu, 1 Aug 2024 14:18:01 -0300 Subject: [PATCH 067/293] fix(`terraform_docs`): Suppress "terraform command not found" error message in case binary does not exist (#693) --- hooks/terraform_docs.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 472d6487c..9fe0de440 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -47,7 +47,10 @@ function terraform_docs_ { IFS=";" read -r -a configs <<< "$hook_config" local hack_terraform_docs - hack_terraform_docs=$(terraform version | sed -n 1p | grep -c 0.12) || true + local hack_terraform_docs=0 + if [[ $(\command -V terraform 2> /dev/null) ]]; then + hack_terraform_docs=$(terraform version | sed -n 1p | grep -c 0.12) || true + fi if [[ ! $(command -v terraform-docs) ]]; then echo "ERROR: terraform-docs is required by terraform_docs pre-commit hook but is not installed or in the system's PATH." From debe478691c6711d19679b9973309e19368e79b6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 1 Aug 2024 17:18:31 +0000 Subject: [PATCH 068/293] chore(release): version 1.92.1 [skip ci] ## [1.92.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.0...v1.92.1) (2024-08-01) ### Bug Fixes * **`terraform_docs`:** Suppress "terraform command not found" error message in case binary does not exist ([#693](https://github.com/antonbabenko/pre-commit-terraform/issues/693)) ([6ff3572](https://github.com/antonbabenko/pre-commit-terraform/commit/6ff3572afb0a70c6fe4c6a0524d1f332a4f8fb6c)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dac95e2a..a864a936f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.92.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.0...v1.92.1) (2024-08-01) + + +### Bug Fixes + +* **`terraform_docs`:** Suppress "terraform command not found" error message in case binary does not exist ([#693](https://github.com/antonbabenko/pre-commit-terraform/issues/693)) ([6ff3572](https://github.com/antonbabenko/pre-commit-terraform/commit/6ff3572afb0a70c6fe4c6a0524d1f332a4f8fb6c)) + # [1.92.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.91.0...v1.92.0) (2024-06-19) From 89704613a746e1244aab916c44c11a400889eb22 Mon Sep 17 00:00:00 2001 From: cwar Date: Wed, 7 Aug 2024 11:31:25 -0400 Subject: [PATCH 069/293] docs: Fix broken github actions link (#695) small typo in the link for the workflow yaml file (`yml` -> `yaml`). --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b41292ad8..291f5ba06 100644 --- a/README.md +++ b/README.md @@ -1207,7 +1207,7 @@ docker run --rm -e "USERID=$(id -u):$(id -g)" -v ~/.netrc:/root/.netrc -v $(pwd) You can use this hook in your GitHub Actions workflow togehther with [pre-commit](https://pre-commit.com). To easy up dependency management, you can use the managed [docker image](#docker-usage) within your workflow. Make sure to set the image tag to the version you want to use. -In this repository's pre-commit [workflow file](.github/workflows/pre-commit.yml) we run pre-commit without the container image. +In this repository's pre-commit [workflow file](.github/workflows/pre-commit.yaml) we run pre-commit without the container image. Here is an example that use the container image, includes caching of pre-commit dependencies and uses the `pre-commit` command to run the checks (but fixes will be not automatically push back to your branch, when it possible): From 9d6a22badbd9693a72c2519eb7dde01d10db57b2 Mon Sep 17 00:00:00 2001 From: Steven Swor <1486524+sworisbreathing@users.noreply.github.com> Date: Sat, 17 Aug 2024 03:41:53 +1000 Subject: [PATCH 070/293] fix(`terraform_docs`): Fix issue and prioritize `output.file` setting from `.terraform-docs.yml` config over `--hook-config=--path-to-file=` (#698) --------- Co-authored-by: MaxymVlasov --- hooks/terraform_docs.sh | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 9fe0de440..657853983 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -121,6 +121,7 @@ function terraform_docs { # Get hook settings # local text_file="README.md" + local use_path_to_file=false local add_to_existing=false local create_if_not_exist=false local use_standard_markers=false @@ -136,6 +137,7 @@ function terraform_docs { case $key in --path-to-file) text_file=$value + use_path_to_file=true ;; --add-to-existing-file) add_to_existing=$value @@ -159,13 +161,29 @@ function terraform_docs { if [[ "$args" != *"--config"* ]]; then local tf_docs_formatter="md" - # Suppress terraform_docs color else local config_file=${args#*--config} config_file=${config_file#*=} config_file=${config_file% *} + # Prioritize `.terraform-docs.yml` `output.file` over + # `--hook-config=--path-to-file=` if it set + local output_file + # Get latest non-commented `output.file` from `.terraform-docs.yml` + output_file=$(grep -A1000 -e '^output:$' "$config_file" | grep -E '^[[:space:]]+file:' | tail -n 1) || true + + if [[ $output_file ]]; then + # Extract filename from `output.file` line + text_file=$(echo "$output_file" | awk -F':' '{print $2}' | tr -d '[:space:]"' | tr -d "'") + + if [[ $use_path_to_file ]]; then + common::colorify "yellow" "NOTE: You set both '--hook-config=--path-to-file=' and 'output.file' in '$config_file'" + common::colorify "yellow" " 'output.file' from '$config_file' will be used." + fi + fi + + # Suppress terraform_docs color local config_file_no_color config_file_no_color="$config_file$(date +%s).yml" @@ -228,7 +246,7 @@ function terraform_docs { if [[ "$terraform_docs_awk_file" == "0" ]]; then # shellcheck disable=SC2086 - terraform-docs $tf_docs_formatter $args ./ > "$tmp_file" + terraform-docs --output-file="" $tf_docs_formatter $args ./ > "$tmp_file" else # Can't append extension for mktemp, so renaming instead local tmp_file_docs @@ -239,7 +257,7 @@ function terraform_docs { awk -f "$terraform_docs_awk_file" ./*.tf > "$tmp_file_docs_tf" # shellcheck disable=SC2086 - terraform-docs $tf_docs_formatter $args "$tmp_file_docs_tf" > "$tmp_file" + terraform-docs --output-file="" $tf_docs_formatter $args "$tmp_file_docs_tf" > "$tmp_file" rm -f "$tmp_file_docs_tf" fi From f809e53ddfcab9fbeb0d58adaf0c44f5dfc8e706 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 16 Aug 2024 17:42:23 +0000 Subject: [PATCH 071/293] chore(release): version 1.92.2 [skip ci] ## [1.92.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.1...v1.92.2) (2024-08-16) ### Bug Fixes * **`terraform_docs`:** Fix issue and prioritize `output.file` setting from `.terraform-docs.yml` config over `--hook-config=--path-to-file=` ([#698](https://github.com/antonbabenko/pre-commit-terraform/issues/698)) ([9d6a22b](https://github.com/antonbabenko/pre-commit-terraform/commit/9d6a22badbd9693a72c2519eb7dde01d10db57b2)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a864a936f..1061c4c5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.92.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.1...v1.92.2) (2024-08-16) + + +### Bug Fixes + +* **`terraform_docs`:** Fix issue and prioritize `output.file` setting from `.terraform-docs.yml` config over `--hook-config=--path-to-file=` ([#698](https://github.com/antonbabenko/pre-commit-terraform/issues/698)) ([9d6a22b](https://github.com/antonbabenko/pre-commit-terraform/commit/9d6a22badbd9693a72c2519eb7dde01d10db57b2)) + ## [1.92.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.0...v1.92.1) (2024-08-01) From 59b2454e076a9d26ad93d0ca4037746fd7f5962d Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 27 Aug 2024 20:16:38 +0300 Subject: [PATCH 072/293] fix(`terraform_docs`): Suppress redundant warnings pop-ups introduced in v1.92.2 (#700) --- hooks/terraform_docs.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 657853983..770f947a5 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -175,12 +175,14 @@ function terraform_docs { if [[ $output_file ]]; then # Extract filename from `output.file` line - text_file=$(echo "$output_file" | awk -F':' '{print $2}' | tr -d '[:space:]"' | tr -d "'") + output_file=$(echo "$output_file" | awk -F':' '{print $2}' | tr -d '[:space:]"' | tr -d "'") - if [[ $use_path_to_file ]]; then - common::colorify "yellow" "NOTE: You set both '--hook-config=--path-to-file=' and 'output.file' in '$config_file'" + if [[ $use_path_to_file == true && "$output_file" != "$text_file" ]]; then + common::colorify "yellow" "NOTE: You set both '--hook-config=--path-to-file=$text_file' and 'output.file: $output_file' in '$config_file'" common::colorify "yellow" " 'output.file' from '$config_file' will be used." fi + + text_file=$output_file fi # Suppress terraform_docs color From de6a8d9174258b777c54ba67ad05fb7c219834fa Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 27 Aug 2024 17:17:12 +0000 Subject: [PATCH 073/293] chore(release): version 1.92.3 [skip ci] ## [1.92.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.2...v1.92.3) (2024-08-27) ### Bug Fixes * **`terraform_docs`:** Suppress redundant warnings pop-ups introduced in v1.92.2 ([#700](https://github.com/antonbabenko/pre-commit-terraform/issues/700)) ([59b2454](https://github.com/antonbabenko/pre-commit-terraform/commit/59b2454e076a9d26ad93d0ca4037746fd7f5962d)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1061c4c5b..80955204a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.92.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.2...v1.92.3) (2024-08-27) + + +### Bug Fixes + +* **`terraform_docs`:** Suppress redundant warnings pop-ups introduced in v1.92.2 ([#700](https://github.com/antonbabenko/pre-commit-terraform/issues/700)) ([59b2454](https://github.com/antonbabenko/pre-commit-terraform/commit/59b2454e076a9d26ad93d0ca4037746fd7f5962d)) + ## [1.92.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.1...v1.92.2) (2024-08-16) From d03f44facabf31ab7d464468907fb0a5d549e5e7 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 28 Aug 2024 20:17:31 +0300 Subject: [PATCH 074/293] feat(`terraform_docs`): Start seamless migration to `terraform-docs` markers (#701) --------- Co-authored-by: George L. Yermulnik --- README.md | 10 +++++----- hooks/terraform_docs.sh | 43 +++++++++++++++++++++++++++++------------ 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 291f5ba06..4c69a1b46 100644 --- a/README.md +++ b/README.md @@ -568,9 +568,9 @@ Unlike most other hooks, this hook triggers once if there are any changed files 1. `terraform_docs` and `terraform_docs_without_aggregate_type_defaults` will insert/update documentation generated by [terraform-docs](https://github.com/terraform-docs/terraform-docs) framed by markers: ```txt - + - + ``` if they are present in `README.md`. @@ -581,8 +581,8 @@ Unlike most other hooks, this hook triggers once if there are any changed files * create a documentation file * extend existing documentation file by appending markers to the end of the file (see item 1 above) * use different filename for the documentation (default is `README.md`) - * use the same insertion markers as `terraform-docs` by default. It will be default in `v2.0`. - To migrate to `terraform-docs` insertion markers, run in repo root: + * use the same insertion markers as `terraform-docs`. It's default starting from `v1.93`. + To migrate everything to `terraform-docs` insertion markers, run in repo root: ```bash grep -rl 'BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK' . | xargs sed -i 's/BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK/BEGIN_TF_DOCS/g' @@ -595,7 +595,7 @@ Unlike most other hooks, this hook triggers once if there are any changed files - --hook-config=--path-to-file=README.md # Valid UNIX path. I.e. ../TFDOC.md or docs/README.md etc. - --hook-config=--add-to-existing-file=true # Boolean. true or false - --hook-config=--create-file-if-not-exist=true # Boolean. true or false - - --hook-config=--use-standard-markers=true # Boolean. Defaults in v1.x to false. Set to true for compatibility with terraform-docs + - --hook-config=--use-standard-markers=true # Boolean. Defaults to true (v1.93+), false ( /dev/null && continue + replace_old_markers "$text_file" + # # If `--add-to-existing-file=true` set, check is in file exist "hook markers", # and if not - append "hook markers" to the end of file. From 99fceb87b07cdbb97111c3b4eb17d3f70d36e13c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 28 Aug 2024 17:18:12 +0000 Subject: [PATCH 075/293] chore(release): version 1.93.0 [skip ci] # [1.93.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.3...v1.93.0) (2024-08-28) ### Features * **`terraform_docs`:** Start seamless migration to `terraform-docs` markers ([#701](https://github.com/antonbabenko/pre-commit-terraform/issues/701)) ([d03f44f](https://github.com/antonbabenko/pre-commit-terraform/commit/d03f44facabf31ab7d464468907fb0a5d549e5e7)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80955204a..6fdb8041f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.93.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.3...v1.93.0) (2024-08-28) + + +### Features + +* **`terraform_docs`:** Start seamless migration to `terraform-docs` markers ([#701](https://github.com/antonbabenko/pre-commit-terraform/issues/701)) ([d03f44f](https://github.com/antonbabenko/pre-commit-terraform/commit/d03f44facabf31ab7d464468907fb0a5d549e5e7)) + ## [1.92.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.2...v1.92.3) (2024-08-27) From 3c8734dc55e69bcfc70eceff485768a0ee89e811 Mon Sep 17 00:00:00 2001 From: Christian Schroer <1319445+cschroer@users.noreply.github.com> Date: Thu, 29 Aug 2024 21:31:48 +0200 Subject: [PATCH 076/293] fix(`terraform_docs`): Fix non-GNU `sed` issues, introduced in v1.93.0 (#704) --------- Co-authored-by: George L. Yermulnik --- README.md | 4 ++-- hooks/terraform_docs.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4c69a1b46..0b262520b 100644 --- a/README.md +++ b/README.md @@ -585,8 +585,8 @@ Unlike most other hooks, this hook triggers once if there are any changed files To migrate everything to `terraform-docs` insertion markers, run in repo root: ```bash - grep -rl 'BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK' . | xargs sed -i 's/BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK/BEGIN_TF_DOCS/g' - grep -rl 'END OF PRE-COMMIT-TERRAFORM DOCS HOOK' . | xargs sed -i 's/END OF PRE-COMMIT-TERRAFORM DOCS HOOK/END_TF_DOCS/g' + grep -rl --null 'BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK' . | xargs -0 sed -i'' -e 's/BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK/BEGIN_TF_DOCS/' + grep -rl --null 'END OF PRE-COMMIT-TERRAFORM DOCS HOOK' . | xargs -0 sed -i'' -e 's/END OF PRE-COMMIT-TERRAFORM DOCS HOOK/END_TF_DOCS/' ``` ```yaml diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 0a3dbc298..0b532ce11 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -40,8 +40,8 @@ function main { function replace_old_markers { local -r file=$1 - sed -i "s/^${old_insertion_marker_begin}$/${insertion_marker_begin}/" "$file" - sed -i "s/^${old_insertion_marker_end}$/${insertion_marker_end}/" "$file" + sed -i'' -e "s/^${old_insertion_marker_begin}$/${insertion_marker_begin}/" "$file" + sed -i'' -e "s/^${old_insertion_marker_end}$/${insertion_marker_end}/" "$file" } ####################################################################### From 12a54bea3be8489625a389c8daa099e4ed9bf553 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 29 Aug 2024 19:32:16 +0000 Subject: [PATCH 077/293] chore(release): version 1.93.1 [skip ci] ## [1.93.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.93.0...v1.93.1) (2024-08-29) ### Bug Fixes * **`terraform_docs`:** Fix non-GNU `sed` issues, introduced in v1.93.0 ([#704](https://github.com/antonbabenko/pre-commit-terraform/issues/704)) ([3c8734d](https://github.com/antonbabenko/pre-commit-terraform/commit/3c8734dc55e69bcfc70eceff485768a0ee89e811)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fdb8041f..428c89e1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.93.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.93.0...v1.93.1) (2024-08-29) + + +### Bug Fixes + +* **`terraform_docs`:** Fix non-GNU `sed` issues, introduced in v1.93.0 ([#704](https://github.com/antonbabenko/pre-commit-terraform/issues/704)) ([3c8734d](https://github.com/antonbabenko/pre-commit-terraform/commit/3c8734dc55e69bcfc70eceff485768a0ee89e811)) + # [1.93.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.92.3...v1.93.0) (2024-08-28) From 9e4a82bda4c7884226cc2e8eba0cea873c0fec90 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 29 Aug 2024 22:35:50 +0300 Subject: [PATCH 078/293] chore(`terraform_docs`): Change variable names, to conform to same style (#706) --- hooks/terraform_docs.sh | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 0b532ce11..123902731 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -135,7 +135,7 @@ function terraform_docs { # # Get hook settings # - local text_file="README.md" + local output_file="README.md" local use_path_to_file=false local add_to_existing=false local create_if_not_exist=false @@ -151,7 +151,7 @@ function terraform_docs { case $key in --path-to-file) - text_file=$value + output_file=$value use_path_to_file=true ;; --add-to-existing-file) @@ -186,20 +186,20 @@ function terraform_docs { # Prioritize `.terraform-docs.yml` `output.file` over # `--hook-config=--path-to-file=` if it set - local output_file + local config_output_file # Get latest non-commented `output.file` from `.terraform-docs.yml` - output_file=$(grep -A1000 -e '^output:$' "$config_file" | grep -E '^[[:space:]]+file:' | tail -n 1) || true + config_output_file=$(grep -A1000 -e '^output:$' "$config_file" | grep -E '^[[:space:]]+file:' | tail -n 1) || true - if [[ $output_file ]]; then + if [[ $config_output_file ]]; then # Extract filename from `output.file` line - output_file=$(echo "$output_file" | awk -F':' '{print $2}' | tr -d '[:space:]"' | tr -d "'") + config_output_file=$(echo "$config_output_file" | awk -F':' '{print $2}' | tr -d '[:space:]"' | tr -d "'") - if [[ $use_path_to_file == true && "$output_file" != "$text_file" ]]; then - common::colorify "yellow" "NOTE: You set both '--hook-config=--path-to-file=$text_file' and 'output.file: $output_file' in '$config_file'" + if [[ $use_path_to_file == true && "$config_output_file" != "$output_file" ]]; then + common::colorify "yellow" "NOTE: You set both '--hook-config=--path-to-file=$output_file' and 'output.file: $config_output_file' in '$config_file'" common::colorify "yellow" " 'output.file' from '$config_file' will be used." fi - text_file=$output_file + output_file=$config_output_file fi # Suppress terraform_docs color @@ -225,7 +225,7 @@ function terraform_docs { # # Create file if it not exist and `--create-if-not-exist=true` provided # - if $create_if_not_exist && [[ ! -f "$text_file" ]]; then + if $create_if_not_exist && [[ ! -f "$output_file" ]]; then dir_have_tf_files="$( find . -maxdepth 1 -type f | sed 's|.*\.||' | sort -u | grep -oE '^tf$|^tfvars$' || exit 0 @@ -234,7 +234,7 @@ function terraform_docs { # if no TF files - skip dir [ ! "$dir_have_tf_files" ] && popd > /dev/null && continue - dir="$(dirname "$text_file")" + dir="$(dirname "$output_file")" mkdir -p "$dir" @@ -243,25 +243,25 @@ function terraform_docs { echo -e "# ${PWD##*/}\n" echo "$insertion_marker_begin" echo "$insertion_marker_end" - } >> "$text_file" + } >> "$output_file" fi # If file still not exist - skip dir - [[ ! -f "$text_file" ]] && popd > /dev/null && continue + [[ ! -f "$output_file" ]] && popd > /dev/null && continue - replace_old_markers "$text_file" + replace_old_markers "$output_file" # # If `--add-to-existing-file=true` set, check is in file exist "hook markers", # and if not - append "hook markers" to the end of file. # if $add_to_existing; then - HAVE_MARKER=$(grep -o "$insertion_marker_begin" "$text_file" || exit 0) + HAVE_MARKER=$(grep -o "$insertion_marker_begin" "$output_file" || exit 0) if [ ! "$HAVE_MARKER" ]; then # Use of insertion markers, where addToExisting=true, with no markers in the existing file - echo "$insertion_marker_begin" >> "$text_file" - echo "$insertion_marker_end" >> "$text_file" + echo "$insertion_marker_begin" >> "$output_file" + echo "$insertion_marker_end" >> "$output_file" fi fi @@ -285,10 +285,10 @@ function terraform_docs { # Use of insertion markers to insert the terraform-docs output between the markers # Replace content between markers with the placeholder - https://stackoverflow.com/questions/1212799/how-do-i-extract-lines-between-two-line-delimiters-in-perl#1212834 perl_expression="if (/$insertion_marker_begin/../$insertion_marker_end/) { print \$_ if /$insertion_marker_begin/; print \"I_WANT_TO_BE_REPLACED\\n\$_\" if /$insertion_marker_end/;} else { print \$_ }" - perl -i -ne "$perl_expression" "$text_file" + perl -i -ne "$perl_expression" "$output_file" # Replace placeholder with the content of the file - perl -i -e 'open(F, "'"$tmp_file"'"); $f = join "", ; while(<>){if (/I_WANT_TO_BE_REPLACED/) {print $f} else {print $_};}' "$text_file" + perl -i -e 'open(F, "'"$tmp_file"'"); $f = join "", ; while(<>){if (/I_WANT_TO_BE_REPLACED/) {print $f} else {print $_};}' "$output_file" rm -f "$tmp_file" From 1a1b4a3181065f221568a9bff86319435a4a87e1 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 29 Aug 2024 22:59:14 +0300 Subject: [PATCH 079/293] feat(`terraform-docs`): Add support for `replace` mode for TF 0.12+; Use native saving to file for TF 0.12+. Both requires `terraform-docs` v0.12.0+ which released in 2021. (#705) --- hooks/terraform_docs.sh | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 123902731..ade82c598 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -130,12 +130,11 @@ function terraform_docs { ((index += 1)) done - local -r tmp_file=$(mktemp) - # # Get hook settings # local output_file="README.md" + local output_mode="inject" local use_path_to_file=false local add_to_existing=false local create_if_not_exist=false @@ -202,6 +201,14 @@ function terraform_docs { output_file=$config_output_file fi + # Use `.terraform-docs.yml` `output.mode` if it set + local config_output_mode + config_output_mode=$(grep -A1000 -e '^output:$' "$config_file" | grep -E '^[[:space:]]+mode:' | tail -n 1) || true + if [[ $config_output_mode ]]; then + # Extract mode from `output.mode` line + output_mode=$(echo "$config_output_mode" | awk -F':' '{print $2}' | tr -d '[:space:]"' | tr -d "'") + fi + # Suppress terraform_docs color local config_file_no_color config_file_no_color="$config_file$(date +%s).yml" @@ -267,7 +274,7 @@ function terraform_docs { if [[ "$terraform_docs_awk_file" == "0" ]]; then # shellcheck disable=SC2086 - terraform-docs --output-file="" $tf_docs_formatter $args ./ > "$tmp_file" + terraform-docs --output-mode="$output_mode" --output-file="$output_file" $tf_docs_formatter $args ./ > /dev/null else # Can't append extension for mktemp, so renaming instead local tmp_file_docs @@ -277,20 +284,22 @@ function terraform_docs { tmp_file_docs_tf="$tmp_file_docs.tf" awk -f "$terraform_docs_awk_file" ./*.tf > "$tmp_file_docs_tf" + + local -r tmp_file=$(mktemp) # shellcheck disable=SC2086 terraform-docs --output-file="" $tf_docs_formatter $args "$tmp_file_docs_tf" > "$tmp_file" rm -f "$tmp_file_docs_tf" - fi - # Use of insertion markers to insert the terraform-docs output between the markers - # Replace content between markers with the placeholder - https://stackoverflow.com/questions/1212799/how-do-i-extract-lines-between-two-line-delimiters-in-perl#1212834 - perl_expression="if (/$insertion_marker_begin/../$insertion_marker_end/) { print \$_ if /$insertion_marker_begin/; print \"I_WANT_TO_BE_REPLACED\\n\$_\" if /$insertion_marker_end/;} else { print \$_ }" - perl -i -ne "$perl_expression" "$output_file" + # Use of insertion markers to insert the terraform-docs output between the markers + # Replace content between markers with the placeholder - https://stackoverflow.com/questions/1212799/how-do-i-extract-lines-between-two-line-delimiters-in-perl#1212834 + perl_expression="if (/$insertion_marker_begin/../$insertion_marker_end/) { print \$_ if /$insertion_marker_begin/; print \"I_WANT_TO_BE_REPLACED\\n\$_\" if /$insertion_marker_end/;} else { print \$_ }" + perl -i -ne "$perl_expression" "$output_file" - # Replace placeholder with the content of the file - perl -i -e 'open(F, "'"$tmp_file"'"); $f = join "", ; while(<>){if (/I_WANT_TO_BE_REPLACED/) {print $f} else {print $_};}' "$output_file" + # Replace placeholder with the content of the file + perl -i -e 'open(F, "'"$tmp_file"'"); $f = join "", ; while(<>){if (/I_WANT_TO_BE_REPLACED/) {print $f} else {print $_};}' "$output_file" - rm -f "$tmp_file" + rm -f "$tmp_file" + fi popd > /dev/null done From 505174399a4bb4e91246df55d64525d5edab2427 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 29 Aug 2024 19:59:45 +0000 Subject: [PATCH 080/293] chore(release): version 1.94.0 [skip ci] # [1.94.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.93.1...v1.94.0) (2024-08-29) ### Features * **`terraform-docs`:** Add support for `replace` mode for TF 0.12+; Use native saving to file for TF 0.12+. Both requires `terraform-docs` v0.12.0+ which released in 2021. ([#705](https://github.com/antonbabenko/pre-commit-terraform/issues/705)) ([1a1b4a3](https://github.com/antonbabenko/pre-commit-terraform/commit/1a1b4a3181065f221568a9bff86319435a4a87e1)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 428c89e1d..c3a4d7338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.94.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.93.1...v1.94.0) (2024-08-29) + + +### Features + +* **`terraform-docs`:** Add support for `replace` mode for TF 0.12+; Use native saving to file for TF 0.12+. Both requires `terraform-docs` v0.12.0+ which released in 2021. ([#705](https://github.com/antonbabenko/pre-commit-terraform/issues/705)) ([1a1b4a3](https://github.com/antonbabenko/pre-commit-terraform/commit/1a1b4a3181065f221568a9bff86319435a4a87e1)) + ## [1.93.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.93.0...v1.93.1) (2024-08-29) From c986c5e3440be4bf5a46c7933bb629227a3cd292 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 30 Aug 2024 16:34:05 +0300 Subject: [PATCH 081/293] fix(`terraform_docs`): Fix non-GNU sed issues, introduced in v1.93.0, as previous fix doesn't work correctly (#708) --- README.md | 5 +++-- hooks/terraform_docs.sh | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0b262520b..66d6ea39c 100644 --- a/README.md +++ b/README.md @@ -585,8 +585,9 @@ Unlike most other hooks, this hook triggers once if there are any changed files To migrate everything to `terraform-docs` insertion markers, run in repo root: ```bash - grep -rl --null 'BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK' . | xargs -0 sed -i'' -e 's/BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK/BEGIN_TF_DOCS/' - grep -rl --null 'END OF PRE-COMMIT-TERRAFORM DOCS HOOK' . | xargs -0 sed -i'' -e 's/END OF PRE-COMMIT-TERRAFORM DOCS HOOK/END_TF_DOCS/' + sed --version &> /dev/null && SED_CMD=(sed -i) || SED_CMD=(sed -i '') + grep -rl --null 'BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK' . | xargs -0 "${SED_CMD[@]}" -e 's/BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK/BEGIN_TF_DOCS/' + grep -rl --null 'END OF PRE-COMMIT-TERRAFORM DOCS HOOK' . | xargs -0 "${SED_CMD[@]}" -e 's/END OF PRE-COMMIT-TERRAFORM DOCS HOOK/END_TF_DOCS/' ``` ```yaml diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index ade82c598..4c402549e 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -40,8 +40,10 @@ function main { function replace_old_markers { local -r file=$1 - sed -i'' -e "s/^${old_insertion_marker_begin}$/${insertion_marker_begin}/" "$file" - sed -i'' -e "s/^${old_insertion_marker_end}$/${insertion_marker_end}/" "$file" + # Determine the appropriate sed command based on the operating system (GNU sed or BSD sed) + sed --version &> /dev/null && SED_CMD=(sed -i) || SED_CMD=(sed -i '') + "${SED_CMD[@]}" -e "s/^${old_insertion_marker_begin}$/${insertion_marker_begin}/" "$file" + "${SED_CMD[@]}" -e "s/^${old_insertion_marker_end}$/${insertion_marker_end}/" "$file" } ####################################################################### From de816af0fa1e042a4eaaa357b4e508fd39aa2a2c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 30 Aug 2024 13:34:33 +0000 Subject: [PATCH 082/293] chore(release): version 1.94.1 [skip ci] ## [1.94.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.0...v1.94.1) (2024-08-30) ### Bug Fixes * **`terraform_docs`:** Fix non-GNU sed issues, introduced in v1.93.0, as previous fix doesn't work correctly ([#708](https://github.com/antonbabenko/pre-commit-terraform/issues/708)) ([c986c5e](https://github.com/antonbabenko/pre-commit-terraform/commit/c986c5e3440be4bf5a46c7933bb629227a3cd292)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3a4d7338..1eac55c6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.94.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.0...v1.94.1) (2024-08-30) + + +### Bug Fixes + +* **`terraform_docs`:** Fix non-GNU sed issues, introduced in v1.93.0, as previous fix doesn't work correctly ([#708](https://github.com/antonbabenko/pre-commit-terraform/issues/708)) ([c986c5e](https://github.com/antonbabenko/pre-commit-terraform/commit/c986c5e3440be4bf5a46c7933bb629227a3cd292)) + # [1.94.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.93.1...v1.94.0) (2024-08-29) From 9d3164c326efc3270b13cfbea58df2e46c060742 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 2 Sep 2024 18:56:06 +0300 Subject: [PATCH 083/293] docs(`terraform_docs`): Clarify requrement of `--args=--config=` (#711) --------- Co-authored-by: George L. Yermulnik --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 66d6ea39c..ffce9e648 100644 --- a/README.md +++ b/README.md @@ -599,7 +599,7 @@ Unlike most other hooks, this hook triggers once if there are any changed files - --hook-config=--use-standard-markers=true # Boolean. Defaults to true (v1.93+), false ( **Warning** > Avoid use `recursive.enabled: true` in config file, that can cause unexpected behavior. -5. If you need some exotic settings, it can be done too. I.e. this one generates HCL files: +5. You can provide [any configuration available in `terraform-docs`](https://terraform-docs.io/user-guide/configuration/) as an argument to `terraform_docs` hook: + + ```yaml + - id: terraform_docs + args: + - --args=--output-mode=replace + ``` + +6. If you need some exotic settings, it can be done too. I.e. this one generates HCL files: ```yaml - id: terraform_docs From 2bca410814fad06f4d9cc9e31123277ae0eed23c Mon Sep 17 00:00:00 2001 From: Robin Bowes Date: Mon, 9 Sep 2024 11:54:00 +0100 Subject: [PATCH 084/293] fix: Support custom TF paths which contains spaces (#714) --- hooks/_common.sh | 4 ++-- hooks/terraform_fmt.sh | 2 +- hooks/terraform_providers_lock.sh | 4 ++-- hooks/terraform_validate.sh | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index c278c3a7a..ffe0490b5 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -536,13 +536,13 @@ function common::terraform_init { # Plugin cache dir can't be written concurrently or read during write # https://github.com/hashicorp/terraform/issues/31964 if [[ -z $TF_PLUGIN_CACHE_DIR || $parallelism_disabled == true ]]; then - init_output=$($tf_path init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) + init_output=$("$tf_path" init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) exit_code=$? else # Locking just doesn't work, and the below works quicker instead. Details: # https://github.com/hashicorp/terraform/issues/31964#issuecomment-1939869453 for i in {1..10}; do - init_output=$($tf_path init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) + init_output=$("$tf_path" init -backend=false "${TF_INIT_ARGS[@]}" 2>&1) exit_code=$? if [ $exit_code -eq 0 ]; then diff --git a/hooks/terraform_fmt.sh b/hooks/terraform_fmt.sh index a39c27fc5..e8f974fd7 100755 --- a/hooks/terraform_fmt.sh +++ b/hooks/terraform_fmt.sh @@ -49,7 +49,7 @@ function per_dir_hook_unique_part { local -a -r args=("$@") # pass the arguments to hook - $tf_path fmt "${args[@]}" + "$tf_path" fmt "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? diff --git a/hooks/terraform_providers_lock.sh b/hooks/terraform_providers_lock.sh index 1cecd1a02..9a4d1b624 100755 --- a/hooks/terraform_providers_lock.sh +++ b/hooks/terraform_providers_lock.sh @@ -140,7 +140,7 @@ function per_dir_hook_unique_part { common::colorify "yellow" "DEPRECATION NOTICE: We introduced '--mode' flag for this hook. Check migration instructions at https://github.com/antonbabenko/pre-commit-terraform#terraform_providers_lock " - common::terraform_init 'terraform providers lock' "$dir_path" "$parallelism_disabled" "$tf_path" || { + common::terraform_init "$tf_path providers lock" "$dir_path" "$parallelism_disabled" "$tf_path" || { exit_code=$? return $exit_code } @@ -155,7 +155,7 @@ Check migration instructions at https://github.com/antonbabenko/pre-commit-terra #? Don't require `tf init` for providers, but required `tf init` for modules #? Mitigated by `function match_validate_errors` from terraform_validate hook # pass the arguments to hook - $tf_path providers lock "${args[@]}" + "$tf_path" providers lock "${args[@]}" # return exit code to common::per_dir_hook exit_code=$? diff --git a/hooks/terraform_validate.sh b/hooks/terraform_validate.sh index e51b5c714..ad792b038 100755 --- a/hooks/terraform_validate.sh +++ b/hooks/terraform_validate.sh @@ -118,7 +118,7 @@ function per_dir_hook_unique_part { # First try `terraform validate` with the hope that all deps are # pre-installed. That is needed for cases when `.terraform/modules` # or `.terraform/providers` missed AND that is expected. - $tf_path validate "${args[@]}" &> /dev/null && { + "$tf_path" validate "${args[@]}" &> /dev/null && { exit_code=$? return $exit_code } @@ -132,11 +132,11 @@ function per_dir_hook_unique_part { if [ "$retry_once_with_cleanup" != "true" ]; then # terraform validate only - validate_output=$($tf_path validate "${args[@]}" 2>&1) + validate_output=$("$tf_path" validate "${args[@]}" 2>&1) exit_code=$? else # terraform validate, plus capture possible errors - validate_output=$($tf_path validate -json "${args[@]}" 2>&1) + validate_output=$("$tf_path" validate -json "${args[@]}" 2>&1) exit_code=$? # Match specific validation errors @@ -159,7 +159,7 @@ function per_dir_hook_unique_part { return $exit_code } - validate_output=$($tf_path validate "${args[@]}" 2>&1) + validate_output=$("$tf_path" validate "${args[@]}" 2>&1) exit_code=$? fi fi From 91b5ba0c184ca6ef14cdca0b6d3266084dbba166 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 9 Sep 2024 10:54:32 +0000 Subject: [PATCH 085/293] chore(release): version 1.94.2 [skip ci] ## [1.94.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.1...v1.94.2) (2024-09-09) ### Bug Fixes * Support custom TF paths which contains spaces ([#714](https://github.com/antonbabenko/pre-commit-terraform/issues/714)) ([2bca410](https://github.com/antonbabenko/pre-commit-terraform/commit/2bca410814fad06f4d9cc9e31123277ae0eed23c)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eac55c6a..6a4831b7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.94.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.1...v1.94.2) (2024-09-09) + + +### Bug Fixes + +* Support custom TF paths which contains spaces ([#714](https://github.com/antonbabenko/pre-commit-terraform/issues/714)) ([2bca410](https://github.com/antonbabenko/pre-commit-terraform/commit/2bca410814fad06f4d9cc9e31123277ae0eed23c)) + ## [1.94.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.0...v1.94.1) (2024-08-30) From 315342e16d8ac8afe67222176e417ea02e415407 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 10 Sep 2024 20:53:39 +0300 Subject: [PATCH 086/293] fix(`terraform_docs`): Restore `--hook-config=--add-to-existing-file` default behavior. Regression from 1.94.0. (#716) --------- Co-authored-by: George L. Yermulnik --- hooks/terraform_docs.sh | 44 ++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 4c402549e..435190a32 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -260,24 +260,40 @@ function terraform_docs { replace_old_markers "$output_file" - # - # If `--add-to-existing-file=true` set, check is in file exist "hook markers", - # and if not - append "hook markers" to the end of file. - # - if $add_to_existing; then - HAVE_MARKER=$(grep -o "$insertion_marker_begin" "$output_file" || exit 0) - - if [ ! "$HAVE_MARKER" ]; then - # Use of insertion markers, where addToExisting=true, with no markers in the existing file - echo "$insertion_marker_begin" >> "$output_file" - echo "$insertion_marker_end" >> "$output_file" - fi - fi - if [[ "$terraform_docs_awk_file" == "0" ]]; then + #? TF 0.12+ and terraform-docs 0.12.0+ + + # + # If `--add-to-existing-file=false` (default behavior), check if "hook markers" exist in file, + # and, if not, skip execution to avoid addition of terraform-docs section, as + # terraform-docs in 'inject' mode adds markers by default if they are not present + # + if [[ $add_to_existing == false ]]; then + have_marker=$(grep -o "$insertion_marker_begin" "$output_file") || unset have_marker + [[ ! $have_marker ]] && continue + fi # shellcheck disable=SC2086 terraform-docs --output-mode="$output_mode" --output-file="$output_file" $tf_docs_formatter $args ./ > /dev/null + else + #? TF 0.12+ and terraform-docs < 0.8 + #? Yes, we don't cover case of TF 0.12+ and terraform-docs 0.8-0.11 + #? but I probably just drop this section in next release of the hook, + #? as there's no sense to support hacks for tool versions which were released more than 3 years ago + + # + # If `--add-to-existing-file=true` set, check if "hook markers" exist in file, + # and, if not, append "hook markers" to the end of the file. + # + if [[ $add_to_existing == true ]]; then + have_marker=$(grep -o "$insertion_marker_begin" "$output_file") || unset have_marker + + if [[ ! $have_marker ]]; then + # Use of insertion markers, when "add_to_existing=true" with no markers in the existing file + echo "$insertion_marker_begin" >> "$output_file" + echo "$insertion_marker_end" >> "$output_file" + fi + fi # Can't append extension for mktemp, so renaming instead local tmp_file_docs tmp_file_docs=$(mktemp "${TMPDIR:-/tmp}/terraform-docs-XXXXXXXXXX") From 7813a73f2b5ad725b9ecf3ed2483c14458c3f33d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 10 Sep 2024 17:54:08 +0000 Subject: [PATCH 087/293] chore(release): version 1.94.3 [skip ci] ## [1.94.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.2...v1.94.3) (2024-09-10) ### Bug Fixes * **`terraform_docs`:** Restore `--hook-config=--add-to-existing-file` default behavior. Regression from 1.94.0. ([#716](https://github.com/antonbabenko/pre-commit-terraform/issues/716)) ([315342e](https://github.com/antonbabenko/pre-commit-terraform/commit/315342e16d8ac8afe67222176e417ea02e415407)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a4831b7c..3232af21f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.94.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.2...v1.94.3) (2024-09-10) + + +### Bug Fixes + +* **`terraform_docs`:** Restore `--hook-config=--add-to-existing-file` default behavior. Regression from 1.94.0. ([#716](https://github.com/antonbabenko/pre-commit-terraform/issues/716)) ([315342e](https://github.com/antonbabenko/pre-commit-terraform/commit/315342e16d8ac8afe67222176e417ea02e415407)) + ## [1.94.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.1...v1.94.2) (2024-09-09) From 81e4572ad4d24fb0066fbfc4626152b6c7d48838 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 11 Sep 2024 19:55:27 +0300 Subject: [PATCH 088/293] feat(`terraform_docs`): Drop support for `terraform-docs` <0.12.0 (#717) --------- Co-authored-by: George L. Yermulnik --- README.md | 2 +- hooks/terraform_docs.sh | 300 +++------------------------------------- 2 files changed, 18 insertions(+), 284 deletions(-) diff --git a/README.md b/README.md index ffce9e648..164fb8cd9 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ If you want to support the development of `pre-commit-terraform` and [many other Hope that it all will work.

* [`checkov`](https://github.com/bridgecrewio/checkov) required for `terraform_checkov` hook -* [`terraform-docs`](https://github.com/terraform-docs/terraform-docs) required for `terraform_docs` hook +* [`terraform-docs`](https://github.com/terraform-docs/terraform-docs) 0.12.0+ required for `terraform_docs` hook * [`terragrunt`](https://terragrunt.gruntwork.io/docs/getting-started/install/) required for `terragrunt_validate` and `terragrunt_valid_inputs` hooks * [`terrascan`](https://github.com/tenable/terrascan) required for `terrascan` hook * [`TFLint`](https://github.com/terraform-linters/tflint) required for `terraform_tflint` hook diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 435190a32..d65bc08e5 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -24,7 +24,7 @@ function main { ARGS[i]=${ARGS[i]/--config=/--config=$(pwd)\/} done # shellcheck disable=SC2153 # False positive - terraform_docs_ "${HOOK_CONFIG[*]}" "${ARGS[*]}" "${FILES[@]}" + terraform_docs "${HOOK_CONFIG[*]}" "${ARGS[*]}" "${FILES[@]}" } ####################################################################### @@ -47,79 +47,25 @@ function replace_old_markers { } ####################################################################### -# Function which prepares hacks for old versions of `terraform` and -# `terraform-docs` that them call `terraform_docs` +# Wrapper around `terraform-docs` tool that checks and changes/creates +# (depending on provided hook_config) terraform documentation in +# Markdown # Arguments: # hook_config (string with array) arguments that configure hook behavior # args (string with array) arguments that configure wrapped tool behavior # files (array) filenames to check ####################################################################### -function terraform_docs_ { +function terraform_docs { local -r hook_config="$1" - local -r args="$2" + local args="$2" shift 2 local -a -r files=("$@") - # Get hook settings - IFS=";" read -r -a configs <<< "$hook_config" - - local hack_terraform_docs - local hack_terraform_docs=0 - if [[ $(\command -V terraform 2> /dev/null) ]]; then - hack_terraform_docs=$(terraform version | sed -n 1p | grep -c 0.12) || true - fi - if [[ ! $(command -v terraform-docs) ]]; then echo "ERROR: terraform-docs is required by terraform_docs pre-commit hook but is not installed or in the system's PATH." exit 1 fi - local is_old_terraform_docs - is_old_terraform_docs=$(terraform-docs version | grep -o "v0.[1-7]\." | tail -1) || true - - if [[ -z "$is_old_terraform_docs" ]]; then # Using terraform-docs 0.8+ (preferred) - - terraform_docs "0" "${configs[*]}" "$args" "${files[@]}" - - elif [[ "$hack_terraform_docs" == "1" ]]; then # Using awk script because terraform-docs is older than 0.8 and terraform 0.12 is used - - if [[ ! $(command -v awk) ]]; then - echo "ERROR: awk is required for terraform-docs hack to work with Terraform 0.12." - exit 1 - fi - - local tmp_file_awk - tmp_file_awk=$(mktemp "${TMPDIR:-/tmp}/terraform-docs-XXXXXXXXXX") - terraform_docs_awk "$tmp_file_awk" - terraform_docs "$tmp_file_awk" "${configs[*]}" "$args" "${files[@]}" - rm -f "$tmp_file_awk" - - else # Using terraform 0.11 and no awk script is needed for that - - terraform_docs "0" "${configs[*]}" "$args" "${files[@]}" - - fi -} - -####################################################################### -# Wrapper around `terraform-docs` tool that check and change/create -# (depends on provided hook_config) terraform documentation in -# markdown format -# Arguments: -# terraform_docs_awk_file (string) filename where awk hack for old -# `terraform-docs` was written. Needed for TF 0.12+. -# Hack skipped when `terraform_docs_awk_file == "0"` -# hook_config (string with array) arguments that configure hook behavior -# args (string with array) arguments that configure wrapped tool behavior -# files (array) filenames to check -####################################################################### -function terraform_docs { - local -r terraform_docs_awk_file="$1" - local -r hook_config="$2" - local args="$3" - shift 3 - local -a -r files=("$@") - local -a paths local index=0 @@ -142,7 +88,7 @@ function terraform_docs { local create_if_not_exist=false local use_standard_markers=true - read -r -a configs <<< "$hook_config" + IFS=";" read -r -a configs <<< "$hook_config" for c in "${configs[@]}"; do @@ -260,64 +206,17 @@ function terraform_docs { replace_old_markers "$output_file" - if [[ "$terraform_docs_awk_file" == "0" ]]; then - #? TF 0.12+ and terraform-docs 0.12.0+ - - # - # If `--add-to-existing-file=false` (default behavior), check if "hook markers" exist in file, - # and, if not, skip execution to avoid addition of terraform-docs section, as - # terraform-docs in 'inject' mode adds markers by default if they are not present - # - if [[ $add_to_existing == false ]]; then - have_marker=$(grep -o "$insertion_marker_begin" "$output_file") || unset have_marker - [[ ! $have_marker ]] && continue - fi - # shellcheck disable=SC2086 - terraform-docs --output-mode="$output_mode" --output-file="$output_file" $tf_docs_formatter $args ./ > /dev/null - - else - #? TF 0.12+ and terraform-docs < 0.8 - #? Yes, we don't cover case of TF 0.12+ and terraform-docs 0.8-0.11 - #? but I probably just drop this section in next release of the hook, - #? as there's no sense to support hacks for tool versions which were released more than 3 years ago - - # - # If `--add-to-existing-file=true` set, check if "hook markers" exist in file, - # and, if not, append "hook markers" to the end of the file. - # - if [[ $add_to_existing == true ]]; then - have_marker=$(grep -o "$insertion_marker_begin" "$output_file") || unset have_marker - - if [[ ! $have_marker ]]; then - # Use of insertion markers, when "add_to_existing=true" with no markers in the existing file - echo "$insertion_marker_begin" >> "$output_file" - echo "$insertion_marker_end" >> "$output_file" - fi - fi - # Can't append extension for mktemp, so renaming instead - local tmp_file_docs - tmp_file_docs=$(mktemp "${TMPDIR:-/tmp}/terraform-docs-XXXXXXXXXX") - mv "$tmp_file_docs" "$tmp_file_docs.tf" - local tmp_file_docs_tf - tmp_file_docs_tf="$tmp_file_docs.tf" - - awk -f "$terraform_docs_awk_file" ./*.tf > "$tmp_file_docs_tf" - - local -r tmp_file=$(mktemp) - # shellcheck disable=SC2086 - terraform-docs --output-file="" $tf_docs_formatter $args "$tmp_file_docs_tf" > "$tmp_file" - rm -f "$tmp_file_docs_tf" - - # Use of insertion markers to insert the terraform-docs output between the markers - # Replace content between markers with the placeholder - https://stackoverflow.com/questions/1212799/how-do-i-extract-lines-between-two-line-delimiters-in-perl#1212834 - perl_expression="if (/$insertion_marker_begin/../$insertion_marker_end/) { print \$_ if /$insertion_marker_begin/; print \"I_WANT_TO_BE_REPLACED\\n\$_\" if /$insertion_marker_end/;} else { print \$_ }" - perl -i -ne "$perl_expression" "$output_file" - - # Replace placeholder with the content of the file - perl -i -e 'open(F, "'"$tmp_file"'"); $f = join "", ; while(<>){if (/I_WANT_TO_BE_REPLACED/) {print $f} else {print $_};}' "$output_file" - - rm -f "$tmp_file" + # + # If `--add-to-existing-file=false` (default behavior), check if "hook markers" exist in file, + # and, if not, skip execution to avoid addition of terraform-docs section, as + # terraform-docs in 'inject' mode adds markers by default if they are not present + # + if [[ $add_to_existing == false ]]; then + have_marker=$(grep -o "$insertion_marker_begin" "$output_file") || unset have_marker + [[ ! $have_marker ]] && continue fi + # shellcheck disable=SC2086 + terraform-docs --output-mode="$output_mode" --output-file="$output_file" $tf_docs_formatter $args ./ > /dev/null popd > /dev/null done @@ -326,169 +225,4 @@ function terraform_docs { rm -f "$config_file_no_color" } -####################################################################### -# Function which creates file with `awk` hacks for old versions of -# `terraform-docs` -# Arguments: -# output_file (string) filename where hack will be written to -####################################################################### -function terraform_docs_awk { - local -r output_file=$1 - - cat << "EOF" > "$output_file" -# This script converts Terraform 0.12 variables/outputs to something suitable for `terraform-docs` -# As of terraform-docs v0.6.0, HCL2 is not supported. This script is a *dirty hack* to get around it. -# https://github.com/terraform-docs/terraform-docs/ -# https://github.com/terraform-docs/terraform-docs/issues/62 -# Script was originally found here: https://github.com/cloudposse/build-harness/blob/master/bin/terraform-docs.awk -{ - if ( $0 ~ /\{/ ) { - braceCnt++ - } - if ( $0 ~ /\}/ ) { - braceCnt-- - } - # ---------------------------------------------------------------------------------------------- - # variable|output "..." { - # ---------------------------------------------------------------------------------------------- - # [END] variable/output block - if (blockCnt > 0 && blockTypeCnt == 0 && blockDefaultCnt == 0) { - if (braceCnt == 0 && blockCnt > 0) { - blockCnt-- - print $0 - } - } - # [START] variable or output block started - if ($0 ~ /^[[:space:]]*(variable|output)[[:space:]][[:space:]]*"(.*?)"/) { - # Normalize the braceCnt and block (should be 1 now) - braceCnt = 1 - blockCnt = 1 - # [CLOSE] "default" and "type" block - blockDefaultCnt = 0 - blockTypeCnt = 0 - # Print variable|output line - print $0 - } - # ---------------------------------------------------------------------------------------------- - # default = ... - # ---------------------------------------------------------------------------------------------- - # [END] multiline "default" continues/ends - if (blockCnt > 0 && blockTypeCnt == 0 && blockDefaultCnt > 0) { - print $0 - # Count opening blocks - blockDefaultCnt += gsub(/\(/, "") - blockDefaultCnt += gsub(/\[/, "") - blockDefaultCnt += gsub(/\{/, "") - # Count closing blocks - blockDefaultCnt -= gsub(/\)/, "") - blockDefaultCnt -= gsub(/\]/, "") - blockDefaultCnt -= gsub(/\}/, "") - } - # [START] multiline "default" statement started - if (blockCnt > 0 && blockTypeCnt == 0 && blockDefaultCnt == 0) { - if ($0 ~ /^[[:space:]][[:space:]]*(default)[[:space:]][[:space:]]*=/) { - if ($3 ~ "null") { - print " default = \"null\"" - } else { - print $0 - # Count opening blocks - blockDefaultCnt += gsub(/\(/, "") - blockDefaultCnt += gsub(/\[/, "") - blockDefaultCnt += gsub(/\{/, "") - # Count closing blocks - blockDefaultCnt -= gsub(/\)/, "") - blockDefaultCnt -= gsub(/\]/, "") - blockDefaultCnt -= gsub(/\}/, "") - } - } - } - # ---------------------------------------------------------------------------------------------- - # type = ... - # ---------------------------------------------------------------------------------------------- - # [END] multiline "type" continues/ends - if (blockCnt > 0 && blockTypeCnt > 0 && blockDefaultCnt == 0) { - # The following 'print $0' would print multiline type definitions - #print $0 - # Count opening blocks - blockTypeCnt += gsub(/\(/, "") - blockTypeCnt += gsub(/\[/, "") - blockTypeCnt += gsub(/\{/, "") - # Count closing blocks - blockTypeCnt -= gsub(/\)/, "") - blockTypeCnt -= gsub(/\]/, "") - blockTypeCnt -= gsub(/\}/, "") - } - # [START] multiline "type" statement started - if (blockCnt > 0 && blockTypeCnt == 0 && blockDefaultCnt == 0) { - if ($0 ~ /^[[:space:]][[:space:]]*(type)[[:space:]][[:space:]]*=/ ) { - if ($3 ~ "object") { - print " type = \"object\"" - } else { - # Convert multiline stuff into single line - if ($3 ~ /^[[:space:]]*list[[:space:]]*\([[:space:]]*$/) { - type = "list" - } else if ($3 ~ /^[[:space:]]*string[[:space:]]*\([[:space:]]*$/) { - type = "string" - } else if ($3 ~ /^[[:space:]]*map[[:space:]]*\([[:space:]]*$/) { - type = "map" - } else { - type = $3 - } - # legacy quoted types: "string", "list", and "map" - if (type ~ /^[[:space:]]*"(.*?)"[[:space:]]*$/) { - print " type = " type - } else { - print " type = \"" type "\"" - } - } - # Count opening blocks - blockTypeCnt += gsub(/\(/, "") - blockTypeCnt += gsub(/\[/, "") - blockTypeCnt += gsub(/\{/, "") - # Count closing blocks - blockTypeCnt -= gsub(/\)/, "") - blockTypeCnt -= gsub(/\]/, "") - blockTypeCnt -= gsub(/\}/, "") - } - } - # ---------------------------------------------------------------------------------------------- - # description = ... - # ---------------------------------------------------------------------------------------------- - # [PRINT] single line "description" - if (blockCnt > 0 && blockTypeCnt == 0 && blockDefaultCnt == 0) { - if ($0 ~ /^[[:space:]][[:space:]]*description[[:space:]][[:space:]]*=/) { - print $0 - } - } - # ---------------------------------------------------------------------------------------------- - # value = ... - # ---------------------------------------------------------------------------------------------- - ## [PRINT] single line "value" - #if (blockCnt > 0 && blockTypeCnt == 0 && blockDefaultCnt == 0) { - # if ($0 ~ /^[[:space:]][[:space:]]*value[[:space:]][[:space:]]*=/) { - # print $0 - # } - #} - # ---------------------------------------------------------------------------------------------- - # Newlines, comments, everything else - # ---------------------------------------------------------------------------------------------- - #if (blockTypeCnt == 0 && blockDefaultCnt == 0) { - # Comments with '#' - if ($0 ~ /^[[:space:]]*#/) { - print $0 - } - # Comments with '//' - if ($0 ~ /^[[:space:]]*\/\//) { - print $0 - } - # Newlines - if ($0 ~ /^[[:space:]]*$/) { - print $0 - } - #} -} -EOF - -} - [ "${BASH_SOURCE[0]}" != "$0" ] || main "$@" From a8ed8946ab802c285d207ea37b15fbc251ff1cf8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 11 Sep 2024 16:55:56 +0000 Subject: [PATCH 089/293] chore(release): version 1.95.0 [skip ci] # [1.95.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.3...v1.95.0) (2024-09-11) ### Features * **`terraform_docs`:** Drop support for `terraform-docs` <0.12.0 ([#717](https://github.com/antonbabenko/pre-commit-terraform/issues/717)) ([81e4572](https://github.com/antonbabenko/pre-commit-terraform/commit/81e4572ad4d24fb0066fbfc4626152b6c7d48838)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3232af21f..3e2dbe7e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.95.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.3...v1.95.0) (2024-09-11) + + +### Features + +* **`terraform_docs`:** Drop support for `terraform-docs` <0.12.0 ([#717](https://github.com/antonbabenko/pre-commit-terraform/issues/717)) ([81e4572](https://github.com/antonbabenko/pre-commit-terraform/commit/81e4572ad4d24fb0066fbfc4626152b6c7d48838)) + ## [1.94.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.2...v1.94.3) (2024-09-10) From bf156b40780275db9b8ab5db6d9ef41cecc78861 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 16 Sep 2024 17:02:55 +0300 Subject: [PATCH 090/293] feat: Expand environment variables in `--args=` which contains lowercase symbols, like `${TF_VAR_lowercase}` (#719) --- hooks/_common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index ffe0490b5..3c71f2b0f 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -128,7 +128,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-Za-z0-9_]*'}' ]]; then # Get `ENV_VAR` from `.*${ENV_VAR}.*` local env_var_name=${arg#*$\{} env_var_name=${env_var_name%%\}*} From 869a106a4c8c48f34f58318a830436142e31e10a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 16 Sep 2024 14:03:27 +0000 Subject: [PATCH 091/293] chore(release): version 1.96.0 [skip ci] # [1.96.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.95.0...v1.96.0) (2024-09-16) ### Features * Expand environment variables in `--args=` which contains lowercase symbols, like `${TF_VAR_lowercase}` ([#719](https://github.com/antonbabenko/pre-commit-terraform/issues/719)) ([bf156b4](https://github.com/antonbabenko/pre-commit-terraform/commit/bf156b40780275db9b8ab5db6d9ef41cecc78861)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e2dbe7e7..30f275829 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.96.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.95.0...v1.96.0) (2024-09-16) + + +### Features + +* Expand environment variables in `--args=` which contains lowercase symbols, like `${TF_VAR_lowercase}` ([#719](https://github.com/antonbabenko/pre-commit-terraform/issues/719)) ([bf156b4](https://github.com/antonbabenko/pre-commit-terraform/commit/bf156b40780275db9b8ab5db6d9ef41cecc78861)) + # [1.95.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.94.3...v1.95.0) (2024-09-11) From 2b1aec86d8a086de4f25b502bdb97345de2eaa27 Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Tue, 17 Sep 2024 05:58:50 -0500 Subject: [PATCH 092/293] fix(`terraform_docs`): Fix issue with processing multiply files without `terraform-docs` markers. Issue introduced in v1.95.0 (#720) A bug in the terraform-docs hook was introduced by the code changes in #717. [A continue statement was added that is not preceded by a `popd` command](https://github.com/antonbabenko/pre-commit-terraform/blob/869a106a4c8c48f34f58318a830436142e31e10a/hooks/terraform_docs.sh#L216). The bug is only triggered when the hook is processing multiple files where one (or more) of the files does not contain the terraform-docs marker statements. The hook fails to process the remaining files because the working directory is not reset to the root of the repository. --- hooks/terraform_docs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index d65bc08e5..72979dc55 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -213,7 +213,7 @@ function terraform_docs { # if [[ $add_to_existing == false ]]; then have_marker=$(grep -o "$insertion_marker_begin" "$output_file") || unset have_marker - [[ ! $have_marker ]] && continue + [[ ! $have_marker ]] && popd > /dev/null && continue fi # shellcheck disable=SC2086 terraform-docs --output-mode="$output_mode" --output-file="$output_file" $tf_docs_formatter $args ./ > /dev/null From 89f45610a846f56273a6c4290b3c42534175949e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 17 Sep 2024 10:59:19 +0000 Subject: [PATCH 093/293] chore(release): version 1.96.1 [skip ci] ## [1.96.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.0...v1.96.1) (2024-09-17) ### Bug Fixes * **`terraform_docs`:** Fix issue with processing multiply files without `terraform-docs` markers. Issue introduced in v1.95.0 ([#720](https://github.com/antonbabenko/pre-commit-terraform/issues/720)) ([2b1aec8](https://github.com/antonbabenko/pre-commit-terraform/commit/2b1aec86d8a086de4f25b502bdb97345de2eaa27)), closes [#717](https://github.com/antonbabenko/pre-commit-terraform/issues/717) [/github.com/antonbabenko/pre-commit-terraform/blob/869a106a4c8c48f34f58318a830436142e31e10a/hooks/terraform_docs.sh#L216](https://github.com//github.com/antonbabenko/pre-commit-terraform/blob/869a106a4c8c48f34f58318a830436142e31e10a/hooks/terraform_docs.sh/issues/L216) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f275829..8fd5ddb99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.96.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.0...v1.96.1) (2024-09-17) + + +### Bug Fixes + +* **`terraform_docs`:** Fix issue with processing multiply files without `terraform-docs` markers. Issue introduced in v1.95.0 ([#720](https://github.com/antonbabenko/pre-commit-terraform/issues/720)) ([2b1aec8](https://github.com/antonbabenko/pre-commit-terraform/commit/2b1aec86d8a086de4f25b502bdb97345de2eaa27)), closes [#717](https://github.com/antonbabenko/pre-commit-terraform/issues/717) [/github.com/antonbabenko/pre-commit-terraform/blob/869a106a4c8c48f34f58318a830436142e31e10a/hooks/terraform_docs.sh#L216](https://github.com//github.com/antonbabenko/pre-commit-terraform/blob/869a106a4c8c48f34f58318a830436142e31e10a/hooks/terraform_docs.sh/issues/L216) + # [1.96.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.95.0...v1.96.0) (2024-09-16) From 068ebb528ae24f46739225317d96bf52526e2614 Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Wed, 30 Oct 2024 23:17:56 +0200 Subject: [PATCH 094/293] chore(`pre-commit`): Update `pre-commit` version (#729) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6944a0adb..00228c809 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.6.0 + rev: v5.0.0 hooks: # Git style - id: check-added-large-files From e87ee4371c9f09daac814845df196a65cac28a7a Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Thu, 31 Oct 2024 21:31:18 +0200 Subject: [PATCH 095/293] fix(WSL): Make parallelism work appropriately (#728) * fix(`common::get_cpu_num`): Do not identify WSL as K8S --------- Co-authored-by: MaxymVlasov --- hooks/_common.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index 3c71f2b0f..25022e329 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -199,7 +199,8 @@ function common::get_cpu_num { local millicpu - if [[ -f /sys/fs/cgroup/cpu/cpu.cfs_quota_us ]]; then + if [[ -f /sys/fs/cgroup/cpu/cpu.cfs_quota_us && + ! -f /proc/sys/fs/binfmt_misc/WSLInterop ]]; then # WSL have cfs_quota_us, but WSL should be checked as usual Linux host # Inside K8s pod or DinD in K8s millicpu=$(< /sys/fs/cgroup/cpu/cpu.cfs_quota_us) @@ -251,7 +252,7 @@ function common::get_cpu_num { fi # On host machine or any other case - # `nproc` - Linux/FreeBSD, `sysctl -n hw.ncpu` - macOS/BSD, `echo 1` - fallback + # `nproc` - Linux/FreeBSD/WSL, `sysctl -n hw.ncpu` - macOS/BSD, `echo 1` - fallback nproc 2> /dev/null || sysctl -n hw.ncpu 2> /dev/null || echo 1 } From e0322737dccad136b751416f9e45d9980fe18435 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 31 Oct 2024 19:31:50 +0000 Subject: [PATCH 096/293] chore(release): version 1.96.2 [skip ci] ## [1.96.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.1...v1.96.2) (2024-10-31) ### Bug Fixes * **WSL:** Make parallelism work appropriately ([#728](https://github.com/antonbabenko/pre-commit-terraform/issues/728)) ([e87ee43](https://github.com/antonbabenko/pre-commit-terraform/commit/e87ee4371c9f09daac814845df196a65cac28a7a)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fd5ddb99..c0f721e6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.96.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.1...v1.96.2) (2024-10-31) + + +### Bug Fixes + +* **WSL:** Make parallelism work appropriately ([#728](https://github.com/antonbabenko/pre-commit-terraform/issues/728)) ([e87ee43](https://github.com/antonbabenko/pre-commit-terraform/commit/e87ee4371c9f09daac814845df196a65cac28a7a)) + ## [1.96.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.0...v1.96.1) (2024-09-17) From 87143fb465503f87d5871b2d579e29b318d2bddf Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 24 Dec 2024 16:06:27 +0200 Subject: [PATCH 097/293] fix(`terraform_docs`): Restore multiply `--hook-config` args support. Regression from v1.95.0 (#731) --- hooks/terraform_docs.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 72979dc55..3d0cc884a 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -93,7 +93,10 @@ function terraform_docs { for c in "${configs[@]}"; do IFS="=" read -r -a config <<< "$c" - key=${config[0]} + # $hook_config receives string like '--foo=bar; --baz=4;' etc. + # It gets split by `;` into array, which we're parsing here ('--foo=bar' ' --baz=4') + # Next line removes leading spaces, to support >1 `--hook-config` args + key="${config[0]## }" value=${config[1]} case $key in From 55d0143972eec4905fdaea2f444f1e88218f9dce Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 24 Dec 2024 14:06:57 +0000 Subject: [PATCH 098/293] chore(release): version 1.96.3 [skip ci] ## [1.96.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.2...v1.96.3) (2024-12-24) ### Bug Fixes * **`terraform_docs`:** Restore multiply `--hook-config` args support. Regression from v1.95.0 ([#731](https://github.com/antonbabenko/pre-commit-terraform/issues/731)) ([87143fb](https://github.com/antonbabenko/pre-commit-terraform/commit/87143fb465503f87d5871b2d579e29b318d2bddf)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0f721e6b..7337a977e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.96.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.2...v1.96.3) (2024-12-24) + + +### Bug Fixes + +* **`terraform_docs`:** Restore multiply `--hook-config` args support. Regression from v1.95.0 ([#731](https://github.com/antonbabenko/pre-commit-terraform/issues/731)) ([87143fb](https://github.com/antonbabenko/pre-commit-terraform/commit/87143fb465503f87d5871b2d579e29b318d2bddf)) + ## [1.96.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.1...v1.96.2) (2024-10-31) From d87bf0282f08d5822b075794217b8d227294e231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Sat, 28 Dec 2024 05:12:43 +0100 Subject: [PATCH 099/293] chore(packaging): Migrate from `setuptools` to `hatchling` (#733) This is another modern build backend. The patch deletes `setup.py` from the repository, and adds configuration for `hatchling` and declares PEP 621 metadata in `pyproject.toml`. It does not attempt to change the previously existing behavior. --- hatch.toml | 12 ++++++++++++ pyproject.toml | 38 ++++++++++++++++++++++++++++++++++++++ setup.py | 33 --------------------------------- 3 files changed, 50 insertions(+), 33 deletions(-) create mode 100644 hatch.toml create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/hatch.toml b/hatch.toml new file mode 100644 index 000000000..488fcb111 --- /dev/null +++ b/hatch.toml @@ -0,0 +1,12 @@ +[build.targets.sdist] +include = [ + 'hooks/*.py', +] +exclude = [ + 'hooks/*.sh', +] + +[build.targets.wheel] +packages = [ + 'hooks/', +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..85c4b8809 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,38 @@ +[build-system] +requires = [ + 'hatchling', +] +build-backend = 'hatchling.build' + +[project] +name = 'pre-commit-terraform' +version = '0.0.0' +# version_format = '{tag}+{gitsha}' +classifiers = [ + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', +] +description = 'Pre-commit hooks for terraform_docs_replace' +dependencies = [ + 'setuptools-git-version', +] + +[[project.authors]] +name = 'Contributors' # FIXME +# email = 'степан@криївка.укр' + +[project.urls] +'GitHub: repo' = 'https://github.com/antonbabenko/pre-commit-terraform' + +[project.readme] +file = 'README.md' +content-type = 'text/markdown' + +[project.scripts] +terraform_docs_replace = 'hooks.terraform_docs_replace:main' diff --git a/setup.py b/setup.py deleted file mode 100644 index 2d88425b9..000000000 --- a/setup.py +++ /dev/null @@ -1,33 +0,0 @@ -from setuptools import find_packages -from setuptools import setup - - -setup( - name='pre-commit-terraform', - description='Pre-commit hooks for terraform_docs_replace', - url='https://github.com/antonbabenko/pre-commit-terraform', - version_format='{tag}+{gitsha}', - - author='Contributors', - - classifiers=[ - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - ], - - packages=find_packages(exclude=('tests*', 'testing*')), - install_requires=[ - 'setuptools-git-version', - ], - entry_points={ - 'console_scripts': [ - 'terraform_docs_replace = hooks.terraform_docs_replace:main', - ], - }, -) From ae5057d56239e7e1a00dea4f77287b43bd6c83d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Sat, 28 Dec 2024 05:22:43 +0100 Subject: [PATCH 100/293] chore(packaging): Fix computing SCM-based version @ the dists (#735) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previously existing attempt to use `setuptools-git-version` never worked as it wasn't integrated correctly — it was added as a runtime dependency while it's meant to be a build dependency. This resulted in 0.0.0 being used all the time. This patch adds a hatchling plugin that implements the original idea in a way that actually works. --- hatch.toml | 7 +++++++ pyproject.toml | 12 +++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/hatch.toml b/hatch.toml index 488fcb111..dca8f8565 100644 --- a/hatch.toml +++ b/hatch.toml @@ -10,3 +10,10 @@ exclude = [ packages = [ 'hooks/', ] + +[metadata.hooks.vcs.urls] +'Source Archive' = 'https://github.com/antonbabenko/pre-commit-terraform/archive/{commit_hash}.tar.gz' +'GitHub: repo' = 'https://github.com/antonbabenko/pre-commit-terraform' + +[version] +source = 'vcs' diff --git a/pyproject.toml b/pyproject.toml index 85c4b8809..20c051ddc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,12 @@ [build-system] requires = [ 'hatchling', + 'hatch-vcs', # setuptools-scm ] build-backend = 'hatchling.build' [project] name = 'pre-commit-terraform' -version = '0.0.0' -# version_format = '{tag}+{gitsha}' classifiers = [ 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 2', @@ -19,17 +18,16 @@ classifiers = [ 'Programming Language :: Python :: Implementation :: PyPy', ] description = 'Pre-commit hooks for terraform_docs_replace' -dependencies = [ - 'setuptools-git-version', +dependencies = [] +dynamic = [ + 'urls', + 'version', ] [[project.authors]] name = 'Contributors' # FIXME # email = 'степан@криївка.укр' -[project.urls] -'GitHub: repo' = 'https://github.com/antonbabenko/pre-commit-terraform' - [project.readme] file = 'README.md' content-type = 'text/markdown' From deeafeaed5b5f2ce960a2e599149e3bbddde1f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Sat, 28 Dec 2024 16:08:42 +0100 Subject: [PATCH 101/293] chore(packaging): Convert project to src-layout (#736) * Drop top import package deprecation warning This patch puts it directly into the check script. In general, it's harmful to have such logic in `__init__.py` since it's executed at import time and would get triggered when interacting with other importables from the same namespace just as well. * Convert project to src-layout This is one of the most resilient Python project structures that offers isolation and prevents accidental local imports during testing [[1]]. [1]: https://blog.ganssle.io/articles/2019/08/test-as-installed.html --- hatch.toml | 7 ++----- hooks/__init__.py | 4 ---- pyproject.toml | 2 +- .../pre_commit_terraform}/terraform_docs_replace.py | 9 +++++++++ 4 files changed, 12 insertions(+), 10 deletions(-) delete mode 100644 hooks/__init__.py rename {hooks => src/pre_commit_terraform}/terraform_docs_replace.py (86%) diff --git a/hatch.toml b/hatch.toml index dca8f8565..053ff6bfb 100644 --- a/hatch.toml +++ b/hatch.toml @@ -1,14 +1,11 @@ [build.targets.sdist] include = [ - 'hooks/*.py', -] -exclude = [ - 'hooks/*.sh', + 'src/', ] [build.targets.wheel] packages = [ - 'hooks/', + 'src/pre_commit_terraform/', ] [metadata.hooks.vcs.urls] diff --git a/hooks/__init__.py b/hooks/__init__.py deleted file mode 100644 index aeb6f9b27..000000000 --- a/hooks/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -print( - '`terraform_docs_replace` hook is DEPRECATED.' - 'For migration instructions see https://github.com/antonbabenko/pre-commit-terraform/issues/248#issuecomment-1290829226' -) diff --git a/pyproject.toml b/pyproject.toml index 20c051ddc..e609e55a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,4 +33,4 @@ file = 'README.md' content-type = 'text/markdown' [project.scripts] -terraform_docs_replace = 'hooks.terraform_docs_replace:main' +terraform_docs_replace = 'pre_commit_terraform.terraform_docs_replace:main' diff --git a/hooks/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py similarity index 86% rename from hooks/terraform_docs_replace.py rename to src/pre_commit_terraform/terraform_docs_replace.py index a9cf6c9bc..3e10913df 100644 --- a/hooks/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -2,6 +2,7 @@ import os import subprocess import sys +import warnings def main(argv=None): @@ -27,6 +28,14 @@ def main(argv=None): parser.add_argument('filenames', nargs='*', help='Filenames to check.') args = parser.parse_args(argv) + warnings.warn( + '`terraform_docs_replace` hook is DEPRECATED.' + 'For migration instructions see ' + 'https://github.com/antonbabenko/pre-commit-terraform/issues/248' + '#issuecomment-1290829226', + category=UserWarning, + ) + dirs = [] for filename in args.filenames: if (os.path.realpath(filename) not in dirs and From e5e882a74f129f3a81dce527b47b05b9f8e34e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Mon, 30 Dec 2024 21:14:22 +0100 Subject: [PATCH 102/293] ci: Disable checks unrunable @ pre-commit.ci (#734) Enabling faster feedback loop for new contributors by enabling pre-commit.ci in this repo. The only visible effect of this is when said app is installed and the service is running the checks, it would skip the checks that rely on the external executables that might be missing in runtime. It's necessary since that testing environment is controlled by someone else. --- .pre-commit-config.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 00228c809..1db1d62b4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,10 @@ +ci: + autoupdate_schedule: quarterly + skip: + - shfmt + - shellcheck + - hadolint + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 From e7db95bab20ef77f06f113bc8cd54f12cbee6ec1 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 30 Dec 2024 22:39:19 +0200 Subject: [PATCH 103/293] chore: Set right values in pyproject.toml form authors and maintainers (#739) --- pyproject.toml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e609e55a9..05559d39a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,8 +25,18 @@ dynamic = [ ] [[project.authors]] -name = 'Contributors' # FIXME -# email = 'степан@криївка.укр' +name = 'Anton Babenko' +email = 'anton@antonbabenko.com' + +[[project.authors]] +name = 'Contributors' + +[[project.maintainers]] +name = 'Maksym Vlasov' + +[[project.maintainers]] +name = 'George L. Yermulnik' +email = 'yz@yz.kiev.ua' [project.readme] file = 'README.md' From 993b8548f0c8822d65d8381874807bf53269c315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Mon, 30 Dec 2024 23:33:15 +0100 Subject: [PATCH 104/293] chore(refactoring): Introduce a Python CLI app layout (#738) * Introduce a Python CLI app layout This includes a structure with purpose-based modules and a standard mechanism for adding more subcommands. When adding a new subcommand, one has to wire the `invoke_cli_app()` and `populate_argument_parser()` from their new module into the mappings defined in `_cli_subcommands.py` and `_cli_parsing.py` respectively. This is the only integration point necessary. `populate_argument_parser()` accepts a subparser instance of `argparse.ArgumentParser()` that a new subcommand would need to attach new arguments into. It does not need to return anything. And the `invoke_cli_app()` hook is called with an instance of `argparse.Namespace()` with all the arguments parsed and pre-processed. This function is supposed to have the main check logic and return an instance of `._structs.ReturnCode()` or `int`. * Add a maintainer's manual into the importable package dir * Simplify subcommand integration to one place Previously, adding a new subcommand required importing it into two separate Python modules, adding them into two mappings, maintaining the same dictionary key string. This is prone to human error and is underintegrated. This patch makes it easier by defining a required subcommand module shape on the typing level. Now, one just need to implement two hook- functions and a constant with specific signatures and import it in a single place. * Drop the `__main__` check per Python docs See https://docs.python.org/3/library/__main__.html#id1. --- .pre-commit-hooks.yaml | 2 +- pyproject.toml | 3 - src/pre_commit_terraform/README.md | 93 +++++++++++++++++++ src/pre_commit_terraform/__main__.py | 9 ++ src/pre_commit_terraform/_cli.py | 50 ++++++++++ src/pre_commit_terraform/_cli_parsing.py | 39 ++++++++ src/pre_commit_terraform/_cli_subcommands.py | 12 +++ src/pre_commit_terraform/_errors.py | 16 ++++ src/pre_commit_terraform/_structs.py | 16 ++++ src/pre_commit_terraform/_types.py | 30 ++++++ .../terraform_docs_replace.py | 58 +++++++----- 11 files changed, 301 insertions(+), 27 deletions(-) create mode 100644 src/pre_commit_terraform/README.md create mode 100644 src/pre_commit_terraform/__main__.py create mode 100644 src/pre_commit_terraform/_cli.py create mode 100644 src/pre_commit_terraform/_cli_parsing.py create mode 100644 src/pre_commit_terraform/_cli_subcommands.py create mode 100644 src/pre_commit_terraform/_errors.py create mode 100644 src/pre_commit_terraform/_structs.py create mode 100644 src/pre_commit_terraform/_types.py diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 520c3f08a..9b8a0cf19 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -37,7 +37,7 @@ name: Terraform docs (overwrite README.md) description: Overwrite content of README.md with terraform-docs. require_serial: true - entry: terraform_docs_replace + entry: python -Im pre_commit_terraform replace-docs language: python files: (\.tf)$ exclude: \.terraform/.*$ diff --git a/pyproject.toml b/pyproject.toml index 05559d39a..e5ff0159a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,3 @@ email = 'yz@yz.kiev.ua' [project.readme] file = 'README.md' content-type = 'text/markdown' - -[project.scripts] -terraform_docs_replace = 'pre_commit_terraform.terraform_docs_replace:main' diff --git a/src/pre_commit_terraform/README.md b/src/pre_commit_terraform/README.md new file mode 100644 index 000000000..392d4221b --- /dev/null +++ b/src/pre_commit_terraform/README.md @@ -0,0 +1,93 @@ +# Maintainer's manual + +## Structure + +This folder is what's called an [importable package]. It's a top-level folder +that ends up being installed into `site-packages/` of virtualenvs. + +When the Git repository is `pip install`ed, this [import package] becomes +available for use within respective Python interpreter instance. It can be +imported and sub-modules can be imported through the dot-syntax. Additionally, +the modules within can import the neighboring ones using relative imports that +have a leading dot in them. + +It additionally implements a [runpy interface], meaning that its name can +be passed to `python -m` to invoke the CLI. This is the primary method of +integration with the [`pre-commit` framework] and local development/testing. + +The layout allows for having several Python modules wrapping third-party tools, +each having an argument parser and being a subcommand for the main CLI +interface. + +## Control flow + +When `python -m pre_commit_terraform` is executed, it imports `__main__.py`. +Which in turn, performs the initialization of the main argument parser and the +parsers of subcommands, followed by executing the logic defined in dedicated +subcommand modules. + +## Integrating a new subcommand + +1. Create a new module called `subcommand_x.py`. +2. Within that module, define two functions — + `invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType | int` and + `populate_argument_parser(subcommand_parser: ArgumentParser) -> None`. + Additionally, define a module-level constant + `CLI_SUBCOMMAND_NAME: Final[str] = 'subcommand-x'`. +3. Edit [`_cli_subcommands.py`], importing `subcommand_x` as a relative module + and add it into the `SUBCOMMAND_MODULES` list. +4. Edit [`.pre-commit-hooks.yaml`], adding a new hook that invokes + `python -m pre_commit_terraform subcommand-x`. + +## Manual testing + +Usually, having a development virtualenv where you `pip install -e .` is enough +to make it possible to invoke the CLI app. Do so first. Most source code +updates do not require running it again. But sometimes, it's needed. + +Once done, you can run `python -m pre_commit_terraform` and/or +`python -m pre_commit_terraform subcommand-x` to see how it behaves. There's +`--help` and all other typical conventions one would usually expect from a +POSIX-inspired CLI app. + +## DX/UX considerations + +Since it's an app that can be executed outside the [`pre-commit` framework], +it is useful to check out and follow these [CLI guidelines][clig]. + +## Subcommand development + +`populate_argument_parser()` accepts a regular instance of +[`argparse.ArgumentParser`]. Call its methods to extend the CLI arguments that +would be specific for the subcommand you are creating. Those arguments will be +available later, as an argument to the `invoke_cli_app()` function — through an +instance of [`argparse.Namespace`]. For the `CLI_SUBCOMMAND_NAME` constant, +choose `kebab-space-sub-command-style`, it does not need to be `snake_case`. + +Make sure to return a `ReturnCode` instance or an integer from +`invoke_cli_app()`. Returning a non-zero value will result in the CLI app +exiting with a return code typically interpreted as an error while zero means +success. You can `import errno` to use typical POSIX error codes through their +human-readable identifiers. + +Another way to interrupt the CLI app control flow is by raising an instance of +one of the in-app errors. `raise PreCommitTerraformExit` for a successful exit, +but it can be turned into an error outcome via +`raise PreCommitTerraformExit(1)`. +`raise PreCommitTerraformRuntimeError('The world is broken')` to indicate +problems within the runtime. The framework will intercept any exceptions +inheriting `PreCommitTerraformBaseError`, so they won't be presented to the +end-users. + +[`.pre-commit-hooks.yaml`]: ../../.pre-commit-hooks.yaml +[`_cli_parsing.py`]: ./_cli_parsing.py +[`_cli_subcommands.py`]: ./_cli_subcommands.py +[`argparse.ArgumentParser`]: +https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser +[`argparse.Namespace`]: +https://docs.python.org/3/library/argparse.html#argparse.Namespace +[clig]: https://clig.dev +[importable package]: https://docs.python.org/3/tutorial/modules.html#packages +[import package]: https://packaging.python.org/en/latest/glossary/#term-Import-Package +[`pre-commit` framework]: https://pre-commit.com +[runpy interface]: https://docs.python.org/3/library/__main__.html diff --git a/src/pre_commit_terraform/__main__.py b/src/pre_commit_terraform/__main__.py new file mode 100644 index 000000000..18a63dfd0 --- /dev/null +++ b/src/pre_commit_terraform/__main__.py @@ -0,0 +1,9 @@ +"""A runpy-style CLI entry-point module.""" + +from sys import argv, exit as exit_with_return_code + +from ._cli import invoke_cli_app + + +return_code = invoke_cli_app(argv[1:]) +exit_with_return_code(return_code) diff --git a/src/pre_commit_terraform/_cli.py b/src/pre_commit_terraform/_cli.py new file mode 100644 index 000000000..f52a50b0b --- /dev/null +++ b/src/pre_commit_terraform/_cli.py @@ -0,0 +1,50 @@ +"""Outer CLI layer of the app interface.""" + +from sys import stderr + +from ._cli_parsing import initialize_argument_parser +from ._errors import ( + PreCommitTerraformBaseError, + PreCommitTerraformExit, + PreCommitTerraformRuntimeError, +) +from ._structs import ReturnCode +from ._types import ReturnCodeType + + +def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: + """Run the entry-point of the CLI app. + + Includes initializing parsers of all the sub-apps and + choosing what to execute. + """ + root_cli_parser = initialize_argument_parser() + parsed_cli_args = root_cli_parser.parse_args(cli_args) + + try: + return parsed_cli_args.invoke_cli_app(parsed_cli_args) + except PreCommitTerraformExit as exit_err: + print(f'App exiting: {exit_err !s}', file=stderr) + raise + except PreCommitTerraformRuntimeError as unhandled_exc: + print( + f'App execution took an unexpected turn: {unhandled_exc !s}. ' + 'Exiting...', + file=stderr, + ) + return ReturnCode.ERROR + except PreCommitTerraformBaseError as unhandled_exc: + print( + f'A surprising exception happened: {unhandled_exc !s}. Exiting...', + file=stderr, + ) + return ReturnCode.ERROR + except KeyboardInterrupt as ctrl_c_exc: + print( + f'User-initiated interrupt: {ctrl_c_exc !s}. Exiting...', + file=stderr, + ) + return ReturnCode.ERROR + + +__all__ = ('invoke_cli_app',) diff --git a/src/pre_commit_terraform/_cli_parsing.py b/src/pre_commit_terraform/_cli_parsing.py new file mode 100644 index 000000000..969b0ae17 --- /dev/null +++ b/src/pre_commit_terraform/_cli_parsing.py @@ -0,0 +1,39 @@ +"""Argument parser initialization logic. + +This defines helpers for setting up both the root parser and the parsers +of all the sub-commands. +""" + +from argparse import ArgumentParser + +from ._cli_subcommands import SUBCOMMAND_MODULES + + +def attach_subcommand_parsers_to(root_cli_parser: ArgumentParser, /) -> None: + """Connect all sub-command parsers to the given one. + + This functions iterates over a mapping of subcommands to their + respective population functions, executing them to augment the + main parser. + """ + subcommand_parsers = root_cli_parser.add_subparsers( + dest='check_name', + help='A check to be performed.', + required=True, + ) + for subcommand_module in SUBCOMMAND_MODULES: + subcommand_parser = subcommand_parsers.add_parser(subcommand_module.CLI_SUBCOMMAND_NAME) + subcommand_parser.set_defaults( + invoke_cli_app=subcommand_module.invoke_cli_app, + ) + subcommand_module.populate_argument_parser(subcommand_parser) + + +def initialize_argument_parser() -> ArgumentParser: + """Return the root argument parser with sub-commands.""" + root_cli_parser = ArgumentParser(prog=f'python -m {__package__ !s}') + attach_subcommand_parsers_to(root_cli_parser) + return root_cli_parser + + +__all__ = ('initialize_argument_parser',) diff --git a/src/pre_commit_terraform/_cli_subcommands.py b/src/pre_commit_terraform/_cli_subcommands.py new file mode 100644 index 000000000..fc268e552 --- /dev/null +++ b/src/pre_commit_terraform/_cli_subcommands.py @@ -0,0 +1,12 @@ +"""A CLI sub-commands organization module.""" + +from . import terraform_docs_replace +from ._types import CLISubcommandModuleProtocol + + +SUBCOMMAND_MODULES: list[CLISubcommandModuleProtocol] = [ + terraform_docs_replace, +] + + +__all__ = ('SUBCOMMAND_MODULES',) diff --git a/src/pre_commit_terraform/_errors.py b/src/pre_commit_terraform/_errors.py new file mode 100644 index 000000000..c0f973acc --- /dev/null +++ b/src/pre_commit_terraform/_errors.py @@ -0,0 +1,16 @@ +"""App-specific exceptions.""" + + +class PreCommitTerraformBaseError(Exception): + """Base exception for all the in-app errors.""" + + +class PreCommitTerraformRuntimeError( + PreCommitTerraformBaseError, + RuntimeError, +): + """An exception representing a runtime error condition.""" + + +class PreCommitTerraformExit(PreCommitTerraformBaseError, SystemExit): + """An exception for terminating execution from deep app layers.""" diff --git a/src/pre_commit_terraform/_structs.py b/src/pre_commit_terraform/_structs.py new file mode 100644 index 000000000..12cf0dad3 --- /dev/null +++ b/src/pre_commit_terraform/_structs.py @@ -0,0 +1,16 @@ +"""Data structures to be reused across the app.""" + +from enum import IntEnum + + +class ReturnCode(IntEnum): + """POSIX-style return code values. + + To be used in check callable implementations. + """ + + OK = 0 + ERROR = 1 + + +__all__ = ('ReturnCode',) diff --git a/src/pre_commit_terraform/_types.py b/src/pre_commit_terraform/_types.py new file mode 100644 index 000000000..99402b447 --- /dev/null +++ b/src/pre_commit_terraform/_types.py @@ -0,0 +1,30 @@ +"""Composite types for annotating in-project code.""" + +from argparse import ArgumentParser, Namespace +from typing import Final, Protocol + +from ._structs import ReturnCode + + +ReturnCodeType = ReturnCode | int + + +class CLISubcommandModuleProtocol(Protocol): + """A protocol for the subcommand-implementing module shape.""" + + CLI_SUBCOMMAND_NAME: Final[str] + """This constant contains a CLI.""" + + def populate_argument_parser( + self, subcommand_parser: ArgumentParser, + ) -> None: + """Run a module hook for populating the subcommand parser.""" + + def invoke_cli_app( + self, parsed_cli_args: Namespace, + ) -> ReturnCodeType | int: + """Run a module hook implementing the subcommand logic.""" + ... # pylint: disable=unnecessary-ellipsis + + +__all__ = ('CLISubcommandModuleProtocol', 'ReturnCodeType') diff --git a/src/pre_commit_terraform/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py index 3e10913df..b79ba479e 100644 --- a/src/pre_commit_terraform/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -1,33 +1,46 @@ -import argparse import os import subprocess -import sys import warnings +from argparse import ArgumentParser, Namespace +from typing import Final +from ._structs import ReturnCode +from ._types import ReturnCodeType -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 - README.md file each time.""" + +CLI_SUBCOMMAND_NAME: Final[str] = 'replace-docs' + + +def populate_argument_parser(subcommand_parser: ArgumentParser) -> None: + subcommand_parser.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 README.md file each time.' ) - parser.add_argument( + subcommand_parser.add_argument( '--dest', dest='dest', default='README.md', ) - parser.add_argument( + subcommand_parser.add_argument( '--sort-inputs-by-required', dest='sort', action='store_true', help='[deprecated] use --sort-by-required instead', ) - parser.add_argument( + subcommand_parser.add_argument( '--sort-by-required', dest='sort', action='store_true', ) - parser.add_argument( - '--with-aggregate-type-defaults', dest='aggregate', action='store_true', + subcommand_parser.add_argument( + '--with-aggregate-type-defaults', + dest='aggregate', + action='store_true', help='[deprecated]', ) - parser.add_argument('filenames', nargs='*', help='Filenames to check.') - args = parser.parse_args(argv) + subcommand_parser.add_argument( + 'filenames', + nargs='*', + help='Filenames to check.', + ) + +def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: warnings.warn( '`terraform_docs_replace` hook is DEPRECATED.' 'For migration instructions see ' @@ -37,29 +50,28 @@ def main(argv=None): ) dirs = [] - for filename in args.filenames: + for filename in parsed_cli_args.filenames: if (os.path.realpath(filename) not in dirs and (filename.endswith(".tf") or filename.endswith(".tfvars"))): dirs.append(os.path.dirname(filename)) - retval = 0 + retval = ReturnCode.OK for dir in dirs: try: procArgs = [] procArgs.append('terraform-docs') - if args.sort: + if parsed_cli_args.sort: procArgs.append('--sort-by-required') procArgs.append('md') procArgs.append("./{dir}".format(dir=dir)) procArgs.append('>') - procArgs.append("./{dir}/{dest}".format(dir=dir, dest=args.dest)) + procArgs.append( + './{dir}/{dest}'. + format(dir=dir, dest=parsed_cli_args.dest), + ) subprocess.check_call(" ".join(procArgs), shell=True) except subprocess.CalledProcessError as e: print(e) - retval = 1 + retval = ReturnCode.ERROR return retval - - -if __name__ == '__main__': - sys.exit(main()) From a92071c4ebcb114ba36d46af573d07fe31732c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Wed, 8 Jan 2025 15:13:55 +0100 Subject: [PATCH 105/293] ci: Set up Python-focused testing infra (#742) This patch integrates running pytest and pre-commit via tox. It includes changes needed for tracking coverage automatically for local inspection and reporting it to Codecov. This is essentially a copy of my typical boilerplate with a few more advanced hacks for communicating with GHA. It can be invoked as `tox`, `tox r`, `tox -qq`, `tox r -e pre-commit -qq -- mypy --all-files`, `tox -qq -- -vv -s tests/pytest/'tests/pytest/_cli_test.py::test_known_interrupts[ctrl-c]'` etc. `tox` itself is installable or runnable ad-hoc via `pipx` / `uvx` / `uv tool install` or one's favorite distro package manager. The Python version of where it's installed is not important. `tox` is not usually installed into regular manually-managed virtualenvs. Mentioned tools maintain internal venvs for isolating the apps and the system install would just live side-by-side with other system stuff. `tox` creates virtualenvs for its wrapped commands automatically and keeps track of cache invalidation for them. The commands wired via `tox` can either be invoked with the default arguments or with the replacement passed to tox after the `--` separator. Furthermore, the change introduces the MyPy integration into the pre-commit configuration and is set up in strict mode. This patch includes a config file for MyPy and adjusts the existing Python modules to pass the checks on every other version down to Python 3.9. Additionally, a set of basic unit tests is added to reach the bar of 100% code coverage metric, which is set as expected in the configs. Another patch included is making stderr interceptable. Previously, `sys.stderr` was exposed as a module-scoped var, while the `capsys` fixture patches the `stderr` attribute on the `sys` module. So such patching did not influence the `stderr` variable in the `_cli` module as it remained linked to the non-patched object. With this change, the ability to inspect printing to stderr is recovered and is then demonstrated in the enclosed tests. Configuration note: each underlying tool must have its configuration in a native file, it supports and *not* passed via CLI arguments in the pre-commit or tox configs. This is important because those tools may be invoked by things other than our direct automation, like editor/IDE plugins or other platforms/tools. In order for other invocations to work the same, the shared configuration must be the same. For example, if we were to disable a rule in a pylint call in pre-commit config, it wouldn't show in CI but would break in people's editors that just call pylint directly and show errors inline. ### How can we test changes Install `tox` however you like (could also optionally attempt installing `tox-uv` with it, in the same env) and run `tox`. This will execute `pytest` under your current Python interpreter. You can also try things like `tox p -qq -e py313,py39,pre-commit`, `tox r -qq -e py313,py39,pre-commit`, `tox r -qq -e pre-commit -- mypy-py313 --all-files`, `tox r -qq -e pre-commit -- mypy --all-files`. --- .codecov.yml | 54 ++ .coveragerc | 34 ++ .github/workflows/ci-cd.yml | 506 ++++++++++++++++++ .github/workflows/reusable-tox.yml | 418 +++++++++++++++ .github/workflows/scheduled-runs.yml | 41 ++ .mypy.ini | 69 +++ .pre-commit-config.yaml | 59 ++ README.md | 11 + hatch.toml | 5 + pyproject.toml | 30 +- pytest.ini | 84 +++ src/pre_commit_terraform/_cli.py | 20 +- src/pre_commit_terraform/_types.py | 12 +- .../terraform_docs_replace.py | 10 +- tests/pytest/_cli_test.py | 100 ++++ tests/pytest/terraform_docs_replace_test.py | 123 +++++ tox.ini | 286 ++++++++++ 17 files changed, 1840 insertions(+), 22 deletions(-) create mode 100644 .codecov.yml create mode 100644 .coveragerc create mode 100644 .github/workflows/ci-cd.yml create mode 100644 .github/workflows/reusable-tox.yml create mode 100644 .github/workflows/scheduled-runs.yml create mode 100644 .mypy.ini create mode 100644 pytest.ini create mode 100644 tests/pytest/_cli_test.py create mode 100644 tests/pytest/terraform_docs_replace_test.py create mode 100644 tox.ini diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..e47301f83 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,54 @@ +--- + +codecov: + notify: + after_n_builds: 21 # Number of test matrix+lint jobs uploading coverage + wait_for_ci: false + + require_ci_to_pass: false + + token: >- # notsecret # repo-scoped, upload-only, stability in fork PRs + 7316089b-55fe-4646-b640-78d84b79d109 + +comment: + require_changes: true + +coverage: + range: 100..100 + status: + patch: + default: + target: 100% + pytest: + target: 100% + flags: + - pytest + typing: + flags: + - MyPy + project: + default: + target: 95% + lib: + flags: + - pytest + paths: + - src/ + target: 100% + tests: + flags: + - pytest + paths: + - tests/ + target: 100% + typing: + flags: + - MyPy + target: 90% + +github_checks: + # Annotations are deprecated in Codecov because they are misleading. + # Ref: https://github.com/codecov/codecov-action/issues/1710 + annotations: false + +... diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..ffab40193 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,34 @@ +[html] +show_contexts = true +skip_covered = false + +[paths] +_site-packages-to-src-mapping = + src + */src + *\src + */lib/pypy*/site-packages + */lib/python*/site-packages + *\Lib\site-packages + +[report] +skip_covered = true +skip_empty = true +show_missing = true +exclude_also = + ^\s*@pytest\.mark\.xfail + ^\s*\.\.\.\s*(#.*)?$ + +[run] +branch = true +cover_pylib = false +# https://coverage.rtfd.io/en/latest/contexts.html#dynamic-contexts +# dynamic_context = test_function # conflicts with `pytest-cov` if set here +parallel = true +plugins = + covdefaults +relative_files = true +source = + . +source_pkgs = + pre_commit_terraform diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 000000000..f5c0ca61d --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,506 @@ +--- + +name: 🧪 + +on: + merge_group: + push: + branches-ignore: + - dependabot/** # Dependabot always creates PRs + - gh-readonly-queue/** # Temporary merge queue-related GH-made branches + - maintenance/pip-tools-constraint-lockfiles # Lock files through PRs + - maintenance/pip-tools-constraint-lockfiles-** # Lock files through PRs + - patchback/backports/** # Patchback always creates PRs + - pre-commit-ci-update-config # pre-commit.ci always creates a PR + pull_request: + workflow_call: # a way to embed the main tests + +concurrency: + group: >- + ${{ + github.workflow + }}-${{ + github.ref_type + }}-${{ + github.event.pull_request.number || github.sha + }} + cancel-in-progress: true + +env: + FORCE_COLOR: 1 # Request colored output from CLI tools supporting it + MYPY_FORCE_COLOR: 1 # MyPy's color enforcement + PIP_DISABLE_PIP_VERSION_CHECK: 1 # Hide "there's a newer pip" message + PIP_NO_PYTHON_VERSION_WARNING: 1 # Hide "this Python is deprecated" message + PIP_NO_WARN_SCRIPT_LOCATION: 1 # Hide "script dir is not in $PATH" message + PRE_COMMIT_COLOR: always + PROJECT_NAME: pre-commit-terraform + PY_COLORS: 1 # Recognized by the `py` package, dependency of `pytest` + PYTHONIOENCODING: utf-8 + PYTHONUTF8: 1 + TOX_PARALLEL_NO_SPINNER: 1 # Disable tox's parallel run spinner animation + TOX_TESTENV_PASSENV: >- # Make tox-wrapped tools see color requests + FORCE_COLOR + MYPY_FORCE_COLOR + NO_COLOR + PIP_DISABLE_PIP_VERSION_CHECK + PIP_NO_PYTHON_VERSION_WARNING + PIP_NO_WARN_SCRIPT_LOCATION + PRE_COMMIT_COLOR + PY_COLORS + PYTEST_THEME + PYTEST_THEME_MODE + PYTHONIOENCODING + PYTHONLEGACYWINDOWSSTDIO + PYTHONUTF8 + UPSTREAM_REPOSITORY_ID: >- + 69382485 + +run-name: >- + ${{ + github.event_name == 'workflow_dispatch' + && format('📦 Releasing v{0}...', github.event.inputs.release-version) + || '' + }} + ${{ + github.event.pull_request.number && '🔀 PR' || '' + }}${{ + !github.event.pull_request.number && '🌱 Commit' || '' + }} + ${{ github.event.pull_request.number || github.sha }} + triggered by: ${{ github.event_name }} of ${{ + github.ref + }} ${{ + github.ref_type + }} + (workflow run ID: ${{ + github.run_id + }}; number: ${{ + github.run_number + }}; attempt: ${{ + github.run_attempt + }}) + +jobs: + pre-setup: + name: ⚙️ Pre-set global build settings + + runs-on: ubuntu-latest + + timeout-minutes: 1 + + defaults: + run: + shell: python + + outputs: + # NOTE: These aren't env vars because the `${{ env }}` context is + # NOTE: inaccessible when passing inputs to reusable workflows. + dists-artifact-name: python-package-distributions + dist-version: ${{ steps.scm-version.outputs.dist-version }} + cache-key-files: >- + ${{ steps.calc-cache-key-files.outputs.files-hash-key }} + git-tag: ${{ steps.git-tag.outputs.tag }} + sdist-artifact-name: ${{ steps.artifact-name.outputs.sdist }} + wheel-artifact-name: ${{ steps.artifact-name.outputs.wheel }} + upstream-repository-id: ${{ env.UPSTREAM_REPOSITORY_ID }} + + steps: + - name: Switch to using Python 3.13 by default + uses: actions/setup-python@v5 + with: + python-version: 3.13 + - name: Check out src from Git + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: >- + Calculate Python interpreter version hash value + for use in the cache key + id: calc-cache-key-py + run: | + from hashlib import sha512 + from os import environ + from pathlib import Path + from sys import version + + FILE_APPEND_MODE = 'a' + + hash = sha512(version.encode()).hexdigest() + + with Path(environ['GITHUB_OUTPUT']).open( + mode=FILE_APPEND_MODE, + ) as outputs_file: + print(f'py-hash-key={hash}', file=outputs_file) + - name: >- + Calculate dependency files' combined hash value + for use in the cache key + id: calc-cache-key-files + run: | + from os import environ + from pathlib import Path + + FILE_APPEND_MODE = 'a' + + with Path(environ['GITHUB_OUTPUT']).open( + mode=FILE_APPEND_MODE, + ) as outputs_file: + print( + "files-hash-key=${{ + hashFiles( + 'tox.ini', + 'pyproject.toml', + '.pre-commit-config.yaml', + 'pytest.ini', + 'dependencies/**/*' + ) + }}", + file=outputs_file, + ) + - name: Get pip cache dir + id: pip-cache-dir + run: >- + echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" + shell: bash + - name: Set up pip cache + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache-dir.outputs.dir }} + key: >- + ${{ runner.os }}-pip-${{ + steps.calc-cache-key-py.outputs.py-hash-key }}-${{ + steps.calc-cache-key-files.outputs.files-hash-key }} + restore-keys: | + ${{ runner.os }}-pip-${{ + steps.calc-cache-key-py.outputs.py-hash-key + }}- + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Drop Git tags from HEAD for non-release requests + run: >- + git tag --points-at HEAD + | + xargs git tag --delete + shell: bash + - name: Set up versioning prerequisites + run: >- + python -m + pip install + --user + setuptools-scm + shell: bash + - name: Set the current dist version from Git + id: scm-version + run: | + from os import environ + from pathlib import Path + + import setuptools_scm + + FILE_APPEND_MODE = 'a' + + ver = setuptools_scm.get_version() + with Path(environ['GITHUB_OUTPUT']).open( + mode=FILE_APPEND_MODE, + ) as outputs_file: + print(f'dist-version={ver}', file=outputs_file) + print( + f'dist-version-for-filenames={ver.replace("+", "-")}', + file=outputs_file, + ) + - name: Set the target Git tag + id: git-tag + run: | + from os import environ + from pathlib import Path + + FILE_APPEND_MODE = 'a' + + with Path(environ['GITHUB_OUTPUT']).open( + mode=FILE_APPEND_MODE, + ) as outputs_file: + print( + "tag=v${{ + steps.scm-version.outputs.dist-version + }}", + file=outputs_file, + ) + - name: Set the expected dist artifact names + id: artifact-name + run: | + from os import environ + from pathlib import Path + + FILE_APPEND_MODE = 'a' + + whl_file_prj_base_name = '${{ env.PROJECT_NAME }}'.replace('-', '_') + sdist_file_prj_base_name = whl_file_prj_base_name.replace('.', '_') + + with Path(environ['GITHUB_OUTPUT']).open( + mode=FILE_APPEND_MODE, + ) as outputs_file: + print( + f"sdist={sdist_file_prj_base_name !s}-${{ + steps.scm-version.outputs.dist-version + }}.tar.gz", + file=outputs_file, + ) + print( + f"wheel={whl_file_prj_base_name !s}-${{ + steps.scm-version.outputs.dist-version + }}-py3-none-any.whl", + file=outputs_file, + ) + + build: + name: 📦 ${{ needs.pre-setup.outputs.git-tag }} + needs: + - pre-setup + + runs-on: ubuntu-latest + + timeout-minutes: 2 + + env: + TOXENV: cleanup-dists,build-dists + + outputs: + dists-base64-hash: ${{ steps.dist-hashes.outputs.combined-hash }} + + steps: + - name: Switch to using Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: 3.13 + + - name: Grab the source from Git + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: >- + Calculate Python interpreter version hash value + for use in the cache key + id: calc-cache-key-py + run: | + from hashlib import sha512 + from os import environ + from pathlib import Path + from sys import version + + FILE_APPEND_MODE = 'a' + + hash = sha512(version.encode()).hexdigest() + + with Path(environ['GITHUB_OUTPUT']).open( + mode=FILE_APPEND_MODE, + ) as outputs_file: + print(f'py-hash-key={hash}', file=outputs_file) + shell: python + - name: Get pip cache dir + id: pip-cache-dir + run: >- + echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" + - name: Set up pip cache + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache-dir.outputs.dir }} + key: >- + ${{ runner.os }}-pip-${{ + steps.calc-cache-key-py.outputs.py-hash-key }}-${{ + needs.pre-setup.outputs.cache-key-files }} + restore-keys: | + ${{ runner.os }}-pip-${{ + steps.calc-cache-key-py.outputs.py-hash-key + }}- + ${{ runner.os }}-pip- + + - name: Install tox + run: >- + python -Im pip install tox + shell: bash # windows compat + + - name: Pre-populate the tox env + run: >- + python -m + tox + --parallel auto + --parallel-live + --skip-missing-interpreters false + --notest + + - name: Set static timestamp for dist build reproducibility + # ... from the last Git commit since it's immutable + run: >- + echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" + >> "${GITHUB_ENV}" + - name: Build dists + run: >- + python -m + tox + --parallel auto + --parallel-live + --skip-missing-interpreters false + --skip-pkg-install + --quiet + - name: Verify that the artifacts with expected names got created + run: >- + ls -1 + 'dist/${{ needs.pre-setup.outputs.sdist-artifact-name }}' + 'dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}' + - name: Generate dist hashes to be used for provenance + id: dist-hashes + run: >- + echo "combined-hash=$( + sha256sum + '${{ needs.pre-setup.outputs.sdist-artifact-name }}' + '${{ needs.pre-setup.outputs.wheel-artifact-name }}' + | base64 -w0 + )" + >> "${GITHUB_OUTPUT}" + working-directory: dist + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: >- + ${{ needs.pre-setup.outputs.dists-artifact-name }} + # NOTE: Exact expected file names are specified here + # NOTE: as a safety measure — if anything weird ends + # NOTE: up being in this dir or not all dists will be + # NOTE: produced, this will fail the workflow. + path: | + dist/${{ needs.pre-setup.outputs.sdist-artifact-name }} + dist/${{ needs.pre-setup.outputs.wheel-artifact-name }} + retention-days: 30 + + lint: + name: 🧹 Linters${{ '' }} # nest jobs under the same sidebar category + needs: + - build + - pre-setup # transitive, for accessing settings + strategy: + matrix: + runner-vm-os: + - ubuntu-latest + python-version: + - 3.13 + toxenv: + - pre-commit + - metadata-validation + environment-variables: + - >- # only affects pre-commit, set for all for simplicity: + SKIP=hadolint,shfmt + tox-run-posargs: + - '' + xfail: + - false + check-name: + - '' + fail-fast: false + uses: ./.github/workflows/reusable-tox.yml + with: + cache-key-files: >- + ${{ needs.pre-setup.outputs.cache-key-files }} + check-name: >- + ${{ matrix.check-name }} + dists-artifact-name: >- + ${{ needs.pre-setup.outputs.dists-artifact-name }} + environment-variables: >- + ${{ matrix.environment-variables }} + python-version: >- + ${{ matrix.python-version }} + runner-vm-os: >- + ${{ matrix.runner-vm-os }} + source-tarball-name: >- + ${{ needs.pre-setup.outputs.sdist-artifact-name }} + timeout-minutes: 3 + toxenv: >- + ${{ matrix.toxenv }} + tox-run-posargs: >- + ${{ matrix.tox-run-posargs }} + upstream-repository-id: >- + ${{ needs.pre-setup.outputs.upstream-repository-id }} + xfail: ${{ fromJSON(matrix.xfail) }} + secrets: + codecov-token: ${{ secrets.CODECOV_TOKEN }} + + tests: + name: 🧪 Tests${{ '' }} # nest jobs under the same sidebar category + needs: + - build + - pre-setup # transitive, for accessing settings + strategy: + matrix: + python-version: + # NOTE: The latest and the lowest supported Pythons are prioritized + # NOTE: to improve the responsiveness. It's nice to see the most + # NOTE: important results first. + - 3.13 + - 3.9 + - >- # str + 3.10 + - 3.12 + - 3.11 + runner-vm-os: + - ubuntu-24.04 + - macos-14 + - macos-13 + - windows-2025 + toxenv: + - py + xfail: + - false + + uses: ./.github/workflows/reusable-tox.yml + with: + built-wheel-names: >- + ${{ needs.pre-setup.outputs.wheel-artifact-name }} + cache-key-files: >- + ${{ needs.pre-setup.outputs.cache-key-files }} + dists-artifact-name: >- + ${{ needs.pre-setup.outputs.dists-artifact-name }} + python-version: >- + ${{ matrix.python-version }} + runner-vm-os: >- + ${{ matrix.runner-vm-os }} + source-tarball-name: >- + ${{ needs.pre-setup.outputs.sdist-artifact-name }} + timeout-minutes: 5 + toxenv: >- + ${{ matrix.toxenv }} + tox-run-posargs: >- + --cov-report=xml:.tox/.tmp/.test-results/pytest-${{ + matrix.python-version + }}/cobertura.xml + --junitxml=.tox/.tmp/.test-results/pytest-${{ + matrix.python-version + }}/test.xml + tox-rerun-posargs: >- + -rA + -vvvvv + --lf + --no-cov + --no-fold-skipped + upstream-repository-id: >- + ${{ needs.pre-setup.outputs.upstream-repository-id }} + xfail: ${{ fromJSON(matrix.xfail) }} + secrets: + codecov-token: ${{ secrets.CODECOV_TOKEN }} + + check: # This job does nothing and is only used for the branch protection + if: always() + + needs: + - lint + - tests + + runs-on: ubuntu-latest + + timeout-minutes: 1 + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} + +... diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml new file mode 100644 index 000000000..b540075e4 --- /dev/null +++ b/.github/workflows/reusable-tox.yml @@ -0,0 +1,418 @@ +--- + +name: >- + ❌ + [DO NOT CLICK] + Reusable Tox + +on: + workflow_call: + inputs: + built-wheel-names: + description: >- + A glob for the built distributions in the artifact + to test (is installed into tox env if passed) + required: false + type: string + cache-key-files: + description: Dependency files cache + required: true + type: string + check-name: + description: A custom name for the Checks API-reported status + required: false + type: string + dists-artifact-name: + description: Workflow artifact name containing dists + required: true + type: string + environment-variables: + description: >- + A newline-delimited blob of text with environment variables + to be set using `${GITHUB_ENV}` + required: false + type: string + python-version: + description: Python version to provision in the VM + required: true + type: string + release-requested: + description: Flag whether this is CI run is a release request + default: 'false' + required: false + type: string + runner-vm-os: + description: VM OS to use + default: ubuntu + required: false + type: string + source-tarball-name: + description: Sdist filename wildcard + required: true + type: string + timeout-minutes: + description: Deadline for the job to complete + required: true + type: string + toxenv: + description: Name of the tox environment to use + required: true + type: string + tox-run-posargs: + description: Positional arguments to pass to the regular tox run + required: false + type: string + tox-rerun-posargs: + description: Positional arguments to pass to the re-attempted tox run + required: false + type: string + upstream-repository-id: + description: ID of the upstream GitHub Repository + required: true + type: string + xfail: + description: >- + Whether this job is expected to fail. Controls if the run outcomes + contribute to the failing CI status or not. The job status will be + treated as successful if this is set to `true`. Setting `false` + should be preferred typically. + required: true + type: string + secrets: + codecov-token: + description: Mandatory token for uploading to Codecov + required: true + +env: + COLOR: >- # Supposedly, pytest or coveragepy use this + yes + FORCE_COLOR: 1 # Request colored output from CLI tools supporting it + MYPY_FORCE_COLOR: 1 # MyPy's color enforcement + PIP_DISABLE_PIP_VERSION_CHECK: 1 + PIP_NO_PYTHON_VERSION_WARNING: 1 + PIP_NO_WARN_SCRIPT_LOCATION: 1 + PRE_COMMIT_COLOR: always + PY_COLORS: 1 # Recognized by the `py` package, dependency of `pytest` + PYTHONIOENCODING: utf-8 + PYTHONUTF8: 1 + TOX_PARALLEL_NO_SPINNER: 1 + TOX_TESTENV_PASSENV: >- # Make tox-wrapped tools see color requests + COLOR + FORCE_COLOR + MYPY_FORCE_COLOR + NO_COLOR + PIP_DISABLE_PIP_VERSION_CHECK + PIP_NO_PYTHON_VERSION_WARNING + PIP_NO_WARN_SCRIPT_LOCATION + PRE_COMMIT_COLOR + PY_COLORS + PYTEST_THEME + PYTEST_THEME_MODE + PYTHONIOENCODING + PYTHONLEGACYWINDOWSSTDIO + PYTHONUTF8 + +jobs: + tox: + name: >- + ${{ + inputs.check-name + && inputs.check-name + || format( + '{0}@🐍{1}@{2}', + inputs.toxenv, + inputs.python-version, + inputs.runner-vm-os + ) + }} + + runs-on: ${{ inputs.runner-vm-os }} + + timeout-minutes: ${{ fromJSON(inputs.timeout-minutes) }} + + continue-on-error: >- + ${{ + ( + fromJSON(inputs.xfail) || + ( + startsWith(inputs.python-version, '~') + ) || + contains(inputs.python-version, 'alpha') + ) && true || false + }} + + env: + TOXENV: ${{ inputs.toxenv }} + + steps: + - name: Export requested job-global environment variables + if: inputs.environment-variables != '' + run: >- + echo '${{ inputs.environment-variables }}' + >> "${GITHUB_ENV}" + + - name: >- + Switch to using Python v${{ inputs.python-version }} + by default + id: python-install + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + + # NOTE: `pre-commit --show-diff-on-failure` and `sphinxcontrib-spellcheck` + # NOTE: with Git authors allowlist enabled both depend on the presence of a + # NOTE: Git repository. + - name: Grab the source from Git + if: >- + contains(fromJSON('["pre-commit", "spellcheck-docs"]'), inputs.toxenv) + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.release-committish }} + - name: Retrieve the project source from an sdist inside the GHA artifact + if: >- + !contains(fromJSON('["pre-commit", "spellcheck-docs"]'), inputs.toxenv) + uses: re-actors/checkout-python-sdist@release/v2 + with: + source-tarball-name: ${{ inputs.source-tarball-name }} + workflow-artifact-name: ${{ inputs.dists-artifact-name }} + + - name: Cache pre-commit.com virtualenvs + if: inputs.toxenv == 'pre-commit' + uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: >- + ${{ + runner.os + }}-pre-commit-${{ + hashFiles('.pre-commit-config.yaml') + }} + + - name: Figure out if the interpreter ABI is stable + id: py-abi + run: | + from os import environ + from pathlib import Path + from sys import version_info + + FILE_APPEND_MODE = 'a' + + is_stable_abi = version_info.releaselevel == 'final' + + with Path(environ['GITHUB_OUTPUT']).open( + mode=FILE_APPEND_MODE, + ) as outputs_file: + print( + 'is-stable-abi={is_stable_abi}'. + format(is_stable_abi=str(is_stable_abi).lower()), + file=outputs_file, + ) + shell: python + - name: >- + Calculate Python interpreter version hash value + for use in the cache key + if: fromJSON(steps.py-abi.outputs.is-stable-abi) + id: calc-cache-key-py + run: | + from hashlib import sha512 + from os import environ + from pathlib import Path + from sys import version + + FILE_APPEND_MODE = 'a' + + hash = sha512(version.encode()).hexdigest() + + with Path(environ['GITHUB_OUTPUT']).open( + mode=FILE_APPEND_MODE, + ) as outputs_file: + print(f'py-hash-key={hash}', file=outputs_file) + shell: python + - name: Get pip cache dir + if: fromJSON(steps.py-abi.outputs.is-stable-abi) + id: pip-cache-dir + run: >- + echo "dir=$(python -Im pip cache dir)" >> "${GITHUB_OUTPUT}" + shell: bash + - name: Set up pip cache + if: fromJSON(steps.py-abi.outputs.is-stable-abi) + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache-dir.outputs.dir }} + key: >- + ${{ runner.os }}-pip-${{ + steps.calc-cache-key-py.outputs.py-hash-key }}-${{ + inputs.cache-key-files }} + restore-keys: | + ${{ runner.os }}-pip-${{ + steps.calc-cache-key-py.outputs.py-hash-key + }}- + ${{ runner.os }}-pip- + + - name: Install tox + run: >- + python -Im pip install tox + shell: bash # windows compat + + - name: Make the env clean of non-test files + if: inputs.toxenv == 'metadata-validation' + run: | + shopt -s extglob + rm -rf !tox.ini + shell: bash + - name: Download all the dists + if: >- + contains(fromJSON('["metadata-validation", "py"]'), inputs.toxenv) + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.dists-artifact-name }} + path: dist/ + + - name: >- + Pre-populate tox envs: `${{ env.TOXENV }}` + run: >- + python -Im + tox + --parallel auto + --parallel-live + --skip-missing-interpreters false + ${{ + inputs.built-wheel-names != '' + && format('--installpkg dist/{0}', inputs.built-wheel-names) + || '' + }} + --notest + - name: Initialize pre-commit envs if needed + if: inputs.toxenv == 'pre-commit' + run: >- + python -Im + tox + exec + --skip-pkg-install + --quiet + -- + python -Im pre_commit install-hooks + - name: >- + Run tox envs: `${{ env.TOXENV }}` + id: tox-run + run: >- + python -Im + tox + --parallel auto + --parallel-live + --skip-missing-interpreters false + --skip-pkg-install + --quiet + ${{ + inputs.tox-run-posargs != '' + && format('-- {0}', inputs.tox-run-posargs) + || '' + }} + - name: Produce markdown test summary from JUnit + if: >- + !cancelled() + && steps.tox-run.outputs.test-result-files != '' + uses: test-summary/action@v2.3 + with: + paths: >- + ${{ steps.tox-run.outputs.test-result-files }} + - name: Produce markdown test summary from Cobertura XML + # NOTE: MyPy is temporarily excluded because it produces incomplete XML + # NOTE: files that `irongut/CodeCoverageSummary` can't stomach. + # Refs: + # * https://github.com/irongut/CodeCoverageSummary/issues/324 + # * https://github.com/python/mypy/issues/17689 + # FIXME: Revert the exclusion once upstream fixes the bug. + if: >- + !cancelled() + && runner.os == 'Linux' + && steps.tox-run.outputs.cov-report-files != '' + && steps.tox-run.outputs.test-result-files == '' + && steps.tox-run.outputs.codecov-flags != 'MyPy' + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + badge: true + filename: >- + ${{ steps.tox-run.outputs.cov-report-files }} + format: markdown + output: both + # Ref: https://github.com/irongut/CodeCoverageSummary/issues/66 + - name: Append coverage results to Job Summary + if: >- + !cancelled() + && runner.os == 'Linux' + && steps.tox-run.outputs.cov-report-files != '' + && steps.tox-run.outputs.test-result-files == '' + && steps.tox-run.outputs.codecov-flags != 'MyPy' + run: >- + cat code-coverage-results.md >> "${GITHUB_STEP_SUMMARY}" + - name: Re-run the failing tests with maximum verbosity + if: >- + !cancelled() + && failure() + && inputs.tox-rerun-posargs != '' + run: >- # `exit 1` makes sure that the job remains red with flaky runs + python -Im + tox + --parallel auto + --parallel-live + --skip-missing-interpreters false + -vvvvv + --skip-pkg-install + -- + ${{ inputs.tox-rerun-posargs }} + && exit 1 + shell: bash + - name: Send coverage data to Codecov + if: >- + !cancelled() + && steps.tox-run.outputs.cov-report-files != '' + uses: codecov/codecov-action@v4 + with: + disable_search: true + fail_ci_if_error: >- + ${{ toJSON(inputs.upstream-repository-id == github.repository_id) }} + files: >- + ${{ steps.tox-run.outputs.cov-report-files }} + flags: >- + CI-GHA, + ${{ steps.tox-run.outputs.codecov-flags }}, + OS-${{ + runner.os + }}, + VM-${{ + inputs.runner-vm-os + }}, + Py-${{ + steps.python-install.outputs.python-version + }} + token: ${{ secrets.codecov-token }} + - name: Upload test results to Codecov + if: >- + !cancelled() + && steps.tox-run.outputs.test-result-files != '' + # FIXME: revert to v1 once Codecov releases v1.0.2 of their action. + # Ref: https://github.com/codecov/test-results-action/issues/108. + # uses: codecov/test-results-action@v1 + uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 + with: + disable_search: true + fail_ci_if_error: >- + ${{ toJSON(inputs.upstream-repository-id == github.repository_id) }} + files: >- + ${{ steps.tox-run.outputs.test-result-files }} + flags: >- + CI-GHA, + ${{ steps.tox-run.outputs.codecov-flags }}, + OS-${{ + runner.os + }}, + VM-${{ + inputs.runner-vm-os + }}, + Py-${{ + steps.python-install.outputs.python-version + }} + token: ${{ secrets.codecov-token }} + +... diff --git a/.github/workflows/scheduled-runs.yml b/.github/workflows/scheduled-runs.yml new file mode 100644 index 000000000..9d8403cf2 --- /dev/null +++ b/.github/workflows/scheduled-runs.yml @@ -0,0 +1,41 @@ +--- + +name: ⏳ + +on: + pull_request: + paths: # only changes to this workflow itself trigger PR testing + - .github/workflows/scheduled-runs.yml + schedule: + - cron: 3 5 * * * # run daily at 5:03 UTC + workflow_dispatch: # manual trigger + +run-name: >- + 🌃 + Nightly run of + ${{ + github.event.pull_request.number && 'PR' || '' + }}${{ + !github.event.pull_request.number && 'Commit' || '' + }} + ${{ github.event.pull_request.number || github.sha }} + triggered by: ${{ github.event_name }} of ${{ + github.ref + }} ${{ + github.ref_type + }} + (workflow run ID: ${{ + github.run_id + }}; number: ${{ + github.run_number + }}; attempt: ${{ + github.run_attempt + }}) + +jobs: + main-ci-cd-pipeline: + name: 🧪 Main CI/CD pipeline + uses: ./.github/workflows/ci-cd.yml + secrets: inherit + +... diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 000000000..a92387a64 --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,69 @@ +[mypy] +python_version = 3.9 +color_output = true +error_summary = true +# IMPORTANT: The file list MUST NOT have a trailing comma after the last entry. +# Ref: https://github.com/python/mypy/issues/11171#issuecomment-2567150548 +files = + src/, + tests/pytest/ + +check_untyped_defs = true + +disallow_any_explicit = true +disallow_any_expr = true +disallow_any_decorated = true +disallow_any_generics = true +disallow_any_unimported = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true + +enable_error_code = + ignore-without-code + +explicit_package_bases = true + +extra_checks = true + +follow_imports = normal + +ignore_missing_imports = false + +local_partial_types = true + +mypy_path = ${MYPY_CONFIG_FILE_DIR}/src:${MYPY_CONFIG_FILE_DIR}/_type_stubs + +namespace_packages = true + +no_implicit_reexport = true + +pretty = true + +show_column_numbers = true +show_error_code_links = true +show_error_codes = true +show_error_context = true +show_error_end = true + +# `strict` will pick up any future strictness-related settings: +strict = true +strict_equality = true +strict_optional = true + +warn_no_return = true +warn_redundant_casts = true +warn_return_any = true +warn_unused_configs = true +warn_unused_ignores = true + +[mypy-tests.*] +# crashes with some decorators like `@pytest.mark.parametrize`: +disallow_any_expr = false +# fails on `@hypothesis.given()`: +disallow_any_decorated = false + +[mypy-tests.pytest.*] +disable_error_code = attr-defined diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1db1d62b4..75a649ea1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,65 @@ repos: args: ['--allow-missing-credentials'] - id: detect-private-key + # Non-modifying checks: + - id: name-tests-test + files: >- + ^tests/[^_].*\.py$ + +- repo: https://github.com/pre-commit/mirrors-mypy.git + rev: v1.14.1 + hooks: + - id: mypy + alias: mypy-py313 + name: MyPy, for Python 3.13 + additional_dependencies: + - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` + - pytest + - pytest-mock + args: + - --python-version=3.13 + - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.13 + - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.13 + - --html-report=.tox/.tmp/.test-results/mypy--py-3.13 + - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.13 + - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.13 + - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.13 + - --txt-report=.tox/.tmp/.test-results/mypy--py-3.13 + pass_filenames: false + - id: mypy + alias: mypy-py311 + name: MyPy, for Python 3.11 + additional_dependencies: + - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` + - pytest + - pytest-mock + args: + - --python-version=3.11 + - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.11 + - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.11 + - --html-report=.tox/.tmp/.test-results/mypy--py-3.11 + - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.11 + - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.11 + - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.11 + - --txt-report=.tox/.tmp/.test-results/mypy--py-3.11 + pass_filenames: false + - id: mypy + alias: mypy-py39 + name: MyPy, for Python 3.9 + additional_dependencies: + - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` + - pytest + - pytest-mock + args: + - --python-version=3.9 + - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.9 + - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.9 + - --html-report=.tox/.tmp/.test-results/mypy--py-3.9 + - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.9 + - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.9 + - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.9 + - --txt-report=.tox/.tmp/.test-results/mypy--py-3.9 + pass_filenames: false - repo: https://github.com/jumanjihouse/pre-commit-hooks rev: 3.0.0 diff --git a/README.md b/README.md index 164fb8cd9..6bfb50225 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ # Collection of git hooks for Terraform to be used with [pre-commit framework](http://pre-commit.com/) [![Github tag](https://img.shields.io/github/tag/antonbabenko/pre-commit-terraform.svg)](https://github.com/antonbabenko/pre-commit-terraform/releases) ![maintenance status](https://img.shields.io/maintenance/yes/2024.svg) [![Help Contribute to Open Source](https://www.codetriage.com/antonbabenko/pre-commit-terraform/badges/users.svg)](https://www.codetriage.com/antonbabenko/pre-commit-terraform) +[![CI/CD Badge]][CI/CD] +[![Codecov Badge]][Codecov] + +[CI/CD Badge]: +https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml/badge.svg?branch=master +[CI/CD]: +https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml + +[Codecov Badge]: +https://codecov.io/gh/antonbabenko/pre-commit-terraform/branch/master/graph/badge.svg?flags[]=pytest +[Codecov]: https://app.codecov.io/gh/antonbabenko/pre-commit-terraform?flags[]=pytest [![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) diff --git a/hatch.toml b/hatch.toml index 053ff6bfb..9d3cd73ea 100644 --- a/hatch.toml +++ b/hatch.toml @@ -1,6 +1,11 @@ [build.targets.sdist] include = [ + '.codecov.yml', + '.coveragerc', 'src/', + 'tests/', + 'pytest.ini', + 'tox.ini', ] [build.targets.wheel] diff --git a/pyproject.toml b/pyproject.toml index e5ff0159a..c8ae73136 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,15 +5,36 @@ requires = [ ] build-backend = 'hatchling.build' +[dependency-groups] +building = [ + 'build', +] +linting = [ + 'pre-commit', +] +testing = [ + 'covdefaults', # sets up `coveragepy` config boilerplate + 'pytest >= 8', + 'pytest-cov', # integrates `coveragepy` into pytest runs + 'pytest-mock', # provides a `mocker` fixture + 'pytest-xdist', # paralellizes tests through subprocesses +] +upstreaming = [ + 'twine', +] + [project] name = 'pre-commit-terraform' classifiers = [ 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ] @@ -23,6 +44,7 @@ dynamic = [ 'urls', 'version', ] +requires-python = ">= 3.9" [[project.authors]] name = 'Anton Babenko' diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..d16390e6f --- /dev/null +++ b/pytest.ini @@ -0,0 +1,84 @@ +[pytest] +addopts = + # `pytest-xdist`: + --numprocesses=auto + # NOTE: the plugin is disabled because it's slower with so few tests + --numprocesses=0 + + # Show 10 slowest invocations: + --durations=10 + + # Report all the things == -rxXs: + -ra + + # Show values of the local vars in errors/tracebacks: + --showlocals + + # Autocollect and invoke the doctests from all modules: + # https://docs.pytest.org/en/stable/doctest.html + --doctest-modules + + # Pre-load the `pytest-cov` plugin early: + -p pytest_cov + + # `pytest-cov`: + --cov + --cov-config=.coveragerc + --cov-context=test + --no-cov-on-fail + + # Fail on config parsing warnings: + # --strict-config + + # Fail on non-existing markers: + # * Deprecated since v6.2.0 but may be reintroduced later covering a + # broader scope: + # --strict + # * Exists since v4.5.0 (advised to be used instead of `--strict`): + --strict-markers + +doctest_optionflags = ALLOW_UNICODE ELLIPSIS + +# Marks tests with an empty parameterset as xfail(run=False) +empty_parameter_set_mark = xfail + +faulthandler_timeout = 30 + +filterwarnings = + error + +# https://docs.pytest.org/en/stable/usage.html#creating-junitxml-format-files +junit_duration_report = call +# xunit1 contains more metadata than xunit2 so it's better for CI UIs: +junit_family = xunit1 +junit_logging = all +junit_log_passing_tests = true +junit_suite_name = awx_plugins_test_suite + +# A mapping of markers to their descriptions allowed in strict mode: +markers = + +minversion = 6.1.0 + +# Optimize pytest's lookup by restricting potentially deep dir tree scan: +norecursedirs = + build + dependencies + dist + docs + .cache + .eggs + .git + .github + .tox + *.egg + *.egg-info + */*.egg-info + */**/*.egg-info + *.dist-info + */*.dist-info + */**/*.dist-info + +testpaths = tests/pytest/ + +xfail_strict = true diff --git a/src/pre_commit_terraform/_cli.py b/src/pre_commit_terraform/_cli.py index f52a50b0b..edcb2ea30 100644 --- a/src/pre_commit_terraform/_cli.py +++ b/src/pre_commit_terraform/_cli.py @@ -1,6 +1,7 @@ """Outer CLI layer of the app interface.""" -from sys import stderr +import sys +from typing import cast as cast_to from ._cli_parsing import initialize_argument_parser from ._errors import ( @@ -9,7 +10,7 @@ PreCommitTerraformRuntimeError, ) from ._structs import ReturnCode -from ._types import ReturnCodeType +from ._types import CLIAppEntryPointCallableType, ReturnCodeType def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: @@ -20,29 +21,34 @@ def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: """ root_cli_parser = initialize_argument_parser() parsed_cli_args = root_cli_parser.parse_args(cli_args) + invoke_cli_app = cast_to( + # FIXME: attempt typing per https://stackoverflow.com/a/75666611/595220 + CLIAppEntryPointCallableType, + parsed_cli_args.invoke_cli_app, + ) try: - return parsed_cli_args.invoke_cli_app(parsed_cli_args) + return invoke_cli_app(parsed_cli_args) except PreCommitTerraformExit as exit_err: - print(f'App exiting: {exit_err !s}', file=stderr) + print(f'App exiting: {exit_err !s}', file=sys.stderr) raise except PreCommitTerraformRuntimeError as unhandled_exc: print( f'App execution took an unexpected turn: {unhandled_exc !s}. ' 'Exiting...', - file=stderr, + file=sys.stderr, ) return ReturnCode.ERROR except PreCommitTerraformBaseError as unhandled_exc: print( f'A surprising exception happened: {unhandled_exc !s}. Exiting...', - file=stderr, + file=sys.stderr, ) return ReturnCode.ERROR except KeyboardInterrupt as ctrl_c_exc: print( f'User-initiated interrupt: {ctrl_c_exc !s}. Exiting...', - file=stderr, + file=sys.stderr, ) return ReturnCode.ERROR diff --git a/src/pre_commit_terraform/_types.py b/src/pre_commit_terraform/_types.py index 99402b447..78db357e7 100644 --- a/src/pre_commit_terraform/_types.py +++ b/src/pre_commit_terraform/_types.py @@ -1,18 +1,20 @@ """Composite types for annotating in-project code.""" from argparse import ArgumentParser, Namespace -from typing import Final, Protocol +from collections.abc import Callable +from typing import Protocol, Union from ._structs import ReturnCode -ReturnCodeType = ReturnCode | int +ReturnCodeType = Union[ReturnCode, int] # Union instead of pipe for Python 3.9 +CLIAppEntryPointCallableType = Callable[[Namespace], ReturnCodeType] class CLISubcommandModuleProtocol(Protocol): """A protocol for the subcommand-implementing module shape.""" - CLI_SUBCOMMAND_NAME: Final[str] + CLI_SUBCOMMAND_NAME: str """This constant contains a CLI.""" def populate_argument_parser( @@ -20,9 +22,7 @@ def populate_argument_parser( ) -> None: """Run a module hook for populating the subcommand parser.""" - def invoke_cli_app( - self, parsed_cli_args: Namespace, - ) -> ReturnCodeType | int: + def invoke_cli_app(self, parsed_cli_args: Namespace) -> ReturnCodeType: """Run a module hook implementing the subcommand logic.""" ... # pylint: disable=unnecessary-ellipsis diff --git a/src/pre_commit_terraform/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py index b79ba479e..cc83a2a7d 100644 --- a/src/pre_commit_terraform/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -2,13 +2,13 @@ import subprocess import warnings from argparse import ArgumentParser, Namespace -from typing import Final +from typing import cast as cast_to from ._structs import ReturnCode from ._types import ReturnCodeType -CLI_SUBCOMMAND_NAME: Final[str] = 'replace-docs' +CLI_SUBCOMMAND_NAME: str = 'replace-docs' def populate_argument_parser(subcommand_parser: ArgumentParser) -> None: @@ -50,7 +50,7 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: ) dirs = [] - for filename in parsed_cli_args.filenames: + for filename in cast_to(list[str], parsed_cli_args.filenames): if (os.path.realpath(filename) not in dirs and (filename.endswith(".tf") or filename.endswith(".tfvars"))): dirs.append(os.path.dirname(filename)) @@ -61,14 +61,14 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: try: procArgs = [] procArgs.append('terraform-docs') - if parsed_cli_args.sort: + if cast_to(bool, parsed_cli_args.sort): procArgs.append('--sort-by-required') procArgs.append('md') procArgs.append("./{dir}".format(dir=dir)) procArgs.append('>') procArgs.append( './{dir}/{dest}'. - format(dir=dir, dest=parsed_cli_args.dest), + format(dir=dir, dest=cast_to(bool, parsed_cli_args.dest)), ) subprocess.check_call(" ".join(procArgs), shell=True) except subprocess.CalledProcessError as e: diff --git a/tests/pytest/_cli_test.py b/tests/pytest/_cli_test.py new file mode 100644 index 000000000..52ea82ab6 --- /dev/null +++ b/tests/pytest/_cli_test.py @@ -0,0 +1,100 @@ +"""Tests for the high-level CLI entry point.""" + +from argparse import ArgumentParser, Namespace +import pytest + +from pre_commit_terraform import _cli_parsing as _cli_parsing_mod +from pre_commit_terraform._cli import invoke_cli_app +from pre_commit_terraform._errors import ( + PreCommitTerraformExit, + PreCommitTerraformBaseError, + PreCommitTerraformRuntimeError, +) +from pre_commit_terraform._structs import ReturnCode +from pre_commit_terraform._types import ReturnCodeType + + +pytestmark = pytest.mark.filterwarnings( + 'ignore:`terraform_docs_replace` hook is DEPRECATED.:UserWarning:' + 'pre_commit_terraform.terraform_docs_replace', +) + + +@pytest.mark.parametrize( + ('raised_error', 'expected_stderr'), + ( + # pytest.param(PreCommitTerraformExit('sentinel'), 'App exiting: sentinel', id='app-exit'), + pytest.param( + PreCommitTerraformRuntimeError('sentinel'), + 'App execution took an unexpected turn: sentinel. Exiting...', + id='app-runtime-exc', + ), + pytest.param( + PreCommitTerraformBaseError('sentinel'), + 'A surprising exception happened: sentinel. Exiting...', + id='app-base-exc', + ), + pytest.param( + KeyboardInterrupt('sentinel'), + 'User-initiated interrupt: sentinel. Exiting...', + id='ctrl-c', + ), + ), +) +def test_known_interrupts( + capsys: pytest.CaptureFixture[str], + expected_stderr: str, + monkeypatch: pytest.MonkeyPatch, + raised_error: BaseException, +) -> None: + """Check that known interrupts are turned into return code 1.""" + class CustomCmdStub: + CLI_SUBCOMMAND_NAME = 'sentinel' + + def populate_argument_parser( + self, subcommand_parser: ArgumentParser, + ) -> None: + return None + + def invoke_cli_app(self, parsed_cli_args: Namespace) -> ReturnCodeType: + raise raised_error + + monkeypatch.setattr( + _cli_parsing_mod, + 'SUBCOMMAND_MODULES', + [CustomCmdStub()], + ) + + assert ReturnCode.ERROR == invoke_cli_app(['sentinel']) + + captured_outputs = capsys.readouterr() + assert captured_outputs.err == f'{expected_stderr !s}\n' + + +def test_app_exit( + capsys: pytest.CaptureFixture[str], + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Check that an exit exception is re-raised.""" + class CustomCmdStub: + CLI_SUBCOMMAND_NAME = 'sentinel' + + def populate_argument_parser( + self, subcommand_parser: ArgumentParser, + ) -> None: + return None + + def invoke_cli_app(self, parsed_cli_args: Namespace) -> ReturnCodeType: + raise PreCommitTerraformExit('sentinel') + + monkeypatch.setattr( + _cli_parsing_mod, + 'SUBCOMMAND_MODULES', + [CustomCmdStub()], + ) + + with pytest.raises(PreCommitTerraformExit, match='^sentinel$'): + invoke_cli_app(['sentinel']) + + captured_outputs = capsys.readouterr() + assert captured_outputs.err == 'App exiting: sentinel\n' diff --git a/tests/pytest/terraform_docs_replace_test.py b/tests/pytest/terraform_docs_replace_test.py new file mode 100644 index 000000000..87989d965 --- /dev/null +++ b/tests/pytest/terraform_docs_replace_test.py @@ -0,0 +1,123 @@ +"""Tests for the `replace-docs` subcommand.""" + +from argparse import ArgumentParser, Namespace +from subprocess import CalledProcessError + +import pytest +import pytest_mock + +from pre_commit_terraform._structs import ReturnCode +from pre_commit_terraform.terraform_docs_replace import ( + invoke_cli_app, + populate_argument_parser, + subprocess as replace_docs_subprocess_mod, +) + + +def test_arg_parser_populated() -> None: + """Check that `replace-docs` populates its parser.""" + test_arg_parser = ArgumentParser() + populate_argument_parser(test_arg_parser) + assert test_arg_parser.get_default('dest') == 'README.md' + + +def test_check_is_deprecated() -> None: + """Verify that `replace-docs` shows a deprecation warning.""" + deprecation_msg_regex = ( + r'^`terraform_docs_replace` hook is DEPRECATED\.' + 'For migration.*$' + ) + with pytest.warns(UserWarning, match=deprecation_msg_regex): + # not `pytest.deprecated_call()` due to this being a user warning + invoke_cli_app(Namespace(filenames=[])) + + +@pytest.mark.parametrize( + ('parsed_cli_args', 'expected_cmds'), + ( + pytest.param(Namespace(filenames=[]), [], id='no-files'), + pytest.param( + Namespace( + dest='SENTINEL.md', + filenames=['some.tf'], + sort=False, + ), + ['terraform-docs md ./ > .//SENTINEL.md'], + id='one-file', + ), + pytest.param( + Namespace( + dest='SENTINEL.md', + filenames=['some.tf', 'thing/weird.tfvars'], + sort=True, + ), + [ + 'terraform-docs --sort-by-required md ./ > .//SENTINEL.md', + 'terraform-docs --sort-by-required md ./thing ' + '> ./thing/SENTINEL.md', + ], + id='two-sorted-files', + ), + pytest.param( + Namespace(filenames=['some.thing', 'un.supported']), + [], + id='invalid-files', + ), + ), +) +@pytest.mark.filterwarnings( + 'ignore:`terraform_docs_replace` hook is DEPRECATED.:UserWarning:' + 'pre_commit_terraform.terraform_docs_replace', +) +def test_control_flow_positive( + expected_cmds: list[str], + mocker: pytest_mock.MockerFixture, + monkeypatch: pytest.MonkeyPatch, + parsed_cli_args: Namespace, +) -> None: + """Check that the subcommand's happy path works.""" + check_call_mock = mocker.Mock() + monkeypatch.setattr( + replace_docs_subprocess_mod, + 'check_call', + check_call_mock, + ) + + assert ReturnCode.OK == invoke_cli_app(parsed_cli_args) + + executed_commands = [ + cmd for ((cmd, ), _shell) in check_call_mock.call_args_list + ] + + assert len(expected_cmds) == check_call_mock.call_count + assert expected_cmds == executed_commands + + +@pytest.mark.filterwarnings( + 'ignore:`terraform_docs_replace` hook is DEPRECATED.:UserWarning:' + 'pre_commit_terraform.terraform_docs_replace', +) +def test_control_flow_negative( + mocker: pytest_mock.MockerFixture, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Check that the subcommand's error processing works.""" + parsed_cli_args = Namespace( + dest='SENTINEL.md', + filenames=['some.tf'], + sort=True, + ) + expected_cmd = 'terraform-docs --sort-by-required md ./ > .//SENTINEL.md' + + check_call_mock = mocker.Mock( + side_effect=CalledProcessError(ReturnCode.ERROR, expected_cmd), + ) + monkeypatch.setattr( + replace_docs_subprocess_mod, + 'check_call', + check_call_mock, + ) + + assert ReturnCode.ERROR == invoke_cli_app(parsed_cli_args) + + check_call_mock.assert_called_once_with(expected_cmd, shell=True) diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..195db1536 --- /dev/null +++ b/tox.ini @@ -0,0 +1,286 @@ +[tox] +isolated_build = true + + +[python-cli-options] +byte-warnings = -b +byte-errors = -bb +max-isolation = -E -s -I +some-isolation = -E -s +warnings-to-errors = -Werror + + +[testenv] +description = Run pytest under {envpython} +dependency_groups = + testing +commands = + {envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -m pytest \ + {tty:--color=yes} \ + {posargs:--cov-report=html:{envtmpdir}{/}htmlcov{/}} +commands_post = + -{envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -c \ + 'import atexit, os, sys; \ + os.getenv("GITHUB_ACTIONS") == "true" or sys.exit(); \ + import coverage; \ + gh_summary_fd = open(\ + os.environ["GITHUB_STEP_SUMMARY"], encoding="utf-8", mode="a",\ + ); \ + atexit.register(gh_summary_fd.close); \ + cov = coverage.Coverage(); \ + cov.load(); \ + cov.report(file=gh_summary_fd, output_format="markdown")' + {envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -c \ + 'import os, pathlib, sys; \ + os.getenv("GITHUB_ACTIONS") == "true" or sys.exit(); \ + cov_report_arg_prefix = "--cov-report=xml:"; \ + test_report_arg_prefix = "--junitxml="; \ + cov_reports = [\ + arg[len(cov_report_arg_prefix):] for arg in sys.argv \ + if arg.startswith(cov_report_arg_prefix)\ + ]; \ + test_reports = [\ + arg[len(test_report_arg_prefix):] for arg in sys.argv \ + if arg.startswith(test_report_arg_prefix)\ + ]; \ + cov_report_file = cov_reports[-1] if cov_reports else None; \ + test_report_file = test_reports[-1] if test_reports else None; \ + gh_output_fd = open(\ + os.environ["GITHUB_OUTPUT"], encoding="utf-8", mode="a",\ + ); \ + cov_report_file and \ + print(f"cov-report-files={cov_report_file !s}", file=gh_output_fd); \ + test_report_file and \ + print(f"test-result-files={test_report_file !s}", file=gh_output_fd); \ + print("codecov-flags=pytest", file=gh_output_fd); \ + gh_output_fd.close()' \ + {posargs} + # Print out the output coverage dir and a way to serve html: + {envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -c\ + 'import pathlib, shlex, sys; \ + cov_html_report_arg_prefix = "--cov-report=html:"; \ + cov_html_reports = [\ + arg[len(cov_html_report_arg_prefix):] for arg in sys.argv \ + if arg.startswith(cov_html_report_arg_prefix)\ + ]; \ + cov_html_reports or sys.exit(); \ + cov_html_report_dir = pathlib.Path(cov_html_reports[-1]); \ + index_file = cov_html_report_dir / "index.html";\ + html_url = f"file://\{index_file\}";\ + browse_cmd = shlex.join(("python3", "-Im", "webbrowser", html_url)); \ + serve_cmd = shlex.join((\ + "python3", "-Im", "http.server", \ + "--directory", "cov_html_report_dir", "0", \ + )); \ + print(f"\nTo open the HTML coverage report, run\n\n\ + \t\{browse_cmd !s\}\n");\ + print(f"To serve \ + the HTML coverage report with a local web server, use\n\n\ + \t\{serve_cmd !s\}\n")' \ + {posargs:--cov-report=html:{envtmpdir}{/}htmlcov{/}} +package = editable +pass_env = + CI + GITHUB_* + SSH_AUTH_SOCK + TERM +set_env = + COVERAGE_PROCESS_START = {toxinidir}{/}.coveragerc +wheel_build_env = .pkg + + +[testenv:cleanup-dists] +description = + Wipe the the dist{/} folder +dependency_groups = +commands_pre = +commands = + {envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -c \ + 'import os, shutil, sys; \ + dists_dir = "{toxinidir}{/}dist{/}"; \ + shutil.rmtree(dists_dir, ignore_errors=True); \ + sys.exit(os.path.exists(dists_dir))' +commands_post = +package = skip + + +[testenv:build-dists] +description = + Build dists with {basepython} and put them into the dist{/} folder +dependency_groups = + building +depends = + cleanup-dists +commands = + {envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -m build \ + {posargs:} +commands_post = +package = skip + + +[testenv:metadata-validation] +description = + Verify that dists under the `dist{/}` dir + have valid metadata +dependency_groups = + upstreaming +depends = + build-dists +commands = + {envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -m twine \ + check \ + --strict \ + dist{/}* +commands_post = +package = skip + + +[testenv:pre-commit] +description = + Run the quality checks under {basepython}; run as + `SKIP=check-id1,check-id2 tox r -e pre-commit` to instruct the underlying + `pre-commit` invocation avoid running said checks; Use + `tox r -e pre-commit -- check-id1 --all-files` to select checks matching IDs + aliases{:} `tox r -e pre-commit -- mypy --all-files` will run 3 MyPy + invocations, but `tox r -e pre-commit -- mypy-py313 --all-files` runs one. +commands = + {envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -m pre_commit \ + run \ + --color=always \ + --show-diff-on-failure \ + {posargs:--all-files} + + # Print out the advice on how to install pre-commit from this env into Git: + -{envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -c \ + 'cmd = "{envpython} -m pre_commit install"; \ + scr_width = len(cmd) + 10; \ + sep = "=" * scr_width; \ + cmd_str = " $ \{cmd\}";' \ + 'print(f"\n\{sep\}\nTo install pre-commit hooks into the Git repo, run:\ + \n\n\{cmd_str\}\n\n\{sep\}\n")' +commands_post = + {envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -c \ + 'import os, pathlib, sys; \ + os.getenv("GITHUB_ACTIONS") == "true" or sys.exit(); \ + project_root_path = pathlib.Path(r"{toxinidir}"); \ + test_results_dir = pathlib.Path(r"{temp_dir}") / ".test-results"; \ + coverage_result_files = ",".join(\ + str(xml_path.relative_to(project_root_path)) \ + for xml_path in test_results_dir.glob("mypy--py-*{/}cobertura.xml")\ + ); \ + gh_output_fd = open(\ + os.environ["GITHUB_OUTPUT"], encoding="utf-8", mode="a",\ + ); \ + print(\ + f"cov-report-files={coverage_result_files !s}", file=gh_output_fd\ + ); \ + print("codecov-flags=MyPy", file=gh_output_fd); \ + gh_output_fd.close()' + {envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -c \ + 'import itertools, os, pathlib, shlex, sys; \ + os.getenv("GITHUB_ACTIONS") == "true" or sys.exit(); \ + test_results_dir = pathlib.Path(r"{temp_dir}") / ".test-results"; \ + text_and_json_reports = itertools.chain( \ + test_results_dir.glob("mypy--py-*{/}*.json"), \ + test_results_dir.glob("mypy--py-*{/}*.txt"), \ + ); \ + report_contents = { \ + report{:} report.read_text() \ + for report in text_and_json_reports \ + }; \ + reports_summary_text_blob = "\n\n".join( \ + f"\N\{NUMBER SIGN\}\N\{NUMBER SIGN\} {report_path.parent.name}{:} " \ + f"`{report_path.name}`\n\n" \ + f"```{report_path.suffix[1:]}\n{report_text}\n```\n" \ + for report_path, report_text in report_contents.items() \ + ); \ + gh_summary_fd = open( \ + os.environ["GITHUB_STEP_SUMMARY"], encoding="utf-8", mode="a", \ + ); \ + print(reports_summary_text_blob, file=gh_summary_fd); \ + gh_summary_fd.close()' + # Print out the output coverage dir and a way to serve html: + {envpython} \ + {[python-cli-options]byte-errors} \ + {[python-cli-options]max-isolation} \ + {[python-cli-options]warnings-to-errors} \ + -c\ + 'import os, pathlib, sys; \ + os.getenv("GITHUB_ACTIONS") == "true" and sys.exit(); \ + len(sys.argv) >= 3 and all(\ + arg != "mypy" and not arg.startswith("mypy-py3") \ + for arg in sys.argv \ + ) and sys.exit(); \ + project_root_path = pathlib.Path(r"{toxinidir}"); \ + test_results_dir = pathlib.Path(r"{temp_dir}") / ".test-results"; \ + coverage_html_report_urls = [\ + f"file://\{xml_path !s\}" \ + for xml_path in test_results_dir.glob("mypy--py-*{/}index.html")\ + ]; \ + coverage_html_report_open_cmds = [\ + f"python3 -Im webbrowser \N\{QUOTATION MARK\}\{html_url !s\}\N\{QUOTATION MARK\}" \ + for html_url in coverage_html_report_urls\ + ]; \ + coverage_html_report_open_cmds_blob = "\n\n\t".join(\ + coverage_html_report_open_cmds,\ + ); \ + print(\ + f"\nTo open the HTML coverage reports, run\n\n\ + \t\{coverage_html_report_open_cmds_blob !s\}\n"\ + ); \ + print(\ + f"[*] Find rest of JSON and text reports, are in the same directories."\ + )\ + ' \ + {posargs:--all-files} +dependency_groups = + linting +isolated_build = true +package = skip +pass_env = + {[testenv]pass_env} + SKIP # set this variable From 8bedb53fdd60d4894527829dbbce9f89faad8500 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 8 Jan 2025 16:23:36 +0200 Subject: [PATCH 106/293] chore: Add to `.gitignore` common Python temp files (#746) --- .gitignore | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/.gitignore b/.gitignore index 0bbeada90..10bbad3e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,179 @@ +# Generated by https://gitignore.io +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python + tests/results/* From 900829c004c103a1d501786f960618d6abbe2d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Wed, 8 Jan 2025 20:37:45 +0100 Subject: [PATCH 107/293] ci: Disable `no-commit-to-branch` @ GHA (#748) This turns off the "is push to master" in GitHub Actions CI/CD as it does not make sense in this environment, being local-only [[1]]. [1]: https://github.com/pre-commit/pre-commit-hooks/issues/1124 --- .github/workflows/ci-cd.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f5c0ca61d..0e18ee217 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -387,8 +387,14 @@ jobs: - pre-commit - metadata-validation environment-variables: + # `no-commit-to-branch` is skipped because it does not make sense + # in the CI, only locally. + # Ref: https://github.com/pre-commit/pre-commit-hooks/issues/1124 - >- # only affects pre-commit, set for all for simplicity: - SKIP=hadolint,shfmt + SKIP= + hadolint, + no-commit-to-branch, + shfmt, tox-run-posargs: - '' xfail: From 1c9823e33971332ec311e7aba73880328c942cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Thu, 9 Jan 2025 23:49:07 +0100 Subject: [PATCH 108/293] chore: Integrate `yamllint` into the project (#747) The initial config does not change many defaults and mostly relies on the upstream config. The only changes are to the sequence style, quoting and allowed truthy values that accomodate for GHA. --- .github/.container-structure-test-config.yaml | 240 +++++++++++------- .github/.dive-ci.yaml | 8 +- .github/FUNDING.yml | 2 + .github/workflows/build-image-test.yaml | 148 ++++++----- .github/workflows/build-image.yaml | 112 ++++---- .github/workflows/pr-title.yml | 89 ++++--- .github/workflows/pre-commit.yaml | 65 +++-- .github/workflows/release.yml | 45 ++-- .github/workflows/stale-actions.yaml | 39 ++- .pre-commit-config.yaml | 106 ++++---- .pre-commit-hooks.yaml | 27 +- .yamllint | 20 ++ 12 files changed, 541 insertions(+), 360 deletions(-) create mode 100644 .yamllint diff --git a/.github/.container-structure-test-config.yaml b/.github/.container-structure-test-config.yaml index 33beae533..bfaa36c18 100644 --- a/.github/.container-structure-test-config.yaml +++ b/.github/.container-structure-test-config.yaml @@ -1,101 +1,145 @@ -schemaVersion: '2.0.0' +--- + +schemaVersion: 2.0.0 + commandTests: - - name: "git" - command: "git" - args: ["--version"] - expectedOutput: ["^git version 2\\.[0-9]+\\.[0-9]+\\n$"] - - - name: "pre-commit" - command: "pre-commit" - args: ["-V"] - expectedOutput: ["^pre-commit ([0-9]+\\.){2}[0-9]+\\n$"] - - - name: "gcc" - command: "gcc" - args: ["--version"] - expectedOutput: ["^gcc \\(Alpine 12\\."] - - - name: "checkov" - command: "checkov" - args: ["--version"] - expectedOutput: ["^([0-9]+\\.){2}[0-9]+\\n$"] - - - name: "infracost" - command: "infracost" - args: ["--version"] - expectedOutput: ["^Infracost v([0-9]+\\.){2}[0-9]+"] - - - name: "opentofu" - command: "tofu" - args: ["-version"] - expectedOutput: ["^OpenTofu v([0-9]+\\.){2}[0-9]+\\n"] - - - name: "terraform" - command: "terraform" - args: ["-version"] - expectedOutput: ["^Terraform v([0-9]+\\.){2}[0-9]+\\n"] - - - name: "terraform-docs" - command: "terraform-docs" - args: ["--version"] - expectedOutput: ["^terraform-docs version v([0-9]+\\.){2}[0-9]+ [a-z0-9]+"] - - - name: "terragrunt" - command: "terragrunt" - args: ["--version"] - expectedOutput: ["^terragrunt version v([0-9]+\\.){2}[0-9]+\\n$"] - - - name: "terrascan" - command: "terrascan" - args: [ "version" ] - expectedOutput: [ "^version: v([0-9]+\\.){2}[0-9]+\\n$" ] - - - name: "tflint" - command: "tflint" - args: [ "--version" ] - expectedOutput: [ "TFLint version ([0-9]+\\.){2}[0-9]+\\n" ] - - - name: "tfsec" - command: "tfsec" - args: [ "--version" ] - expectedOutput: [ "([0-9]+\\.){2}[0-9]+\\n$" ] - - - name: "trivy" - command: "trivy" - args: [ "--version" ] - expectedOutput: [ "Version: ([0-9]+\\.){2}[0-9]+\\n" ] - - - name: "tfupdate" - command: "tfupdate" - args: [ "--version" ] - expectedOutput: [ "([0-9]+\\.){2}[0-9]+\\n$" ] - - - name: "hcledit" - command: "hcledit" - args: [ "version" ] - expectedOutput: [ "([0-9]+\\.){2}[0-9]+\\n$" ] - - - name: "entrypoint.sh" - envVars: - - key: "USERID" - value: "1000:1000" - command: "/entrypoint.sh" - args: [ "-V" ] - expectedError: ["^ERROR: uid:gid 1000:1000 lacks permissions to //\\n$"] - exitCode: 1 - - - name: "su-exec" - command: "su-exec" - expectedOutput: ["^Usage: su-exec user-spec command \\[args\\]\\n$"] - - - name: "ssh" - command: "ssh" - args: [ "-V" ] - expectedError: ["^OpenSSH_9\\.[0-9]+"] +- name: git + command: git + args: + - --version + expectedOutput: + - ^git version 2\.[0-9]+\.[0-9]+\n$ + +- name: pre-commit + command: pre-commit + args: + - -V + expectedOutput: + - ^pre-commit ([0-9]+\.){2}[0-9]+\n$ + +- name: gcc + command: gcc + args: + - --version + expectedOutput: + - ^gcc \(Alpine 12\. + +- name: checkov + command: checkov + args: + - --version + expectedOutput: + - ^([0-9]+\.){2}[0-9]+\n$ + +- name: infracost + command: infracost + args: + - --version + expectedOutput: + - ^Infracost v([0-9]+\.){2}[0-9]+ + +- name: opentofu + command: tofu + args: + - --version + expectedOutput: + - ^OpenTofu v([0-9]+\.){2}[0-9]+\n + +- name: terraform + command: terraform + args: + - --version + expectedOutput: + - ^Terraform v([0-9]+\.){2}[0-9]+\n + +- name: terraform-docs + command: terraform-docs + args: + - --version + expectedOutput: + - ^terraform-docs version v([0-9]+\.){2}[0-9]+ [a-z0-9]+ + +- name: terragrunt + command: terragrunt + args: + - --version + expectedOutput: + - ^terragrunt version v([0-9]+\.){2}[0-9]+\n$ + +- name: terrascan + command: terrascan + args: + - version + expectedOutput: + - >- + ^version: v([0-9]+\.){2}[0-9]+\n$ + +- name: tflint + command: tflint + args: + - --version + expectedOutput: + - TFLint version ([0-9]+\.){2}[0-9]+\n + +- name: tfsec + command: tfsec + args: + - --version + expectedOutput: + - ([0-9]+\.){2}[0-9]+\n$ + +- name: trivy + command: trivy + args: + - --version + expectedOutput: + - >- + Version: ([0-9]+\.){2}[0-9]+\n + +- name: tfupdate + command: tfupdate + args: + - --version + expectedOutput: + - ([0-9]+\.){2}[0-9]+\n$ + +- name: hcledit + command: hcledit + args: + - version + expectedOutput: + - ([0-9]+\.){2}[0-9]+\n$ + +- name: entrypoint.sh + envVars: + - key: USERID + value: 1000:1000 + command: /entrypoint.sh + args: + - -V + expectedError: + - >- + ^ERROR: uid:gid 1000:1000 lacks permissions to //\n$ + exitCode: 1 + +- name: su-exec + command: su-exec + expectedOutput: + - >- + ^Usage: su-exec user-spec command \[args\]\n$ + +- name: ssh + command: ssh + args: + - -V + expectedError: + - ^OpenSSH_9\.[0-9]+ fileExistenceTests: - - name: 'terrascan init' - path: '/root/.terrascan/pkg/policies/opa/rego/github/github_repository/privateRepoEnabled.rego' - shouldExist: true - uid: 0 - gid: 0 +- name: terrascan init + # yamllint disable-line rule:line-length + path: >- + /root/.terrascan/pkg/policies/opa/rego/github/github_repository/privateRepoEnabled.rego + shouldExist: true + uid: 0 + gid: 0 diff --git a/.github/.dive-ci.yaml b/.github/.dive-ci.yaml index 3c526cd5e..95598bea3 100644 --- a/.github/.dive-ci.yaml +++ b/.github/.dive-ci.yaml @@ -1,13 +1,17 @@ +--- + rules: # If the efficiency is measured below X%, mark as failed. # Expressed as a ratio between 0-1. lowestEfficiency: 0.981 - # If the amount of wasted space is at least X or larger than X, mark as failed. + # If the amount of wasted space is at least X or larger than X, mark + # as failed. # Expressed in B, KB, MB, and GB. highestWastedBytes: 32MB - # If the amount of wasted space makes up for X% or more of the image, mark as failed. + # If the amount of wasted space makes up for X% or more of the image, + # mark as failed. # Note: the base image layer is NOT included in the total image size. # Expressed as a ratio between 0-1; fails if the threshold is met or crossed. highestUserWastedPercent: 0.036 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 9fdcfce62..bea8a72a9 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,4 @@ +--- + github: [antonbabenko] custom: https://www.paypal.me/antonbabenko diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 60cc8edef..7c43a42f5 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -1,4 +1,6 @@ -name: "Build Dockerfile if changed and run smoke tests" +--- + +name: Build Dockerfile if changed and run smoke tests on: [pull_request] @@ -14,76 +16,88 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - fetch-depth: 0 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + fetch-depth: 0 - - name: Get changed Docker related files - id: changed-files-specific - uses: tj-actions/changed-files@d6babd6899969df1a11d14c368283ea4436bca78 # v44.5.2 - with: - files: | - Dockerfile - .dockerignore - tools/entrypoint.sh - .github/workflows/build-image-test.yaml - tools/*.sh + - name: Get changed Docker related files + id: changed-files-specific + # yamllint disable-line rule:line-length + uses: tj-actions/changed-files@d6babd6899969df1a11d14c368283ea4436bca78 # v44.5.2 + with: + files: | + Dockerfile + .dockerignore + tools/entrypoint.sh + .github/workflows/build-image-test.yaml + tools/*.sh - - name: Set up QEMU - if: matrix.os != 'ubuntu-latest' || matrix.arch != 'amd64' - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - with: - platforms: 'arm64' + - name: Set up QEMU + if: matrix.os != 'ubuntu-latest' || matrix.arch != 'amd64' + # yamllint disable-line rule:line-length + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + with: + platforms: arm64 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + - name: Set up Docker Buildx + # yamllint disable-line rule:line-length + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - - name: Build if Dockerfile changed - if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 - with: - context: . - build-args: | - INSTALL_ALL=true - platforms: linux/${{ matrix.arch }} # Only one allowed here, see https://github.com/docker/buildx/issues/59#issuecomment-1433097926 - push: false - load: true - tags: | - ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} - # Fix multi-platform: https://github.com/docker/buildx/issues/1533 - provenance: false - secrets: | - "github_token=${{ secrets.GITHUB_TOKEN }}" + - name: Build if Dockerfile changed + if: steps.changed-files-specific.outputs.any_changed == 'true' + # yamllint disable-line rule:line-length + uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 + with: + context: . + build-args: | + INSTALL_ALL=true + # yamllint disable-line rule:line-length + platforms: >- # Only one allowed here, see https://github.com/docker/buildx/issues/59#issuecomment-1433097926 + linux/${{ matrix.arch }} + push: false + load: true + tags: | + ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} + # Fix multi-platform: https://github.com/docker/buildx/issues/1533 + provenance: false + secrets: | + "github_token=${{ secrets.GITHUB_TOKEN }}" - - name: Run structure tests - if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0 - with: - image: ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} - config: .github/.container-structure-test-config.yaml + - name: Run structure tests + if: steps.changed-files-specific.outputs.any_changed == 'true' + # yamllint disable-line rule:line-length + uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0 + with: + image: ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} + config: .github/.container-structure-test-config.yaml - - name: Dive - check image for waste files - if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: MaxymVlasov/dive-action@379af3fc636888ada5899c997e8b52db6ad45023 # v1.0.1 - with: - image: ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} - config-file: ${{ github.workspace }}/.github/.dive-ci.yaml - github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Dive - check image for waste files + if: steps.changed-files-specific.outputs.any_changed == 'true' + # yamllint disable-line rule:line-length + uses: MaxymVlasov/dive-action@379af3fc636888ada5899c997e8b52db6ad45023 # v1.0.1 + with: + image: ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} + config-file: ${{ github.workspace }}/.github/.dive-ci.yaml + github-token: ${{ secrets.GITHUB_TOKEN }} - # Can't build both platforms and use --load at the same time - # https://github.com/docker/buildx/issues/59#issuecomment-1433097926 - - name: Build Multi-arch docker-image - if: steps.changed-files-specific.outputs.any_changed == 'true' && matrix.os == 'ubuntu-latest' && matrix.arch == 'amd64' - uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 - with: - context: . - build-args: | - INSTALL_ALL=true - platforms: linux/amd64,linux/arm64 - push: false - tags: | - ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} - # Fix multi-platform: https://github.com/docker/buildx/issues/1533 - provenance: false - secrets: | - "github_token=${{ secrets.GITHUB_TOKEN }}" + # Can't build both platforms and use --load at the same time + # https://github.com/docker/buildx/issues/59#issuecomment-1433097926 + - name: Build Multi-arch docker-image + if: >- + steps.changed-files-specific.outputs.any_changed == 'true' + && matrix.os == 'ubuntu-latest' + && matrix.arch == 'amd64' + # yamllint disable-line rule:line-length + uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 + with: + context: . + build-args: | + INSTALL_ALL=true + platforms: linux/amd64,linux/arm64 + push: false + tags: | + ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} + # Fix multi-platform: https://github.com/docker/buildx/issues/1533 + provenance: false + secrets: | + "github_token=${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 34d535c7e..ebc2e8911 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -1,65 +1,77 @@ +--- + name: Publish container image on: workflow_dispatch: release: types: - - created + - created schedule: - - cron: '00 00 * * *' + - cron: 00 00 * * * jobs: docker: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - name: Set up QEMU - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - - name: Login to GitHub Container Registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Set tag for image - run: | - echo IMAGE_TAG=$([ ${{ github.ref_type }} == 'tag' ] && echo ${{ github.ref_name }} || echo 'latest') >> $GITHUB_ENV + - name: Checkout code + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Set up QEMU + # yamllint disable-line rule:line-length + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + - name: Set up Docker Buildx + # yamllint disable-line rule:line-length + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + - name: Login to GitHub Container Registry + # yamllint disable-line rule:line-length + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Set tag for image + run: >- + echo IMAGE_TAG=$( + [ ${{ github.ref_type }} == 'tag' ] + && echo ${{ github.ref_name }} + || echo 'latest' + ) >> $GITHUB_ENV - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + - name: Set up Docker Buildx + # yamllint disable-line rule:line-length + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - - name: Build and Push release - if: github.event_name != 'schedule' - uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 - with: - context: . - build-args: | - INSTALL_ALL=true - platforms: linux/amd64,linux/arm64 - push: true - tags: | - ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} - ghcr.io/${{ github.repository }}:latest - # Fix multi-platform: https://github.com/docker/buildx/issues/1533 - provenance: false - secrets: | - "github_token=${{ secrets.GITHUB_TOKEN }}" + - name: Build and Push release + if: github.event_name != 'schedule' + # yamllint disable-line rule:line-length + uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 + with: + context: . + build-args: | + INSTALL_ALL=true + platforms: linux/amd64,linux/arm64 + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} + ghcr.io/${{ github.repository }}:latest + # Fix multi-platform: https://github.com/docker/buildx/issues/1533 + provenance: false + secrets: | + "github_token=${{ secrets.GITHUB_TOKEN }}" - - name: Build and Push nightly - if: github.event_name == 'schedule' - uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 - with: - context: . - build-args: | - INSTALL_ALL=true - platforms: linux/amd64,linux/arm64 - push: true - tags: | - ghcr.io/${{ github.repository }}:nightly - # Fix multi-platform: https://github.com/docker/buildx/issues/1533 - provenance: false - secrets: | - "github_token=${{ secrets.GITHUB_TOKEN }}" + - name: Build and Push nightly + if: github.event_name == 'schedule' + # yamllint disable-line rule:line-length + uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 + with: + context: . + build-args: | + INSTALL_ALL=true + platforms: linux/amd64,linux/arm64 + push: true + tags: | + ghcr.io/${{ github.repository }}:nightly + # Fix multi-platform: https://github.com/docker/buildx/issues/1533 + provenance: false + secrets: | + "github_token=${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 52568beef..da41a36d0 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -1,52 +1,57 @@ -name: "Validate PR title" +--- + +name: Validate PR title on: pull_request_target: types: - - opened - - edited - - synchronize + - opened + - edited + - synchronize jobs: main: name: Validate PR title runs-on: ubuntu-latest steps: - # Please look up the latest version from - # https://github.com/amannn/action-semantic-pull-request/releases - - uses: amannn/action-semantic-pull-request@cfb60706e18bc85e8aec535e3c577abe8f70378e # v5.5.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - # Configure which types are allowed. - # Default: https://github.com/commitizen/conventional-commit-types - types: | - fix - feat - docs - ci - chore - # Configure that a scope must always be provided. - requireScope: false - # Configure additional validation for the subject based on a regex. - # This example ensures the subject starts with an uppercase character. - subjectPattern: ^[A-Z].+$ - # If `subjectPattern` is configured, you can use this property to override - # the default error message that is shown when the pattern doesn't match. - # The variables `subject` and `title` can be used within the message. - subjectPatternError: | - The subject "{subject}" found in the pull request title "{title}" - didn't match the configured pattern. Please ensure that the subject - starts with an uppercase character. - # For work-in-progress PRs you can typically use draft pull requests - # from Github. However, private repositories on the free plan don't have - # this option and therefore this action allows you to opt-in to using the - # special "[WIP]" prefix to indicate this state. This will avoid the - # validation of the PR title and the pull request checks remain pending. - # Note that a second check will be reported if this is enabled. - wip: true - # When using "Squash and merge" on a PR with only one commit, GitHub - # will suggest using that commit message instead of the PR title for the - # merge commit, and it's easy to commit this by mistake. Enable this option - # to also validate the commit message for one commit PRs. - validateSingleCommit: false + # Please look up the latest version from + # https://github.com/amannn/action-semantic-pull-request/releases + # yamllint disable-line rule:line-length + - uses: amannn/action-semantic-pull-request@cfb60706e18bc85e8aec535e3c577abe8f70378e # v5.5.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Configure which types are allowed. + # Default: https://github.com/commitizen/conventional-commit-types + types: | + fix + feat + docs + ci + chore + # Configure that a scope must always be provided. + requireScope: false + # Configure additional validation for the subject based on a regex. + # This example ensures the subject starts with an uppercase character. + subjectPattern: ^[A-Z].+$ + # If `subjectPattern` is configured, you can use this property to + # override the default error message that is shown when the pattern + # doesn't match. The variables `subject` and `title` can be used within + # the message. + subjectPatternError: | + The subject "{subject}" found in the pull request title "{title}" + didn't match the configured pattern. Please ensure that the subject + starts with an uppercase character. + # For work-in-progress PRs you can typically use draft pull requests + # from Github. However, private repositories on the free plan don't + # have this option and therefore this action allows you to opt-in to + # using the special "[WIP]" prefix to indicate this state. This will + # avoid the validation of the PR title and the pull request checks + # remain pending. Note that a second check will be reported if this + # is enabled. + wip: true + # When using "Squash and merge" on a PR with only one commit, GitHub + # will suggest using that commit message instead of the PR title for + # the merge commit, and it's easy to commit this by mistake. Enable + # this option to also validate the commit message for one commit PRs. + validateSingleCommit: false diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 99e246818..ab6337910 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -1,3 +1,5 @@ +--- + name: Common issues check on: [pull_request] @@ -6,20 +8,27 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - run: | - git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - run: >- + git fetch --no-tags --prune --depth=1 origin + +refs/heads/*:refs/remotes/origin/* - name: Get changed files id: file_changes run: | - export DIFF=$(git diff --name-only origin/${{ github.base_ref }} ${{ github.sha }}) + export DIFF=$(git diff --name-only origin/${{ github.base_ref }} ${{ + github.sha + }}) echo "Diff between ${{ github.base_ref }} and ${{ github.sha }}" echo "files=$( echo "$DIFF" | xargs echo )" >> $GITHUB_OUTPUT - name: Install shfmt - run: | - curl -L "$(curl -s https://api.github.com/repos/mvdan/sh/releases/latest | grep -o -E -m 1 "https://.+?linux_amd64")" > shfmt \ + run: >- + curl -L "$( + curl -s https://api.github.com/repos/mvdan/sh/releases/latest + | grep -o -E -m 1 "https://.+?linux_amd64" + )" + > shfmt && chmod +x shfmt && sudo mv shfmt /usr/bin/ - name: Install shellcheck @@ -27,30 +36,56 @@ jobs: sudo apt update && sudo apt install shellcheck - name: Install hadolint - 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 \ + 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/ # Need to success pre-commit fix push - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} # Skip terraform_tflint which interferes to commit pre-commit auto-fixes - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + # yamllint disable-line rule:line-length + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.9' - name: Execute pre-commit - uses: pre-commit/action@9b88afc9cd57fd75b655d5c71bd38146d07135fe # v2.0.3 + uses: pre-commit/action@9b88afc9cd57fd75b655d5c71bd38146d07135fe # v2.0.3 env: SKIP: no-commit-to-branch,hadolint with: token: ${{ secrets.GITHUB_TOKEN }} - extra_args: --color=always --show-diff-on-failure --files ${{ steps.file_changes.outputs.files }} + extra_args: >- + --color=always + --show-diff-on-failure + --files ${{ steps.file_changes.outputs.files }} # 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@9b88afc9cd57fd75b655d5c71bd38146d07135fe # v2.0.3 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 + 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: - extra_args: --color=always --show-diff-on-failure --files ${{ steps.file_changes.outputs.files }} + extra_args: >- + --color=always + --show-diff-on-failure + --files ${{ steps.file_changes.outputs.files }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c6e8f431f..f5ca45aff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,34 +1,37 @@ +--- + name: Release on: workflow_dispatch: push: branches: - - master + - master paths: - - '**/*.py' - - '**/*.sh' - - 'Dockerfile' - - '.pre-commit-hooks.yaml' - # Ignore paths - - '!tests/**' + - '**/*.py' + - '**/*.sh' + - Dockerfile + - .pre-commit-hooks.yaml + # Ignore paths + - '!tests/**' jobs: release: name: Release runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - persist-credentials: false - fetch-depth: 0 + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + persist-credentials: false + fetch-depth: 0 - - name: Release - uses: cycjimmy/semantic-release-action@cb425203a562475bca039ba4dbf90c7f9ac790f4 # v4.1.0 - with: - semantic_version: 18.0.0 - extra_plugins: | - @semantic-release/changelog@6.0.0 - @semantic-release/git@10.0.0 - env: - GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} + - name: Release + # yamllint disable-line rule:line-length + uses: cycjimmy/semantic-release-action@cb425203a562475bca039ba4dbf90c7f9ac790f4 # v4.1.0 + with: + semantic_version: 18.0.0 + extra_plugins: | + @semantic-release/changelog@6.0.0 + @semantic-release/git@10.0.0 + env: + GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} diff --git a/.github/workflows/stale-actions.yaml b/.github/workflows/stale-actions.yaml index 32f08e781..d4ed2caef 100644 --- a/.github/workflows/stale-actions.yaml +++ b/.github/workflows/stale-actions.yaml @@ -1,32 +1,45 @@ -name: "Mark or close stale issues and PRs" +--- + +name: Mark or close stale issues and PRs on: schedule: - - cron: "0 0 * * *" + - cron: 0 0 * * * jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} # Staling issues and PR's days-before-stale: 30 stale-issue-label: stale stale-pr-label: stale - stale-issue-message: | - This issue has been automatically marked as stale because it has been open 30 days - with no activity. Remove stale label or comment or this issue will be closed in 10 days - stale-pr-message: | - This PR has been automatically marked as stale because it has been open 30 days - with no activity. Remove stale label or comment or this PR will be closed in 10 days + stale-issue-message: > + This issue has been automatically marked as stale because it has been + open 30 days + + + with no activity. Remove stale label or comment or this issue will be + closed in 10 days + stale-pr-message: > + This PR has been automatically marked as stale because it has been + open 30 days + + + with no activity. Remove stale label or comment or this PR will be + closed in 10 days # Not stale if have this labels or part of milestone exempt-issue-labels: bug,wip,on-hold,auto-update - exempt-pr-labels: bug,wip,on-hold + exempt-pr-labels: bug,wip,on-hold exempt-all-milestones: true # Close issue operations - # Label will be automatically removed if the issues are no longer closed nor locked. + # Label will be automatically removed if the issues are no longer + # closed nor locked. days-before-close: 10 delete-branch: true - close-issue-message: This issue was automatically closed because of stale in 10 days - close-pr-message: This PR was automatically closed because of stale in 10 days + close-issue-message: >- + This issue was automatically closed because of stale in 10 days + close-pr-message: >- + This PR was automatically closed because of stale in 10 days diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75a649ea1..d649bafe7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,44 +1,57 @@ +--- + ci: autoupdate_schedule: quarterly skip: - - shfmt - - shellcheck - - hadolint + - shfmt + - shellcheck + - hadolint repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - # Git style - - id: check-added-large-files - - id: check-merge-conflict - - id: check-vcs-permalinks - - id: forbid-new-submodules - - id: no-commit-to-branch + # Git style + - id: check-added-large-files + - id: check-merge-conflict + - id: check-vcs-permalinks + - id: forbid-new-submodules + - id: no-commit-to-branch - # Common errors - - id: end-of-file-fixer - - id: trailing-whitespace - args: [--markdown-linebreak-ext=md] - exclude: CHANGELOG.md - - id: check-yaml - - id: check-merge-conflict - - id: check-executables-have-shebangs + # Common errors + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + exclude: CHANGELOG.md + - id: check-yaml + - id: check-merge-conflict + - id: check-executables-have-shebangs - # Cross platform - - id: check-case-conflict - - id: mixed-line-ending - args: [--fix=lf] + # Cross platform + - id: check-case-conflict + - id: mixed-line-ending + args: [--fix=lf] - # Security - - id: detect-aws-credentials - args: ['--allow-missing-credentials'] - - id: detect-private-key + # Security + - id: detect-aws-credentials + args: + - --allow-missing-credentials + - id: detect-private-key - # Non-modifying checks: - - id: name-tests-test - files: >- - ^tests/[^_].*\.py$ + # Non-modifying checks: + - id: name-tests-test + files: >- + ^tests/[^_].*\.py$ + +- repo: https://github.com/adrienverge/yamllint.git + rev: v1.35.1 + hooks: + - id: yamllint + types: + - file + - yaml + args: + - --strict - repo: https://github.com/pre-commit/mirrors-mypy.git rev: v1.14.1 @@ -98,25 +111,30 @@ repos: - repo: https://github.com/jumanjihouse/pre-commit-hooks rev: 3.0.0 hooks: - - id: shfmt - args: ['-l', '-i', '2', '-ci', '-sr', '-w'] - - id: shellcheck + - id: shfmt + args: + - -l + - -i + - '2' + - -ci + - -sr + - -w + - id: shellcheck # Dockerfile linter - repo: https://github.com/hadolint/hadolint rev: v2.12.1-beta hooks: - - id: hadolint - args: [ - '--ignore', 'DL3007', # Using latest - '--ignore', 'DL3013', # Pin versions in pip - '--ignore', 'DL3027', # Do not use apt - '--ignore', 'DL3059', # Docker `RUN`s shouldn't be consolidated here - '--ignore', 'DL4006', # Not related to alpine - '--ignore', 'SC1091', # Useless check - '--ignore', 'SC2015', # Useless check - '--ignore', 'SC3037', # Not related to alpine - ] + - id: hadolint + args: + - --ignore=DL3007 # Using latest + - --ignore=DL3013 # Pin versions in pip + - --ignore=DL3027 # Do not use apt + - --ignore=DL3059 # Docker `RUN`s shouldn't be consolidated here + - --ignore=DL4006 # Not related to alpine + - --ignore=SC1091 # Useless check + - --ignore=SC2015 # Useless check + - --ignore=SC3037 # Not related to alpine # JSON5 Linter - repo: https://github.com/pre-commit/mirrors-prettier @@ -124,4 +142,4 @@ repos: hooks: - id: prettier # https://prettier.io/docs/en/options.html#parser - files: '.json5$' + files: .json5$ diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 9b8a0cf19..3be2522f1 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,3 +1,5 @@ +--- + - id: infracost_breakdown name: Infracost breakdown description: Check terraform infrastructure cost @@ -9,7 +11,8 @@ - id: terraform_fmt name: Terraform fmt - description: Rewrites all Terraform configuration files to a canonical format. + description: >- + Rewrites all Terraform configuration files to a canonical format. entry: hooks/terraform_fmt.sh language: script files: (\.tf|\.tfvars)$ @@ -17,7 +20,9 @@ - id: terraform_docs name: Terraform 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/terraform_docs.sh language: script @@ -26,7 +31,9 @@ - id: terraform_docs_without_aggregate_type_defaults name: Terraform 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/terraform_docs.sh language: script @@ -71,7 +78,8 @@ - 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)$ @@ -95,7 +103,8 @@ - id: terragrunt_providers_lock name: Terragrunt providers lock - description: Updates provider signatures in dependency lock files using terragrunt. + description: >- + Updates provider signatures in dependency lock files using terragrunt. entry: hooks/terragrunt_providers_lock.sh language: script files: (terragrunt|\.terraform\.lock)\.hcl$ @@ -103,7 +112,8 @@ - id: terraform_tfsec name: Terraform validate with tfsec (deprecated, use "terraform_trivy") - description: Static analysis of Terraform templates to spot potential security issues. + description: >- + Static analysis of Terraform templates to spot potential security issues. require_serial: true entry: hooks/terraform_tfsec.sh files: \.tf(vars)?$ @@ -111,7 +121,8 @@ - id: terraform_trivy name: Terraform validate with trivy - description: Static analysis of Terraform templates to spot potential security issues. + description: >- + Static analysis of Terraform templates to spot potential security issues. require_serial: true entry: hooks/terraform_trivy.sh files: \.tf(vars)?$ @@ -164,6 +175,6 @@ language: script entry: hooks/tfupdate.sh args: - - --args=terraform + - --args=terraform files: \.tf$ require_serial: true diff --git a/.yamllint b/.yamllint new file mode 100644 index 000000000..3dde2eaa4 --- /dev/null +++ b/.yamllint @@ -0,0 +1,20 @@ +--- + +extends: default + +rules: + indentation: + level: error + indent-sequences: false + quoted-strings: + required: only-when-needed + truthy: + allowed-values: + - >- + false + - >- + true + - >- # Allow "on" key name in GHA CI/CD workflow definitions + on + +... From 5181957858627579cc1ac59888e5cc7aab1a6704 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 10 Jan 2025 01:59:43 +0200 Subject: [PATCH 109/293] chore(mypy): Fix type annotation (#751) --- src/pre_commit_terraform/terraform_docs_replace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pre_commit_terraform/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py index cc83a2a7d..444bbc588 100644 --- a/src/pre_commit_terraform/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -49,7 +49,7 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: category=UserWarning, ) - dirs = [] + dirs: list[str] = [] for filename in cast_to(list[str], parsed_cli_args.filenames): if (os.path.realpath(filename) not in dirs and (filename.endswith(".tf") or filename.endswith(".tfvars"))): From e251eb2e2e23cf4b1c35fcce789ff70ecc3f55b3 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 13 Jan 2025 22:56:25 +0200 Subject: [PATCH 110/293] chore: Rename 'py' to 'pytest' in GHA to avoid confusion (#749) When you see `py` in tests it not clear what exactly going on. Until now I thought that it represents "python", but actually this part run `pytest` ![image](https://github.com/user-attachments/assets/a7f5aeef-a4ab-4d18-a793-834f0b3c5ce9) So, for avoiding confusion and add ability to explicitly specify `tox r -e pytest`, this PR was made --- .github/workflows/ci-cd.yml | 2 +- .github/workflows/reusable-tox.yml | 2 +- tox.ini | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 0e18ee217..4f05785af 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -452,7 +452,7 @@ jobs: - macos-13 - windows-2025 toxenv: - - py + - pytest xfail: - false diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index b540075e4..bcd1a2182 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -262,7 +262,7 @@ jobs: shell: bash - name: Download all the dists if: >- - contains(fromJSON('["metadata-validation", "py"]'), inputs.toxenv) + contains(fromJSON('["metadata-validation", "pytest"]'), inputs.toxenv) uses: actions/download-artifact@v4 with: name: ${{ inputs.dists-artifact-name }} diff --git a/tox.ini b/tox.ini index 195db1536..adb9defac 100644 --- a/tox.ini +++ b/tox.ini @@ -104,6 +104,9 @@ set_env = COVERAGE_PROCESS_START = {toxinidir}{/}.coveragerc wheel_build_env = .pkg +# Duplicate default 'py' env to 'pytest' to be able run pytest with 'tox run -e pytest' +[testenv:pytest] + [testenv:cleanup-dists] description = From 2e6b2702240964dc57556936740d27d39cfbe3e7 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 14 Jan 2025 21:05:57 +0200 Subject: [PATCH 111/293] chore: Fix shellcheck v0.10.0 linters rules (#753) --- hooks/_common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index 25022e329..fc0c930a7 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -139,7 +139,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 From 1f14254c960791674066698b17031ff0b0f47f31 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 22:42:14 +0200 Subject: [PATCH 112/293] chore(deps): update amannn/action-semantic-pull-request action to v5.5.3 (#754) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pr-title.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index da41a36d0..b9c032372 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -17,7 +17,7 @@ jobs: # Please look up the latest version from # https://github.com/amannn/action-semantic-pull-request/releases # yamllint disable-line rule:line-length - - uses: amannn/action-semantic-pull-request@cfb60706e18bc85e8aec535e3c577abe8f70378e # v5.5.2 + - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: From b0c1334f4418568007c8d80777ac477febf70203 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 23:22:19 +0200 Subject: [PATCH 113/293] chore(deps): update cycjimmy/semantic-release-action action to v4.1.1 (#755) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f5ca45aff..e2fe69053 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - name: Release # yamllint disable-line rule:line-length - uses: cycjimmy/semantic-release-action@cb425203a562475bca039ba4dbf90c7f9ac790f4 # v4.1.0 + uses: cycjimmy/semantic-release-action@b1b432f13acb7768e0c8efdec416d363a57546f2 # v4.1.1 with: semantic_version: 18.0.0 extra_plugins: | From d10f4f24f10a388adfa6c15de2f6ef4094dc7854 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 15 Jan 2025 00:46:28 +0200 Subject: [PATCH 114/293] chore(renovate): Require first upper letter in PR titles (#768) --- .github/renovate.json5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 632adfd30..fd2ff0f9e 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,4 +1,6 @@ { $schema: "https://docs.renovatebot.com/renovate-schema.json", extends: ["local>SpotOnInc/renovate-config"], + // To make happy 'Validate PR title' GHA + commitMessageLowerCase: "never", } From 2928ff1676d4f371ebec9be02bcb2a10b2d44cae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 01:23:19 +0200 Subject: [PATCH 115/293] chore(deps): Update docker/login-action action to v3.3.0 (#765) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index ebc2e8911..77d92a35b 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -24,7 +24,7 @@ jobs: uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Login to GitHub Container Registry # yamllint disable-line rule:line-length - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From bfa8822bde1f132b57f57d5b8cdb30c975ef1ca8 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 15 Jan 2025 02:05:17 +0200 Subject: [PATCH 116/293] ci: Avoid reaching GH limits on MacOS runners (#769) We are hit limits for how many runs on MacOS runners can be run, just by creation 10 renovate PRs and fix twise issues in half of them ~= 20 new PRs/commits for check Each commit trigger 10 MacOS runs, which means that limit is aproximatly ~200 MacOS runs per hour. I didn't find such limits in offical docs, btw https://docs.github.com/en/actions/administering-github-actions/usage-limits-billing-and-administration#usage-limits --- .github/renovate.json5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index fd2ff0f9e..4e54aed92 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -3,4 +3,6 @@ extends: ["local>SpotOnInc/renovate-config"], // To make happy 'Validate PR title' GHA commitMessageLowerCase: "never", + // Disable auto-rebase on every commit to avoid reaching Github limits on macos runners + rebaseWhen: "conflicted", } From 20706321184a5568e9d8f788e103451cbe6d4bcc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 02:15:25 +0200 Subject: [PATCH 117/293] chore(deps): Update docker/setup-qemu-action action to v3.3.0 (#767) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Maksym Vlasov --- .github/workflows/build-image-test.yaml | 2 +- .github/workflows/build-image.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 7c43a42f5..fa866159f 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -35,7 +35,7 @@ jobs: - name: Set up QEMU if: matrix.os != 'ubuntu-latest' || matrix.arch != 'amd64' # yamllint disable-line rule:line-length - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0 with: platforms: arm64 diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 77d92a35b..98b992692 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up QEMU # yamllint disable-line rule:line-length - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0 - name: Set up Docker Buildx # yamllint disable-line rule:line-length uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 From a1677c7ae10d4d1ce23b7013049ccca4c988887c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 02:40:18 +0200 Subject: [PATCH 118/293] chore(deps): Update docker/setup-buildx-action action to v3.8.0 (#766) --- .github/workflows/build-image-test.yaml | 2 +- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index fa866159f..91f527d3a 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -41,7 +41,7 @@ jobs: - name: Set up Docker Buildx # yamllint disable-line rule:line-length - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 98b992692..aaf925f2b 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -21,7 +21,7 @@ jobs: uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0 - name: Set up Docker Buildx # yamllint disable-line rule:line-length - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to GitHub Container Registry # yamllint disable-line rule:line-length uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -39,7 +39,7 @@ jobs: - name: Set up Docker Buildx # yamllint disable-line rule:line-length - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Build and Push release if: github.event_name != 'schedule' From 1e656c9a1231f02b37dcb2ff7d07415ca74d3fed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 02:47:34 +0200 Subject: [PATCH 119/293] chore(deps): Update docker/build-push-action action to v6.11.0 (#764) --- .github/workflows/build-image-test.yaml | 4 ++-- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 91f527d3a..ee5dedc83 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -46,7 +46,7 @@ jobs: - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' # yamllint disable-line rule:line-length - uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 + uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 with: context: . build-args: | @@ -88,7 +88,7 @@ jobs: && matrix.os == 'ubuntu-latest' && matrix.arch == 'amd64' # yamllint disable-line rule:line-length - uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 + uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 with: context: . build-args: | diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index aaf925f2b..0148886b2 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -44,7 +44,7 @@ jobs: - name: Build and Push release if: github.event_name != 'schedule' # yamllint disable-line rule:line-length - uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 + uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 with: context: . build-args: | @@ -62,7 +62,7 @@ jobs: - name: Build and Push nightly if: github.event_name == 'schedule' # yamllint disable-line rule:line-length - uses: docker/build-push-action@94f8f8c2eec4bc3f1d78c1755580779804cb87b2 # v6.0.1 + uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 with: context: . build-args: | From 2fb51a13bc6ce06b4abcc19dac9612bf830b0ae7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 02:55:20 +0200 Subject: [PATCH 120/293] chore(deps): Update codecov/codecov-action action to v4.6.0 (#763) --- .github/workflows/reusable-tox.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index bcd1a2182..95d9356d4 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -367,7 +367,8 @@ jobs: if: >- !cancelled() && steps.tox-run.outputs.cov-report-files != '' - uses: codecov/codecov-action@v4 + # yamllint disable-line rule:line-length + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 with: disable_search: true fail_ci_if_error: >- From 43b89a795602c03d021b99e4ff1b3bb5fefa5485 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:02:32 +0200 Subject: [PATCH 121/293] chore(deps): Update actions/upload-artifact action to v4.6.0 (#762) --- .github/workflows/ci-cd.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 4f05785af..7f2e27458 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -359,7 +359,8 @@ jobs: >> "${GITHUB_OUTPUT}" working-directory: dist - name: Store the distribution packages - uses: actions/upload-artifact@v4 + # yamllint disable-line rule:line-length + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: >- ${{ needs.pre-setup.outputs.dists-artifact-name }} From ed4757f8c1b1b1f07b484460318890cc56b583a1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:10:49 +0200 Subject: [PATCH 122/293] chore(deps): Update actions/setup-python action to v5.3.0 (#761) --- .github/workflows/ci-cd.yml | 6 ++++-- .github/workflows/pre-commit.yaml | 2 +- .github/workflows/reusable-tox.yml | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 7f2e27458..a2f4e7a6a 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -106,7 +106,8 @@ jobs: steps: - name: Switch to using Python 3.13 by default - uses: actions/setup-python@v5 + # yamllint disable-line rule:line-length + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: 3.13 - name: Check out src from Git @@ -268,7 +269,8 @@ jobs: steps: - name: Switch to using Python 3.13 - uses: actions/setup-python@v5 + # yamllint disable-line rule:line-length + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: 3.13 diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index ab6337910..450bd476f 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -50,7 +50,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} # Skip terraform_tflint which interferes to commit pre-commit auto-fixes # yamllint disable-line rule:line-length - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: '3.9' - name: Execute pre-commit diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 95d9356d4..6a9fe67ac 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -155,7 +155,8 @@ jobs: Switch to using Python v${{ inputs.python-version }} by default id: python-install - uses: actions/setup-python@v5 + # yamllint disable-line rule:line-length + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ inputs.python-version }} From 87b9f80900f9fab11e8085d35e0930ba94eb42c4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:16:21 +0200 Subject: [PATCH 123/293] chore(deps): Update actions/download-artifact action to v4.1.8 (#760) --- .github/workflows/reusable-tox.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 6a9fe67ac..a0274856e 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -264,7 +264,8 @@ jobs: - name: Download all the dists if: >- contains(fromJSON('["metadata-validation", "pytest"]'), inputs.toxenv) - uses: actions/download-artifact@v4 + # yamllint disable-line rule:line-length + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: ${{ inputs.dists-artifact-name }} path: dist/ From 2b49de57aaee4c140e0d6c859f6b7ba076845d8f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:08:52 +0200 Subject: [PATCH 124/293] chore(deps): Update actions/checkout action to v4.2.2 (#759) --- .github/workflows/build-image-test.yaml | 2 +- .github/workflows/build-image.yaml | 2 +- .github/workflows/ci-cd.yml | 6 ++++-- .github/workflows/pre-commit.yaml | 4 ++-- .github/workflows/release.yml | 2 +- .github/workflows/reusable-tox.yml | 3 ++- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index ee5dedc83..0c6a57497 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -16,7 +16,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 0148886b2..a97869ee0 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up QEMU # yamllint disable-line rule:line-length uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0 diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index a2f4e7a6a..95d85892d 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -111,7 +111,8 @@ jobs: with: python-version: 3.13 - name: Check out src from Git - uses: actions/checkout@v4 + # yamllint disable-line rule:line-length + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - name: >- @@ -275,7 +276,8 @@ jobs: python-version: 3.13 - name: Grab the source from Git - uses: actions/checkout@v4 + # yamllint disable-line rule:line-length + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 450bd476f..3e92524dd 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -8,7 +8,7 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - run: >- git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* @@ -44,7 +44,7 @@ jobs: > hadolint && chmod +x hadolint && sudo mv hadolint /usr/bin/ # Need to success pre-commit fix push - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2fe69053..307dc0615 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false fetch-depth: 0 diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index a0274856e..927247149 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -166,7 +166,8 @@ jobs: - name: Grab the source from Git if: >- contains(fromJSON('["pre-commit", "spellcheck-docs"]'), inputs.toxenv) - uses: actions/checkout@v4 + # yamllint disable-line rule:line-length + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.inputs.release-committish }} - name: Retrieve the project source from an sdist inside the GHA artifact From f826dc90cb278199c80231f54910e6f63994d516 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:51:23 +0200 Subject: [PATCH 125/293] chore(deps): Update actions/cache action to v4.2.0 (#758) --- .github/workflows/ci-cd.yml | 6 ++++-- .github/workflows/reusable-tox.yml | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 95d85892d..6a44d39dd 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -164,7 +164,8 @@ jobs: echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" shell: bash - name: Set up pip cache - uses: actions/cache@v4 + # yamllint disable-line rule:line-length + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- @@ -305,7 +306,8 @@ jobs: run: >- echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" - name: Set up pip cache - uses: actions/cache@v4 + # yamllint disable-line rule:line-length + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 927247149..d3bdb807c 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -180,7 +180,8 @@ jobs: - name: Cache pre-commit.com virtualenvs if: inputs.toxenv == 'pre-commit' - uses: actions/cache@v4 + # yamllint disable-line rule:line-length + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ~/.cache/pre-commit key: >- @@ -238,7 +239,8 @@ jobs: shell: bash - name: Set up pip cache if: fromJSON(steps.py-abi.outputs.is-stable-abi) - uses: actions/cache@v4 + # yamllint disable-line rule:line-length + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- From 976cef3caeb4ad50640ee2ef7de234e9ce1e8bb1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:39:02 +0200 Subject: [PATCH 126/293] chore(deps): Update codecov/codecov-action action to v5 (#770) --- .github/workflows/reusable-tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index d3bdb807c..70cdfb7de 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -373,7 +373,7 @@ jobs: !cancelled() && steps.tox-run.outputs.cov-report-files != '' # yamllint disable-line rule:line-length - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 + uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 with: disable_search: true fail_ci_if_error: >- From a2ee205585905a7baea4e4cebbb06daf2bc4e32c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:24:19 +0200 Subject: [PATCH 127/293] chore(deps): Pin dependencies and update test-summary/action (#756) --- .github/workflows/reusable-tox.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 70cdfb7de..53b6fcbe3 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -317,7 +317,8 @@ jobs: if: >- !cancelled() && steps.tox-run.outputs.test-result-files != '' - uses: test-summary/action@v2.3 + # yamllint disable-line rule:line-length + uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4 with: paths: >- ${{ steps.tox-run.outputs.test-result-files }} @@ -334,7 +335,8 @@ jobs: && steps.tox-run.outputs.cov-report-files != '' && steps.tox-run.outputs.test-result-files == '' && steps.tox-run.outputs.codecov-flags != 'MyPy' - uses: irongut/CodeCoverageSummary@v1.3.0 + # yamllint disable-line rule:line-length + uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0 with: badge: true filename: >- From e01b4dec5af830e25240f5de9ae81021f5d33dbd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:19:13 +0200 Subject: [PATCH 128/293] chore(deps): Update tj-actions/changed-files action to v45 (#771) --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 0c6a57497..703462329 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -23,7 +23,7 @@ jobs: - name: Get changed Docker related files id: changed-files-specific # yamllint disable-line rule:line-length - uses: tj-actions/changed-files@d6babd6899969df1a11d14c368283ea4436bca78 # v44.5.2 + uses: tj-actions/changed-files@d6e91a2266cdb9d62096cebf1e8546899c6aa18f # v45.0.6 with: files: | Dockerfile From a98f23c91cd005b7f45c86728c697d4f82774958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Wed, 15 Jan 2025 21:57:27 +0100 Subject: [PATCH 129/293] chore(packaging): Enable support for versioning Git archives (#737) It essentially allows one to ``` $ pip install https://github.com/antonbabenko/pre-commit-terraform/archive/a7155a7.tar.gz ``` and get a correctly computed version in the installed metadata. [1]: https://setuptools-scm.rtfd.io/en/latest/usage/#git-archives --- .git_archival.txt | 3 +++ .gitattributes | 1 + hatch.toml | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .git_archival.txt create mode 100644 .gitattributes diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 000000000..3e26627d4 --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,3 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true)$ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..00a7b00c9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.git_archival.txt export-subst diff --git a/hatch.toml b/hatch.toml index 9d3cd73ea..1cb850f63 100644 --- a/hatch.toml +++ b/hatch.toml @@ -14,7 +14,9 @@ packages = [ ] [metadata.hooks.vcs.urls] -'Source Archive' = 'https://github.com/antonbabenko/pre-commit-terraform/archive/{commit_hash}.tar.gz' +# FIXME: Uncomment 'Source Archive' as soon as +# FIXME: https://github.com/ofek/hatch-vcs/issues/80 is fixed. +# 'Source Archive' = 'https://github.com/antonbabenko/pre-commit-terraform/archive/{commit_hash}.tar.gz' 'GitHub: repo' = 'https://github.com/antonbabenko/pre-commit-terraform' [version] From 92fbec7bea068e629c4582bf46abf014dc093abb Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 15 Jan 2025 23:05:16 +0200 Subject: [PATCH 130/293] docs: Simplify undestanding what's going on (#750) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Co-authored-by: George L. Yermulnik Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- .github/CONTRIBUTING.md | 42 +++++++++++++++++++++++++----- .github/workflows/reusable-tox.yml | 4 +++ pytest.ini | 2 +- tox.ini | 14 +++++++++- 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8066f6f30..9da5c6410 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,11 +1,5 @@ # Notes for contributors -1. Python hooks are supported now too. All you have to do is: - 1. add a line to the `console_scripts` array in `entry_points` in `setup.py` - 2. Put your python script in the `pre_commit_hooks` folder - -Enjoy the clean, valid, and documented code! - * [Run and debug hooks locally](#run-and-debug-hooks-locally) * [Run hook performance test](#run-hook-performance-test) * [Run via BASH](#run-via-bash) @@ -18,6 +12,7 @@ Enjoy the clean, valid, and documented code! * [Prepare basic documentation](#prepare-basic-documentation) * [Add code](#add-code) * [Finish with the documentation](#finish-with-the-documentation) +* [Contributing to Python code](#contributing-to-python-code) ## Run and debug hooks locally @@ -152,5 +147,38 @@ You can use [this PR](https://github.com/antonbabenko/pre-commit-terraform/pull/ ### Finish with the documentation -1. Add hook description to [Available Hooks](../README.md#available-hooks). +1. Add the hook description to [Available Hooks](../README.md#available-hooks). 2. Create and populate a new hook section in [Hooks usage notes and examples](../README.md#hooks-usage-notes-and-examples). + +## Contributing to Python code + +1. [Install `tox`](https://tox.wiki/en/stable/installation.html) +2. To run tests, run: + + ```bash + tox -qq + ``` + + The easiest way to find out what parts of the code base are left uncovered, is to copy-paste and run the `python3 ...` command that will open the HTML report, so you can inspect it visually. + +3. Before committing any changes (if you do not have `pre-commit` installed locally), run: + + ```bash + tox r -qq -e pre-commit + ``` + + Make sure that all checks pass. + +4. (Optional): If you want to limit the checks to MyPy only, you can run: + + ```bash + tox r -qq -e pre-commit -- mypy --all-files + ``` + + Then copy-paste and run the `python3 ...` commands to inspect the strictest MyPy coverage reports visually. + +5. (Optional): You can find all available `tox` environments by running: + + ```bash + tox list + ``` diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 53b6fcbe3..81d66e61e 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -297,6 +297,9 @@ jobs: --quiet -- python -Im pre_commit install-hooks + # Create GHA Job Summary markdown table of the coverage report + # But only for 'pytest' env in 'tox'. + # For details: ../../tox.ini '[testenv:pytest]' 'commands_post' - name: >- Run tox envs: `${{ env.TOXENV }}` id: tox-run @@ -313,6 +316,7 @@ jobs: && format('-- {0}', inputs.tox-run-posargs) || '' }} + # Generate nice SVG image of passed/failed tests in GHA Job Summary - name: Produce markdown test summary from JUnit if: >- !cancelled() diff --git a/pytest.ini b/pytest.ini index d16390e6f..b2bb788d6 100644 --- a/pytest.ini +++ b/pytest.ini @@ -43,7 +43,7 @@ doctest_optionflags = ALLOW_UNICODE ELLIPSIS empty_parameter_set_mark = xfail faulthandler_timeout = 30 - +# Turn all warnings into errors filterwarnings = error diff --git a/tox.ini b/tox.ini index adb9defac..57681be03 100644 --- a/tox.ini +++ b/tox.ini @@ -14,6 +14,10 @@ warnings-to-errors = -Werror description = Run pytest under {envpython} dependency_groups = testing + +# In: +# 'tox run -e py -- --lf', 'tox run -- --lf', 'tox run -e py313,py312 -- --lf' +# '{posargs}' (positional arguments) == '--lf' commands = {envpython} \ {[python-cli-options]byte-errors} \ @@ -23,6 +27,9 @@ commands = {tty:--color=yes} \ {posargs:--cov-report=html:{envtmpdir}{/}htmlcov{/}} commands_post = + # Create GHA Job Summary markdown table of the coverage report + # https://github.blog/news-insights/product-news/supercharging-github-actions-with-job-summaries/ + # a leading '-' suppresses non-zero return codes -{envpython} \ {[python-cli-options]byte-errors} \ {[python-cli-options]max-isolation} \ @@ -38,6 +45,7 @@ commands_post = cov = coverage.Coverage(); \ cov.load(); \ cov.report(file=gh_summary_fd, output_format="markdown")' + # Expose the coverage & test run XML report paths into GHA {envpython} \ {[python-cli-options]byte-errors} \ {[python-cli-options]max-isolation} \ @@ -165,7 +173,9 @@ commands = commands_post = package = skip - +# In: +# 'tox run -e pre-commit -- mypy-py313 --all' +# '{posargs}' == 'mypy-py313 --all' [testenv:pre-commit] description = Run the quality checks under {basepython}; run as @@ -186,6 +196,7 @@ commands = {posargs:--all-files} # Print out the advice on how to install pre-commit from this env into Git: + # a leading '-' suppresses non-zero return codes -{envpython} \ {[python-cli-options]byte-errors} \ {[python-cli-options]max-isolation} \ @@ -219,6 +230,7 @@ commands_post = ); \ print("codecov-flags=MyPy", file=gh_output_fd); \ gh_output_fd.close()' + # Publish available MyPy-produced text and JSON reports wrapped as Markdown code blocks, to a GHA job summary {envpython} \ {[python-cli-options]byte-errors} \ {[python-cli-options]max-isolation} \ From cd090b69c19869924a468b5a81be63264b679171 Mon Sep 17 00:00:00 2001 From: Tobias Breitwieser Date: Thu, 16 Jan 2025 17:10:21 +0100 Subject: [PATCH 131/293] feat(`terraform_docs`): Add support for custom markers to better support other formats than Markdown (#752) --- README.md | 10 ++++++++++ hooks/terraform_docs.sh | 19 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6bfb50225..bbce9d522 100644 --- a/README.md +++ b/README.md @@ -608,6 +608,16 @@ Unlike most other hooks, this hook triggers once if there are any changed files - --hook-config=--add-to-existing-file=true # Boolean. true or false - --hook-config=--create-file-if-not-exist=true # Boolean. true or false - --hook-config=--use-standard-markers=true # Boolean. Defaults to true (v1.93+), false ( # String. + # Set to use custom marker which helps you with using other formats like asciidoc. + # For Asciidoc this could be "--hook-config=--custom-marker-begin=// BEGIN_TF_DOCS" + - --hook-config=--custom-marker-end= # String. + # Set to use custom marker which helps you with using other formats like asciidoc. + # For Asciidoc this could be "--hook-config=--custom-marker-end=// END_TF_DOCS" + - --hook-config=--custom-doc-header="# " # String. Defaults to "# " + # Set to use custom marker which helps you with using other formats like asciidoc. + # For Asciidoc this could be "--hook-config=--custom-marker-end=\= " ``` 4. If you want to use a terraform-docs config file, you must supply the path to the file, relative to the git repo root path: diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 3d0cc884a..accbb02a1 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -9,6 +9,7 @@ readonly SCRIPT_DIR insertion_marker_begin="" insertion_marker_end="" +doc_header="# " # Old markers used by the hook before the introduction of the terraform-docs markers readonly old_insertion_marker_begin="" @@ -42,8 +43,8 @@ function replace_old_markers { # Determine the appropriate sed command based on the operating system (GNU sed or BSD sed) sed --version &> /dev/null && SED_CMD=(sed -i) || SED_CMD=(sed -i '') - "${SED_CMD[@]}" -e "s/^${old_insertion_marker_begin}$/${insertion_marker_begin}/" "$file" - "${SED_CMD[@]}" -e "s/^${old_insertion_marker_end}$/${insertion_marker_end}/" "$file" + "${SED_CMD[@]}" -e "s/^${old_insertion_marker_begin}$/${insertion_marker_begin//\//\\/}/" "$file" + "${SED_CMD[@]}" -e "s/^${old_insertion_marker_end}$/${insertion_marker_end//\//\\/}/" "$file" } ####################################################################### @@ -115,6 +116,18 @@ function terraform_docs { common::colorify "yellow" "WARNING: --use-standard-markers is deprecated and will be removed in the future." common::colorify "yellow" " All needed changes already done by the hook, feel free to remove --use-standard-markers setting from your pre-commit config" ;; + --custom-marker-begin) + insertion_marker_begin=$value + common::colorify "green" "INFO: --custom-marker-begin is used and the marker is set to \"$value\"." + ;; + --custom-marker-end) + insertion_marker_end=$value + common::colorify "green" "INFO: --custom-marker-end is used and the marker is set to \"$value\"." + ;; + --custom-doc-header) + doc_header=$value + common::colorify "green" "INFO: --custom-doc-header is used and the doc header is set to \"$value\"." + ;; esac done @@ -198,7 +211,7 @@ function terraform_docs { # Use of insertion markers, where there is no existing README file { - echo -e "# ${PWD##*/}\n" + echo -e "${doc_header}${PWD##*/}\n" echo "$insertion_marker_begin" echo "$insertion_marker_end" } >> "$output_file" From 6d8b032c33520354085c90922eef804ec2011c31 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 16 Jan 2025 16:10:50 +0000 Subject: [PATCH 132/293] chore(release): version 1.97.0 [skip ci] # [1.97.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.3...v1.97.0) (2025-01-16) ### Features * **`terraform_docs`:** Add support for custom markers to better support other formats than Markdown ([#752](https://github.com/antonbabenko/pre-commit-terraform/issues/752)) ([cd090b6](https://github.com/antonbabenko/pre-commit-terraform/commit/cd090b69c19869924a468b5a81be63264b679171)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7337a977e..d67a16d01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.97.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.3...v1.97.0) (2025-01-16) + + +### Features + +* **`terraform_docs`:** Add support for custom markers to better support other formats than Markdown ([#752](https://github.com/antonbabenko/pre-commit-terraform/issues/752)) ([cd090b6](https://github.com/antonbabenko/pre-commit-terraform/commit/cd090b69c19869924a468b5a81be63264b679171)) + ## [1.96.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.2...v1.96.3) (2024-12-24) From dcbb554b2a2911af8feb1df76d5bb8fb16772595 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:40:02 +0200 Subject: [PATCH 133/293] chore(deps): Update docker/build-push-action action to v6.12.0 (#774) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 4 ++-- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 703462329..f13091229 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -46,7 +46,7 @@ jobs: - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' # yamllint disable-line rule:line-length - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 with: context: . build-args: | @@ -88,7 +88,7 @@ jobs: && matrix.os == 'ubuntu-latest' && matrix.arch == 'amd64' # yamllint disable-line rule:line-length - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 with: context: . build-args: | diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index a97869ee0..8abff277c 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -44,7 +44,7 @@ jobs: - name: Build and Push release if: github.event_name != 'schedule' # yamllint disable-line rule:line-length - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 with: context: . build-args: | @@ -62,7 +62,7 @@ jobs: - name: Build and Push nightly if: github.event_name == 'schedule' # yamllint disable-line rule:line-length - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 with: context: . build-args: | From 8eec7e9fe42056ce0a746f5e1a40c7c17d261eed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:03:03 +0200 Subject: [PATCH 134/293] chore(deps): Update actions/stale action to v9.1.0 (#773) --- .github/workflows/stale-actions.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale-actions.yaml b/.github/workflows/stale-actions.yaml index d4ed2caef..eefcd8b16 100644 --- a/.github/workflows/stale-actions.yaml +++ b/.github/workflows/stale-actions.yaml @@ -9,7 +9,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} # Staling issues and PR's From 3030d6340d4db661d52cbc12c7fd9948ac75bf4f Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 22 Jan 2025 12:46:08 +0200 Subject: [PATCH 135/293] chore: Repo still maintained (#775) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bbce9d522..fec0d10dc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Collection of git hooks for Terraform to be used with [pre-commit framework](http://pre-commit.com/) -[![Github tag](https://img.shields.io/github/tag/antonbabenko/pre-commit-terraform.svg)](https://github.com/antonbabenko/pre-commit-terraform/releases) ![maintenance status](https://img.shields.io/maintenance/yes/2024.svg) [![Help Contribute to Open Source](https://www.codetriage.com/antonbabenko/pre-commit-terraform/badges/users.svg)](https://www.codetriage.com/antonbabenko/pre-commit-terraform) +[![Github tag](https://img.shields.io/github/tag/antonbabenko/pre-commit-terraform.svg)](https://github.com/antonbabenko/pre-commit-terraform/releases) ![maintenance status](https://img.shields.io/maintenance/yes/2025.svg) [![Help Contribute to Open Source](https://www.codetriage.com/antonbabenko/pre-commit-terraform/badges/users.svg)](https://www.codetriage.com/antonbabenko/pre-commit-terraform) [![CI/CD Badge]][CI/CD] [![Codecov Badge]][Codecov] From 9a68d89d11a82aaf647bbf7ff9b11740310181bc Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 23 Jan 2025 13:17:32 +0200 Subject: [PATCH 136/293] ci: Fix edge case with Nightly builds right after new release (#776) --- .github/workflows/ci-cd.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 6a44d39dd..272dffeeb 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -334,6 +334,13 @@ jobs: --skip-missing-interpreters false --notest + - name: Drop Git tags from HEAD for non-tag-create events + run: >- + git tag --points-at HEAD + | + xargs git tag --delete + shell: bash + - name: Set static timestamp for dist build reproducibility # ... from the last Git commit since it's immutable run: >- From f921071c001967c85f26117ff0ca1349ccad573a Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 24 Jan 2025 01:48:41 +0200 Subject: [PATCH 137/293] ci(StepSecurity): Add Dependency Review Workflow (#779) ## Security Fixes ### Add Dependency Review Workflow The Dependency Review Workflow enforces dependency reviews on your pull requests. The action scans for vulnerable versions of dependencies introduced by package version changes in pull requests, and warns you about the associated security vulnerabilities. This gives you better visibility of what's changing in a pull request, and helps prevent vulnerabilities being added to your repository. - [Github Guide about Dependency Review](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review) - [Github Guide for Configuring Dependency Review Action](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/configuring-dependency-review#using-inline-configuration-to-set-up-the-dependency-review-action) Co-authored-by: StepSecurity Bot --- .github/workflows/dependency-review.yml | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/dependency-review.yml diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 000000000..f219dd1c7 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,29 @@ +--- + +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a +# Pull Request, surfacing known-vulnerable versions of the packages declared +# or updated in the PR. +# Once installed, if the workflow run is marked as required, +# PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +name: Dependency Review + +on: + pull_request: + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + # yamllint disable-line rule:line-length + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Dependency Review + # yamllint disable-line rule:line-length + uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 From 073a4fd22746c024c4ea2b867ee6727d65c7605c Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 24 Jan 2025 02:04:05 +0200 Subject: [PATCH 138/293] chore(StepSecurity): Pin dependecies (#778) --- .github/workflows/ci-cd.yml | 3 ++- .github/workflows/reusable-tox.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 272dffeeb..2f3a641e8 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -519,7 +519,8 @@ jobs: steps: - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 + # yamllint disable-line rule:line-length + uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1 with: jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 81d66e61e..90a021ce3 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -173,7 +173,8 @@ jobs: - name: Retrieve the project source from an sdist inside the GHA artifact if: >- !contains(fromJSON('["pre-commit", "spellcheck-docs"]'), inputs.toxenv) - uses: re-actors/checkout-python-sdist@release/v2 + # yamllint disable-line rule:line-length + uses: re-actors/checkout-python-sdist@187f55296b0f54d88259aaaf99af32ad3647d3bc # release/v2 with: source-tarball-name: ${{ inputs.source-tarball-name }} workflow-artifact-name: ${{ inputs.dists-artifact-name }} From 911e9ceb17243c31a76a0e32c2aaed2c0069c3c4 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 24 Jan 2025 02:14:53 +0200 Subject: [PATCH 139/293] chore(StepSecurity): Add `gitleaks` pre-commit hook; rearange hooks (#781) Signed-off-by: StepSecurity Bot Co-authored-by: StepSecurity Bot --- .pre-commit-config.yaml | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d649bafe7..6e861e5e2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,16 +32,22 @@ repos: - id: mixed-line-ending args: [--fix=lf] + # Non-modifying checks: + - id: name-tests-test + files: >- + ^tests/[^_].*\.py$ + # Security - id: detect-aws-credentials args: - --allow-missing-credentials - id: detect-private-key - # Non-modifying checks: - - id: name-tests-test - files: >- - ^tests/[^_].*\.py$ + # Detect hardcoded secrets +- repo: https://github.com/gitleaks/gitleaks + rev: v8.16.3 + hooks: + - id: gitleaks - repo: https://github.com/adrienverge/yamllint.git rev: v1.35.1 @@ -53,6 +59,14 @@ repos: args: - --strict +# JSON5 Linter +- repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.1.0 + hooks: + - id: prettier + # https://prettier.io/docs/en/options.html#parser + files: .json5$ + - repo: https://github.com/pre-commit/mirrors-mypy.git rev: v1.14.1 hooks: @@ -135,11 +149,3 @@ repos: - --ignore=SC1091 # Useless check - --ignore=SC2015 # Useless check - --ignore=SC3037 # Not related to alpine - -# JSON5 Linter -- repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.1.0 - hooks: - - id: prettier - # https://prettier.io/docs/en/options.html#parser - files: .json5$ From 917f4d0179d70fffd0f21d5e7503611f5bc5b38c Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 24 Jan 2025 11:28:02 +0200 Subject: [PATCH 140/293] chore(StepSecurity): Add OpenSSF Scorecard Workflow (#780) ### Add OpenSSF Scorecard Workflow OpenSSF Scorecard is an automated tool that assesses a number of important heuristics ("checks") associated with software security and assigns each check a score of 0-10. You can use these scores to understand specific areas to improve in order to strengthen the security posture of your project. Scorecard workflow also allows maintainers to display a Scorecard badge on their repository to show off their hard work. - [The Open Source Security Foundation (OpenSSF) Scorecard](https://github.com/ossf/scorecard) --------- Signed-off-by: StepSecurity Bot Co-authored-by: StepSecurity Bot --- .github/workflows/scorecards.yml | 84 ++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .github/workflows/scorecards.yml diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml new file mode 100644 index 000000000..5c61608d3 --- /dev/null +++ b/.github/workflows/scorecards.yml @@ -0,0 +1,84 @@ +--- +# This workflow uses actions that are not certified by GitHub. They are +# provided by a third-party and are governed by separate terms of service, +# privacy policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: 20 7 * * 2 + push: + branches: + - master + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + contents: read + actions: read + # To allow GraphQL ListCommits to work + issues: read + pull-requests: read + # To detect SAST tools + checks: read + + steps: + - name: Checkout code + # yamllint disable-line rule:line-length + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Run analysis + # yamllint disable-line rule:line-length + uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line if: + # - you want to enable the Branch-Protection check on a *public* + # repository, or + # - you are installing Scorecards on a *private* repository + # To create the PAT, follow the steps in + # https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable + # uploads of run results in SARIF format to the repository Actions tab. + - name: Upload artifact + # yamllint disable-line rule:line-length + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: Upload to code-scanning + # yamllint disable-line rule:line-length + uses: github/codeql-action/upload-sarif@ee117c905ab18f32fa0f66c2fe40ecc8013f3e04 # v3.28.4 + with: + sarif_file: results.sarif From e7d51db8498abffd3c28c65b34f1f5cbafc52271 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Sat, 25 Jan 2025 10:26:16 +0200 Subject: [PATCH 141/293] ci: Make able to run workflow in forks with uppercase in names (#782) ### Description of your changes Deal with ``` ERROR: invalid tag "ghcr.io/MaxymVlasov/pre-commit-terraform-712:latest": repository name must be lowercase ``` Found during testing changes for OSSF score improvement - as I need to test all workflows without disrupting main repo --------- Co-authored-by: George L. Yermulnik --- .github/workflows/build-image-test.yaml | 16 ++++++++++------ .github/workflows/build-image.yaml | 11 ++++++++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index f13091229..56f53c93f 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -32,6 +32,12 @@ jobs: .github/workflows/build-image-test.yaml tools/*.sh + - name: Set IMAGE environment variable + # Lowercase the org/repo name to allow for workflow to run in forks, + # which owners have uppercase letters in username + run: >- + echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}:${{ env.IMAGE_TAG }}" + >> $GITHUB_ENV - name: Set up QEMU if: matrix.os != 'ubuntu-latest' || matrix.arch != 'amd64' # yamllint disable-line rule:line-length @@ -56,8 +62,7 @@ jobs: linux/${{ matrix.arch }} push: false load: true - tags: | - ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} + tags: ${{ env.IMAGE }} # Fix multi-platform: https://github.com/docker/buildx/issues/1533 provenance: false secrets: | @@ -68,7 +73,7 @@ jobs: # yamllint disable-line rule:line-length uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0 with: - image: ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} + image: ${{ env.IMAGE }} config: .github/.container-structure-test-config.yaml - name: Dive - check image for waste files @@ -76,7 +81,7 @@ jobs: # yamllint disable-line rule:line-length uses: MaxymVlasov/dive-action@379af3fc636888ada5899c997e8b52db6ad45023 # v1.0.1 with: - image: ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} + image: ${{ env.IMAGE }} config-file: ${{ github.workspace }}/.github/.dive-ci.yaml github-token: ${{ secrets.GITHUB_TOKEN }} @@ -95,8 +100,7 @@ jobs: INSTALL_ALL=true platforms: linux/amd64,linux/arm64 push: false - tags: | - ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} + tags: ${{ env.IMAGE }} # Fix multi-platform: https://github.com/docker/buildx/issues/1533 provenance: false secrets: | diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 8abff277c..d183ef0f4 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -37,6 +37,11 @@ jobs: || echo 'latest' ) >> $GITHUB_ENV + - name: Set IMAGE_REPO environment variable + # Lowercase the org/repo name to allow for workflow to run in forks, + # which owners have uppercase letters in username + run: >- + echo "IMAGE_REPO=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV - name: Set up Docker Buildx # yamllint disable-line rule:line-length uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 @@ -52,8 +57,8 @@ jobs: platforms: linux/amd64,linux/arm64 push: true tags: | - ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} - ghcr.io/${{ github.repository }}:latest + ${{ env.IMAGE_REPO }}:${{ env.IMAGE_TAG }} + ${{ env.IMAGE_REPO }}:latest # Fix multi-platform: https://github.com/docker/buildx/issues/1533 provenance: false secrets: | @@ -70,7 +75,7 @@ jobs: platforms: linux/amd64,linux/arm64 push: true tags: | - ghcr.io/${{ github.repository }}:nightly + ${{ env.IMAGE_REPO }}:nightly # Fix multi-platform: https://github.com/docker/buildx/issues/1533 provenance: false secrets: | From 980a129d326bcc4ac3da94a1936eb767a5633180 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Sat, 25 Jan 2025 19:52:36 +0200 Subject: [PATCH 142/293] ci(StepSecurity): Harden GHA token permissions (#777) --------- Signed-off-by: StepSecurity Bot Co-authored-by: StepSecurity Bot --- .github/workflows/build-image-test.yaml | 6 +++++- .github/workflows/build-image.yaml | 7 +++++++ .github/workflows/ci-cd.yml | 3 +++ .github/workflows/pr-title.yml | 9 +++++++++ .github/workflows/pre-commit.yaml | 8 +++++++- .github/workflows/release.yml | 15 +++++++++++++++ .github/workflows/reusable-tox.yml | 3 +++ .github/workflows/scheduled-runs.yml | 3 +++ .github/workflows/stale-actions.yaml | 6 ++++++ 9 files changed, 58 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 56f53c93f..f39770b9b 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -2,7 +2,11 @@ name: Build Dockerfile if changed and run smoke tests -on: [pull_request] +on: + pull_request: + +permissions: + contents: read env: IMAGE_TAG: pr-test diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index d183ef0f4..632d78d10 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -10,8 +10,15 @@ on: schedule: - cron: 00 00 * * * +permissions: + contents: read + jobs: docker: + permissions: + # for docker/build-push-action to publish docker image + packages: write + runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 2f3a641e8..7aca07139 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -15,6 +15,9 @@ on: pull_request: workflow_call: # a way to embed the main tests +permissions: + contents: read + concurrency: group: >- ${{ diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index b9c032372..b4ba2b43a 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -9,8 +9,17 @@ on: - edited - synchronize +permissions: + contents: read + jobs: main: + permissions: + # for amannn/action-semantic-pull-request to analyze PRs + pull-requests: read + # for amannn/action-semantic-pull-request to mark status of analyzed PR + statuses: write + name: Validate PR title runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 3e92524dd..51fcc4e07 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -2,10 +2,16 @@ name: Common issues check -on: [pull_request] +on: + pull_request: + +permissions: + contents: read jobs: pre-commit: + permissions: + contents: write # for pre-commit/action to push back fixes to PR branch runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 307dc0615..62f720f95 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,8 +14,20 @@ on: - .pre-commit-hooks.yaml # Ignore paths - '!tests/**' + +permissions: + contents: read + jobs: release: + permissions: + # for cycjimmy/semantic-release-action to create a release + contents: write + # for cycjimmy/semantic-release-action to write comments to issues + issues: write + # for cycjimmy/semantic-release-action to write comments to PRs + pull-requests: write + name: Release runs-on: ubuntu-latest steps: @@ -34,4 +46,7 @@ jobs: @semantic-release/changelog@6.0.0 @semantic-release/git@10.0.0 env: + # Custom token for triggering Docker image build GH Workflow on release + # created by cycjimmy/semantic-release-action. Events created by + # workflows with default GITHUB_TOKEN not trigger other GH Workflow. GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 90a021ce3..e95ba798a 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -83,6 +83,9 @@ on: description: Mandatory token for uploading to Codecov required: true +permissions: + contents: read + env: COLOR: >- # Supposedly, pytest or coveragepy use this yes diff --git a/.github/workflows/scheduled-runs.yml b/.github/workflows/scheduled-runs.yml index 9d8403cf2..88d3a6171 100644 --- a/.github/workflows/scheduled-runs.yml +++ b/.github/workflows/scheduled-runs.yml @@ -10,6 +10,9 @@ on: - cron: 3 5 * * * # run daily at 5:03 UTC workflow_dispatch: # manual trigger +permissions: + contents: read + run-name: >- 🌃 Nightly run of diff --git a/.github/workflows/stale-actions.yaml b/.github/workflows/stale-actions.yaml index eefcd8b16..e3f925abd 100644 --- a/.github/workflows/stale-actions.yaml +++ b/.github/workflows/stale-actions.yaml @@ -5,8 +5,14 @@ on: schedule: - cron: 0 0 * * * +permissions: + contents: read + jobs: stale: + permissions: + issues: write # for actions/stale to close stale issues + pull-requests: write # for actions/stale to close stale PRs runs-on: ubuntu-latest steps: - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 From c1154d61a4d2a1bbd75d077f2f8dfea434bbfe16 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 27 Jan 2025 16:31:36 +0200 Subject: [PATCH 143/293] ci: Utilize native arm64 linux runners (#784) --- .github/workflows/build-image-test.yaml | 58 +++++++++++++++++-------- .github/workflows/build-image.yaml | 3 -- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index f39770b9b..7cc6c3086 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -13,11 +13,25 @@ env: jobs: build: + permissions: + # for MaxymVlasov/dive-action to write comments to PRs + pull-requests: write + strategy: matrix: - os: [ubuntu-latest] - arch: [amd64, arm64] + arch: + - amd64 + - arm64 + include: + - os-name: Ubuntu x64 + os: ubuntu-latest + arch: amd64 + + - os-name: Ubuntu ARM + os: ubuntu-24.04-arm + arch: arm64 + name: ${{ matrix.os-name }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -37,21 +51,17 @@ jobs: tools/*.sh - name: Set IMAGE environment variable + if: steps.changed-files-specific.outputs.any_changed == 'true' # Lowercase the org/repo name to allow for workflow to run in forks, # which owners have uppercase letters in username run: >- echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}:${{ env.IMAGE_TAG }}" >> $GITHUB_ENV - - name: Set up QEMU - if: matrix.os != 'ubuntu-latest' || matrix.arch != 'amd64' - # yamllint disable-line rule:line-length - uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0 - with: - platforms: arm64 - name: Set up Docker Buildx # yamllint disable-line rule:line-length uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + if: steps.changed-files-specific.outputs.any_changed == 'true' - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' @@ -61,9 +71,6 @@ jobs: context: . build-args: | INSTALL_ALL=true - # yamllint disable-line rule:line-length - platforms: >- # Only one allowed here, see https://github.com/docker/buildx/issues/59#issuecomment-1433097926 - linux/${{ matrix.arch }} push: false load: true tags: ${{ env.IMAGE }} @@ -72,18 +79,34 @@ jobs: secrets: | "github_token=${{ secrets.GITHUB_TOKEN }}" + - name: Setup Container Structure Tests + if: steps.changed-files-specific.outputs.any_changed == 'true' + env: + # yamllint disable-line rule:line-length + # renovate: datasource=github-releases depName=container-structure-test lookupName=GoogleContainerTools/container-structure-test + CST_VERSION: 1.19.3 + CST_REPO: github.com/GoogleContainerTools/container-structure-test + run: >- + curl -L "https://${{ env.CST_REPO }}/releases/download/v${{ + env.CST_VERSION }}/container-structure-test-linux-${{ matrix.arch }}" + > container-structure-test + && chmod +x container-structure-test + && mkdir -p $HOME/bin/ + && mv container-structure-test $HOME/bin/ + && echo $HOME/bin/ >> $GITHUB_PATH + - name: Run structure tests if: steps.changed-files-specific.outputs.any_changed == 'true' - # yamllint disable-line rule:line-length - uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0 - with: - image: ${{ env.IMAGE }} - config: .github/.container-structure-test-config.yaml + run: >- + container-structure-test test + --config ${{ github.workspace + }}/.github/.container-structure-test-config.yaml + --image ${{ env.IMAGE }} - name: Dive - check image for waste files if: steps.changed-files-specific.outputs.any_changed == 'true' # yamllint disable-line rule:line-length - uses: MaxymVlasov/dive-action@379af3fc636888ada5899c997e8b52db6ad45023 # v1.0.1 + uses: MaxymVlasov/dive-action@b6a02b38f0f309e8817199658eab090d4f0f93ce # v1.1.0 with: image: ${{ env.IMAGE }} config-file: ${{ github.workspace }}/.github/.dive-ci.yaml @@ -95,7 +118,6 @@ jobs: if: >- steps.changed-files-specific.outputs.any_changed == 'true' && matrix.os == 'ubuntu-latest' - && matrix.arch == 'amd64' # yamllint disable-line rule:line-length uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 with: diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 632d78d10..f70db2148 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -23,9 +23,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Set up QEMU - # yamllint disable-line rule:line-length - uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0 - name: Set up Docker Buildx # yamllint disable-line rule:line-length uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 From 89d2f41ecd22027fc43e3d9db6b3c2eff3f01a13 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 27 Jan 2025 16:38:55 +0200 Subject: [PATCH 144/293] chore: Add OSSF badge (#787) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fec0d10dc..670fdd114 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Github tag](https://img.shields.io/github/tag/antonbabenko/pre-commit-terraform.svg)](https://github.com/antonbabenko/pre-commit-terraform/releases) ![maintenance status](https://img.shields.io/maintenance/yes/2025.svg) [![Help Contribute to Open Source](https://www.codetriage.com/antonbabenko/pre-commit-terraform/badges/users.svg)](https://www.codetriage.com/antonbabenko/pre-commit-terraform) [![CI/CD Badge]][CI/CD] [![Codecov Badge]][Codecov] +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/antonbabenko/pre-commit-terraform/badge)](https://scorecard.dev/viewer/?uri=github.com/antonbabenko/pre-commit-terraform) [CI/CD Badge]: https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml/badge.svg?branch=master From 07ff4975befca39bc78aaf6ce196d6c7b4b4bb55 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 27 Jan 2025 17:25:29 +0200 Subject: [PATCH 145/293] chore(StepSecurity): Add CodeQL (#786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Security Fixes ### Detect Vulnerabilities with SAST Workflow Static Code Analysis (also known as Source Code Analysis) is usually performed as part of a Code Review (also known as clear-box testing) and is carried out at the Implementation phase of a Security Development Lifecycle (SDL). Static Code Analysis commonly refers to the running of Static Code Analysis tools that attempt to highlight possible vulnerabilities within ‘static’ (non-running) source code by using techniques such as Taint Analysis and Data Flow Analysis. - [The Open Source Security Foundation (OpenSSF) Security Guide](https://github.com/ossf/scorecard/blob/main/docs/checks.md#sast) - [OWASP Static Code Analysis](https://owasp.org/www-community/controls/Static_Code_Analysis) - [Github Guide For Code Scanning](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/setting-up-code-scanning-for-a-repository) --------- Signed-off-by: StepSecurity Bot Co-authored-by: StepSecurity Bot --- .github/workflows/codeql.yml | 82 ++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..25c919b61 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,82 @@ +--- +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: CodeQL + +on: + push: + branches: + - master + pull_request: + schedule: + - cron: 0 0 * * 1 + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: + - python + # CodeQL supports [ $supported-codeql-languages ] + # Learn more about CodeQL language support at + # https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + # yamllint disable-line rule:line-length + uses: github/codeql-action/init@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in + # a config file. # By default, queries listed here will override any + # specified in a config file. Prefix the list here with "+" to use + # these queries and those in the config file. + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java) + # If this step fails, then you should remove it and run the build + # manually (see below) + - name: Autobuild + # yamllint disable-line rule:line-length + uses: github/codeql-action/autobuild@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 + + # ℹ️ Command-line programs to run using the OS shell. + # yamllint disable-line rule:line-length + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following + # three lines. Modify them (or add more) to build your code if your + # project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + # yamllint disable-line rule:line-length + uses: github/codeql-action/analyze@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 + with: + category: /language:${{matrix.language}} From bda84a52411231e499646884c035aadf9e6f7a61 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 16:49:07 +0200 Subject: [PATCH 146/293] chore(deps): Update codecov/codecov-action action to v5.3.1 (#791) --- .github/workflows/reusable-tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index e95ba798a..e6b05e9f5 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -383,7 +383,7 @@ jobs: !cancelled() && steps.tox-run.outputs.cov-report-files != '' # yamllint disable-line rule:line-length - uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 + uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 with: disable_search: true fail_ci_if_error: >- From 056698398b805ab9f122ce1cc9b066146e318dd6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 16:55:34 +0200 Subject: [PATCH 147/293] chore(deps): Update github/codeql-action action to v3.28.5 (#790) --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 5c61608d3..31cf8ae89 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -79,6 +79,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning # yamllint disable-line rule:line-length - uses: github/codeql-action/upload-sarif@ee117c905ab18f32fa0f66c2fe40ecc8013f3e04 # v3.28.4 + uses: github/codeql-action/upload-sarif@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 with: sarif_file: results.sarif From f10095ae9c4dde3055bb3f6482a6350000d43c82 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 18:25:32 +0200 Subject: [PATCH 148/293] chore(deps): Update ossf/scorecard-action action to v2.4.0 (#793) --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 31cf8ae89..8f0e93792 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -45,7 +45,7 @@ jobs: - name: Run analysis # yamllint disable-line rule:line-length - uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif From 435e04e8e68d6637145b634fec550b6dde2c7953 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 18:51:30 +0200 Subject: [PATCH 149/293] chore(deps): Update pre-commit hook gitleaks/gitleaks to v8.23.2 (#794) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e861e5e2..d2b4ac4ae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: # Detect hardcoded secrets - repo: https://github.com/gitleaks/gitleaks - rev: v8.16.3 + rev: v8.23.2 hooks: - id: gitleaks From f3432b3ad26231fb5cea26f2891dbc7c11ed66d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 20:34:53 +0200 Subject: [PATCH 150/293] chore(deps): Update docker/build-push-action action to v6.13.0 (#792) --- .github/workflows/build-image-test.yaml | 4 ++-- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 7cc6c3086..4087daeea 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -66,7 +66,7 @@ jobs: - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' # yamllint disable-line rule:line-length - uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 + uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 with: context: . build-args: | @@ -119,7 +119,7 @@ jobs: steps.changed-files-specific.outputs.any_changed == 'true' && matrix.os == 'ubuntu-latest' # yamllint disable-line rule:line-length - uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 + uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 with: context: . build-args: | diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index f70db2148..86b49f8ad 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -53,7 +53,7 @@ jobs: - name: Build and Push release if: github.event_name != 'schedule' # yamllint disable-line rule:line-length - uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 + uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 with: context: . build-args: | @@ -71,7 +71,7 @@ jobs: - name: Build and Push nightly if: github.event_name == 'schedule' # yamllint disable-line rule:line-length - uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 + uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 with: context: . build-args: | From 391568c46b29d1ed2d93ee0dbbf36dbe1c4b63d5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 20:44:17 +0200 Subject: [PATCH 151/293] chore(deps): Update re-actors/checkout-python-sdist to v2.0.0 (#789) --- .github/workflows/reusable-tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index e6b05e9f5..c58a996a1 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -177,7 +177,7 @@ jobs: if: >- !contains(fromJSON('["pre-commit", "spellcheck-docs"]'), inputs.toxenv) # yamllint disable-line rule:line-length - uses: re-actors/checkout-python-sdist@187f55296b0f54d88259aaaf99af32ad3647d3bc # release/v2 + uses: re-actors/checkout-python-sdist@187f55296b0f54d88259aaaf99af32ad3647d3bc # v2.0.0 with: source-tarball-name: ${{ inputs.source-tarball-name }} workflow-artifact-name: ${{ inputs.dists-artifact-name }} From a14834b3960d6e8dee92e8a7d124bb8e176446d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 03:51:50 +0200 Subject: [PATCH 152/293] chore(deps): Pin re-actors/alls-green digest to v1.2.2 (#788) --- .github/workflows/ci-cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 7aca07139..7006955ea 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -523,7 +523,7 @@ jobs: steps: - name: Decide whether the needed jobs succeeded or failed # yamllint disable-line rule:line-length - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1 + uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} From 4b0ad90860f13e2d007fe4ae252eb6bfa750ec6b Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 29 Jan 2025 21:02:36 +0200 Subject: [PATCH 153/293] chore: Make codecov badge green in README (#795) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 670fdd114..78740b8be 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml [Codecov Badge]: -https://codecov.io/gh/antonbabenko/pre-commit-terraform/branch/master/graph/badge.svg?flags[]=pytest +https://codecov.io/gh/antonbabenko/pre-commit-terraform/branch/master/graph/badge.svg?flag=pytest [Codecov]: https://app.codecov.io/gh/antonbabenko/pre-commit-terraform?flags[]=pytest [![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) From dfbde651c588acfaa5f30d1952cab450804f39b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Thu, 30 Jan 2025 19:28:16 +0100 Subject: [PATCH 154/293] chore: Suppress insufficient coverage `pytest` warning (#798) This is emitted by `pytest-cov` [[1]] and is turned into an error by the default `-Werror` passed to Python, not the `filterwarnings` setting within the `pytest`. The patch selectively suppresses the warning so there's nothing to turn into an error in the first place. Insufficient coverage still marks the test session as failed as it's supposed to. [1]: https://github.com/pytest-dev/pytest-cov/issues/675 --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 57681be03..a7e967941 100644 --- a/tox.ini +++ b/tox.ini @@ -23,6 +23,7 @@ commands = {[python-cli-options]byte-errors} \ {[python-cli-options]max-isolation} \ {[python-cli-options]warnings-to-errors} \ + -W 'ignore:Coverage failure::pytest_cov.plugin' \ -m pytest \ {tty:--color=yes} \ {posargs:--cov-report=html:{envtmpdir}{/}htmlcov{/}} From 0ce9d4a3d9b0d02242d056afe199175b64f3b691 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 30 Jan 2025 23:16:48 +0200 Subject: [PATCH 155/293] chore: Fix path to HTML coverage report during local `tox` run (#797) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix path to HTML coverage report during local `tox` run * Do not show how to serve coverage report if there is no coverage --------- Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a7e967941..0b0f26b6b 100644 --- a/tox.ini +++ b/tox.ini @@ -91,11 +91,12 @@ commands_post = cov_html_reports or sys.exit(); \ cov_html_report_dir = pathlib.Path(cov_html_reports[-1]); \ index_file = cov_html_report_dir / "index.html";\ + index_file.exists() or sys.exit(); \ html_url = f"file://\{index_file\}";\ browse_cmd = shlex.join(("python3", "-Im", "webbrowser", html_url)); \ serve_cmd = shlex.join((\ "python3", "-Im", "http.server", \ - "--directory", "cov_html_report_dir", "0", \ + "--directory", str(cov_html_report_dir), "0", \ )); \ print(f"\nTo open the HTML coverage report, run\n\n\ \t\{browse_cmd !s\}\n");\ From 53cadec5254c205bacdceb36ea6e153e9e0b1650 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 31 Jan 2025 23:09:01 +0200 Subject: [PATCH 156/293] chore: Pin action tag (#800) --- .github/workflows/reusable-tox.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index c58a996a1..fc35c7db8 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -407,10 +407,8 @@ jobs: if: >- !cancelled() && steps.tox-run.outputs.test-result-files != '' - # FIXME: revert to v1 once Codecov releases v1.0.2 of their action. - # Ref: https://github.com/codecov/test-results-action/issues/108. - # uses: codecov/test-results-action@v1 - uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 + # yamllint disable-line rule:line-length + uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2 with: disable_search: true fail_ci_if_error: >- From 58a89a1cc0760daa515f58da9bb8b167f01044bb Mon Sep 17 00:00:00 2001 From: Daniel Sampliner Date: Sat, 1 Feb 2025 17:51:46 -0500 Subject: [PATCH 157/293] fix: Parallelism CPU calculation inside Kubernetes and Docker with limits (#799) The value of /sys/fs/cgroup/cpu/cpu.cfs_quota_us is not in milliseconds and cannot be simply divided by 1000 to determine the CPU limit. As per kernel documentation[^1], the cpu limit can be determined by dividing that value by /sys/fs/cgroup/cpu/cpu.cfs_period_us. [^1]: https://docs.kernel.org/scheduler/sched-bwc.html --------- Co-authored-by: George L. Yermulnik Co-authored-by: MaxymVlasov --- hooks/_common.sh | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index fc0c930a7..e9defff52 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -188,6 +188,11 @@ function common::is_hook_run_on_whole_repo { ####################################################################### # Get the number of CPU logical cores available for pre-commit to use +# +# CPU quota should be calculated as `cpu.cfs_quota_us / cpu.cfs_period_us` +# For K8s see: https://docs.kernel.org/scheduler/sched-bwc.html +# For Docker see: https://docs.docker.com/engine/containers/resource_constraints/#configure-the-default-cfs-scheduler +# # Arguments: # parallelism_ci_cpu_cores (string) Used in edge cases when number of # CPU cores can't be derived automatically @@ -197,14 +202,15 @@ function common::is_hook_run_on_whole_repo { function common::get_cpu_num { local -r parallelism_ci_cpu_cores=$1 - local millicpu + local cpu_quota cpu_period cpu_num if [[ -f /sys/fs/cgroup/cpu/cpu.cfs_quota_us && ! -f /proc/sys/fs/binfmt_misc/WSLInterop ]]; then # WSL have cfs_quota_us, but WSL should be checked as usual Linux host # Inside K8s pod or DinD in K8s - millicpu=$(< /sys/fs/cgroup/cpu/cpu.cfs_quota_us) + cpu_quota=$(< /sys/fs/cgroup/cpu/cpu.cfs_quota_us) + cpu_period=$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us 2> /dev/null || echo "$cpu_quota") - if [[ $millicpu -eq -1 ]]; then + if [[ $cpu_quota -eq -1 || $cpu_period -lt 1 ]]; then # K8s no limits or in DinD if [[ -n $parallelism_ci_cpu_cores ]]; then if [[ ! $parallelism_ci_cpu_cores =~ ^[[:digit:]]+$ ]]; then @@ -233,21 +239,24 @@ function common::get_cpu_num { return fi - echo $((millicpu / 1000)) + cpu_num=$((cpu_quota / cpu_period)) + [[ $cpu_num -lt 1 ]] && echo 1 || echo $cpu_num return fi if [[ -f /sys/fs/cgroup/cpu.max ]]; then # Inside Linux (Docker?) container - millicpu=$(cut -d' ' -f1 /sys/fs/cgroup/cpu.max) + cpu_quota=$(cut -d' ' -f1 /sys/fs/cgroup/cpu.max) + cpu_period=$(cut -d' ' -f2 /sys/fs/cgroup/cpu.max) - if [[ $millicpu == max ]]; then + if [[ $cpu_quota == max || $cpu_period -lt 1 ]]; then # No limits nproc 2> /dev/null || echo 1 return fi - echo $((millicpu / 1000)) + cpu_num=$((cpu_quota / cpu_period)) + [[ $cpu_num -lt 1 ]] && echo 1 || echo $cpu_num return fi From 1d4507a044254f9d17cb18bda786f6dd1829a598 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 1 Feb 2025 22:52:15 +0000 Subject: [PATCH 158/293] chore(release): version 1.97.1 [skip ci] ## [1.97.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.0...v1.97.1) (2025-02-01) ### Bug Fixes * Parallelism CPU calculation inside Kubernetes and Docker with limits ([#799](https://github.com/antonbabenko/pre-commit-terraform/issues/799)) ([58a89a1](https://github.com/antonbabenko/pre-commit-terraform/commit/58a89a1cc0760daa515f58da9bb8b167f01044bb)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d67a16d01..22196cf95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.97.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.0...v1.97.1) (2025-02-01) + + +### Bug Fixes + +* Parallelism CPU calculation inside Kubernetes and Docker with limits ([#799](https://github.com/antonbabenko/pre-commit-terraform/issues/799)) ([58a89a1](https://github.com/antonbabenko/pre-commit-terraform/commit/58a89a1cc0760daa515f58da9bb8b167f01044bb)) + # [1.97.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.96.3...v1.97.0) (2025-01-16) From 7d839114a62b61f2658167356df7e5da1a42ef8f Mon Sep 17 00:00:00 2001 From: Tobias Breitwieser Date: Mon, 3 Feb 2025 18:30:19 +0100 Subject: [PATCH 159/293] fix(`terraform_docs`): Allow having whitespaces in path to `.terraform-docs.yaml` config file (#796) --- hooks/terraform_docs.sh | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index accbb02a1..5290fef9f 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -88,6 +88,7 @@ function terraform_docs { local add_to_existing=false local create_if_not_exist=false local use_standard_markers=true + local have_config_flag=false IFS=";" read -r -a configs <<< "$hook_config" @@ -142,10 +143,18 @@ function terraform_docs { local tf_docs_formatter="md" else - - local config_file=${args#*--config} - config_file=${config_file#*=} - config_file=${config_file% *} + have_config_flag=true + # Enable extended pattern matching operators + shopt -qp extglob || EXTGLOB_IS_NOT_SET=true && shopt -s extglob + # Trim any args before the `--config` arg value + local config_file=${args##*--config@(+([[:space:]])|=)} + # Trim any trailing spaces and args (if any) + config_file="${config_file%%+([[:space:]])?(--*)}" + # Trim `--config` arg and its value from original args as we will + # pass `--config` separately to allow whitespaces in its value + args=${args/--config@(+([[:space:]])|=)$config_file*([[:space:]])/} + # Restore state of `extglob` if we changed it + [[ $EXTGLOB_IS_NOT_SET ]] && shopt -u extglob # Prioritize `.terraform-docs.yml` `output.file` over # `--hook-config=--path-to-file=` if it set @@ -231,8 +240,10 @@ function terraform_docs { have_marker=$(grep -o "$insertion_marker_begin" "$output_file") || unset have_marker [[ ! $have_marker ]] && popd > /dev/null && continue fi + local config_options + [[ $have_config_flag == true ]] && config_options="--config=$config_file" # shellcheck disable=SC2086 - terraform-docs --output-mode="$output_mode" --output-file="$output_file" $tf_docs_formatter $args ./ > /dev/null + terraform-docs --output-mode="$output_mode" --output-file="$output_file" $tf_docs_formatter "$config_options" $args ./ > /dev/null popd > /dev/null done From 03d62702734aa7dcccb6448e1e6d9401aabe8ab5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 3 Feb 2025 17:30:48 +0000 Subject: [PATCH 160/293] chore(release): version 1.97.2 [skip ci] ## [1.97.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.1...v1.97.2) (2025-02-03) ### Bug Fixes * **`terraform_docs`:** Allow having whitespaces in path to `.terraform-docs.yaml` config file ([#796](https://github.com/antonbabenko/pre-commit-terraform/issues/796)) ([7d83911](https://github.com/antonbabenko/pre-commit-terraform/commit/7d839114a62b61f2658167356df7e5da1a42ef8f)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22196cf95..104724a16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.97.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.1...v1.97.2) (2025-02-03) + + +### Bug Fixes + +* **`terraform_docs`:** Allow having whitespaces in path to `.terraform-docs.yaml` config file ([#796](https://github.com/antonbabenko/pre-commit-terraform/issues/796)) ([7d83911](https://github.com/antonbabenko/pre-commit-terraform/commit/7d839114a62b61f2658167356df7e5da1a42ef8f)) + ## [1.97.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.0...v1.97.1) (2025-02-01) From 64b81f449344ed72d180d57ce0a801389c018584 Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Tue, 4 Feb 2025 15:49:00 +0200 Subject: [PATCH 161/293] fix(`terraform_docs`): Fix bug introduced in `v1.97.2` (#801) * Fix bug introduced via #796 by passing config file only when it is defined * While here make array declarations in `common::parse_cmdline` in `hooks/_common.sh` compliant with Bash v3 * While here suppress error outputs from `grep` for non-existing config file in `hooks/terraform_docs.sh` where error output makes no sense --- hooks/_common.sh | 4 +++- hooks/terraform_docs.sh | 23 +++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index e9defff52..fbd4bc2b4 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -52,7 +52,9 @@ function common::initialize { function common::parse_cmdline { # common global arrays. # Populated via `common::parse_cmdline` and can be used inside hooks' functions - ARGS=() HOOK_CONFIG=() FILES=() + ARGS=() + HOOK_CONFIG=() + FILES=() # Used inside `common::terraform_init` function TF_INIT_ARGS=() # Used inside `common::export_provided_env_vars` function diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 5290fef9f..6213fda08 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -160,7 +160,7 @@ function terraform_docs { # `--hook-config=--path-to-file=` if it set local config_output_file # Get latest non-commented `output.file` from `.terraform-docs.yml` - config_output_file=$(grep -A1000 -e '^output:$' "$config_file" | grep -E '^[[:space:]]+file:' | tail -n 1) || true + config_output_file=$(grep -A1000 -e '^output:$' "$config_file" 2> /dev/null | grep -E '^[[:space:]]+file:' | tail -n 1) || true if [[ $config_output_file ]]; then # Extract filename from `output.file` line @@ -176,7 +176,7 @@ function terraform_docs { # Use `.terraform-docs.yml` `output.mode` if it set local config_output_mode - config_output_mode=$(grep -A1000 -e '^output:$' "$config_file" | grep -E '^[[:space:]]+mode:' | tail -n 1) || true + config_output_mode=$(grep -A1000 -e '^output:$' "$config_file" 2> /dev/null | grep -E '^[[:space:]]+mode:' | tail -n 1) || true if [[ $config_output_mode ]]; then # Extract mode from `output.mode` line output_mode=$(echo "$config_output_mode" | awk -F':' '{print $2}' | tr -d '[:space:]"' | tr -d "'") @@ -240,10 +240,21 @@ function terraform_docs { have_marker=$(grep -o "$insertion_marker_begin" "$output_file") || unset have_marker [[ ! $have_marker ]] && popd > /dev/null && continue fi - local config_options - [[ $have_config_flag == true ]] && config_options="--config=$config_file" - # shellcheck disable=SC2086 - terraform-docs --output-mode="$output_mode" --output-file="$output_file" $tf_docs_formatter "$config_options" $args ./ > /dev/null + + # shellcheck disable=SC2206 + # Need to pass $tf_docs_formatter and $args as separate arguments, not as single string + local tfdocs_cmd=( + terraform-docs + --output-mode="$output_mode" + --output-file="$output_file" + $tf_docs_formatter + $args + ) + if [[ $have_config_flag == true ]]; then + "${tfdocs_cmd[@]}" "--config=$config_file" ./ > /dev/null + else + "${tfdocs_cmd[@]}" ./ > /dev/null + fi popd > /dev/null done From 236cbaedf7e10643b38b1e6cc04e4035810dd13a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 4 Feb 2025 13:49:26 +0000 Subject: [PATCH 162/293] chore(release): version 1.97.3 [skip ci] ## [1.97.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.2...v1.97.3) (2025-02-04) ### Bug Fixes * **`terraform_docs`:** Fix bug introduced in `v1.97.2` ([#801](https://github.com/antonbabenko/pre-commit-terraform/issues/801)) ([64b81f4](https://github.com/antonbabenko/pre-commit-terraform/commit/64b81f449344ed72d180d57ce0a801389c018584)), closes [#796](https://github.com/antonbabenko/pre-commit-terraform/issues/796) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 104724a16..cfe106290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.97.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.2...v1.97.3) (2025-02-04) + + +### Bug Fixes + +* **`terraform_docs`:** Fix bug introduced in `v1.97.2` ([#801](https://github.com/antonbabenko/pre-commit-terraform/issues/801)) ([64b81f4](https://github.com/antonbabenko/pre-commit-terraform/commit/64b81f449344ed72d180d57ce0a801389c018584)), closes [#796](https://github.com/antonbabenko/pre-commit-terraform/issues/796) + ## [1.97.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.1...v1.97.2) (2025-02-03) From 0dac00210f1da291dbd283cb37cdf76cbace9388 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:16:08 +0200 Subject: [PATCH 163/293] chore(deps): Update actions/setup-python action to v5.4.0 (#806) --- .github/workflows/ci-cd.yml | 4 ++-- .github/workflows/pre-commit.yaml | 2 +- .github/workflows/reusable-tox.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 7006955ea..8cbde4986 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -110,7 +110,7 @@ jobs: steps: - name: Switch to using Python 3.13 by default # yamllint disable-line rule:line-length - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: 3.13 - name: Check out src from Git @@ -275,7 +275,7 @@ jobs: steps: - name: Switch to using Python 3.13 # yamllint disable-line rule:line-length - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: 3.13 diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 51fcc4e07..74f13b657 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -56,7 +56,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} # Skip terraform_tflint which interferes to commit pre-commit auto-fixes # yamllint disable-line rule:line-length - - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.9' - name: Execute pre-commit diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index fc35c7db8..1b83a6240 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -159,7 +159,7 @@ jobs: by default id: python-install # yamllint disable-line rule:line-length - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: ${{ inputs.python-version }} From d2a8e5c014d7f87128e1d0a8b04fbfd6c1f2a100 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:29:35 +0200 Subject: [PATCH 164/293] chore(deps): Update github/codeql-action action to v3.28.8 (#803) --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 25c919b61..ad09a69b4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL # yamllint disable-line rule:line-length - uses: github/codeql-action/init@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 + uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in @@ -61,7 +61,7 @@ jobs: # manually (see below) - name: Autobuild # yamllint disable-line rule:line-length - uses: github/codeql-action/autobuild@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 + uses: github/codeql-action/autobuild@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 # ℹ️ Command-line programs to run using the OS shell. # yamllint disable-line rule:line-length @@ -77,6 +77,6 @@ jobs: - name: Perform CodeQL Analysis # yamllint disable-line rule:line-length - uses: github/codeql-action/analyze@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 + uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: category: /language:${{matrix.language}} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 8f0e93792..86caf83d8 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -79,6 +79,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning # yamllint disable-line rule:line-length - uses: github/codeql-action/upload-sarif@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 + uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: sarif_file: results.sarif From 8b50354c4692bab4c32b02618bc9310c2f25c33e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:38:44 +0200 Subject: [PATCH 165/293] chore(deps): Update pre-commit hook gitleaks/gitleaks to v8.23.3 (#804) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d2b4ac4ae..6fa3f57fc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: # Detect hardcoded secrets - repo: https://github.com/gitleaks/gitleaks - rev: v8.23.2 + rev: v8.23.3 hooks: - id: gitleaks From 156a897bac0e45903d3b1832d589054dbe3e7e84 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 10 Feb 2025 18:14:01 +0200 Subject: [PATCH 166/293] chore(`yamllint`): Allow longer lines to avoid unneeded linter disabling (#811) Formatter which will be introduced later, will enforce stricter rules when it is possible --- .github/.container-structure-test-config.yaml | 1 - .github/workflows/build-image-test.yaml | 5 ----- .github/workflows/build-image.yaml | 5 ----- .github/workflows/ci-cd.yml | 8 -------- .github/workflows/codeql.yml | 3 --- .github/workflows/dependency-review.yml | 2 -- .github/workflows/pr-title.yml | 1 - .github/workflows/pre-commit.yaml | 1 - .github/workflows/release.yml | 1 - .github/workflows/reusable-tox.yml | 10 ---------- .github/workflows/scorecards.yml | 4 ---- .yamllint | 2 ++ 12 files changed, 2 insertions(+), 41 deletions(-) diff --git a/.github/.container-structure-test-config.yaml b/.github/.container-structure-test-config.yaml index bfaa36c18..f200f52b2 100644 --- a/.github/.container-structure-test-config.yaml +++ b/.github/.container-structure-test-config.yaml @@ -137,7 +137,6 @@ commandTests: fileExistenceTests: - name: terrascan init - # yamllint disable-line rule:line-length path: >- /root/.terrascan/pkg/policies/opa/rego/github/github_repository/privateRepoEnabled.rego shouldExist: true diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 4087daeea..ea0956885 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -40,7 +40,6 @@ jobs: - name: Get changed Docker related files id: changed-files-specific - # yamllint disable-line rule:line-length uses: tj-actions/changed-files@d6e91a2266cdb9d62096cebf1e8546899c6aa18f # v45.0.6 with: files: | @@ -59,13 +58,11 @@ jobs: >> $GITHUB_ENV - name: Set up Docker Buildx - # yamllint disable-line rule:line-length uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 if: steps.changed-files-specific.outputs.any_changed == 'true' - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' - # yamllint disable-line rule:line-length uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 with: context: . @@ -105,7 +102,6 @@ jobs: - name: Dive - check image for waste files if: steps.changed-files-specific.outputs.any_changed == 'true' - # yamllint disable-line rule:line-length uses: MaxymVlasov/dive-action@b6a02b38f0f309e8817199658eab090d4f0f93ce # v1.1.0 with: image: ${{ env.IMAGE }} @@ -118,7 +114,6 @@ jobs: if: >- steps.changed-files-specific.outputs.any_changed == 'true' && matrix.os == 'ubuntu-latest' - # yamllint disable-line rule:line-length uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 with: context: . diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 86b49f8ad..c8a588aa0 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -24,10 +24,8 @@ jobs: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Docker Buildx - # yamllint disable-line rule:line-length uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Login to GitHub Container Registry - # yamllint disable-line rule:line-length uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io @@ -47,12 +45,10 @@ jobs: run: >- echo "IMAGE_REPO=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV - name: Set up Docker Buildx - # yamllint disable-line rule:line-length uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Build and Push release if: github.event_name != 'schedule' - # yamllint disable-line rule:line-length uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 with: context: . @@ -70,7 +66,6 @@ jobs: - name: Build and Push nightly if: github.event_name == 'schedule' - # yamllint disable-line rule:line-length uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 with: context: . diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 8cbde4986..2db819b3b 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -109,12 +109,10 @@ jobs: steps: - name: Switch to using Python 3.13 by default - # yamllint disable-line rule:line-length uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: 3.13 - name: Check out src from Git - # yamllint disable-line rule:line-length uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 @@ -167,7 +165,6 @@ jobs: echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" shell: bash - name: Set up pip cache - # yamllint disable-line rule:line-length uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ${{ steps.pip-cache-dir.outputs.dir }} @@ -274,13 +271,11 @@ jobs: steps: - name: Switch to using Python 3.13 - # yamllint disable-line rule:line-length uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: 3.13 - name: Grab the source from Git - # yamllint disable-line rule:line-length uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 @@ -309,7 +304,6 @@ jobs: run: >- echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" - name: Set up pip cache - # yamllint disable-line rule:line-length uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ${{ steps.pip-cache-dir.outputs.dir }} @@ -375,7 +369,6 @@ jobs: >> "${GITHUB_OUTPUT}" working-directory: dist - name: Store the distribution packages - # yamllint disable-line rule:line-length uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: >- @@ -522,7 +515,6 @@ jobs: steps: - name: Decide whether the needed jobs succeeded or failed - # yamllint disable-line rule:line-length uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ad09a69b4..4913969a8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,7 +47,6 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - # yamllint disable-line rule:line-length uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: languages: ${{ matrix.language }} @@ -60,7 +59,6 @@ jobs: # If this step fails, then you should remove it and run the build # manually (see below) - name: Autobuild - # yamllint disable-line rule:line-length uses: github/codeql-action/autobuild@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 # ℹ️ Command-line programs to run using the OS shell. @@ -76,7 +74,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - # yamllint disable-line rule:line-length uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: category: /language:${{matrix.language}} diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index f219dd1c7..0d5d6cf31 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -22,8 +22,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - # yamllint disable-line rule:line-length uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Dependency Review - # yamllint disable-line rule:line-length uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index b4ba2b43a..0690029ae 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -25,7 +25,6 @@ jobs: steps: # Please look up the latest version from # https://github.com/amannn/action-semantic-pull-request/releases - # yamllint disable-line rule:line-length - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 74f13b657..b35edf7b4 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -55,7 +55,6 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} # Skip terraform_tflint which interferes to commit pre-commit auto-fixes - # yamllint disable-line rule:line-length - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.9' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 62f720f95..b2be2bf41 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,6 @@ jobs: fetch-depth: 0 - name: Release - # yamllint disable-line rule:line-length uses: cycjimmy/semantic-release-action@b1b432f13acb7768e0c8efdec416d363a57546f2 # v4.1.1 with: semantic_version: 18.0.0 diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 1b83a6240..46a88f349 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -158,7 +158,6 @@ jobs: Switch to using Python v${{ inputs.python-version }} by default id: python-install - # yamllint disable-line rule:line-length uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: ${{ inputs.python-version }} @@ -169,14 +168,12 @@ jobs: - name: Grab the source from Git if: >- contains(fromJSON('["pre-commit", "spellcheck-docs"]'), inputs.toxenv) - # yamllint disable-line rule:line-length uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.inputs.release-committish }} - name: Retrieve the project source from an sdist inside the GHA artifact if: >- !contains(fromJSON('["pre-commit", "spellcheck-docs"]'), inputs.toxenv) - # yamllint disable-line rule:line-length uses: re-actors/checkout-python-sdist@187f55296b0f54d88259aaaf99af32ad3647d3bc # v2.0.0 with: source-tarball-name: ${{ inputs.source-tarball-name }} @@ -184,7 +181,6 @@ jobs: - name: Cache pre-commit.com virtualenvs if: inputs.toxenv == 'pre-commit' - # yamllint disable-line rule:line-length uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ~/.cache/pre-commit @@ -243,7 +239,6 @@ jobs: shell: bash - name: Set up pip cache if: fromJSON(steps.py-abi.outputs.is-stable-abi) - # yamllint disable-line rule:line-length uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ${{ steps.pip-cache-dir.outputs.dir }} @@ -271,7 +266,6 @@ jobs: - name: Download all the dists if: >- contains(fromJSON('["metadata-validation", "pytest"]'), inputs.toxenv) - # yamllint disable-line rule:line-length uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: ${{ inputs.dists-artifact-name }} @@ -325,7 +319,6 @@ jobs: if: >- !cancelled() && steps.tox-run.outputs.test-result-files != '' - # yamllint disable-line rule:line-length uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 # v2.4 with: paths: >- @@ -343,7 +336,6 @@ jobs: && steps.tox-run.outputs.cov-report-files != '' && steps.tox-run.outputs.test-result-files == '' && steps.tox-run.outputs.codecov-flags != 'MyPy' - # yamllint disable-line rule:line-length uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0 with: badge: true @@ -382,7 +374,6 @@ jobs: if: >- !cancelled() && steps.tox-run.outputs.cov-report-files != '' - # yamllint disable-line rule:line-length uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 with: disable_search: true @@ -407,7 +398,6 @@ jobs: if: >- !cancelled() && steps.tox-run.outputs.test-result-files != '' - # yamllint disable-line rule:line-length uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2 with: disable_search: true diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 86caf83d8..c35ab79fd 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -38,13 +38,11 @@ jobs: steps: - name: Checkout code - # yamllint disable-line rule:line-length uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Run analysis - # yamllint disable-line rule:line-length uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif @@ -69,7 +67,6 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable # uploads of run results in SARIF format to the repository Actions tab. - name: Upload artifact - # yamllint disable-line rule:line-length uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: SARIF file @@ -78,7 +75,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - # yamllint disable-line rule:line-length uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: sarif_file: results.sarif diff --git a/.yamllint b/.yamllint index 3dde2eaa4..3cfa8875b 100644 --- a/.yamllint +++ b/.yamllint @@ -8,6 +8,8 @@ rules: indent-sequences: false quoted-strings: required: only-when-needed + line-length: + max: 100 truthy: allowed-values: - >- From 6eeb9a02f3bd466a02e24290e0e4b31cd717a6e8 Mon Sep 17 00:00:00 2001 From: Gianluca755 <19376177+Gianluca755@users.noreply.github.com> Date: Mon, 10 Feb 2025 17:28:59 +0100 Subject: [PATCH 167/293] docs: README fixes (#808) --------- Co-authored-by: Gianluca755 Co-authored-by: MaxymVlasov --- README.md | 188 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 104 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 78740b8be..789d8da92 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,36 @@ # Collection of git hooks for Terraform to be used with [pre-commit framework](http://pre-commit.com/) -[![Github tag](https://img.shields.io/github/tag/antonbabenko/pre-commit-terraform.svg)](https://github.com/antonbabenko/pre-commit-terraform/releases) ![maintenance status](https://img.shields.io/maintenance/yes/2025.svg) [![Help Contribute to Open Source](https://www.codetriage.com/antonbabenko/pre-commit-terraform/badges/users.svg)](https://www.codetriage.com/antonbabenko/pre-commit-terraform) -[![CI/CD Badge]][CI/CD] -[![Codecov Badge]][Codecov] -[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/antonbabenko/pre-commit-terraform/badge)](https://scorecard.dev/viewer/?uri=github.com/antonbabenko/pre-commit-terraform) - -[CI/CD Badge]: -https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml/badge.svg?branch=master -[CI/CD]: -https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml - -[Codecov Badge]: -https://codecov.io/gh/antonbabenko/pre-commit-terraform/branch/master/graph/badge.svg?flag=pytest -[Codecov]: https://app.codecov.io/gh/antonbabenko/pre-commit-terraform?flags[]=pytest - -[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) - -Want to contribute? Check [open issues](https://github.com/antonbabenko/pre-commit-terraform/issues?q=label%3A%22good+first+issue%22+is%3Aopen+sort%3Aupdated-desc) and [contributing notes](/.github/CONTRIBUTING.md). +[![Latest Github tag]](https://github.com/antonbabenko/pre-commit-terraform/releases) +![Maintenance status](https://img.shields.io/maintenance/yes/2025.svg) +[![Codetriage - Help Contribute to Open Source Badge]](https://www.codetriage.com/antonbabenko/pre-commit-terraform) +[![GHA Tests CI/CD Badge]](https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml) +[![Codecov pytest Badge]](https://app.codecov.io/gh/antonbabenko/pre-commit-terraform?flags[]=pytest) +[![OpenSSF Scorecard Badge]](https://scorecard.dev/viewer/?uri=github.com/antonbabenko/pre-commit-terraform) + +[![StandWithUkraine Banner]](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) + +Want to contribute? +Check [open issues](https://github.com/antonbabenko/pre-commit-terraform/issues?q=label%3A%22good+first+issue%22+is%3Aopen+sort%3Aupdated-desc) +and [contributing notes](/.github/CONTRIBUTING.md). + +[Latest Github tag]: https://img.shields.io/github/tag/antonbabenko/pre-commit-terraform.svg +[Codetriage - Help Contribute to Open Source Badge]: https://www.codetriage.com/antonbabenko/pre-commit-terraform/badges/users.svg +[GHA Tests CI/CD Badge]: https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml/badge.svg?branch=master +[Codecov Pytest Badge]: https://codecov.io/gh/antonbabenko/pre-commit-terraform/branch/master/graph/badge.svg?flag=pytest +[OpenSSF Scorecard Badge]: https://api.scorecard.dev/projects/github.com/antonbabenko/pre-commit-terraform/badge +[StandWithUkraine Banner]: https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg ## Sponsors
-
Terramate: Automate, Orchestrate and Observe Terraform + + Terramate: Automate, Orchestrate and Observe Terraform + -Terramate is an IaC collaboration, visibility and observability platform that empowers your team to manage Terraform and OpenTofu faster and more confidently than ever before. +Terramate is an IaC collaboration, visibility and observability platform that empowers your team to manage Terraform and OpenTofu faster and more confidently than ever before. If you want to support the development of `pre-commit-terraform` and [many other open-source projects](https://github.com/antonbabenko/terraform-aws-devops), please become a [GitHub Sponsor](https://github.com/sponsors/antonbabenko)! @@ -66,7 +71,7 @@ If you want to support the development of `pre-commit-terraform` and [many other * [Docker Usage](#docker-usage) * [File Permissions](#file-permissions) * [Download Terraform modules from private GitHub repositories](#download-terraform-modules-from-private-github-repositories) -* [Github Actions](#github-actions) +* [GitHub Actions](#github-actions) * [Authors](#authors) * [License](#license) * [Additional information for users from Russia and Belarus](#additional-information-for-users-from-russia-and-belarus) @@ -74,29 +79,29 @@ If you want to support the development of `pre-commit-terraform` and [many other ## How to install ### 1. Install dependencies - + * [`pre-commit`](https://pre-commit.com/#install), [`terraform`](https://www.terraform.io/downloads.html) or [`opentofu`](https://opentofu.org/docs/intro/install/), [`git`](https://git-scm.com/downloads), [BASH `3.2.57` or newer](https://www.gnu.org/software/bash/#download), Internet connection (on first run), - x86_64 or arm64 compatible operation system, + x86_64 or arm64 compatible operating system, Some hardware where this OS will run, Electricity for hardware and internet connection, Some basic physical laws, Hope that it all will work.

-* [`checkov`](https://github.com/bridgecrewio/checkov) required for `terraform_checkov` hook -* [`terraform-docs`](https://github.com/terraform-docs/terraform-docs) 0.12.0+ required for `terraform_docs` hook -* [`terragrunt`](https://terragrunt.gruntwork.io/docs/getting-started/install/) required for `terragrunt_validate` and `terragrunt_valid_inputs` hooks -* [`terrascan`](https://github.com/tenable/terrascan) required for `terrascan` hook -* [`TFLint`](https://github.com/terraform-linters/tflint) required for `terraform_tflint` hook -* [`TFSec`](https://github.com/liamg/tfsec) required for `terraform_tfsec` hook -* [`Trivy`](https://github.com/aquasecurity/trivy) required for `terraform_trivy` hook -* [`infracost`](https://github.com/infracost/infracost) required for `infracost_breakdown` hook -* [`jq`](https://github.com/stedolan/jq) required for `terraform_validate` with `--retry-once-with-cleanup` flag, and for `infracost_breakdown` hook -* [`tfupdate`](https://github.com/minamijoyo/tfupdate) required for `tfupdate` hook -* [`hcledit`](https://github.com/minamijoyo/hcledit) required for `terraform_wrapper_module_for_each` hook +* [`checkov`][checkov repo] required for `terraform_checkov` hook +* [`terraform-docs`][terraform-docs repo] 0.12.0+ required for `terraform_docs` hook +* [`terragrunt`][terragrunt repo] required for `terragrunt_validate` and `terragrunt_valid_inputs` hooks +* [`terrascan`][terrascan repo] required for `terrascan` hook +* [`TFLint`][tflint repo] required for `terraform_tflint` hook +* [`TFSec`][tfsec repo] required for `terraform_tfsec` hook +* [`Trivy`][trivy repo] required for `terraform_trivy` hook +* [`infracost`][infracost repo] required for `infracost_breakdown` hook +* [`jq`][jq repo] required for `terraform_validate` with `--retry-once-with-cleanup` flag, and for `infracost_breakdown` hook +* [`tfupdate`][tfupdate repo] required for `tfupdate` hook +* [`hcledit`][hcledit repo] required for `terraform_wrapper_module_for_each` hook #### 1.1 Custom Terraform binaries and OpenTofu support @@ -293,26 +298,26 @@ docker run --rm --entrypoint cat ghcr.io/antonbabenko/pre-commit-terraform:$TAG There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform configurations (both `*.tf` and `*.tfvars`) and Terragrunt configurations (`*.hcl`) in a good shape: -| Hook name | Description | Dependencies
[Install instructions here](#1-install-dependencies) | -| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -| `checkov` and `terraform_checkov` | [checkov](https://github.com/bridgecrewio/checkov) static analysis of terraform templates to spot potential security issues. [Hook notes](#checkov-deprecated-and-terraform_checkov) | `checkov`
Ubuntu deps: `python3`, `python3-pip` | -| `infracost_breakdown` | Check how much your infra costs with [infracost](https://github.com/infracost/infracost). [Hook notes](#infracost_breakdown) | `infracost`, `jq`, [Infracost API key](https://www.infracost.io/docs/#2-get-api-key) | -| `terraform_docs` | Inserts input and output documentation into `README.md`. Recommended. [Hook notes](#terraform_docs) | `terraform-docs` | -| `terraform_docs_replace` | Runs `terraform-docs` and pipes the output directly to README.md. **DEPRECATED**, see [#248](https://github.com/antonbabenko/pre-commit-terraform/issues/248). [Hook notes](#terraform_docs_replace-deprecated) | `python3`, `terraform-docs` | -| `terraform_docs_without_`
`aggregate_type_defaults` | Inserts input and output documentation into `README.md` without aggregate type defaults. Hook notes same as for [terraform_docs](#terraform_docs) | `terraform-docs` | -| `terraform_fmt` | Reformat all Terraform configuration files to a canonical format. [Hook notes](#terraform_fmt) | - | -| `terraform_providers_lock` | Updates provider signatures in [dependency lock files](https://www.terraform.io/docs/cli/commands/providers/lock.html). [Hook notes](#terraform_providers_lock) | - | -| `terraform_tflint` | Validates all Terraform configuration files with [TFLint](https://github.com/terraform-linters/tflint). [Available TFLint rules](https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/README.md). [Hook notes](#terraform_tflint). | `tflint` | -| `terraform_tfsec` | [TFSec](https://github.com/aquasecurity/tfsec) static analysis of terraform templates to spot potential security issues. **DEPRECATED**, use `terraform_trivy`. [Hook notes](#terraform_tfsec-deprecated) | `tfsec` | -| `terraform_trivy` | [Trivy](https://github.com/aquasecurity/trivy) static analysis of terraform templates to spot potential security issues. [Hook notes](#terraform_trivy) | `trivy` | -| `terraform_validate` | Validates all Terraform configuration files. [Hook notes](#terraform_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](https://github.com/gruntwork-io/terragrunt) unused and undefined inputs (`*.hcl`) -| `terragrunt_providers_lock` | Generates `.terraform.lock.hcl` files using [Terragrunt](https://github.com/gruntwork-io/terragrunt). | `terragrunt` | -| `terraform_wrapper_module_for_each` | Generates Terraform 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 Terraform core, providers, and modules. [Hook notes](#tfupdate) | `tfupdate` | +| Hook name | Description | Dependencies
[Install instructions here](#1-install-dependencies) | +| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| `checkov` and `terraform_checkov` | [checkov][checkov repo] static analysis of terraform templates to spot potential security issues. [Hook notes](#checkov-deprecated-and-terraform_checkov) | `checkov`
Ubuntu deps: `python3`, `python3-pip` | +| `infracost_breakdown` | Check how much your infra costs with [infracost][infracost repo]. [Hook notes](#infracost_breakdown) | `infracost`, `jq`, [Infracost API key](https://www.infracost.io/docs/#2-get-api-key) | +| `terraform_docs` | Inserts input and output documentation into `README.md`. [Hook notes](#terraform_docs) | `terraform-docs` | +| `terraform_docs_replace` | Runs `terraform-docs` and pipes the output directly to README.md. **DEPRECATED**, see [#248](https://github.com/antonbabenko/pre-commit-terraform/issues/248). [Hook notes](#terraform_docs_replace-deprecated) | `python3`, `terraform-docs` | +| `terraform_docs_without_`
`aggregate_type_defaults` | Inserts input and output documentation into `README.md` without aggregate type defaults. Hook notes same as for [terraform_docs](#terraform_docs) | `terraform-docs` | +| `terraform_fmt` | Reformat all Terraform configuration files to a canonical format. [Hook notes](#terraform_fmt) | - | +| `terraform_providers_lock` | Updates provider signatures in [dependency lock files](https://www.terraform.io/docs/cli/commands/providers/lock.html). [Hook notes](#terraform_providers_lock) | - | +| `terraform_tflint` | Validates all Terraform configuration files with [TFLint][tflint repo]. [Available TFLint rules](https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/README.md). [Hook notes](#terraform_tflint). | `tflint` | +| `terraform_tfsec` | [TFSec][tfsec repo] static analysis of terraform templates to spot potential security issues. **DEPRECATED**, use `terraform_trivy`. [Hook notes](#terraform_tfsec-deprecated) | `tfsec` | +| `terraform_trivy` | [Trivy][trivy repo] static analysis of terraform templates to spot potential security issues. [Hook notes](#terraform_trivy) | `trivy` | +| `terraform_validate` | Validates all Terraform configuration files. [Hook notes](#terraform_validate) | `jq`, only for `--retry-once-with-cleanup` flag | +| `terragrunt_fmt` | Reformat all [Terragrunt][terragrunt repo] configuration files (`*.hcl`) to a canonical format. | `terragrunt` | +| `terragrunt_validate` | Validates all [Terragrunt][terragrunt repo] configuration files (`*.hcl`) | `terragrunt` | +| `terragrunt_validate_inputs` | Validates [Terragrunt][terragrunt repo] unused and undefined inputs (`*.hcl`) | | +| `terragrunt_providers_lock` | Generates `.terraform.lock.hcl` files using [Terragrunt][terragrunt repo]. | `terragrunt` | +| `terraform_wrapper_module_for_each` | Generates Terraform wrappers with `for_each` in module. [Hook notes](#terraform_wrapper_module_for_each) | `hcledit` | +| `terrascan` | [terrascan][terrascan repo] Detect compliance and security violations. [Hook notes](#terrascan) | `terrascan` | +| `tfupdate` | [tfupdate][tfupdate repo] Update version constraints of Terraform core, providers, and modules. [Hook notes](#tfupdate) | `tfupdate` | Check the [source file](https://github.com/antonbabenko/pre-commit-terraform/blob/master/.pre-commit-hooks.yaml) to know arguments used for each hook. @@ -465,15 +470,15 @@ Note that `terraform_checkov` runs recursively during `-d .` usage. That means, Check all available arguments [here](https://www.checkov.io/2.Basics/CLI%20Command%20Reference.html). -For deprecated hook you need to specify each argument separately: + For deprecated hook you need to specify each argument separately: -```yaml -- id: checkov - args: [ - "-d", ".", - "--skip-check", "CKV2_AWS_8", - ] -``` + ```yaml + - id: checkov + args: [ + "-d", ".", + "--skip-check", "CKV2_AWS_8", + ] + ``` 2. When you have multiple directories and want to run `terraform_checkov` in all of them and share a single config file - use the `__GIT_WORKING_DIR__` placeholder. It will be replaced by `terraform_checkov` hooks with the Git working directory (repo root) at run time. For example: @@ -577,7 +582,7 @@ Unlike most other hooks, this hook triggers once if there are any changed files ### terraform_docs -1. `terraform_docs` and `terraform_docs_without_aggregate_type_defaults` will insert/update documentation generated by [terraform-docs](https://github.com/terraform-docs/terraform-docs) framed by markers: +1. `terraform_docs` and `terraform_docs_without_aggregate_type_defaults` will insert/update documentation generated by [terraform-docs][terraform-docs repo] framed by markers: ```txt @@ -672,7 +677,7 @@ To replicate functionality in `terraform_docs` hook: ```yaml - id: terraform_docs - args: + args: - --args=--config=.terraform-docs.yml ``` @@ -762,8 +767,7 @@ To replicate functionality in `terraform_docs` hook: - --hook-config=--mode=always-regenerate-lockfile ``` - -3. `terraform_providers_lock` supports custom arguments: +2. `terraform_providers_lock` supports custom arguments: ```yaml - id: terraform_providers_lock @@ -772,7 +776,7 @@ To replicate functionality in `terraform_docs` hook: - --args=-platform=darwin_amd64 ``` -4. It may happen that Terraform working directory (`.terraform`) already exists but not in the best condition (eg, not initialized modules, wrong version of Terraform, etc.). To solve this problem, you can find and delete all `.terraform` directories in your repository: +3. It may happen that Terraform working directory (`.terraform`) already exists but not in the best condition (eg, not initialized modules, wrong version of Terraform, etc.). To solve this problem, you can find and delete all `.terraform` directories in your repository: ```bash echo " @@ -786,7 +790,7 @@ To replicate functionality in `terraform_docs` hook: `terraform_providers_lock` hook will try to reinitialize directories before running the `terraform providers lock` command. -3. `terraform_providers_lock` support passing custom arguments to its `terraform init`: +4. `terraform_providers_lock` support passing custom arguments to its `terraform init`: > **Warning** > DEPRECATION NOTICE: This is available only in `no-mode` mode, which will be removed in v2.0. Please provide this keys to [`terraform_validate`](#terraform_validate) hook, which, to take effect, should be called before `terraform_providers_lock` @@ -823,8 +827,8 @@ To replicate functionality in `terraform_docs` hook: ```yaml - id: terraform_tflint - args: - - --hook-config=--delegate-chdir + args: + - --hook-config=--delegate-chdir ``` @@ -923,9 +927,9 @@ To replicate functionality in `terraform_docs` hook: ```yaml - id: terraform_trivy - args: - - --args=--format=json - - --args=--skip-dirs="**/.terraform" + args: + - --args=--format=json + - --args=--skip-dirs="**/.terraform" ``` 4. When you have multiple directories and want to run `trivy` in all of them and share a single config file - use the `__GIT_WORKING_DIR__` placeholder. It will be replaced by `terraform_trivy` hooks with Git working directory (repo root) at run time. For example: @@ -1005,7 +1009,7 @@ To replicate functionality in `terraform_docs` hook: > **Caution** > If you use Terraform workspaces, DO NOT use this option ([details](https://github.com/antonbabenko/pre-commit-terraform/issues/203#issuecomment-918791847)). Consider the first option, or wait for [`force-init`](https://github.com/antonbabenko/pre-commit-terraform/issues/224) option implementation. -1. `terraform_validate` in a repo with Terraform module, written using Terraform 0.15+ and which uses provider `configuration_aliases` ([Provider Aliases Within Modules](https://www.terraform.io/language/modules/develop/providers#provider-aliases-within-modules)), errors out. +4. `terraform_validate` in a repo with Terraform module, written using Terraform 0.15+ and which uses provider `configuration_aliases` ([Provider Aliases Within Modules](https://www.terraform.io/language/modules/develop/providers#provider-aliases-within-modules)), errors out. When running the hook against Terraform code where you have provider `configuration_aliases` defined in a `required_providers` configuration block, terraform will throw an error like: @@ -1043,15 +1047,14 @@ To replicate functionality in `terraform_docs` hook: - repo: local hooks: - id: generate-terraform-providers - name: generate-terraform-providers - require_serial: true - entry: .generate-providers.sh - language: script - files: \.tf(vars)?$ - pass_filenames: false + name: generate-terraform-providers + require_serial: true + entry: .generate-providers.sh + language: script + files: \.tf(vars)?$ + pass_filenames: false - repo: https://github.com/pre-commit/pre-commit-hooks - [...] ``` > **Tip** @@ -1234,13 +1237,16 @@ Finally, you can execute `docker run` with an additional volume mount so that th docker run --rm -e "USERID=$(id -u):$(id -g)" -v ~/.netrc:/root/.netrc -v $(pwd):/lint -w /lint ghcr.io/antonbabenko/pre-commit-terraform:latest run -a ``` -## Github Actions +## GitHub Actions -You can use this hook in your GitHub Actions workflow togehther with [pre-commit](https://pre-commit.com). To easy up dependency management, you can use the managed [docker image](#docker-usage) within your workflow. Make sure to set the image tag to the version you want to use. +You can use this hook in your GitHub Actions workflow together with [pre-commit](https://pre-commit.com). To easy up +dependency management, you can use the managed [docker image](#docker-usage) within your workflow. Make sure to set the +image tag to the version you want to use. In this repository's pre-commit [workflow file](.github/workflows/pre-commit.yaml) we run pre-commit without the container image. -Here is an example that use the container image, includes caching of pre-commit dependencies and uses the `pre-commit` command to run the checks (but fixes will be not automatically push back to your branch, when it possible): +Here's an example using the container image. It includes caching of pre-commit dependencies and utilizes the pre-commit +command to run checks (Note: Fixes will not be automatically pushed back to your branch, even when possible.): ```yaml name: pre-commit-terraform @@ -1295,7 +1301,7 @@ jobs: This repository is managed by [Anton Babenko](https://github.com/antonbabenko) with help from these awesome contributors: - + Contributors @@ -1316,3 +1322,17 @@ MIT licensed. See [LICENSE](LICENSE) for full details. * Russia has [illegally annexed Crimea in 2014](https://en.wikipedia.org/wiki/Annexation_of_Crimea_by_the_Russian_Federation) and [brought the war in Donbas](https://en.wikipedia.org/wiki/War_in_Donbas) followed by [full-scale invasion of Ukraine in 2022](https://en.wikipedia.org/wiki/2022_Russian_invasion_of_Ukraine). * Russia has brought sorrow and devastations to millions of Ukrainians, killed hundreds of innocent people, damaged thousands of buildings, and forced several million people to flee. * [Putin khuylo!](https://en.wikipedia.org/wiki/Putin_khuylo!) + + + +[checkov repo]: https://github.com/bridgecrewio/checkov +[terraform-docs repo]: https://github.com/terraform-docs/terraform-docs +[terragrunt repo]: https://github.com/gruntwork-io/terragrunt +[terrascan repo]: https://github.com/tenable/terrascan +[tflint repo]: https://github.com/terraform-linters/tflint +[tfsec repo]: https://github.com/aquasecurity/tfsec +[trivy repo]: https://github.com/aquasecurity/trivy +[infracost repo]: https://github.com/infracost/infracost +[jq repo]: https://github.com/stedolan/jq +[tfupdate repo]: https://github.com/minamijoyo/tfupdate +[hcledit repo]: https://github.com/minamijoyo/hcledit From 02c407271aaf34590939d6f5a8ab48374a2c0b4c Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 11 Feb 2025 04:22:43 +0200 Subject: [PATCH 168/293] chore(yamllint): Drop requirement for explisit document starts and end (#809) --- .codecov.yml | 4 ---- .github/.container-structure-test-config.yaml | 2 -- .github/.dive-ci.yaml | 2 -- .github/FUNDING.yml | 2 -- .github/workflows/build-image-test.yaml | 2 -- .github/workflows/build-image.yaml | 2 -- .github/workflows/ci-cd.yml | 4 ---- .github/workflows/codeql.yml | 1 - .github/workflows/dependency-review.yml | 2 -- .github/workflows/pr-title.yml | 2 -- .github/workflows/pre-commit.yaml | 2 -- .github/workflows/release.yml | 2 -- .github/workflows/reusable-tox.yml | 4 ---- .github/workflows/scheduled-runs.yml | 4 ---- .github/workflows/scorecards.yml | 1 - .github/workflows/stale-actions.yaml | 2 -- .pre-commit-config.yaml | 2 -- .pre-commit-hooks.yaml | 2 -- .yamllint | 6 ++---- 19 files changed, 2 insertions(+), 46 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index e47301f83..001c42cce 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,5 +1,3 @@ ---- - codecov: notify: after_n_builds: 21 # Number of test matrix+lint jobs uploading coverage @@ -50,5 +48,3 @@ github_checks: # Annotations are deprecated in Codecov because they are misleading. # Ref: https://github.com/codecov/codecov-action/issues/1710 annotations: false - -... diff --git a/.github/.container-structure-test-config.yaml b/.github/.container-structure-test-config.yaml index f200f52b2..0425a71eb 100644 --- a/.github/.container-structure-test-config.yaml +++ b/.github/.container-structure-test-config.yaml @@ -1,5 +1,3 @@ ---- - schemaVersion: 2.0.0 commandTests: diff --git a/.github/.dive-ci.yaml b/.github/.dive-ci.yaml index 95598bea3..84e22312a 100644 --- a/.github/.dive-ci.yaml +++ b/.github/.dive-ci.yaml @@ -1,5 +1,3 @@ ---- - rules: # If the efficiency is measured below X%, mark as failed. # Expressed as a ratio between 0-1. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index bea8a72a9..9fdcfce62 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,2 @@ ---- - github: [antonbabenko] custom: https://www.paypal.me/antonbabenko diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index ea0956885..ef6f6b949 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -1,5 +1,3 @@ ---- - name: Build Dockerfile if changed and run smoke tests on: diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index c8a588aa0..6f1181978 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -1,5 +1,3 @@ ---- - name: Publish container image on: diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 2db819b3b..787e7c83b 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -1,5 +1,3 @@ ---- - name: 🧪 on: @@ -518,5 +516,3 @@ jobs: uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} - -... diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4913969a8..98ddfa17e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,4 +1,3 @@ ---- # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 0d5d6cf31..3b3d9d5c0 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,5 +1,3 @@ ---- - # Dependency Review Action # # This Action will scan dependency manifest files that change as part of a diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 0690029ae..5c487ccf3 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -1,5 +1,3 @@ ---- - name: Validate PR title on: diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index b35edf7b4..0599ed75d 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -1,5 +1,3 @@ ---- - name: Common issues check on: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b2be2bf41..094c6d3d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,3 @@ ---- - name: Release on: diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 46a88f349..e20734738 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -1,5 +1,3 @@ ---- - name: >- ❌ [DO NOT CLICK] @@ -418,5 +416,3 @@ jobs: steps.python-install.outputs.python-version }} token: ${{ secrets.codecov-token }} - -... diff --git a/.github/workflows/scheduled-runs.yml b/.github/workflows/scheduled-runs.yml index 88d3a6171..1e80d97cd 100644 --- a/.github/workflows/scheduled-runs.yml +++ b/.github/workflows/scheduled-runs.yml @@ -1,5 +1,3 @@ ---- - name: ⏳ on: @@ -40,5 +38,3 @@ jobs: name: 🧪 Main CI/CD pipeline uses: ./.github/workflows/ci-cd.yml secrets: inherit - -... diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index c35ab79fd..d8a15fe4f 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -1,4 +1,3 @@ ---- # This workflow uses actions that are not certified by GitHub. They are # provided by a third-party and are governed by separate terms of service, # privacy policy, and support documentation. diff --git a/.github/workflows/stale-actions.yaml b/.github/workflows/stale-actions.yaml index e3f925abd..f35908f06 100644 --- a/.github/workflows/stale-actions.yaml +++ b/.github/workflows/stale-actions.yaml @@ -1,5 +1,3 @@ ---- - name: Mark or close stale issues and PRs on: schedule: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6fa3f57fc..05a92198d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,3 @@ ---- - ci: autoupdate_schedule: quarterly skip: diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 3be2522f1..0b2690351 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,5 +1,3 @@ ---- - - id: infracost_breakdown name: Infracost breakdown description: Check terraform infrastructure cost diff --git a/.yamllint b/.yamllint index 3cfa8875b..166ab4fe9 100644 --- a/.yamllint +++ b/.yamllint @@ -1,11 +1,11 @@ ---- - extends: default rules: indentation: level: error indent-sequences: false + document-start: + present: false quoted-strings: required: only-when-needed line-length: @@ -18,5 +18,3 @@ rules: true - >- # Allow "on" key name in GHA CI/CD workflow definitions on - -... From 4b72060ce655a1e6ba6ee8b2e77c09358520c1ae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:48:28 +0200 Subject: [PATCH 169/293] chore(deps): Update github/codeql-action action to v3.28.9 (#813) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 98ddfa17e..6f0b862c1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -46,7 +46,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 + uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in @@ -58,7 +58,7 @@ jobs: # If this step fails, then you should remove it and run the build # manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 + uses: github/codeql-action/autobuild@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 # ℹ️ Command-line programs to run using the OS shell. # yamllint disable-line rule:line-length @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 + uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 with: category: /language:${{matrix.language}} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index d8a15fe4f..582d8f7b5 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -74,6 +74,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 + uses: github/codeql-action/upload-sarif@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 with: sarif_file: results.sarif From f5e2f39341a89b4afbf87c5babcf099a52561f48 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Feb 2025 17:06:51 +0200 Subject: [PATCH 170/293] chore(deps): Update codecov/test-results-action action to v1.0.3 (#812) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/reusable-tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index e20734738..4b61e59bf 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -396,7 +396,7 @@ jobs: if: >- !cancelled() && steps.tox-run.outputs.test-result-files != '' - uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2 + uses: codecov/test-results-action@44ecb3a270cd942bdf0fa8f2ce14cb32493e810a # v1.0.3 with: disable_search: true fail_ci_if_error: >- From 7a35bfcd7d499daf4d61f3c4b4c8985abc305191 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Feb 2025 17:56:01 +0200 Subject: [PATCH 171/293] chore(deps): Update pre-commit hook pre-commit/mirrors-mypy to v1.15.0 (#816) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05a92198d..c2c2dfeee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -66,7 +66,7 @@ repos: files: .json5$ - repo: https://github.com/pre-commit/mirrors-mypy.git - rev: v1.14.1 + rev: v1.15.0 hooks: - id: mypy alias: mypy-py313 From fe58af2c9f6694f87865780b203b67faa7d3abab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Feb 2025 22:13:09 +0200 Subject: [PATCH 172/293] chore(deps): Update tj-actions/changed-files action to v45.0.7 (#814) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index ef6f6b949..852ddff10 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -38,7 +38,7 @@ jobs: - name: Get changed Docker related files id: changed-files-specific - uses: tj-actions/changed-files@d6e91a2266cdb9d62096cebf1e8546899c6aa18f # v45.0.6 + uses: tj-actions/changed-files@dcc7a0cba800f454d79fff4b993e8c3555bcc0a8 # v45.0.7 with: files: | Dockerfile From 93ea737735a67ccaa3602393fea679da9f8b0546 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 14:05:31 +0200 Subject: [PATCH 173/293] chore(deps): Update docker/setup-buildx-action action to v3.9.0 (#815) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 852ddff10..6c3b3b3d1 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -56,7 +56,7 @@ jobs: >> $GITHUB_ENV - name: Set up Docker Buildx - uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 if: steps.changed-files-specific.outputs.any_changed == 'true' - name: Build if Dockerfile changed diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 6f1181978..5301fa58b 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -22,7 +22,7 @@ jobs: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 - name: Login to GitHub Container Registry uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: @@ -43,7 +43,7 @@ jobs: run: >- echo "IMAGE_REPO=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV - name: Set up Docker Buildx - uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 - name: Build and Push release if: github.event_name != 'schedule' From 1e54f11bfc5e3fd60ebd25d752c8dacafcb86cd2 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 12 Feb 2025 14:24:15 +0200 Subject: [PATCH 174/293] chore(coderabbitai): Codify and improve config (#810) Except what was already here, I changed next settings: * Disable: related_issues, related_prs - to not make useless cross-links between PRs and issues * Collapse Walkthrough - as it took too much place in PR and needed just for first interaction. * Move summary to Walkthrough --------- Co-authored-by: George L. Yermulnik --- .coderabbit.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..28d9564e4 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +reviews: + # Collapse main AI comment by default, as it takes too much space when + # expanded. It also is needless for subsequent rounds of PR review, mostly + # only for the first one + collapse_walkthrough: true + # Move AI-generated summary from PR description to main AI comment. It + # hallucinates sometimes, especially with PRs that change code linting rules + high_level_summary_in_walkthrough: true + # Disable false-positive cross links to issues + related_issues: false + # Disable false-positive cross links to PRs + related_prs: false + # Disable useless Poem generation + poem: false + + auto_review: + # Enable AI review for Draft PRs + drafts: true From c613da7618d4a8c64b7854e23bbdeec19cdc0d9b Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 17 Feb 2025 21:28:04 +0200 Subject: [PATCH 175/293] chore: Set emojis that better represent what happens in GH workflows (#819) * Change emoji to neutral symbol and fix ignorance rules --- .github/workflows/ci-cd.yml | 6 ++---- .github/workflows/scheduled-runs.yml | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 787e7c83b..e62e58862 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -1,14 +1,12 @@ -name: 🧪 +name: ∞ on: merge_group: push: branches-ignore: - dependabot/** # Dependabot always creates PRs + - renovate/** # Our Renovate setup always creates PRs - gh-readonly-queue/** # Temporary merge queue-related GH-made branches - - maintenance/pip-tools-constraint-lockfiles # Lock files through PRs - - maintenance/pip-tools-constraint-lockfiles-** # Lock files through PRs - - patchback/backports/** # Patchback always creates PRs - pre-commit-ci-update-config # pre-commit.ci always creates a PR pull_request: workflow_call: # a way to embed the main tests diff --git a/.github/workflows/scheduled-runs.yml b/.github/workflows/scheduled-runs.yml index 1e80d97cd..d8915c4b1 100644 --- a/.github/workflows/scheduled-runs.yml +++ b/.github/workflows/scheduled-runs.yml @@ -1,4 +1,4 @@ -name: ⏳ +name: 🕔 on: pull_request: @@ -35,6 +35,6 @@ run-name: >- jobs: main-ci-cd-pipeline: - name: 🧪 Main CI/CD pipeline + name: ∞ Main CI/CD pipeline uses: ./.github/workflows/ci-cd.yml secrets: inherit From 8a38366274f3d62638848a199b2f7ac31931f4da Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 18 Feb 2025 14:45:59 +0200 Subject: [PATCH 176/293] docs: Describe succinctly what the software does (what problem does it solve?) (#820) --------- Co-authored-by: George L. Yermulnik --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 789d8da92..f5a601a23 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,13 @@ [![StandWithUkraine Banner]](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) +[`pre-commit-terraform`](https://github.com/antonbabenko/pre-commit-terraform) provides a collection of [Git Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) for Terraform and related tools and is driven by the [pre-commit framework](https://pre-commit.com). It helps ensure that Terraform, OpenTofu, and Terragrunt configurations are kept in good shape by automatically running various checks and formatting code before committing changes to version control system. This helps maintain code quality and consistency across the project. + +It can be run: +* Locally and in CI +* As standalone Git hooks or as a Docker image +* For the entire repository or just for change-related files (e.g., local git stash, last commit, or all changes in a Pull Request) + Want to contribute? Check [open issues](https://github.com/antonbabenko/pre-commit-terraform/issues?q=label%3A%22good+first+issue%22+is%3Aopen+sort%3Aupdated-desc) and [contributing notes](/.github/CONTRIBUTING.md). From a7f1c12fd1747fc8d96bd49685d71cb7be4cb50b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 22 Feb 2025 23:12:00 +0200 Subject: [PATCH 177/293] chore(deps): Update MaxymVlasov/dive-action action to v1.3.0 (#805) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 6c3b3b3d1..abd6b9a09 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -100,7 +100,7 @@ jobs: - name: Dive - check image for waste files if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: MaxymVlasov/dive-action@b6a02b38f0f309e8817199658eab090d4f0f93ce # v1.1.0 + uses: MaxymVlasov/dive-action@94506fd846be3ec26406118c3878ccd2ad2b0150 # v1.3.0 with: image: ${{ env.IMAGE }} config-file: ${{ github.workspace }}/.github/.dive-ci.yaml From b64526d7860bcbf1012a2b6e98235549ff47021e Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 24 Feb 2025 12:26:37 +0200 Subject: [PATCH 178/293] chore(linters): Add yamlfmt (#824) --- .codecov.yml | 4 ++-- .github/workflows/ci-cd.yml | 9 ++++++--- .github/workflows/reusable-tox.yml | 9 ++++++--- .github/workflows/stale-actions.yaml | 7 ++----- .pre-commit-config.yaml | 14 ++++++++++++++ .yamllint | 3 ++- 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 001c42cce..ab3e3a8b1 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -4,8 +4,8 @@ codecov: wait_for_ci: false require_ci_to_pass: false - - token: >- # notsecret # repo-scoped, upload-only, stability in fork PRs + # notsecret # repo-scoped, upload-only, stability in fork PRs + token: >- 7316089b-55fe-4646-b640-78d84b79d109 comment: diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index e62e58862..a4e545386 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -37,7 +37,8 @@ env: PYTHONIOENCODING: utf-8 PYTHONUTF8: 1 TOX_PARALLEL_NO_SPINNER: 1 # Disable tox's parallel run spinner animation - TOX_TESTENV_PASSENV: >- # Make tox-wrapped tools see color requests + # Make tox-wrapped tools see color requests + TOX_TESTENV_PASSENV: >- FORCE_COLOR MYPY_FORCE_COLOR NO_COLOR @@ -396,7 +397,8 @@ jobs: # `no-commit-to-branch` is skipped because it does not make sense # in the CI, only locally. # Ref: https://github.com/pre-commit/pre-commit-hooks/issues/1124 - - >- # only affects pre-commit, set for all for simplicity: + # only affects pre-commit, set for all for simplicity: + - >- SKIP= hadolint, no-commit-to-branch, @@ -448,7 +450,8 @@ jobs: # NOTE: important results first. - 3.13 - 3.9 - - >- # str + # str + - >- 3.10 - 3.12 - 3.11 diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 4b61e59bf..39e1c4869 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -85,7 +85,8 @@ permissions: contents: read env: - COLOR: >- # Supposedly, pytest or coveragepy use this + # Supposedly, pytest or coveragepy use this + COLOR: >- yes FORCE_COLOR: 1 # Request colored output from CLI tools supporting it MYPY_FORCE_COLOR: 1 # MyPy's color enforcement @@ -97,7 +98,8 @@ env: PYTHONIOENCODING: utf-8 PYTHONUTF8: 1 TOX_PARALLEL_NO_SPINNER: 1 - TOX_TESTENV_PASSENV: >- # Make tox-wrapped tools see color requests + # Make tox-wrapped tools see color requests + TOX_TESTENV_PASSENV: >- COLOR FORCE_COLOR MYPY_FORCE_COLOR @@ -356,7 +358,8 @@ jobs: !cancelled() && failure() && inputs.tox-rerun-posargs != '' - run: >- # `exit 1` makes sure that the job remains red with flaky runs + # `exit 1` makes sure that the job remains red with flaky runs + run: >- python -Im tox --parallel auto diff --git a/.github/workflows/stale-actions.yaml b/.github/workflows/stale-actions.yaml index f35908f06..5957e0ebd 100644 --- a/.github/workflows/stale-actions.yaml +++ b/.github/workflows/stale-actions.yaml @@ -22,11 +22,8 @@ jobs: stale-pr-label: stale stale-issue-message: > This issue has been automatically marked as stale because it has been - open 30 days - - - with no activity. Remove stale label or comment or this issue will be - closed in 10 days + open 30 days with no activity. Remove stale label or comment or this + issue will be closed in 10 days stale-pr-message: > This PR has been automatically marked as stale because it has been open 30 days diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c2c2dfeee..e499d49a8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,6 +47,20 @@ repos: hooks: - id: gitleaks +# +# YAML Linters +# +- repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt + rev: 0.2.3 + hooks: + - id: yamlfmt + args: + - --mapping=2 + - --sequence=2 + - --offset=0 + - --width=75 + - --implicit_start + - repo: https://github.com/adrienverge/yamllint.git rev: v1.35.1 hooks: diff --git a/.yamllint b/.yamllint index 166ab4fe9..198952e2e 100644 --- a/.yamllint +++ b/.yamllint @@ -16,5 +16,6 @@ rules: false - >- true - - >- # Allow "on" key name in GHA CI/CD workflow definitions + # Allow "on" key name in GHA CI/CD workflow definitions + - >- on From 857093ea1cf16894ca790866eaf19580472c2750 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 25 Feb 2025 00:22:01 +0200 Subject: [PATCH 179/293] chore: Add Anton to codeowners as he wanna be able to approve PRs sometimes (#826) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e8de3562f..c58606bd4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @maxymvlasov @yermulnik +* @maxymvlasov @yermulnik @antonbabenko From 1d67ef3272629bfd2c6f15ecbcdf16a8ab2ae94c Mon Sep 17 00:00:00 2001 From: Anton Babenko Date: Mon, 24 Feb 2025 23:39:46 +0100 Subject: [PATCH 180/293] docs: Remove terramate from sponsors (#827) --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index f5a601a23..3ce1d798c 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,6 @@ and [contributing notes](/.github/CONTRIBUTING.md). -
- - Terramate: Automate, Orchestrate and Observe Terraform - - -Terramate is an IaC collaboration, visibility and observability platform that empowers your team to manage Terraform and OpenTofu faster and more confidently than ever before. - If you want to support the development of `pre-commit-terraform` and [many other open-source projects](https://github.com/antonbabenko/terraform-aws-devops), please become a [GitHub Sponsor](https://github.com/sponsors/antonbabenko)! From 0e42395a33dff4dee3eddffeeed727943422c312 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 25 Feb 2025 00:51:27 +0200 Subject: [PATCH 181/293] chore: Add SECURITY policy that conforms with OSSF requirements (#825) --------- Co-authored-by: George L. Yermulnik --- .github/SECURITY.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/SECURITY.md diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 000000000..61c87d55d --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,22 @@ +# Reporting a Vulnerability + +If you believe you have discovered a potential security vulnerability in this project, please report it securely. **Do not create a public GitHub issue for any security concerns.** + +## How to Report + +Send an email with a detailed description of the vulnerability, including any evidence of the disclosure, the impact, and any timelines related to the issue to: [anton@antonbabenko.com](mailto:anton@antonbabenko.com) + +## Vulnerability Disclosure Process + +- **Confidential Disclosure:** All vulnerability reports will be kept confidential until a fix is developed and verified. +- **Assessment and Response:** We aim to acknowledge any valid report within 15 business days. +- **Timelines:** After verification, we plan to have a coordinated disclosure within 60 days, though this may vary depending on the complexity of the fix. +- **Communication:** We will work directly with the vulnerability reporter to clarify details, answer questions, and discuss potential mitigations. +- **Updates:** We may provide periodic updates on the progress of the remediation of the reported vulnerability. + +## Guidelines + +- **Vulnerability Definition:** A vulnerability is any flaw or weakness in this project that can be exploited to compromise system security. +- **Disclosure Expectations:** When you report a vulnerability, please include as much detail as possible to allow us to assess its validity and scope without exposing sensitive information publicly. + +By following this process, you help us improve the security of our project while protecting users and maintainers. We appreciate your efforts to responsibly disclose vulnerabilities. From 0ab40daa2bd3e87436e7878dd0f24ea0e511181a Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 25 Feb 2025 23:53:35 +0200 Subject: [PATCH 182/293] docs: Add logo and cleanup assets (#829) --- README.md | 6 ++++-- assets/env0.png | Bin 10210 -> 0 bytes assets/infracost.png | Bin 7258 -> 0 bytes assets/pre-commit-terraform-banner.png | Bin 0 -> 69715 bytes assets/terramate.png | Bin 9372 -> 0 bytes 5 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 assets/env0.png delete mode 100644 assets/infracost.png create mode 100644 assets/pre-commit-terraform-banner.png delete mode 100644 assets/terramate.png diff --git a/README.md b/README.md index 3ce1d798c..dae873098 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,13 @@ [![StandWithUkraine Banner]](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) + +

pre-commit-terraform logo

+ [`pre-commit-terraform`](https://github.com/antonbabenko/pre-commit-terraform) provides a collection of [Git Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) for Terraform and related tools and is driven by the [pre-commit framework](https://pre-commit.com). It helps ensure that Terraform, OpenTofu, and Terragrunt configurations are kept in good shape by automatically running various checks and formatting code before committing changes to version control system. This helps maintain code quality and consistency across the project. It can be run: + * Locally and in CI * As standalone Git hooks or as a Docker image * For the entire repository or just for change-related files (e.g., local git stash, last commit, or all changes in a Pull Request) @@ -29,8 +33,6 @@ and [contributing notes](/.github/CONTRIBUTING.md). ## Sponsors - - If you want to support the development of `pre-commit-terraform` and [many other open-source projects](https://github.com/antonbabenko/terraform-aws-devops), please become a [GitHub Sponsor](https://github.com/sponsors/antonbabenko)! diff --git a/assets/env0.png b/assets/env0.png deleted file mode 100644 index da3eec1e38f1722e8a82d972f4b26d7c9b21194d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10210 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 diff --git a/assets/infracost.png b/assets/infracost.png deleted file mode 100644 index bacbd04705e81b9e75edf46831cdae97e1f91632..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7258 zcmbVR2RK{r_ZOS0)l$?5qN-{{6p7k1R@G{?Rz!k|1hJJ;RMl#%QmZXhwf9~{Q6rkF zS*x@{?V6?PpMKZ#`~RQ+zEAGG?|t9T8K3i>bCM@F+SEkvG&3JF6&2NKxW2X-<=#s< zFEG+mu3_9CF_asVhrTtQib`1N_X*HR1v^kt(E(j8ZhGG|Hi98>?$QV+oFhuw-`#_P zrlPv4=I?<(x}m&*jwokWtcu7+Q=16T)k#I<#uZ~(V-GEqi>rR17s@=)!~z-UhJ-qa zsHp<4`oky++)>^Lpual?i--BEi2R`!Mj8K}mJtE|0r7TI5z+W<5O~wr6sU#sLIJNx zD@Y;1@>hV0P-(CNL>>yg43v`vE6KFd2V@hYVO+_BW+JfKJGNa2`Hhm_Lj= zA!Se)lsgLRji+G2|6n~_aNan)3+}&A{m=5hGe8NgvGG4P{!?Gv-TyIx_tx>H(D*AL z|0x=85#WK6F+<^TK3+(ajxWVc(cf%5U|L=%gg4I10*Ax=y-}uryA0IQ`n@qw;6-Bu z(iQughs3`~p|la+C>0SoSvjzj>=h|FIScR=7+8_Ady=wpFj?6@p~g5TS9HL?K*6$7 zatam{9OZLG;om_idEq;V?KaieZX( zkbj+rYiXH!;n1!a$^za@PZJ2&(NX|I6%?f8q``l-k-szY_?i?~12vZNOjkV~+CtYlLwH{*f~<1oC$+sE8ncCjpAW;LmB- z|91rbl=pK%QI!5SdGRLHbMZ=0EcMW9?tc{BJm= zcl;jy)3hiL|Fk_6meT9IC{4{G>^2t_755xmTf@RXb1gF)@M{PD}rDYxDZB>hj zFs?;#XsC0lYeaHz(ZDp$iK03Kt)pXBC}$STDoK|28=JE#Qd;$8`=a zeMUq54{-6U^^mf4+^>4e-NJ7zCa~w%^^SswDjSv~CHB)VL-y-tK6FHi+lhH5^0Gxa zB&4_)G;*cU*`TNRnf#K9#fx!zEf2XEvIc_BkG&t8?aq&z=Rw`d zv0Facv3f#llQ{D&(IY%@=N5U`{zsQ7%~khW(CKcQGtOaK<^9FHQ-?=K0b6bN(&c%M z2c4!GTjnw!&)krkjxRQX7N`^!%swx^crKN^ve ztQZvbcIj^HD=jK;)+Mo#pG`j?a1`y-ksgXg=qtYB`jjSg#Ne||=c{Gz)@%B7We z8?17ANAUTlNL9D_xoB^9X7dC58$iZ)8pthlwZYg8=Qg%fVu>U+73tDXpI!gu;$Bu0 zLq_)lQGxb_nK}3j4&cZk%y>%O!(knh9wOmyoA5S(q9aB<8D%-Hbkan0=jV0Y%8blx z9x@w^!_Z1Fhb+`c`<#1?4>tR+mu*^I&-j<*uV4UCr-H03f0b)Kh}MR(9}p{(sW|6zFG_1{Ka*Ar{=c8 z_iySl?qE&nmnqo15mtZ2w;*%>ozK2sE&#NU852GZim7V>^5M4?fK1UhU|SshmajFv zv|l55leD{^9puoHLh9dfC^LrHm)O+BjT$4ElcLtp?BYFhy}*8Mp~1xT7#_aq9nOTdsou2k zcwsw4gt{B#eLWXCFEv*%InnaZneYP zJ=0qJIXJ9tiYP#*hoV9v7ew{v~y5;zjpwpX%A2Tb|WN*20 zLlQ77uSp|lw|<+189jS}gK5sN=SPs8s&q&eC9tD)*&*!^Yk9w+s?pPGo39_dFyN|u zSPZ9Sx#3@Y@uQ@{faVgaxyNm0;(FpG7HVtS#&qD>NTV7d``4Byf}3Byz1G`vTqJ!n zllLpdAHM8rwj=mD=(1f(C^&7tq&q-gkU2@?k6#`KJOP|kwN#&R2wWcTlQ@OGOpNyf z@L*3@xs1-gS=dwkwK}-m>r6x7b!6#W;&(R7!yqFHCoT7!t|x+M5%F))mQlE-l*;XP z`NOA7u8bf{nN$E9jq(#>$!p(TLQ5JrP6T(&h6R+xwjjsQE}N1WBe9rfqz`_1fmWZh z!KcJ>|x;tL>Vb^)B_Z7s6Lr_IfSl;v~7kctt!{)JWr%T;#nEc4(bHZshfhwnT zWsf(X``2y#$T!aL#P8J;(%B)@{5|Z|+Kn=&&zk3^(>1k=Q&oq?q8oRFf z|I900({6Q9a-KMuQkak6!&hf6)S!O?X>ZLew6GbNTc=fz8a-#JN*f+=vB>N7NGMeP zG7Sew(@IAiP*WkFacwDOC++w^T2+hg5x?h@Hvf3IG1G8TbZvZJX%uUtQ|>$S(TwB@ zP@OXh+LwP{A_`@e(9#+FW_QOpZ#lP>dLLlyARn)V>jG&DlS*wHCkLdLjVz4Hn4bI1 z+vQp6V*piE!{G}v>|mj^q?#H!487os{g?`=0a{l~1yU_$c$vKTU8VHqm2KhSU={tcWlwBI0nf6JyNVnA&->PVZjGEM;a&~BC=q&9iN|ZmQsUXoF71y= z8qAiMrcR;zX)XaA1VC&W<0eOWMp!4PMSULL?&$C|5AlL<;sa@-^}jeN*bz%vfLBu8 zG7B1ij=uZoqSM`{lRoH1v-#JFR$UzGoKlb=rciwGocY06!?+YJP&a z+`yT@zsQcBmAJ<39nr7d0rf+%LgB%H1G6#~GCy(Ih^%DzVIhnzk*}t>Kzsu^&O$ zy?gM0jU?W~6eKW66JAtq)XBscMRG>a?wma}756Tqq5eB}_T_WD{qxM9;5x!`ngUh@ zSCsEBw;Q^VLq2CJ+sw~be$GOuJ1Wk(kaxO$Kbc@_>Y?z6Ep9!Ze-Q^V$QEt(IpN4-SlSmWP*me5H+n-`yCS@r* zs?n__?sqJ=f}6^i=BDP>8et zM(JR7Q%wjF5TB^TykoWTqmIB46x1Y@7(vc(olx2r$VlObq)~;R?SS8xk0g6R_VF%=`$u78_F-K z$(5H$^GUcZQ*U!8f(nTwNt%j4dsuE5hKbiCLyz$;2lN|1-snvE?@O(MgE-Q_;R=a_ zKBTALfVgCLfZ$01?>g#QYI<>p>`Rv;(2LdkEh$SET3LAt%aVzole$_i1sbk)qldE> zT2vV7>vXplK^2hmr-Muq$G@Y8r3VxkN6xF_nsre_cI<0veyko2?li-EWF;*}Hize3 zj?phU$5LGNcdqcQr_aRb(&QTt6W}itpJdVTP7A@C-$7Fin@#7Z+xxsSG(*wmPxkh! z3P=w#GAX#_nU$|Tft<5RN;e!Ex*iR53l#5HwawZpBI#R6RAhPk`Rd93;37Of0iJy< zw)Qnsir{aXmAb#~7##+xP*lv=!;n{VR*;t_^fCsCQ=n*D*%|(1!k4D5@A1AYg2dDv z4G8!97V5-Xea*{w>#6`L^2w&?xF^};!_Q-$^XFfnxN>*)Y0rzeJnQYh)DUoLnXp2D zc78O~fBx>REpeh>eT;m(pPe3f4i@XLueN!U+c;T`kXl#!5g)F*DK8=>-{M=f=1aF(cN3!uZn zP3OUP znDLSP(N|XbHJJ|Q@bOCd`}HYDp6>E=`aX_bk&{b|_NurSQEWvEz>KT0V$o7Gf|qf; zbV1|LY%c0>?F1ufokKB{Z+ay>w@eq0e%gH)M_>J^5My4_3B3(y6jAN$t^1kpI>kCQ zTh>46E-e}0%m{Gqeq)Wf;|KAjS!2o^DN2}#Q5iG3R$_ZZGu~irSincDrSk_bRnEr> z!(9_YofrT|$toto9$Ji_?_VxzqJAgn6%YC39&)`Ey_k(cKM7x7_r2%>1!r&Jqb<=> z48DK8QWsr)~1mI=c;dkL%s%&)p!yt&R=flhU+})F00i6o|YJF zT^?{Tah7*?26R@Mg!;|lGjf|<3zJ@@7@I}~6cbMiCn+R7#8rQ~npT!=Hg%GTW`&aL z1#aG2FLa8mP@XZ&k&#Ct)3*^n1JA*nZ=6LEgl~IR0n-rOzc^)R1x~Ln@Na9JG1jfp zH)rs%m2D~?@scYh_b#M|BpfM}IBx`;2%0$lfQ=I_;k1 zE?x)@idH#GJ!@c|WplplC8wciNnfNe+%J90{9S8m{$5-KnB=1E<8`$Q95*?roeo-N zF;E4spM`PeLcwxix)~48R}QZsp0?VN!5k5hoRBYOMTyw^Bav3cD@#&#wL<8r^lKr> zK;5UQYF^JwGm2fzOy09hS~aZFQTUGYt=rvOPVPdJk>54d^?-IyFIh=3slk3#AR?4?4>`Y)eYoDJDFvGK3|3e z55}IU{XWG{!x`1{;)&ti$`NLn6z$xK-JhS9V$CYpv8Paq8xq>N+u706lX1yd;tOBt^{HQHT+ z?)}L8GN8=yg~cnP%Lff3V21`G2w*KSr>F48_rZNE8r-Nov1?X~qXK&OjSA1L3^j^f z7cr-I%+V|#&@kKCT2@*A@uEvj)8h2@h$7$Eo)X-4VuIvfJNwcVC8jt3r2-t(geTec z_0;C65}zD+q$l(48#HTFDmRilOu$u=av2PjcWZ10<&dd)T7B+}Z>$!P@3H6B8tLkF zE~)NAGI~KV1LHJGRQ?2OaXeppP!_~(n+{l+yw{BBo{O1H z1xFkWeYq*^YwT)O$t1N4aPJG_H@-Kr+dq?!1d;Tz7Gft2ex9KsAHbhhl48?;6s~3x zvGVmitK9~tE`PwsUsKl0EglUV+OIkhBKV64xX*t0m>PaO3RPrexj%i~4A5p)nY8_D zV0rxd^anNCy}{-X%uvji%wbD(Bq+UnkaTJ^H^S_-)8>Opc@Eq2MN2kSMr(m*r<(79 zdq*;s2!OyY)==`-_=tmfUNrxgmeN$EQihR^m_`1L0(*D)9n{9I-3y2#(W?R)QQD2C3^Uj>YtJE}!Pn#U9~haq>i_?yXz1-Im~dYhxDqV)Nh z4`X=^KYVnli}O`}oc|tsyN%b`H>mHXP?i+^%^1GSVda)FvY0g9#6I~TN^s#*WJDpU zTB{cne2u2e!-`#q(wr+VRC!THgWD*$)pC>p+S4RsjB5|uUQUl1;zz`j^z}kRaNTTSBXedBc8N<{ z?N}8@Sk53uc;G-Qp=S>;Bvn&$+GesUOw2-+e_!A933ts4oBI~c)?DGXI`LLex>lPH zALm2-3J20srtkD@8=PeE9@nf?)h$*4d`m`VU+T5ky7l1uv5!8q(5OsiBqz=qzC>Jm@5#9)kCGP<-8%LW?SJ^mt^a&J zR^}K7Xp1m#*ziM9`_$!hU3>Yc?epk7B8%e)N}~MqbKL$^xn|#IP_GyR@aDrzzg|YM z8SQDd4MV5hx--IhKCaMcao$jNK5@*qL}z}p`N%kLfKk>FRn2EdeH*|J_+Qw(HAm~v z-229K@4Nlwi8{Aq;zX)W-P&wnRyS5p%5cnJuWI^N&&{dW;FDmY0 zw@SK%mecgf96KH7)iwY2=X6KbkA+E}RhVq^Fuhvt8q5gJ(P1aiN*^B~MdW)uOO+Ao4N_RYHkJvodE6O`rDcWTC`|qmGJn%yfwG(7& zFnxiA+=b-=L{01rF`JEnUby#`#7DzBj$aSsGJSgy{Id5{-4xw_6?q?-e3oHIDs|kw zY-Lu%d?ltM$TB{LU0Z#$n=mgaHfP<0N`8y?`zoky-`Y)Lt`?>ci0gEJl2*c92rHO;h)jms3^8 zA0CgqHrX|NJviu2?O?gRc$&*`xZTj)c!50g(r_&moEXH~cW~_y`FxSFv&hGy+fB{P zxpmrles~3uX{LI%8Pu~{)EV~m@Q1E+^Q||_`=aN%`d-!X^#i+IaO(>}pTUbbzl+C$ z$RC>(7YD~#?vH98!#k`>L%TGauNUs>Wz>JZ#dscag-?I%NLUvBC0seW`OU~6wTUE8 z!u0uGmY&j&9~(T#jT+OzZS$%}b+@@uL~c5h z_)PWe#AiGCc#fk%z2wQp`P^s}{Cyd3WH_?+&{JLWall0zedlTMFGY2P(gNLLWa>o9 zb-TIQy>7-Y-?l4Fj6zFJo?x=bmaBZ>F}p1p?q`fC;JpvewG?vlFKl{Oqv>5r-&`|h zJm^u=o&I=bD7H?0C9e7G^w-?Y?-8mW`7AX=uq#VdaweJXM2A1ZzY^owEE!+ZBI50H zfuqfcE&G&DQ{w)&b8SPp^C-g6TWdXjZTkgS`@CPSE797(neM7VAywM+$7xdc@0eZV zLo+WwP|=!#MN9S=!~}J&>tAuGc( zln_TUc1e@NX|2T1r)xA}R-?liG`Uf1D?{a`>4=_h4t;!1%y3Nth2c{1f;XidLbud( z)0%koOLUy)5WSVBR30{Sy;)IQH#fPhQ>OWsM+3amygQR04eotY>P`)Jp%&Kh(kXP_ zbeM^s*{SL#0Yi@fvb!5V(}-KTAfZ0pu+k5BmQ&7m`erSq3F zdfCoxFTP2B8wpY#eY!3jsbMl*@`~Ztn_r&Hcb*-X;|%HwcouqPSk`}te?7l6X7;B2 zCD+Hs<-?4Ugl+Ny+oLX#2WA37*1S>dg*s`xPD$T7e*WZl9*aBtrG~6*vPEpwTV~@=ZZz8tO`DZH^b?xlB&a7XMs`FstC#43NW-;6>cV>bE~#Usj?~PG$A9g4Vtj z^bj&isNi=@#yjF%cHUDOj=483$dTIBSzB6Bs$pH1=;iXlgd3}+D%CCV`oU7c2$?KR zHob?dKdv+FKp?ayp8t^%MQ<@(P>3!AG)_{*!JSEPl=*s|)u;{M-C{hYhB=$y#cpnH zv)(-ZgCWoO$uXO!k3XLp;hDl9{PS@wu_xIdnBSgBkhH*E?y&> zn^b=C{_8&6=^pi4q80}k$RS*!@Tjl+0&^o1Dm zn7q<3g)ivSGBW{R2E=HOV}(*b|;M7X7NLDvypVrERA<>=a%LN@Y%* zalY37E&m&biRkeQ>ub#RwS_r)Z|y#NhJE)H9#;Lh9pg_pu2>mxh8-xLT)STTl_JZt z3}JKQk->Mvl!4FulFx^aR^QT|#X zH>+kDN4zUupK63HUEQ8{+wy90ymdibps~c(9L3Vpb!4ZjYM4sy;b@g;TI8Hv_oDT4 zPI-*sB2ytlgSj9-9JPAo7C#QSbOVb%qcV@AthU-__pU=LOu3L|Pvrt9!lJM07 zt|!nIYsG~HyoMXw3*v5RGj@~F5z%Srt^8sNmBiZo6wx$fRa627K|_bmLs?f=3=T<{ z-zh~M_0eiU9hewBi54eunRPXttwuB+-^h&Nk9LjZFEuF}cyMN8vCynCbnysZP9u*u zb%85|##@J1Zv{Qz@$x$gD;LfuiRL-7Fm#G%4hcCK{Qaj)5_q@tvJ<5_MyCj0D{J6j zWaMEwKPG)xeO)#N6D*a@q z?w2!G@xf7kS)b?sZMPsVl1;)l8G04lB+^x!dBEg#zsE zV3vl`hz3Sm>l9ujea>jQQK-s^6V87WHb0%~Jc?dzhbINcd_j+f<}FNeUdcgVM{%3w zE)V|?zh5Q`9s#EE64ZhgQYpP&rg?EfNdKAOSc)!t@(D&JOsQF~LCIkJy`N93=JYg! zuFdAr-ASp>4a*85B1JoF%kQ~%BzbXwjJQ+5zac$6H56E4F-UP#Izmo3Ep>yM-hD_t z#&>5)^s!hPBfWoJ;JweY5%#YFh@aeCP>gCD4{1y-8TQ78p*Qch8BO+(vn}c%@2e1w ziRu_x3CS1477*RXX|&VyL=Uzuh+VLJYbwb?%XT)N0`{Gx155=Gsui z3_RDu<&oyjP4|-T@ySUfs-y%NjilTU=H4aBu6<;j+?5Irg1LGYi1+cS4|+aQE>fqr zHACtS(c^YH9MD-mx~XhZv`RcRiCNkf2n{mXC=I`N7b6#o)g432@dYHH{?q=igT7iZ z=e~jDV#yRg`34Vs@-Z#7(!Wn#lcM>fB$q3-cEFTP!n(+>6EmqFgCmz_>J zjo2!X>Y)hdr_q0ZXYI2*F070$^yxR1T*;5B!J_Ym2|~w7^`Wq+wNj*u1QnIi6s}0< zu7sHw;i1Z>;m_A6On2Pu1#@07aAVyMv10w0hMQ=(p+b&)watC@SUgbZ{x1CHxeg?x zjlX^OawXu+35!3rvUj364~HUGs@RA*2!s7 z+)1^Js<)?NwMFlxLPxtvYB4yYlt+_Uj%S$Zn3h8PVEnH_c*!I+?nneiRTZ*35)KdT zO_3=Dy$l$I71O96pjou*Q}39c+{Y1Ohn1@6QvJvpevqH_!x+nl3sZw#v7qdM^N}I$ZgK9)pXJcpJ40AYLs>E^d1%!@YdY-`^`Z^zawR)I5=I&z*G4`Vf znf*C(1tcw3fW`XM0jeX@t95f$2v(B@46!UWl;`D`JU5_WNPlI2pSfy-jJ z!b>l28{J4t!Y`GoOyT*Nq?Ok3L2 zF!A1<0L~VL-4KrAMVRBFzU>;DXgL0R25k{#!k&#C;|^EtthHhP#M;A=uM}9=H}c6! z_ujJwb4DWGAO5H6!c+@%^1G!4nBh`Gws$hK*_lRXVl9PH7w~|XLt?l+= z??T3xHrU9MXD?zS*1Agr@#?qEXwkY)JrO&SEJqrsF8-1))ddNn(*l|sM} zltTd);^Im&;^O~QK%nkP^NJCYZhuJGq4y-8mKaN*kk`0Skwik0-;y{?iDvX#34fCI z&#MaLu|&5XwN}l~cNvbgc4(+!sfj{=L7v}ABhtzHa6unL!bU}Tyg1j?H7D@U{ArS@ zlL92gCUxCdtZzlNV~7z0m&TtL#?hPfDv!vsYeER!vnhO+>B7M$Qkct@4F)3|WN$}? z@`RXiBLen?p9jVp$DgFR&gNYw6tUOYJ&AA_Ht{o)>XUcR;1y#c5l~;(S7qn=YVo!3 z&3YV*v5+dC?ep;mT;uf&J_fvHWlRp~E7-B>9|-9}l$6BDh$lma-@G!UQ8>k+zIeBp z_%T1a7*{It>eFmd=d*dAod&I9R<3pWpqmem!xueYqG#)5X}F|q8+BP^Kw8b_Wo=K3 z@nGjN?oB0z`-z=$$8o`2z z>*U+`28ipwm7dxZ-u}M+ZR-n1$+Vs0VrZg29$UGb@r zyOj~2F|CO3H9=Q?AOK%H;>2QD3vr!Y+q6S_#prD=5LlZS759 zoXnietZ+$J3uks(;cGBKdt+066$z=oMS#DAXw4lR?f6+(TwGk3T{xI+?af%&`1tr( zSlLseGx9BiHJjZ7q+ zO>7+R{3*iN=->HvPWIN9$1yfyF|js5fT0dxRkr_F@{x>!(!VoMDloG^*j>&7%Knd$ zjuxiBmGvKcL;Z4joIf`LrvEGVKSuv^_RGOwl!5}kgsqViYI`ygLbRy$`HgLjER6Xt zKl1W%u=AOi7{EDrjZNU3>_(<=Lsm8}I2Sjki3z8nDJ!dy;UA)8Y#ba7Y>Z4$qJVH_ z3m}J;!;p`gi-Q-=&B4YC=QQSEgBzOgn83MASlM_uO^uA$3|RjV@z~x1c%^~$pSwbd zG6tg9_&B)Pjo5kNrW^)*a86!sQ#c=+ff3w@+l1SYm)p>o%YgGzl(7-Nl&w9&0GQJP zVPIy$VrOG^`2$Mf{Gv)SLbUA6f314?jgqy2qbZmmL@RG$kXM)4;^QfCtXQ#%cuTme&dvM7T55O44SpK$D?H5?$2>o-yVp+Qp8NWqs_ zjxq|8SnKFy#EhJ)X6=&@2n-@4A^OyH`rF)u&(q^{k@JaUvO`^H4#g9un-qkUSTn7P zU(cqWr&Yajt)%yev9__V@frBMWy_QL$lBTtZA8@9T zI(k|KC{%VYoFAfZ_j^diB8i)X1Pi~UAM{$AA8&nWszLJ36)02_`FqU0+gIel80euV zefAl{F5jv)_VHj}*6syG@CYS3#3t&|zgxQg^->%*4MawEiu^J5hqh~`Sf1uhtl_&1 zHW*&Y#(dF#$i+j+ok=5Cm$mj_pchGi?sn}gB@m}4!ru+J({vGIOZr|<{8tO+_iFCwqGQLn~g>Nsp#O76wjL+Qd+o2EdU4xNo{Hw>DS=JT_31oVa)>j-z z>_bjkqsHE#QY#X9M*#x-PFSFCoF|;%pO-Q-O2gBpfzod^(INMwF_K}>y`Hrugyc*I z-{h))^x|_wgWzx3%lbgM<`l|Tq(b4l86~Q6E0a6orsDV84d4<*r~PT?L&ugWL`!GK|4KxnQ{ zzD75{_#Eq{4#O)vG^$IP!@PNXXGqs1Al{t)nx?jA~8)!|Zo zct4@|QX|ceWlOaT^?#dB)epFA&gx6({o)wk4|;_2E*(23DP&P)4Amc=5XE?U?NN|D z{jD#qHpoqaVFw9pG?`dfAcXGW$Iw9y26S^ik>Kn{_$NXJaBTY6VTRCF`hVwWilZzp z#1Pn0IUfsw`P#u=lq=riXHI6D+m;d}f6S~QD-lO0mGIj|ZeJR_gC1QD0)t8H89}_& zFm5OIui0?6BXI6eg)^uEuQ_1q{B0HF%B9(R0)6R3uQQ?fMiY}hIx{~T2+SrC@b_a4 zl$Lr$hbBW8ATJNP7Jt7q87@6hd0Np$>{E_hj6P*MT+vaN%fPuM+?*GaPVe{tPv5?X<2$o1meYGp3RLp{R&UMUljW+zE{vl!l7= zMWgKpQr~!=(ji@ZdqGKFAW24+s+x|i{`Vm$LQtlF)0Pw7C%8{2h6ce9dX8H)IqWqT zR!6{+lTC_2xXNzPo0xT;X(9dZG>QP!EQPy)L)CRpZn4P>wVL4MEXI-v)%5T1(<&L0 zg^tM%4rqR2t^Ie3#9u2Z#9}kiqf1R6xr#lE#OcF39dh=@Z$~Hw2E;5yx)Q=d%`O=J zQ5Nkq-KDk&oyKC_zsEw+MIYm~N2QrJ@nZD`Qtu|E5h@Y-&b!I|y9V;drA72Ndh#Xg z?*Cw;TJRMr9te2H~}=do80+c&NODf0Ay3>M| z$%reNKqI4KsMWY+^?ODJ7V6w3G*00HG=vwy(a`XZNn|?Zza6*`y2<9hA5k0gS!a)l~j%fjI@6PNrutop5okXVjt3eh!ga@PZh)Vd8;$m z)|72zRg#nZY9IjWn)cuJUZqAUijj^k2AiG6?Q<;ceAT02pD!=Ir;;QwG%69H(a=dl zqid~yuThPI(v<4M5VPLIKuBOt3pJ`V@!rX$UKs93^Py8DZ7={x^`LkiTP?>!py zm-VHSQIh!jE#LKNt9j}bL}Fjk#&a5y2!;U@L#00!xQ|-Eo`z5~{{x#`_cOT)-x+ez zZ5@hw+m!jV>og>ZeoM}-VtE*UnvVcAha?*_l<+xywwJCl#>+dxNIg!2+A*|dfwYtQ zI9!R{zTbE_-HGx)O$oy>6~hjY0u7~y-@PoyXGj_BZ~Mz>@1e&k`qM?f7oz&t)rYx1 zpn#WF!hQ@)@t0>d!hUR{H9TP)jOe@}nSdr8?MMhgqw!<>BZhh=bpEpFz8{8uqg)dyYIm~qIM^}j5pggm{zp<9mz=|y zI^a;{ymU15)vY(Xm5Bay6m%3p!5*w`lamM|yhvO`*VQ6@Y@<2s(1jb!hE30`@}A*2 z^UdE!34eh?EKYP^UrMaknf1d*O*pTFY%m%P?#Z%e#m$OFmH$?jyD}R{{w&OB*de1g z(9k5%*VlqCyTNWNxpyrB$l;__)T37&{2ohl@8t73UnTgh59OCP*&0iT5#qb zm)^Fqf>yjWpP9#@c`o;x1JI(f4GV0mmVWmeCA|ho($;>?mOoBzI(MKyNVIuxRSoTa zdx&%gutO8bD3lp=GPLD}IOtG9|M$F@RFgyA!`Pc~)WhbKP~dEukgz{;V2&BkYR;`- z3~6bMM8e`kI`sl{SNGm906Vh@`g>M{bb?xQzk^j`{c^a!^!M@J;D{BM-9N2v-y0eG85<`ds&mKW!z z+(_M|0H?bU`ck^%Hu0Z5zYR^>g9Fe22@he?`r7>*ZPOSsHvuQLc5X4cA(lW4A*6%9 z{xca5+oSwqkc$RFr$&=ssvWzCZgiU&m0%n*ug65ERkVe!OZ}@*BssvQVF$)?OB~;5 z7!27GGe{u%1w?hJhJN~yXe8r?3(V{AcMuSiV1UgWXv+d)iPCeu%lmM(Z_(TF3o`4t z^s4A%raJnJ|FCXV3plBe9$|Pi;e`SYv!pmXP|aGc`G>w}ECU}RbQN~&_!G=O2wuoI zCTf9MrJ4^<1~p}WF}7}p_M_bkLzjf`--cnR{bA!0E&wxYb=G zAfjiwPJWuo{a*!1AW^(I2QGJ=6bBLwp~=FAt%c3+;&D*vRkj%5K1?t%FHQ)=koNtM z|2soSY+gDYGL*Wb&s(|Sc>`29WtURaq2b*&GUq>#!eer?pgTxHVUyuuU1TwKmV)>0# zv2$KbYThB|OUNmgrrgMIwRVQ07C}T*douIdNlPG`dc2*E$!IVf`O}ZMB$c!7g&kG( z)jup45iJZvkOk-siak!ZNa4gr5JI5c@Li1AaaMko)ZK$fVrB;j;7EeuOyxeZ zC{ICZAWlBC7z$DacP(nz>np|55fKw-*Vdj)EwO`YrF0X*7zTS`=j^m0%(XfTI8S@#7u~%cEz{h&MJil=SqX!ose_#l<}=o2c}LO-rE^A7L9u zC+w%L-s)I(OJ_o!bkm-8DN0P{edHD{ij(D(3`b%?UUdgv{4Bc=<0RLySK3Kz{zWhR zYeyU(Mo54N*we%IQ)j|V?SByyL+~;p!obuNlRFj9yP3LG9~vW#vFl1%!6c527SQVZ z>z5}H2}w?EEvbo#Nnvqud-Jc}=J}n4_P5j_)u^Oyo=!mr$unDJDvAj4<%vA^;1;a2 z=$H+`+AVw;Cn?uGrYJ9RxvTy>e8`J-gM*_ZT1tGDA3tw0DCT*wfTD+*{fdYfHgCG3 zqmxs1NeNy+K!9DHc1VX|w27N8Um0dTk)$XmdC-^b3E|Ad#Nd&F$BX~5zChlFs%t1m zB9TT5;#%(h8?rf`2<%#;XvrL9TG5BngmizidSrZ_O%!sp1%`zm8j`WSM+Gs~=o}p- zV%BX83BEzyw&lEvRM-$R)_9R~Z~8m81X2hUh?~Cg_vZ&826)2EtgT^!5?)lb1j(}YbQO?;u~VL9{bBLycvH_L`cMgsI%3zyxp zZzEdReM3-Ibh6F52cg55RX*UF+C6zemT;fiaJY7wQ6}?h_YuxzC>VIR+?jx2V+l<# z_urB;%E%T&3iU#0Fn<30xl_}8hIe*$7Sgq7;gGD-Lz{D}5Uv81Su&TUI8 zt7O++P7aQML6Hke?`EF`n@s4?y5q@VnNLIj)(u(~mMEM@$OwsCWlk=x?1F;eRXPG? zMU?)yXkajmSiie1t1okotBL0#NG8pGg-Csc&`C>&-e>!}lH6_tmlFTP^L@~%S2yGdE2*(R0piYvZ2tPX1z3QF=o-Av{=SpbEB*!EL&}2DvggD^ z!$+tbU4nz7!v2v?KNUY`F^8<_m}y6V7Al?A9%ugeowDdR-sPw-0`ql91)2CTkSrGRos&aMQY$a?EbPIO8@DtD;!a~6p zHy$B2wxs@EC|1<_^mgN@>kAJ#$sWtv1pmw5w8Eh1B;i+ebaX(B@Lhcs1T8Erl@b@f z5=qJDe_OygtFe(XC@5$jUEZdK0t^&z{-*Aj10fLdxl~&=HYj9YiZ~m*9SKrrN=O<; z$BBk0+W%8>O!JQ-w!jlaOG|W8Qd0KUm}O!EbsC%(MjnfF6g??Wdh+B-Kr19&G4ZHL z3>ze2jRb;gK}y=%Z@oka5I0aRk)kxH8Wv;{Qj1<#sXS>4Lq-2I6~Y@I8(n@B67OM$X86;taXY3^#%jo6ywlB_0Ng@CBW6(!RkV=oe z#c}B)!Cgu~is(qfTLC-}oeZx#^1AKOy(ump*$C~}2paVXHTOVK`|(_gYY)HvUE&UU z^a@WEmK7GZJfikO=4{ggOjZ!OV^L(Uh3yv`OZaW763e|Q!$gQcw2SUiKLE9@Rcbh= zKcdD)z&96nT@}k1A3Ts1cnp}jHARKLjJa8b;7swWVMI>JWDX>d4u*3-#x~jPk~rv^ z7f(%xcwqyoDv9ma*eFPfJ4+NELOAYq^%XR<)lz!>%2-;r1aeFh=OJzJ-F>T`1(|Q8 z{}6$uN#56T63N_-S6|iBe$|M^5zGxT??n<~(M5htddS5?v01QkK8~+c>Kk zb#`0A63*rlLDpVR9+5L{b_G@YZP&qS+l<-LUW8p+B8t@GZeu5CNzMG$mQDSPws|u8uuA`& ze)=7{;yvG9qPCH{0`9h220TB}rhJzcEF_RUQfeEkqhr2dIp#MR9rG<;#ni7-!G{Z##Avr&5{WTqr?W@DReg5S1ehOX|H?xBXtOni|^)%5$mBl!}S z^mbw2IEVAW{2M#8vjw9EBkPW-pLp~XwJghb13LC*-RB0YHh2Ud52hLz*$PAPC0IXM z1P@XLb&hu{!iQIObF{U{TiCy^0n(!##-sJEt))Vll*;Y(4- zP;m_Xis5fSSA z{QT)AMmStjR21#N-Ai$huw)yevf{A3{{wn0=+mANCpl=`1VPa;1JJ-5fYQDD_eBj1 zXu`t6lvGswKYjXi6)U6qYg0d$LZdDVIEYwww5n>6Mb5oz;4oFII$i@$dW%QRgl4v9 z3&}oMX(+;<34d68Uh*x6(|6IFCw=90s(!FHNp>onop{ZJ*HN>1VOr7a9o@3FV z?S>GXnfXPlzWN^dc;$Xm+G+fjCQ1E;Wy;5jx67w?yl?YEe|iD9#0(Ja-3Hs!)Pxg- z=!75F4pkKm#%f*^Z((rVAjiervZ9DzRgLXZjU7xN&+Ded7(6_*a_S#+@NnTr(ga&Hh#Q0JbqgY`(s6#)31kT!s&`wf05v=j8c>2jNZu?U$!$7y)E_S z;de&O9>Z^DBnlT;wxi3dd=0rJ9StKw+eF^DF(74-iT^d2`n$wgw#wSR1yBJsGkR?SCx42GT$z9b^R`vr~%k6eX zAEv}*rSRe406gwws?VLeU|%=C>pFjYT7@56FZ&Q7xOV8iw7i^+M1oR-xUi_Gbs$}! z++|z$+m~kQXV0EZXHB?s4cxM<1wM6hc8;8KodPrB5)#D9CRnC@`@cO55~Fc#(gjIb z9~1(BF)$}y*DX`C`qf*33k>CRaqQFOvE5X&l784{#hNVtZpwPcRZ!FJzEf4W`52;WxWq)GuE^VyRaUo-zR!hwogQUZR1gAZn6aO?EXK<+!H)(GQ#)tVH!#p<&e>+o z-ELahx_Q{Px=%xiUoiKCOXLlnS5>ZthDPX{HxEXE7rcK@<#GI7(#xxf1Y>}k!l`oG zwtD`Zrf{7$GfIdtd#h!YrA3ajr?Eyx;@u7Es+SNUuN;)!xJa#_Gv1HfRlhf1FTT~Jzax}12ep9(paQ79?lP6DDQ~T|BDS?tC1@r)BYN(ZzaA_nsywL&~0Bp>FmDFDGX4%B`@D6`K zJ^>R$aw=cp+t@F89jbXTK0a=E{C$qsbq5A0a@G8vz1oIJ1Gh%P!lAoCecW)Ag59vI676g>{ZMy3E>CqNYMLhRTRff~cpZlPw8C>nPm@fo? zDJdx#t{6)ome;h}k%wvTkc}VBvMNMELb5_+=an(tU^zt^Dq-*5#J@m0AxMdfS-H|5 zR4AHu=q|-Eg9cnyVA($%Y1Q6X<)l0RNO)9fRHa)7-(`=!;@ zvO-F|ejyNmatVn58v#`Fm3-??Q03s@03rg$EQ3G^3<|ruOdl&=f+;BK)oE4Ml;G^O z(Hxf3vVF{wtagiW;ufR$7>>SFE&*5@rEvcA{S71FntEp&YA0K^J`Ha@DwXQCj!zey z8w@zftAzb~m%;nf^kYl1_~PUc|E(&nMN5P%#Kyr;&(`ylzBeJv`w4?i14 zUdKS!oFa?r+ReQ9k$JLk!-sdfsaWL6RxgVrV$)cz*?PHQa8-`6k`fmp?S!t`G z*i9+@=`&s3(nEt$_NIyo`wkQLYW6!&?3Tq{^TlhRddCkI_c$7zu9znK=UD)ERj-x3 zy*(uN6G_RdZ{EC7($Yc&rU5uVXfZtAbg&uQ4aC!F&BIrhla?SA8i&?XDD0@;;oV(O zr1Yx1_M_v48BTnoh!`O*CqaoRlxu$rq)L0Ery|EI;zvZaK$Ym|^mG`A zRrvIpihxz~>D;#6w@J2=D{syzs|;QE(@Dw5MD_HjUjzpml&jA>@&hnmfj8?t7<_Yk z*54ofshXM*%GvFgu*u2E?EvWp-{F;Y!>WDU%h&@wzbtUKMz9zWX(*~fP!Ux(Q)hcaasM9Sa%`6hme~$Z<10{ zerRcdfFbjCyii=sRRnSMB(8PUe&NVEs9#9v+_S;h5UeyGxVZ8#S-=V<#&)URK^@Z@e*$tS0z zh#48($r)aYH5p^b^z=u$SXvM6(>Li~Lql**D}y@i2! z-s+omf9x3!5ORNAw}gNKHOd~+W~<#lmkGvz>&h*7PSi5vqemIY9e+UtPe z=-U)WnT?D>CZI|Hx=?$vvs0oV+Wp~aAv!Y3F0}7mH0WevLcg)O`TS(ZfKtSp666MO z@*wAC5(?=a=jN}wyi6q5U%YrRG%*nj&Xv?}3NjEPTCT}#N>$#(BLg(QP_Vl&rl!|1 zocl;wPp^4xV`^-L4P@kkku$5JqeV}6oi^f*nx;OcJ3DB5d%LCpj1u2!P=^_@H;O|7 z(0#Jp2n$FJ201W0I~xrPpYmfJr5@sQYI1Vx)=Ui`pd?=4B_w=y@C5!ah7aeimJkm= zf7s{Gy6}=TMFz3#p+@)zNYvsW#|>ti3Q3d*h#+RSj)NbuEG<4PvhK<45ef3=CB*S{rov z@z!oB)3!=8Do##LngR>)YPzlGXQ#@b7xSh%4vG%Cet$J8H1?8#_;;Emd?l7gr)RYe ztN8Bj?)L99P!0=}{js-K>)H9B^Wdo8+3xQ1t&Acsxy-@DDg<)WOQrWd0A)bU5n=<3 zWsUyr!8aC{6ZNE?JsW$YMglJog!lq~(XO>K7>1NE^%jKro z1r{kQyCE(v&PpBuTwHT1x$Z2tp4~dTIy&c`xs_ErX#XX8>M#@a!@;xq_F(oU!G#E=sz7~DK)%#MoHT(oS zL9fu};)V#-ZhYb{$lze-=6><|^=o=8u4~t>?Tloe6M+~>t*OcY+X7KaCF~jU{kx5N ziK+x`%rmZ(G0c#9dvi<6Yf;o9QV0ZGF+(ua<+B^sqbMp#kaea#G|M!46$@@gM4yg)gu?08n!TLejy3 z`q#oYECa>?J==0N^7N!kPfte~YsR)Y0<|Vf(?RUGhOwOmVuSb4sQF7& zb~X(XiOj01B4$pMqkwfEX)mXoTRxIiCIa2INo#IgeEgjJ{HqNv+np4)jeb0zU9UWf zq!a~YZ`z@p>dJmyUMRosigUy8%uIMf0?EO_0Z3~g#8{FB6H@PPRFeU&1)80;J{Q8e z?(6i8hvQnPHVhydsYi`vYAJ==UFMu$e;#gv661QprWiIQuN`E&`NDggok;qvXHrVa zr=H;CAC8G?PDXrbsuL4ih@&r_ShCe0Rzb3e!69PjtTZFl{W0jL&L*< zSU0Eu;HqDL09W-kJ;!x&u4C#2Wyp?u6Dc+IBQ>>KfJN*qQeTi3JkIPaH|qMNx?Q^5 z%C8Fwc9*(u&Ng}60szUcm?<3bis7;8+#6H)UJ}2Z&(<_o38jX${(As3 z1&ZmK2jl>UOZA!~7Zwaa1p%+?*AS!{bgO#F?{_cpr)n%6pm3-F?tJfcTK&vJ4cpJp z4=s()!L<9R^yh4+VR@SOcaU{aeM0Z?oZ)-k`>9{%Pj(l3H4o#g1Yl5XeMd*4C@LW_ zD=S88Zi==AyYbE5D(z?!vI?g5WqXS&sUVM|s2FOeJYPI-g6JH}_;P3&mD+F0$<5ui zY!6R4j=U}KB1+_(ck@Ba=s{SAA+V+xFd8aAfJ?*ekm{%UMaRpa9;&w}-+cS_?FUrP z50sZI8hS;`r%fKmAQ7iktGH!b=_pQRYLpc^$-1}z^acNO2P>p(qF6y!qra(t+*S!| zJwobEhOk!{u=@N^wz%tRy35W=s_kn9D)sJd{NVOlmu)IdEiH#@ZCXDM>@-TVFd~}#^QQ`-yVt$*g zyUFl4;{)iY+MVnyAi0;_k;y8V)SwgqPOE#Jj^$}HHvpbhkqf?_-rif*#d*~p)I#oo zD=Vgb{rwh!w_s6w`U2<30qg}1t5KiP#`bd+j0AKA707jU8Xo5r z3SpXsZ{@tkAuS7bEjBeq`uY%%Bc^$KQThc$vgD1tI!h{{{-@50m=m=alS$Jnu6E8O zM|Ds|&)S;}`0;JgiRvA6j_9${e6gX6Y!Y%$>&b$iu{U3xWdqlX!_T`hMv`unOWP+q zTIxmq2-k&f$P+Z_HZS${_QAi5zWDI%8 z!FO)BEhHp_0s`-&%FG3DDFhlp+7oEAqxeIH=^cGn5F8ZmKozmNzp%U51p*TAKVR-# zCZ{Y!3IECz<{RL$FQx8K0mTB)kylW#D9%x#DD34^=ghN>15&&|zgW?2A! zY7xMQ%U#lFM0PC*slN+!2Ws&V%s5s1$CYzx&o-KFQ&6;n_ytx+qWu673NFoD=S(?R z4$=x;v8pl=NC&_^^WWzhP#6w_symUzPq*R*x06y(fO4v1Xb39& zW9eq&`OnSiu(Hts>y<$ORco7@?SO@#E^a^tUZSo0_GV*R{ax}1P(&}R^rxcOGs=g< z^?soP43zP39^W`GI<_kMX_meEdD%X@p@E{tX0B~#hgFa&%DGuJ_3mHy7oZ|bWY$4_ z?|oRTw@^~^SxiPoM)Q5|FCHh8mUID^p{XgHhPhI_S@`JpkB9tYr^HcR1~oIgI}75t zczEDG2e{!58j3VCJIQbOo~o4fI!U>3k}GH_ByXPTZCJ^bv~_cTtW1NSWH5kh$pt&t zZpeszW{0a_#z?i+#^95|4E0TZ7T^#dHM(qnmIRQzva*6CJ7)Aczx8Gt0<4cBZ4M3& zYu~AlZ(j$E>Y8{v811H;nsX22T9jO1ZdP`7^vuD6_?g~r5k`bu z(akZyn^@V{{00UTJx>;bfuH87ZZp+rs|9q1E8wbI{Djw}J*z3uL7b~y;03p%?0tsXyYCf997J^YfPPe zCFHj@9`U6sqC5kT&}oeH{GNd2#scl0fKhJoYD5&n-MelOiQ)iH-7530h`BB<>hS(rd_WXOs)E^k^^UwmFR81X zH@X-^@dvb|#1O5isY$cTI=fxNq2%C} zN6F9f`m53XrtN^qe)hTW0!dm^E2B9BZ+dxPy4Skpjn>v&odXt0xFm(~M@kHB>&|6a ze!HUd=*UR?*+#dU03HA%*ss2-we{dl(D&5(PY96pK_Pc?vKaOJ>jxY}bPQukLL$~! z$@N(qo|Kf7q0!O6^73+Ii;5+KBiuC2bkn#9joE1??(=8e#*6b)*W)>ts(8=MI(r5V zU&dbc&aSTE$;ptTBj+Uf*OFJDR`GX>sEY&cfODWII;eWj7pTwCY#VnmWo2cNikt4G zJDS~Rszg`8E^1wOX#rdSmqM;u7ws7o*$X=`a&xJVj*p`*=LdyPakt-}+DupbCs#U* z?d^YV@&L4W`q~vPxS9iEDg)tk{R!v+W(j zyA}A90RXB2X3Y&VmISN{g=p*`F%a=%M<*9+xlu}*nkh$u#aH+@`o3$`*kqTN69BVY zoNlo&oY^NMO7UkYBBW+(pI-suE)E`F1OoDAH1~bNeuk3~ak}(V{_V;!%b}q4t={{$ zCO9G{Dh8hHPXprr{PYL`Xy0o*=CZ@e49$TDVdzm^0l>-tXAO*vFAGUfrKXUuPOdf9 z3@wo!UVy3=r4f+Wu^Z;z&mVbPJ@02s&dS#<0MAE&qS$7()(?<4BtJ`HwRGuV?DP$A zsf;S?L3?25WY>t7GSbbdM@4}At&>*ImNQWB71K&G(9dlCIaY=wegnGERvMUZOk$tEx4YrHZRAuR%c!lGQ>_JR>P7>Cj#LC3f!U z-dfp>Q&3HUtxS~a1WZ|XQlIZ&QS#Z-ZPux59OL8TfBlp%x3;n2KeMG<>D?}7v9$kV zXk_F{@z}NEv9QO&KP0ZRBZ3@~x#2Bz^hfjlbSj@4A+&zHQVo2oFQ z=17eB{R$=&9o6&Py^4v2#p7v5>+nua$mr{<$HJ&aa^-im#^eC9jh2EA!`2BspNP&* zDbPiBi}+rp*w7aVEYMn_Rb#i@Lk#-Id0dc*P4bHTpoz^fOmUIZZyb$hM>D9bTuJ6U z(~#l8O$rNa0sYqZ7v~<}z8zeuvP`<_mHDOj5nl&w8o=>Edk^?Hs;O6uIIExw11-1^yml$?K+=KB$90=d2y%Xec(uxYxYYZBJB3rOshBIx{os zhR83and>54ESgLt+6~TlAoTnI|97-Af^R4CPmY0F0$kgGYm3ICX{&uzX5npBilp3C zo(PVbOwQE)TKnZ|z(hWm|!xw<&26uQ2L-AcT zRQ%e@();7W1pWl@hN#(%U*^_<+}blVFRue{QkW#g8hpWa&Rmc^ZnV87H9&0`L)6` zo#4&#{o398XL@KcxZF4p^lCm4DlY-N8v4MS)T6oQN;rGh^2}Q4?>|qtnHni!nf;1y zM51Lo%%HZz4?IrRhvxx}pgYd$KM|(wiO9Lz`43z>CZZQ#(QIt?!p+Yg_^O@8b=j7Y+pjVaH8?-r528x}9 z%0c0F(8-$cEPm66h5(n2pc?+Vx7-WzzBC2p)LTxOGgoJ4k}sb7c%bhq{BtcA+0@Q+ znjF<(xVE;|0v^0!V`Y6uW0Cm7!OYBTch2RD0caS2{-ODFr7$mVh{NgtczQ} ze@u~x2uV2VVJgs@KLO=w;q9LHWC8MFIr^Z%Wj2r|3PfnaoN~V>0%F<`ehc*1qEJcQ zA^;!s=6QY21p)Cc8FQhCQB7ua#=^whnP@&I_W9APx6{K|X5Di}t2yZ0vbJ^)z=mn3%pwPJHi+A|$?yj3w7$WFES?yaUP%G%Moy-T^K<>l!0_}wk zy*n143wr$|y2YX^@#s-kc7@IK6r0IFQB{Q2l>s zIuCHH`}h4z2uVVrkgV)gS&LM1C{5g{am5b{4i z&-ed3j*jQ(d5#|4_ve1UuW_EQ>$)o|{mzcKy}$DCL^mu9DYvgJKEv1YPxqV6&2*(n zf57eh_|bf0N;jz2+eR-WVmG_oe@|RqzTQUBzkY@dpVv+%ku2zNjsKQQ^(`#wrdHg$ zv>oFlICUcF=BqhTjC-?~R2+&G8p%k3MJc<6J1EXL@p) zyytiH9-qd0ZA;g8mffP^cib(vAXK}jCvI(K=tYl-^|ib=mkb5xEvmfqSCrpwJpJr1 zwCLksZ$${3L0jv?#3GVXhK7`>b5Don(I!0HRq5&w-eOla0*#?5QP5<>yY}+*ZO4b7 z%!iM2>~WCTA1&?kixnX*f0pNTl!7550J3jp&_5aUJr7(Xs^TW)u5@o&%+1<09Zu!tOE-(~hy9xJUjJ4YL?ODE<`)fL z4tECKnF=4H`TK6u!oMnU+{Ns_rl-y4e}*qNSWnkiyMH77>0SFrCcOCuK82C@#b-Yb z1y*}44gL)*5TwNMbf4NioJoM!?GWfjTXSs`RD*+Wb!2TUlp$Es*PP*)_; zbGR{OC1noCY}H=sW%dP6EsX`2<8u59#VQHMq3Lxci{=suv|7GkSGY^KA_xK4#>_M~ z{n+%Eb>-M~cbW@V8SbSG?}7P)tV&!SpcR{SPY+4f++dDY^H`iX{ki(`d9!T^iB@js zxO$x{Mi;vkx~Eq1C9MosR-~9|K5lA#k>yx^or-o&a!h=X1=1btlCW0fIW_b@O{nJ4w6e}{`J>C1(IrBz zM9tA+{qHx^Pbl*PpTy6d7(;F%4lIIF{FN(T4g7?b{k>$r)E(9D;y?C0pZ!Nh^?1AT z+~LK`U|K_GwWkQ-_ms2{hnymll z#pRiv3yehOTZjp|jqr5u8)_~h1DziQ`nU$;fCYk z>>y+y)HYH@MFv6PXms#7zx(>NWLKPsxA!goMuR@C3K|Idi@(1cIQlqmZlcU3yi9mKOEdBj0OI`#9Ghfe@~hEb zi6t|e0S-D#49Zzh2!hr>4U6Hb72Q~J*<7rvt9w>yF)g3yXePKlX|dtRItdpS*Xnd( zsUEN(d}8gX#*+^oCL*t`Elxs7O*2$*&g~5DKVMf}%|euWMaCC(9BV%QdBb{M|C3)z zA^l}Ph3Q3@f3FgOu>xe_4QV z6kOL|zj5w!DJC(TK7ESwbpo@3$;iE(qdjY8A0|nq7q-0Zh70$;V^Y`i8L$tDwJK6m z;LspdenZlcsAYAPjLTKTOUhhK>bsg;5;LQwWLPhgka!oJaG~uVsJKhZ%fd1rawV6v zw6w}#G2AW5%)OF#~rMBn;QsuOj!slbp^BlQWWGsl{D2j+cg%+N!&-XGiW>r@1Lvf-8 zzlv}w0iXi`e`zD8sL$A9Cl2TbB9f`lK6rMxtN6WZtW5H@vxmr_r4hLn{?EpbQwksH z*Z;V+-VnKR?9@4T{^au-|MBFoGli{xLH`7*LcEW_z=rqpIN)g0!BBuK`Iw?f8_sp{ z{N=J4NK8nEkUi(GYG2a=Rk%tben^|8`YWR2(>Qk3aPKmrL!-GlSr? zpN`O|EZ?W0*bbP4c6FWSxLjK_CDiL;xXQ@YAVpaF?b5 zfs))sj$SjWHHXb6L(9LgI{{4)As03u{HJ$3-=lCqxZvq}^5qLYB5MIMPm@f_4Ho^% zKl>jURO%Xku%KyD{J49zzdxZeSbXsJna!_ww>Ya#%a#Dk--`^+EbB3n8;jiwkh@u63-wU2NEt}8wxO%<{rFPQDeC-zpVozSIS&WX z0(tz~SbMfNS#b7_3 ztIl0aYux1pDUy6MsjqSR=T8-geJJ4T)2isScV(40vebT-;28Ae>%+}&_&Y6l`}Xak zA4A|C`9wr;6YBq*auv<1+yxXd;Kac+oqS^ooGCQIj+V9lx@xCRc}%=!0g*Tc!+L<% z-Brz!A*P+sKoA&h%;HVTo5;{fBRPsmKGo{6_aF8OM?5UiC`ewZen98o~ z#ki+Z(sCD97GSS&kqL4ma6aT<7J?^^qN;{QN8K4Q=kwO-{4CLG-xhpPxhZ(^70d@z2^Y;Qbmj_{AM@hy?94IQ-E=Lh^$aO>k zxXPz?|FXcPBXOiHRJ-sh;YEndeooH&?I6b7f+*rK@?smI6`GHn zJGLgs9+Qhc3{b)KR=bWVGmZ3X#e*|tS642v9`~kESZ&%33&<9VAJ})r1I+qexBFSV z%S^Qe^Wp9H3=hY2bZ7v0Bwrj;cIw4jM>O8}<=iG9xdF#rf;>mg$?@(?B9fOgRM@vfTY=buO_#$Ajpz!VG0z}0clAQ?CBO=G^{ndxaTlYMU=cNst2 ze!YFHr}Ci1V_IT#!q(PSXZaDgh8POcnJV33@w@u~{eFOgROfl<(?zbklolc;Iw9`( zk4RbF)y4GD-~sf@iY{O2pK-7K^4x36;G?(d&RxIq0*i}^Xz)Ubnr;C#$>@t4+s!_W z7Zft#sR8}{7xd?At$d{5>4Oc0vOa~m%2nphj#L`Re%t6d@4LiouS}_s&lnQBJbUUx z<4QWD=kmQ`AU)h)`j#O^Xyu(X=4r9l`9=K z{onejy8T8s36%nQ()aHY|Hn_C#7~^aFxp}aaueFu45K18Jv}<;Z+?)nx3{-iVKA&? z!t!)$+%#5KzH2@!oODAoPa^wc$d6V*)piy7Vxm0A$jC_8YuDjIAz^IrM)B>U-Q4=x zWTwZ~ziS$@JbuQ}YTF@nAm%C^e$~;jhm^5E;NC4O8F~3=lTxd1sKW>i2F5m1v5}Q# z%zdb*NK<95pCnO0Xw;~BdrN|WNa}yu(%%HaFOL3Jf@Yq3xJKeO$<)*3C8yf@aSsUO z2cNKp|DR;@<8Vq(r%DnTAN3H@Af}n9eC9T$C%{E3a$JX}(IgVIFdCNCr!nATU&f4^ zJ)f_zNfor(d=QE^^_?+^_uEgiQaYox!pmbCI1^W|kRmrCt)rfH?QH55gbkwGChCDl z#oBOj^{lM!Oiw!#)xe`id`(uFZlb_N2@eaGo2V$RUcIWUtp8X%yxEp;9&j0a=SP}I zQ^c*+P=XLVE2=P@AY^XLH@!@Ile`P)8l-ld{5AyZ1yuI8@7$sOeLC{eFQ7kYXu+Ov z96o%0U`cL>;vT)^v0G2H0AgF&<*kx6#%p$c1Gilv+uu{0{?h*XU1dZ7e#n zT%6f!cXAE_+Ui34!7#;}{aJ-WE=0`=6Pyj{6e&^TzTxN(?#Xgs=|r?&ob2}a_L3a^ zlgW>DjxdoBcLx&_liUXnzDLuDdn9p8om9PGX>cvccPmAk*JZfD@*YFsfPfRLN=C|? zb6~3pa1xoMT~}c<6q+FA9Y20rM|v6s=quDacOvTFxZS~j(-v@U7?3#_kHQ*E$?@?a zcqaspf^-dKo4@_s(fxPvUvYkIQRY2|t=OSnSHUuRM%Ox5UdKL>HhL#05_lwbp!WOftG_wA7-SlRM;f z_Y%|0j&NGGm%uMjj_~Y|czx~&Do{cxBtE&9VLP7(tVw`i;@**R?)-V8+yD8gN|$n? zbNrHifzj$4x4`&!r_S@EWeC{XTp@y_!Dgf_Gx6_BJ*NZx{yn=mD00Z1|4ioFuC5TZTr*?$2%}M=M21@w=dia~ z4An4!N#9Q;!1#Tq4a{A1Nf$=Ukdr0dz7m;Y6sjxXD`hAIiB$Vb?Th*blN5h_8!}a& zRklcy0;F?=jrr}cKjNpqEfx&-J0HWFMLUCkqCWzuh~M`+SI`v&q)yTuFK+%O7WjAS zuw1~ZyIB(t==l$nQ0ZYb!H9fh_YQdbTBfd()X}4%$f_^L7Szv40jMKduj6xdTtCR% zNHIbOZwL49=hylJh!v~<*5^E6(Q0(CfA=HE6lrNbGT(Uer_#72m%%wrB`oVPj)czOEd(>xBm^^zRrTxkTfR5|u^kF{gbR zqz_0b$z;w_{43!o-CZtwwzd3LEHDC}Q%rf}I;=DY#WlAuCK;%9k^XR6zrwmkL zFeujy?QvmqWq9mk#l`uxfVhcZM>9ybM)OB*4H|C*!smp7QMdT^IG?a^SgYOLy0)RA zAyo1&@nhaWTH4wi-fBfIP8twg#wP~1T}4Mnr?Wmqq$q-fVM>%CpVa;Zs36vbu|LBn z=WpLE+kDPOo~o_qWI8|cqWtzqX#SD}<4kkM&FW35RfS=49fC#jmK_%{<;)Y!Whk!A40g7N&WTsihO zQy~WLmV=o_lU5=c;uPc@gv}dYF6q|h9Fp9ZDd)#&bf0_1keme9xVpL;?@!gtj0Jb- zZfYt4l~Uz8)RAO0Ol#s(p3CdZ^r^JhM!@e#M6^#g#yc=WH+ zq2(iZV-SSOmo8lj*5~KB>|Q4H zFLPqj`*65#Dd~cZyM_i80vIp1&uazij$dxS^9P(&wX}AYKYhA@PL1#nl^Y)$jWHY_ zot%p(DiS&>B{hb}v~wxMVdeFPe4cTkK4!RJ!jj>7Z!IcpaT%qkv)F?&0nM&&X2zdN z!obq}@N_D72dhCf|G+l`W8<5ypDOd@4s^6tvU{13AmIm#+;hobxv3=0n#7pq$V`F5 zpqwScCSR&f{sIAN9u}Q?js>rmFGh7=ppQY9Uh5F7DB#DRGzpYg5%-wzp^#K4_x>&L zIu898xh%J)##}mR>a)Fy+Hm#dQJMwCIxiAl*oa{`$13a63HU^HR&JeH9DTYp`=Fu0v3WVT5 zJaRb>uLbUJ@vpWUZ1N=SaQiDn(=22Yxu-h@20R`nF`$4efw%IY|Jvjc^9x7!6E{Sv zacvjEJz+*dJJL{6E(%6dbtSn z@t&_=?;!YU>5Q5>5y9zXWc-A%@{0p)eHZQT?ayfalk4r`{PyP8BGUFf`^A;{)!1?w zFMb`6obnvfGmkrBcHyb{A@01Fj+*{LW(R*9Yh%`-p_N0utP4WO^!&+l-H z_SaxO%Aa(w9guo>KSYTPIqbD{_D-L2M^I0Xd- zxjdZ%-Ie5Y34MOPdAI(VGd%A|ge9f4Iw%(=r@dr}sSk)#K^tQSAysgn5f@H6^L9}V zR6a~tE)3BHDV_W7`RD0s5xVG+rKvvD&#>z?J3HAt6?S3x0Sz^_cCgfjPnI%ul@m)c z2&x@HK=@Up4sSbC{FnXFYSM>MF{646%3bEnJYQz&KHbj`qRXy_!$%_glb!wHqo)A1 zD{|1i82TCOTU{mB;eA_pJ?(d$d@uKbGonkbI zLqvS$e%Hryxfg{i$PzNtg(7J>lQ~K^$6qtCAZG)$X2G5JynNZ?&rh@LAYFkkC544M zQEP@{0;|bJeMVlYQTaUoTKA$HN^oMD1l`#U+1DlsTtfW7>(`alNz@F3 z#_RH>W-mZ_lwYLBz65+M$W!!s2(5y+);r_0lRoOwhHr-l(u7HutiGwFJW-7y6U{t& z!jJ}bM0UT>XtP!QN)TcO=Uw&A>(;X0e+CTI&`T)RP0%#kfHfr&lFAfs(nX1yluIW6{>OzR|CWpI=Ag0*eZ#?EW3~@lM2KD?xXGq>B+jao z$k-T1A)E|^vwLqQ$Ygu8XZ+>5xeg!R0T%it!Y(~&ots_z9{FjEIRLM0ffth`FS)?H zmztcIWP!kk8?$whkK1v!;nLgox1y9fJ(0YT6%-khO(9JdeLSHj#q0ck zHWfE6=dP6PxT|NBb@xG5)~nCeWTV?6u`7i*c*@o5n(hKoi5&O@5CH@Jtxe*cLRwPS z$mY28>)O8!KLRa=R_!F(J}FIGTwLtC^rMG#vdXll{L?H^fMUc4{jY$-kWf3z(c$WC zB+!mp5wmz?e&KDRh+e=Q1g=m_8M6^U-gDt7!RaH;IFb>EuL_oB40)aL2Bm$)J3-T%sOY=s9%|5-j&;aW@I;QNK!4q1A!X zz%V>AqzAt7P1VN+phuDWBy>`1R1<$=__p>Vmrg;U} z)o-8%SzYSWMWeX*e)7^kD=wA^6jm5rd9k(GxMf1Aa`+@9a`^pEi0Q+R6@so;6PB3k z0ykwLb?gfWm##(uM+ak_!0Vx0hH@9S#vL&BPg5!N`?nzjl&&nRG|#tzYPKIWP?=L7 z?;*viv}=M5i+Giw&$#%XKj^tY^o*>sUg|&UpBpTS2U(9#OpGGI5V{hYB_5fCQ)+5- z5i0u}eN3rjYU6&}Ro7_nF}?&E7fL2yfHW+9d4dL%pTLJ?x7wK2i+Or{g!;7d_3CZ_ z#K4EnDAQXA5N$p|FA%GRu;(a$xr9?b@YmP+sFISB&==vpncEdTJjCHQ68bJ&{)D5? zQhJvWroLWVu_P)JbSi>>*icmwEI4tlp#Nz6wr>b*T9iZKK^*B^6f937zCM$16}Mc1 z(+FizQ`1ba(jLf4nyK|Pq_gJir33@5qQ>zZwZhVK>wj1v9Ggu>VmrAoQkxwr@wU*z=xt2csinK-8`-;7hR zmWa7Dhy#s@&1R8z5m`Mjgb8WLRASr`nl}0k^8J+j7X>fX1wud}LS5i)5|^)aEdRkU zmX`H7+p5+)sjt(K$-wSVzC3Gm2d*`j(TS3q+9rIAA1YlBoB#|6e?&BPhud)z1Qf7K z0M;$N$tgVUv!m0(ow86={ax8XWY@EIpNf3# z!v2f(mAO-x%PMC*`(4LCAdv`fFmw(}t9= z4=?1p0X7_fGI{#`6OI#Au$%}K?lvMr(@>Rx|Cfyus&C%B!O^|_E{NCcMf{IfP)GJX z7Vizixgs8S$J0w<%mgVMv^f~mO8md&<>lmiHw23Xg>FKAL@lugqv25>X#^6fA>gcj znF|ab-u&gGtoqmPvY@I0_ZbCI>jo7uX6-c>)^=i29%Vk=uP=$Tl@W6`B$%ddMtg2( zV0U)~WoXAH>EOp9w~H6GKC~N<1}|QDmq;lhoaa_xBtRhFV=)R?jFI6`D+g&~GBZdG zQsm3@4BFW1gc88?&Ln>BC^HS@?A?TuO8jvhN=Z@gw-V#_+c1{oR~0S7}iqN1wk_+x?>KNKRHZh98qy6Q_h z`R~Yzo1E{eywWqvgUzj5`8#w=Zrir8ZS&Qk6j+dxTG3>GHWH%zi>c7G`Wq+f&EuQT z|Ky*rSbxkElFr_eQ|8wmvWK(`1(s8T+@t_0F~oqTFz*dr>Oo(AupIfBf%o5)awIA< zqI-ZOc}P$`G5EJ6;DV+PA54Q!>S!OR!u;!hBs<2!YKz*EJ8o0;W}Yl6%AXE1#8v@< zEJW|BakW~(!@YlSkeDN;558NF(1LbT&hNKF4{y$aaoPZ|i2!eyX2&ZME)xYWhGk4+ z^QuM#rs_0Nz(I=U}uE0zvtY zx>B-d=E9Y3!y^1PN)vBpmZ{IAq{Qu3OuPRF<-iBnSCM_vce@4ffFKia@A%vTgQC4O zX!GKYa0Scr#W3<=iz2tgkyeAsyQ>~5;K5k_ep`#|I2Lh{Q9pKQ;z@EWySDBfu(8no z>dl+IR+&0OD#6;s?KCb-G}2hb%*c&jd~7SMQ}z<@5o}WFEJKXG6Z#My4AdkG_rs2z zH&`Q7h#{-2v53LTT}i?Oz8iHioc0jp>!vRoyklwU>8co1%IUe#xdM%tD7i&o;w~zm zI9obskK7vfhk?$ASM)HIUGWDJAc$!C9>1H^j@*@nV1!qy>+HTyfm7FE&NYSX z@c6srlBH_`7MtH|0m{~+YXdXbHZWJWNc(RsbBhICHCC6!k%x(B zYezhU2WX zJCBYRemicu_$8MiZAy-g>QvA0uQuJSfvC)_^K^zMmF8J9O{-hF!%{B9MkqhjIkvEM zii=e?Fy&>U!k^KmHCtcab4{(%`?>S7OjVl)zqx~DJb~dX&Dukfjr$+M-O3F7>(qJf z&FBg(JcWviW5sv!95%U3lm1&kF?2pNyRy=|$2mr-aPjno^f$PE>fkB$o$AAzR z=IaPQa@S}NxD|x+LMVG&r|xi%tMX!xl-%LM>vgNZA2lk+Pl=k{ThlPj_3R2#B+sC_sI%hp zXN$V^U$x4mxn4zVYud1mP?LAu=w4$fV-dG9+lP+Z`i$B3Q6oiz^vqDt3-XS&hDC0cS!id7kj_Fg;>Qj+5iL-O-syyr>D0B%i%0hp z2@Nb5my_L2zfCOtBbe_V#nKPkf|h>hXXgZ#U4P>&k##FX3o{U{kj>7*d=b*5T7S41 z6L2E)1#K)1Ydgw2$}{zXSK8HCa2F7vg>PVm>E}BC-4wj5~~tKkf*vV6(AHS`Eqw zzk2lg`Y`S2?}Jx+24ti4IQHjvjV1evl83oe9@{QP^PD+m>Kn(eYsZ7Tw$`XEck}Lv zR*}6YyAzS% z`0~&OT5IaJ6Ki{|-TL?lMIALHm8QQ09$FSg2<_D>R%NdIPq{`_aoAF7)IA7QXH9Lb z)Z5L&osz#WkcL#ly~7a3$wJePgD;#b&b{8l!Ph4gg%6wRPO(upmXN#y$3HY=)-fy& zClN~FMJF;}w~Km#17Se5SW9Jx!r4s)Moy$M4MbIDHW z3>lO9R#puz=FRAapACzDe~PO`L~C+k4TaR@iQ5lMgZxS8@AUUNw3v67@{_&(N`P>L+I`+xUnA0 zcfPj}*5iGdHF@hPk>a^VXxj^uUhNj%mK}Qtjwe#P)nAV>(aq-zXAY|Q*WFUd*%`)k zGDM4!k>z-5&h7gU`)=%zi)T6U{i=GULPE*%NX4x#T9zCc&UyyP#BUVtpj{g}j4h&1?pXj@6g`!_(pG0TVzQwMHx z#*o<{i4$^;OcLB2(3^VDr^Lg|1AbA3DFIC4)?`m|6ql0|TG|<3-i=YX(1-m6w_VE^ ztX`XCIE}^X>7u9A%;?6mgcd5uGd`%nP)_KVertceszR}+rnB|208{OO9Xoa`AbXPSe~{P^ z)Ea~`4TL?>X;xg|DM|~Vf)7QAKhXCWCVY6>I>cx&7anlJy0hB=(!HoC)8l*zjROw{ zoU{9;B-4s`&kxV7j6pKQOjz94d?SIfpsm0gk7W%fgZ`};85`SyYm3{y7fvs77g#jx1kXYFRtxSIgch()XU+&=K6H;4ojSR}525xiBGB|7a69u{<~AlSCYkMes;PEoL=AqQ+#j|rTal5i zQTto()>Kn-RP%hz*wL8LdSNNY*kHTd>toVCztl288ha`8x}QyA>$M5iYL=B@nik@m zGP%k_Mzb_OWMg)yK2Bun z{;rd|`iBPOcsYX~YA3jhZ+&n6-k_*L5e>xG;@!RDDNUOg*|G8Wm*u+JfXj^*LpfyF zYmP0XiGys@;Eg@j|MpF(;A8}c2wUEioMK8&PZv&U6YblTx%m4xv7?ME|D^06$~BiT z^x75xj^Q;*fr zz=U4vib;VHOd$Hd3OrXU5nMml`59)3Yp(e9UpPt_&G>tD1VjgOH&pS9rm^MQ z*RMGksd>m&_O078qrEirJlK)=)x?0UrSQy+B%S|_@}BCgS(L*fn*i{TR7)RH(snOc zYX3;WvEW($NPeC0qcCa0l0FQZg;>Rb)nv*x8X_HsRd#s9(tPOpSXY000SMU3Xf{6b@Vpw_Pil3&*pLZ^Dn zj;6gjrJv5?{3VUVZt}LRSb}T}X9y{y4YSpw$76mr{=WNkOQXl8@;q)YuhT`hy(*#D zZS`RojYnbb639%Y2+Qc57C-9-E*vHLNk~Fr=NdY&J#b;Pbtk%JO1Xxq{R-lDAS)0` z^$pI8c?~gqFOhC?bGdlJsJj>O+EG8fD_?giTKc$cLEw2!jVum_(0Cex0imy7*w^@# zE#yI@Rp!o{?Za}|g6&}&pRkV=zA#>h z;eMw7sg}^Qsg%+O9@&ga(;{Dh)Ix*bhP?;zCA-XOA(lkpO4`tP9Jy zD}VC4_hT!|QSllJ5qJU)N2MPP&L9?Gp2@-fMcndGx#LcFINTd&YuXO|B3BMJGe{GK zx*c4kQ)M3-P>F*7otEkyogLKF1vPBli;#kxG+A0aSmCIV*UR-S#VjuGLO}<}6w{v~ zGW+7>FasWey%Dfi9xP{Uzy9~HKQwh~^#`%JLMYV`Ea7Q{x7zJ#d)_%835;+B$02Qz^%sBwxBhMJ^e@euSvO5eEIM`94@Xb) z$4Y8MZJTq2=KE!1hR$W)H0IuO5!+sLFF#eX(5G5$zR-AVTem6m@~Ooq1^hcESa-cx zxO{_$T~3#6m2qmHL#Y#+pGboVmn|)qWuTn2Lgb`WyVw|22oqyw^^3#0+p}=M3g*RV zLIC4oS6-Jgi#;<1a3Eq818xNmOA{z|;>vr78E$0=1US3KLuI`hFp@vI~cC$b(=37 zEz#u7U#dk_@;Sr+w0f2i&!nt zeJw99lU~u2Wec=3<2;rfy5zzsU)ehw>tMEzwOy&$_^|XHe!llLwAgY)+$;@^z34lr zHswvUO2_=Ls&X6PX$Yd`z0@wsH$M0D9QwdGRWk_;gIIEm{z2(d=V{~E&=_o05qeZ^ z?v!E$FCfJ#;KaCH&E)ml1&xgdv8Rb;mvaX>8eecWCy(w=^qQRb{(WSmDFn}sWjEQ| z#+VKk%v4Je)2P@>t|>bhnyqNDCtd^|6^!7WzZEYv@$?oZ7>PP62VQ2~f$bn-dff55 z&~^2tMvTb4h5@m=N39b5G6i#m+ef zcLN$2^Fw@fr@U%z--#h3ce+B~>i=##^UWsUGgzy@sbRdAiK*E^!m-ZZNqnfpNFc01 z9vXpGLx!Un*v}laeyC9PNAtSc7yjpOUTd4XJ3bwVSvko68b3nHd+V1;D5)3?mY!)( z>5P`4$R?&}E>)fN6wO@r`U4sJ-SDue#@2aeid0EgbH$kI`+K!%nmI79f}pAv6cp5{ zDbwp#g8TB$r>}Yr2o{V;fe+fAeFuyL_7dgR585M#8qWwMez!^mwPbTm z-Jh6kJ$S#~jc7_+44$N$CtH|*DJHUcYPZ$b^p>C6jB?#KCRsj`Byzek>24erVYbt! z{dJe)SKyIbg6Pt&x%Mf$YxwS7C6A;fCvTlbqlDdT=KGfDN5!$i6NgY;0{;|4`-w%j zeX@a2fzjaPz46n^sCfGH2#-8Fi>|G0_II@(Q$~!fXV@Neawl#(CML$QWNSWmONA3L zM|&tJg4pfmU2D1Tyr+x;P<(@n2dTs6p>uVNy?Xn$ElKz+{R#VH>6gCO&U)9zI%YVt z<0I5-O)9ytoc6@m8ZbPHmpHbYguCz8yqu`sy;nUR zCzTFeO@5Phq`1D`l3q0lz9e3n(Dy`XJ>?4~hVnCD?>}Gi`Pi0gP=Kk29Z{{E7l-#X zi=pJyeWY^MX-#Hpt+oF0zIBa1UlZRS=omhK?i?WoqC(e7N<5eH+-?2uUjqYsdL+C< zrV?A1jW2F||Lls`W)K^?^nd4QtNVkk}n12s`8KLM{eL!@7IwoTT_(0J#@di)&7x( zVp(r0oub@b>YEcj;iT*3>7=09YZY7CohB_ir8m zf`owt6yQJ*6P_L3g=XR4!FJ4E#O5VKpy&|MziC9G-h=^r{7jxHn+FoS402x#{)}lJ zc#fihj&cMQlO;WtuHB^b0a`hUy!R zLuZ`Kg-zg?h8|bFvv}7@_M~%RNr{D4VtU5ZRdvlP4?bhN-Td@`Al6+a$Ue}?*ZSe7 z_Uq+9|126c+!NX3#|^&@Vi^NSY2~=rnp}c{I}zNeWbd)2cT$Iu!b7II#?_aXU*EA< z`aq|O8K)Uz5AHMXg$Ya_gzUx6>^g(Rmy2=~6ckvwY>;uyK*PYoBF(0-t4R_!_P?YG?_5HH|7y}%pnd8vzv=`dYJ^tW-* zzUS+2IIQGTD0VBSNDXd9{n?hA6`Q`Wy`{kI!5_O<{m&`g+%cS$py}B8c(>g`WHhZpzfD5~oH(G<9V?i3oucQg*0CaHo-*F1d4 ziB#*+Qj1b~>qK5@sczP;gs7$`7#GPla44AM?uOn%);KI0!G03J&Ev<;XQmc(rzVSx`(`THhg_mnz-7k56 zzhjo2mvzna{oW)7?cobL;mh2qei^4uoeGboIke-^fHRsN)vQyNg;#+zVzZag?;~f6 zjm^maddXULQuA`!f4L-8XEhgj{Ave##iv<7!gT*mUy`Ly$wk4BnrEj{SjBUnyjR=A z_j+MTKk(F+7BZ`bfCxxpn3Cb(uNd`DDrkAnf^DHf`{EO_ez;46d39uwd-L|~P0U7) zjM(&E-y>6qBSA?K66f#LFvVJHQ9|~~;?AUYo`F(<(K)$2M$;B_GBx|l6O6?=^nGnh z{54h?79x2m=5^kT{;RgtrXlND&0r{b!~5g3z}?);J;CIRjJ@RB^m&<8a>ccGQ!Fgy zi_29@+$qRrahH>-%4c?FHPCzYSLg`b%<|F3y@oPa=85N)kYXUzg$F@-Mqw%^3oC#D zuQ{#L-n7M{QV_sA1)`^q?r%l)gsbh4{ow9--{tAVcaN^|yR(Ly3h^#+sZVf2n8)6| zR!mqr_K_uwNpGw-Ze7*PXBz4Py+Cy2Cq6kybRo zL)??~NMib;~SxIa5~4-zslB84Q@ne#Y((hq3% z#;3;3R6X)?1z?o*ZH!D7Hgbl}a`3`=GwU=4XsE zS}@F~q@zQt%^NS|FdraeBgV`<>y#6P`+@0}(CPFrkj$9QN4darncw zw#PR2nZBI!eI0gxv?*VYBe>+f#%lh{51!Jy&(e-JCFG`D-5rtj_Kadry=vL_Vu>nM zNM!9KG<3V#-wjWw&Q$NIdoF*&hJ!RvCt_q$U^?`)>dHRyu%9$mU^k%9WlJdPtlU@5 z#{6IM=PU_UT4F1pTz7uhT#BO^nnKEG|Gsa`6r)DY=JjE)9)@}X$&9eeN-%?}S6|A3|q1*rw1N%GghUvWe5AHPlT5)k( z3zcYV$Ik`dd5p=K?d!ijdJVCV`^?!$2L9fjb%MRcnt=CLgD3s|H()sQDY)b3W61ny z^6>#tQ@LZ!Bk7mY;kj`9!shG28C5<;WocG5tlL`s867Z)fd(Sw+@OfN0L|v9vyo$g z*WN@;^5iZaF`)GlV zN9p&@HV*E+!pJB=cZ*Rg%@xlnzT??X-r1D>nH>)1NpZ6azArpJN3N)IZghl}i&}Pa zb}t5J^XBR9Zucn9OOKTe?2S>IzWUwB>G1e5?neZIug=ZcA!Gk^=)LRph}S!)#5Aas zf#_u$Xx|}*Mf%eUGKR+%9t_Yu^p_+5V#qk-SU>ptD?`^Bo>)o>i-xQs^qAo}>!;m9 z?`Mtga}x5yrK+9RD8|(fp@4>ThicZOK6%2%BDF%uCJo4MYlyvfuIRiJ6fgg>TnNn>BX!sV?lS!S zNT$!ot~1}n7nx{fIp$~iTh(NJ{*b!;mI?P=H7x|Rc~;U4I`&Oz(v=pW%7d4)>YAvd zm1LMK?8v5%)HLz69EjY*-(hjP+1HxHb+Bz=ew6Q_+PUl1*LhVEFViqjf5Ue81W;xtJII)T+RUTJwtRq#!fR!v|y*qMaNFLMbVG z$}I*{|0!jJOP{qXyT?5l*JN`YV15+^P50u?3x-Me6-dC{I2E#5L`(u8Lf05qE~BAm zDw61n8^y>tHCVO%#&yN$nmt~}dqbRf=3MWL*;JU{h1#_~&4W=QOaY|6h%!|;WaL9E z3dgh%o+>amaV*U3>Cwk*m_42M>v>Vhjw$7a1jBq+tHSmoTNU&JYLQD~Y=ten- z(}__h}tG)ZT=#WNV+O_>oRK(+?(PLT*Te(@L^{!n<>Yi|sn^E$zZ_g{9 z*vEICI?S$h>bo&LYC?fTfjk4vKwZ|Vsprku(oi03=|&yL|H!yds^tVtc=P=FCXUsN z@op}bEQ6J3<3h`-ZE6c(Ig7`Y-% zB1boWbN+k2{^~={58j{e^!guJI(LluCR2}wcvjiN=m+H8`T}of?_LOPUV0_OZpqwC z@WSjVkMMZ|9_cBrpVh6#OV>!$R)<8q-whw6OT9^-K1tgc-Bhl5KLp56|ks!dP& zFKS%pXQNvQ(ae9T`{=18b>`8IpO)R-U{84rbHr)5X0ql!?GO3hJJMeedO7aeZ>?v_bKl-fK+XW9X zi&U39&w&5awOux5RI#HYe>J-W+dUADXWpO7iDmjBa`yMpx0Ym=m3Y||`RJ5PX1E{T zBz>gd+@5sO?}<#s(Se{`fu;w)Lw3A;O2CqBAv?K#IA5`UFPii0(4CxWS7qj9K0f21 zEeVq55L?q9(auIv<2m0RZ1+>85a`>d&p7_pUZg_cxK|)mNTxD?a-TFkQSatXiU9NkTlK=#Xg6xrjv{nZ!>9 z%lK*#jvsqGbz=LRD_Cap_}?q|X0$`6CD1j@hh<^^9KJ?_a+ONxRMZclF0;}#BY!fS zZb&OQy~Ge5P{~S{Axr~Z$AY3=c`J1hmWL}Edz|IFJlhjRru{P}G4M~W>G4?C@Bzco z7YW+Zl%eNJk8I@jFt_d4SY0c%^;J80Awc0^wJHVQfY9~90qv!c65V+@G6Qi68tIIa z?+!LE9$T0g0d+_`p3!w2~=l9(;$M}q0=j`tMyTj!FmY1UP(VHw8~2?B*bT znHR$}f2nJ1ybq&k#7vw+s&!d?kBd8qY$E}lsWO$!ghEQxbxS?Hh2+3XS0!S!jboupy5^L=>I$}xB>}>;AEi@Y7gnPZ@AoWb0*Nt^nj-IaM7{AkZlqmv!(ZKW!qCU*C__YrQ>8MWN;Q z)?GTSlDA0^-=%qzC>du|{ps~IcOy;=$>=3$65|>G5XxB?y2`OT#JaCM&X)PmAysOO z6cRIikW`MXf5ufpJkYpOE0xk-(d(xAbMxH! zEfdxKtT*JF|G)DA-=uTlD7AN_abXyw9D(~2CUOytF+(pJxZ|XmX()K=PcYMTCbebQ zGQ++Sn$gaW7m%=g&BMsJ^R0M#@VGiPjL@z<1>I?{S9!M7BzVTXZzkDWXl}lSM|efp z2j#IdxWdBQhHT#VG%DXA40SZu7t{~Phffj@QD`#4tRy#H<~c%JQ4exJts z?LddYNa1@Vs?V`4VxFpc`kUafx7sod8dvmyyCU&>to?S>hzPN}7K!0B$TsM%U{?|o ze9(6A)6l+#4hrpCQh(nb@$s_<#1VWrzV%-dlul$!ep_67;gKf9a98>cu+xMW&tjcj z9*~N#1yXpZvVXbAI7P8bOn99$V3X-)OkbYIi1-1@v7rk6*V?L2HlJ^IJT4?=YBZz# z?bD-M>)$6i)#T`QU1pxyJ<`)EF4rJ;o5hD&u6vu@!fV5Pxv)B#ec3UeKR5LMFyeb$ zVfRV=xPXsY_L0Z*!NW#9mLEy(r>EnKQQtaPqOh1T~?C z!IFk!yLrA*yk;`*+3IJnR$Kd{(Mo!fV;Sa2KE^ohrm6`C>1N9F$5_aD?`ptKxuK|7 zS-%gX70Q_*4wyjTjn^=UA-={Sav-+n;%rG`t<_0^x2JgxH4h(|zrWABe&&kj+UpAc zJ1yHX0i%qfDKHpwh)}BzVd4uXqRE^0MoeBt6t4#_)^LTRCOvuz zp3B$*eE)0-ZJn$4$E48(?DQHe0INSk9%(oom2KLEf~zWtqjYOOn*T+I{e8LHZm%4F zGW&=vDL|Of<#WdYQ4X66SGw+-8e5rKXH4#)b|>Yr8a}f7+~%V68J2GfjEv=m?^$l% z=-Hp&a#4Cp(JF#nv%>C|PZq5lmD+xf+Yh<=+R4?fFo7NgPT zWf0mI6*9=jFjBMG-PAa2>tWq?P zl^Ll>Mt1hjN@QjeNk$1#LZt{Hq)25akD0>ny!-y1;nhD+k3OIGxbN$}u5+F19F|CH z2iAv!(Fb{9oB2?POXy{91V!`<@7yaP_r)U-I9_g7y(=w@QirJdE04UaFfb;9h-cru z%=(QY7f-{=+6mUE5kVm81N4>uCz25{aLv~mwhdG zjZ_($KO=G@T8p}#|LYfsEHF_uIwckEl?}~V?eD@iQf#1}O!you|8|I6_p+<;*k%(p z67RZC`f`lT&n=iAuJ_U}Ycd@8*mmSZG)+_hsq7g&-ntZ3Uu{j3SF|^I6g#YJ_XiZH z935`|L?yLPikzF%?30(Ag(jbhx-qAeB|A$^u+e#ilpDK4UCzDgd`@2AawSrW>F9u` z3qOJ?>SGkSL5xd1W*6`>BAcc5?-yh1F9HruAr@Mdyhm%P`l-c1Gc&VSv%sKGN%C*L4181&3j?m1sOVmZxSvPvk<|-#kE~#|5s}# z9IBY=z(aURDs;C;yF(0#Li47ssyDW>;E5xZ6{JkgU0TeEoqcR)X+EcT1EMe4RaK{) zRtvOWbb}fJN+furXl++zX=^xM?p9h^;w8;L86If;5%Svb6SA?o6^RxMUP=%=TjcDI zrLAa*CCgt?-`PAswMD9a(VjB7=A)2+?Q;@pN`4PFE4uIHM$?KDYW-VxJtz_Lw;5Yq z_22LMm12Dnv;m!6J4U1iWCM?|9{KV6n?Qam`5Xx!kKY#4eVg}vx3ayo@qmu@V$$Y& zJD7IFjO93nZXXcOI~dL?`PeGx$rWbJqTgG#Zslj*d?=^$g+n`tVt17mj)K-ilR^x(`ZK^*7S>%#lIFsJLzhCe^Pqt0sQ-#KV@w)=3Qi;t z_{&xw$NLb<9v~QKl?iXjPI#G)-$EWngug3dkN|~d8{XE%l-SGr_aeElu;86A+TH*( z;TZsw?Yave^kD&9;-}R3r-`bWUAH;3iL92So<}69D~H4a2pl?`P-xMrd8p|$fys$K zZoqIzqI~?(duWg8=toW7@IZY+fCn$Me=(i%GaW6!t@s5~APE)ypbQsr<`~W+9^C8> z<4vr3ZK^nOoT9$t!;k1AQql~KNln*Y?CuIYm(u6sC1U-+H@hv3%GPHaF@H6*!KLx3 z!ahi~)8ppA2ld7uS?cGRADqZwx4qXF&%jR^)xX2>Qn}S&x#E|bJF-re&-p*nJh`n? zo$q1>s9A7l3m%R#>HP?M5o>ET89P@vR*JK<-F8u*f@Bj)UpDb{ZNq#6NF$onrQkXKfNtRCr-Ip5U9Q@*{gNUg z{>G(A?`qFO8ikykyb$BPh6nad6Fd_TLv=PM?z*Xs)C4Et4)FrRUt`I~p1#_guy11N zv5{cwxhwB(oV5!~W=m6_pHNj0dyu7H5q@Zkr%`DJzrvAFDiY3Ct6rw3O*}_BSJ$Xp zevKx2klcB~7pXpW!?J3x)HgB>U9!>~?Yy9#drj@;`^p!cxF3`_=wA)>aOIBMdv)`z zU7rf_^WP2)VSftVJzxiZG#XCviTW91a2q%$lhQk#e)vH>n6#L6LHY%VE`i_|fw*)I zn!EecFC7*8F=cv$_dRGpr0XYy9LeB4KMwq{|%_KO`xAu&2Cnh+P} zTMbu7>_OU%q?h&T3t7?nF9o(zfXQ>dY9R65E%d7ysI#Eu4fG;WonC&<7n(-_IR33~ z!HN!40Gj&xLdC2<2jxQ8%SW-+nt6<&ZylK>FCA^%yRD_#>e~Ct0~|*EJERWQWz#Tl zwlJqWA`P?RR&^5Pj5=4@b|X|J=FZxf^NP@67oTJ^syl^^1+j6-+nAZ2ZYw%-$NQ{& zHP6GG+>iX(&v*9EoqSOj-8H`ai{K6uY(4F~E2p*?Id9})>Qv`dJXlNMH-N$i5Mt^X z+sjw4R59fRHA?+^M<5tWjMt-K#j*1_T!?0ugeDuv< zl|uoVU;%*f9ju*a?7B}q;~|&?&>)W=R|Oh`tHIGw?~T`l_k<2HSPt-Wi2JJw~bD;uw8pJQJYb*9VMr8{A4J~thiW%{quWXbY zIgome4Z9Z7K2$jKGe&? zUDF~X?Ea**+y@0X5Bb${JbB5R9~$`H?e*4F1Nz{%wc&RH1ZN(yQiw@s+*mgF%+4#v z)ZAvC|JL&oo6kkFpulahwtJq7>eWh|cyU?saG|A)&D8P1_~_M~tqQN`EN-gtg^1*< zE&mvNv4DbxxG0o`V3*z%Dr??c**f|ZIwIho3EF7&JIZ6q<=Rk5IC?e^V8lFqQlck_ zSGu~ogq4YE(t8JF;v>+A@xTP3wpAYpy%+g?SfG($J&DU6I?92QY z>~&d*@)zyp_?W;^1&BP2U^vLQyM*k)-doqW5lsp8qt*LYI0-#(*vSR?q-fRKgZhan zFAfL2Si34vqd>+YKH;=eTpv9h9pTZU;QRZL!2ZX&!${V^a844BMDz_>)A&l#Y>bp3 zPhB-){S~>QY#&Fk-5&`Pn8j5fN+vgdr%!Y0NzUrJKW8b-U76y%U-FWW^rPp_iggKm ziofhRm!EL8JkK)E6=>i0K%j_Xx$d!aVN;avS2 zElH<0m6_E)12+&{0_>t1u~*_nNhJQ@{lFAN$2YTk!ep+<_y@H-*P%>NhzV>OIfzY3Z2Xu&8za!zM#0I# zSoz)4&1y?xpcsO-@h7hKr-73T{!4zo#)@B!!WLpEo6LX> zAt8VOmOB!GGmB3^GDQ3&RH87>l5$cwT8$q%Ok}%R)K`09?0(L{L0jhLBMfwym30(G zA8cuH<^*Vtk%ROHx5hjxNx|R15=3Ye$J-dXC@RthXQsI5FtuSXIhdFjSz>qd8A>#f$|)ENKBIT}_v z^&Lrpeq1IIAy#bXmq#Wy1|vEACYrvyea$KOamt^U{I=FXK4C$2XM4s^$!|IK05oC^ z-6*0MjOj0yC#%CFgRQ7 zxNUr{_kkl5)mc;+(Ekvk7>WmVl^n`4Opo52+4YFxxxFcFA6O0sRFZz50Ban&JpGPl z%ih!0N;tbv{R$|RA(G;R10yx4&+babi`%PfzMxzl*SE6dJw}=+b|FO(>7y#}O_;yt z+JVvs=~nF&(%Yg|zmUB&f`Oo2b2y*{1KxOtSuOH^((&$!mwFIvNjqlu>Oj<}0yNLh z7P1-cu1|YU4J|(mkn2odg@gbgRBUFP9F_We4_BXkaPX@)O4-7b#ZkhpabGGhCw(z_ zU1z?70nDLYyFjW=6qh7GeWdi89l7s1uWRuVRw{@WPXrF6j$MNQ4neC_y?j5q`aAO! zJ%HAtwu|3Y=wz4Oxx+N9z|Cu}DqD40yK5G;^x8kNy|bF;So{io^I2-2AIW#s$y!}V z=qbFnRSu=CZ1mU8-g^{NX}WS|rv2>}xjT1K3rWJn6HBaL#+Qt<>m*flyjJXZZDdzJ z<$ZmuHf`Tj%DxQtf^wx8t9>sXY!^4$cZ7kAFHV+UZm520EywG!+fIsTwe!QPrJepI zn{E%Bs=7P*(UmpkcY~+NTFv$wp1(($wb+d+gQE72hMd1gWqM*QJe#*U!GF)My7%j? zWa8!tn;uuZU9+yTUKfEW>DrVO7yB-CIhwLs zrI*4a8~cJ~sQu!aN8AgEC784)yE~y~`iyB_H7~DUX|GA;pl||D9Vw)4mYXF+BLcvk z+PQb{Ua>|ExlHLsIo7>3KdObO*?qIK?_MJ*Xz&ip-01Z8I^)T}NyENh{N;jUhi6kf znf_+YQ>xlo1+Hv%4Gt~K8_V325^hgbINusDg}Cg{kUW1sr(oZAscaVJ?Flp=^CTR7 zWm!`{zEfX?-c-z~{Vqif@f;pBA(izA=8;2U&=iH}cqJr@W2{=|2 zgsHZ4^tieza2nRS?ZyyL?u_x-62gRj!$K*zU7Fr))j9ODVH`Q^r#Z_aq&$ycC~ zaoHz;0li<9{KO~SYZ-`C#AF{14av!Ojc{v2zbR&)OJE`+W&xWq*J8IgqBq(BZ5cvJ z3rrZ9i8_pUA*=vkj!Jajo#C0<64 zEyMF%ztS6jy}_qnc(&e|^W=$n;?7f`?B)Nx_mE~`9CL_FysxfQI$QG@u9k5FiSU=F z6CGbPM*0~C+&oY*?3A)N|19dI0=voW{tMYh`5T~WQ&^B^`}jlEEDzl#Vzw2O68Uc@ zFJowt5VNc?p}q0|?%0t~l7(HDo}upm>1#rW4^x_pf2F=rUxtqi;oMhV?%Y+-1L)~= znc0>bt+E#)1zip(@7NMsW>`)J_5K_7@%g5Ggt#u~b`^|jJx?AM7ZcVV$I=w;6pSSo zKDqtIw#2An6WS{$X8j&y+B_Uw0dI^!x9~jDmobMre-8a@LY{G-Ya)V<=C>Xgd zC*7+8haj}UQB~OZi+!6?+gcFqa)m*A_s_LTm0hPx!t7ZS{^wc%vHhHa`x(){Lg$vb zVblbjjK!<>nS7$b=7Rh{TynC)g<03~z7rSE=!?4+!9NtT&@JSCEz;d^vLmS8AW$Kb z-2KY8?fnw63v7Hv`&E}2;=K9&>(fRHEEE^gf5~23i*lJgE)qoO$itGUX50pB75Hb|78_w4%N@UbY5DAMzjHu%^c-xDbd11Cr=R~Ej&77OSygUts+!j z;P*{0C%JOHi1B+LyvtB*-r4wGt}AsJz;u^4gn4#_YU7IuP3l`CpqZN z0WYm1e%c$Cz9IZ^haS61D{&Pa_3VJ2o#c}hGFWm}JgC(fRB|OWARzvsvdo+0E2g5t zgE8&yj_{rQo=H!_p5~?OWjdf#`OD1Mc!!zyoI2#ywcNd6;Y+x<5%vU#R14C5j_)c! zsKI>l2qp^9b4l<$3XH!O6+@wW(d(s0GQFn!1BZh!Mqj~bLiX%w9Pz8>8>_5D)ZHPw zd!fe7!9mQ-V}ugIAIaZH^tCZvG?KaTTbLlh!;Rf){(x0+8(uxixRLthkC8t=80>yg z2NlfRyu9-3UC$;j8&S9nUFVM{(;?G8cuH`!HpFQuRp|H1Ew4xJY#7(8$dyYY+{_Kqwj(WB^s%KyKdRlA|=4F=LqwwKfRYhNrf4`+hFC%Mp zT|(%a%io?E#8Nj4EcDTyqmGq%B6oq@oCLgqMkq%IL$HbzV2Nzfv)7M~`_6t;BBW=a zg?wYePD}J$l`$bO57?}5-TBVKPvdS+tm9@SEl-P;$L_i5qRTDLE~p|}la zTfLSx#(u8bo4?o-L*83veq;Nt)6eB3c;LfD82n(Vsj5&luUIteewfcqBz&d`&m*nb zYx>#{kHxeQ?|}mczB!X94OYpd=%Vb9?wG?Ir>$Ju^|@cs=ni8-bfr~gotRQnS6S-x zbyq)e-}cRp67vS-{GT^^V+iFQ$ok{}9VnJe{cf6MT>r>xSpuT(N+$K5d%iLE9$Yc$ z*>j;+H~CuNd1Xs~Q3+4|gW9CmUu-+5Z)vUIe_(9?k0!rQO~HrXjN5aC$kBZavUpWD z-tPJO)2Z;dI}DR$3&WusPeKhcRSuq+;tk^8VzZm4Hf7rzS36$bsz<_UFWnMVGQ&+b zE{KX%+cl0nh&i?zB;R;$D822F6Ce&^66enY9v^?(y+gM(==q8do;zVdc|&=X8WA?~ zr`N8C0L)T3zQ2En=eb1g%aXY?7R*~hc4~a$i_owAr%n+k$R9z~($XFSG2K4?m+#5b zIXE~7L81}J<0kade_qH2%!LC7|Ij-QV04l z!-42Ty@C0sAK<&CU&>rPF)jMIQBfYY3-Y2Bl@{p~q zt?sB7E)tejFp7fG!eV%z@BS8Ds2yT&@+_41cTb-`8|JEc;V^s!Fas}n<;oSy?`N?@ zq0dAp4iNzkvqjx3Bzt30F*!nb-x1N8ummc2xBSyv8EUNu^={T1esizXY@rtq@R-ot z$;C=St==#vH#E~6e&hS-ts5Mj#(VuO4o>k#r*pOG?K)5@&b>(CG;^NnPj0W$8*$yt z?~5YGwwTW}hwm^oAD)SJG>vDFN@32m6|{Jx%u~ItOs`WsBz5*E16jz^g0RU0=Pzw) zAR#1E3C}?KlVDIp9bk)g{jq;(6vOsT5E}R+4@(@G3mgew-IMhBaUfujuBYRoQ6ahE z%(9rIWM8IRWHaV42;o>l-3J;ax+k^iMqRxIrl^84kJVwLuK(=t`wWFDV3r9Is+yv( z!-_(3Ect4PvnA^l0t{rHKXd7uu z@b@_aPlpV7w`nDtY?+M0y691VM1m}AwQr$fmA{5ysOgR?^!bibAsi@|~~s_edRm3m?>>{*qqh;;d)zW{;u|xC`Ie`@h8yz<)rCNz~HmS8!r$y#LAm4-3q5>ZV zyN#isn3i*n3gd@;Z8kg72$77K>Z%TsR=rp)XuY(V@%Uk})d3Bsky@5Cm3Ki$hI}=P z_^-U(9wf&i*J*Ob@}Ne$1rtA~iA?HtYu0>=nadUTEe-ZN(zG1bY7W@S_}t6SN|Dya zQRN-@g}C3%m{lY83tn7YQqrb`m(1PEQp!Jbw^)NGg(>|X7=QlbstwEQN9G&3*K3lD zb00tUJVb99ZS(m`&3$+x5K7|!22<{rRF-j|;2;h`sA@pDgB%q0kd86*rTnHJvo*M7 z2UA@P7=UpB@k$79Jv@t?jNEjQ;0U;LK@Dd?Sc26hb>2gfxF+NqApj4xgq;T(aG4{7 zc_LJ033K%v=%gJhkQEF5?D4c#IIsKosqHvliOo)EdjeIG{x+PM$Oz+6LP7~uRmS7p z4zs(^sfI?z;fohbpBS8fP)7c2B9N$jJ~a68;P0D?Pu+#jU^a_s%Tz(j-O+(;%#f*E z`dAl!`N_{sQ0#k4-;z;cQaM}Jvj|ME)$PKZ*%39XUk`$Ug0#9@9DG`_pPYZMIQ#S! zEXDS|SAMnP!}6;}Vc;ik`E+HE+ZMlHGY>Vjv@k`{?VcR%&cinNuoZKo1qD_>a-jIa z0{H{0)yIIL2|)|YKQ=+Kul&-`erp>W=$J&Eb#L+Q5RQIx>XiDCBUFTK58}JxaSi;t3Blzt+x{2(1<0GqkhEuKZ??uF2nBFoPeQwN!tD@f#zFbs<^*l<`W5z-Q zv-gU{h{prHJVMkDXCFc1Y8pwtujkpd=bipR$Z+zgZ2XobiUN43w0;*&5XB%A@(Aj? zJ+I0oc5Bi}HIJoB{k6@Xjw(*|Det6&`?;)+8^v5kh3~&#m+dEO7U1PuNW2y~Lt{7U zFR8%a=~P9<+HgUAreA_?f&PMX-i!37$M2~*OI>GTh!Ydz3kp!XwY}|hf#ssIUm8vzI4H=gmoujMh`V_Ajfww+7FLAOpC1Mx=laV;IIf$ zMLPAzDgaW7gv0>meFa3-9+I} zn7Gx{TzeKwH@Xa(@Tz4POO{Dg0=@c)h3eDL`$DDn8Oe0PlQ!|B;b0a;M<~LhEIED8 zq=@P^kN_*%b1hRvN|Z&W9fVyaEIEj~g-oQCc4UE1CI-kc%guwD&ttM;AZo_^1apZ= z4P#mT(vCmJyt}*J#76^o4mGUJTqXbo=z*iFv-w7A&a%Pi(d6jsmrHe#r$L?32EuBT zXr~z*Q@4>%Eq5N(Ac`C?p!HlBJA>AE7>=b{mm3Pr1XiVs6S8E0ccIfw3F9kx7p1=0 zRMB5j5YWBKt$yP8$&;1C?ml{_dy$c7>gq<>OS6&?^1T2C?_jgzZ~sSR!-G4{EI4V# zu{jt+JLpMEO|Z3ke8LE6X7gMQ$l$`3P52i+Nljy4qzOk8)bp|zd*teqgtcZ623%U)%2$Dub9 zm(mWri~2U8OhL}KTbZ?gBzW9{sgtwUeA_;2QN>|u$z0nn3Mb0n1U@L`&b1ko-fz1t zZvX4yE)%5vko<0e!H=+RcwYLOE68*Sxq5ItAXP@3xH7V`5dDWD@j`ewOCl~;6>_qO ze2|**2cw866#+TurTOd1g}v$MXyv$EcO4>&61UaQ&eV~h27<>F;d-<3B|#4Z%uMsv zw&=D(O{|q@mBhsns>&GOdpfnqzkBx<8X6jGYosTu$JlmL&y9EPL>>zmW*gEGNZ*hW z6>nll0*2Owz-7AW@%84J*tE2|abaF#4rc{ULWvV@EPtF^&z~2`Z{%ax`av1Kd5~!( zU>N}GScCo;ZinWdfRGTxWf~pjkC=8qJON#KjIC13jmxOFcYnO5Ex?8a&p6+QCd6kb)nH3x`>LQn=E;6xSyBB2=D7n1 zpaaF7!>tYLQ4&-$`0s4ks6f(e1~_02M#zW-h>n4(t!=)t{IX{~%D+|1iY4FZLwPvT z(6c%cJ%f?w9R1m2OWbJaqrNP=LEaLxcK#b%$9(@I=bgmV+ta5{lgSbR=>Wn;CG^OB zHiJ8tkgC_v*kmGc>tlgPL;#Qj;Az;xLY57uTe8=e>laywY5^6@bMK)lmwp)P zMq^FFe0no9UT|(6adWHwxL)-upmzz>5n@OK^*w>sAfv_DdWwESj_FR47q4GGhigP2 zs&$P;FJyXer5c$(J$(M-nGAn=G98un>V7Kc^;Ds&ikyuhfi;H4>2wvRq{S62&V9Fy zC@G_5@1NhcuR6cNnE_{AbMWP}bA@(n`i1^WzuG!^4RSTZzdhoP)Qn|jP`%z?(pdXC zcx=fo=c>;B*YXK>Zbt+UW{k08pP=5%f|#Jn`pRf7ElA14lj7pyBBa^DWxyXG$#H3E zbpP{_o85#4t~$1&2IOI>DKZ?k4J7MNLvOzsX%S1$R<@btGpDx8r*0u!22ms+F6CxM zZojY(FN$!SLWZW`vnq#=#j8cemF*^1R9sAT3+ovcPTQy{K)1?aIVJR~e_-tZfGOg< z!R~qVL(0|}uHb~e2Vt2E`bI(G0aI$wPW@3DKp$wEU3=Q~X3Oi)ydInh0mTVXJoEJQ z?CcSU6M}J&IqB)3az;BG0}RBucIly)w!NOv-CZOLG852e1(r(P@JS_awlvQf1_p#K z>LR7#!@*Ljl*)9A0n3+f-^Q8=86RJ;zh{p=)*lsQxCMP1vQVoG3+;$|@@8!Q>BLod zRY~QqGx|UIWiqw1)z^Hc3XcLE=Ex^$=zT9X`jspTTs-GyZEgKbW(mjn_wO;A-|8;; zRyrTNazzH3rv5OMg?2dEo#P_kyfUiU%yGg3GW6lJdq^z8Y!%0B%FWPyg62_n%eOnn zHF;=tztZJFY>T)nz`crI_A!%{*&^?;QDS&hxDAS{%3W(Aii$QHk@@1ugO>?Zk#lS4 z6Q&l!%ZMk?i&)G`Sh9Fy4bl12rN+T%pnOK1AH834UTd~2KYx?o`g|s)xT2pwS3^%G z3wE1$zZrd`LZge)sOj)sga{Mxmx-dS1(qHjL|S^-)HEHNz~XV!$lI~6D65I&1#%Ya z{x{{_(jDr%+Yc){4U<4@p*Ztm>yjSrc5>bMJ1)Ut6leI`y7YMqH#yH<*&E^+F#i7Q zp_eUJbFBxOMCU8#lUd$}P>J$y6J+1|_$N>B+mpYj$DUkLovArSM^CX}s-2^^`~Ldz z4?SOg@HXc*rdS>xnUpITNTo49p&+!LLc#`h#(1dbu}X&Q zqe7Kh%jk5VrpZvC=1Nt)EXHKVM*$Z#LJ~+3?nEb^<~1{vNUa0G$j!|iuIe)**mY>v zEO2p`C*>`@ODisB=?0~R`hbEnec<8ESdKjCZpQs?F z#Xfq^9}NPwTIb>XO!z=+YL-LayOqQuw|m_lD(;R4GG6h$LNr3Q*GgoKf;vg#vo??K z<((EHZUDJJmdIwJdxQ+=Xpwi-Lgv8t-Mce4JZ8IBesQ!}i!~&?v{nT*im*-szsRLL zI?VF>d}X;IIBWQK`}3%_wA2c-Z77><9&I*k0YV2uBuH{tmOrn&;67a=$8>YfL$oMg zrn(K`3R=$L*m6XxfRjNkW`+bFDH^+lR3ROM&7n+heNzy#V{o^$%T(SAP(V800 zBMz>@`}VO*)-NzZ<4)?kiDUNTi=4DEQBmXcR4w|KqppXCPsSkBzPh<D`EuY+E3U2d$Gaaem4CP4 zFNRY$loW(o5O_!S{g1SObXZg*7@CV{ZS;c z88aJBKZXxM&|62veZs!OD`l4UxUrF~jm-$mzFRVsykc>FEmOLciath{?lNw` z_TNufgsd9fIac7JebMT{+<^BJ-vQA1!(qxXyxg#%1dS5;5fjDUB^87DzmP=vY%Lxt z%im!keEagFK>Lh1DQ+=}LI-McB|ek&JCyaMz9nA$k$F9r-ZQ=)dFT0Rl-6ecn|t*1 zKv%myng8+Q>Uwv1#u_fQG&k)YpWP9{ps&X##BQDKaiL7cgr})Ug7j{4RN8h?!`e@g z&s>Y{3n%C34@dXfC7CPgGgE?;M2P2P0ShvHyzoB3p`!0djKt=#u`$9Qv>BBG!2iXK zCv2IqJ7(?s(2eJU4i@y#w}VlUZzA8lwtr^8o+_f@=eJbE&C%>Z*Cn|GT^z*7UO&_O zt&pQFj!O8i=UjVTo?6Qg4z&Olgou+_sI~jr(N|gokB3o+T7>jEvX`-_2_qyf$?)cR*K!gmPrV@2`@mCY?LaaPaUOKd`3p zU9SCv(# z2DEy8KnP?_C0GeluDu^U$dOQy5V;?Q@KNwLCsLk!T2{o?l7KEK)Xop%pd-YT2~v+< z%|0fV+M@QhqiA)O7+xoy7CdwNx<9{JOinv5FV!gb=~8T5V>pXiT-?oZWOI)rO##?H zFRoWkRL;DfsLP?N%D(pa+Phm?N-;wG_N1ri=!4!mRk4oI@5x!eLLah8gFkXE?yHyR z*wCyNt&d!8aFPHs->|l;#m~u;CF{|w`_v_<6XY8Y32?g=*(qq{LEYd zT26Qppb}{J%!0=;2x8cw1mE#OZ;8>u)4Xz8fq^4<697%vVT}S3N(-XYLZI7Nb`-OE zC;g`()CIuI*i9lvJRLGohN-ZR&v`fj{2{<3G(_8l>d|#nQsTPMcdr@y@;-z(oTo;0 ze>=|1=jI9!u4`puAaSVa>2;*_2+!yypll(IAp%Cl%M8>}TX?rjDk_LG9U_K-5S{-3 zOw7Ssqg%GU%DD7B1TG2e1%<<65zA*g2a|%Kc|@WgEgYA@Y5Giv?v|BhCdgK{#E@rr zecwlzmO_Kz{ME2^T>^?gi<$OFYZlO*MX=^V#iN4~9*@1@$}ZP-@V8?)Sx8F8G8;LDEUI`N$tF>Kth2 zaX8dguUvSi)0A6PkB9oOvQqZGlBQVCG=t5g30k~Lt>HH%ZSOc<*suR!`gr1;0iE-2 zX_ZYi#=f4A9K}I8Oem_uF$dWpPb!I?o!{NX8>j~`(~nBJm-XUq7M+tm#?yiQhoE?`>Rcw91Dorcxc4K2HS+Yt)e~rMM7DDe0icip{IhJ zl_+*mj1nG&fW2{O#-vUn>%7<+#d9IYPA?chD5K^N7>;5BpG)j*sOY zTlV;^wcHsLM1k(*Ic(jE{adq&PB2pz7Zd(XL|_16hcJ>rHLfJ@mR97J7kvZkov^?~ z4zZJukFPie`NaP^ejMN`Fy)V!Ma}P@fo>I_A~!w3MTcnpXNbEJ{yEc*`6(Gx0s;c# z^ZqE`pUt=($!iuc76*}ImP?#2P>B8GuFg+NQEj2AoxM{2Z_36je>&_f3IFyYChG;$ zP}rwvGQkPYO$f|aYFUg4@rjpk=*5>=_dPjVe7ah(Wct? z5_#Dxrv*l9&OA7=U?Qe#D_DH{ukh-hgH#vv#L&WPBh>mqjQN2!Mf>@;sM>k=?Ad|!h^01muXwA=+od2A|fuXhVcMEVnhqihw64Un>5qx zA!<@Y#n-QOOH6B72r+6DuII`Pv)zoHNi7|0ij`iP!NZ1FnDCn;xCR6=go@cGV4s2g z1a>&uRrk@IBitFV5O2(=rAA-a@79`u3=iLC=C|fbKy;w@J_ooC69eW&f$6KaA`=-w z`qa|WBJKuH8XX)+K`Gr`t}_gnb{T57oX{B^QQmuYR{EoRCDhqG>&(k09Oqq5g+|-Q zo1y;?Y>23d&)hRPgjx!PUv>*$xx^2bGhbJ}n!u)6Wo<$as7*7d2@w$d0rP$W38Q{X z_bJJ!<oQ8DCL;|tD>u)xN!+Xr?Ys^|JGb6q^m zXH#WjZeocLF2}^lh!{i|pu;wqXHUWH_RI@1De$f&j13TSEf*(y0GVGad1LP?gsKs6 zni{O&fJ^6_+{kcMW-0Ue{9gWMT3QrQ?E=Qds2ag9fjDy@66cA&EBOO{u&)Mj^1kny zC()9Y@#vaqLTNodt-7O{VNOhP`@lk(d=6Zhi z+EZRJvlny)uVs%2EsYGYKHN(uO1fdeuz1VUX|DG4yR?~u76Q!sS~W-*7~(|O3&@yx zZnDP4UA)RTBAlCci9=i7S_@#$>D{J6l zbuTm@9==+am0Hb)We^QTSM=1m2~-R~4OUF9x$LP^nVt+ql)SrHXi+6%$pLf?f;j?W z03Rsw_6GUqM%oN*p)UmSeImUe*czZ*5h|rfM^*!#c2^%B-BZ=?NKg%d@=Sg-pCd|9E!grz5L^ne0M z7crozp%L=rtvtuq?A^oYP`757`E>othQ#@9r}~My|k^EIM6Qi%hY!C zBUzC9_x97NGD~a7OWkUHy2wZH6w3M>ANrNj2^UKmldRZ(x>fE=P+VF$RKp6FRG600 zB_-S-JnGE|Z(mfugbA>_`(Bi|Qh!vr%9dTrE02C>_E5A)L<0p>C??|k#v^nB5d^`L z!0OOmL>;IBjR_Iv+xMf{S~a?rs>%IvsuTMcl9)s*hiG1*lFmj!jm{LRnr`{6nmRhr zVB#k7A#kq6&sOVRW|ehqkcGzd;0)BLn35{8MWDv#;-*oOw7xs(*9XU?snJCvq*onE z%bbJ}7#xNP9y4%W0xZE1{_*6!(!aR?vUw)m_YMOdM*e`zIs`clF`v0>*Hrns^K=!8 zf%i2o!nj!{f_``P>hqI++tm+Ycjy}!WIAx?n5u*}NuTp81a(1vuw*%9w91c<0-t5( zIUz{!>rsy&uiZq9HsOpUqJc}TT}{RS8%83S;N)XiZb?ZT64oynebx`0ohO+wD~Z<2 zICLk@+Li zW8Xq;ofV@NKwMQVEk8esl$YJ-{{t{SAY@pkqEyFQ?_)9_K{ou7}KhoD;(3-?h@v~i9j4XXt}(zj;>A0 zcPG_Nbp-0*p~y`z0r3ei)+C&t(c33C(OHT&n!*ZID}Q@{WQxOcc7AUs@{g$O0U&Aod;fTIO_(}Az| zy-M_%j{mtC6EOb#PqZ2D7Kmw~VRQR+yRB(QTU!(Gdg+N6HS~1!^<}0+iI70l4>$v~ z%r5}z%E(ZjS`-F}0q|a0Wdo~fR#w(d-yfF(Lq49F_@{v znxC5p96WVVdFX#qz`!cg7sen&rnJ4g`yD+W1(xs}()weMe~1MkcJ-IMGeR!7uy7R% zmyk+;spa_Oq^h4^1GjaAVDcsTRM>PiaZD>L#~}lR(jC!K2A+p(!<8A3TPqh;6xX2rC<)AB z(d|dK4dDj;h&x$XBuKnYh)PG`{s&X~~AqEIOP&JRPUxT1Ne{*z9NlXkvPr!np z_`Q+*c4hADt{hYbYn*QoO(v~c@kY>toILanB@+hP_w*Vt3yQ5|QQF}|MMz(uqJU-< z`YYTAZrRmI=bXL)kk=XL1TG>C=p)hffDFuU-v(~Su7GM_nCPrw${azsIb;h*MzJEY z5kf{B>8{pPEVRu5n3+UftCH?T^-1`E5eOm@Uc~jDvCrTg{!eb_?O=o3<+t37(q?)W zk9~Jih|S2@irHOUIl>$v@PVEl=id8@-J>LY6u3^98vQ=kMYRt^%WKzurd7$}U4!+b zZX@f#SUU8&8m()BkfV}$E&IX3Pwx|b-qn+yIFdekPL*oSmY=@6XYzN0(dAoLLY6)3 zKdry0byZkBME=g-`lLm^{m)tcR*6HJ$w4$Vf}$SZxAR0~=mhqhlgaI;&193^>ng=) z7*Tg|k3grHioeUorKewcjHTri9M2nmn348UBDVxSFg60bM+O!M0Xr0f>@a;v6%qz5-R8q!~)<|gMXz3<7rTR~_K5=0b&ahI^Q z9R=0;RKq$gvO=Hf9Wg^(R(0)9EsmRypND2U)kAc0Rs-%ODZjx{4u|>CIbLiGRGfeS z38nx>rWyFfE;c#N#wWjW;`=zY^4`?EsQLmF~Yhe7oO8jqe<3H^AhvVE(mPPh5qRx^WAZ$dy9TnywJUID)}!ju2111|j>`OyyVQ?nHY3p+CCvz`zW3B{!;+|005V4iWIH{ z&oMz-rz|~q!&Z0ID=fP*?YOu2Ooi?3XY07E+~G^~5fw`;G~Q&#b;({%Y-Luu6>_S! zBDO|+n@~ly0jY_uS!2t$(LH8%n3F|N#0w!(%2m~+_ohsvGYrWhuFo8|TC!RWrH|jn z)Wo}YCv)@jEnmh9u4XqSWL)X#B0<{bV<}^H?agBLFV)L|R>HTex}$Ie;SVzl3qR1} z8O`o{Mb5&By)0Ez4vHterxzo?zRs*E8Pj{!myWQ81PPHHT4v*ZQF<;8U!|AAFIDva zHrx?e{#AJ}VY=v>hM&Gqc)^pa@}EY#6GtjO3fs=x?S8(0539>-53w8NV;$_Xl$`Ur zvumzGtBVs7Q^Gw|yuY)OtB;H4c{@7pm2b?7CXZTH990SVgQW*kVfQE*I-=9DkmOmnt7RK=t^xD>lQDlgln$_s^C>^qS8SIG|!G_aGKT8-^1 zPnxZuxG_4yHxNGHhr9~05fBT~X67o6qp9Rk?n= z|N2vIZNtRE-~P^4GD_au-p=5Maz*;t^CKj_Yty6y98~3=<}81;>03M?=`tj(;yNz- zQB#9M+x;#N%G#zpo`JV>_jw#NCuv&MzUXpfuhh(wuJT!s=thv~Jz=`LWOL`L%O;be z9ML%nirlx)9#t9&4UZn(6kQWPdNH1-t;00zP`=?Nr-AR$6KQo_ulsuDGPHXJM!L|i;3P5hnoD{pq$ z=BZ^)4oA*k$K#x(*X>(KM=43(vz{}nZboaQ`~Yv2YZ2iPm6 zPd=J9;%11rbwSzeEc*fax9xjrI5`#M6Wn9F>gRVXv>y*1X_ylYc3G+7wp zDw3aNQd1YkvW+i~4s?``U*c}b@4Ir}`qXAliAX+5)0GSPwOd1fSy?S;t8OV8f20!B zZLavdCv#9rQGqdBk%syloy@jocJ_O>)ovHukM#sy%)Z%ihQ9I;Whirz&^735h?-Zn&n;qF6(7TsPuLg_hb59^ia`|5P&I zi<4iB>m7EWHZ2WQm_NnzIQ6&T`~%h}s;rJ%DT|qpXI&KaTOHCJ;X0we!KtshMQg$R z#_(VCrH>X4(>}f(!ax^H-@`2ReVsQVr`oD8);5yHh*F~DLqhm=j;^@GOIt3lF_F-@ zCT~v(`}>DQ43D42r&hAk`#E(%M_|MqUzK}27Y<)oBa!EPt?_bxLenYb&Z{31bbJ}# zj|*LV7@Cw#&qz&qO0G}&S%2AW=c!v;>F8GY_&0M|qVeh-F&Eo~)(Gp*nnrTI?zi`Sn=89;( z)IInz|LV#5@k8HagqhXVZnvE0NC}^B`}y*NwbcO(JnbMO*Xg!>z*$WAty0 zm#L?Grw(5-Vk~i!4Pv}zM0X%6S1$iKxoWYR`#i(F^>;BBjtso*lnXm9BDX5EKyPxh zU?r>1MU;f^T-EK^n&;s?UiF6M`?zSdlAN4gzE7&mZ+6iA`>m=)lHfbjrJmo;^)J?@ zQ`+$~+I-;c;OADCGV2^`gZkQNyl`x4{p(t>suP_>$vSSQ@|2*l7?pc zTlZ^Ddon&B@n}ka=l+QX-)Vf`(RD{}!|mwbe+h`#3OY~?pEs~D72^N@pX}y9QXv^B zikP5?B(JrViP`11y;5N}YZYI-(D8p;Epv3iG&d-b@vzUK5Bs{qBR^GV&)G@_+<7~G zNsw=6?P0@7W|g4C*tm+u*S1XhGF&1MZOzaQ#(X-ofb zx}XtR%HkcW?t{GTYrptHRs-J8wR#KKycb%N&?md&uQt=q&H3{Jx#s{C->D`eosm*L zlUoJ*3TE`XI(9~S+dfE+Gf0b?R5B7V%M5wWtRqkuvOQ~$(aS-J%gm`2g_DesWmlwt^R)a{QOdlYD{n4 zq)11GxD0+-HVF?r9LZK}CTB0|P#qVYEpel*YfCq*?}*GE(~paFq!GT#{8lC8g6w|+ zff`nf94SpvSzc&D*ijoBpM}NOA{x$ujB;I;8P#gE5t(g`tAWZZ5vB~PYHoYf%~+Kc zqKdfwc_m?VY!=zVjC5N%Rr#y3>=k;_`z-?w$dcNemAQZAj8NAVPPU;e=6$<#$wufr z=_LNyliS#(8`ackZzp{@5%sMmu*X*8djxaIS}DEGnY_jkIgWM)+Dw)F`lNpzTo`vN z3DdUS=T$>G&c?^R^N69~*qo`;91__7Lc%TBeRueedqR|(1-eK6{feUzq(c10j|BMb zc55ZS5z76^c)Ui5cKe`gY`jfhppL)W$M|yvO#lAA(l&BkwaQ^N`Q5=ZjkXVYAIeeQ zU9!z~y10KjJYRG5z)V{Ck0YCI{d-B4O}M0^clT}S)T9yRKeSVnm$@;+LVcoz&e2Qv6p_9+7p7O&r zVeP=*ch>pmI^XE5e&4z6<4pk@g&9-J_$`8|GWStZcx+}wXVRWjP?R!IC-TX1kr}2tBz>ISYZjpngYNi+>i7NC0tf<^m*qTWVtk z9JkUYozAmd)G>Ndrh9$&(~?=^h_rT!(XB%KN5v=R~V)4yudsxK> z^;@c*_GJ_^U$O!uhEk(%n3202yEnB+gdysh&p!cN#X(wQ%LKd8I{wqN^-1OKbz z7pzo=y%VByLHm*Cgb_=CNk63HC}W%)q>v(x$HrPW7n}AQy7SVC zrns$rQu}g&qQm$vZX%&7<5APc5jvJNA5w{LmBFd7NyFHWxz;4ibO`S%{0nTS>18x*KB za8Hnw^5B)nCIQyS?@EK#IYI3_Y5L@_3B75j&udtqO z_@a))=Kg`#ahx6l-v$30K0br1q$X!2AP~eE%@7=@OBVjpP2Buz%k5kBKej)%DBm>_ zt>e!p$o=1NJx7F&+kA1u@nN}jfk9d#PWc1n-vlLld44>NvkuQ~ONdMK$gz|A_cl)a zCRT5L`a2beLqbGYD-dDt@sc@F$+u-|0j2&cO{s9vxxaT*SlG8ecfCn-O_q;>(V#@c zQ-W*CY>D3`oJlB>jQU_B+fhrWe>QK}`6t9G%+F-@d{vuRNpW(v?b+>59&w4b!NuAl z5n zKM%B&f`ZXRK2b3<;%2Sv2+_?g9^YX3De(LHe>dH=a6B1LTm6vv4_ca!x=IcG-iEqL zdYyBLag!c#^cd+=&#eFNM5MXmQGe zH;n&$X{R`(?Go?5KZ-(O-F^3q=SOwboDY(yMmW_nk6hF_9ZJcPI#FKzZg;~$B|14ibpiLhpzP$>xY1`x8*w*g{KWkSFN6k3 zQ3SG|`{&v6^EWEatEhh3as73G+Vn~$rDj|C=wzJyRb|eDyoDj|vZTYG_jJDQ4p1LB zLW2No$W-z|(si_k9F3q9vWipJ6Gu#SR5h#h{{6(RHy-}>SH})3yiH8R@cEesaYnIW z<|>y}o2o8>n0`1moVH(0_NB+B+YDr?+cb~qsr{s#DEycZypTpA5+NmbxcbPy9~NPo z5zQm>IE(A!7A&jdb<$fVuF(*ru@C$EgP*QLTuOpZ>akJ4Q=#mMi;-bak5IinIMq0R z`D+eNA;TGM0aI>X-uJ)%eRAhbDi|$xn7-9eu+0rRQ`+sqbbsDYB3SrSIPE9-JE-?FjI|j2ca3JZCfiL> zv9lHh$HvOkrSf-PY`*=;ns3iwNQL>HoH-525GwN6f&QZ*yO|kf>h1=FS?14h2GKp_ zOVB!``e4g0cbjvI-^Fun2b)Ca7-H76)dC)Wc>14Bre~3{m?=`3u9b7(rsGywZF!r< zv&liDv!7aTW*QIk(5VI&H-ywChvjQ%RPUtWBPGfl`IOj{%%JGYE5{$~BR`!=6L(QN zZYUrwnK5<8KTCGo(MCm9m6PWW2?e>IV0bz`VDXnrta%FEo%35{x*jKQz47l;wzST;%TrQjHFZGf z*`TmVk$lO|X!;DnV`=NZ`M#Te5AYKyoVAFrvsIIhT|JVJ-I#te>=mo*iK_o=>e}O( z{=dJZQj!X3%B9Rb!`#UwmkBfX$#N@-5xFh7lS^SEbKOjcA=h%tC6|)RqPgD|soV)4 z_dCD!dsuzn|LyVKo_n72JeSvb@3GfQ`M7KP@hBZl%Dt`6{K^>ae?*PWc6jce4C}h| zzDe-LvEb%n+hM$ERoNr?^*3DRL9We)4Ll;B+Nac&Fv3`R^ovgr6P<`&Lp+-lc;(`| z?FCC}|5p==DC;`^T_LYR<6nfmzJnY8sn1B&{9;^!kBD&@>s#u6125y!gIC9-Gwa;x zLezw?)&}ZlK5kt!(v*h-*JLN%DDV7HA_EJX*Zjgody3B|dSFBTty-@8a!x(<;roel zy|A6mkU?jB{Tc~v=0|-5Vb@ws9lob6Ip`16o=RGM`}jExTAl(}{YFhK$!pEyYU{-} z3P;i+?PHm*_f(>p!~dr&U-v~!J@q%CxC$(=0m}Nr9u%0w#U;PUj^+9BC06Y6Z=R6+ z{9v}1>h#9{odqDSUO#3gT(OlO{PLnYox0J>3gGFx2il;h0ph1eQnT(vH?_#_JO7;E z`@|h!%c+m!P%wcU%b+K5U#Z-`#slyxzmA%?1B3{Rn}UJ36K&UJo%-qmQC@ zz%uKZ*S`B{E9j{O(9ISLZ%7IGUs(9m(*V0zv1i%`i*ERLf5wWG1C~wAHW?+j4O3>f1JxXiEBee()B5}5 zI*vv`Hu9QW%J0a?S{&LarIORi3XaOo;NTw1GEW~ZFlp`7_2^dQjK9iM&Lo%|rbz|> z=Gh`LcP>3~YEONfoFIKow+L$@eR8|Kk6=zjhV zYEY?sZUbLDOvxfImfNdT-6fSD{Gx%nT?j8*bY7)&rz&uj4O2`{r|rjA6z64$StN56I zU#JUkf3}No24JS`+3pk+`@#kT9;ywFn3@GzBL;SM4%JpK9^ug?-;)1kkWp<%3R)n< zRlqdb_2xJ`3KXl%#g(OUShdM*3S+YqESTDKA^MK1i~5t9oCpSPxa|e{o62) z2)EPbuME$hctJ(*&I4H9Uc(=*W8KXG$0JV-$M(pCO z*Uxh7HGZhFBXy#Hcj%8GI~ecnroTq|yc8#Th9tSn!ilfC+dF^vrZ%W#Q?qc3FOB8( z9j8#e_Q4buJmHgsDJm~xzH*T2!Q>TULWwpX05{{PVL^j?h^}Nw`4z*$8p|OC3z3;? z$j|#$pJZaLoNJ@9QBS_gW3Iu>1I7u_^E!WOo=AP!#|b(i+qIX-?&4!_7}~CDRJ8Lt zT7zHr{1H8g-Z0Ne&2cwjZn=l{38E1QZ-a&}>xI_79wkred`!ou$-C(-o*j%hH={WN ztUH)IXmkD!yQqTLsAd0-->%tgC;x5|kO$n1I&*~B2$gc6&v}}RIlM_h@@1LIcJ*LJ zMKi*kuT?m}7YH1bBj@Yvn;CMj&uSToQ56kvx@Jjp=TCsK&;iFeF;Ic$5Dy;qY_4K2P z|EBlv^WOk$WsBx3 zHw#8mlRYuiGbsb*;_-fsM`&~@`iv>}jm#rW=8p$unNsHR*RAr`rq_R&_^q}4F?UEo zL52}`9vPukYJ8c2^Nwjx#Ho{idJ9fnXc`2?p56UGgDSgptoHlNg=w#4^gq_mXzYW6aiT?e8`WMzX;JzEgZpdMj}vTcLc~~yXPgfZMDgaoSJ*4 zNGl8-PZ*#)AFG8JBtRDN@nYHKJ2BRYyQP%neP!PzOqQbG!;wS7;s>W#D7Er?Vsb5- z*WyjiGl=w;A_AP#2S$bq?t8ZkhGMjT>6DAoFexXF^=iNHyw0&;zakkzt)zB;ahZX3;3eNhPLaGCi%Pmno!xCpQ3&$!Z@=hFC zt>$;lc89tdqs50*VQ$>C=@rn-lsHR)pM0*(;Wt+u0t|nhO`)m`O8d zLEiTVpJs1MrWBp)NQkxCKT%Xup-l|{lT3nZ^-s`gfz!z}F&16~BRPL{@g7)WpBZvm z$QY#cqw_nb)*4im^X#6(b|rm`*%V*Oesv-hJ!-o=Rw4a1+a#6yYTur7yYBQOW8OgV z_?$jk3#Fs-LgMk6A<+$d&S^ELR%*6I`Q`v(GuuJK#L!yUm!Swt4bp*tmXg23YITy| zjc*w;WMlk#k=r zi|g>6_EMd@se=JVyH(GFfu(HzV5d^b4(NP5v8D{+=iTr}T7O|KnOvFJ)Y2fPBa4G& zA&qNq`1f_dEkcZF2)x6DD3?oJC*_(4-ppZr-ub0Ej}?M$Pi4lQ6BuVZnF%pBEC2aD zkh$cBa6I#`&k}ErJ^Nb(EY%y=kbv#3+gQQ`I$j1sL_kt`I1M*#VVyzV$p;^Hvjy}u z$XZsC?msla?vqxRx0;m_?AgMEi-zF67#8L?1+U`uVkW=SES&<;=R~&w#xf@e_Jt;j ztWMhl`69;^_UFdD=hMv3&5qzsS3{a&`$Nb>W?a~t#Pt4!Vpx|sH760CQZZ*eJk8Dm#RC8|nC`oz(aXF?76 z3Z{^~`>3xrCP~bNKoTb(pef+^k<&VS7@*12-!bw491F9O##E0L#hxvwugaUQ-!I;1 z@HXzT?_p>4CjS6OwgB)z2@emlP`n;;-6f#r!1FlXyj{0NYqJ?>w^n69lc72$c-B}# z_Zi&G=;~98;LJwJRW3DLe6l$?!TRs#Ie#V@9zzx7T9%4-#kE2*Fl~0jX zimD3?W*>FkG=#KN2{B9@&|+Dw(JK;q`Z_v0FO_r|>4wbJ9c#;1#P=3-#>@_dcsf-< z+N&z_PMqOL^AsDDowKEGNNR<0c|R~ZnEP{*KODz9D-^v6-hLT5e~QLZv(am|Ico(y zL5M2^3MS?QM*j=HwoVqOM)Y^$=wqJsvUNhd^J%HeY2NPgf`E!AFSav;&D)&T7nCV` zT^UiUJ>AODO==JhT2ue;9%BmVxxKf6^h2?oSIpU3^R;~zUuTlX{A_}ruD>9^QV}0? z?2_^9UJ6-dzP~)9Zr88em5JG8aopA){qiJCOyBM+Z`3@?5y8e&7bL9UIS(fA)CR`4 zE;iGZHfEVOn68wOoIYF->IZ8>H1M=3OnaXIM0Wf1N)dP4_}{DH9_ca-!pjfpYfdIV z5xH$)VYk;mx$>9dJI0ja;Hjtgo9hKbiAhkl9j)DWR|jd0EjU)I(JOq5H)|L^jy5^ZDfYdK z=wSm_;=aU#(pV8U(G^Yjjx_Q&i|!XM8!#6OKY0M zD%{6<+3)|-f?jaO5LWsAbJ95B;^%c|RKucfZ!IO}s(e92YQpOnL601k>DR-d5Uf$( zHoq_rZE$LDO=yL#{V%?+ZfjVhc02g03wNO2!_1>VT z=1cYNnq=7tPNP;#bZZSxxFoH8PsUBh)}COb?ErILn(ak-Z@#$$6KrkX<{msp3z}Iz zTl_VAnlxzp6>}j3PVP+`L*#MAE)9HBIyc`(^7Oz!*ua1%Qd++jiCc)1vWA_G&;K}i zQqGJ$r;sHf`{85Oe`p3_!N`-yn6I8DHSW~)!{|=`cE0%UzG#D`?vGZ2S!(=IdQ)y3 zF`h6ZRV+G=AoXQR3Q=BMh}vAECaB3R#WTO`U(6 z$k(vg|6N{sk0HXBWU7TgcQ^!|JmalVY`c~axOnF%TP~<*(?5h3oti{Y{u0CFx%2^6^D_Q%L z!`yLd5_=Rz+syW<+%jpe-f|;(11;KOn$#06+J`pyPIIh}s2&(my9bmk^LiAzAOkL0AK4%5M#ULrO66kGi{>dxzWqk9 zb_EjT9wF0(1{SRMEEqW;Ay%ZMa`QBO!B2Mn(alZ0wNK-IYqd64`y%-q_KGuQ&)~3b z!iwz9XyaMK0H!Ev1q?-$*T3d{@Y;^w0edTiX6uiOyta6LX!#w|cFmIzVT#cpcN?<;$o$D5+~cX>}| ziJ)3)D5bf~QcHm-A7O6x>DTo+GZj*GwZg6gV0XDB z_fi4Fci8@QmVt`74V6hnb-?&*(R(@4KD6`*)_Wd|ycF$yaoep~XTZ@-CwFmgihg15 z7Q96Dy@3Plm3fm)_P_zZ+^SN*ulF1*wM>sP;wIT$o@Nvm3?x$HdC3(Fai`;V_}7`L z&Qe8|?1kO~0s!L^guedt{-o#6`)c22B)_8M)&EE}8k5OAE!b#IgRe<6qt?^8N6?I?7STWxC(2R?+HhfFW z(gdDz8u1J$H?F-$b7B{niWf6x!ImotF(#_!im|X1C}Ibr4HmnEo(VGuag3en1qIcg;o*_>u)5VO)1P|%?9a|=H&QIQ3#inC;EwOcI?-eoFZB^6p9l8&Xxd%>v(ssygbQOCkItGNqa zu!Awf)iGTMdS>yGnHKSLZ|bu(?ol9CdIE~cGs+i0G|SzZFGy6plg~f~E&>(UYr&MY z9~n+fT$Jr5$%Vk(Jjwc%fMnR^|SnI@y;9f48B@(?94rVT( z?7>_PMuL+@4b4RC_VaFzD-p0{ek^UJO7~EjK1bok_ja~2_@DLol(x2X$*cbP!vHny z&GaNs7{^2CGAK88D>eQQsM8{ns*jwR!@6#LdV4aNojn_0n`l|;5NS(Gx8UL6VB|R| z4xnbePg|a8yJK%?C{?nq$ANF}E*&oBjr{~BGw4m(mGWjyeb4pjfA+`JxrSMzH-e(h zUlRtec>E*APA>qR1x7WHCkm6S$B2s`3j_CuxVg5PEJ!$^gq?Q2Rl;fzCv$G`!H3E~ zAH!%llyDle{Uc8X?vH<;jg`f_r+a-oM~UIV3F<&F5%IBD0*oiMYsp5XFpix+(XZ5q zBYwM!x{FfJ=cgJ${_8JF!wrw?MVDskAGq56!t&kwD1*a-N~l zwGeZ~!%wTzSI%gN+GX9(dhpi`m1>P)ER(ef1j6br)*qLG#8fzoi8aKME`VwA-*xgv zG98Zqw}TvNtj8u<%R0rPCG+CXztk5(&I!;|r-L$aLI1u;xkUfJcaE|Cy^|9jOrtB4lPa zb0-9CJZx+=c*6pwMrN70Al)jqNQ4%&-%LLbO}oN|9s{Mm+-BacdR}G=-ah`#7UI6m z@^)bW#Qw66W5P&6qB2aO$GmhZ9_a0#bfN{o@dVIJAX?h16n!Pe!2tkM$zRz|A7OZS zS9#JSuTR%rOPRiSrjh}2fFZa|sTq)v@6->Gv^Z8U0ZZQ(T$8OLitcE-wKz4Xy0MB= z#Cps$GXzGhuWD7LQ;@ou0qgSi^(j~Yd93Q`8bKdA%t2feG-@3xX(CS~_WgZggOIq^ zPULfJYGM5BxI06#mo-xc1+(9aKJUiMEcDN^f|S%rymwxk$X`EAP1Dr~q66vaqkMZ} zp)=_I@6d3AgRJyAGjFnY%_G3utS($4o0h4=H&@AH%4n!5J9L5kiX@Ct_?*M7 zN|k4N1XdyPF7y#1DK#Sm<2#839~6!Ug_C&vRGg)+Sgp|Vf-D8Oc~|_$2Bd5W6}t$z6ky5h5E0gRJ8D4Ap{}&j~y1qx1@B zaSxYMJ_R=;M_=|=ShvN;Mls?}Oi-6W=1GB!XRt7Tq>$n|NfrQ##%QsbgPY|k@AErp zrD`=;*BPxQQ~$7SP3lR;-1{^qh9+VFG~L~Hd5hdcvd)rs&0!2qeD3yuR2)`iA_ zxEhRt9P|Z9oF`rqOHhE7Dya>Ga}I3H0WhIQhT>3((uL4X*jm$SAt#FCt6|Ilo?*$u z$HbF!ME59okiSWSbH+JP8M?G|;rIdwJJC~69(%CQOjEEx!7l5<9%u%b^W8`re0S(R z2<;eHouG9mJn_LCYbJ*T;>f@YafJL$HF+jZTecFw$FLF^-u(CKB0tlA!ptbnF!B&6 zOH5B%S~J#y-s6l)fTSvA2yn zxTa{2c#b%XaO{c@L=~D~RNv@+wrAx-XhHoH0{!ODfxafjoNe*Bu;Ww%j&wt6Sf3e1iI_3&@1Zh+UbHyDvG;C+K=sZ%=X%jocM zZ*bqE%p>=Npya_$#_lOaQxyUEfx{7_y_db=y$xJkT<^IsxpKK+Q?I#pxWX;ihtP+7 zXx_yu$x#$|#D~;|LIP%Fnq|Ed8Gc~=aoiSgCh$aS3iL^ON_kRGqOoHij^7!o_)(tV z@S~0TQ6Yw^7&MUZi6)s=hrwOzByB~}GpIGNHC*_UU%5}=lheJ>#TF_af?}v~C?u37 z^h2m3Ds7~I44e!|rouF9Q?#!pQVI`GCV5k0Lyb@4MsrLv?3Zo{b)t-NE`bJ?y7w51uyU?zXH)Z)#eZq0XU zHtJ2~7kY^m2Zd=R&B}F#*Ks8Jqta|z4oyoGb(glFzv_PlN4G{;Qryi~eO+~-(jrQ} zi4UUJr3YIiX(XZ~G~WzKtay!Vr+A<|nmmfUsx~pU>a_;7@cM=N#dTTJiF-<3ByAaf zF``A~700D)lNFOEvsn|IGZ8a`vo@2S1&T8kdkp(Y2X2Qk`!@Rs2jz2iQ!SOM6IOeS zho>{Alds0c)kaNcqPqQwU4cbp+SW*~g*{wRh*aof!H6kn% zT(RC+n%KozRm%Hw(xX$3pV7b%*jssU{asL#%pR0*)fl%Jy%<@tS7$C4 z1y@@LiJo<1Y{Qq=ZfYxXt9qAjmjP%{`Ywyg;egV5mgTy+>xlFU()seSW8&$Q)NbKF zK0A&Z>~3J!*Y%CP>i+y;+@1X`z4gVz!ae-`j~mwup9a!Tq1z(6W`{LH#lwVKho>#0 zx0s5-eI~qB{ZB{Fb9b4SjQ8P>I(K3y94J5}2x51jU*IwvCE`93O7K#!1P6 zQCUiiq@}P_+g1lH#0VG|Xc-u;+|g9Z2oYcmEcgE%xCQYUw3?n5(%ULdC1n^eHzc%O zS;I0=zt82|Kod$a9Hvk;r`M*BaS|2~5(G6mu4tSvji>5#dnavYXvOn%yYGVkerIT5 zNT%<=^FU^XuhjI>EYTD#KrE(^%T$U|v6CMtW-Op2WJc8t{maF}t!;i4p(Jyk7ibYP z?8Zup`xte|l{?axO0K6>AlS^z8UI8@JmNN3G4j$VY5mR2{h)LREVgv{@hlNz1YwEMw1Ngaf(IkPz{Iq45QO}9dC3&ljQ+1(PXmZlc)k<;?DLAko2?z3m5@hOxfJFQ+nc274pQj<@|`>!s4LgP+9+C`S{7M(h$7N`)Ef#PhR1E!*(R( z+H>8Xnx4fES}}z-I}7c(efBqhg$kX!Bc3>)#;ho|+&=i-JVziMQkqf_2*>+t-RM3$ z9iR4hG@vhFWRP8vwLZ{24F*$>O+<-tz20$U+$KEL9;MFJgkhJ);pVUA&u4?YM4u$D zOgAlFvQhoD+qk@3k3bC_6)}C$7###1Sw5Oq+SkW{?tG`s15*8z9jg9f=eg_o2LlhP z(W)ZZ`C?+}x-ST~g-@mD^JCzbH42p?Rh7sE(;Em&fFz!Z0_^%IOo8fGgaBu9z-C@* zmY04`>MP7N{0r{*6?EDZN!)>Os*Q>y+PZF8Nm0=VOB}fy+YwM-pLAgVDg-T6jd+p( z+|23qA|hNZ?*#Y{DN()}LSM-Oc@Eb4{b4Yn2wl9zvLHO-tYEF4{|!4TPk zT#7eSvo>0~5M5=Z_hw)Rb`x{3sRg@-!)m_#DvPj!QRp3y@v?Rzck*z z@&9y#XsG_B0>Qx~*nWcw|I_0ieje2a^WsH`JaV;P+4>V3-d6^2T;C(w~3wojv~V$XOtH0>c+uP@U~!1y2_s?3KE zX$uRwm%X?m%1TPnp3m2lmVd{1G;6J~9nj1fi&eAvid8cCbd2=o=;$$czmf2A?F=Py zx40eiVPl6XHO2yw@q2tUp zTWOVO4f5EzHM~U`X|q)Oiy6smEJKjZzvpB9(;lJU>sqn697Qjs*PBBwXtQfvYe8)) zuU(Q}y?v9Q$5~E@VW@Ajkf2ge7>4rSvCQ5rJl^Tz4zNmwz{UaL#skF*Nl|CEYG70a zmB%-`%5q1?jH2*(#o>dQ|Loru@%=l0YHI4=D~Ye?%=pY&6YaoZ z!XW&Ui=E+g+u4!`4Qb}d@8rW=9Zwfg+I>DwkV*?FiLbtW_&Ps3x%l{Rqc@eaT-(n_ z`gCtPaT$`|Y4($e`5CO~!Q^1jTZ$y)nGb4AHKb_o+f(l5Wn!HwBrxSFBqSylnh_JC zirQ{8d|v4!b35&e#OE$l zOrATSwc{Kc+o0%q$;;30`)X9`&8Ed9*>n7{v|XsO%b2Y{i2LztWaQNSFSiR!1K)4e z-caL?@B%6hpW`J;B}vI3t|@kQo^wFA^YyHbx8LWxD6?mE=2ivE-P}u@kuju=il$u> zZpfb4M*-^9G+w*Ww}Fg;JDx3#$pAg9HRWHlDh%{4b8zS@fJJ@QfhMmn&&-YM=ZBR- z=&-08$GW%86EHBX=l!I-NTmi%@nI>i5A*gt!W*Uba4Cq zhg5V?U9GcR%41H>#L)95%e8jjlD&{pJWvd|P*Nool{k~vY{^H2Y@8y~zDNx~(-fJg zkM!Tg7Wc}LYQ@kLQ-!`roz%ug0b)%_RQ8Wr6I0XfgCw#D9fk^GH+$o9Y6d1TB)qnJ z?I~zaUD(aRj2}>8+@`Z^8Sy7fdcq>PD6k88^sANR^=MF!uW3$rbE$_^)PMTiJLi41 zNKomEG-L?PC1;@B3K0CV9uYl?sJrK~9%3dwY2cNo^D&wssL^9PGQ+7~h7|CQ3U;;$mW7r&dVJIsHd z4N}!E*SA>p1~-pX%gr?|3EvsZ=1O?)WP4XcN27CtS9Q`zoyS7s7n*dDm^6!Oe|_%A z@_afUP&!_!yJR}vpSDuc*H4=`HM^YExs>_po^?&2#o#jgQ)6ZW?zH{!M5qwW2tVL7 zNry>9LA}yQWV+ea&Slx5!Dd!dDC@b#-#<*R&8sd|*fUaISy^O&yX6t_K?LLqM^67t z1svI5ZurhLsPr+Z-eLJb^9Tau7Br+5W+(2F6g-%(1V7yR>ks$aFL?!^DIaOIXBhQG zeRd?_u{O4yEBn<*b0+rsRI^xX^MmWR+wm)>W)WR!-7P<8uEY`A{E#Z*T~91{;VJ@@ zfnhaHeZ6UTK-LSZd%cNz`RU9 zj*Ar%5A`}M-yP*&Fl2IlFxxlWdzgD9n7NIR zRiT(HTi2YDkYLUSUfooeJF|>AaW!;f4iu-59=V)T?{%BHuiGd|v z)C<z*RjRuVp8X9k zlzi+zZ|F`eN|3~EXqn~fmczCCa9D}ELz*fpsHP@s{R(8jn>Fax-|Ov_Mj`wyO`P>H zH)If*HEW=pjDw_9*4ol+$8z)p8x5+`L;l5tHFqWu4#`zykBE@aZq>^!Pv+wzFiRlP zk|(Q7m_w_PRkeS})S_ik8| zW^ua}g>P+?yplcJBXI?4WURjRL{57cEfbDWd{Acq%=m&Oel$Ld_Sbb`_PL6NFgz5L zdFC3&#tlT(4u|FXRBYOp&t)$B!2y~-J@yL;MUH)_rHOdt3VQx@iY9`>!6wQBVOZVu<3nM^cP4J&P? zy?cMqdnN?>y`9Lj888%+uP=|E_JxFyHddFx9kf@Ta*}_%iaaj~eitXxgZ@+*!Z)x# zY}m&4E%I?y?j)vzeTo+@S6uwZxAgcOSFHP^iCGk;C7dXP+{(hNPwo-1D11~Fte4?@ z!?q=S2)hz-YH(<(-94~iAM~@a=dYeBC-g@)A=ERA- z#aO)gY@|lpm8Lu0|Lrthbd?6d$Z2oF#PpfpE=9KE?oJnDi&a$jNhOnF?g=3miKxMT zk?XobRd`Z!Ems%$U>MYqw;CNn@!is7Fuu^XdU|@=wfuOwAvFS*(e@}i#iQf6$aA&I zq=&9UZwnEfl+v8p-@oJ3<(o#Vn8=AcN_AsM5Jxnj(D;;yC#|6aIhELxdf z%?FxjcVV8+p2C6xWRTx=**M|NQxBHdo~~byJ6@dXa1xuyYBb;7T|X%}1=N10AwqMi zM&QTD*irfcj_fNA@?w!nW=ZS1VHNOq(6-?f7JjdK{)gcYXc3Rct`hVTRbN{2Wjk_7 zUBCfNmt&`nZD+T&wUEmtyUJf2c~oVgTq}@i#rGaz%!s~EG)zwZ*F>0MX=UW3& zo#vk12qX^O3bP!B%!-_xllpzXJ|0@)(=2Umw%&1st;9xU*TR&An&PlG;lqREnM`}A zAGpHk+FkyRWufliYwVcDyycVI8+Kj~j^`Aows%{*gf43G28~~G31B#wiK2d6RBA0% z#UMOroFC4g+F*=&eAnr!VWiW#4$)?s3d11f_5Uqg?J^-ozw=4`NFk^OmtwBmJ3Wec6~HE8K~K_ulx66sMB&&Gp1yexN}9o7d^nd-}A$+qVR5&C$gDBGJ7F~^;&xy zO{|j}iD493nwl8Bl@*uYA*9$}H`8rym!EY{b!~QfX|>0q1-Ly(P<|@tlB%;`>|XH(U%K=! zd#+qaN(6C1B&Z9>LNFYbu731SQw6xu;@P7|ad=*spQD@cjGW6%l#*?Qc#L(?7$LzL zQ!^^17`q+GY0e*T47rE3cIdf`XF(OzmVNd|2~4upEy1V0EnJ zjIg!iA^gS>(8dc9<^yNd>RUIUc!LkX6d65w4usUU%o{;v(}%|VPa(T=qw{5g7MIr$tzggv? zqQv-+eVy2ZiG)4NYX7^H>Rx7+@bs>O`!@9TZFiwkYH@R*3wu`nv1B=#hJu?ZgH4rS z{BX73yblA1Xic83jb}PlAg_O&$0gkttZZR^#7|bC?gE}Js%yI6O|Kiz67jx3w((nD zs9uEnzC3(^4t@1He?Ra^JBI}+z z8>u$#f*l15L_-oVwj}P3&Z6$wI6$kjV^(WO59sm|#^{5j)N`_wzV=4G*SyNC4szYN zu*&IthH-D{Z)4*%&H*`cxj<$;Y^Om6!FW2qs#Ml{*8}T_?>FM95xl!Iq(NpDa;8X_ zKH91-fhsgq(697vzK^VEAu1d>^_c zA&}bL^e6(hDGdPycy^Cp{!d#7Y|zcYw)Koh;I7jJMh9TbU*It>4bee23JWRvY!220 zR}eS6K~Ha>K-J_r?=2mhh^iza2M~)h3_5g%3kHYA;)V28RjdeYQYu#y$J}GHv)t*5 zEd2z3C*qW3^U|Jksv{74*)vcxr{ELcn=1drU%qpRxf4&&jtI|0H%nN;21NR)S5w4{ zf{Nsr!0kB&K3kVq3ZDr1VO;VtK|?(!C52gIuB?=Ca!LM$sz6V#23}_Xx_p}}oJ%=J z)MJcmRpy{E#ep4wSL5)hWQvHs9|(0;JXT9W^R(aSj@t}$FKqx2I$~3Kc*NRFul~q@ zB<{*-H8S}CQDWWiU~{Rnyf?ZlU0Gc^xyPc5lv3?qo=<1J5G>xR>D}jBv593QtjU1jw~Cx&$h$WwnrZQDw7TnX5bU1o`fzugG(NI{B)7BX4of$ zE=dMDx5XR#`akc!XCn%>HnYfBL?$Su>mppT{6Y;7l< zqozbk^2v5+-$?nVc2IBya(5xbh3uL|WfWz4D}O+%zV7ixLe-qL4rn`rMuIu%0_V!u zFQ0^W)W&p~WL?_D(MPX|)3wY5%91fHj)S86(yL#d?$K)RxU46ru6@?03*^nNohMLD zWEuY8=kTvVjo?ASgqzt?Ip|vHfQx4B@)KO68FkQ5HB1A`hZwR#LegV4xU7T~P9Gxk z))XDb=k1=iJx%1IvikiMwxv?DB>=dw&5jB#+DfJh@eKBc3adN)D z`^0I?8;lStiutXzEe8uREVz>_>si%A?Qb66gEd%{JUvI{xGcD$fSjrtyNc#sk7l zM9;c{9TuuqTkkf9z1Rb9OSSfGN7;<&BABZAs28UShj}Pz@MxYSB1|X_jSe^u zgKW0kZx|Ms?-nAu+k#C@(J`=k8qq@vdyz ziV>P|aW)FNt?pbEz2C;=v~S^&Emhw_9OSHwulXs!Tfbd75yTzeyC~>DdiRj!a2Dfqfm`TKZoSMo=?wF{Ov zobk^+W2PZa&6iIQZkBq#1o>bs1~14|7lr7P&FLOmE~}v`bQxgfaP^d{-V%R34mVaK zl~sGz!(0YEp=+*>=XJ_x0)hQp&28b{xV5>`!ad!mMM`&%g-U_3QmnVT7n+z469iu$ zA>)KE6h)n%m0?cbK|g42O;Tix$OB&eM|;^6qS2ZCy;C X{1&rAy^{W)A1CrMD$*5_#zFrFT(iK( From 3f68a95e9ccc3a16de3b296e9ef9d25d2bd8cfa3 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 26 Feb 2025 09:20:57 +0200 Subject: [PATCH 183/293] docs: Document how to use GH workflows in forks (#821) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- .github/CONTRIBUTING.md | 9 ++++++ .github/workflows/ci-cd.yml | 28 ++++++++++++++++-- .../contributing/enable_actions_in_fork.png | Bin 0 -> 200688 bytes 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 assets/contributing/enable_actions_in_fork.png diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9da5c6410..a15eedfa6 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -13,6 +13,7 @@ * [Add code](#add-code) * [Finish with the documentation](#finish-with-the-documentation) * [Contributing to Python code](#contributing-to-python-code) +* [Run tests in your fork](#run-tests-in-your-fork) ## Run and debug hooks locally @@ -182,3 +183,11 @@ You can use [this PR](https://github.com/antonbabenko/pre-commit-terraform/pull/ ```bash tox list ``` + +## Run tests in your fork + +Go to your fork's `Actions` tab and click the big green button. + +![Enable workflows](/assets/contributing/enable_actions_in_fork.png) + +Now you can verify that the tests pass before submitting your PR. diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index a4e545386..f5e7c8d8f 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -52,8 +52,7 @@ env: PYTHONIOENCODING PYTHONLEGACYWINDOWSSTDIO PYTHONUTF8 - UPSTREAM_REPOSITORY_ID: >- - 69382485 + UPSTREAM_REPOSITORY_ID: 69382485 # Repo ID of antonbabenko/pre-commit-terraform run-name: >- ${{ @@ -255,6 +254,13 @@ jobs: name: 📦 ${{ needs.pre-setup.outputs.git-tag }} needs: - pre-setup + # Prevent run 'push' events for the branches in upstream repository as it + # already covered by 'pull_request' event + if: >- + github.repository_id != needs.pre-setup.outputs.upstream-repository-id + || github.event_name != 'push' + || github.ref_name == github.event.repository.default_branch + runs-on: ubuntu-latest @@ -502,6 +508,12 @@ jobs: codecov-token: ${{ secrets.CODECOV_TOKEN }} check: # This job does nothing and is only used for the branch protection + + # Separate 'pull_request' check from other checks to avoid confusion in + # GitHub branch protection about which check is required when multiple + # events trigger this workflow. + name: >- + ${{ github.event_name == 'push' && 'check​' || 'check' }} if: always() needs: @@ -517,3 +529,15 @@ jobs: uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} + # Needed to not fail on skipped 'push' events for the branches in + # upstream repository as they already covered by 'pull_request' event + allowed-skips: >- + ${{ + ( + github.repository_id != needs.pre-setup.outputs.upstream-repository-id + || github.event_name != 'push' + || github.ref_name == github.event.repository.default_branch + ) + && 'lint, tests' + || '' + }} diff --git a/assets/contributing/enable_actions_in_fork.png b/assets/contributing/enable_actions_in_fork.png new file mode 100644 index 0000000000000000000000000000000000000000..d39433bea4eecd413fdaecf0be54917cf79edad4 GIT binary patch literal 200688 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
Docker
@@ -249,6 +212,47 @@ E.g. `C:\Users\USERNAME\AppData\Local\Programs\Python\Python39\Lib\site-packages
+Full list of dependencies and where they are used: + + +* [`pre-commit`](https://pre-commit.com/#install), + [`terraform`](https://www.terraform.io/downloads.html) or [`opentofu`](https://opentofu.org/docs/intro/install/), + [`git`](https://git-scm.com/downloads), + [BASH `3.2.57` or newer](https://www.gnu.org/software/bash/#download), + Internet connection (on first run), + x86_64 or arm64 compatible operating system, + Some hardware where this OS will run, + Electricity for hardware and internet connection, + Some basic physical laws, + Hope that it all will work. +

+* [`checkov`][checkov repo] required for `terraform_checkov` hook +* [`terraform-docs`][terraform-docs repo] 0.12.0+ required for `terraform_docs` hook +* [`terragrunt`][terragrunt repo] required for `terragrunt_validate` and `terragrunt_valid_inputs` hooks +* [`terrascan`][terrascan repo] required for `terrascan` hook +* [`TFLint`][tflint repo] required for `terraform_tflint` hook +* [`TFSec`][tfsec repo] required for `terraform_tfsec` hook +* [`Trivy`][trivy repo] required for `terraform_trivy` hook +* [`infracost`][infracost repo] required for `infracost_breakdown` hook +* [`jq`][jq repo] required for `terraform_validate` with `--retry-once-with-cleanup` flag, and for `infracost_breakdown` hook +* [`tfupdate`][tfupdate repo] required for `tfupdate` hook +* [`hcledit`][hcledit repo] required for `terraform_wrapper_module_for_each` hook + + +#### 1.1 Custom Terraform binaries and OpenTofu support + +It is possible to set custom path to `terraform` binary. +This makes it possible to use [OpenTofu](https://opentofu.org) binary (`tofu`) instead of `terraform`. + +How binary discovery works and how you can redefine it (first matched takes precedence): + +1. Check if per hook configuration `--hook-config=--tf-path=` is set +2. Check if `PCT_TFPATH=` environment variable is set +3. Check if `TERRAGRUNT_TFPATH=` environment variable is set +4. Check if `terraform` binary can be found in the user's `$PATH` +5. Check if `tofu` binary can be found in the user's `$PATH` + + ### 2. Install the pre-commit hook globally > [!NOTE] @@ -276,6 +280,14 @@ repos: EOF ``` +If this repository was initialized locally via `git init` or `git clone` _before_ +you installed the pre-commit hook globally ([step 2](#2-install-the-pre-commit-hook-globally)), +you will need to run: + +```bash +pre-commit install +``` + ### 4. Run Execute this command to run `pre-commit` on all files in the repository (not only changed files): From e935ef9e3dd63e82be16eeaa41683b931040caf0 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 27 May 2025 23:04:55 +0300 Subject: [PATCH 235/293] chore(ci): PPrepare to switch to Merge Queue for PR merging (#893) --- .github/workflows/build-image-test.yaml | 1 + .github/workflows/codeql.yml | 1 + .github/workflows/dependency-review.yml | 1 + .github/workflows/pre-commit.yaml | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index edc36afc5..8711514b9 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -1,6 +1,7 @@ name: Build Dockerfile if changed and run smoke tests on: + merge_group: pull_request: permissions: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 89dc03bed..c5878ef5f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -15,6 +15,7 @@ on: push: branches: - master + merge_group: pull_request: schedule: - cron: 0 0 * * 1 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 3b3d9d5c0..c9d5ab0e5 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -10,6 +10,7 @@ name: Dependency Review on: + merge_group: pull_request: permissions: diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 9d6e1c1f8..afad86b56 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -1,6 +1,7 @@ name: Common issues check on: + merge_group: pull_request: permissions: From ec0da032b672cdea9cd6eca55ef64a2df63110a8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 23:17:23 +0300 Subject: [PATCH 236/293] chore(deps): Update codecov/codecov-action action to v5.4.3 (#894) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/reusable-tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 29e65ba47..2e28908b9 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -375,7 +375,7 @@ jobs: if: >- !cancelled() && steps.tox-run.outputs.cov-report-files != '' - uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0 + uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 with: disable_search: true fail_ci_if_error: >- From 497100e83695a5cd301b1086bc90648f8fec92f3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 20:26:27 +0000 Subject: [PATCH 237/293] chore(deps): Update codecov/test-results-action action to v1.1.1 (#895) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/reusable-tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 2e28908b9..748e97ccb 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -399,7 +399,7 @@ jobs: if: >- !cancelled() && steps.tox-run.outputs.test-result-files != '' - uses: codecov/test-results-action@f2dba722c67b86c6caa034178c6e4d35335f6706 # v1.1.0 + uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1 # FIXME There is a bug in action which provokes it to fail during upload # Related issue: https://github.com/codecov/codecov-action/issues/1794 continue-on-error: true From 95d66e107f765d34c2dbbfa3e5b04bfc3742039f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 20:30:36 +0000 Subject: [PATCH 238/293] chore(deps): Update github/codeql-action action to v3.28.18 (#896) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c5878ef5f..792199c09 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in @@ -59,7 +59,7 @@ jobs: # If this step fails, then you should remove it and run the build # manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 # ℹ️ Command-line programs to run using the OS shell. # yamllint disable-line rule:line-length @@ -74,6 +74,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: category: /language:${{matrix.language}} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 8abeecd69..40dec7de9 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -74,6 +74,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: sarif_file: results.sarif From d016ae546d8b88b7eafd675822859a6fb3fd4ebf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 20:34:55 +0000 Subject: [PATCH 239/293] chore(deps): Update actions/dependency-review-action action to v4.7.1 (#898) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index c9d5ab0e5..a0fc2f821 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -23,4 +23,4 @@ jobs: - name: Checkout Repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Dependency Review - uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 + uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 From dd939dd6d8f712e80a1b9ddd63ecda78c4dce4dc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 20:39:05 +0000 Subject: [PATCH 240/293] chore(deps): Update pre-commit hook astral-sh/ruff-pre-commit to v0.11.11 (#897) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b153330ee..a519084c7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -99,7 +99,7 @@ repos: # Python - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.4 + rev: v0.11.11 hooks: - id: ruff args: From 54468bb79590e155b38b462be44937c4809aa84e Mon Sep 17 00:00:00 2001 From: bai <17348+bai@users.noreply.github.com> Date: Thu, 29 May 2025 19:20:07 +0300 Subject: [PATCH 241/293] fix(`terragrunt_*` hooks): Use new subcommands for terragrunt v0.78.0+ instead of deprecated ones (#901) --------- Co-authored-by: MaxymVlasov Co-authored-by: Julien Rottenberg Co-authored-by: George L. Yermulnik --- hooks/_common.sh | 31 +++++++++++++++++++++++++++++ hooks/terragrunt_fmt.sh | 12 ++++++++--- hooks/terragrunt_providers_lock.sh | 8 +++++++- hooks/terragrunt_validate.sh | 8 +++++++- hooks/terragrunt_validate_inputs.sh | 12 +++++++++-- 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index defc249a8..7281b0244 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -606,3 +606,34 @@ function common::export_provided_env_vars { export $var_name="$var_value" done } + +####################################################################### +# Check if the installed Terragrunt version is >=0.78.0 or not +# +# This function helps to determine which terragrunt subcomand to use +# based on Terragrunt version +# +# Returns: +# - 0 if version >= 0.78.0 +# - 1 if version < 0.78.0 +# Defaults to 0 if version cannot be determined +####################################################################### +# TODO: Drop after May 2027. Two years to upgrade is more than enough. +function common::terragrunt_version_ge_0.78 { + local terragrunt_version + + # Extract version number (e.g., "terragrunt version v0.80.4" -> "0.80") + terragrunt_version=$(terragrunt --version 2> /dev/null | grep -oE '[0-9]+\.[0-9]+') + # If we can't parse version, default to newer command + [[ ! $terragrunt_version ]] && return 0 + + local major minor + IFS='.' read -r major minor <<< "$terragrunt_version" + + # New subcommands added in v0.78.0 (May 2025) + if [[ $major -gt 0 || ($major -eq 0 && $minor -ge 78) ]]; then + return 0 + else + return 1 + fi +} diff --git a/hooks/terragrunt_fmt.sh b/hooks/terragrunt_fmt.sh index 698b3843b..2d6697ae3 100755 --- a/hooks/terragrunt_fmt.sh +++ b/hooks/terragrunt_fmt.sh @@ -12,7 +12,13 @@ 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 + + if common::terragrunt_version_ge_0.78; then + local -ra SUBCOMMAND=(hcl format) + else + local -ra SUBCOMMAND=(hclfmt) + fi # shellcheck disable=SC2153 # False positive common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" @@ -46,7 +52,7 @@ function per_dir_hook_unique_part { local -a -r args=("$@") # pass the arguments to hook - terragrunt hclfmt "${args[@]}" + terragrunt "${SUBCOMMAND[@]}" "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? @@ -63,7 +69,7 @@ function run_hook_on_whole_repo { local -a -r args=("$@") # pass the arguments to hook - terragrunt hclfmt "$(pwd)" "${args[@]}" + terragrunt "${SUBCOMMAND[@]}" "$(pwd)" "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? diff --git a/hooks/terragrunt_providers_lock.sh b/hooks/terragrunt_providers_lock.sh index 9e3557aeb..21ee668fe 100755 --- a/hooks/terragrunt_providers_lock.sh +++ b/hooks/terragrunt_providers_lock.sh @@ -14,6 +14,12 @@ function main { common::parse_and_export_env_vars # JFYI: terragrunt providers lock color already suppressed via PRE_COMMIT_COLOR=never + if common::terragrunt_version_ge_0.78; then + local -ra RUN_ALL_SUBCOMMAND=(run --all providers lock) + else + local -ra RUN_ALL_SUBCOMMAND=(run-all providers lock) + fi + # shellcheck disable=SC2153 # False positive common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" } @@ -63,7 +69,7 @@ function run_hook_on_whole_repo { local -a -r args=("$@") # pass the arguments to hook - terragrunt run-all providers lock "${args[@]}" + terragrunt "${RUN_ALL_SUBCOMMAND[@]}" "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? diff --git a/hooks/terragrunt_validate.sh b/hooks/terragrunt_validate.sh index 5a62c3913..54b7a98d6 100755 --- a/hooks/terragrunt_validate.sh +++ b/hooks/terragrunt_validate.sh @@ -14,6 +14,12 @@ function main { common::parse_and_export_env_vars # JFYI: terragrunt validate color already suppressed via PRE_COMMIT_COLOR=never + if common::terragrunt_version_ge_0.78; then + local -ra RUN_ALL_SUBCOMMAND=(run --all validate) + else + local -ra RUN_ALL_SUBCOMMAND=(run-all validate) + fi + # shellcheck disable=SC2153 # False positive common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" } @@ -63,7 +69,7 @@ function run_hook_on_whole_repo { local -a -r args=("$@") # pass the arguments to hook - terragrunt run-all validate "${args[@]}" + terragrunt "${RUN_ALL_SUBCOMMAND[@]}" "${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 index 0c8d79e2d..39e8e484e 100755 --- a/hooks/terragrunt_validate_inputs.sh +++ b/hooks/terragrunt_validate_inputs.sh @@ -14,6 +14,14 @@ function main { common::parse_and_export_env_vars # JFYI: terragrunt validate color already suppressed via PRE_COMMIT_COLOR=never + if common::terragrunt_version_ge_0.78; then + local -ra SUBCOMMAND=(hcl validate --inputs) + local -ra RUN_ALL_SUBCOMMAND=(run --all hcl validate --inputs) + else + local -ra SUBCOMMAND=(validate-inputs) + local -ra RUN_ALL_SUBCOMMAND=(run-all validate-inputs) + fi + # shellcheck disable=SC2153 # False positive common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" } @@ -46,7 +54,7 @@ function per_dir_hook_unique_part { local -a -r args=("$@") # pass the arguments to hook - terragrunt validate-inputs "${args[@]}" + terragrunt "${SUBCOMMAND[@]}" "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? @@ -63,7 +71,7 @@ function run_hook_on_whole_repo { local -a -r args=("$@") # pass the arguments to hook - terragrunt run-all validate-inputs "${args[@]}" + terragrunt "${RUN_ALL_SUBCOMMAND[@]}" "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? From 2c8b87754df7a582f48bd982ff10a0015b8895da Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 29 May 2025 16:55:30 +0000 Subject: [PATCH 242/293] chore(release): version 1.99.1 [skip ci] ## [1.99.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.0...v1.99.1) (2025-05-29) ### Bug Fixes * **`terragrunt_*` hooks:** Use new subcommands for terragrunt v0.78.0+ instead of deprecated ones ([#901](https://github.com/antonbabenko/pre-commit-terraform/issues/901)) ([54468bb](https://github.com/antonbabenko/pre-commit-terraform/commit/54468bb79590e155b38b462be44937c4809aa84e)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e51bd263c..72b892406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.99.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.0...v1.99.1) (2025-05-29) + + +### Bug Fixes + +* **`terragrunt_*` hooks:** Use new subcommands for terragrunt v0.78.0+ instead of deprecated ones ([#901](https://github.com/antonbabenko/pre-commit-terraform/issues/901)) ([54468bb](https://github.com/antonbabenko/pre-commit-terraform/commit/54468bb79590e155b38b462be44937c4809aa84e)) + # [1.99.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.98.1...v1.99.0) (2025-04-14) From 79085a61bf51c92032101ce5a29525f0ec76fc86 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 00:04:22 +0300 Subject: [PATCH 243/293] chore(deps): Update docker/build-push-action action to v6.18.0 (#899) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 4 ++-- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 8711514b9..e728cd4b7 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -62,7 +62,7 @@ jobs: - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . build-args: | @@ -113,7 +113,7 @@ jobs: if: >- steps.changed-files-specific.outputs.any_changed == 'true' && matrix.os == 'ubuntu-latest' - uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . build-args: | diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index cfd70a48d..f673be00d 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -47,7 +47,7 @@ jobs: - name: Build and Push release if: github.event_name != 'schedule' - uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . build-args: | @@ -64,7 +64,7 @@ jobs: - name: Build and Push nightly if: github.event_name == 'schedule' - uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . build-args: | From 7f976b5310285defdff26e666b69086743fcda38 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 6 Jun 2025 01:02:39 +0300 Subject: [PATCH 244/293] ci: Integrate zizmor checks to repo (#818) That PR integrate one more static analysis tool, as CodeQL is good, but it not cover all cases Here is an article about zizmor value - https://blog.yossarian.net/2024/12/06/zizmor-ultralytics-injection zizmor repo - https://github.com/woodruffw/zizmor --------- Co-authored-by: George L. Yermulnik --- .github/workflows/build-image-test.yaml | 1 + .github/workflows/build-image.yaml | 10 +++- .github/workflows/ci-cd.yml | 40 +++++++------- .github/workflows/codeql.yml | 2 + .github/workflows/dependency-review.yml | 3 + .github/workflows/pr-title.yml | 2 +- .github/workflows/pre-commit.yaml | 14 +++-- .github/workflows/reusable-tox.yml | 73 +++++++++++++++---------- .github/workflows/scheduled-runs.yml | 3 +- .pre-commit-config.yaml | 6 ++ 10 files changed, 95 insertions(+), 59 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index e728cd4b7..9c6065d49 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -36,6 +36,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 + persist-credentials: false - name: Get changed Docker related files id: changed-files-specific diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index f673be00d..ae6e407d0 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -21,6 +21,9 @@ jobs: steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Set up Docker Buildx uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Login to GitHub Container Registry @@ -30,10 +33,13 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set tag for image + env: + REF_TYPE: ${{ github.ref_type }} + REF_NAME: ${{ github.ref_name }} run: >- echo IMAGE_TAG=$( - [ ${{ github.ref_type }} == 'tag' ] - && echo ${{ github.ref_name }} + [ $REF_TYPE == 'tag' ] + && echo $REF_NAME || echo 'latest' ) >> $GITHUB_ENV diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 966d76211..872605850 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -10,6 +10,9 @@ on: - pre-commit-ci-update-config # pre-commit.ci always creates a PR pull_request: workflow_call: # a way to embed the main tests + secrets: + CODECOV_TOKEN: + required: true permissions: contents: read @@ -112,6 +115,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 + persist-credentials: false - name: >- Calculate Python interpreter version hash value for use in the cache key @@ -208,6 +212,8 @@ jobs: ) - name: Set the target Git tag id: git-tag + env: + DIST_VERSION: ${{ steps.scm-version.outputs.dist-version }} run: | from os import environ from pathlib import Path @@ -218,13 +224,13 @@ jobs: mode=FILE_APPEND_MODE, ) as outputs_file: print( - "tag=v${{ - steps.scm-version.outputs.dist-version - }}", + f"tag=v{environ['DIST_VERSION'].removeprefix('v')}", file=outputs_file, ) - name: Set the expected dist artifact names id: artifact-name + env: + DIST_VERSION: ${{ steps.scm-version.outputs.dist-version }} run: | from os import environ from pathlib import Path @@ -238,15 +244,11 @@ jobs: mode=FILE_APPEND_MODE, ) as outputs_file: print( - f"sdist={sdist_file_prj_base_name !s}-${{ - steps.scm-version.outputs.dist-version - }}.tar.gz", + f"sdist={sdist_file_prj_base_name !s}-{environ['DIST_VERSION']}.tar.gz", file=outputs_file, ) print( - f"wheel={whl_file_prj_base_name !s}-${{ - steps.scm-version.outputs.dist-version - }}-py3-none-any.whl", + f"wheel={whl_file_prj_base_name !s}-{environ['DIST_VERSION']}-py3-none-any.whl", file=outputs_file, ) @@ -268,7 +270,8 @@ jobs: env: TOXENV: cleanup-dists,build-dists - + SDIST_ARTIFACT_NAME: ${{ needs.pre-setup.outputs.sdist-artifact-name }} + WHEEL_ARTIFACT_NAME: ${{ needs.pre-setup.outputs.wheel-artifact-name }} outputs: dists-base64-hash: ${{ steps.dist-hashes.outputs.combined-hash }} @@ -282,6 +285,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 + persist-credentials: false - name: >- Calculate Python interpreter version hash value @@ -357,19 +361,13 @@ jobs: --quiet - name: Verify that the artifacts with expected names got created run: >- - ls -1 - 'dist/${{ needs.pre-setup.outputs.sdist-artifact-name }}' - 'dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}' + ls -1 "dist/${SDIST_ARTIFACT_NAME}" "dist/${WHEEL_ARTIFACT_NAME}" - name: Generate dist hashes to be used for provenance id: dist-hashes run: >- echo "combined-hash=$( - sha256sum - '${{ needs.pre-setup.outputs.sdist-artifact-name }}' - '${{ needs.pre-setup.outputs.wheel-artifact-name }}' - | base64 -w0 - )" - >> "${GITHUB_OUTPUT}" + sha256sum "$SDIST_ARTIFACT_NAME" "$WHEEL_ARTIFACT_NAME" | base64 -w0 + )" >> $GITHUB_OUTPUT working-directory: dist - name: Store the distribution packages uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 @@ -386,7 +384,7 @@ jobs: retention-days: 30 lint: - name: 🧹 Linters${{ '' }} # nest jobs under the same sidebar category + name: 🧹 Linters${{ '' }} # Group jobs in sidebar # zizmor: ignore[obfuscation] needs: - build - pre-setup # transitive, for accessing settings @@ -444,7 +442,7 @@ jobs: codecov-token: ${{ secrets.CODECOV_TOKEN }} tests: - name: 🧪 Tests${{ '' }} # nest jobs under the same sidebar category + name: 🧪 Tests${{ '' }} # Group jobs in sidebar # zizmor: ignore[obfuscation] needs: - build - pre-setup # transitive, for accessing settings diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 792199c09..fa105c1b8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -44,6 +44,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index a0fc2f821..b158f07aa 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -22,5 +22,8 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Dependency Review uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 5c487ccf3..8c76ed8ba 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -1,7 +1,7 @@ name: Validate PR title on: - pull_request_target: + pull_request: types: - opened - edited diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index afad86b56..fbaa235ac 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -14,17 +14,20 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - run: >- git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* - name: Get changed files id: file_changes + env: + BASE_REF: ${{ github.base_ref }} + SHA: ${{ github.sha }} run: | - export DIFF=$(git diff --name-only origin/${{ github.base_ref }} ${{ - github.sha - }}) - echo "Diff between ${{ github.base_ref }} and ${{ github.sha }}" + export DIFF=$(git diff --name-only "origin/$BASE_REF" "$SHA") + echo "Diff between $BASE_REF and $SHA" echo "files=$( echo "$DIFF" | xargs echo )" >> $GITHUB_OUTPUT - name: Install shfmt @@ -48,10 +51,11 @@ jobs: )" > hadolint && chmod +x hadolint && sudo mv hadolint /usr/bin/ - # Needed for pre-commit fix push to succeed + # Needed for pre-commit fix push to succeed - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 + persist-credentials: false ref: ${{ github.event.pull_request.head.sha }} # Needed to trigger pre-commit workflow on autofix commit. Guide: # https://web.archive.org/web/20210731173012/https://github.community/t/required-check-is-expected-after-automated-push/187545/ diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 748e97ccb..5ee1edba3 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -150,9 +150,10 @@ jobs: steps: - name: Export requested job-global environment variables if: inputs.environment-variables != '' + env: + INPUT_ENV_VARS: ${{ inputs.environment-variables }} run: >- - echo '${{ inputs.environment-variables }}' - >> "${GITHUB_ENV}" + echo "$INPUT_ENV_VARS" >> $GITHUB_ENV - name: >- Switch to using Python v${{ inputs.python-version }} @@ -170,6 +171,7 @@ jobs: contains(fromJSON('["pre-commit", "spellcheck-docs"]'), inputs.toxenv) uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: + persist-credentials: false ref: ${{ github.event.inputs.release-committish }} - name: Retrieve the project source from an sdist inside the GHA artifact if: >- @@ -273,18 +275,24 @@ jobs: - name: >- Pre-populate tox envs: `${{ env.TOXENV }}` - run: >- - python -Im - tox - --parallel auto - --parallel-live - --skip-missing-interpreters false - ${{ - inputs.built-wheel-names != '' - && format('--installpkg dist/{0}', inputs.built-wheel-names) - || '' - }} - --notest + shell: bash + env: + INPUT_BUILT_WHEEL_NAMES: ${{ inputs.built-wheel-names }} + run: |- + tox_common_args=( + --parallel auto + --parallel-live + --skip-missing-interpreters false + ) + if [[ $INPUT_BUILT_WHEEL_NAMES ]]; then + python -Im tox "${tox_common_args[@]}" \ + --installpkg "dist/$INPUT_BUILT_WHEEL_NAMES" \ + --notest + else + python -Im tox "${tox_common_args[@]}" \ + --notest + fi + - name: Initialize pre-commit envs if needed if: inputs.toxenv == 'pre-commit' run: >- @@ -301,19 +309,24 @@ jobs: - name: >- Run tox envs: `${{ env.TOXENV }}` id: tox-run - run: >- - python -Im - tox - --parallel auto - --parallel-live - --skip-missing-interpreters false - --skip-pkg-install - --quiet - ${{ - inputs.tox-run-posargs != '' - && format('-- {0}', inputs.tox-run-posargs) - || '' - }} + shell: bash + env: + INPUT_TOX_RUN_POSARGS: ${{ inputs.tox-run-posargs }} + run: |- + tox_common_args=( + --parallel auto + --parallel-live + --skip-missing-interpreters false + --skip-pkg-install + --quiet + ) + if [ -n "$INPUT_TOX_RUN_POSARGS" ]; then + python -Im tox "${tox_common_args[@]}" \ + -- "$INPUT_TOX_RUN_POSARGS" + else + python -Im tox "${tox_common_args[@]}" + fi + # Generate nice SVG image of passed/failed tests in GHA Job Summary - name: Produce markdown test summary from JUnit if: >- @@ -352,13 +365,15 @@ jobs: && steps.tox-run.outputs.test-result-files == '' && steps.tox-run.outputs.codecov-flags != 'MyPy' run: >- - cat code-coverage-results.md >> "${GITHUB_STEP_SUMMARY}" + cat code-coverage-results.md >> "$GITHUB_STEP_SUMMARY" - name: Re-run the failing tests with maximum verbosity if: >- !cancelled() && failure() && inputs.tox-rerun-posargs != '' # `exit 1` makes sure that the job remains red with flaky runs + env: + INPUT_TOX_RERUN_POSARGS: ${{ inputs.tox-rerun-posargs }} run: >- python -Im tox @@ -368,7 +383,7 @@ jobs: -vvvvv --skip-pkg-install -- - ${{ inputs.tox-rerun-posargs }} + $INPUT_TOX_RERUN_POSARGS && exit 1 shell: bash - name: Send coverage data to Codecov diff --git a/.github/workflows/scheduled-runs.yml b/.github/workflows/scheduled-runs.yml index d8915c4b1..ba075e805 100644 --- a/.github/workflows/scheduled-runs.yml +++ b/.github/workflows/scheduled-runs.yml @@ -37,4 +37,5 @@ jobs: main-ci-cd-pipeline: name: ∞ Main CI/CD pipeline uses: ./.github/workflows/ci-cd.yml - secrets: inherit + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a519084c7..50c91499f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,6 +47,12 @@ repos: hooks: - id: gitleaks +# Github Action static analysis tool +- repo: https://github.com/woodruffw/zizmor-pre-commit + rev: v1.9.0 + hooks: + - id: zizmor + # Dockerfile - repo: https://github.com/hadolint/hadolint rev: v2.13.1-beta From 9aa7be503b56de4464f4dbac128231781c2413db Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 6 Jun 2025 02:22:53 +0300 Subject: [PATCH 245/293] chore(security): Pin pre-commit hooks by SHA, as it is immutable (#902) Pinned by `pre-commit autoupdate --freeze` as `pre-commit` in Renovate currently [does not support](https://github.com/renovatebot/renovate/discussions/22488) freezing style used by `pre-commit` --- .github/renovate.json5 | 3 +++ .pre-commit-config.yaml | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index ccac711fd..c3cbb66ff 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -15,4 +15,7 @@ commitMessageLowerCase: "never", // Disable auto-rebase on every commit to avoid reaching Github limits on macos runners rebaseWhen: "conflicted", + "pre-commit": { + enabled: false, // Use pre-commit.ci freeze instead + }, } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 50c91499f..c0d86e84e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0 hooks: # Git style - id: check-added-large-files @@ -43,25 +43,25 @@ repos: # Detect hardcoded secrets - repo: https://github.com/gitleaks/gitleaks - rev: v8.26.0 + rev: a248f9279b38aeff5bbd4c85cc6f15b64d27e794 # frozen: v8.27.0 hooks: - id: gitleaks # Github Action static analysis tool - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.9.0 + rev: d2c1833a059c66713cd52c032617766134679a0f # frozen: v1.9.0 hooks: - id: zizmor # Dockerfile - repo: https://github.com/hadolint/hadolint - rev: v2.13.1-beta + rev: c3dc18df7a501f02a560a2cc7ba3c69a85ca01d3 # frozen: v2.13.1-beta hooks: - id: hadolint # YAML - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt - rev: 0.2.3 + rev: 8d1b9cadaf854cb25bb0b0f5870e1cc66a083d6b # frozen: 0.2.3 hooks: - id: yamlfmt args: @@ -72,7 +72,7 @@ repos: - --implicit_start - repo: https://github.com/adrienverge/yamllint.git - rev: v1.37.1 + rev: 79a6b2b1392eaf49cdd32ac4f14be1a809bbd8f7 # frozen: v1.37.1 hooks: - id: yamllint types: @@ -83,7 +83,7 @@ repos: # JSON5 - repo: https://github.com/pre-commit/mirrors-prettier - rev: v4.0.0-alpha.8 + rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 hooks: - id: prettier # https://prettier.io/docs/en/options.html#parser @@ -91,7 +91,7 @@ repos: # Bash - repo: https://github.com/jumanjihouse/pre-commit-hooks - rev: 3.0.0 + rev: 38980559e3a605691d6579f96222c30778e5a69e # frozen: 3.0.0 hooks: - id: shfmt args: @@ -105,7 +105,7 @@ repos: # Python - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.11 + rev: 9aeda5d1f4bbd212c557da1ea78eca9e8c829e19 # frozen: v0.11.13 hooks: - id: ruff args: @@ -113,7 +113,7 @@ repos: - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy.git - rev: v1.15.0 + rev: 7010b10a09f65cd60a23c207349b539aa36dbec1 # frozen: v1.16.0 hooks: - id: mypy alias: mypy-py313 From dcb4c3640b9c32db2d1cef5d9d109b56f743a783 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 6 Jun 2025 02:35:20 +0300 Subject: [PATCH 246/293] fix: make infracost_breakdown.sh compatible with bash 3.2 (macOS) (#903) Signed-off-by: Oliver Ladner Co-authored-by: Oliver Ladner --- hooks/infracost_breakdown.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/hooks/infracost_breakdown.sh b/hooks/infracost_breakdown.sh index d5351b9bf..14d34b82f 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")" From 9aa1e0e7b353dca419a08d3abd2e9f5a0978c383 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 5 Jun 2025 23:35:45 +0000 Subject: [PATCH 247/293] chore(release): version 1.99.2 [skip ci] ## [1.99.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.1...v1.99.2) (2025-06-05) ### Bug Fixes * make infracost_breakdown.sh compatible with bash 3.2 (macOS) ([#903](https://github.com/antonbabenko/pre-commit-terraform/issues/903)) ([dcb4c36](https://github.com/antonbabenko/pre-commit-terraform/commit/dcb4c3640b9c32db2d1cef5d9d109b56f743a783)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b892406..7119f4c4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.99.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.1...v1.99.2) (2025-06-05) + + +### Bug Fixes + +* make infracost_breakdown.sh compatible with bash 3.2 (macOS) ([#903](https://github.com/antonbabenko/pre-commit-terraform/issues/903)) ([dcb4c36](https://github.com/antonbabenko/pre-commit-terraform/commit/dcb4c3640b9c32db2d1cef5d9d109b56f743a783)) + ## [1.99.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.0...v1.99.1) (2025-05-29) From 4f852124da4d95fc9671138771e959b6c6adf1ee Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 6 Jun 2025 19:12:58 +0300 Subject: [PATCH 248/293] fix(`terraform_docs`, `terraform_wrapper_module_for_each`): Improve `.tofu` files support (#904) --------- Co-authored-by: T. Hinrichsmeyer Co-authored-by: George L. Yermulnik --- hooks/terraform_docs.sh | 2 +- hooks/terraform_wrapper_module_for_each.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 6213fda08..02d012f83 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -207,7 +207,7 @@ function terraform_docs { # if $create_if_not_exist && [[ ! -f "$output_file" ]]; then dir_have_tf_files="$( - find . -maxdepth 1 -type f | sed 's|.*\.||' | sort -u | grep -oE '^tf$|^tfvars$' || + find . -maxdepth 1 \( -name '*.tf' -o -name '*.tofu' -o -name '*.tfvars' \) -type f || exit 0 )" diff --git a/hooks/terraform_wrapper_module_for_each.sh b/hooks/terraform_wrapper_module_for_each.sh index 945bf9a8a..dc08b791c 100755 --- a/hooks/terraform_wrapper_module_for_each.sh +++ b/hooks/terraform_wrapper_module_for_each.sh @@ -312,10 +312,10 @@ EOF # Read content of all terraform 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}" -maxdepth 1 \( -name '*.tf' -o -name '*.tofu' \) -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 From 8e4d393a810d599896aff8520b443396f053f05f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 6 Jun 2025 16:13:25 +0000 Subject: [PATCH 249/293] chore(release): version 1.99.3 [skip ci] ## [1.99.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.2...v1.99.3) (2025-06-06) ### Bug Fixes * **`terraform_docs`, `terraform_wrapper_module_for_each`:** Improve `.tofu` files support ([#904](https://github.com/antonbabenko/pre-commit-terraform/issues/904)) ([4f85212](https://github.com/antonbabenko/pre-commit-terraform/commit/4f852124da4d95fc9671138771e959b6c6adf1ee)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7119f4c4f..9cd6e8009 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.99.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.2...v1.99.3) (2025-06-06) + + +### Bug Fixes + +* **`terraform_docs`, `terraform_wrapper_module_for_each`:** Improve `.tofu` files support ([#904](https://github.com/antonbabenko/pre-commit-terraform/issues/904)) ([4f85212](https://github.com/antonbabenko/pre-commit-terraform/commit/4f852124da4d95fc9671138771e959b6c6adf1ee)) + ## [1.99.2](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.1...v1.99.2) (2025-06-05) From 7ce059e3f83782a76b863da2fc87fe631c9142c2 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 12 Jun 2025 14:54:12 +0300 Subject: [PATCH 250/293] chore(docker): Add validation for pip installed packages for integrity validation (#906) --- tools/install/checkov.sh | 1 + tools/install/pre-commit.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/tools/install/checkov.sh b/tools/install/checkov.sh index 8be8c649e..df031cfe2 100755 --- a/tools/install/checkov.sh +++ b/tools/install/checkov.sh @@ -29,6 +29,7 @@ if [[ $VERSION == latest ]]; then else pip3 install --no-cache-dir "${TOOL}==${VERSION}" fi +pip3 check apk del gcc libffi-dev musl-dev # no longer required once checkov version depends on rustworkx >0.14.0 diff --git a/tools/install/pre-commit.sh b/tools/install/pre-commit.sh index 9f3bdfb24..ca46e679d 100755 --- a/tools/install/pre-commit.sh +++ b/tools/install/pre-commit.sh @@ -14,3 +14,4 @@ if [[ $VERSION == latest ]]; then else pip3 install --no-cache-dir "${TOOL}==${VERSION}" fi +pip3 check From 3c9ef3d744011e44642726714521a45e66203eb3 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 12 Jun 2025 22:19:04 +0300 Subject: [PATCH 251/293] fix(docker): Drop Mac arm64 build-time hack, needed for `checkov`<3.2.395 (#907) ### Description of your changes Drop not more needed to hack for `checkov` during docker images installation, as https://github.com/bridgecrewio/checkov/pull/7072 merged 3 months ago as `3.2.395`. That's make obsolete hack from https://github.com/antonbabenko/pre-commit-terraform/pull/635 as https://github.com/bridgecrewio/checkov/pull/7072 finally closes https://github.com/bridgecrewio/checkov/issues/5608 for Mac arm64, because `checkov` finally uses `rustworkx >= 0.14.0` --- tools/install/checkov.sh | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tools/install/checkov.sh b/tools/install/checkov.sh index df031cfe2..708e4772b 100755 --- a/tools/install/checkov.sh +++ b/tools/install/checkov.sh @@ -13,25 +13,9 @@ apk add --no-cache \ libffi-dev=~3 \ musl-dev=~1 -# cargo, gcc, git, musl-dev, rust and CARGO envvar required for compilation of rustworkx@0.13.2 -# no longer required once checkov version depends on rustworkx >0.14.0 -# https://github.com/bridgecrewio/checkov/pull/6045 -# gcc libffi-dev musl-dev required for compilation of cffi, until it contains musl aarch64 -export CARGO_NET_GIT_FETCH_WITH_CLI=true -apk add --no-cache \ - cargo=~1 \ - git=~2 \ - libgcc=~12 \ - rust=~1 - if [[ $VERSION == latest ]]; then pip3 install --no-cache-dir "${TOOL}" else pip3 install --no-cache-dir "${TOOL}==${VERSION}" fi pip3 check - -apk del gcc libffi-dev musl-dev -# no longer required once checkov version depends on rustworkx >0.14.0 -# https://github.com/bridgecrewio/checkov/pull/6045 -apk del cargo git rust From 2f8bda194a420ad77a050a9de627d77a74841fdc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 12 Jun 2025 22:31:31 +0000 Subject: [PATCH 252/293] chore(release): version 1.99.4 [skip ci] ## [1.99.4](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.3...v1.99.4) (2025-06-12) ### Bug Fixes * **docker:** Drop Mac arm64 build-time hack, needed for `checkov`<3.2.395 ([#907](https://github.com/antonbabenko/pre-commit-terraform/issues/907)) ([3c9ef3d](https://github.com/antonbabenko/pre-commit-terraform/commit/3c9ef3d744011e44642726714521a45e66203eb3)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cd6e8009..433149570 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.99.4](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.3...v1.99.4) (2025-06-12) + + +### Bug Fixes + +* **docker:** Drop Mac arm64 build-time hack, needed for `checkov`<3.2.395 ([#907](https://github.com/antonbabenko/pre-commit-terraform/issues/907)) ([3c9ef3d](https://github.com/antonbabenko/pre-commit-terraform/commit/3c9ef3d744011e44642726714521a45e66203eb3)) + ## [1.99.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.2...v1.99.3) (2025-06-06) From 6c8459528bfcc7c0f7a5a18aa1250e5d2c92b90d Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 17 Jun 2025 16:49:27 +0300 Subject: [PATCH 253/293] chore(checks): Init wemake-python-styleguide configuration (#905) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description of your changes * Integrate wemake-python-styleguide and ruff * Add ignores for deprecated hook * Make initial setup of wemake-python-styleguide, by disabling a few too strict rules --------- Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- .flake8 | 108 ++++++++++++++++++ .pre-commit-config.yaml | 5 + ruff.toml | 1 + src/pre_commit_terraform/_cli.py | 14 +-- src/pre_commit_terraform/_cli_subcommands.py | 4 +- src/pre_commit_terraform/_structs.py | 6 +- src/pre_commit_terraform/_types.py | 6 +- .../terraform_docs_replace.py | 14 ++- tests/pytest/_cli_test.py | 4 +- 9 files changed, 143 insertions(+), 19 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..98e9a7dbc --- /dev/null +++ b/.flake8 @@ -0,0 +1,108 @@ +[flake8] + +# Print the total number of errors: +count = true + +# Don't even try to analyze these: +extend-exclude = + # GitHub configs + .github, + # Cache files of MyPy + .mypy_cache, + # Cache files of pytest + .pytest_cache, + # Countless third-party libs in venvs + .tox, + # Occasional virtualenv dir + .venv, + # VS Code + .vscode, + # Metadata of `pip wheel` cmd is autogenerated + pip-wheel-metadata, + +# IMPORTANT: avoid using ignore option, always use extend-ignore instead +# Completely and unconditionally ignore the following errors: +extend-ignore = + # Legitimate cases, no need to "fix" these violations: + # D202: No blank lines allowed after function docstring, conflicts with `ruff format` + D202, + # E203: whitespace before ':', conflicts with `ruff format` + E203, + # E501: "line too long", its function is replaced by `flake8-length` + E501, + # W505: "doc line too long", its function is replaced by `flake8-length` + W505, + # I: flake8-isort is drunk + we have isort integrated into pre-commit + I, + # WPS305: "Found f string" -- nothing bad about this + WPS305, + # WPS322: "Found incorrect multi-line string" -- false-positives with + # attribute docstrings. Ref: + # https://github.com/wemake-services/wemake-python-styleguide/issues/3056 + WPS322, + # WPS326: "Found implicit string concatenation" -- nothing bad about this + WPS326, + # WPS428: "Found statement that has no effect" -- false-positives with + # attribute docstrings. Ref: + # https://github.com/wemake-services/wemake-python-styleguide/issues/3056 + WPS428, + # WPS462: "Wrong multiline string usage" -- false-positives with + # attribute docstrings. Ref: + # https://github.com/wemake-services/wemake-python-styleguide/issues/3056 + WPS462, + # WPS300: "Forbid imports relative to the current folder" -- we use relative imports + WPS300, + +# https://wemake-python-styleguide.readthedocs.io/en/latest/pages/usage/formatter.html +format = wemake + +# Let's not overcomplicate the code: +max-complexity = 10 + +# Accessibility/large fonts and PEP8 friendly. +# This is being flexibly extended through the `flake8-length`: +max-line-length = 79 + +# Allow certain violations in certain files: +# Please keep both sections of this list sorted, as it will be easier for others to find and add entries in the future +per-file-ignores = + # The following ignores have been researched and should be considered permanent + # each should be preceded with an explanation of each of the error codes + # If other ignores are added for a specific file in the section following this, + # these will need to be added to that line as well. + + tests/pytest/_cli_test.py: + # WPS431: "Forbid nested classes" -- this is a legitimate use case for tests + WPS431, + # WPS226: "Forbid the overuse of string literals" -- this is a legitimate use case for tests + WPS226, + # WPS115: "Require snake_case for naming class attributes" -- testing legitimate case, ignored in main code + WPS115, + # We will not spend time on fixing complexity in deprecated hook + src/pre_commit_terraform/terraform_docs_replace.py: WPS232 + +# Count the number of occurrences of each error/warning code and print a report: +statistics = true + +# ## Plugin-provided settings: ## + +# flake8-eradicate +# E800: +eradicate-whitelist-extend = isort:\s+\w+ + +# flake8-pytest-style +# PT001: +pytest-fixture-no-parentheses = true +# PT006: +pytest-parametrize-names-type = tuple +# PT007: +pytest-parametrize-values-type = tuple +pytest-parametrize-values-row-type = tuple +# PT023: +pytest-mark-no-parentheses = true + +# wemake-python-styleguide +# WPS410: "Forbid some module-level variables" -- __all__ is a legitimate use case +allowed-module-metadata = __all__ + +show-source = true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c0d86e84e..498a147bd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -112,6 +112,11 @@ repos: - --fix - id: ruff-format +- repo: https://github.com/wemake-services/wemake-python-styleguide + rev: 6d4ca2bdc16b3098422a2770728136fc0751b817 # frozen: 1.1.0 + hooks: + - id: wemake-python-styleguide + - repo: https://github.com/pre-commit/mirrors-mypy.git rev: 7010b10a09f65cd60a23c207349b539aa36dbec1 # frozen: v1.16.0 hooks: diff --git a/ruff.toml b/ruff.toml index 8bb19749a..0e57620ea 100644 --- a/ruff.toml +++ b/ruff.toml @@ -17,6 +17,7 @@ convention = "pep257" [lint] select = ["ALL"] preview = true +external = ["WPS"] # Do not remove noqa for wemake-python-style (WPS) checks ignore = [ "CPY001", # Skip copyright notice requirement at top of files ] diff --git a/src/pre_commit_terraform/_cli.py b/src/pre_commit_terraform/_cli.py index abd95df75..8c99e2b54 100644 --- a/src/pre_commit_terraform/_cli.py +++ b/src/pre_commit_terraform/_cli.py @@ -33,28 +33,28 @@ def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: parsed_cli_args.invoke_cli_app, ) - try: + try: # noqa: WPS225 - too many `except` cases return invoke_cli_app(parsed_cli_args) except PreCommitTerraformExit as exit_err: - # T201 - FIXME here and below - we will replace 'print' with - # logging later - print(f'App exiting: {exit_err!s}', file=sys.stderr) # noqa: T201 + # T201,WPS421 - FIXME here and below - we will replace 'print' + # with logging later + print(f'App exiting: {exit_err!s}', file=sys.stderr) # noqa: T201,WPS421 raise except PreCommitTerraformRuntimeError as unhandled_exc: - print( # noqa: T201 + print( # noqa: T201,WPS421 f'App execution took an unexpected turn: {unhandled_exc!s}. ' 'Exiting...', file=sys.stderr, ) return ReturnCode.ERROR except PreCommitTerraformBaseError as unhandled_exc: - print( # noqa: T201 + print( # noqa: T201,WPS421 f'A surprising exception happened: {unhandled_exc!s}. Exiting...', file=sys.stderr, ) return ReturnCode.ERROR except KeyboardInterrupt as ctrl_c_exc: - print( # noqa: T201 + print( # noqa: T201,WPS421 f'User-initiated interrupt: {ctrl_c_exc!s}. Exiting...', file=sys.stderr, ) diff --git a/src/pre_commit_terraform/_cli_subcommands.py b/src/pre_commit_terraform/_cli_subcommands.py index fc268e552..ba7a6982d 100644 --- a/src/pre_commit_terraform/_cli_subcommands.py +++ b/src/pre_commit_terraform/_cli_subcommands.py @@ -4,9 +4,9 @@ from ._types import CLISubcommandModuleProtocol -SUBCOMMAND_MODULES: list[CLISubcommandModuleProtocol] = [ +SUBCOMMAND_MODULES: tuple[CLISubcommandModuleProtocol] = ( terraform_docs_replace, -] +) __all__ = ('SUBCOMMAND_MODULES',) diff --git a/src/pre_commit_terraform/_structs.py b/src/pre_commit_terraform/_structs.py index 12cf0dad3..f701d71a9 100644 --- a/src/pre_commit_terraform/_structs.py +++ b/src/pre_commit_terraform/_structs.py @@ -9,8 +9,10 @@ class ReturnCode(IntEnum): To be used in check callable implementations. """ - OK = 0 - ERROR = 1 + # WPS115: "Require snake_case for naming class attributes". According to + # "Correct" example in docs - it's valid use case => false-positive + OK = 0 # noqa: WPS115 + ERROR = 1 # noqa: WPS115 __all__ = ('ReturnCode',) diff --git a/src/pre_commit_terraform/_types.py b/src/pre_commit_terraform/_types.py index c08320f71..69f71e690 100644 --- a/src/pre_commit_terraform/_types.py +++ b/src/pre_commit_terraform/_types.py @@ -14,7 +14,11 @@ class CLISubcommandModuleProtocol(Protocol): """A protocol for the subcommand-implementing module shape.""" - CLI_SUBCOMMAND_NAME: str + # WPS115: "Require snake_case for naming class attributes". + # This protocol describes module shapes and not regular classes. + # It's a valid use case as then it's used as constants: + # "CLI_SUBCOMMAND_NAME: Final[str] = 'hook-name'"" on top level + CLI_SUBCOMMAND_NAME: str # noqa: WPS115 """This constant contains a CLI.""" def populate_argument_parser( diff --git a/src/pre_commit_terraform/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py index 43f496bb1..3bfb3bc43 100644 --- a/src/pre_commit_terraform/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -58,7 +58,9 @@ def populate_argument_parser(subcommand_parser: ArgumentParser) -> None: ) -def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: +# WPS231 - Found function with too much cognitive complexity +# We will not spend time on fixing complexity in deprecated hook +def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: # noqa: WPS231 """Run the entry-point of the CLI app. Returns: @@ -86,7 +88,7 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: retval = ReturnCode.OK for directory in dirs: - try: + try: # noqa: WPS229 - ignore as it's deprecated hook proc_args = [] proc_args.append('terraform-docs') if cast_to('bool', parsed_cli_args.sort): @@ -107,8 +109,10 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: subprocess.check_call(' '.join(proc_args), shell=True) # noqa: S602 # PERF203 - try-except shouldn't be in a loop, but it's deprecated # hook, so leave as is - except subprocess.CalledProcessError as e: # noqa: PERF203 - # T201 - Leave print statement as is, as this is deprecated hook - print(e) # noqa: T201 + # WPS111 - Too short var name, but it's deprecated hook, so leave as is + except subprocess.CalledProcessError as e: # noqa: PERF203,WPS111 + # T201,WPS421 - Leave print statement as is, as this is + # deprecated hook + print(e) # noqa: T201,WPS421,WPS111 retval = ReturnCode.ERROR return retval diff --git a/tests/pytest/_cli_test.py b/tests/pytest/_cli_test.py index fd4b1fef5..773825269 100644 --- a/tests/pytest/_cli_test.py +++ b/tests/pytest/_cli_test.py @@ -55,7 +55,7 @@ def populate_argument_parser( self, subcommand_parser: ArgumentParser, ) -> None: - return None + pass # noqa: WPS420. This is a stub, docstring not needed so "pass" required. def invoke_cli_app(self, parsed_cli_args: Namespace) -> ReturnCodeType: raise raised_error @@ -85,7 +85,7 @@ def populate_argument_parser( self, subcommand_parser: ArgumentParser, ) -> None: - return None + pass # noqa: WPS420. This is a stub, docstring not needed so "pass" required. def invoke_cli_app(self, parsed_cli_args: Namespace) -> ReturnCodeType: raise PreCommitTerraformExit(self.CLI_SUBCOMMAND_NAME) From a5bd84e94dc228794a017da7f3e21f02745b8c06 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 8 Jul 2025 21:44:50 +0300 Subject: [PATCH 254/293] fix: Regression from (at least) v1.99.0 which incorrectly handle `pre-commit run -a`, that causes multiply hooks runs. `terraform_trivy` from its introduction could always be `Passed` for `pre-commit run -a` (#909) ### Description of your changes Probably introduced in https://github.com/antonbabenko/pre-commit-terraform/pull/875 or even earlier. There were issue that regex in `.pre-commit-hooks.yaml` specified for Python `re` library, but we also used it with `grep -e`. `grep -e` working slightly different than we expect, so I switched to `grep -E` which have less differences with Python `re`. (step 3 below) Then I found that there is no [required `--exit-code=1`](https://trivy.dev/latest/docs/configuration/others/#exit-code) in `run_hook_on_whole_repo` from hook introduction 2 years ago https://github.com/antonbabenko/pre-commit-terraform/pull/606. Fixed it too. From above, I assume that `pre-commit run -a` for Fix #908 ### How can we test changes 1. Clone https://github.com/pre-commit-terraform/GH-908-reproduce. 2. Run `pre-commit run -a` - you'll see 2 occurrences of same error. 3. (Optional) Change `.pre-commit-config.yaml` to ```yaml repos: - repo: https://github.com/antonbabenko/pre-commit-terraform rev: 48525b2912a56f8b707671ba67d5932549384cce hooks: - id: terraform_trivy args: # https://trivy.dev/latest/docs/configuration/others/#exit-code # It wasn't set, when it should be set by default in hook. Another issue. - --args=--exit-code=1 ``` and run `pre-commit run -a` 4. Change `.pre-commit-config.yaml` to ```yaml repos: - repo: https://github.com/antonbabenko/pre-commit-terraform rev: bafa6635606e8f6f5bd81fe99a391a2b12245768 hooks: - id: terraform_trivy ``` and run `pre-commit run -a` --- hooks/_common.sh | 4 ++-- hooks/terraform_docs.sh | 2 +- hooks/terraform_trivy.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index 7281b0244..c63918033 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -176,9 +176,9 @@ function common::is_hook_run_on_whole_repo { local all_files_that_can_be_checked if [ -z "$excluded_files" ]; then - all_files_that_can_be_checked=$(git ls-files | sort | grep -e "$included_files" | tr '\n' ' ') + all_files_that_can_be_checked=$(git ls-files | sort | grep -E -- "$included_files" | tr '\n' ' ') else - all_files_that_can_be_checked=$(git ls-files | sort | grep -e "$included_files" | grep -v -e "$excluded_files" | tr '\n' ' ') + all_files_that_can_be_checked=$(git ls-files | sort | grep -E -- "$included_files" | grep -v -E -- "$excluded_files" | tr '\n' ' ') fi if [ "$files_to_check" == "$all_files_that_can_be_checked" ]; then diff --git a/hooks/terraform_docs.sh b/hooks/terraform_docs.sh index 02d012f83..7644b5e4c 100755 --- a/hooks/terraform_docs.sh +++ b/hooks/terraform_docs.sh @@ -187,7 +187,7 @@ function terraform_docs { config_file_no_color="$config_file$(date +%s).yml" if [ "$PRE_COMMIT_COLOR" = "never" ] && - [[ $(grep -e '^formatter:' "$config_file") == *"pretty"* ]] && + [[ $(grep -E '^formatter:' "$config_file") == *"pretty"* ]] && [[ $(grep ' color: ' "$config_file") != *"false"* ]]; then cp "$config_file" "$config_file_no_color" diff --git a/hooks/terraform_trivy.sh b/hooks/terraform_trivy.sh index fb4a529a0..d607d67a5 100755 --- a/hooks/terraform_trivy.sh +++ b/hooks/terraform_trivy.sh @@ -65,7 +65,7 @@ function run_hook_on_whole_repo { local -a -r args=("$@") # pass the arguments to hook - trivy conf "$(pwd)" "${args[@]}" + trivy conf "$(pwd)" --exit-code=1 "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? From c0f51014b8ec51c4455a7dac40697be15e285668 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 8 Jul 2025 18:45:16 +0000 Subject: [PATCH 255/293] chore(release): version 1.99.5 [skip ci] ## [1.99.5](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.4...v1.99.5) (2025-07-08) ### Bug Fixes * Regression from (at least) v1.99.0 which incorrectly handle `pre-commit run -a`, that causes multiply hooks runs. `terraform_trivy` from its introduction could always be `Passed` for `pre-commit run -a` ([#909](https://github.com/antonbabenko/pre-commit-terraform/issues/909)) ([a5bd84e](https://github.com/antonbabenko/pre-commit-terraform/commit/a5bd84e94dc228794a017da7f3e21f02745b8c06)), closes [#908](https://github.com/antonbabenko/pre-commit-terraform/issues/908) [/github.com/pre-commit-terraform/#908](https://github.com//github.com/pre-commit-terraform//issues/908) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 433149570..9952ea155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.99.5](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.4...v1.99.5) (2025-07-08) + + +### Bug Fixes + +* Regression from (at least) v1.99.0 which incorrectly handle `pre-commit run -a`, that causes multiply hooks runs. `terraform_trivy` from its introduction could always be `Passed` for `pre-commit run -a` ([#909](https://github.com/antonbabenko/pre-commit-terraform/issues/909)) ([a5bd84e](https://github.com/antonbabenko/pre-commit-terraform/commit/a5bd84e94dc228794a017da7f3e21f02745b8c06)), closes [#908](https://github.com/antonbabenko/pre-commit-terraform/issues/908) [/github.com/pre-commit-terraform/#908](https://github.com//github.com/pre-commit-terraform//issues/908) + ## [1.99.4](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.3...v1.99.4) (2025-06-12) From a4b0a467d6ed69d14ca524baeae5bccfdc99aaa7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 17:56:18 +0300 Subject: [PATCH 256/293] chore(deps): Update pre-commit (#910) --- .github/workflows/build-image-test.yaml | 4 +++- .pre-commit-config.yaml | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 9c6065d49..740d2d1ed 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -94,11 +94,13 @@ jobs: - name: Run structure tests if: steps.changed-files-specific.outputs.any_changed == 'true' + env: + IMAGE_NAME: ${{ env.IMAGE }} run: >- container-structure-test test --config ${{ github.workspace }}/.github/.container-structure-test-config.yaml - --image ${{ env.IMAGE }} + --image "${IMAGE_NAME}" - name: Dive - check image for waste files if: steps.changed-files-specific.outputs.any_changed == 'true' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 498a147bd..f0c43a200 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,13 +43,13 @@ repos: # Detect hardcoded secrets - repo: https://github.com/gitleaks/gitleaks - rev: a248f9279b38aeff5bbd4c85cc6f15b64d27e794 # frozen: v8.27.0 + rev: 47218a628da59ef6b24197d15f4b0248ca87f4f0 # frozen: v8.27.2 hooks: - id: gitleaks # Github Action static analysis tool - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: d2c1833a059c66713cd52c032617766134679a0f # frozen: v1.9.0 + rev: 69fa534d69454f44ddd4451b5e2da7a1c48e525b # frozen: v1.11.0 hooks: - id: zizmor @@ -105,7 +105,7 @@ repos: # Python - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 9aeda5d1f4bbd212c557da1ea78eca9e8c829e19 # frozen: v0.11.13 + rev: 0b19ef1fd6ad680ed7752d6daba883ce1265a6de # frozen: v0.12.2 hooks: - id: ruff args: @@ -113,12 +113,12 @@ repos: - id: ruff-format - repo: https://github.com/wemake-services/wemake-python-styleguide - rev: 6d4ca2bdc16b3098422a2770728136fc0751b817 # frozen: 1.1.0 + rev: 89eca4e53c6a95aebebd9cc86b717fb11a915de9 # frozen: 1.2.0 hooks: - id: wemake-python-styleguide - repo: https://github.com/pre-commit/mirrors-mypy.git - rev: 7010b10a09f65cd60a23c207349b539aa36dbec1 # frozen: v1.16.0 + rev: 0f86793af5ef5f6dc63c8d04a3cabfa3ea8f9c6a # frozen: v1.16.1 hooks: - id: mypy alias: mypy-py313 From 108be5153f6d26222fc97ebe78f4ee669f678603 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:36:20 +0300 Subject: [PATCH 257/293] chore(deps): Update MaxymVlasov/dive-action action to v1.5.0 (#922) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 740d2d1ed..b05aa35eb 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -104,7 +104,7 @@ jobs: - name: Dive - check image for waste files if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: MaxymVlasov/dive-action@43dafd0015826beaca5110157c9262c5dc10672a # v1.4.0 + uses: MaxymVlasov/dive-action@b08c8287e603d028c986d7044e83fa76bcca6a65 # v1.5.0 with: image: ${{ env.IMAGE }} config-file: ${{ github.workspace }}/.github/.dive-ci.yaml From 1919ece5eb6df0a17cb0f98263dc1f0743f12ec1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 17:40:33 +0000 Subject: [PATCH 258/293] chore(deps): Update github/codeql-action action to v3.29.5 (#921) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fa105c1b8..c99fd3674 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -49,7 +49,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in @@ -61,7 +61,7 @@ jobs: # If this step fails, then you should remove it and run the build # manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/autobuild@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 # ℹ️ Command-line programs to run using the OS shell. # yamllint disable-line rule:line-length @@ -76,6 +76,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 with: category: /language:${{matrix.language}} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 40dec7de9..e7d2a3baa 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -74,6 +74,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 with: sarif_file: results.sarif From a7ba005499baee97a37965d21d2576b0ca030daa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 17:44:54 +0000 Subject: [PATCH 259/293] chore(deps): Update ossf/scorecard-action action to v2.4.2 (#918) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index e7d2a3baa..bb901e8f8 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -42,7 +42,7 @@ jobs: persist-credentials: false - name: Run analysis - uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 + uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: results_file: results.sarif results_format: sarif From 1442de7dfab143592abf9e91d230587ee103a6c1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 17:51:17 +0000 Subject: [PATCH 260/293] chore(deps): Update docker/setup-buildx-action action to v3.11.1 (#920) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index b05aa35eb..cba0d4246 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -58,7 +58,7 @@ jobs: >> $GITHUB_ENV - name: Set up Docker Buildx - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 if: steps.changed-files-specific.outputs.any_changed == 'true' - name: Build if Dockerfile changed diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index ae6e407d0..79a0a828d 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -25,7 +25,7 @@ jobs: persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Login to GitHub Container Registry uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: @@ -49,7 +49,7 @@ jobs: run: >- echo "IMAGE_REPO=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV - name: Set up Docker Buildx - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Build and Push release if: github.event_name != 'schedule' From 775234972d6f16648e2fcd58a7e33a4e0bdb0b1f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 18:05:32 +0000 Subject: [PATCH 261/293] chore(deps): Update cycjimmy/semantic-release-action action to v4.2.2 (#917) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 904486ca6..63498c9df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,7 +36,7 @@ jobs: fetch-depth: 0 - name: Release - uses: cycjimmy/semantic-release-action@0a51e81a6baff2acad3ee88f4121c589c73d0f0e # v4.2.0 + uses: cycjimmy/semantic-release-action@16ca923e6ccbb50770c415a0ccd43709a8c5f7a4 # v4.2.2 with: semantic_version: 18.0.0 extra_plugins: | From 1a52970e554be4dc5d5af1f8940d4daeec5eb102 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 18:18:11 +0000 Subject: [PATCH 262/293] chore(deps): Update actions/cache action to v4.2.4 (#916) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 4 ++-- .github/workflows/reusable-tox.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 872605850..147cba773 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -165,7 +165,7 @@ jobs: echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" shell: bash - name: Set up pip cache - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- @@ -311,7 +311,7 @@ jobs: run: >- echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" - name: Set up pip cache - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 5ee1edba3..8588887b9 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -183,7 +183,7 @@ jobs: - name: Cache pre-commit.com virtualenvs if: inputs.toxenv == 'pre-commit' - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: ~/.cache/pre-commit key: >- @@ -241,7 +241,7 @@ jobs: shell: bash - name: Set up pip cache if: fromJSON(steps.py-abi.outputs.is-stable-abi) - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- From befa7024362cd6ac8ba55d21f8064d4e248bd163 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 7 Aug 2025 22:30:38 +0300 Subject: [PATCH 263/293] docs: Clarify docker instructions to work with spaces in paths (#914) * docs: Clarify docker instructions to work with spaces in paths * Update README.md Co-authored-by: George Yermulnik (Georgii Iermulnik) --------- Co-authored-by: George Yermulnik (Georgii Iermulnik) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1546f0466..3aad1e698 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,7 @@ Or, using Docker ([available tags](https://github.com/antonbabenko/pre-commit-te ```bash TAG=latest -docker run -e "USERID=$(id -u):$(id -g)" -v $(pwd):/lint -w /lint ghcr.io/antonbabenko/pre-commit-terraform:$TAG run -a +docker run -e "USERID=$(id -u):$(id -g)" -v "$(pwd):/lint" -w "/lint" "ghcr.io/antonbabenko/pre-commit-terraform:$TAG" run -a ``` Execute this command to list the versions of the tools in Docker: From 435f4884b57780235a765646262f8b01b9214c91 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 19:43:40 +0000 Subject: [PATCH 264/293] chore(deps): Update docker/login-action action to v3.5.0 (#919) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 79a0a828d..53530320c 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -27,7 +27,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Login to GitHub Container Registry - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From a134d1636910c671f3e3f626dd25d2c6b31bf553 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 8 Aug 2025 03:54:24 +0300 Subject: [PATCH 265/293] chore(deps): Use latest available Python 3.12 base image (#924) * chore(deps): Use latest available Python 3.12 base image * Bump docker image versions * f * f * Update tests --- .github/.container-structure-test-config.yaml | 4 ++-- Dockerfile | 6 +++--- tools/install/checkov.sh | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/.container-structure-test-config.yaml b/.github/.container-structure-test-config.yaml index 0425a71eb..98aeac0c4 100644 --- a/.github/.container-structure-test-config.yaml +++ b/.github/.container-structure-test-config.yaml @@ -20,7 +20,7 @@ commandTests: args: - --version expectedOutput: - - ^gcc \(Alpine 12\. + - ^gcc \(Alpine 14\. - name: checkov command: checkov @@ -131,7 +131,7 @@ commandTests: args: - -V expectedError: - - ^OpenSSH_9\.[0-9]+ + - ^OpenSSH_10\.[0-9]+ fileExistenceTests: - name: terrascan init diff --git a/Dockerfile b/Dockerfile index 4c69f2586..ac7c92676 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12.0-alpine3.17@sha256:fc34b07ec97a4f288bc17083d288374a803dd59800399c76b977016c9fe5b8f2 AS python_base +FROM python:3.12-alpine@sha256:9b8808206f4a956130546a32cbdd8633bc973b19db2923b7298e6f90cc26db08 AS python_base FROM python_base AS builder ARG TARGETOS @@ -116,11 +116,11 @@ RUN apk add --no-cache \ bash=~5 \ # pre-commit-hooks deps: https://github.com/pre-commit/pre-commit-hooks musl-dev=~1 \ - gcc=~12 \ + gcc=~14 \ # entrypoint wrapper deps su-exec=~0.2 \ # ssh-client for external private module in ssh - openssh-client=~9 + openssh-client=~10 # Copy tools COPY --from=builder \ diff --git a/tools/install/checkov.sh b/tools/install/checkov.sh index 708e4772b..bdf8c894a 100755 --- a/tools/install/checkov.sh +++ b/tools/install/checkov.sh @@ -9,7 +9,7 @@ readonly SCRIPT_DIR # apk add --no-cache \ - gcc=~12 \ + gcc=~14 \ libffi-dev=~3 \ musl-dev=~1 From 7433e138f8b346687d4ff6357d7b48be709d8130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolai=20R=C3=B8ed=20Kristiansen?= Date: Fri, 8 Aug 2025 13:19:21 +0200 Subject: [PATCH 266/293] feat(`terraform_fmt`): Add support for `.tftest.hcl` and `.tfmock.hcl` formatting (#911) Both supported in https://github.com/hashicorp/terraform/blob/0c63fb2b097edcd5cb1a91322765a414206fbea2/internal/command/fmt.go#L30-L35 --------- Co-authored-by: Maksym Vlasov --- .pre-commit-hooks.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 50607541e..f99971ee5 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -13,7 +13,9 @@ Rewrites all Terraform configuration files to a canonical format. entry: hooks/terraform_fmt.sh language: script - files: \.(tf|tofu|tfvars)$ + # Supported extensions by Terraform specified in + # https://github.com/hashicorp/terraform/blob/0c63fb2b097edcd5cb1a91322765a414206fbea2/internal/command/fmt.go#L30-L35 + files: \.(tf|tofu|tfvars|tftest\.hcl|tfmock\.hcl)$ exclude: \.terraform/.*$ - id: terraform_docs From 9a790d2bbf52124eecb5a71a4bab4884804ffa2a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 8 Aug 2025 11:19:47 +0000 Subject: [PATCH 267/293] chore(release): version 1.100.0 [skip ci] # [1.100.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.5...v1.100.0) (2025-08-08) ### Features * **`terraform_fmt`:** Add support for `.tftest.hcl` and `.tfmock.hcl` formatting ([#911](https://github.com/antonbabenko/pre-commit-terraform/issues/911)) ([7433e13](https://github.com/antonbabenko/pre-commit-terraform/commit/7433e138f8b346687d4ff6357d7b48be709d8130)), closes [/github.com/hashicorp/terraform/blob/0c63fb2b097edcd5cb1a91322765a414206fbea2/internal/command/fmt.go#L30-L35](https://github.com//github.com/hashicorp/terraform/blob/0c63fb2b097edcd5cb1a91322765a414206fbea2/internal/command/fmt.go/issues/L30-L35) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9952ea155..9aa2b815b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.100.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.5...v1.100.0) (2025-08-08) + + +### Features + +* **`terraform_fmt`:** Add support for `.tftest.hcl` and `.tfmock.hcl` formatting ([#911](https://github.com/antonbabenko/pre-commit-terraform/issues/911)) ([7433e13](https://github.com/antonbabenko/pre-commit-terraform/commit/7433e138f8b346687d4ff6357d7b48be709d8130)), closes [/github.com/hashicorp/terraform/blob/0c63fb2b097edcd5cb1a91322765a414206fbea2/internal/command/fmt.go#L30-L35](https://github.com//github.com/hashicorp/terraform/blob/0c63fb2b097edcd5cb1a91322765a414206fbea2/internal/command/fmt.go/issues/L30-L35) + ## [1.99.5](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.4...v1.99.5) (2025-07-08) From acf85ff66e3f60c76070ce66b1377a5380e86781 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:25:11 +0000 Subject: [PATCH 268/293] chore(deps): Update actions/download-artifact action to v5 (#923) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/reusable-tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 8588887b9..7b02d6965 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -268,7 +268,7 @@ jobs: - name: Download all the dists if: >- contains(fromJSON('["metadata-validation", "pytest"]'), inputs.toxenv) - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: ${{ inputs.dists-artifact-name }} path: dist/ From 2e591ea0b4a3a0f19a179e17a2e4a7cb460d061b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=87=BA=F0=9F=87=A6=20Sviatoslav=20Sydorenko=20=28?= =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D1=82=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=B8=D0=B4=D0=BE=D1=80=D0=B5=D0=BD=D0=BA=D0=BE=29?= Date: Mon, 1 Sep 2025 17:58:29 +0200 Subject: [PATCH 269/293] ci: Keep `setuptools-scm` in sync across CI (#929) --- .github/workflows/ci-cd.yml | 3 ++- .../lock-files/dist-build-constraints.txt | 16 ++++++++++++++++ tox.ini | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 dependencies/lock-files/dist-build-constraints.txt diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 147cba773..b9df58231 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -189,7 +189,8 @@ jobs: python -m pip install --user - setuptools-scm~=8.2 + setuptools-scm + --constraint=dependencies/lock-files/dist-build-constraints.txt shell: bash - name: Set the current dist version from Git id: scm-version diff --git a/dependencies/lock-files/dist-build-constraints.txt b/dependencies/lock-files/dist-build-constraints.txt new file mode 100644 index 000000000..6ef52ad7c --- /dev/null +++ b/dependencies/lock-files/dist-build-constraints.txt @@ -0,0 +1,16 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# tox r -e pip-compile-build-lock -- +# +packaging==24.1 + # via setuptools-scm +setuptools-scm==8.1.0 + # via awx-plugins-core (pyproject.toml::build-system.requires) + +# The following packages are considered to be unsafe in a requirements file: +setuptools==73.0.0 + # via + # awx-plugins-core (pyproject.toml::build-system.requires) + # setuptools-scm diff --git a/tox.ini b/tox.ini index 0b0f26b6b..fc2237aa3 100644 --- a/tox.ini +++ b/tox.ini @@ -153,6 +153,8 @@ commands = {posargs:} commands_post = package = skip +set_env = + PIP_CONSTRAINT = {toxinidir}{/}dependencies{/}lock-files{/}dist-build-constraints.txt [testenv:metadata-validation] From ef4fe4532d4624b3f4a395e052eda8238faed9b9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 19:56:21 +0300 Subject: [PATCH 270/293] chore(deps): Update python:3.12-alpine Docker digest to 02a73ea (#926) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ac7c92676..ba98ef6b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12-alpine@sha256:9b8808206f4a956130546a32cbdd8633bc973b19db2923b7298e6f90cc26db08 AS python_base +FROM python:3.12-alpine@sha256:02a73ead8397e904cea6d17e18516f1df3590e05dc8823bd5b1c7f849227d272 AS python_base FROM python_base AS builder ARG TARGETOS From 199343e67f50ccd133601b925d0c027cf276d450 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 2 Sep 2025 15:18:12 +0300 Subject: [PATCH 271/293] chore: Add zizimor as workflow to get GH-native alerts (#930) --- .github/workflows/ci-cd.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b9df58231..c57b87bf8 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -506,6 +506,13 @@ jobs: secrets: codecov-token: ${{ secrets.CODECOV_TOKEN }} + lint-github-actions: + name: Lint GitHub Actions + permissions: + security-events: write + # yamllint disable-line rule:line-length + uses: zizmorcore/workflow/.github/workflows/reusable-zizmor.yml@3bb5e95068d0f44b6d2f3f7e91379bed1d2f96a8 + check: # This job does nothing and is only used for the branch protection # Separate 'pull_request' check from other checks to avoid confusion in From 48b3a2975474bd501ff2ea8aa7d37dcdbc69d4ed Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 5 Sep 2025 15:06:09 +0300 Subject: [PATCH 272/293] ci: Set required underlying permissions (#931) --- .github/workflows/pre-commit.yaml | 2 +- .github/workflows/scheduled-runs.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index fbaa235ac..7fee1ad58 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -47,7 +47,7 @@ jobs: run: >- curl -L "$( curl -s https://api.github.com/repos/hadolint/hadolint/releases/latest - | grep -o -E -m 1 "https://.+?/hadolint-Linux-x86_64" + | grep -o -E -i -m 1 "https://.+?/hadolint-linux-x86_64" )" > hadolint && chmod +x hadolint && sudo mv hadolint /usr/bin/ diff --git a/.github/workflows/scheduled-runs.yml b/.github/workflows/scheduled-runs.yml index ba075e805..9a3164097 100644 --- a/.github/workflows/scheduled-runs.yml +++ b/.github/workflows/scheduled-runs.yml @@ -10,6 +10,7 @@ on: permissions: contents: read + security-events: write run-name: >- 🌃 From ea46354766ddcca2607ae7d460c6e3bf6f48e665 Mon Sep 17 00:00:00 2001 From: Jannis <2538064+jannis-a@users.noreply.github.com> Date: Sun, 28 Sep 2025 00:22:28 +0200 Subject: [PATCH 273/293] fix(`terragrunt_providers_lock`, `terragrunt_validate`): Properly handle arguments passed from terragrunt to TF (#933) --- hooks/terragrunt_providers_lock.sh | 2 +- hooks/terragrunt_validate.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/terragrunt_providers_lock.sh b/hooks/terragrunt_providers_lock.sh index 21ee668fe..aacf65cd9 100755 --- a/hooks/terragrunt_providers_lock.sh +++ b/hooks/terragrunt_providers_lock.sh @@ -15,7 +15,7 @@ function main { # JFYI: terragrunt providers lock color already suppressed via PRE_COMMIT_COLOR=never if common::terragrunt_version_ge_0.78; then - local -ra RUN_ALL_SUBCOMMAND=(run --all providers lock) + local -ra RUN_ALL_SUBCOMMAND=(run --all -- providers lock) else local -ra RUN_ALL_SUBCOMMAND=(run-all providers lock) fi diff --git a/hooks/terragrunt_validate.sh b/hooks/terragrunt_validate.sh index 54b7a98d6..dc9ac941c 100755 --- a/hooks/terragrunt_validate.sh +++ b/hooks/terragrunt_validate.sh @@ -15,7 +15,7 @@ function main { # JFYI: terragrunt validate color already suppressed via PRE_COMMIT_COLOR=never if common::terragrunt_version_ge_0.78; then - local -ra RUN_ALL_SUBCOMMAND=(run --all validate) + local -ra RUN_ALL_SUBCOMMAND=(run --all -- validate) else local -ra RUN_ALL_SUBCOMMAND=(run-all validate) fi From 947c1ac39f7511e525b500d1d43087bf1072786c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 27 Sep 2025 22:23:05 +0000 Subject: [PATCH 274/293] chore(release): version 1.100.1 [skip ci] ## [1.100.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.100.0...v1.100.1) (2025-09-27) ### Bug Fixes * **`terragrunt_providers_lock`, `terragrunt_validate`:** Properly handle arguments passed from terragrunt to TF ([#933](https://github.com/antonbabenko/pre-commit-terraform/issues/933)) ([ea46354](https://github.com/antonbabenko/pre-commit-terraform/commit/ea46354766ddcca2607ae7d460c6e3bf6f48e665)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa2b815b..f642d6252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.100.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.100.0...v1.100.1) (2025-09-27) + + +### Bug Fixes + +* **`terragrunt_providers_lock`, `terragrunt_validate`:** Properly handle arguments passed from terragrunt to TF ([#933](https://github.com/antonbabenko/pre-commit-terraform/issues/933)) ([ea46354](https://github.com/antonbabenko/pre-commit-terraform/commit/ea46354766ddcca2607ae7d460c6e3bf6f48e665)) + # [1.100.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.99.5...v1.100.0) (2025-08-08) From ba7cf0ea2afc8a288d3d94bfe9e74cee7cfb16a7 Mon Sep 17 00:00:00 2001 From: "T. van Riel" <80752652+tvanriel@users.noreply.github.com> Date: Tue, 30 Sep 2025 13:22:15 +0200 Subject: [PATCH 275/293] feat(`terraform_fmt`): Add support for `.tofutest.hcl` and `.tfquery.hcl` files (#935) OpenTofu has their own file pattern for defining tests: *.tofutest.hcl - https://opentofu.org/docs/cli/commands/test/#the-tftesthcl--tofutesthcl-file-structure And Terraform 1.14 introduces *.tfquery.hcl - https://github.com/hashicorp/terraform/blob/eae5ac13dd47eafd2d338b21db7311bbfa861b68/internal/command/fmt.go#L56 Co-authored-by: Ted van Riel --- .pre-commit-hooks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index f99971ee5..b9ab341a8 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -15,7 +15,7 @@ language: script # Supported extensions by Terraform specified in # https://github.com/hashicorp/terraform/blob/0c63fb2b097edcd5cb1a91322765a414206fbea2/internal/command/fmt.go#L30-L35 - files: \.(tf|tofu|tfvars|tftest\.hcl|tfmock\.hcl)$ + files: \.(tf|tofu|tfvars|(tftest|tofutest|tfmock|tfquery)\.hcl)$ exclude: \.terraform/.*$ - id: terraform_docs From 6e6379209d5dc8aa5a91ed7fef17d01a19d90dbd Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 30 Sep 2025 11:22:50 +0000 Subject: [PATCH 276/293] chore(release): version 1.101.0 [skip ci] # [1.101.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.100.1...v1.101.0) (2025-09-30) ### Features * **`terraform_fmt`:** Add support for `.tofutest.hcl` and `.tfquery.hcl` files ([#935](https://github.com/antonbabenko/pre-commit-terraform/issues/935)) ([ba7cf0e](https://github.com/antonbabenko/pre-commit-terraform/commit/ba7cf0ea2afc8a288d3d94bfe9e74cee7cfb16a7)), closes [/github.com/hashicorp/terraform/blob/eae5ac13dd47eafd2d338b21db7311bbfa861b68/internal/command/fmt.go#L56](https://github.com//github.com/hashicorp/terraform/blob/eae5ac13dd47eafd2d338b21db7311bbfa861b68/internal/command/fmt.go/issues/L56) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f642d6252..69275ab39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.101.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.100.1...v1.101.0) (2025-09-30) + + +### Features + +* **`terraform_fmt`:** Add support for `.tofutest.hcl` and `.tfquery.hcl` files ([#935](https://github.com/antonbabenko/pre-commit-terraform/issues/935)) ([ba7cf0e](https://github.com/antonbabenko/pre-commit-terraform/commit/ba7cf0ea2afc8a288d3d94bfe9e74cee7cfb16a7)), closes [/github.com/hashicorp/terraform/blob/eae5ac13dd47eafd2d338b21db7311bbfa861b68/internal/command/fmt.go#L56](https://github.com//github.com/hashicorp/terraform/blob/eae5ac13dd47eafd2d338b21db7311bbfa861b68/internal/command/fmt.go/issues/L56) + ## [1.100.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.100.0...v1.100.1) (2025-09-27) From 718ff95bfd363bdcc0b5a1852f55641852dd945c Mon Sep 17 00:00:00 2001 From: Kovtunov Oleksandr <123635332+ajax-kovtunov-o@users.noreply.github.com> Date: Thu, 9 Oct 2025 14:27:30 +0300 Subject: [PATCH 277/293] fix(`terragrunt_providers_lock`, `terragrunt_validate`: Properly handle arguments passed from terragrunt to TF (#939) --- hooks/terragrunt_providers_lock.sh | 4 +++- hooks/terragrunt_validate.sh | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/hooks/terragrunt_providers_lock.sh b/hooks/terragrunt_providers_lock.sh index aacf65cd9..cf6fb13ea 100755 --- a/hooks/terragrunt_providers_lock.sh +++ b/hooks/terragrunt_providers_lock.sh @@ -15,8 +15,10 @@ function main { # JFYI: terragrunt providers lock color already suppressed via PRE_COMMIT_COLOR=never if common::terragrunt_version_ge_0.78; then + local -ra SUBCOMMAND=(run -- providers lock) local -ra RUN_ALL_SUBCOMMAND=(run --all -- providers lock) else + local -ra SUBCOMMAND=(providers lock) local -ra RUN_ALL_SUBCOMMAND=(run-all providers lock) fi @@ -52,7 +54,7 @@ function per_dir_hook_unique_part { local -a -r args=("$@") # pass the arguments to hook - terragrunt providers lock "${args[@]}" + terragrunt "${SUBCOMMAND[@]}" "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? diff --git a/hooks/terragrunt_validate.sh b/hooks/terragrunt_validate.sh index dc9ac941c..a3f7e1d0a 100755 --- a/hooks/terragrunt_validate.sh +++ b/hooks/terragrunt_validate.sh @@ -15,8 +15,10 @@ function main { # JFYI: terragrunt validate color already suppressed via PRE_COMMIT_COLOR=never if common::terragrunt_version_ge_0.78; then + local -ra SUBCOMMAND=(run -- validate) local -ra RUN_ALL_SUBCOMMAND=(run --all -- validate) else + local -ra SUBCOMMAND=(validate) local -ra RUN_ALL_SUBCOMMAND=(run-all validate) fi @@ -52,7 +54,7 @@ function per_dir_hook_unique_part { local -a -r args=("$@") # pass the arguments to hook - terragrunt validate "${args[@]}" + terragrunt "${SUBCOMMAND[@]}" "${args[@]}" # return exit code to common::per_dir_hook local exit_code=$? From bae0525b6bb6e4c3bcefc8d69f977905801257e1 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 9 Oct 2025 14:37:43 +0300 Subject: [PATCH 278/293] fix(`terragrunt_providers_lock`, `terragrunt_validate`): Properly handle arguments passed from terragrunt to TF (#939) From 48b3b78187549e20f71c431ad8d2e2e2c1bffb2a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 9 Oct 2025 11:42:31 +0000 Subject: [PATCH 279/293] chore(release): version 1.101.1 [skip ci] ## [1.101.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.101.0...v1.101.1) (2025-10-09) ### Bug Fixes * **`terragrunt_providers_lock`, `terragrunt_validate`:** Properly handle arguments passed from terragrunt to TF ([#939](https://github.com/antonbabenko/pre-commit-terraform/issues/939)) ([bae0525](https://github.com/antonbabenko/pre-commit-terraform/commit/bae0525b6bb6e4c3bcefc8d69f977905801257e1)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69275ab39..2a8d6f0f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.101.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.101.0...v1.101.1) (2025-10-09) + + +### Bug Fixes + +* **`terragrunt_providers_lock`, `terragrunt_validate`:** Properly handle arguments passed from terragrunt to TF ([#939](https://github.com/antonbabenko/pre-commit-terraform/issues/939)) ([bae0525](https://github.com/antonbabenko/pre-commit-terraform/commit/bae0525b6bb6e4c3bcefc8d69f977905801257e1)) + # [1.101.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.100.1...v1.101.0) (2025-09-30) From 5b4638b82893175e472d3f6dd29c8044e3afecee Mon Sep 17 00:00:00 2001 From: "George Yermulnik (Georgii Iermulnik)" Date: Thu, 9 Oct 2025 14:49:53 +0300 Subject: [PATCH 280/293] ci(action-semantic-pull-request): Fix for PRs from forks (#941) Suppress `zizmor`'s error: we do want `pull_request_target` - https://github.com/amannn/action-semantic-pull-request#event-triggers --- .github/workflows/pr-title.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 8c76ed8ba..871ed8994 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -1,10 +1,11 @@ name: Validate PR title -on: - pull_request: +on: # zizmor: ignore[dangerous-triggers] + pull_request_target: types: - opened - edited + - reopened - synchronize permissions: From 546eb94af5a2983ea9eb9ca9e7606d465271f00f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:00:56 +0300 Subject: [PATCH 281/293] [pre-commit.ci] pre-commit autoupdate (#938) --- .pre-commit-config.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f0c43a200..1b28af60c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0 + rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 hooks: # Git style - id: check-added-large-files @@ -43,19 +43,19 @@ repos: # Detect hardcoded secrets - repo: https://github.com/gitleaks/gitleaks - rev: 47218a628da59ef6b24197d15f4b0248ca87f4f0 # frozen: v8.27.2 + rev: 39fdb480a06768cc41a84ef86959c07ff33091c4 # frozen: v8.28.0 hooks: - id: gitleaks # Github Action static analysis tool - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: 69fa534d69454f44ddd4451b5e2da7a1c48e525b # frozen: v1.11.0 + rev: 3c10df247c55cf21f75003105b879f145096bd4a # frozen: v1.14.2 hooks: - id: zizmor # Dockerfile - repo: https://github.com/hadolint/hadolint - rev: c3dc18df7a501f02a560a2cc7ba3c69a85ca01d3 # frozen: v2.13.1-beta + rev: 4e697ba704fd23b2409b947a319c19c3ee54d24f # frozen: v2.14.0 hooks: - id: hadolint @@ -105,7 +105,7 @@ repos: # Python - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 0b19ef1fd6ad680ed7752d6daba883ce1265a6de # frozen: v0.12.2 + rev: 9c89adb347f6b973f4905a4be0051eb2ecf85dea # frozen: v0.13.3 hooks: - id: ruff args: @@ -113,12 +113,12 @@ repos: - id: ruff-format - repo: https://github.com/wemake-services/wemake-python-styleguide - rev: 89eca4e53c6a95aebebd9cc86b717fb11a915de9 # frozen: 1.2.0 + rev: be985eceeba226ba83c955f7a31317ff4e9dfd9a # frozen: 1.4.0 hooks: - id: wemake-python-styleguide - repo: https://github.com/pre-commit/mirrors-mypy.git - rev: 0f86793af5ef5f6dc63c8d04a3cabfa3ea8f9c6a # frozen: v1.16.1 + rev: 9f70dc58c23dfcca1b97af99eaeee3140a807c7e # frozen: v1.18.2 hooks: - id: mypy alias: mypy-py313 From c4ef91ef002a49e689756d4ae70f6b261bf9ae2f Mon Sep 17 00:00:00 2001 From: Grigorii Date: Tue, 14 Oct 2025 17:16:21 +0300 Subject: [PATCH 282/293] feat(terrascan): Add support for `__GIT_WORKING_DIR__` in hooks arguments (#943) --- README.md | 8 ++++++++ hooks/terrascan.sh | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/README.md b/README.md index 3aad1e698..ce09ffe44 100644 --- a/README.md +++ b/README.md @@ -1123,6 +1123,14 @@ If the generated name is incorrect, set them by providing the `module-repo-short See the `terrascan run -h` command line help for available options. + To pass the config file path, use the `__GIT_WORKING_DIR__` placeholder. It will be replaced with the Git working directory (repo root) at run time. For example: + + ```yaml + - id: terrascan + args: + - --args=--config-path=__GIT_WORKING_DIR__/.terrascan.toml + ``` + 2. Use the `--args=--verbose` parameter to see the rule ID in the scanning output. Useful to skip validations. 3. Use `--skip-rules="ruleID1,ruleID2"` parameter to skip one or more rules globally while scanning (e.g.: `--args=--skip-rules="ruleID1,ruleID2"`). 4. Use the syntax `#ts:skip=RuleID optional_comment` inside a resource to skip the rule for that resource. diff --git a/hooks/terrascan.sh b/hooks/terrascan.sh index f6586ceae..277abca97 100755 --- a/hooks/terrascan.sh +++ b/hooks/terrascan.sh @@ -12,6 +12,10 @@ function main { common::parse_cmdline "$@" common::export_provided_env_vars "${ENV_VARS[@]}" common::parse_and_export_env_vars + # Support for setting PATH to repo root. + for i in "${!ARGS[@]}"; do + ARGS[i]=${ARGS[i]/__GIT_WORKING_DIR__/$(pwd)\/} + done # JFYI: terrascan color already suppressed via PRE_COMMIT_COLOR=never # shellcheck disable=SC2153 # False positive From 18cead24f7d3df230683a008aaf903f43e8457d6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 14 Oct 2025 14:16:56 +0000 Subject: [PATCH 283/293] chore(release): version 1.102.0 [skip ci] # [1.102.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.101.1...v1.102.0) (2025-10-14) ### Features * **terrascan:** Add support for `__GIT_WORKING_DIR__` in hooks arguments ([#943](https://github.com/antonbabenko/pre-commit-terraform/issues/943)) ([c4ef91e](https://github.com/antonbabenko/pre-commit-terraform/commit/c4ef91ef002a49e689756d4ae70f6b261bf9ae2f)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a8d6f0f2..2798f3f3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.102.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.101.1...v1.102.0) (2025-10-14) + + +### Features + +* **terrascan:** Add support for `__GIT_WORKING_DIR__` in hooks arguments ([#943](https://github.com/antonbabenko/pre-commit-terraform/issues/943)) ([c4ef91e](https://github.com/antonbabenko/pre-commit-terraform/commit/c4ef91ef002a49e689756d4ae70f6b261bf9ae2f)) + ## [1.101.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.101.0...v1.101.1) (2025-10-09) From 983042000161a50588a6a66302ffe6f86b2eb103 Mon Sep 17 00:00:00 2001 From: "George Yermulnik (Georgii Iermulnik)" Date: Fri, 17 Oct 2025 16:31:21 +0300 Subject: [PATCH 284/293] feat: Add support of `__GIT_WORKING_DIR__` placeholder for all hooks (#945) Move `__GIT_WORKING_DIR__` permutation feature over right into `common::parse_cmdline` function so that it is available to all hooks that utilize `common::parse_cmdline` function. Resolves #944 --- README.md | 118 ++++++++++++++----------------------- hooks/_common.sh | 4 +- hooks/terraform_checkov.sh | 5 +- hooks/terraform_tflint.sh | 6 +- hooks/terraform_tfsec.sh | 4 -- hooks/terraform_trivy.sh | 5 +- 6 files changed, 51 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index ce09ffe44..4fc4f41d2 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ If you want to support the development of `pre-commit-terraform` and [many other * [Hooks usage notes and examples](#hooks-usage-notes-and-examples) * [Known limitations](#known-limitations) * [All hooks: Usage of environment variables in `--args`](#all-hooks-usage-of-environment-variables-in---args) + * [All hooks: Usage of `__GIT_WORKING_DIR__` placeholder in `--args`](#all-hooks-usage-of-__git_working_dir__-placeholder-in---args) * [All hooks: Set env vars inside hook at runtime](#all-hooks-set-env-vars-inside-hook-at-runtime) * [All hooks: Disable color output](#all-hooks-disable-color-output) * [All hooks: Log levels](#all-hooks-log-levels) @@ -366,6 +367,24 @@ Config example: If for config above set up `export CONFIG_NAME=.tflint; export CONFIG_EXT=hcl` before `pre-commit run`, args will be expanded to `--config=.tflint.hcl --call-module-type="all"`. +### All hooks: Usage of `__GIT_WORKING_DIR__` placeholder in `--args` + + +> All, except deprecated hooks: `checkov`, `terraform_docs_replace` + +You can use `__GIT_WORKING_DIR__` placeholder in `--args`. It will be replaced +by the Git working directory (repo root) at run time. + +For instance, if you have multiple directories and want to run +`terraform_tflint` in all of them while sharing a single config file — use the +`__GIT_WORKING_DIR__` placeholder in the file path. For example: + +```yaml +- id: terraform_tflint + args: + - --args=--config=__GIT_WORKING_DIR__/.tflint.hcl +``` + ### All hooks: Set env vars inside hook at runtime > All, except deprecated hooks: `checkov`, `terraform_docs_replace` @@ -478,34 +497,26 @@ If you don't see code above in your `pre-commit-config.yaml` or logs - you don't Note that `terraform_checkov` runs recursively during `-d .` usage. That means, for example, if you change `.tf` file in repo root, all existing `.tf` files in the repo will be checked. -1. You can specify custom arguments. E.g.: - - ```yaml - - id: terraform_checkov - args: - - --args=--quiet - - --args=--skip-check CKV2_AWS_8 - ``` - - Check all available arguments [here](https://www.checkov.io/2.Basics/CLI%20Command%20Reference.html). +You can specify custom arguments. E.g.: - For deprecated hook you need to specify each argument separately: +```yaml +- id: terraform_checkov + args: + - --args=--quiet + - --args=--skip-check CKV2_AWS_8 +``` - ```yaml - - id: checkov - args: [ - "-d", ".", - "--skip-check", "CKV2_AWS_8", - ] - ``` +Check all available arguments [here](https://www.checkov.io/2.Basics/CLI%20Command%20Reference.html). -2. When you have multiple directories and want to run `terraform_checkov` in all of them and share a single config file - use the `__GIT_WORKING_DIR__` placeholder. It will be replaced by `terraform_checkov` hooks with the Git working directory (repo root) at run time. For example: +For deprecated hook you need to specify each argument separately: - ```yaml - - id: terraform_checkov - args: - - --args=--config-file __GIT_WORKING_DIR__/.checkov.yml - ``` +```yaml +- id: checkov + args: [ + "-d", ".", + "--skip-check", "CKV2_AWS_8", + ] +``` ### infracost_breakdown @@ -702,15 +713,15 @@ To replicate functionality in `terraform_docs` hook: ### terraform_fmt -1. `terraform_fmt` supports custom arguments so you can pass [supported flags](https://www.terraform.io/docs/cli/commands/fmt.html#usage). Eg: +`terraform_fmt` supports custom arguments so you can pass [supported flags](https://www.terraform.io/docs/cli/commands/fmt.html#usage). Eg: - ```yaml - - id: terraform_fmt - args: - - --args=-no-color - - --args=-diff - - --args=-write=false - ``` +```yaml + - id: terraform_fmt + args: + - --args=-no-color + - --args=-diff + - --args=-write=false +``` ### terraform_providers_lock @@ -834,15 +845,7 @@ To replicate functionality in `terraform_docs` hook: - --args=--enable-rule=terraform_documented_variables ``` -2. When you have multiple directories and want to run `tflint` in all of them and share a single config file, it is impractical to hard-code the path to the `.tflint.hcl` file. The solution is to use the `__GIT_WORKING_DIR__` placeholder which will be replaced by `terraform_tflint` hooks with the Git working directory (repo root) at run time. For example: - - ```yaml - - id: terraform_tflint - args: - - --args=--config=__GIT_WORKING_DIR__/.tflint.hcl - ``` - -3. By default, pre-commit-terraform performs directory switching into the terraform 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: +2. By default, pre-commit-terraform performs directory switching into the terraform 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: terraform_tflint @@ -850,7 +853,6 @@ To replicate functionality in `terraform_docs` hook: - --hook-config=--delegate-chdir ``` - ### terraform_tfsec (deprecated) **DEPRECATED**. [tfsec was replaced by trivy](https://github.com/aquasecurity/tfsec/discussions/1994), so please use [`terraform_trivy`](#terraform_trivy). @@ -894,22 +896,6 @@ To replicate functionality in `terraform_docs` hook: -e aws-s3-enable-bucket-logging,aws-s3-specify-public-access-block ``` -4. When you have multiple directories and want to run `tfsec` in all of them and share a single config file - use the `__GIT_WORKING_DIR__` placeholder. It will be replaced by `terraform_tfsec` hooks with Git working directory (repo root) at run time. For example: - - ```yaml - - id: terraform_tfsec - args: - - --args=--config-file=__GIT_WORKING_DIR__/.tfsec.json - ``` - - Otherwise, will be used files that located in sub-folders: - - ```yaml - - id: terraform_tfsec - args: - - --args=--config-file=.tfsec.json - ``` - ### terraform_trivy 1. `terraform_trivy` will consume modified files that pre-commit @@ -951,14 +937,6 @@ To replicate functionality in `terraform_docs` hook: - --args=--skip-dirs="**/.terraform" ``` -4. When you have multiple directories and want to run `trivy` in all of them and share a single config file - use the `__GIT_WORKING_DIR__` placeholder. It will be replaced by `terraform_trivy` hooks with Git working directory (repo root) at run time. For example: - - ```yaml - - id: terraform_trivy - args: - - --args=--ignorefile=__GIT_WORKING_DIR__/.trivyignore - ``` - ### terraform_validate > [!IMPORTANT] @@ -1123,14 +1101,6 @@ If the generated name is incorrect, set them by providing the `module-repo-short See the `terrascan run -h` command line help for available options. - To pass the config file path, use the `__GIT_WORKING_DIR__` placeholder. It will be replaced with the Git working directory (repo root) at run time. For example: - - ```yaml - - id: terrascan - args: - - --args=--config-path=__GIT_WORKING_DIR__/.terrascan.toml - ``` - 2. Use the `--args=--verbose` parameter to see the rule ID in the scanning output. Useful to skip validations. 3. Use `--skip-rules="ruleID1,ruleID2"` parameter to skip one or more rules globally while scanning (e.g.: `--args=--skip-rules="ruleID1,ruleID2"`). 4. Use the syntax `#ts:skip=RuleID optional_comment` inside a resource to skip the rule for that resource. diff --git a/hooks/_common.sh b/hooks/_common.sh index c63918033..fd26dfce1 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -83,7 +83,9 @@ function common::parse_cmdline { # into a proper array, where each element is a standalone array slice # with quoted elements being treated as a standalone slice of array as well. while read -r -d '' ARG; do - ARGS+=("$ARG") + # Also replace any occurrence of `__GIT_WORKING_DIR__` with + # actual path to Git working dir (repo root) + ARGS+=("${ARG//__GIT_WORKING_DIR__/$PWD}") done < <(echo "$1" | xargs printf '%s\0') shift ;; diff --git a/hooks/terraform_checkov.sh b/hooks/terraform_checkov.sh index 01e2f2454..39a93ef5f 100755 --- a/hooks/terraform_checkov.sh +++ b/hooks/terraform_checkov.sh @@ -12,16 +12,13 @@ function main { common::parse_cmdline "$@" common::export_provided_env_vars "${ENV_VARS[@]}" common::parse_and_export_env_vars - # Support for setting PATH to repo root. - for i in "${!ARGS[@]}"; do - ARGS[i]=${ARGS[i]/__GIT_WORKING_DIR__/$(pwd)\/} - done # Suppress checkov color if [ "$PRE_COMMIT_COLOR" = "never" ]; then export ANSI_COLORS_DISABLED=true fi + # shellcheck disable=SC2153 # ARGS is set in common::parse_cmdline common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" } diff --git a/hooks/terraform_tflint.sh b/hooks/terraform_tflint.sh index 54bcebe38..1c2439f0f 100755 --- a/hooks/terraform_tflint.sh +++ b/hooks/terraform_tflint.sh @@ -13,15 +13,13 @@ function main { common::parse_cmdline "$@" common::export_provided_env_vars "${ENV_VARS[@]}" common::parse_and_export_env_vars - # Support for setting PATH to repo root. - for i in "${!ARGS[@]}"; do - ARGS[i]=${ARGS[i]/__GIT_WORKING_DIR__/$(pwd)\/} - done + # JFYI: tflint color already suppressed via PRE_COMMIT_COLOR=never # Run `tflint --init` for check that plugins installed. # It should run once on whole repo. { + # shellcheck disable=SC2153 # ARGS is set in common::parse_cmdline TFLINT_INIT=$(tflint --init "${ARGS[@]}" 2>&1) 2> /dev/null && common::colorify "green" "Command 'tflint --init' successfully done:" && echo -e "${TFLINT_INIT}\n\n\n" diff --git a/hooks/terraform_tfsec.sh b/hooks/terraform_tfsec.sh index dddad49a6..aa1f3fc8b 100755 --- a/hooks/terraform_tfsec.sh +++ b/hooks/terraform_tfsec.sh @@ -12,10 +12,6 @@ function main { common::parse_cmdline "$@" common::export_provided_env_vars "${ENV_VARS[@]}" common::parse_and_export_env_vars - # Support for setting PATH to repo root. - for i in "${!ARGS[@]}"; do - ARGS[i]=${ARGS[i]/__GIT_WORKING_DIR__/$(pwd)\/} - done # Suppress tfsec color if [ "$PRE_COMMIT_COLOR" = "never" ]; then diff --git a/hooks/terraform_trivy.sh b/hooks/terraform_trivy.sh index d607d67a5..86f56f013 100755 --- a/hooks/terraform_trivy.sh +++ b/hooks/terraform_trivy.sh @@ -12,11 +12,8 @@ function main { common::parse_cmdline "$@" common::export_provided_env_vars "${ENV_VARS[@]}" common::parse_and_export_env_vars - # Support for setting PATH to repo root. - for i in "${!ARGS[@]}"; do - ARGS[i]=${ARGS[i]/__GIT_WORKING_DIR__/$(pwd)\/} - done + # shellcheck disable=SC2153 # ARGS is set in common::parse_cmdline common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" } From 3e855bb9fed2fee7d95af267e52cd88eb6120369 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 17 Oct 2025 13:32:15 +0000 Subject: [PATCH 285/293] chore(release): version 1.103.0 [skip ci] # [1.103.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.102.0...v1.103.0) (2025-10-17) ### Features * Add support of `__GIT_WORKING_DIR__` placeholder for all hooks ([#945](https://github.com/antonbabenko/pre-commit-terraform/issues/945)) ([9830420](https://github.com/antonbabenko/pre-commit-terraform/commit/983042000161a50588a6a66302ffe6f86b2eb103)), closes [#944](https://github.com/antonbabenko/pre-commit-terraform/issues/944) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2798f3f3b..546ad6bb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.103.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.102.0...v1.103.0) (2025-10-17) + + +### Features + +* Add support of `__GIT_WORKING_DIR__` placeholder for all hooks ([#945](https://github.com/antonbabenko/pre-commit-terraform/issues/945)) ([9830420](https://github.com/antonbabenko/pre-commit-terraform/commit/983042000161a50588a6a66302ffe6f86b2eb103)), closes [#944](https://github.com/antonbabenko/pre-commit-terraform/issues/944) + # [1.102.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.101.1...v1.102.0) (2025-10-14) From 57a9bdce24f52323963a9266934c20925a17f598 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 20 Nov 2025 15:59:49 +0200 Subject: [PATCH 286/293] ci: Drop EOL Python 3.9 and macos-13 (#948) --- .github/workflows/ci-cd.yml | 8 ++-- .mypy.ini | 2 +- .pre-commit-config.yaml | 60 +++++++++++++++--------------- pyproject.toml | 4 +- ruff.toml | 3 -- src/pre_commit_terraform/_types.py | 4 +- 6 files changed, 39 insertions(+), 42 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index c57b87bf8..58f2d6e28 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -453,17 +453,17 @@ jobs: # NOTE: The latest and the lowest supported Pythons are prioritized # NOTE: to improve the responsiveness. It's nice to see the most # NOTE: important results first. - - 3.13 - - 3.9 + - 3.14 # str - >- 3.10 + - 3.13 - 3.12 - 3.11 runner-vm-os: - ubuntu-24.04 - - macos-14 - - macos-13 + - macos-15 + - macos-15-intel - windows-2025 toxenv: - pytest diff --git a/.mypy.ini b/.mypy.ini index a92387a64..b5c7808b9 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -1,5 +1,5 @@ [mypy] -python_version = 3.9 +python_version = 3.10 color_output = true error_summary = true # IMPORTANT: The file list MUST NOT have a trailing comma after the last entry. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b28af60c..c15a5d27e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -121,53 +121,53 @@ repos: rev: 9f70dc58c23dfcca1b97af99eaeee3140a807c7e # frozen: v1.18.2 hooks: - id: mypy - alias: mypy-py313 - name: MyPy, for Python 3.13 + alias: mypy-py314 + name: MyPy, for Python 3.14 additional_dependencies: - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` - pytest - pytest-mock args: - - --python-version=3.13 - - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.13 - - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.13 - - --html-report=.tox/.tmp/.test-results/mypy--py-3.13 - - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.13 - - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.13 - - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.13 - - --txt-report=.tox/.tmp/.test-results/mypy--py-3.13 + - --python-version=3.14 + - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.14 + - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.14 + - --html-report=.tox/.tmp/.test-results/mypy--py-3.14 + - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.14 + - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.14 + - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.14 + - --txt-report=.tox/.tmp/.test-results/mypy--py-3.14 pass_filenames: false - id: mypy - alias: mypy-py311 - name: MyPy, for Python 3.11 + alias: mypy-py312 + name: MyPy, for Python 3.12 additional_dependencies: - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` - pytest - pytest-mock args: - - --python-version=3.11 - - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.11 - - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.11 - - --html-report=.tox/.tmp/.test-results/mypy--py-3.11 - - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.11 - - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.11 - - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.11 - - --txt-report=.tox/.tmp/.test-results/mypy--py-3.11 + - --python-version=3.12 + - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.12 + - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.12 + - --html-report=.tox/.tmp/.test-results/mypy--py-3.12 + - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.12 + - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.12 + - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.12 + - --txt-report=.tox/.tmp/.test-results/mypy--py-3.12 pass_filenames: false - id: mypy - alias: mypy-py39 - name: MyPy, for Python 3.9 + alias: mypy-py310 + name: MyPy, for Python 3.10 additional_dependencies: - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` - pytest - pytest-mock args: - - --python-version=3.9 - - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.9 - - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.9 - - --html-report=.tox/.tmp/.test-results/mypy--py-3.9 - - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.9 - - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.9 - - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.9 - - --txt-report=.tox/.tmp/.test-results/mypy--py-3.9 + - --python-version=3.10 + - --any-exprs-report=.tox/.tmp/.test-results/mypy--py-3.10 + - --cobertura-xml-report=.tox/.tmp/.test-results/mypy--py-3.10 + - --html-report=.tox/.tmp/.test-results/mypy--py-3.10 + - --linecount-report=.tox/.tmp/.test-results/mypy--py-3.10 + - --linecoverage-report=.tox/.tmp/.test-results/mypy--py-3.10 + - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.10 + - --txt-report=.tox/.tmp/.test-results/mypy--py-3.10 pass_filenames: false diff --git a/pyproject.toml b/pyproject.toml index afef00e9d..8bcc4f992 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,11 +29,11 @@ classifiers = [ 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ] @@ -43,7 +43,7 @@ dynamic = [ 'urls', 'version', ] -requires-python = ">= 3.9" +requires-python = ">= 3.10" [[project.authors]] name = 'Anton Babenko' diff --git a/ruff.toml b/ruff.toml index 0e57620ea..10a18fb0f 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,6 +1,3 @@ -# Assume Python 3.9 -target-version = "py39" - line-length = 79 # To decrease PR diff size namespace-packages = ["src/pre_commit_terraform/", "tests/pytest/"] diff --git a/src/pre_commit_terraform/_types.py b/src/pre_commit_terraform/_types.py index 69f71e690..9d062afbf 100644 --- a/src/pre_commit_terraform/_types.py +++ b/src/pre_commit_terraform/_types.py @@ -2,12 +2,12 @@ from argparse import ArgumentParser, Namespace from collections.abc import Callable -from typing import Protocol, Union +from typing import Protocol from ._structs import ReturnCode -ReturnCodeType = Union[ReturnCode, int] # Union instead of pipe for Python 3.9 +ReturnCodeType = ReturnCode | int CLIAppEntryPointCallableType = Callable[[Namespace], ReturnCodeType] From 6deaf20ea403ad4d220a0c80330390a5bc53f72f Mon Sep 17 00:00:00 2001 From: William Poussier Date: Thu, 20 Nov 2025 15:40:33 +0100 Subject: [PATCH 287/293] feat(docker): Allow authenticated calls to GitHub API (#947) Accept build arg `GITHUB_TOKEN` to authenticate calls made to GitHub API in `common::install_from_gh_release` function. --- Dockerfile | 2 ++ README.md | 6 ++++++ tools/install/_common.sh | 9 +++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ba98ef6b6..c7c53cd13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,6 +65,8 @@ RUN if [ "$INSTALL_ALL" != "false" ]; then \ echo "TRIVY_VERSION=latest" >> /.env \ ; fi +ARG GITHUB_TOKEN=${GITHUB_TOKEN:-""} + # Docker `RUN`s shouldn't be consolidated here # hadolint global ignore=DL3059 RUN /install/opentofu.sh diff --git a/README.md b/README.md index 4fc4f41d2..2ca672780 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,12 @@ docker build -t pre-commit-terraform \ Set `-e PRE_COMMIT_COLOR=never` to disable the color output in `pre-commit`. +> **NOTE** +> The build install scripts are calling the GitHub API to resolve the release URL. If you need to authenticate those calls, you can pass a GitHub token (the `GITHUB_TOKEN` environment variable is expected to be set with an [access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)): +> ```bash +> docker build -t pre-commit-terraform --build-arg GITHUB_TOKEN . +> ``` +

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 literal 0 HcmV?d00001 From 8c1c83d61aa8d48485d2fe45470f22e3df10ea82 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 26 Feb 2025 13:26:52 +0200 Subject: [PATCH 184/293] ci: Automerge Renovate minor update if all tests pass (#828) --- .github/renovate.json5 | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 4e54aed92..ccac711fd 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,6 +1,16 @@ { $schema: "https://docs.renovatebot.com/renovate-schema.json", - extends: ["local>SpotOnInc/renovate-config"], + extends: [ + "local>SpotOnInc/renovate-config", + // Automerge patch and minor upgrades if they pass tests. | https://docs.renovatebot.com/presets-default/#automergeminor + ":automergeMinor", + // Require all status checks to pass before any automerging. | https://docs.renovatebot.com/presets-default/#automergerequireallstatuschecks + ":automergeRequireAllStatusChecks", + // Automerge digest upgrades if they pass tests. | https://docs.renovatebot.com/presets-default/#automergedigest + ":automergeDigest", + // Raise a PR first before any automerging. | https://docs.renovatebot.com/presets-default/#automergepr + ":automergePr", + ], // To make happy 'Validate PR title' GHA commitMessageLowerCase: "never", // Disable auto-rebase on every commit to avoid reaching Github limits on macos runners From 2c3aa85a2ad4a2d903b2f54ae83ef56ac63146e0 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 26 Feb 2025 22:24:37 +0200 Subject: [PATCH 185/293] fix(docker image security): Improve dependency pinning and disable ability to build image from different tag from what specified in Dockefile (#830) --- .github/workflows/ci-cd.yml | 2 +- Dockerfile | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f5e7c8d8f..448f731b7 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -185,7 +185,7 @@ jobs: python -m pip install --user - setuptools-scm + setuptools-scm~=8.2 shell: bash - name: Set the current dist version from Git id: scm-version diff --git a/Dockerfile b/Dockerfile index 9fa4f2557..59bc2fd7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ -ARG TAG=3.12.0-alpine3.17@sha256:fc34b07ec97a4f288bc17083d288374a803dd59800399c76b977016c9fe5b8f2 -FROM python:${TAG} as builder +FROM python:3.12.0-alpine3.17@sha256:fc34b07ec97a4f288bc17083d288374a803dd59800399c76b977016c9fe5b8f2 AS python_base + +FROM python_base AS builder ARG TARGETOS ARG TARGETARCH @@ -11,8 +12,8 @@ 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~=25.0 \ + setuptools~=75.8 COPY tools/install/ /install/ @@ -100,7 +101,7 @@ RUN . /.env && \ -FROM python:${TAG} +FROM python_base RUN apk add --no-cache \ # pre-commit deps From c949dd26514c71667c4f1c29b318e8ffce077b32 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 26 Feb 2025 22:23:29 +0000 Subject: [PATCH 186/293] chore(release): version 1.97.4 [skip ci] ## [1.97.4](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.3...v1.97.4) (2025-02-26) ### Bug Fixes * **docker image security:** Improve dependency pinning and disable ability to build image from different tag from what specified in Dockefile ([#830](https://github.com/antonbabenko/pre-commit-terraform/issues/830)) ([2c3aa85](https://github.com/antonbabenko/pre-commit-terraform/commit/2c3aa85a2ad4a2d903b2f54ae83ef56ac63146e0)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfe106290..95ec97d97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.97.4](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.3...v1.97.4) (2025-02-26) + + +### Bug Fixes + +* **docker image security:** Improve dependency pinning and disable ability to build image from different tag from what specified in Dockefile ([#830](https://github.com/antonbabenko/pre-commit-terraform/issues/830)) ([2c3aa85](https://github.com/antonbabenko/pre-commit-terraform/commit/2c3aa85a2ad4a2d903b2f54ae83ef56ac63146e0)) + ## [1.97.3](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.2...v1.97.3) (2025-02-04) From 3b6ca9151b88f9758e0612de4f48549038499bd9 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 21 Mar 2025 19:58:13 +0200 Subject: [PATCH 187/293] chore(codecov): Ignore errors during codecov upload (#846) --- .github/workflows/reusable-tox.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 39e1c4869..b1292a3b1 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -400,6 +400,9 @@ jobs: !cancelled() && steps.tox-run.outputs.test-result-files != '' uses: codecov/test-results-action@44ecb3a270cd942bdf0fa8f2ce14cb32493e810a # v1.0.3 + # FIXME There is a bug in action which provokes it to fail during upload + # Related issue: https://github.com/codecov/codecov-action/issues/1794 + continue-on-error: true with: disable_search: true fail_ci_if_error: >- From 7edc6c99f42cc6a7b459cb4193fcc26703f6c5e6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 18:33:39 +0000 Subject: [PATCH 188/293] chore(deps): Update codecov/test-results-action action to v1.1.0 (#842) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/reusable-tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index b1292a3b1..e6bdbe51c 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -399,7 +399,7 @@ jobs: if: >- !cancelled() && steps.tox-run.outputs.test-result-files != '' - uses: codecov/test-results-action@44ecb3a270cd942bdf0fa8f2ce14cb32493e810a # v1.0.3 + uses: codecov/test-results-action@f2dba722c67b86c6caa034178c6e4d35335f6706 # v1.1.0 # FIXME There is a bug in action which provokes it to fail during upload # Related issue: https://github.com/codecov/codecov-action/issues/1794 continue-on-error: true From 73b1d79cd63bd04feda36ccbb58587163dd78ad3 Mon Sep 17 00:00:00 2001 From: Poil Date: Fri, 21 Mar 2025 20:27:26 +0100 Subject: [PATCH 189/293] chore(docker debug): Display opentofu & terraform versions (#839) Co-authored-by: George L. Yermulnik --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 59bc2fd7b..587a9c652 100644 --- a/Dockerfile +++ b/Dockerfile @@ -84,8 +84,9 @@ RUN /install/trivy.sh RUN . /.env && \ F=tools_versions_info && \ pre-commit --version >> $F && \ - (if [ "$OPENTOFU_VERSION" != "false" ]; then echo "./tofu --version | head -n 1" >> $F; else echo "opentofu SKIPPED" >> $F ; fi) && \ - (if [ "$TERRAFORM_VERSION" != "false" ]; then echo "./terraform --version | head -n 1" >> $F; else echo "terraform SKIPPED" >> $F ; fi) && \ + (if [ "$OPENTOFU_VERSION" != "false" ]; then ./tofu --version | head -n 1 >> $F; else echo "opentofu SKIPPED" >> $F ; fi) && \ + (if [ "$TERRAFORM_VERSION" != "false" ]; then ./terraform --version | head -n 1 >> $F; else echo "terraform SKIPPED" >> $F ; fi) && \ + \ (if [ "$CHECKOV_VERSION" != "false" ]; then echo "checkov $(checkov --version)" >> $F; else echo "checkov SKIPPED" >> $F ; fi) && \ (if [ "$HCLEDIT_VERSION" != "false" ]; then echo "hcledit $(./hcledit version)" >> $F; else echo "hcledit SKIPPED" >> $F ; fi) && \ From 926089584613b4e6d79c599fa8ff2abdb6136f26 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 19:28:11 +0000 Subject: [PATCH 190/293] chore(deps): Update dependency python to 3.13 (#847) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pre-commit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 0599ed75d..6e205ea9a 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -55,7 +55,7 @@ jobs: # Skip terraform_tflint which interferes to commit pre-commit auto-fixes - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: - python-version: '3.9' + python-version: '3.13' - name: Execute pre-commit uses: pre-commit/action@9b88afc9cd57fd75b655d5c71bd38146d07135fe # v2.0.3 env: From 7fa8aa582a8e77a28e1c04f548eadc6e946a5ed6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 19:47:37 +0000 Subject: [PATCH 191/293] chore(deps): Update codecov/codecov-action action to v5.4.0 (#841) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/reusable-tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index e6bdbe51c..ee904d7cc 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -375,7 +375,7 @@ jobs: if: >- !cancelled() && steps.tox-run.outputs.cov-report-files != '' - uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 + uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0 with: disable_search: true fail_ci_if_error: >- From 2044f1b64db7baedfc8e714c93b6fd7b178ceecc Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Fri, 21 Mar 2025 22:41:47 +0200 Subject: [PATCH 192/293] docs(docker): Describe current state of docker image security plus minor fixes (#836) * fix(docker image build): Point `changed-files` step to correct scripts dir (and sort list) * Add information about docker security as it not obvious --------- Co-authored-by: MaxymVlasov --- .github/workflows/build-image-test.yaml | 6 +++--- README.md | 21 ++++++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index abd6b9a09..76875dd25 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -41,11 +41,11 @@ jobs: uses: tj-actions/changed-files@dcc7a0cba800f454d79fff4b993e8c3555bcc0a8 # v45.0.7 with: files: | - Dockerfile .dockerignore - tools/entrypoint.sh .github/workflows/build-image-test.yaml - tools/*.sh + Dockerfile + tools/entrypoint.sh + tools/install/*.sh - name: Set IMAGE environment variable if: steps.changed-files-specific.outputs.any_changed == 'true' diff --git a/README.md b/README.md index dae873098..34cf50ba3 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ If you want to support the development of `pre-commit-terraform` and [many other * [terragrunt\_providers\_lock](#terragrunt_providers_lock) * [terragrunt\_validate\_inputs](#terragrunt_validate_inputs) * [Docker Usage](#docker-usage) + * [About Docker image security](#about-docker-image-security) * [File Permissions](#file-permissions) * [Download Terraform modules from private GitHub repositories](#download-terraform-modules-from-private-github-repositories) * [GitHub Actions](#github-actions) @@ -129,9 +130,12 @@ docker pull ghcr.io/antonbabenko/pre-commit-terraform:$TAG All available tags [here](https://github.com/antonbabenko/pre-commit-terraform/pkgs/container/pre-commit-terraform/versions). +Check [About Docker image security](#about-docker-image-security) section to learn more about possible security issues and why you probably want to build and maintain your own image. + + **Build from scratch**: -> [!IMPORTANT] +> **IMPORTANT** > To build image you need to have [`docker buildx`](https://docs.docker.com/build/install-buildx/) enabled as default builder. > Otherwise - provide `TARGETOS` and `TARGETARCH` as additional `--build-arg`'s to `docker build`. @@ -226,8 +230,8 @@ curl -L "$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/late We highly recommend using [WSL/WSL2](https://docs.microsoft.com/en-us/windows/wsl/install) with Ubuntu and following the Ubuntu installation guide. Or use Docker. -> [!IMPORTANT] -> We won't be able to help with issues that can't be reproduced in Linux/Mac. +> **IMPORTANT** +> We won't be able to help with issues that can't be reproduced in Linux/Mac. > So, try to find a working solution and send PR before open an issue. Otherwise, you can follow [this gist](https://gist.github.com/etiennejeanneaurevolve/1ed387dc73c5d4cb53ab313049587d09): @@ -1182,6 +1186,17 @@ Example: ## Docker Usage +### About Docker image security + +Pre-built Docker images contain the latest versions of tools available at the time of their build and remain unchanged afterward. Tags should be immutable whenever possible, and it is highly recommended to pin them using hash sums for security and reproducibility. + +This means that most Docker images will include known CVEs, and the longer an image exists, the more CVEs it may accumulate. This applies even to the latest `vX.Y.Z` tags. +To address this, you can use the `nightly` tag, which rebuilds nightly with the latest versions of all dependencies and latest `pre-commit-terraform` hooks. However, using mutable tags introduces different security concerns. + +Note: Currently, we DO NOT test third-party tools or their dependencies for security vulnerabilities, corruption, or injection (including obfuscated content). If you have ideas for introducing image scans or other security improvements, please open an issue or submit a PR. Some ideas are already tracked in [#835](https://github.com/antonbabenko/pre-commit-terraform/issues/835). + +From a security perspective, the best approach is to manage the Docker image yourself and update its dependencies as needed. This allows you to remove unnecessary dependencies, reducing the number of potential CVEs and improving overall security. + ### File Permissions A mismatch between the Docker container's user and the local repository file ownership can cause permission issues in the repository where `pre-commit` is run. The container runs as the `root` user by default, and uses a `tools/entrypoint.sh` script to assume a user ID and group ID if specified by the environment variable `USERID`. From 8620e0f20e69610ef0199b61bc1ac16e29911a90 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 20:42:28 +0000 Subject: [PATCH 193/293] chore(deps): Update docker/build-push-action action to v6.15.0 (#848) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 4 ++-- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 76875dd25..84d566c6c 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -61,7 +61,7 @@ jobs: - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: context: . build-args: | @@ -112,7 +112,7 @@ jobs: if: >- steps.changed-files-specific.outputs.any_changed == 'true' && matrix.os == 'ubuntu-latest' - uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: context: . build-args: | diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 5301fa58b..f3840cadf 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -47,7 +47,7 @@ jobs: - name: Build and Push release if: github.event_name != 'schedule' - uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: context: . build-args: | @@ -64,7 +64,7 @@ jobs: - name: Build and Push nightly if: github.event_name == 'schedule' - uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: context: . build-args: | From 9b1e16dc3308bfc410de8d7749454a676154f5f8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 22:55:59 +0200 Subject: [PATCH 194/293] chore(deps): Update pre-commit hook adrienverge/yamllint to v1.36.2 (#849) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e499d49a8..b769fe7ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -62,7 +62,7 @@ repos: - --implicit_start - repo: https://github.com/adrienverge/yamllint.git - rev: v1.35.1 + rev: v1.36.2 hooks: - id: yamllint types: From bd80cf1c2fb99117302b42e50b8bbb49fe6315be Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 23:00:25 +0200 Subject: [PATCH 195/293] chore(deps): Update pre-commit hook gitleaks/gitleaks to v8.24.0 (#850) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b769fe7ff..7a1c40114 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: # Detect hardcoded secrets - repo: https://github.com/gitleaks/gitleaks - rev: v8.23.3 + rev: v8.24.0 hooks: - id: gitleaks From a0c86ac9a1642677c9d84fb2f44a8705da3ae89d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 23:44:02 +0200 Subject: [PATCH 196/293] chore(deps): Update tj-actions/changed-files action to v46 (#851) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 84d566c6c..417172563 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -38,7 +38,7 @@ jobs: - name: Get changed Docker related files id: changed-files-specific - uses: tj-actions/changed-files@dcc7a0cba800f454d79fff4b993e8c3555bcc0a8 # v45.0.7 + uses: tj-actions/changed-files@2f7c5bfce28377bc069a65ba478de0a74aa0ca32 # v46.0.1 with: files: | .dockerignore From b678d06881046187dfd7f74be1f892457eed8a5f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 21:44:45 +0000 Subject: [PATCH 197/293] chore(deps): Update actions/upload-artifact action to v4.6.1 (#853) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 2 +- .github/workflows/scorecards.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 448f731b7..fceab3c08 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -372,7 +372,7 @@ jobs: >> "${GITHUB_OUTPUT}" working-directory: dist - name: Store the distribution packages - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: >- ${{ needs.pre-setup.outputs.dists-artifact-name }} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 582d8f7b5..630c6cf13 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -66,7 +66,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable # uploads of run results in SARIF format to the repository Actions tab. - name: Upload artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: SARIF file path: results.sarif From 711f5ffb8f24e782eb42b7f537d40519c19a406d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 21:45:13 +0000 Subject: [PATCH 198/293] chore(deps): Update github/codeql-action action to v3.28.11 (#854) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6f0b862c1..41e8d1c56 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -46,7 +46,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 + uses: github/codeql-action/init@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in @@ -58,7 +58,7 @@ jobs: # If this step fails, then you should remove it and run the build # manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 + uses: github/codeql-action/autobuild@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 # ℹ️ Command-line programs to run using the OS shell. # yamllint disable-line rule:line-length @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 + uses: github/codeql-action/analyze@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 with: category: /language:${{matrix.language}} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 630c6cf13..0bd6259db 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -74,6 +74,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 + uses: github/codeql-action/upload-sarif@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 with: sarif_file: results.sarif From 57a19012c07421f8ae2170b0d89c425bb78fa894 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 23:52:47 +0200 Subject: [PATCH 199/293] chore(deps): Update actions/cache action to v4.2.2 (#852) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 4 ++-- .github/workflows/reusable-tox.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index fceab3c08..77f2e64f6 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -161,7 +161,7 @@ jobs: echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" shell: bash - name: Set up pip cache - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- @@ -307,7 +307,7 @@ jobs: run: >- echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" - name: Set up pip cache - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index ee904d7cc..5f121b5d0 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -181,7 +181,7 @@ jobs: - name: Cache pre-commit.com virtualenvs if: inputs.toxenv == 'pre-commit' - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 with: path: ~/.cache/pre-commit key: >- @@ -239,7 +239,7 @@ jobs: shell: bash - name: Set up pip cache if: fromJSON(steps.py-abi.outputs.is-stable-abi) - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- From 91b5ff71855224b0abb094c4a20fc7ab2cc118a8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 23:57:30 +0200 Subject: [PATCH 200/293] chore(deps): Update docker/login-action action to v3.4.0 (#855) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index f3840cadf..17aeb05f0 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -24,7 +24,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 - name: Login to GitHub Container Registry - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From e15cbe065111669663b2974944d7b93c960997bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:23:03 +0200 Subject: [PATCH 201/293] chore(deps): Update ossf/scorecard-action action to v2.4.1 (#856) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 0bd6259db..fb8d42a23 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -42,7 +42,7 @@ jobs: persist-credentials: false - name: Run analysis - uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 with: results_file: results.sarif results_format: sarif From 55f403373d0b9531e0baf5f7d9dca9dab05fb7ad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 23:23:47 +0000 Subject: [PATCH 202/293] chore(deps): Update MaxymVlasov/dive-action action to v1.4.0 (#857) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 417172563..f9a955576 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -100,7 +100,7 @@ jobs: - name: Dive - check image for waste files if: steps.changed-files-specific.outputs.any_changed == 'true' - uses: MaxymVlasov/dive-action@94506fd846be3ec26406118c3878ccd2ad2b0150 # v1.3.0 + uses: MaxymVlasov/dive-action@43dafd0015826beaca5110157c9262c5dc10672a # v1.4.0 with: image: ${{ env.IMAGE }} config-file: ${{ github.workspace }}/.github/.dive-ci.yaml From 4629d41fa11ca70e35b16462809f86604cf6a852 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 23:24:14 +0000 Subject: [PATCH 203/293] chore(deps): Update docker/setup-buildx-action action to v3.10.0 (#859) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- .github/workflows/build-image.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index f9a955576..62c3a0443 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -56,7 +56,7 @@ jobs: >> $GITHUB_ENV - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 if: steps.changed-files-specific.outputs.any_changed == 'true' - name: Build if Dockerfile changed diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 17aeb05f0..cfd70a48d 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -22,7 +22,7 @@ jobs: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Login to GitHub Container Registry uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: @@ -43,7 +43,7 @@ jobs: run: >- echo "IMAGE_REPO=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Build and Push release if: github.event_name != 'schedule' From 75b439e9a9e122984ac55708d3901a15ddf3e95d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:36:46 +0200 Subject: [PATCH 204/293] chore(deps): Update actions/download-artifact action to v4.2.0 (#858) --- .github/workflows/reusable-tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 5f121b5d0..f779df1a9 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -266,7 +266,7 @@ jobs: - name: Download all the dists if: >- contains(fromJSON('["metadata-validation", "pytest"]'), inputs.toxenv) - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + uses: actions/download-artifact@b14cf4c92620c250e1c074ab0a5800e37df86765 # v4.2.0 with: name: ${{ inputs.dists-artifact-name }} path: dist/ From f61a87ecc300fad4d822e1fd5afd9ea7177d8694 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:46:20 +0200 Subject: [PATCH 205/293] chore(deps): Update cycjimmy/semantic-release-action action to v4.2.0 (#860) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 094c6d3d7..904486ca6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,7 +36,7 @@ jobs: fetch-depth: 0 - name: Release - uses: cycjimmy/semantic-release-action@b1b432f13acb7768e0c8efdec416d363a57546f2 # v4.1.1 + uses: cycjimmy/semantic-release-action@0a51e81a6baff2acad3ee88f4121c589c73d0f0e # v4.2.0 with: semantic_version: 18.0.0 extra_plugins: | From f3c26482e4003d4ac4199e1d1570c724ff132cbe Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 25 Mar 2025 21:02:13 +0200 Subject: [PATCH 206/293] chore(linters): Reorder linters, make hadolint ignores more specific (#832) * Introduce hadolint config file and reorder linters * Move hadolint ignores to meaningful locations Sadly, but hadolint ignore doesn't work if there any symbol on the same line - ignore stop working. That's why set justification on different line to hadolint ignore comment --------- Co-authored-by: George L. Yermulnik --- .pre-commit-config.yaml | 55 +++++++++++++++++------------------------ Dockerfile | 9 +++++-- tests/Dockerfile | 10 +++----- 3 files changed, 34 insertions(+), 40 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7a1c40114..d59c8d987 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,9 +47,13 @@ repos: hooks: - id: gitleaks -# -# YAML Linters -# +# Dockerfile +- repo: https://github.com/hadolint/hadolint + rev: v2.12.1-beta + hooks: + - id: hadolint + +# YAML - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt rev: 0.2.3 hooks: @@ -71,7 +75,7 @@ repos: args: - --strict -# JSON5 Linter +# JSON5 - repo: https://github.com/pre-commit/mirrors-prettier rev: v3.1.0 hooks: @@ -79,6 +83,21 @@ repos: # https://prettier.io/docs/en/options.html#parser files: .json5$ +# Bash +- repo: https://github.com/jumanjihouse/pre-commit-hooks + rev: 3.0.0 + hooks: + - id: shfmt + args: + - -l + - -i + - '2' + - -ci + - -sr + - -w + - id: shellcheck + +# Python - repo: https://github.com/pre-commit/mirrors-mypy.git rev: v1.15.0 hooks: @@ -133,31 +152,3 @@ repos: - --lineprecision-report=.tox/.tmp/.test-results/mypy--py-3.9 - --txt-report=.tox/.tmp/.test-results/mypy--py-3.9 pass_filenames: false - -- repo: https://github.com/jumanjihouse/pre-commit-hooks - rev: 3.0.0 - hooks: - - id: shfmt - args: - - -l - - -i - - '2' - - -ci - - -sr - - -w - - id: shellcheck - -# Dockerfile linter -- repo: https://github.com/hadolint/hadolint - rev: v2.12.1-beta - hooks: - - id: hadolint - args: - - --ignore=DL3007 # Using latest - - --ignore=DL3013 # Pin versions in pip - - --ignore=DL3027 # Do not use apt - - --ignore=DL3059 # Docker `RUN`s shouldn't be consolidated here - - --ignore=DL4006 # Not related to alpine - - --ignore=SC1091 # Useless check - - --ignore=SC2015 # Useless check - - --ignore=SC3037 # Not related to alpine diff --git a/Dockerfile b/Dockerfile index 587a9c652..4c69f2586 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,6 +65,8 @@ RUN if [ "$INSTALL_ALL" != "false" ]; then \ echo "TRIVY_VERSION=latest" >> /.env \ ; fi +# Docker `RUN`s shouldn't be consolidated here +# hadolint global ignore=DL3059 RUN /install/opentofu.sh RUN /install/terraform.sh @@ -81,12 +83,15 @@ RUN /install/trivy.sh # Checking binaries versions and write it to debug file + +# SC2086 - We do not need to quote "$F" variable, because it's not contain spaces +# DL4006 - Not Applicable for /bin/sh in alpine images. Disable, as recommended by check itself +# hadolint ignore=SC2086,DL4006 RUN . /.env && \ F=tools_versions_info && \ pre-commit --version >> $F && \ (if [ "$OPENTOFU_VERSION" != "false" ]; then ./tofu --version | head -n 1 >> $F; else echo "opentofu SKIPPED" >> $F ; fi) && \ (if [ "$TERRAFORM_VERSION" != "false" ]; then ./terraform --version | head -n 1 >> $F; else echo "terraform SKIPPED" >> $F ; fi) && \ - \ (if [ "$CHECKOV_VERSION" != "false" ]; then echo "checkov $(checkov --version)" >> $F; else echo "checkov SKIPPED" >> $F ; fi) && \ (if [ "$HCLEDIT_VERSION" != "false" ]; then echo "hcledit $(./hcledit version)" >> $F; else echo "hcledit SKIPPED" >> $F ; fi) && \ @@ -98,7 +103,7 @@ RUN . /.env && \ (if [ "$TFSEC_VERSION" != "false" ]; then echo "tfsec $(./tfsec --version)" >> $F; else echo "tfsec SKIPPED" >> $F ; fi) && \ (if [ "$TFUPDATE_VERSION" != "false" ]; then echo "tfupdate $(./tfupdate --version)" >> $F; else echo "tfupdate SKIPPED" >> $F ; fi) && \ (if [ "$TRIVY_VERSION" != "false" ]; then echo "trivy $(./trivy --version)" >> $F; else echo "trivy SKIPPED" >> $F ; fi) && \ - echo -e "\n\n" && cat $F && echo -e "\n\n" + printf "\n\n\n" && cat $F && printf "\n\n\n" diff --git a/tests/Dockerfile b/tests/Dockerfile index ec77d18af..d64ba61e0 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -1,11 +1,9 @@ +# We use `latest` tag for tests proposes +# hadolint ignore=DL3007 FROM pre-commit-terraform:latest -RUN apt update && \ - apt install -y \ - datamash \ - time && \ - # Cleanup - rm -rf /var/lib/apt/lists/* +RUN apk add --no-cache \ + datamash=~1.8 WORKDIR /pct ENTRYPOINT [ "/pct/tests/hooks_performance_test.sh" ] From e64974ed7745a3c35882b71be49fc89570cb006e Mon Sep 17 00:00:00 2001 From: Andrew Brown <2384077+AndyAtTib@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:30:44 +0000 Subject: [PATCH 207/293] feat(docker): Support execution on repos under `git worktree` (#845) --- tools/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/entrypoint.sh b/tools/entrypoint.sh index 86d5e369a..d7f0c9c83 100755 --- a/tools/entrypoint.sh +++ b/tools/entrypoint.sh @@ -39,7 +39,7 @@ wdir="$(pwd)" if ! su-exec "$USERID" "$BASHPATH" -c "test -w $wdir && test -r $wdir"; then echo_error_and_exit "uid:gid $USERID lacks permissions to $wdir/" fi -wdirgitindex="$wdir/.git/index" +wdirgitindex="$(git rev-parse --git-dir 2>&1)/index" || echo_error_and_exit "${wdirgitindex%/index}" if ! su-exec "$USERID" "$BASHPATH" -c "test -w $wdirgitindex && test -r $wdirgitindex"; then echo_error_and_exit "uid:gid $USERID cannot write to $wdirgitindex" fi From e5f4170e017cfa2a848a1be0763bdab683076242 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 25 Mar 2025 20:31:12 +0000 Subject: [PATCH 208/293] chore(release): version 1.98.0 [skip ci] # [1.98.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.4...v1.98.0) (2025-03-25) ### Features * **docker:** Support execution on repos under `git worktree` ([#845](https://github.com/antonbabenko/pre-commit-terraform/issues/845)) ([e64974e](https://github.com/antonbabenko/pre-commit-terraform/commit/e64974ed7745a3c35882b71be49fc89570cb006e)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ec97d97..b94e405ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.98.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.4...v1.98.0) (2025-03-25) + + +### Features + +* **docker:** Support execution on repos under `git worktree` ([#845](https://github.com/antonbabenko/pre-commit-terraform/issues/845)) ([e64974e](https://github.com/antonbabenko/pre-commit-terraform/commit/e64974ed7745a3c35882b71be49fc89570cb006e)) + ## [1.97.4](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.3...v1.97.4) (2025-02-26) From 23928fbf8511697c915c3231977ee254bd3fa0c2 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 26 Mar 2025 15:48:58 +0200 Subject: [PATCH 209/293] chore(linters): Apply ruff-format (#861) --- src/pre_commit_terraform/_cli_parsing.py | 4 ++- src/pre_commit_terraform/_errors.py | 4 +-- src/pre_commit_terraform/_types.py | 3 ++- .../terraform_docs_replace.py | 26 ++++++++++++------- tests/pytest/_cli_test.py | 20 ++++++++------ tests/pytest/terraform_docs_replace_test.py | 17 ++++++------ 6 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/pre_commit_terraform/_cli_parsing.py b/src/pre_commit_terraform/_cli_parsing.py index 969b0ae17..9f6e14340 100644 --- a/src/pre_commit_terraform/_cli_parsing.py +++ b/src/pre_commit_terraform/_cli_parsing.py @@ -22,7 +22,9 @@ def attach_subcommand_parsers_to(root_cli_parser: ArgumentParser, /) -> None: required=True, ) for subcommand_module in SUBCOMMAND_MODULES: - subcommand_parser = subcommand_parsers.add_parser(subcommand_module.CLI_SUBCOMMAND_NAME) + subcommand_parser = subcommand_parsers.add_parser( + subcommand_module.CLI_SUBCOMMAND_NAME + ) subcommand_parser.set_defaults( invoke_cli_app=subcommand_module.invoke_cli_app, ) diff --git a/src/pre_commit_terraform/_errors.py b/src/pre_commit_terraform/_errors.py index c0f973acc..7bdb738da 100644 --- a/src/pre_commit_terraform/_errors.py +++ b/src/pre_commit_terraform/_errors.py @@ -6,8 +6,8 @@ class PreCommitTerraformBaseError(Exception): class PreCommitTerraformRuntimeError( - PreCommitTerraformBaseError, - RuntimeError, + PreCommitTerraformBaseError, + RuntimeError, ): """An exception representing a runtime error condition.""" diff --git a/src/pre_commit_terraform/_types.py b/src/pre_commit_terraform/_types.py index 78db357e7..c08320f71 100644 --- a/src/pre_commit_terraform/_types.py +++ b/src/pre_commit_terraform/_types.py @@ -18,7 +18,8 @@ class CLISubcommandModuleProtocol(Protocol): """This constant contains a CLI.""" def populate_argument_parser( - self, subcommand_parser: ArgumentParser, + self, + subcommand_parser: ArgumentParser, ) -> None: """Run a module hook for populating the subcommand parser.""" diff --git a/src/pre_commit_terraform/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py index 444bbc588..9721131e1 100644 --- a/src/pre_commit_terraform/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -18,14 +18,20 @@ def populate_argument_parser(subcommand_parser: ArgumentParser) -> None: 'replace the entire README.md file each time.' ) subcommand_parser.add_argument( - '--dest', dest='dest', default='README.md', + '--dest', + dest='dest', + default='README.md', ) subcommand_parser.add_argument( - '--sort-inputs-by-required', dest='sort', action='store_true', + '--sort-inputs-by-required', + dest='sort', + action='store_true', help='[deprecated] use --sort-by-required instead', ) subcommand_parser.add_argument( - '--sort-by-required', dest='sort', action='store_true', + '--sort-by-required', + dest='sort', + action='store_true', ) subcommand_parser.add_argument( '--with-aggregate-type-defaults', @@ -51,8 +57,9 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: dirs: list[str] = [] for filename in cast_to(list[str], parsed_cli_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('.tfvars') + ): dirs.append(os.path.dirname(filename)) retval = ReturnCode.OK @@ -64,13 +71,14 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: if cast_to(bool, parsed_cli_args.sort): procArgs.append('--sort-by-required') procArgs.append('md') - procArgs.append("./{dir}".format(dir=dir)) + procArgs.append('./{dir}'.format(dir=dir)) procArgs.append('>') procArgs.append( - './{dir}/{dest}'. - format(dir=dir, dest=cast_to(bool, parsed_cli_args.dest)), + './{dir}/{dest}'.format( + dir=dir, dest=cast_to(bool, parsed_cli_args.dest) + ), ) - subprocess.check_call(" ".join(procArgs), shell=True) + subprocess.check_call(' '.join(procArgs), shell=True) except subprocess.CalledProcessError as e: print(e) retval = ReturnCode.ERROR diff --git a/tests/pytest/_cli_test.py b/tests/pytest/_cli_test.py index 52ea82ab6..b8176b8e7 100644 --- a/tests/pytest/_cli_test.py +++ b/tests/pytest/_cli_test.py @@ -42,17 +42,19 @@ ), ) def test_known_interrupts( - capsys: pytest.CaptureFixture[str], - expected_stderr: str, - monkeypatch: pytest.MonkeyPatch, - raised_error: BaseException, + capsys: pytest.CaptureFixture[str], + expected_stderr: str, + monkeypatch: pytest.MonkeyPatch, + raised_error: BaseException, ) -> None: """Check that known interrupts are turned into return code 1.""" + class CustomCmdStub: CLI_SUBCOMMAND_NAME = 'sentinel' def populate_argument_parser( - self, subcommand_parser: ArgumentParser, + self, + subcommand_parser: ArgumentParser, ) -> None: return None @@ -72,15 +74,17 @@ def invoke_cli_app(self, parsed_cli_args: Namespace) -> ReturnCodeType: def test_app_exit( - capsys: pytest.CaptureFixture[str], - monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], + monkeypatch: pytest.MonkeyPatch, ) -> None: """Check that an exit exception is re-raised.""" + class CustomCmdStub: CLI_SUBCOMMAND_NAME = 'sentinel' def populate_argument_parser( - self, subcommand_parser: ArgumentParser, + self, + subcommand_parser: ArgumentParser, ) -> None: return None diff --git a/tests/pytest/terraform_docs_replace_test.py b/tests/pytest/terraform_docs_replace_test.py index 87989d965..2fe765b91 100644 --- a/tests/pytest/terraform_docs_replace_test.py +++ b/tests/pytest/terraform_docs_replace_test.py @@ -24,8 +24,7 @@ def test_arg_parser_populated() -> None: def test_check_is_deprecated() -> None: """Verify that `replace-docs` shows a deprecation warning.""" deprecation_msg_regex = ( - r'^`terraform_docs_replace` hook is DEPRECATED\.' - 'For migration.*$' + r'^`terraform_docs_replace` hook is DEPRECATED\.For migration.*$' ) with pytest.warns(UserWarning, match=deprecation_msg_regex): # not `pytest.deprecated_call()` due to this being a user warning @@ -70,10 +69,10 @@ def test_check_is_deprecated() -> None: 'pre_commit_terraform.terraform_docs_replace', ) def test_control_flow_positive( - expected_cmds: list[str], - mocker: pytest_mock.MockerFixture, - monkeypatch: pytest.MonkeyPatch, - parsed_cli_args: Namespace, + expected_cmds: list[str], + mocker: pytest_mock.MockerFixture, + monkeypatch: pytest.MonkeyPatch, + parsed_cli_args: Namespace, ) -> None: """Check that the subcommand's happy path works.""" check_call_mock = mocker.Mock() @@ -86,7 +85,7 @@ def test_control_flow_positive( assert ReturnCode.OK == invoke_cli_app(parsed_cli_args) executed_commands = [ - cmd for ((cmd, ), _shell) in check_call_mock.call_args_list + cmd for ((cmd,), _shell) in check_call_mock.call_args_list ] assert len(expected_cmds) == check_call_mock.call_count @@ -98,8 +97,8 @@ def test_control_flow_positive( 'pre_commit_terraform.terraform_docs_replace', ) def test_control_flow_negative( - mocker: pytest_mock.MockerFixture, - monkeypatch: pytest.MonkeyPatch, + mocker: pytest_mock.MockerFixture, + monkeypatch: pytest.MonkeyPatch, ) -> None: """Check that the subcommand's error processing works.""" parsed_cli_args = Namespace( From 03ab1529a887fcbe356ecd68f350331376e29c10 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 26 Mar 2025 19:24:50 +0200 Subject: [PATCH 210/293] chore: Ignore ruff-format apply in git blame (#862) Ignore https://github.com/antonbabenko/pre-commit-terraform/pull/861 --- .git-blame-ignore-revs | 70 +++++++++++++++++++++++++++++++++++++++++ .github/CONTRIBUTING.md | 5 +++ 2 files changed, 75 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..db412838b --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,70 @@ +# `git blame` master ignore list. +# +# This file contains a list of git hashes of revisions to be ignored +# by `git blame`. These revisions are considered "unimportant" in +# that they are unlikely to be what you are interested in when blaming. +# They are typically expected to be formatting-only changes. +# +# It can be used for `git blame` using `--ignore-revs-file` or by +# setting `blame.ignoreRevsFile` in the `git config`[1]. +# +# Ignore these commits when reporting with blame. Calling +# +# git blame --ignore-revs-file .git-blame-ignore-revs +# +# will tell `git blame` to ignore changes made by these revisions when +# assigning blame, as if the change never happened. +# +# You can enable this as a default for your local repository by +# running +# +# git config blame.ignoreRevsFile .git-blame-ignore-revs +# +# This will probably be automatically picked by your IDE +# (VSCode+GitLens and JetBrains products are confirmed to do this). +# +# Important: if you are switching to a branch without this file, +# `git blame` will fail with an error. +# +# GitHub also excludes the commits listed below from its "Blame" +# views[2][3]. +# +# [1]: https://git-scm.com/docs/git-blame#Documentation/git-blame.txt-blameignoreRevsFile +# [2]: https://github.blog/changelog/2022-03-24-ignore-commits-in-the-blame-view-beta/ +# [3]: https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view +# +# Guidelines: +# - Only large (generally automated) reformatting or renaming PRs +# should be added to this list. Do not put things here just because +# you feel they are trivial or unimportant. If in doubt, do not put +# it on this list. +# - When adding a single revision, use inline comment to link relevant +# issue/PR. Alternatively, paste the commit title instead. +# Example: +# d4a8b7307acc2dc8a8833ccfa65426ad28b3ffc9 # https://github.com/sanitizers/octomachinery/issues/1 +# - When adding multiple revisions (like a bulk of work over many +# commits), organize them in blocks. Precede each such block with a +# comment starting with the word "START", followed by a link to the +# relevant issue or PR. Add a similar comment after the last block +# line but use the word "END", followed by the same link. +# Alternatively, add or augment the link with a text motivation and +# description of work performed in each commit. +# After each individual commit in the block, add an inline comment +# with the commit title line. +# Example: +# # START https://github.com/sanitizers/octomachinery/issues/1 +# 6f0bd2d8a1e6cd2e794cd39976e9756e0c85ac66 # Bulk-replace smile emojis with unicorns +# d53974df11dbc22cbea9dc7dcbc9896c25979a27 # Replace double with single quotes +# ... +# # END https://github.com/sanitizers/octomachinery/issues/1 +# - Only put full 40-character hashes on this list (not short hashes +# or any other revision reference). +# - Append to the bottom of the file, regardless of the chronological +# order of the revisions. Revisions within blocks should be in +# chronological order from oldest to newest. +# - Because you must use a hash, you need to append to this list in a +# follow-up PR to the actual reformatting PR that you are trying to +# ignore. This approach helps avoid issues with arbitrary rebases +# and squashes while the pull request is in progress. + +23928fbf8511697c915c3231977ee254bd3fa0c2 # chore(linters): Apply ruff-format diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a15eedfa6..e40a40a1f 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,5 +1,6 @@ # Notes for contributors +* [Configure `git blame` to ignore formatting commits](#configure-git-blame-to-ignore-formatting-commits) * [Run and debug hooks locally](#run-and-debug-hooks-locally) * [Run hook performance test](#run-hook-performance-test) * [Run via BASH](#run-via-bash) @@ -15,6 +16,10 @@ * [Contributing to Python code](#contributing-to-python-code) * [Run tests in your fork](#run-tests-in-your-fork) +## Configure `git blame` to ignore formatting commits + +This project uses `.git-blame-ignore-revs` to exclude formatting-related commits from `git blame` history. To configure your local `git blame` to ignore these commits, refer to the [.git-blame-ignore-revs](/.git-blame-ignore-revs) file for details. + ## Run and debug hooks locally ```bash From 4366a734f507351dca19c49eacdbdf2b4d74e6fd Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 26 Mar 2025 22:47:30 +0200 Subject: [PATCH 211/293] chore(linters): Apply ruff linter semi-automatic linting fixes (#863) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * I001 - Import block is un-sorted or un-formatted * B028 - No explicit `stacklevel` keyword argument found * COM812 - Trailing comma missing * EM101 - Exception must not use a string literal, assign to variable first * FURB113 - Use `procArgs.extend(...)` instead of repeatedly calling `procArgs.append()` * PIE810 - Call `endswith` once with a `tuple` * SIM300 - Yoda condition detected * TC006 - Add quotes to type expression in `typing.cast()` * UP032 - Use f-string instead of `format` call * Fix few ruff fixes --------- Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- src/pre_commit_terraform/__main__.py | 3 ++- src/pre_commit_terraform/_cli.py | 2 +- src/pre_commit_terraform/_cli_parsing.py | 2 +- .../terraform_docs_replace.py | 22 +++++++++++-------- tests/pytest/_cli_test.py | 8 +++---- tests/pytest/terraform_docs_replace_test.py | 8 ++++--- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/pre_commit_terraform/__main__.py b/src/pre_commit_terraform/__main__.py index 18a63dfd0..00629a7b7 100644 --- a/src/pre_commit_terraform/__main__.py +++ b/src/pre_commit_terraform/__main__.py @@ -1,6 +1,7 @@ """A runpy-style CLI entry-point module.""" -from sys import argv, exit as exit_with_return_code +from sys import argv +from sys import exit as exit_with_return_code from ._cli import invoke_cli_app diff --git a/src/pre_commit_terraform/_cli.py b/src/pre_commit_terraform/_cli.py index edcb2ea30..70f7a70a3 100644 --- a/src/pre_commit_terraform/_cli.py +++ b/src/pre_commit_terraform/_cli.py @@ -23,7 +23,7 @@ def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: parsed_cli_args = root_cli_parser.parse_args(cli_args) invoke_cli_app = cast_to( # FIXME: attempt typing per https://stackoverflow.com/a/75666611/595220 - CLIAppEntryPointCallableType, + 'CLIAppEntryPointCallableType', parsed_cli_args.invoke_cli_app, ) diff --git a/src/pre_commit_terraform/_cli_parsing.py b/src/pre_commit_terraform/_cli_parsing.py index 9f6e14340..a51a6fc2b 100644 --- a/src/pre_commit_terraform/_cli_parsing.py +++ b/src/pre_commit_terraform/_cli_parsing.py @@ -23,7 +23,7 @@ def attach_subcommand_parsers_to(root_cli_parser: ArgumentParser, /) -> None: ) for subcommand_module in SUBCOMMAND_MODULES: subcommand_parser = subcommand_parsers.add_parser( - subcommand_module.CLI_SUBCOMMAND_NAME + subcommand_module.CLI_SUBCOMMAND_NAME, ) subcommand_parser.set_defaults( invoke_cli_app=subcommand_module.invoke_cli_app, diff --git a/src/pre_commit_terraform/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py index 9721131e1..f72a556b1 100644 --- a/src/pre_commit_terraform/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -53,12 +53,13 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: 'https://github.com/antonbabenko/pre-commit-terraform/issues/248' '#issuecomment-1290829226', category=UserWarning, + stacklevel=1, # It's should be 2, but tests are failing w/ values >1. As it's deprecated hook, it's safe to leave it as is w/o fixing it later. ) dirs: list[str] = [] - for filename in cast_to(list[str], parsed_cli_args.filenames): + for filename in cast_to('list[str]', parsed_cli_args.filenames): if os.path.realpath(filename) not in dirs and ( - filename.endswith('.tf') or filename.endswith('.tfvars') + filename.endswith(('.tf', '.tfvars')) ): dirs.append(os.path.dirname(filename)) @@ -68,14 +69,17 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: try: procArgs = [] procArgs.append('terraform-docs') - if cast_to(bool, parsed_cli_args.sort): + if cast_to('bool', parsed_cli_args.sort): procArgs.append('--sort-by-required') - procArgs.append('md') - procArgs.append('./{dir}'.format(dir=dir)) - procArgs.append('>') - procArgs.append( - './{dir}/{dest}'.format( - dir=dir, dest=cast_to(bool, parsed_cli_args.dest) + procArgs.extend( + ( + 'md', + f'./{dir}', + '>', + './{dir}/{dest}'.format( + dir=dir, + dest=cast_to('bool', parsed_cli_args.dest), + ), ), ) subprocess.check_call(' '.join(procArgs), shell=True) diff --git a/tests/pytest/_cli_test.py b/tests/pytest/_cli_test.py index b8176b8e7..a265102c4 100644 --- a/tests/pytest/_cli_test.py +++ b/tests/pytest/_cli_test.py @@ -1,13 +1,13 @@ """Tests for the high-level CLI entry point.""" from argparse import ArgumentParser, Namespace -import pytest +import pytest from pre_commit_terraform import _cli_parsing as _cli_parsing_mod from pre_commit_terraform._cli import invoke_cli_app from pre_commit_terraform._errors import ( - PreCommitTerraformExit, PreCommitTerraformBaseError, + PreCommitTerraformExit, PreCommitTerraformRuntimeError, ) from pre_commit_terraform._structs import ReturnCode @@ -67,7 +67,7 @@ def invoke_cli_app(self, parsed_cli_args: Namespace) -> ReturnCodeType: [CustomCmdStub()], ) - assert ReturnCode.ERROR == invoke_cli_app(['sentinel']) + assert invoke_cli_app(['sentinel']) == ReturnCode.ERROR captured_outputs = capsys.readouterr() assert captured_outputs.err == f'{expected_stderr !s}\n' @@ -89,7 +89,7 @@ def populate_argument_parser( return None def invoke_cli_app(self, parsed_cli_args: Namespace) -> ReturnCodeType: - raise PreCommitTerraformExit('sentinel') + raise PreCommitTerraformExit(self.CLI_SUBCOMMAND_NAME) monkeypatch.setattr( _cli_parsing_mod, diff --git a/tests/pytest/terraform_docs_replace_test.py b/tests/pytest/terraform_docs_replace_test.py index 2fe765b91..38cd12c3f 100644 --- a/tests/pytest/terraform_docs_replace_test.py +++ b/tests/pytest/terraform_docs_replace_test.py @@ -3,13 +3,15 @@ from argparse import ArgumentParser, Namespace from subprocess import CalledProcessError -import pytest import pytest_mock +import pytest from pre_commit_terraform._structs import ReturnCode from pre_commit_terraform.terraform_docs_replace import ( invoke_cli_app, populate_argument_parser, +) +from pre_commit_terraform.terraform_docs_replace import ( subprocess as replace_docs_subprocess_mod, ) @@ -82,7 +84,7 @@ def test_control_flow_positive( check_call_mock, ) - assert ReturnCode.OK == invoke_cli_app(parsed_cli_args) + assert invoke_cli_app(parsed_cli_args) == ReturnCode.OK executed_commands = [ cmd for ((cmd,), _shell) in check_call_mock.call_args_list @@ -117,6 +119,6 @@ def test_control_flow_negative( check_call_mock, ) - assert ReturnCode.ERROR == invoke_cli_app(parsed_cli_args) + assert invoke_cli_app(parsed_cli_args) == ReturnCode.ERROR check_call_mock.assert_called_once_with(expected_cmd, shell=True) From 9017b560da6aad77a2edd331aae073aef92f0cf5 Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Thu, 27 Mar 2025 14:45:12 +0200 Subject: [PATCH 212/293] chore: Streamline `cast_to()` into correct type (#864) --- src/pre_commit_terraform/terraform_docs_replace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pre_commit_terraform/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py index f72a556b1..d92e9c6b8 100644 --- a/src/pre_commit_terraform/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -78,7 +78,7 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: '>', './{dir}/{dest}'.format( dir=dir, - dest=cast_to('bool', parsed_cli_args.dest), + dest=cast_to('str', parsed_cli_args.dest), ), ), ) From 8d386881b73b258ab7f7e07439071250a4320c0d Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Thu, 3 Apr 2025 15:55:39 +0300 Subject: [PATCH 213/293] chore(linters): Fix ruff linter issues manually (#866) * RUF043 - Pattern passed to `match=` contains metacharacters but is neither escaped nor raw * ERA001 - Found commented code * DOC201 - `return` is not documented in docstring * A001 - Variable `dir` is shadowing a Python builtin * D103 - Missing docstring in public function * E501 - Line too long (99 > 79) * N806 - Variable `procArgs` in function should be lowercase --- src/pre_commit_terraform/_cli.py | 3 +++ src/pre_commit_terraform/_cli_parsing.py | 6 ++++- .../terraform_docs_replace.py | 25 ++++++++++++------- tests/pytest/_cli_test.py | 3 +-- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/pre_commit_terraform/_cli.py b/src/pre_commit_terraform/_cli.py index 70f7a70a3..2ad7c5e31 100644 --- a/src/pre_commit_terraform/_cli.py +++ b/src/pre_commit_terraform/_cli.py @@ -18,6 +18,9 @@ def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: Includes initializing parsers of all the sub-apps and choosing what to execute. + + Returns: + ReturnCodeType: The return code of the app. """ root_cli_parser = initialize_argument_parser() parsed_cli_args = root_cli_parser.parse_args(cli_args) diff --git a/src/pre_commit_terraform/_cli_parsing.py b/src/pre_commit_terraform/_cli_parsing.py index a51a6fc2b..4bdebf6d0 100644 --- a/src/pre_commit_terraform/_cli_parsing.py +++ b/src/pre_commit_terraform/_cli_parsing.py @@ -32,7 +32,11 @@ def attach_subcommand_parsers_to(root_cli_parser: ArgumentParser, /) -> None: def initialize_argument_parser() -> ArgumentParser: - """Return the root argument parser with sub-commands.""" + """Return the root argument parser with sub-commands. + + Returns: + ArgumentParser: The root parser with sub-commands attached. + """ root_cli_parser = ArgumentParser(prog=f'python -m {__package__ !s}') attach_subcommand_parsers_to(root_cli_parser) return root_cli_parser diff --git a/src/pre_commit_terraform/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py index d92e9c6b8..d273d6dac 100644 --- a/src/pre_commit_terraform/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -12,6 +12,7 @@ def populate_argument_parser(subcommand_parser: ArgumentParser) -> None: + """Populate the parser for the subcommand.""" subcommand_parser.description = ( 'Run terraform-docs on a set of files. Follows the standard ' 'convention of pulling the documentation from main.tf in order to ' @@ -47,13 +48,19 @@ def populate_argument_parser(subcommand_parser: ArgumentParser) -> None: def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: + """Run the entry-point of the CLI app. + + Returns: + ReturnCodeType: The return code of the app. + """ warnings.warn( '`terraform_docs_replace` hook is DEPRECATED.' 'For migration instructions see ' 'https://github.com/antonbabenko/pre-commit-terraform/issues/248' '#issuecomment-1290829226', category=UserWarning, - stacklevel=1, # It's should be 2, but tests are failing w/ values >1. As it's deprecated hook, it's safe to leave it as is w/o fixing it later. + stacklevel=1, # It's should be 2, but tests are failing w/ values >1. + # As it's deprecated hook, it's safe to leave it as is w/o fixing it. ) dirs: list[str] = [] @@ -65,24 +72,24 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: retval = ReturnCode.OK - for dir in dirs: + for directory in dirs: try: - procArgs = [] - procArgs.append('terraform-docs') + proc_args = [] + proc_args.append('terraform-docs') if cast_to('bool', parsed_cli_args.sort): - procArgs.append('--sort-by-required') - procArgs.extend( + proc_args.append('--sort-by-required') + proc_args.extend( ( 'md', - f'./{dir}', + f'./{directory}', '>', './{dir}/{dest}'.format( - dir=dir, + dir=directory, dest=cast_to('str', parsed_cli_args.dest), ), ), ) - subprocess.check_call(' '.join(procArgs), shell=True) + subprocess.check_call(' '.join(proc_args), shell=True) except subprocess.CalledProcessError as e: print(e) retval = ReturnCode.ERROR diff --git a/tests/pytest/_cli_test.py b/tests/pytest/_cli_test.py index a265102c4..262df81d7 100644 --- a/tests/pytest/_cli_test.py +++ b/tests/pytest/_cli_test.py @@ -23,7 +23,6 @@ @pytest.mark.parametrize( ('raised_error', 'expected_stderr'), ( - # pytest.param(PreCommitTerraformExit('sentinel'), 'App exiting: sentinel', id='app-exit'), pytest.param( PreCommitTerraformRuntimeError('sentinel'), 'App execution took an unexpected turn: sentinel. Exiting...', @@ -97,7 +96,7 @@ def invoke_cli_app(self, parsed_cli_args: Namespace) -> ReturnCodeType: [CustomCmdStub()], ) - with pytest.raises(PreCommitTerraformExit, match='^sentinel$'): + with pytest.raises(PreCommitTerraformExit, match=r'^sentinel$'): invoke_cli_app(['sentinel']) captured_outputs = capsys.readouterr() From e41252b0d1a25565a38a05e7fd6b02035260f277 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 4 Apr 2025 00:43:56 +0300 Subject: [PATCH 214/293] chore(liners): Suppress ruff rules which shouldn't be fixed (#869) --- src/pre_commit_terraform/_cli.py | 11 ++++++----- src/pre_commit_terraform/_errors.py | 5 ++++- .../terraform_docs_replace.py | 15 +++++++++++---- tests/pytest/terraform_docs_replace_test.py | 5 +++-- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/pre_commit_terraform/_cli.py b/src/pre_commit_terraform/_cli.py index 2ad7c5e31..934c907d2 100644 --- a/src/pre_commit_terraform/_cli.py +++ b/src/pre_commit_terraform/_cli.py @@ -25,7 +25,7 @@ def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: root_cli_parser = initialize_argument_parser() parsed_cli_args = root_cli_parser.parse_args(cli_args) invoke_cli_app = cast_to( - # FIXME: attempt typing per https://stackoverflow.com/a/75666611/595220 + # FIXME: attempt typing per https://stackoverflow.com/a/75666611/595220 # noqa: TD001, TD002, TD003, FIX001, E501 All these suppressions caused by "FIXME" comment 'CLIAppEntryPointCallableType', parsed_cli_args.invoke_cli_app, ) @@ -33,23 +33,24 @@ def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: try: return invoke_cli_app(parsed_cli_args) except PreCommitTerraformExit as exit_err: - print(f'App exiting: {exit_err !s}', file=sys.stderr) + # T201 - FIXME here and below - we will replace 'print' with logging later + print(f'App exiting: {exit_err !s}', file=sys.stderr) # noqa: T201 raise except PreCommitTerraformRuntimeError as unhandled_exc: - print( + print( # noqa: T201 f'App execution took an unexpected turn: {unhandled_exc !s}. ' 'Exiting...', file=sys.stderr, ) return ReturnCode.ERROR except PreCommitTerraformBaseError as unhandled_exc: - print( + print( # noqa: T201 f'A surprising exception happened: {unhandled_exc !s}. Exiting...', file=sys.stderr, ) return ReturnCode.ERROR except KeyboardInterrupt as ctrl_c_exc: - print( + print( # noqa: T201 f'User-initiated interrupt: {ctrl_c_exc !s}. Exiting...', file=sys.stderr, ) diff --git a/src/pre_commit_terraform/_errors.py b/src/pre_commit_terraform/_errors.py index 7bdb738da..6d0019808 100644 --- a/src/pre_commit_terraform/_errors.py +++ b/src/pre_commit_terraform/_errors.py @@ -12,5 +12,8 @@ class PreCommitTerraformRuntimeError( """An exception representing a runtime error condition.""" -class PreCommitTerraformExit(PreCommitTerraformBaseError, SystemExit): +# N818 - The name mimics the built-in SystemExit and is meant to have exactly +# the same semantics. For this reason, it shouldn't have Error in the name to +# maintain resemblance. +class PreCommitTerraformExit(PreCommitTerraformBaseError, SystemExit): # noqa: N818 """An exception for terminating execution from deep app layers.""" diff --git a/src/pre_commit_terraform/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py index d273d6dac..ae77f1efc 100644 --- a/src/pre_commit_terraform/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -68,7 +68,9 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: if os.path.realpath(filename) not in dirs and ( filename.endswith(('.tf', '.tfvars')) ): - dirs.append(os.path.dirname(filename)) + # PTH120 - It should use 'pathlib', but this hook is deprecated and + # we don't want to spent time on testing fixes for it + dirs.append(os.path.dirname(filename)) # noqa: PTH120 retval = ReturnCode.OK @@ -89,8 +91,13 @@ def invoke_cli_app(parsed_cli_args: Namespace) -> ReturnCodeType: ), ), ) - subprocess.check_call(' '.join(proc_args), shell=True) - except subprocess.CalledProcessError as e: - print(e) + # S602 - 'shell=True' is insecure, but this hook is deprecated and + # we don't want to spent time on testing fixes for it + subprocess.check_call(' '.join(proc_args), shell=True) # noqa: S602 + # PERF203 - try-except shouldn't be in a loop, but it's deprecated + # hook, so leave as is + except subprocess.CalledProcessError as e: # noqa: PERF203 + # T201 - Leave print statement as is, as this is deprecated hook + print(e) # noqa: T201 retval = ReturnCode.ERROR return retval diff --git a/tests/pytest/terraform_docs_replace_test.py b/tests/pytest/terraform_docs_replace_test.py index 38cd12c3f..c4190c3c0 100644 --- a/tests/pytest/terraform_docs_replace_test.py +++ b/tests/pytest/terraform_docs_replace_test.py @@ -120,5 +120,6 @@ def test_control_flow_negative( ) assert invoke_cli_app(parsed_cli_args) == ReturnCode.ERROR - - check_call_mock.assert_called_once_with(expected_cmd, shell=True) + # S604 - 'shell=True' is insecure, but this hook is deprecated and we don't + # want to spent time on testing fixes for it + check_call_mock.assert_called_once_with(expected_cmd, shell=True) # noqa: S604 From f7717cd978022f03fe8bd0c87fac655690cc34e1 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Sat, 5 Apr 2025 15:43:44 +0300 Subject: [PATCH 215/293] chore(linters): Introduce ruff and fix issues (#831) --- .pre-commit-config.yaml | 8 ++++ ruff.toml | 40 +++++++++++++++++++ src/pre_commit_terraform/_cli.py | 6 ++- .../terraform_docs_replace.py | 12 +++++- 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 ruff.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d59c8d987..93278fd92 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -98,6 +98,14 @@ repos: - id: shellcheck # Python +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + - id: ruff + args: + - --fix + - id: ruff-format + - repo: https://github.com/pre-commit/mirrors-mypy.git rev: v1.15.0 hooks: diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..8bb19749a --- /dev/null +++ b/ruff.toml @@ -0,0 +1,40 @@ +# Assume Python 3.9 +target-version = "py39" + +line-length = 79 # To decrease PR diff size + +namespace-packages = ["src/pre_commit_terraform/", "tests/pytest/"] + +[format] +quote-style = "single" + +[lint.flake8-quotes] +inline-quotes = "single" + +[lint.pydocstyle] +convention = "pep257" + +[lint] +select = ["ALL"] +preview = true +ignore = [ + "CPY001", # Skip copyright notice requirement at top of files +] + +[lint.isort] +# force-single-line = true # To decrease PR diff size +lines-after-imports = 2 + +[lint.flake8-pytest-style] +parametrize-values-type = "tuple" + +[lint.per-file-ignores] +# Exceptions for test files +"tests/**.py" = [ + "S101", # Allow use of `assert` in test files + "PLC2701", # Allow importing internal files needed for testing + "PLR6301", # Allow 'self' parameter in method definitions (required for test stubs) + "ARG002", # Allow unused arguments in instance methods (required for test stubs) + "S404", # Allow importing 'subprocess' module to testing call external tools needed by these hooks + +] diff --git a/src/pre_commit_terraform/_cli.py b/src/pre_commit_terraform/_cli.py index 934c907d2..976429790 100644 --- a/src/pre_commit_terraform/_cli.py +++ b/src/pre_commit_terraform/_cli.py @@ -21,6 +21,9 @@ def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: Returns: ReturnCodeType: The return code of the app. + + Raises: + PreCommitTerraformExit: If the app is exiting with error. """ root_cli_parser = initialize_argument_parser() parsed_cli_args = root_cli_parser.parse_args(cli_args) @@ -33,7 +36,8 @@ def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: try: return invoke_cli_app(parsed_cli_args) except PreCommitTerraformExit as exit_err: - # T201 - FIXME here and below - we will replace 'print' with logging later + # T201 - FIXME here and below - we will replace 'print' with + # logging later print(f'App exiting: {exit_err !s}', file=sys.stderr) # noqa: T201 raise except PreCommitTerraformRuntimeError as unhandled_exc: diff --git a/src/pre_commit_terraform/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py index ae77f1efc..07be3593b 100644 --- a/src/pre_commit_terraform/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -1,5 +1,15 @@ +"""Terraform Docs Replace Hook. + +This hook is deprecated and will be removed in the future. +Please, use 'terraform_docs' hook instead. +""" + import os -import subprocess + +# S404 - Allow importing 'subprocess' module to call external tools +# needed by these hooks. FIXME - should be moved to separate module +# when more hooks will be introduced +import subprocess # noqa: S404 import warnings from argparse import ArgumentParser, Namespace from typing import cast as cast_to From da2e9a874ac61f94fe0a05e9d952ffb3c7c7639c Mon Sep 17 00:00:00 2001 From: "George L. Yermulnik" Date: Mon, 7 Apr 2025 00:34:43 +0300 Subject: [PATCH 216/293] fix(WSL): Fix parallelism support for WSL systems with enabled systemd (#872) * chore(_common.sh): Add `WSLInterop-late` file check in `get_cpu_num` * Fix typo * pre-commit fixes * Streamline condition wrapping * make WSLInterop local var readonly Co-authored-by: Maksym Vlasov * Also check `/run/WSL` file does not exist --------- Co-authored-by: pre-commit Co-authored-by: Maksym Vlasov --- hooks/_common.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hooks/_common.sh b/hooks/_common.sh index fbd4bc2b4..defc249a8 100644 --- a/hooks/_common.sh +++ b/hooks/_common.sh @@ -206,8 +206,10 @@ function common::get_cpu_num { local cpu_quota cpu_period cpu_num + local -r wslinterop_path="/proc/sys/fs/binfmt_misc/WSLInterop" + if [[ -f /sys/fs/cgroup/cpu/cpu.cfs_quota_us && - ! -f /proc/sys/fs/binfmt_misc/WSLInterop ]]; then # WSL have cfs_quota_us, but WSL should be checked as usual Linux host + (! -f "${wslinterop_path}" && ! -f "${wslinterop_path}-late" && ! -f "/run/WSL") ]]; then # WSL has cfs_quota_us, but WSL should be checked as usual Linux host # Inside K8s pod or DinD in K8s cpu_quota=$(< /sys/fs/cgroup/cpu/cpu.cfs_quota_us) cpu_period=$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us 2> /dev/null || echo "$cpu_quota") From 93bd55541bf0c967d7a48dc26567685008112bc7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 6 Apr 2025 21:35:10 +0000 Subject: [PATCH 217/293] chore(release): version 1.98.1 [skip ci] ## [1.98.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.98.0...v1.98.1) (2025-04-06) ### Bug Fixes * **WSL:** Fix parallelism support for WSL systems with enabled systemd ([#872](https://github.com/antonbabenko/pre-commit-terraform/issues/872)) ([da2e9a8](https://github.com/antonbabenko/pre-commit-terraform/commit/da2e9a874ac61f94fe0a05e9d952ffb3c7c7639c)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b94e405ef..9102376ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.98.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.98.0...v1.98.1) (2025-04-06) + + +### Bug Fixes + +* **WSL:** Fix parallelism support for WSL systems with enabled systemd ([#872](https://github.com/antonbabenko/pre-commit-terraform/issues/872)) ([da2e9a8](https://github.com/antonbabenko/pre-commit-terraform/commit/da2e9a874ac61f94fe0a05e9d952ffb3c7c7639c)) + # [1.98.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.97.4...v1.98.0) (2025-03-25) From d4a9e90a9eba098e114a5b4e260fff1687708a23 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 20:04:35 +0300 Subject: [PATCH 218/293] chore(pre-commit.ci): Apply pre-commit autoupdate (#873) --- .pre-commit-config.yaml | 10 +++++----- src/pre_commit_terraform/_cli.py | 10 +++++----- src/pre_commit_terraform/_cli_parsing.py | 2 +- tests/pytest/_cli_test.py | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93278fd92..740b1adcd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,13 +43,13 @@ repos: # Detect hardcoded secrets - repo: https://github.com/gitleaks/gitleaks - rev: v8.24.0 + rev: v8.24.2 hooks: - id: gitleaks # Dockerfile - repo: https://github.com/hadolint/hadolint - rev: v2.12.1-beta + rev: v2.13.1-beta hooks: - id: hadolint @@ -66,7 +66,7 @@ repos: - --implicit_start - repo: https://github.com/adrienverge/yamllint.git - rev: v1.36.2 + rev: v1.37.0 hooks: - id: yamllint types: @@ -77,7 +77,7 @@ repos: # JSON5 - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.1.0 + rev: v4.0.0-alpha.8 hooks: - id: prettier # https://prettier.io/docs/en/options.html#parser @@ -99,7 +99,7 @@ repos: # Python - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.4 + rev: v0.11.4 hooks: - id: ruff args: diff --git a/src/pre_commit_terraform/_cli.py b/src/pre_commit_terraform/_cli.py index 976429790..abd95df75 100644 --- a/src/pre_commit_terraform/_cli.py +++ b/src/pre_commit_terraform/_cli.py @@ -28,7 +28,7 @@ def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: root_cli_parser = initialize_argument_parser() parsed_cli_args = root_cli_parser.parse_args(cli_args) invoke_cli_app = cast_to( - # FIXME: attempt typing per https://stackoverflow.com/a/75666611/595220 # noqa: TD001, TD002, TD003, FIX001, E501 All these suppressions caused by "FIXME" comment + # FIXME: attempt typing per https://stackoverflow.com/a/75666611/595220 # noqa: TD001, TD002, FIX001, E501 All these suppressions caused by "FIXME" comment 'CLIAppEntryPointCallableType', parsed_cli_args.invoke_cli_app, ) @@ -38,24 +38,24 @@ def invoke_cli_app(cli_args: list[str]) -> ReturnCodeType: except PreCommitTerraformExit as exit_err: # T201 - FIXME here and below - we will replace 'print' with # logging later - print(f'App exiting: {exit_err !s}', file=sys.stderr) # noqa: T201 + print(f'App exiting: {exit_err!s}', file=sys.stderr) # noqa: T201 raise except PreCommitTerraformRuntimeError as unhandled_exc: print( # noqa: T201 - f'App execution took an unexpected turn: {unhandled_exc !s}. ' + f'App execution took an unexpected turn: {unhandled_exc!s}. ' 'Exiting...', file=sys.stderr, ) return ReturnCode.ERROR except PreCommitTerraformBaseError as unhandled_exc: print( # noqa: T201 - f'A surprising exception happened: {unhandled_exc !s}. Exiting...', + f'A surprising exception happened: {unhandled_exc!s}. Exiting...', file=sys.stderr, ) return ReturnCode.ERROR except KeyboardInterrupt as ctrl_c_exc: print( # noqa: T201 - f'User-initiated interrupt: {ctrl_c_exc !s}. Exiting...', + f'User-initiated interrupt: {ctrl_c_exc!s}. Exiting...', file=sys.stderr, ) return ReturnCode.ERROR diff --git a/src/pre_commit_terraform/_cli_parsing.py b/src/pre_commit_terraform/_cli_parsing.py index 4bdebf6d0..fac4da15c 100644 --- a/src/pre_commit_terraform/_cli_parsing.py +++ b/src/pre_commit_terraform/_cli_parsing.py @@ -37,7 +37,7 @@ def initialize_argument_parser() -> ArgumentParser: Returns: ArgumentParser: The root parser with sub-commands attached. """ - root_cli_parser = ArgumentParser(prog=f'python -m {__package__ !s}') + root_cli_parser = ArgumentParser(prog=f'python -m {__package__!s}') attach_subcommand_parsers_to(root_cli_parser) return root_cli_parser diff --git a/tests/pytest/_cli_test.py b/tests/pytest/_cli_test.py index 262df81d7..fd4b1fef5 100644 --- a/tests/pytest/_cli_test.py +++ b/tests/pytest/_cli_test.py @@ -69,7 +69,7 @@ def invoke_cli_app(self, parsed_cli_args: Namespace) -> ReturnCodeType: assert invoke_cli_app(['sentinel']) == ReturnCode.ERROR captured_outputs = capsys.readouterr() - assert captured_outputs.err == f'{expected_stderr !s}\n' + assert captured_outputs.err == f'{expected_stderr!s}\n' def test_app_exit( From 9b02536f5a781e87c53a5d17e1d21a4f00cafdf5 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 11 Apr 2025 13:45:31 +0300 Subject: [PATCH 219/293] chore: Update essential configurations (#743) Let VS Code know about Python code location --- .vscode/settings.json | 4 ++++ pyproject.toml | 5 ++--- src/pre_commit_terraform/terraform_docs_replace.py | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c1dc51901..2d002b5a6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,4 +7,8 @@ "code-block-style": false }, "markdown.validate.enabled": true, + "python.analysis.extraPaths": [ + "./src", + "./tests/pytest" + ], } diff --git a/pyproject.toml b/pyproject.toml index c8ae73136..afef00e9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,18 +27,17 @@ upstreaming = [ name = 'pre-commit-terraform' classifiers = [ 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', - 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ] -description = 'Pre-commit hooks for terraform_docs_replace' +description = 'Pre-commit hooks for Terraform, OpenTofu, Terragrunt and related tools' dependencies = [] dynamic = [ 'urls', diff --git a/src/pre_commit_terraform/terraform_docs_replace.py b/src/pre_commit_terraform/terraform_docs_replace.py index 07be3593b..43f496bb1 100644 --- a/src/pre_commit_terraform/terraform_docs_replace.py +++ b/src/pre_commit_terraform/terraform_docs_replace.py @@ -12,13 +12,14 @@ import subprocess # noqa: S404 import warnings from argparse import ArgumentParser, Namespace +from typing import Final from typing import cast as cast_to from ._structs import ReturnCode from ._types import ReturnCodeType -CLI_SUBCOMMAND_NAME: str = 'replace-docs' +CLI_SUBCOMMAND_NAME: Final[str] = 'replace-docs' def populate_argument_parser(subcommand_parser: ArgumentParser) -> None: From fe1f62f3aecadbc22f8ab5e1f8cb02c2821cf5c2 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 14 Apr 2025 17:15:47 +0300 Subject: [PATCH 220/293] feat: Add support for running hooks on `.tofu` files by default (#875) --- .pre-commit-hooks.yaml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 0b2690351..50607541e 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: terraform_fmt @@ -13,7 +13,7 @@ Rewrites all Terraform configuration files to a canonical format. entry: hooks/terraform_fmt.sh language: script - files: (\.tf|\.tfvars)$ + files: \.(tf|tofu|tfvars)$ exclude: \.terraform/.*$ - id: terraform_docs @@ -24,7 +24,7 @@ require_serial: true entry: hooks/terraform_docs.sh language: script - files: (\.tf|\.terraform\.lock\.hcl)$ + files: \.(tf|tofu|terraform\.lock\.hcl)$ exclude: \.terraform/.*$ - id: terraform_docs_without_aggregate_type_defaults @@ -35,7 +35,7 @@ require_serial: true entry: hooks/terraform_docs.sh language: script - files: (\.tf)$ + files: \.(tf|tofu)$ exclude: \.terraform/.*$ - id: terraform_docs_replace @@ -44,7 +44,7 @@ require_serial: true entry: python -Im pre_commit_terraform replace-docs language: python - files: (\.tf)$ + files: \.(tf|tofu)$ exclude: \.terraform/.*$ - id: terraform_validate @@ -53,7 +53,7 @@ require_serial: true entry: hooks/terraform_validate.sh language: script - files: \.(tf(vars)?|terraform\.lock\.hcl)$ + files: \.(tf|tofu|tfvars|terraform\.lock\.hcl)$ exclude: \.terraform/.*$ - id: terraform_providers_lock @@ -71,7 +71,7 @@ require_serial: true entry: hooks/terraform_tflint.sh language: script - files: (\.tf|\.tfvars)$ + files: \.(tf|tofu|tfvars)$ exclude: \.terraform/.*$ - id: terragrunt_fmt @@ -114,7 +114,7 @@ Static analysis of Terraform templates to spot potential security issues. require_serial: true entry: hooks/terraform_tfsec.sh - files: \.tf(vars)?$ + files: \.(tf|tofu|tfvars)$ language: script - id: terraform_trivy @@ -123,7 +123,7 @@ Static analysis of Terraform templates to spot potential security issues. require_serial: true entry: hooks/terraform_trivy.sh - files: \.tf(vars)?$ + files: \.(tf|tofu|tfvars)$ language: script - id: checkov @@ -133,7 +133,7 @@ language: python pass_filenames: false always_run: false - files: \.tf$ + files: \.(tf|tofu)$ exclude: \.terraform/.*$ require_serial: true @@ -143,7 +143,7 @@ entry: hooks/terraform_checkov.sh language: script always_run: false - files: \.tf$ + files: \.(tf|tofu)$ exclude: \.terraform/.*$ require_serial: true @@ -155,7 +155,7 @@ pass_filenames: false always_run: false require_serial: true - files: \.tf$ + files: \.(tf|tofu)$ exclude: \.terraform/.*$ - id: terrascan @@ -163,7 +163,7 @@ description: Runs terrascan on Terraform templates. language: script entry: hooks/terrascan.sh - files: \.tf$ + files: \.(tf|tofu)$ exclude: \.terraform/.*$ require_serial: true @@ -174,5 +174,5 @@ entry: hooks/tfupdate.sh args: - --args=terraform - files: \.tf$ + files: \.(tf|tofu)$ require_serial: true From 521ada54e82bdee23c6a1f42a83cd22898f1efd4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 14 Apr 2025 14:16:14 +0000 Subject: [PATCH 221/293] chore(release): version 1.99.0 [skip ci] # [1.99.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.98.1...v1.99.0) (2025-04-14) ### Features * Add support for running hooks on `.tofu` files by default ([#875](https://github.com/antonbabenko/pre-commit-terraform/issues/875)) ([fe1f62f](https://github.com/antonbabenko/pre-commit-terraform/commit/fe1f62f3aecadbc22f8ab5e1f8cb02c2821cf5c2)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9102376ca..e51bd263c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.99.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.98.1...v1.99.0) (2025-04-14) + + +### Features + +* Add support for running hooks on `.tofu` files by default ([#875](https://github.com/antonbabenko/pre-commit-terraform/issues/875)) ([fe1f62f](https://github.com/antonbabenko/pre-commit-terraform/commit/fe1f62f3aecadbc22f8ab5e1f8cb02c2821cf5c2)) + ## [1.98.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.98.0...v1.98.1) (2025-04-06) From b42487404da33c2cef325ec1ac2e53929adffd9f Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 2 May 2025 16:01:31 +0300 Subject: [PATCH 222/293] chore(ci): Fix `pre-commit/action`, as v2.x not work anymore (#878) --- .github/workflows/pre-commit.yaml | 57 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 6e205ea9a..ef3649347 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -56,39 +56,38 @@ jobs: - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.13' + + # Needed for pre-commit fix push to succeed + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} + # Needed to trigger pre-commit workflow on autofix commit. Guide: + # https://web.archive.org/web/20210731173012/https://github.community/t/required-check-is-expected-after-automated-push/187545/ + ssh-key: ${{ secrets.GHA_AUTOFIX_COMMIT_KEY }} + - name: Execute pre-commit - uses: pre-commit/action@9b88afc9cd57fd75b655d5c71bd38146d07135fe # v2.0.3 + uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 env: - SKIP: no-commit-to-branch,hadolint + SKIP: no-commit-to-branch with: - token: ${{ secrets.GITHUB_TOKEN }} extra_args: >- --color=always --show-diff-on-failure - --files ${{ steps.file_changes.outputs.files }} - # Run only skipped checks - - name: Execute pre-commit check that have no auto-fixes - if: always() - uses: pre-commit/action@9b88afc9cd57fd75b655d5c71bd38146d07135fe # v2.0.3 - 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, + --files ${{ steps.file_changes.outputs.files}} + + # Needed to trigger pre-commit workflow on autofix commit + - name: Push fixes + if: failure() + uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4 with: - extra_args: >- - --color=always - --show-diff-on-failure - --files ${{ steps.file_changes.outputs.files }} + # Determines the way the action fills missing author name and email. + # Three options are available: + # - github_actor -> UserName + # - user_info -> Your Display Name + # - github_actions -> github-actions + # Default: github_actor + default_author: github_actor + # The message for the commit. + # Default: 'Commit from GitHub Actions (name of the workflow)' + message: '[pre-commit] Autofix violations' From dd4bb9d3d8faad4a795ac5c12cc08a9afb8c5129 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 2 May 2025 16:18:48 +0300 Subject: [PATCH 223/293] chore: Remove duplication checkout (#879) --- .github/workflows/pre-commit.yaml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index ef3649347..da3d7f8f8 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -47,25 +47,19 @@ jobs: )" > hadolint && chmod +x hadolint && sudo mv hadolint /usr/bin/ - # Need to success pre-commit fix push + # Needed for pre-commit fix push to succeed - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} + # Needed to trigger pre-commit workflow on autofix commit. Guide: + # https://web.archive.org/web/20210731173012/https://github.community/t/required-check-is-expected-after-automated-push/187545/ + ssh-key: ${{ secrets.GHA_AUTOFIX_COMMIT_KEY }} # Skip terraform_tflint which interferes to commit pre-commit auto-fixes - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.13' - # Needed for pre-commit fix push to succeed - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.ref }} - # Needed to trigger pre-commit workflow on autofix commit. Guide: - # https://web.archive.org/web/20210731173012/https://github.community/t/required-check-is-expected-after-automated-push/187545/ - ssh-key: ${{ secrets.GHA_AUTOFIX_COMMIT_KEY }} - - name: Execute pre-commit uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 env: From 8931c6516772cdb9ef2d79f47252c4140dfb8e2b Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 7 May 2025 18:12:13 +0300 Subject: [PATCH 224/293] docs(tflint): Replace repecated value in example (#880) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 34cf50ba3..99332f00c 100644 --- a/README.md +++ b/README.md @@ -347,10 +347,10 @@ Config example: - id: terraform_tflint args: - --args=--config=${CONFIG_NAME}.${CONFIG_EXT} - - --args=--module + - --args=--call-module-type="all" ``` -If for config above set up `export CONFIG_NAME=.tflint; export CONFIG_EXT=hcl` before `pre-commit run`, args will be expanded to `--config=.tflint.hcl --module`. +If for config above set up `export CONFIG_NAME=.tflint; export CONFIG_EXT=hcl` before `pre-commit run`, args will be expanded to `--config=.tflint.hcl --call-module-type="all"`. ### All hooks: Set env vars inside hook at runtime From 6cc5fd7196742c1e921cb2445bc5c502935a6af1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 22:26:43 +0000 Subject: [PATCH 225/293] chore(deps): Update actions/cache action to v4.2.3 (#881) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 4 ++-- .github/workflows/reusable-tox.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 77f2e64f6..0a68168ca 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -161,7 +161,7 @@ jobs: echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" shell: bash - name: Set up pip cache - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- @@ -307,7 +307,7 @@ jobs: run: >- echo "dir=$(python -m pip cache dir)" >> "${GITHUB_OUTPUT}" - name: Set up pip cache - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index f779df1a9..0d980d35a 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -181,7 +181,7 @@ jobs: - name: Cache pre-commit.com virtualenvs if: inputs.toxenv == 'pre-commit' - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: ~/.cache/pre-commit key: >- @@ -239,7 +239,7 @@ jobs: shell: bash - name: Set up pip cache if: fromJSON(steps.py-abi.outputs.is-stable-abi) - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: >- From f7451cdc2cbcae3583d42231d19b85aea509b6a6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 22:27:23 +0000 Subject: [PATCH 226/293] chore(deps): Update github/codeql-action action to v3.28.17 (#884) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 41e8d1c56..89dc03bed 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -46,7 +46,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 + uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in @@ -58,7 +58,7 @@ jobs: # If this step fails, then you should remove it and run the build # manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 + uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 # ℹ️ Command-line programs to run using the OS shell. # yamllint disable-line rule:line-length @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 + uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: category: /language:${{matrix.language}} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index fb8d42a23..7c4ae12a0 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -74,6 +74,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: results.sarif From 0a3e60486498c2fe3d5750fa234b4f2fe4931ef9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 22:28:18 +0000 Subject: [PATCH 227/293] chore(deps): Update tj-actions/changed-files action to v46.0.5 (#887) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-image-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 62c3a0443..edc36afc5 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -38,7 +38,7 @@ jobs: - name: Get changed Docker related files id: changed-files-specific - uses: tj-actions/changed-files@2f7c5bfce28377bc069a65ba478de0a74aa0ca32 # v46.0.1 + uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5 with: files: | .dockerignore From a1663f22010052f423f402ff4818a4b40e6222a1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 22:28:49 +0000 Subject: [PATCH 228/293] chore(deps): Update actions/download-artifact action to v4.3.0 (#888) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/reusable-tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 0d980d35a..36c162da6 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -266,7 +266,7 @@ jobs: - name: Download all the dists if: >- contains(fromJSON('["metadata-validation", "pytest"]'), inputs.toxenv) - uses: actions/download-artifact@b14cf4c92620c250e1c074ab0a5800e37df86765 # v4.2.0 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: ${{ inputs.dists-artifact-name }} path: dist/ From 817924b480331ebc12ffa78203ecdcdadb668a86 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 22:31:04 +0000 Subject: [PATCH 229/293] chore(deps): Update actions/setup-python action to v5.6.0 (#889) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 4 ++-- .github/workflows/pre-commit.yaml | 2 +- .github/workflows/reusable-tox.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 0a68168ca..3861e798a 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -105,7 +105,7 @@ jobs: steps: - name: Switch to using Python 3.13 by default - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: 3.13 - name: Check out src from Git @@ -274,7 +274,7 @@ jobs: steps: - name: Switch to using Python 3.13 - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: 3.13 diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index da3d7f8f8..9d6e1c1f8 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -56,7 +56,7 @@ jobs: # https://web.archive.org/web/20210731173012/https://github.community/t/required-check-is-expected-after-automated-push/187545/ ssh-key: ${{ secrets.GHA_AUTOFIX_COMMIT_KEY }} # Skip terraform_tflint which interferes to commit pre-commit auto-fixes - - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.13' diff --git a/.github/workflows/reusable-tox.yml b/.github/workflows/reusable-tox.yml index 36c162da6..29e65ba47 100644 --- a/.github/workflows/reusable-tox.yml +++ b/.github/workflows/reusable-tox.yml @@ -158,7 +158,7 @@ jobs: Switch to using Python v${{ inputs.python-version }} by default id: python-install - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ inputs.python-version }} From 7358a93f9da21b52f9071fb0e10dec10d9ec7772 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 01:41:34 +0300 Subject: [PATCH 230/293] chore(deps): Update pre-commit hook adrienverge/yamllint to v1.37.1 (#885) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 740b1adcd..f8dc33620 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -66,7 +66,7 @@ repos: - --implicit_start - repo: https://github.com/adrienverge/yamllint.git - rev: v1.37.0 + rev: v1.37.1 hooks: - id: yamllint types: From 3c55d325b2975a37685dbde5809158f5ce4726f6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 01:47:35 +0300 Subject: [PATCH 231/293] chore(deps): Update pre-commit hook gitleaks/gitleaks to v8.26.0 (#891) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f8dc33620..b153330ee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: # Detect hardcoded secrets - repo: https://github.com/gitleaks/gitleaks - rev: v8.24.2 + rev: v8.26.0 hooks: - id: gitleaks From 71c5498673355c1ac7a8155274563a4bc374c6f4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 22:48:22 +0000 Subject: [PATCH 232/293] chore(deps): Update actions/upload-artifact action to v4.6.2 (#882) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 2 +- .github/workflows/scorecards.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 3861e798a..966d76211 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -372,7 +372,7 @@ jobs: >> "${GITHUB_OUTPUT}" working-directory: dist - name: Store the distribution packages - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: >- ${{ needs.pre-setup.outputs.dists-artifact-name }} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 7c4ae12a0..8abeecd69 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -66,7 +66,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable # uploads of run results in SARIF format to the repository Actions tab. - name: Upload artifact - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: SARIF file path: results.sarif From 86557c2e68f60fc39edada07848efa5aa30e0760 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Fri, 16 May 2025 20:29:44 +0300 Subject: [PATCH 233/293] chore: Add Open Source Security Foundation best practices badge (#785) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 99332f00c..782e16ebf 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ [![Latest Github tag]](https://github.com/antonbabenko/pre-commit-terraform/releases) ![Maintenance status](https://img.shields.io/maintenance/yes/2025.svg) -[![Codetriage - Help Contribute to Open Source Badge]](https://www.codetriage.com/antonbabenko/pre-commit-terraform) [![GHA Tests CI/CD Badge]](https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml) [![Codecov pytest Badge]](https://app.codecov.io/gh/antonbabenko/pre-commit-terraform?flags[]=pytest) [![OpenSSF Scorecard Badge]](https://scorecard.dev/viewer/?uri=github.com/antonbabenko/pre-commit-terraform) +[![OpenSSF Best Practices Badge]](https://www.bestpractices.dev/projects/9963) +[![Codetriage - Help Contribute to Open Source Badge]](https://www.codetriage.com/antonbabenko/pre-commit-terraform) [![StandWithUkraine Banner]](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) @@ -29,6 +30,7 @@ and [contributing notes](/.github/CONTRIBUTING.md). [GHA Tests CI/CD Badge]: https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml/badge.svg?branch=master [Codecov Pytest Badge]: https://codecov.io/gh/antonbabenko/pre-commit-terraform/branch/master/graph/badge.svg?flag=pytest [OpenSSF Scorecard Badge]: https://api.scorecard.dev/projects/github.com/antonbabenko/pre-commit-terraform/badge +[OpenSSF Best Practices Badge]: https://www.bestpractices.dev/projects/9963/badge [StandWithUkraine Banner]: https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg ## Sponsors From 3dd96e2a2883d67675e017a2a624d5f7a5bd4b22 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Mon, 19 May 2025 17:51:46 +0300 Subject: [PATCH 234/293] docs: Add missed step for init'ed repos and resolve confusion that deps should be find out and installed manually (#892) ### Description of your changes * Previously, these docs wrongly assumed that repo was `git init`/`git clone` after setting up of `init.templateDir`, which is not the case for 90%+ of newcomers. * Installation instructions were too far away for list of dependencies, so it were easy to interpret that you need to install all these deps manually, and only then find out that there are already instructions that cover their installation. Rearranging them slightly, fix that issues --------- Co-authored-by: George L. Yermulnik --- README.md | 86 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 782e16ebf..1546f0466 100644 --- a/README.md +++ b/README.md @@ -83,43 +83,6 @@ If you want to support the development of `pre-commit-terraform` and [many other ## How to install ### 1. Install dependencies - -* [`pre-commit`](https://pre-commit.com/#install), - [`terraform`](https://www.terraform.io/downloads.html) or [`opentofu`](https://opentofu.org/docs/intro/install/), - [`git`](https://git-scm.com/downloads), - [BASH `3.2.57` or newer](https://www.gnu.org/software/bash/#download), - Internet connection (on first run), - x86_64 or arm64 compatible operating system, - Some hardware where this OS will run, - Electricity for hardware and internet connection, - Some basic physical laws, - Hope that it all will work. -

-* [`checkov`][checkov repo] required for `terraform_checkov` hook -* [`terraform-docs`][terraform-docs repo] 0.12.0+ required for `terraform_docs` hook -* [`terragrunt`][terragrunt repo] required for `terragrunt_validate` and `terragrunt_valid_inputs` hooks -* [`terrascan`][terrascan repo] required for `terrascan` hook -* [`TFLint`][tflint repo] required for `terraform_tflint` hook -* [`TFSec`][tfsec repo] required for `terraform_tfsec` hook -* [`Trivy`][trivy repo] required for `terraform_trivy` hook -* [`infracost`][infracost repo] required for `infracost_breakdown` hook -* [`jq`][jq repo] required for `terraform_validate` with `--retry-once-with-cleanup` flag, and for `infracost_breakdown` hook -* [`tfupdate`][tfupdate repo] required for `tfupdate` hook -* [`hcledit`][hcledit repo] required for `terraform_wrapper_module_for_each` hook - - -#### 1.1 Custom Terraform binaries and OpenTofu support - -It is possible to set custom path to `terraform` binary. -This makes it possible to use [OpenTofu](https://opentofu.org) binary `tofu` instead of `terraform`. - -How binary discovery works and how you can redefine it (first matched takes precedence): - -1. Check if per hook configuration `--hook-config=--tf-path=` is set -2. Check if `PCT_TFPATH=` environment variable is set -3. Check if `TERRAGRUNT_TFPATH=` environment variable is set -4. Check if `terraform` binary can be found in the user's $PATH -5. Check if `tofu` binary can be found in the user's $PATH

diff --git a/tools/install/_common.sh b/tools/install/_common.sh index 70297f297..eaaafa0cd 100755 --- a/tools/install/_common.sh +++ b/tools/install/_common.sh @@ -60,11 +60,16 @@ function common::install_from_gh_release { # Download tool local -r RELEASES="https://api.github.com/repos/${GH_ORG}/${TOOL}/releases" + local CURL_OPTS=() + + [[ $GITHUB_TOKEN ]] && CURL_OPTS+=('-H' "Authorization: Bearer $GITHUB_TOKEN") + + local -r CURL_CMD=("curl" "${CURL_OPTS[@]}") if [[ $VERSION == latest ]]; then - curl -L "$(curl -s "${RELEASES}/latest" | grep -o -E -i -m 1 "$GH_RELEASE_REGEX_LATEST")" > "$PKG" + "${CURL_CMD[@]}" -L "$("${CURL_CMD[@]}" -s "${RELEASES}/latest" | grep -o -E -i -m 1 "$GH_RELEASE_REGEX_LATEST")" > "$PKG" else - curl -L "$(curl -s "$RELEASES" | grep -o -E -i -m 1 "$GH_RELEASE_REGEX_SPECIFIC_VERSION")" > "$PKG" + "${CURL_CMD[@]}" -L "$("${CURL_CMD[@]}" -s "$RELEASES" | grep -o -E -i -m 1 "$GH_RELEASE_REGEX_SPECIFIC_VERSION")" > "$PKG" fi # Make tool ready to use From cba87736ffe2c479523d9d69919c1215084c7acc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 20 Nov 2025 14:41:04 +0000 Subject: [PATCH 288/293] chore(release): version 1.104.0 [skip ci] # [1.104.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.103.0...v1.104.0) (2025-11-20) ### Features * **docker:** Allow authenticated calls to GitHub API ([#947](https://github.com/antonbabenko/pre-commit-terraform/issues/947)) ([6deaf20](https://github.com/antonbabenko/pre-commit-terraform/commit/6deaf20ea403ad4d220a0c80330390a5bc53f72f)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 546ad6bb5..61cbb67ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.104.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.103.0...v1.104.0) (2025-11-20) + + +### Features + +* **docker:** Allow authenticated calls to GitHub API ([#947](https://github.com/antonbabenko/pre-commit-terraform/issues/947)) ([6deaf20](https://github.com/antonbabenko/pre-commit-terraform/commit/6deaf20ea403ad4d220a0c80330390a5bc53f72f)) + # [1.103.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.102.0...v1.103.0) (2025-10-17) From 673e0714e6bf5ddd4e1bf2d2b7bfa95c4d89cd22 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Wed, 31 Dec 2025 20:33:28 +0200 Subject: [PATCH 289/293] docs: Happy New Year and svit bez rusni! (#951) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ca672780..0b5bfff64 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Collection of git hooks for Terraform to be used with [pre-commit framework](http://pre-commit.com/) [![Latest Github tag]](https://github.com/antonbabenko/pre-commit-terraform/releases) -![Maintenance status](https://img.shields.io/maintenance/yes/2025.svg) +![Maintenance status](https://img.shields.io/maintenance/yes/2026.svg) [![GHA Tests CI/CD Badge]](https://github.com/antonbabenko/pre-commit-terraform/actions/workflows/ci-cd.yml) [![Codecov pytest Badge]](https://app.codecov.io/gh/antonbabenko/pre-commit-terraform?flags[]=pytest) [![OpenSSF Scorecard Badge]](https://scorecard.dev/viewer/?uri=github.com/antonbabenko/pre-commit-terraform) From 3117fe2efa4aa0f0a4c1e9c0670c4183c7d90d24 Mon Sep 17 00:00:00 2001 From: Arash Hatami Date: Sun, 4 Jan 2026 18:09:25 +0330 Subject: [PATCH 290/293] fix(`terraform_wrapper_module_for_each`): Generate right usage example for S3 (#952) --- hooks/terraform_wrapper_module_for_each.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/terraform_wrapper_module_for_each.sh b/hooks/terraform_wrapper_module_for_each.sh index dc08b791c..24a3a465b 100755 --- a/hooks/terraform_wrapper_module_for_each.sh +++ b/hooks/terraform_wrapper_module_for_each.sh @@ -113,9 +113,9 @@ module "wrapper" { ```hcl terraform { - source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + source = "tfr:///MODULE_REPO_ORG/MODULE_REPO_SHORTNAME/MODULE_REPO_PROVIDER//WRAPPER_PATH" # Alternative source: - # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" + # source = "git::git@github.com:MODULE_REPO_ORG/terraform-MODULE_REPO_PROVIDER-MODULE_REPO_SHORTNAME.git//WRAPPER_PATH?ref=master" } inputs = { From d46fbb60df6028458b10520fbf432ee1c9830983 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 4 Jan 2026 14:39:50 +0000 Subject: [PATCH 291/293] chore(release): version 1.104.1 [skip ci] ## [1.104.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.104.0...v1.104.1) (2026-01-04) ### Bug Fixes * **`terraform_wrapper_module_for_each`:** Generate right usage example for S3 ([#952](https://github.com/antonbabenko/pre-commit-terraform/issues/952)) ([3117fe2](https://github.com/antonbabenko/pre-commit-terraform/commit/3117fe2efa4aa0f0a4c1e9c0670c4183c7d90d24)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61cbb67ad..986c8c042 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.104.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.104.0...v1.104.1) (2026-01-04) + + +### Bug Fixes + +* **`terraform_wrapper_module_for_each`:** Generate right usage example for S3 ([#952](https://github.com/antonbabenko/pre-commit-terraform/issues/952)) ([3117fe2](https://github.com/antonbabenko/pre-commit-terraform/commit/3117fe2efa4aa0f0a4c1e9c0670c4183c7d90d24)) + # [1.104.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.103.0...v1.104.0) (2025-11-20) From 95a52e363965dde1ce1fc33e2e848980145b4575 Mon Sep 17 00:00:00 2001 From: Maksym Vlasov Date: Tue, 6 Jan 2026 14:53:50 +0200 Subject: [PATCH 292/293] feat(`terraform_providers_lock`): Fix logical issue in hook modes: Add `check-lockfile-is-cross-platform` and `regenerate-lockfile-if-some-platform-missed` modes. Last one is same as `only-check-is-current-lockfile-cross-platform`, which now is deprecated. Check README for more details (#950) --------- Co-authored-by: George Yermulnik (Georgii Iermulnik) --- README.md | 40 ++++++++++++++++---- hooks/terraform_providers_lock.sh | 62 ++++++++++++++++++++++++++----- 2 files changed, 84 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0b5bfff64..59f01426e 100644 --- a/README.md +++ b/README.md @@ -759,26 +759,44 @@ To replicate functionality in `terraform_docs` hook: > - --hook-config=--mode=always-regenerate-lockfile > ``` > -> Why? When v2.x will be introduced - the default mode will be changed, probably, to `only-check-is-current-lockfile-cross-platform`. +> Why? When v2.x will be introduced - the default mode will be changed, probably, to `check-lockfile-is-cross-platform`. > > You can check available modes for hook below. >
-1. The hook can work in a few different modes: `only-check-is-current-lockfile-cross-platform` with and without [terraform_validate hook](#terraform_validate) and `always-regenerate-lockfile` - only with terraform_validate hook. +1. The hook can work in a few different modes: - * `only-check-is-current-lockfile-cross-platform` without terraform_validate - only checks that lockfile has all required SHAs for all providers already added to lockfile. + 1.
--mode=check-lockfile-is-cross-platform (standalone) + Checks that lockfile has the same number of platform checksums (`h1:`) as requested by the hook configuration. It **does not** check whether these checksums are valid or that they match target platforms. ```yaml - id: terraform_providers_lock args: - - --hook-config=--mode=only-check-is-current-lockfile-cross-platform + - --hook-config=--mode=check-lockfile-is-cross-platform ``` - * `only-check-is-current-lockfile-cross-platform` with [terraform_validate hook](#terraform_validate) - make up-to-date lockfile by adding/removing providers and only then check that lockfile has all required SHAs. +
+ + 2.
--mode=regenerate-lockfile-if-some-platform-missed (standalone) + + Checks that lockfile has checksums (`h1:`) for all requested platforms for all providers tracked by the lockfile, and if any are missed - tries to add them (but could fail if `terraform init` wasn't run previously). + + + ```yaml + - id: terraform_providers_lock + args: + - --hook-config=--mode=regenerate-lockfile-if-some-platform-missed + ``` + +
+ + 3.
--mode=regenerate-lockfile-if-some-platform-missed with terraform_validate hook + + Regenerates lockfile for all required providers and checks that the lockfile tracks all required platform checksums (`h1:`) afterwards. If any are missed - adds them; superfluous providers are removed. > **Important** - > Next `terraform_validate` flag requires additional dependency to be installed: `jq`. Also, it could run another slow and time consuming command - `terraform init` + > The following [`terraform_validate`](#terraform_validate) hook's flag requires additional dependency to be installed: [`jq`](https://github.com/jqlang/jq). Also, it could run another slow and time consuming command - `terraform init` ```yaml - id: terraform_validate @@ -787,10 +805,14 @@ To replicate functionality in `terraform_docs` hook: - id: terraform_providers_lock args: - - --hook-config=--mode=only-check-is-current-lockfile-cross-platform + - --hook-config=--mode=regenerate-lockfile-if-some-platform-missed ``` - * `always-regenerate-lockfile` only with [terraform_validate hook](#terraform_validate) - regenerate lockfile from scratch. Can be useful for upgrading providers in lockfile to latest versions +
+ + 4.
always-regenerate-lockfile - meant to be used only along with terraform_validate hook + + Regenerates lockfile from the scratch. May be useful for upgrading providers in the lockfile to the latest versions. ```yaml - id: terraform_validate @@ -803,6 +825,8 @@ To replicate functionality in `terraform_docs` hook: - --hook-config=--mode=always-regenerate-lockfile ``` +
+ 2. `terraform_providers_lock` supports custom arguments: ```yaml diff --git a/hooks/terraform_providers_lock.sh b/hooks/terraform_providers_lock.sh index 9a4d1b624..32283cd33 100755 --- a/hooks/terraform_providers_lock.sh +++ b/hooks/terraform_providers_lock.sh @@ -101,9 +101,11 @@ function per_dir_hook_unique_part { local -a -r args=("$@") local platforms_count=0 + local platforms_names=() for arg in "${args[@]}"; do if grep -Eq '^-platform=' <<< "$arg"; then platforms_count=$((platforms_count + 1)) + platforms_names+=("${arg#*=}") fi done @@ -121,44 +123,84 @@ function per_dir_hook_unique_part { key=${config[0]} value=${config[1]} - case $key in + case "$key" in --mode) if [ "$mode" ]; then - common::colorify "yellow" 'Invalid hook config. Make sure that you specify not more than one "--mode" flag' + common::colorify "yellow" 'Invalid hook config. Make sure that you specify not more than one "--mode" flag.' exit 1 fi mode=$value + + case "$mode" in + check-lockfile-is-cross-platform) ;; + regenerate-lockfile-if-some-platform-missed) ;; + always-regenerate-lockfile) ;; + + only-check-is-current-lockfile-cross-platform) + common::colorify "yellow" "DEPRECATION NOTICE: Flag '--mode=only-check-is-current-lockfile-cross-platform' was renamed to '--mode=regenerate-lockfile-if-some-platform-missed' to better reflect its behavior. +Please update your configuration." + mode="regenerate-lockfile-if-some-platform-missed" + ;; + *) + common::colorify "red" "Invalid hook config. Supported --mode values are: + - check-lockfile-is-cross-platform + - regenerate-lockfile-if-some-platform-missed + - always-regenerate-lockfile" + exit 1 + ;; + esac ;; esac done # Available options: - # only-check-is-current-lockfile-cross-platform (will be default) + # check-lockfile-is-cross-platform (will be default in v2.0) + # regenerate-lockfile-if-some-platform-missed # always-regenerate-lockfile # TODO: Remove in 2.0 if [ ! "$mode" ]; then common::colorify "yellow" "DEPRECATION NOTICE: We introduced '--mode' flag for this hook. -Check migration instructions at https://github.com/antonbabenko/pre-commit-terraform#terraform_providers_lock -" +Check migration instructions at https://github.com/antonbabenko/pre-commit-terraform#terraform_providers_lock" common::terraform_init "$tf_path providers lock" "$dir_path" "$parallelism_disabled" "$tf_path" || { exit_code=$? return $exit_code } fi - if [ "$mode" == "only-check-is-current-lockfile-cross-platform" ] && - lockfile_contains_all_needed_sha "$platforms_count"; then + case "$mode" in + "check-lockfile-is-cross-platform") + if lockfile_contains_all_needed_sha "$platforms_count"; then + exit 0 + fi - exit 0 - fi + common::colorify "red" "$dir_path/.terraform.lock.hcl missing some of required platforms. +All required platforms: ${platforms_names[*]}" + + exit 1 + ;; + "regenerate-lockfile-if-some-platform-missed") + if lockfile_contains_all_needed_sha "$platforms_count"; then + exit 0 + fi + + common::colorify "yellow" "$dir_path/.terraform.lock.hcl missing some of required platforms. +All required platforms: ${platforms_names[*]}" + + ;; + esac #? Don't require `tf init` for providers, but required `tf init` for modules #? Mitigated by `function match_validate_errors` from terraform_validate hook # pass the arguments to hook "$tf_path" providers lock "${args[@]}" - # return exit code to common::per_dir_hook exit_code=$? + if [[ $exit_code -ne 0 ]]; then + common::colorify "red" "$dir_path run failed. Detailed error above. +Most common issue is that required 'terraform init' command was likely not run before running this hook. It might be run for you automatically by 'terraform_validate' hook - see https://github.com/antonbabenko/pre-commit-terraform#terraform_validate for more details." + fi + + # return exit code to common::per_dir_hook return $exit_code } From d0e12caebb2ab0ee8bf98181c8bfe9702bca103d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 6 Jan 2026 12:54:16 +0000 Subject: [PATCH 293/293] chore(release): version 1.105.0 [skip ci] # [1.105.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.104.1...v1.105.0) (2026-01-06) ### Features * **`terraform_providers_lock`:** Fix logical issue in hook modes: Add `check-lockfile-is-cross-platform` and `regenerate-lockfile-if-some-platform-missed` modes. Last one is same as `only-check-is-current-lockfile-cross-platform`, which now is deprecated. Check README for more details ([#950](https://github.com/antonbabenko/pre-commit-terraform/issues/950)) ([95a52e3](https://github.com/antonbabenko/pre-commit-terraform/commit/95a52e363965dde1ce1fc33e2e848980145b4575)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 986c8c042..c1870ea6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +# [1.105.0](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.104.1...v1.105.0) (2026-01-06) + + +### Features + +* **`terraform_providers_lock`:** Fix logical issue in hook modes: Add `check-lockfile-is-cross-platform` and `regenerate-lockfile-if-some-platform-missed` modes. Last one is same as `only-check-is-current-lockfile-cross-platform`, which now is deprecated. Check README for more details ([#950](https://github.com/antonbabenko/pre-commit-terraform/issues/950)) ([95a52e3](https://github.com/antonbabenko/pre-commit-terraform/commit/95a52e363965dde1ce1fc33e2e848980145b4575)) + ## [1.104.1](https://github.com/antonbabenko/pre-commit-terraform/compare/v1.104.0...v1.104.1) (2026-01-04)