├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── cron-check-dependencies.yml │ ├── manual-sync-common-files.yml │ ├── auto-create-pull-request.yml │ ├── auto-create-release.yml │ └── manual-update-version.yml ├── .dockerignore ├── .gitignore ├── .shellcheckrc ├── .editorconfig ├── .hadolint.yaml ├── .yamllint.yml ├── Dockerfile ├── Taskfile.yml ├── LICENSE ├── action.yml ├── .pre-commit-config.yaml ├── entrypoint.sh ├── Taskfile.docker.yml ├── README.md ├── Taskfile.variables.yml └── Taskfile.cicd.yml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Owner of everything 2 | * @ChristophShyper @devops-infra/christophshyper 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Exclude 2 | * 3 | 4 | # Include 5 | !Dockerfile 6 | !entrypoint.sh 7 | !LICENSE 8 | !README.md 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | /.idea/ 3 | *.iml 4 | 5 | # Custom 6 | .tmp/ 7 | .venv 8 | .venv/ 9 | .envrc 10 | .env 11 | .tmp 12 | -------------------------------------------------------------------------------- /.shellcheckrc: -------------------------------------------------------------------------------- 1 | # shellcheck configuration 2 | shell=bash 3 | check-sourced=true 4 | external-sources=true 5 | source-path=SCRIPTDIR 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent coding styles 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /.hadolint.yaml: -------------------------------------------------------------------------------- 1 | failure-threshold: error 2 | format: tty 3 | strict-labels: false 4 | no-color: false 5 | no-fail: false 6 | disable-ignore-pragma: false 7 | trustedRegistries: 8 | - docker.io 9 | - ghcr.io 10 | 11 | # ignored: [string] 12 | # label-schema: 13 | # author: text 14 | # contact: email 15 | # created: rfc3339 16 | # version: semver 17 | # documentation: url 18 | # git-revision: hash 19 | # license: spdx 20 | # override: 21 | # error: [string] 22 | # warning: [string] 23 | # info: [string] 24 | # style: [string] 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: github-actions 5 | directory: / 6 | schedule: 7 | interval: weekly 8 | assignees: 9 | - ChristophShyper 10 | 11 | # Enable version updates for Docker 12 | - package-ecosystem: docker 13 | directory: / 14 | schedule: 15 | interval: weekly 16 | assignees: 17 | - ChristophShyper 18 | 19 | # # Enable version updates for pip 20 | # - package-ecosystem: pip 21 | # directory: / 22 | # schedule: 23 | # interval: daily 24 | # assignees: 25 | # - ChristophShyper 26 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | extends: default 2 | rules: 3 | empty-lines: 4 | max: 2 5 | document-end: 6 | present: false 7 | document-start: 8 | present: false 9 | indentation: 10 | spaces: 2 11 | indent-sequences: true 12 | check-multi-line-strings: false 13 | line-length: 14 | max: 140 15 | allow-non-breakable-inline-mappings: true 16 | new-line-at-end-of-file: enable 17 | new-lines: 18 | type: unix 19 | quoted-strings: 20 | required: only-when-needed 21 | extra-allowed: ['true', 'false'] 22 | trailing-spaces: {} 23 | truthy: 24 | allowed-values: ['true', 'false', 'yes', 'no'] 25 | check-keys: false 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Instead of building from scratch pull my other docker image 2 | FROM devopsinfra/docker-terragrunt:slim-latest AS builder 3 | 4 | # Use a clean tiny image to store artifacts in 5 | FROM ubuntu:questing-20251029 6 | 7 | # Disable interactive mode 8 | ENV DEBIAN_FRONTEND=noninteractive 9 | 10 | # Copy all needed files 11 | COPY --from=builder /usr/bin/terraform /usr/bin/format-hcl /usr/bin/fmt.sh /usr/bin/terragrunt-fmt.sh /usr/bin/ 12 | COPY entrypoint.sh / 13 | 14 | # Install needed packages 15 | SHELL ["/bin/bash", "-euxo", "pipefail", "-c"] 16 | # hadolint ignore=DL3008 17 | RUN set -eux ;\ 18 | apt-get update -y ;\ 19 | chmod +x /entrypoint.sh /usr/bin/format-hcl /usr/bin/fmt.sh /usr/bin/terragrunt-fmt.sh ;\ 20 | apt-get clean ;\ 21 | rm -rf /var/lib/apt/lists/* 22 | 23 | # Finish up 24 | CMD ["terraform --version"] 25 | WORKDIR /github/workspace 26 | ENTRYPOINT ["/entrypoint.sh"] 27 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | silent: true 4 | 5 | includes: 6 | variables: ./Taskfile.variables.yml 7 | cicd: 8 | taskfile: ./Taskfile.cicd.yml 9 | flatten: true 10 | docker: 11 | taskfile: ./Taskfile.docker.yml 12 | flatten: true 13 | 14 | 15 | tasks: 16 | default: 17 | desc: List tasks 18 | cmds: 19 | - task help 20 | 21 | help: 22 | desc: Detailed help 23 | cmds: 24 | - | 25 | echo "Tasks:" 26 | task --list 27 | echo "" 28 | echo "Environment:" 29 | echo " DOCKER_NAME={{.DOCKER_NAME}} DOCKER_USERNAME={{.DOCKER_USERNAME}}" 30 | echo " GHRC_NAME={{.GHRC_NAME}} GITHUB_USERNAME={{.GITHUB_USERNAME}}" 31 | echo " LAST_RELEASE={{.LAST_RELEASE}}" VERSION={{.VERSION}} VERSION_FULL={{.VERSION_FULL}} 32 | echo " BRANCH={{.GIT_BRANCH}} GIT_SHORT_SHA={{.GIT_SHORT_SHA}}" GIT_SHA={{.GIT_SHA}} 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Krzysztof Szyper aka ChristophShyper (https://shyper.pro/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Action for formating HCL files 2 | author: Krzysztof Szyper / ChristophShyper / shyper.pro 3 | description: GitHub Action automatically formatting all HCL and TF files 4 | inputs: 5 | list: 6 | description: List files containing formatting inconsistencies. 7 | required: false 8 | default: "false" 9 | write: 10 | description: Overwrite input files. Disabled if using check. 11 | required: false 12 | default: "true" 13 | ignore: 14 | description: Comma separated list of paths to ignore. Only for .hcl files. 15 | required: false 16 | default: "" 17 | diff: 18 | description: Display diffs of formatting changes. 19 | required: false 20 | default: "false" 21 | check: 22 | description: Check if files are malformatted. 23 | required: false 24 | default: "false" 25 | recursive: 26 | description: Also process files in subdirectories. 27 | required: false 28 | default: "true" 29 | dir: 30 | description: Path to be checked. Current dir as default. 31 | required: false 32 | default: "" 33 | outputs: 34 | files_changed: 35 | description: List of formatted files 36 | runs: 37 | using: docker 38 | image: docker://devopsinfra/action-format-hcl:v1.0.1 39 | branding: 40 | color: purple 41 | icon: upload-cloud 42 | -------------------------------------------------------------------------------- /.github/workflows/cron-check-dependencies.yml: -------------------------------------------------------------------------------- 1 | name: (Cron) Check dependencies 2 | 3 | on: 4 | schedule: 5 | # Run every Monday at 08:00 UTC 6 | - cron: 0 8 * * 1 7 | 8 | permissions: 9 | contents: read 10 | packages: write 11 | 12 | jobs: 13 | dependency-check: 14 | name: Test dependencies 15 | runs-on: ubuntu-24.04-arm 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v6 19 | with: 20 | fetch-depth: 0 21 | fetch-tags: true 22 | 23 | - name: Install Task 24 | uses: arduino/setup-task@v2.0.0 25 | with: 26 | version: 3.x 27 | 28 | - name: Install Docker Buildx 29 | uses: docker/setup-buildx-action@v3 30 | with: 31 | install: true 32 | 33 | - name: Install QEMU 34 | uses: docker/setup-qemu-action@v3 35 | with: 36 | image: tonistiigi/binfmt:latest 37 | platforms: amd64,arm64 38 | 39 | - name: Run linters 40 | run: task lint 41 | 42 | - name: Get Docker commands 43 | run: task docker:cmds 44 | 45 | - name: Build and push test image 46 | env: 47 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | run: task docker:push 50 | 51 | - name: Inspect image 52 | run: task docker:push:inspect 53 | -------------------------------------------------------------------------------- /.github/workflows/manual-sync-common-files.yml: -------------------------------------------------------------------------------- 1 | name: (Manual) Sync Common Files 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | type: 7 | description: File type to sync 8 | required: true 9 | default: all 10 | type: choice 11 | options: 12 | - all 13 | - configs 14 | - ignores 15 | - taskfiles 16 | - workflows 17 | 18 | permissions: 19 | contents: write 20 | pull-requests: write 21 | 22 | jobs: 23 | update: 24 | name: Sync common files and create pull request 25 | runs-on: ubuntu-24.04-arm 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v6 29 | with: 30 | fetch-depth: 0 31 | fetch-tags: true 32 | 33 | - name: Install Task 34 | uses: arduino/setup-task@v2.0.0 35 | with: 36 | version: 3.x 37 | 38 | - name: Sync files and get PR template 39 | id: version 40 | run: | 41 | task sync:${{ github.event.inputs.type }} 42 | task git:set-config 43 | task git:get-pr-template 44 | 45 | - name: Push to release branch 46 | uses: devops-infra/action-commit-push@v1 47 | with: 48 | github_token: ${{ secrets.GITHUB_TOKEN }} 49 | commit_message: ":art: Sync common files with action-template repository" 50 | target_branch: ${{ format('release/{0}', steps.version.outputs.REL_VERSION) }} 51 | 52 | - name: Create Pull Request 53 | uses: devops-infra/action-pull-request@v1 54 | with: 55 | github_token: ${{ secrets.GITHUB_TOKEN }} 56 | assignee: ${{ github.actor }} 57 | template: .tmp/PULL_REQUEST_TEMPLATE.md 58 | get_diff: true 59 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v6.0.0 4 | hooks: 5 | - id: check-added-large-files 6 | - id: check-case-conflict 7 | - id: check-executables-have-shebangs 8 | - id: check-illegal-windows-names 9 | - id: check-json 10 | - id: check-merge-conflict 11 | - id: check-shebang-scripts-are-executable 12 | - id: check-symlinks 13 | - id: check-xml 14 | - id: check-yaml 15 | - id: destroyed-symlinks 16 | - id: detect-private-key 17 | - id: end-of-file-fixer 18 | - id: mixed-line-ending 19 | args: [--fix=lf] 20 | - id: no-commit-to-branch 21 | args: [--branch, master, --branch, main] 22 | - id: pretty-format-json 23 | args: [--autofix] 24 | - id: trailing-whitespace 25 | - repo: local 26 | hooks: 27 | - id: actionlint 28 | name: actionlint 29 | entry: bash -lc 'docker run --rm -v "$PWD:/work" -w /work rhysd/actionlint:latest -color' 30 | language: system 31 | pass_filenames: false 32 | - id: hadolint 33 | name: hadolint 34 | entry: bash -lc 'docker run --rm -v "$PWD:/work" -w /work hadolint/hadolint:latest-debian hadolint Dockerfile' 35 | language: system 36 | pass_filenames: false 37 | - id: shellcheck 38 | name: shellcheck 39 | entry: bash -lc 'docker run --rm -v "$PWD:/work" -w /work koalaman/shellcheck:stable -x -S style entrypoint.sh' 40 | language: system 41 | pass_filenames: false 42 | - id: yamllint 43 | name: yamllint 44 | entry: bash -lc 'docker run --rm -v "$PWD:/work" -w /work cytopia/yamllint -c .yamllint.yml .' 45 | language: system 46 | pass_filenames: false 47 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -Eeuo pipefail 4 | 5 | # Return code 6 | RET_CODE=0 7 | 8 | # Print input variables 9 | echo "Inputs:" 10 | echo " list: ${INPUT_LIST}" 11 | echo " write: ${INPUT_WRITE}" 12 | echo " ignore: ${INPUT_IGNORE}" 13 | #shellcheck disable=SC2153 14 | echo " diff: ${INPUT_DIFF}" 15 | echo " check: ${INPUT_CHECK}" 16 | echo " recursive: ${INPUT_RECURSIVE}" 17 | echo " dir: ${INPUT_DIR}" 18 | 19 | # Remap input variables as parameters for format-hcl 20 | LIST="-list=${INPUT_LIST}" 21 | WRITE="-write=${INPUT_WRITE}" 22 | 23 | if [[ -n "${INPUT_IGNORE}" ]]; then 24 | IGNORE="-ignore=${INPUT_IGNORE}" 25 | else 26 | IGNORE="" 27 | fi 28 | 29 | if [[ "${INPUT_DIFF}" == "true" ]]; then 30 | DIFF="-diff" 31 | else 32 | DIFF="" 33 | fi 34 | 35 | if [[ "${INPUT_CHECK}" == "true" ]]; then 36 | CHECK="-check" 37 | else 38 | CHECK="" 39 | fi 40 | 41 | if [[ "${INPUT_RECURSIVE}" == "true" ]]; then 42 | RECURSIVE="-recursive" 43 | else 44 | RECURSIVE="" 45 | fi 46 | 47 | if [[ -n "${INPUT_DIR}" ]]; then 48 | DIR="${INPUT_DIR}" 49 | else 50 | DIR="" 51 | fi 52 | 53 | # Run main action 54 | touch /tmp/time_compare 55 | /usr/bin/format-hcl "${LIST}" "${WRITE}" "${IGNORE}" "${DIFF}" "${CHECK}" "${RECURSIVE}" "${DIR}" 56 | RET_CODE=$? 57 | 58 | # List of changed files 59 | FILES_CHANGED=$(find . -newer /tmp/time_compare -type f) 60 | 61 | # Info about changed files 62 | if [[ -n ${FILES_CHANGED} ]]; then 63 | echo -e "\n[INFO] Updated files:" 64 | for FILE in ${FILES_CHANGED}; do 65 | echo "${FILE}" 66 | done 67 | else 68 | echo -e "\n[INFO] No files updated." 69 | fi 70 | 71 | # Finish 72 | if [[ ${RET_CODE} != "0" ]]; then 73 | echo -e "\n[ERROR] Check log for errors." 74 | exit 1 75 | else 76 | # Pass in other cases 77 | echo -e "\n[INFO] No errors found." 78 | exit 0 79 | fi 80 | -------------------------------------------------------------------------------- /.github/workflows/auto-create-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: (Auto) Create Pull Request 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - master 7 | - dependabot/** 8 | 9 | permissions: 10 | contents: read 11 | packages: write 12 | pull-requests: write 13 | 14 | jobs: 15 | lint: 16 | name: Lint 17 | runs-on: ubuntu-24.04-arm 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v6 21 | with: 22 | fetch-depth: 0 23 | fetch-tags: true 24 | 25 | - name: Install Task 26 | uses: arduino/setup-task@v2.0.0 27 | with: 28 | version: 3.x 29 | 30 | - name: Run linters 31 | run: task lint 32 | 33 | build-and-push: 34 | name: Build and push 35 | runs-on: ubuntu-24.04-arm 36 | needs: [lint] 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v6 40 | with: 41 | fetch-depth: 0 42 | fetch-tags: true 43 | 44 | - name: Install Task 45 | uses: arduino/setup-task@v2.0.0 46 | with: 47 | version: 3.x 48 | 49 | - name: Install Docker Buildx 50 | uses: docker/setup-buildx-action@v3 51 | with: 52 | install: true 53 | 54 | - name: Install QEMU 55 | uses: docker/setup-qemu-action@v3 56 | with: 57 | image: tonistiigi/binfmt:latest 58 | platforms: amd64,arm64 59 | 60 | - name: Get Docker commands 61 | run: task docker:cmds 62 | 63 | - name: Build and push test image 64 | env: 65 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | run: task docker:push 68 | 69 | - name: Inspect image 70 | run: task docker:push:inspect 71 | 72 | pull-request: 73 | name: Pull Request 74 | runs-on: ubuntu-24.04-arm 75 | steps: 76 | - name: Checkout 77 | uses: actions/checkout@v6 78 | with: 79 | fetch-depth: 0 80 | fetch-tags: true 81 | 82 | - name: Install Task 83 | uses: arduino/setup-task@v2.0.0 84 | with: 85 | version: 3.x 86 | 87 | - name: Get template 88 | run: task git:get-pr-template 89 | 90 | - name: Create Pull Request 91 | uses: devops-infra/action-pull-request@v1 92 | with: 93 | github_token: ${{ secrets.GITHUB_TOKEN }} 94 | assignee: ${{ github.actor }} 95 | template: .tmp/PULL_REQUEST_TEMPLATE.md 96 | get_diff: true 97 | -------------------------------------------------------------------------------- /.github/workflows/auto-create-release.yml: -------------------------------------------------------------------------------- 1 | name: (Auto) Create release 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | push: 7 | branches: 8 | - release/** 9 | 10 | permissions: 11 | contents: write 12 | packages: write 13 | 14 | jobs: 15 | release: 16 | if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') 17 | name: Create Release 18 | runs-on: ubuntu-24.04-arm 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v6 22 | with: 23 | fetch-depth: 0 24 | fetch-tags: true 25 | 26 | - name: Install Task 27 | uses: arduino/setup-task@v2.0.0 28 | with: 29 | version: 3.x 30 | 31 | - name: Create and push git tags 32 | id: version 33 | env: 34 | VERSION_SUFFIX: "" 35 | run: | 36 | task lint 37 | task git:set-config 38 | task version:tag-release 39 | echo "REL_VERSION=$(task version:get)" >> "$GITHUB_OUTPUT" 40 | 41 | - name: Install Docker Buildx 42 | uses: docker/setup-buildx-action@v3 43 | with: 44 | install: true 45 | 46 | - name: Install QEMU 47 | uses: docker/setup-qemu-action@v3 48 | with: 49 | image: tonistiigi/binfmt:latest 50 | platforms: amd64,arm64 51 | 52 | - name: Get Docker commands 53 | env: 54 | VERSION_SUFFIX: "" 55 | run: task docker:cmds 56 | 57 | - name: Build and Push 58 | env: 59 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | VERSION_SUFFIX: "" 62 | run: task docker:push 63 | 64 | - name: Inspect image 65 | env: 66 | VERSION_SUFFIX: "" 67 | run: task docker:push:inspect 68 | 69 | - name: Create GitHub release 70 | uses: softprops/action-gh-release@v2 71 | with: 72 | tag_name: ${{ steps.version.outputs.REL_VERSION }} 73 | name: ${{ steps.version.outputs.REL_VERSION }} 74 | draft: false 75 | prerelease: false 76 | generate_release_notes: true 77 | env: 78 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 79 | 80 | - name: Update Docker hub description 81 | uses: peter-evans/dockerhub-description@v5 82 | with: 83 | username: ${{ vars.DOCKER_USERNAME }} 84 | password: ${{ secrets.DOCKER_TOKEN }} 85 | repository: ${{ vars.DOCKER_ORG_NAME }}/${{ github.event.repository.name }} 86 | short-description: ${{ github.event.repository.description }} 87 | -------------------------------------------------------------------------------- /.github/workflows/manual-update-version.yml: -------------------------------------------------------------------------------- 1 | name: (Manual) Update Version 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | type: 7 | description: Bump type 8 | required: true 9 | default: patch 10 | type: choice 11 | options: 12 | - patch 13 | - minor 14 | - major 15 | - set 16 | version: 17 | description: Explicit version when type="set" (e.g., v1.2.3) 18 | required: false 19 | default: '' 20 | 21 | permissions: 22 | contents: write 23 | pull-requests: write 24 | 25 | jobs: 26 | update: 27 | name: Update version and push release branch 28 | runs-on: ubuntu-24.04-arm 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v6 32 | with: 33 | fetch-depth: 0 34 | fetch-tags: true 35 | 36 | - name: Install Task 37 | uses: arduino/setup-task@v2.0.0 38 | with: 39 | version: 3.x 40 | 41 | - name: Update version 42 | id: version 43 | env: 44 | BUMP_TYPE: ${{ github.event.inputs.type }} 45 | INPUT_VERSION: ${{ github.event.inputs.version }} 46 | run: | 47 | set -eux 48 | case "${BUMP_TYPE}" in 49 | set) 50 | if [ -z "${INPUT_VERSION}" ]; then 51 | echo "Missing version for type=set" 52 | exit 1 53 | fi 54 | task version:set VERSION_OVERRIDE="${INPUT_VERSION}" 55 | ;; 56 | patch) 57 | task version:update:patch 58 | ;; 59 | minor) 60 | task version:update:minor 61 | ;; 62 | major) 63 | task version:update:major 64 | ;; 65 | *) 66 | echo "Unknown type: ${BUMP_TYPE}" 67 | exit 1 68 | ;; 69 | esac 70 | echo "REL_VERSION=$(task version:get)" >> "$GITHUB_OUTPUT" 71 | 72 | - name: Get template 73 | env: 74 | VERSION_SUFFIX: "" 75 | run: | 76 | task git:set-config 77 | task git:get-pr-template 78 | 79 | - name: Push to release branch 80 | uses: devops-infra/action-commit-push@v1 81 | with: 82 | github_token: ${{ secrets.GITHUB_TOKEN }} 83 | commit_message: ":rocket: Bump version to ${{ steps.version.outputs.REL_VERSION }}" 84 | target_branch: ${{ format('release/{0}', steps.version.outputs.REL_VERSION) }} 85 | 86 | - name: Create Pull Request 87 | uses: devops-infra/action-pull-request@v1 88 | with: 89 | github_token: ${{ secrets.GITHUB_TOKEN }} 90 | assignee: ${{ github.actor }} 91 | template: .tmp/PULL_REQUEST_TEMPLATE.md 92 | get_diff: true 93 | -------------------------------------------------------------------------------- /Taskfile.docker.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | silent: true 4 | 5 | tasks: 6 | docker:login: 7 | desc: Login to hub.docker.com and ghcr.io 8 | cmds: 9 | - echo "Logging into Docker Hub as {{.DOCKER_USERNAME}}" 10 | - echo "${DOCKER_TOKEN}" | docker login -u "{{.DOCKER_USERNAME}}" --password-stdin 11 | - echo "Logging into GHCR as {{.GITHUB_USERNAME}}" 12 | - echo "${GITHUB_TOKEN}" | docker login ghcr.io -u "{{.GITHUB_USERNAME}}" --password-stdin 13 | 14 | docker:cmds: 15 | desc: Show full docker build command 16 | cmds: 17 | - echo -e '{{.DOCKER_BUILD_START}} {{.DOCKER_BUILD_FINISH}}' | {{.SED}} 's/--/ \\\n --/g' 18 | 19 | docker:build: 20 | desc: Build Docker image 21 | cmds: 22 | - docker buildx create --use 23 | - '{{.DOCKER_BUILD_START}} {{.DOCKER_BUILD_FINISH}}' 24 | 25 | docker:build:inspect: 26 | desc: Inspect built Docker image 27 | cmds: 28 | - | 29 | image_inspect_out=$(docker image inspect {{.DOCKER_NAME}}:{{.VERSION_FULL}}{{.VERSION_SUFFIX}} | jq -r) 30 | echo -e "\nℹ️ Docker image inspect:" 31 | echo "$image_inspect_out" | jq 32 | 33 | docker:push: 34 | desc: Build and push Docker images 35 | deps: 36 | - task: docker:login 37 | cmds: 38 | - docker buildx create --use 39 | - '{{.DOCKER_BUILD_START}} --push {{.DOCKER_BUILD_FINISH}}' 40 | 41 | docker:push:inspect: 42 | desc: Inspect built Docker image 43 | cmds: 44 | - | 45 | set -eu 46 | image="{{.DOCKER_NAME}}:{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 47 | 48 | echo -e "\nℹ️ Trying local image inspect: $image" 49 | set +e 50 | image_inspect_out=$(docker image inspect "$image" 2>/dev/null || true) 51 | rc=$? 52 | set -e 53 | 54 | # Validate that docker inspect returned a non-empty array with an Id 55 | has_local=0 56 | if [ "$rc" -eq 0 ] && [ -n "$image_inspect_out" ]; then 57 | if echo "$image_inspect_out" | jq -e 'type=="array" and (length > 0) and \ 58 | (.[0].Id != null and .[0].Id != "")' >/dev/null 2>&1; then 59 | has_local=1 60 | fi 61 | fi 62 | 63 | if [ "$has_local" -eq 1 ]; then 64 | echo -e "\n✅ Local image found. Docker image inspect:" 65 | echo "$image_inspect_out" | jq 66 | image_sha=$(echo "$image_inspect_out" | jq -r '.[0].Id // empty') 67 | if [ -n "$image_sha" ]; then 68 | echo -e "\nℹ️ Docker manifest inspect (local):" 69 | docker manifest inspect "${image}@${image_sha}" | jq || true 70 | fi 71 | exit 0 72 | fi 73 | 74 | echo -e "\nℹ️ Local image not found or inspect returned empty; inspecting remote with buildx imagetools..." 75 | set +e 76 | raw=$(docker buildx imagetools inspect --raw "$image" 2>/dev/null || true) 77 | set -e 78 | 79 | if [ -z "$raw" ]; then 80 | echo "❌ Failed to inspect remote image with buildx imagetools: $image" 81 | exit 1 82 | fi 83 | 84 | echo -e "\n✅ Remote manifest/index (raw):" 85 | echo "$raw" | jq 86 | 87 | echo -e "\nℹ️ Attempting to pull and inspect per-platform manifests:" 88 | echo "$raw" | jq -r '.manifests[]?.digest' | while IFS= read -r digest; do 89 | if [ -z "$digest" ] || [ "$digest" = "null" ]; then 90 | continue 91 | fi 92 | ref="${image%@*}@${digest}" 93 | echo -e "\nℹ️ Pulling $ref (may fail for some registries)..." 94 | set +e 95 | docker pull "$ref" >/dev/null 2>&1 || true 96 | pulled_rc=$? 97 | set -e 98 | if [ "$pulled_rc" -eq 0 ]; then 99 | echo "ℹ️ Inspecting pulled image $ref" 100 | docker image inspect "$ref" | jq || true 101 | else 102 | echo "⚠️ Could not pull $ref; skipping image inspect" 103 | fi 104 | done 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Action for formating HCL files 2 | **GitHub Action automatically formatting all [HCL](https://github.com/hashicorp/hcl) and [TF](https://www.terraform.io/docs/configuration/index.html) files (.hcl, .tf, .tfvars).** 3 | 4 | 5 | ## 📦 Available on 6 | - **Docker Hub:** [devopsinfra/action-format-hcl:latest](https://hub.docker.com/repository/docker/devopsinfra/action-format-hcl) 7 | - **GitHub Packages:** [ghcr.io/devops-infra/action-format-hcl:latest](https://github.com/devops-infra/action-format-hcl/pkgs/container/action-format-hcl) 8 | 9 | 10 | ## ✨ Features 11 | * Container is a stripped down image of my other creation - [devopsinfra/docker-terragrunt](https://github.com/devopsinfra/docker-terragrunt) - framework for managing Infrastructure-as-a-Code. 12 | * Main use will be everywhere where [Terraform](https://github.com/hashicorp/terraform) or [Terragrunt](https://github.com/gruntwork-io/terragrunt) is used. 13 | * Using combination of my wrapper for [cytopia](https://github.com/cytopia)'s [docker-terragrunt-fmt](https://github.com/cytopia/docker-terragrunt-fmt). 14 | 15 | 16 | ## 🔗 Related Actions 17 | **Useful in combination with my other action [devops-infra/action-commit-push](https://github.com/devops-infra/action-commit-push).** 18 | 19 | 20 | ## 📊 Badges 21 | [ 22 | ![GitHub repo](https://img.shields.io/badge/GitHub-devops--infra%2Faction--format--hcl-blueviolet.svg?style=plastic&logo=github) 23 | ![GitHub last commit](https://img.shields.io/github/last-commit/devops-infra/action-format-hcl?color=blueviolet&logo=github&style=plastic&label=Last%20commit) 24 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/devops-infra/action-format-hcl?color=blueviolet&label=Code%20size&style=plastic&logo=github) 25 | ![GitHub license](https://img.shields.io/github/license/devops-infra/action-format-hcl?color=blueviolet&logo=github&style=plastic&label=License) 26 | ](https://github.com/devops-infra/action-format-hcl "shields.io") 27 |
28 | [ 29 | ![DockerHub](https://img.shields.io/badge/DockerHub-devopsinfra%2Faction--format--hcl-blue.svg?style=plastic&logo=docker) 30 | ![Docker version](https://img.shields.io/docker/v/devopsinfra/action-format-hcl?color=blue&label=Version&logo=docker&style=plastic&sort=semver) 31 | ![Image size](https://img.shields.io/docker/image-size/devopsinfra/action-format-hcl/latest?label=Image%20size&style=plastic&logo=docker) 32 | ![Docker Pulls](https://img.shields.io/docker/pulls/devopsinfra/action-format-hcl?color=blue&label=Pulls&logo=docker&style=plastic) 33 | ](https://hub.docker.com/r/devopsinfra/action-format-hcl "shields.io") 34 | 35 | 36 | ## 🏷️ Version Tags: vX, vX.Y, vX.Y.Z 37 | This action supports three tag levels for flexible versioning: 38 | - `vX`: latest patch of the major version (e.g., `v1`). 39 | - `vX.Y`: latest patch of the minor version (e.g., `v1.2`). 40 | - `vX.Y.Z`: fixed to a specific release (e.g., `v1.2.3`). 41 | 42 | 43 | ## 📖 API Reference 44 | ```yaml 45 | - name: Run the Action 46 | uses: devops-infra/action-format-hcl@v1.0.1 47 | with: 48 | list: false 49 | write: true 50 | ignore: config 51 | diff: false 52 | check: false 53 | recursive: true 54 | dir: modules 55 | ``` 56 | 57 | 58 | ### 🔧 Input Parameters 59 | | Input Variable | Required | Default | Description | 60 | |----------------|----------|---------|---------------------------------------------------------------| 61 | | `list` | No | `false` | List files containing formatting inconsistencies. | 62 | | `write` | No | `true` | Overwrite input files. Should be disabled if using check. | 63 | | `ignore` | No | `""` | Comma separated list of paths to ignore. Only for .hcl files. | 64 | | `diff` | No | `false` | Display diffs of formatting changes. | 65 | | `check` | No | `false` | Check if files are malformatted. | 66 | | `recursive` | No | `true` | Also process files in subdirectories. | 67 | | `dir` | No | `""` | Path to be checked. Current dir as default. | 68 | 69 | 70 | ### 📤 Outputs Parameters 71 | | Output | Description | 72 | |-----------------|-----------------------| 73 | | `files_changed` | List of changed files | 74 | 75 | 76 | ## 💻 Usage Examples 77 | 78 | ### 📝 Basic Example 79 | Action can fail if malformed files will be found. 80 | 81 | ```yaml 82 | name: Check HCL 83 | on: 84 | push 85 | jobs: 86 | format-hcl: 87 | runs-on: ubuntu-latest 88 | steps: 89 | - name: Checkout 90 | uses: actions/checkout@v5 91 | 92 | - name: Fail on malformatted files 93 | uses: devops-infra/action-format-hcl@v1.0.1 94 | with: 95 | check: true 96 | ``` 97 | 98 | ### 🔀 Advanced Example 99 | Action can automatically format all HCL files and commit updated files back to the repository using my other action [action-commit-push](https://github.com/devops-infra/action-commit-push). 100 | 101 | ```yaml 102 | name: Format HCL 103 | on: 104 | push 105 | jobs: 106 | format-hcl: 107 | runs-on: ubuntu-latest 108 | steps: 109 | - name: Checkout 110 | uses: actions/checkout@v5 111 | 112 | - name: Format HCL files 113 | uses: devops-infra/action-format-hcl@v1.0.1 114 | 115 | - name: Commit changes to repo 116 | uses: devops-infra/action-commit-push@v1 117 | with: 118 | github_token: ${{ secrets.GITHUB_TOKEN }} 119 | commit_prefix: "[AUTO-FORMAT-HCL]" 120 | ``` 121 | 122 | 123 | ## 🤝 Contributing 124 | Contributions are welcome! See [CONTRIBUTING](https://github.com/devops-infra/.github/blob/master/CONTRIBUTING.md). 125 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 126 | 127 | 128 | ## 📄 License 129 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 130 | 131 | 132 | ## 💬 Support 133 | If you have any questions or need help, please: 134 | - 📝 Create an [issue](https://github.com/devops-infra/action-format-hcl/issues) 135 | - 🌟 Star this repository if you find it useful! 136 | -------------------------------------------------------------------------------- /Taskfile.variables.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | silent: true 4 | 5 | env: 6 | DOCKER_BUILDKIT: '1' 7 | TERM: xterm-256color 8 | 9 | vars: 10 | # System context 11 | SED: 12 | sh: | 13 | if [ "$(uname -s)" = "Darwin" ]; then 14 | if command -v gsed >/dev/null 2>&1; then 15 | echo gsed 16 | else 17 | if command -v brew >/dev/null 2>&1; then 18 | # Quietly ensure gnu-sed is installed 19 | brew list gnu-sed >/dev/null 2>&1 || { 20 | HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_ANALYTICS=1 brew update >/dev/null 2>&1 || true 21 | HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_ANALYTICS=1 brew install gnu-sed >/dev/null 2>&1 22 | } 23 | echo gsed 24 | else 25 | echo sed 26 | fi 27 | fi 28 | else 29 | echo sed 30 | fi 31 | PROJECT_DIR_NAME: 32 | sh: basename "$PWD" 33 | 34 | # Container metadata 35 | DOCKER_IMAGE: '{{.DOCKER_IMAGE | default .PROJECT_DIR_NAME}}' 36 | GITHUB_REPO: '{{.GITHUB_REPO | default .PROJECT_DIR_NAME}}' 37 | DOCKER_ORG_NAME: '{{.DOCKER_ORG_NAME | default "devopsinfra"}}' 38 | GITHUB_ORG_NAME: '{{.GITHUB_ORG_NAME | default "devops-infra"}}' 39 | DOCKER_USERNAME: '{{.DOCKER_USERNAME | default "christophshyper"}}' 40 | GITHUB_USERNAME: '{{.GITHUB_USERNAME | default "ChristophShyper"}}' 41 | DOCKER_NAME: '{{.DOCKER_ORG_NAME}}/{{.DOCKER_IMAGE}}' 42 | GITHUB_NAME: '{{.GITHUB_ORG_NAME}}/{{.GITHUB_REPO}}' 43 | GHRC_NAME: ghcr.io/{{.GITHUB_ORG_NAME}}/{{.GITHUB_REPO}} 44 | DEFAULT_BRANCH: master 45 | VERSION_FROM_ACTION_YML: 46 | sh: 'grep "image: docker://{{.DOCKER_NAME}}:" action.yml | cut -d ":" -f 4' 47 | AUTHOR_FROM_ACTION_YML: 48 | sh: | 49 | grep -e "^author:" action.yml | head -1 | awk -F": " '{print $2}' 50 | NAME_FROM_ACTION_YML: 51 | sh: | 52 | grep -e "^name:" action.yml | head -1 | awk -F": " '{print $2}' 53 | DESCRIPTION_FROM_ACTION_YML: 54 | sh: | 55 | grep -e "^description:" action.yml | head -1 | awk -F": " '{print $2}' 56 | LABEL_AUTHOR: '{{.LABEL_AUTHOR | default .AUTHOR_FROM_ACTION_YML}}' 57 | LABEL_NAME: '{{.LABEL_NAME | default .NAME_FROM_ACTION_YML}}' 58 | LABEL_DESCRIPTION: '{{.LABEL_DESCRIPTION | default .DESCRIPTION_FROM_ACTION_YML}}' 59 | LABEL_REPO_URL: '{{ default (printf "https://github.com/%s/%s" .GITHUB_ORG_NAME .DOCKER_IMAGE) .LABEL_REPO_URL }}' 60 | LABEL_DOCS_URL: >- 61 | {{ default (printf "https://github.com/%s/%s/blob/%s/README.md" .GITHUB_ORG_NAME .DOCKER_IMAGE 62 | .DEFAULT_BRANCH) .LABEL_DOCS_URL }} 63 | LABEL_HOMEPAGE: '{{.LABEL_HOMEPAGE | default "https://shyper.pro"}}' 64 | LABEL_VENDOR: '{{.LABEL_VENDOR | default "DevOps-Infra"}}' 65 | LABEL_LICENSE: '{{.LABEL_LICENSE | default "MIT"}}' 66 | 67 | # Build context 68 | CONTEXT: '{{.CONTEXT | default "."}}' 69 | DOCKERFILE: '{{.DOCKERFILE | default "Dockerfile"}}' 70 | PLATFORMS: '{{.PLATFORMS | default "linux/amd64,linux/arm64"}}' 71 | BUILD_DATE: 72 | sh: date -u +"%Y-%m-%dT%H:%M:%SZ" 73 | LAST_RELEASE: 74 | sh: | 75 | curl --silent "https://api.github.com/repos/{{.GITHUB_ORG_NAME}}/{{.GITHUB_REPO}}/releases/latest" | \ 76 | grep '"tag_name":' | \ 77 | {{.SED}} -E 's/.*"([^"]+)".*/\1/' 78 | VERSION_SUFFIX: 79 | sh: | 80 | if [ "${VERSION_SUFFIX+x}" = "x" ]; then 81 | printf "%s" "${VERSION_SUFFIX}" 82 | else 83 | printf "%s" "-test" 84 | fi 85 | VERSION_OVERRIDE: 86 | sh: echo "__TAKEN_FROM_ACTION_YML__" 87 | VERSION: 88 | sh: | 89 | if [ "${VERSION_OVERRIDE}" = "__TAKEN_FROM_ACTION_YML__" ]; then 90 | echo "{{.VERSION_FROM_ACTION_YML}}" 91 | elif [ -z "${VERSION_OVERRIDE}" ]; then 92 | echo "{{.LAST_RELEASE}}" 93 | else 94 | echo "${VERSION_OVERRIDE}" 95 | fi 96 | VERSION_NO_V: 97 | sh: v="{{.VERSION}}"; echo "${v#v}" 98 | MAJOR: 99 | sh: echo "{{.VERSION_NO_V}}" | awk -F\. '{print $1}' 100 | MINOR: 101 | sh: echo "{{.VERSION_NO_V}}" | awk -F\. '{print $2}' 102 | PATCH: 103 | sh: echo "{{.VERSION_NO_V}}" | awk -F\. '{print $3}' 104 | VERSION_MAJOR: v{{.MAJOR}} 105 | VERSION_MINOR: v{{.MAJOR}}.{{.MINOR}} 106 | VERSION_FULL: v{{.MAJOR}}.{{.MINOR}}.{{.PATCH}} 107 | NEXT_PATCH: 108 | sh: echo $(( {{.PATCH}} + 1 )) 109 | NEXT_MINOR: 110 | sh: echo $(( {{.MINOR}} + 1 )) 111 | NEXT_MAJOR: 112 | sh: echo $(( {{.MAJOR}} + 1 )) 113 | MAJOR_FROM_ACTION_YML: 114 | sh: echo "{{.VERSION_FROM_ACTION_YML}}" | awk -F\. '{print $1}' 115 | MINOR_FROM_ACTION_YML: 116 | sh: echo "{{.VERSION_FROM_ACTION_YML}}" | awk -F\. '{print $1"."$2}' 117 | 118 | # Git metadata 119 | GIT_SHA: 120 | sh: git rev-parse HEAD 2>/dev/null || echo 0000000000000000000000000000000000000000 121 | GIT_SHORT_SHA: 122 | sh: git rev-parse --short HEAD 2>/dev/null || echo 0000000 123 | GIT_BRANCH: 124 | sh: | 125 | if [ -n "${GITHUB_REF:-}" ]; then 126 | echo "${GITHUB_REF#refs/heads/}" 127 | else 128 | git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown" 129 | fi 130 | 131 | # Labels for http://label-schema.org/rc1/#build-time-labels 132 | # And for https://github.com/opencontainers/image-spec/blob/master/annotations.md 133 | ANNOTATIONS: >- 134 | --annotation index:org.label-schema.schema-version="1.0" 135 | --annotation index:org.label-schema.build-date="{{.BUILD_DATE}}" 136 | --annotation index:org.label-schema.name="{{.LABEL_NAME}}" 137 | --annotation index:org.label-schema.description="{{.LABEL_DESCRIPTION}}" 138 | --annotation index:org.label-schema.usage="{{.LABEL_DOCS_URL}}" 139 | --annotation index:org.label-schema.url="{{.LABEL_HOMEPAGE}}" 140 | --annotation index:org.label-schema.vcs-url="{{.LABEL_REPO_URL}}" 141 | --annotation index:org.label-schema.vcs-ref="{{.GIT_SHA}}" 142 | --annotation index:org.label-schema.vendor="{{.LABEL_VENDOR}}" 143 | --annotation index:org.label-schema.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 144 | --annotation index:org.opencontainers.image.created="{{.BUILD_DATE}}" 145 | --annotation index:org.opencontainers.image.authors="{{.LABEL_AUTHOR}}" 146 | --annotation index:org.opencontainers.image.url="{{.LABEL_HOMEPAGE}}" 147 | --annotation index:org.opencontainers.image.documentation="{{.LABEL_DOCS_URL}}" 148 | --annotation index:org.opencontainers.image.source="{{.LABEL_REPO_URL}}" 149 | --annotation index:org.opencontainers.image.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 150 | --annotation index:org.opencontainers.image.revision="{{.GIT_SHA}}" 151 | --annotation index:org.opencontainers.image.vendor="{{.LABEL_VENDOR}}" 152 | --annotation index:org.opencontainers.image.licenses="{{.LABEL_LICENSE}}" 153 | --annotation index:org.opencontainers.image.title="{{.LABEL_NAME}}" 154 | --annotation index:org.opencontainers.image.description="{{.LABEL_DESCRIPTION}}" 155 | --annotation manifest:org.label-schema.schema-version="1.0" 156 | --annotation manifest:org.label-schema.build-date="{{.BUILD_DATE}}" 157 | --annotation manifest:org.label-schema.name="{{.LABEL_NAME}}" 158 | --annotation manifest:org.label-schema.description="{{.LABEL_DESCRIPTION}}" 159 | --annotation manifest:org.label-schema.usage="{{.LABEL_DOCS_URL}}" 160 | --annotation manifest:org.label-schema.url="{{.LABEL_HOMEPAGE}}" 161 | --annotation manifest:org.label-schema.vcs-url="{{.LABEL_REPO_URL}}" 162 | --annotation manifest:org.label-schema.vcs-ref="{{.GIT_SHA}}" 163 | --annotation manifest:org.label-schema.vendor="{{.LABEL_VENDOR}}" 164 | --annotation manifest:org.label-schema.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 165 | --annotation manifest:org.opencontainers.image.created="{{.BUILD_DATE}}" 166 | --annotation manifest:org.opencontainers.image.authors="{{.LABEL_AUTHOR}}" 167 | --annotation manifest:org.opencontainers.image.url="{{.LABEL_HOMEPAGE}}" 168 | --annotation manifest:org.opencontainers.image.documentation="{{.LABEL_DOCS_URL}}" 169 | --annotation manifest:org.opencontainers.image.source="{{.LABEL_REPO_URL}}" 170 | --annotation manifest:org.opencontainers.image.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 171 | --annotation manifest:org.opencontainers.image.revision="{{.GIT_SHA}}" 172 | --annotation manifest:org.opencontainers.image.vendor="{{.LABEL_VENDOR}}" 173 | --annotation manifest:org.opencontainers.image.licenses="{{.LABEL_LICENSE}}" 174 | --annotation manifest:org.opencontainers.image.title="{{.LABEL_NAME}}" 175 | --annotation manifest:org.opencontainers.image.description="{{.LABEL_DESCRIPTION}}" 176 | LABELS: >- 177 | --label org.label-schema.schema-version="1.0" 178 | --label org.label-schema.build-date="{{.BUILD_DATE}}" 179 | --label org.label-schema.name="{{.LABEL_NAME}}" 180 | --label org.label-schema.description="{{.LABEL_DESCRIPTION}}" 181 | --label org.label-schema.usage="{{.LABEL_DOCS_URL}}" 182 | --label org.label-schema.url="{{.LABEL_HOMEPAGE}}" 183 | --label org.label-schema.vcs-url="{{.LABEL_REPO_URL}}" 184 | --label org.label-schema.vcs-ref="{{.GIT_SHA}}" 185 | --label org.label-schema.vendor="{{.LABEL_VENDOR}}" 186 | --label org.label-schema.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 187 | --label org.opencontainers.image.created="{{.BUILD_DATE}}" 188 | --label org.opencontainers.image.authors="{{.LABEL_AUTHOR}}" 189 | --label org.opencontainers.image.url="{{.LABEL_HOMEPAGE}}" 190 | --label org.opencontainers.image.documentation="{{.LABEL_DOCS_URL}}" 191 | --label org.opencontainers.image.source="{{.LABEL_REPO_URL}}" 192 | --label org.opencontainers.image.version="{{.VERSION_FULL}}{{.VERSION_SUFFIX}}" 193 | --label org.opencontainers.image.revision="{{.GIT_SHA}}" 194 | --label org.opencontainers.image.vendor="{{.LABEL_VENDOR}}" 195 | --label org.opencontainers.image.licenses="{{.LABEL_LICENSE}}" 196 | --label org.opencontainers.image.title="{{.LABEL_NAME}}" 197 | --label org.opencontainers.image.description="{{.LABEL_DESCRIPTION}}" 198 | TAGS: >- 199 | --tag {{.DOCKER_NAME}}:{{.VERSION_FULL}}{{.VERSION_SUFFIX}} 200 | --tag {{.DOCKER_NAME}}:{{.VERSION_MINOR}}{{.VERSION_SUFFIX}} 201 | --tag {{.DOCKER_NAME}}:{{.VERSION_MAJOR}}{{.VERSION_SUFFIX}} 202 | --tag {{.DOCKER_NAME}}:latest{{.VERSION_SUFFIX}} 203 | --tag {{.GHRC_NAME}}:{{.VERSION_FULL}}{{.VERSION_SUFFIX}} 204 | --tag {{.GHRC_NAME}}:{{.VERSION_MINOR}}{{.VERSION_SUFFIX}} 205 | --tag {{.GHRC_NAME}}:{{.VERSION_MAJOR}}{{.VERSION_SUFFIX}} 206 | --tag {{.GHRC_NAME}}:latest{{.VERSION_SUFFIX}} 207 | DOCKER_BUILD_START: 208 | sh: | 209 | if docker buildx version >/dev/null 2>&1; then 210 | echo "docker buildx build --platform {{.PLATFORMS}}" 211 | else 212 | echo "docker build" 213 | fi 214 | DOCKER_BUILD_FINISH: '{{.ANNOTATIONS}} {{.LABELS}} {{.TAGS}} --file={{.DOCKERFILE}} .' 215 | -------------------------------------------------------------------------------- /Taskfile.cicd.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | silent: true 4 | 5 | vars: 6 | PR_TEMPLATE: https://raw.githubusercontent.com/devops-infra/.github/master/PULL_REQUEST_TEMPLATE.md 7 | TEMPLATE_REPO_BASE_URL: https://raw.githubusercontent.com/devops-infra/template-action/refs/heads/master 8 | 9 | tasks: 10 | pre-commit: 11 | desc: Run all pre-commit hooks 12 | cmds: 13 | - pre-commit run --all-files 14 | 15 | pre-commit:install: 16 | desc: Install pre-commit hooks 17 | cmds: 18 | - pre-commit install 19 | 20 | lint: 21 | desc: Run all linters (Dockerfile, shell scripts, workflows, YAML) 22 | cmds: 23 | - task: lint:actionlint 24 | - task: lint:hadolint 25 | - task: lint:shellcheck 26 | - task: lint:yamllint 27 | 28 | lint:actionlint: 29 | desc: Lint GitHub Actions workflows with actionlint 30 | cmds: 31 | - | 32 | echo "▶️ Running actionlint..." 33 | set +e 34 | docker run --rm -i -v "$PWD:/work" -w /work rhysd/actionlint:latest -color 35 | rc=$? 36 | set -e 37 | if [ "$rc" -eq 0 ]; then 38 | echo "✅ actionlint passed" 39 | else 40 | echo "❌ actionlint failed" 41 | exit $rc 42 | fi 43 | 44 | lint:hadolint: 45 | desc: Lint Dockerfile with hadolint 46 | cmds: 47 | - | 48 | echo "▶️ Running hadolint..." 49 | set +e 50 | docker run --rm -i -v "$PWD:/work" -w /work hadolint/hadolint:latest-debian < Dockerfile 51 | rc=$? 52 | set -e 53 | if [ "$rc" -eq 0 ]; then 54 | echo "✅ hadolint passed" 55 | else 56 | echo "❌ hadolint failed" 57 | exit $rc 58 | fi 59 | 60 | lint:shellcheck: 61 | desc: Lint shell scripts with shellcheck 62 | cmds: 63 | - | 64 | echo "▶️ Running shellcheck..." 65 | set +e 66 | docker run --rm -i -v "$PWD:/work" -w /work koalaman/shellcheck:stable -x -S style entrypoint.sh 67 | rc=$? 68 | set -e 69 | if [ "$rc" -eq 0 ]; then 70 | echo "✅ shellcheck passed" 71 | else 72 | echo "❌ shellcheck failed" 73 | exit $rc 74 | fi 75 | 76 | lint:yamllint: 77 | desc: Lint YAML files with yamllint 78 | cmds: 79 | - | 80 | echo "▶️ Running yamllint..." 81 | set +e 82 | docker run --rm -i -v "$PWD:/work" -w /work cytopia/yamllint -c .yamllint.yml . 83 | rc=$? 84 | set -e 85 | if [ "$rc" -eq 0 ]; then 86 | echo "✅ yamllint passed" 87 | else 88 | echo "❌ yamllint failed" 89 | exit $rc 90 | fi 91 | 92 | version:get: 93 | desc: Get current version 94 | cmds: 95 | - echo "{{.VERSION}}" 96 | 97 | version:set: 98 | desc: Update version in README.md and action.yml 99 | cmds: 100 | - | 101 | # check if VERSION if different than VERSION_FROM_ACTION_YML 102 | if [ "{{.VERSION}}" = "{{.VERSION_FROM_ACTION_YML}}" ]; then 103 | echo "❌ ERROR: VERSION is same as VERSION_FROM_ACTION_YML ({{.VERSION}})" 104 | exit 1 105 | fi 106 | - echo Updating full version from {{.VERSION_FROM_ACTION_YML}} to {{.VERSION}} 107 | - echo Updating minor version from {{.MINOR_FROM_ACTION_YML}} to {{.VERSION_MINOR}} 108 | - echo Updating major version from {{.MAJOR_FROM_ACTION_YML}} to {{.VERSION_MAJOR}} 109 | - "{{.SED}} -i 's#{{.DOCKER_NAME}}:{{.VERSION_FROM_ACTION_YML}}#{{.DOCKER_NAME}}:{{.VERSION}}#g' action.yml" 110 | - "{{.SED}} -i 's#{{.DOCKER_NAME}}@{{.VERSION_FROM_ACTION_YML}}#{{.DOCKER_NAME}}@{{.VERSION}}#g' README.md" 111 | - "{{.SED}} -i 's#{{.GITHUB_NAME}}@{{.VERSION_FROM_ACTION_YML}}#{{.GITHUB_NAME}}@{{.VERSION}}#g' README.md" 112 | - "{{.SED}} -i 's#{{.DOCKER_NAME}}@{{.MINOR_FROM_ACTION_YML}}#{{.DOCKER_NAME}}@{{.VERSION_MINOR}}#g' README.md" 113 | - "{{.SED}} -i 's#{{.GITHUB_NAME}}@{{.MINOR_FROM_ACTION_YML}}#{{.GITHUB_NAME}}@{{.VERSION_MINOR}}#g' README.md" 114 | - "{{.SED}} -i 's#{{.DOCKER_NAME}}@{{.MAJOR_FROM_ACTION_YML}}#{{.DOCKER_NAME}}@{{.VERSION_MAJOR}}#g' README.md" 115 | - "{{.SED}} -i 's#{{.GITHUB_NAME}}@{{.MAJOR_FROM_ACTION_YML}}#{{.GITHUB_NAME}}@{{.VERSION_MAJOR}}#g' README.md" 116 | 117 | version:update:patch: 118 | desc: Increment patch version (e.g., 1.2.3 -> 1.2.4) 119 | cmds: 120 | - task version:set VERSION=v{{.MAJOR}}.{{.MINOR}}.{{.NEXT_PATCH}} 121 | 122 | version:update:minor: 123 | desc: Increment minor version (e.g., 1.2.3 -> 1.3.0) 124 | cmds: 125 | - task version:set VERSION=v{{.MAJOR}}.{{.NEXT_MINOR}}.0 126 | 127 | version:update:major: 128 | desc: Increment major version (e.g., 1.2.3 -> 2.0.0) 129 | cmds: 130 | - task version:set VERSION=v{{.NEXT_MAJOR}}.0.0 131 | 132 | version:tag-release: 133 | internal: true 134 | desc: Create set of git tags 135 | cmds: 136 | - | 137 | set -eu 138 | if (set -o | grep -q pipefail) 2>/dev/null; then set -o pipefail; fi 139 | 140 | REMOTE='origin' 141 | FULL='{{.VERSION_FULL}}' 142 | MINOR='{{.VERSION_MINOR}}' 143 | MAJOR='{{.VERSION_MAJOR}}' 144 | 145 | # Validate vX.Y.Z 146 | if ! printf "%s" "$FULL" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then 147 | echo "❌ ERROR: VERSION '$FULL' must match vX.Y.Z" >&2 148 | exit 1 149 | fi 150 | 151 | tag_sha() { git rev-parse "refs/tags/$1" 2>/dev/null || true; } 152 | remote_tag_sha() { git ls-remote --tags "$REMOTE" "refs/tags/$1" 2>/dev/null | awk '{print $1}' || true; } 153 | 154 | echo "ℹ️ INFO: Tags - Full: $FULL | Minor: $MINOR | Major: $MAJOR" 155 | 156 | # Full tag: must NOT exist on remote; fail fast if it does 157 | full_remote_sha="$(remote_tag_sha "$FULL")" 158 | if [ -n "$full_remote_sha" ]; then 159 | echo "❌ ERROR: Full tag '$FULL' already exists on remote; aborting" >&2 160 | exit 1 161 | fi 162 | 163 | # Create full tag locally (if missing) and push 164 | if git rev-parse --quiet --verify "refs/tags/$FULL" >/dev/null 2>&1; then 165 | echo "ℹ️ INFO: Full tag '$FULL' exists locally but not on remote; pushing" 166 | else 167 | echo "ℹ️ INFO: Creating full tag '$FULL'" 168 | git tag --annotate "$FULL" --message "$FULL" 169 | fi 170 | git push "$REMOTE" "refs/tags/$FULL" 171 | echo "✅ OK: Pushed full tag '$FULL'" 172 | 173 | # Minor tag: create or update 174 | git tag --force --annotate "$MINOR" --message "$FULL" 175 | minor_local_sha="$(tag_sha "$MINOR")" 176 | minor_remote_sha="$(remote_tag_sha "$MINOR")" 177 | if [ -z "$minor_remote_sha" ]; then 178 | git push "$REMOTE" "refs/tags/$MINOR" 179 | echo "✅ OK: Created and pushed minor tag '$MINOR' -> $minor_local_sha" 180 | else 181 | if [ "$minor_local_sha" != "$minor_remote_sha" ]; then 182 | echo "⚠️ WARN: Updating remote minor tag '$MINOR' to $minor_local_sha (was $minor_remote_sha)" 183 | git push --force "$REMOTE" "refs/tags/$MINOR" 184 | else 185 | echo "ℹ️ INFO: Minor tag '$MINOR' already up-to-date" 186 | fi 187 | fi 188 | 189 | # Major tag: create or update 190 | git tag --force --annotate "$MAJOR" --message "$FULL" 191 | major_local_sha="$(tag_sha "$MAJOR")" 192 | major_remote_sha="$(remote_tag_sha "$MAJOR")" 193 | if [ -z "$major_remote_sha" ]; then 194 | git push "$REMOTE" "refs/tags/$MAJOR" 195 | echo "✅ OK: Created and pushed major tag '$MAJOR' -> $major_local_sha" 196 | else 197 | if [ "$major_local_sha" != "$major_remote_sha" ]; then 198 | echo "⚠️ WARN: Updating remote major tag '$MAJOR' to $major_local_sha (was $major_remote_sha)" 199 | git push --force "$REMOTE" "refs/tags/$MAJOR" 200 | else 201 | echo "ℹ️ INFO: Major tag '$MAJOR' already up-to-date" 202 | fi 203 | fi 204 | 205 | git:get-pr-template: 206 | desc: Get pull request template 207 | cmds: 208 | - mkdir -p .tmp 209 | - curl -LsS {{.PR_TEMPLATE}} -o .tmp/PULL_REQUEST_TEMPLATE.md 210 | 211 | git:set-config: 212 | desc: Set git user config 213 | cmds: 214 | - git config user.name "github-actions[bot]" 215 | - git config user.email "github-actions[bot]@users.noreply.github.com" 216 | 217 | sync:all: 218 | desc: Sync all files with template-action 219 | cmds: 220 | - task sync:configs 221 | - task sync:ignores 222 | - task sync:taskfiles 223 | - task sync:workflows 224 | 225 | sync:configs: 226 | desc: Sync configuration files with template-action 227 | cmds: 228 | - | 229 | echo "▶️ Syncing configuration files from template-action..." 230 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.editorconfig -o ./.editorconfig 231 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.hadolint.yaml -o ./.hadolint.yaml 232 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.pre-commit-config.yaml -o ./.pre-commit-config.yaml 233 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.shellcheckrc -o ./.shellcheckrc 234 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.yamllint.yml -o ./.yamllint.yml 235 | git add .editorconfig .hadolint.yaml .pre-commit-config.yaml .shellcheckrc .yamllint.yml 236 | echo "✅ Synced configuration files" 237 | 238 | sync:ignores: 239 | desc: Sync ignore files with template-action 240 | cmds: 241 | - | 242 | echo "▶️ Syncing ignore files from template-action..." 243 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.gitignore -o ./.gitignore 244 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.dockerignore -o ./.dockerignore 245 | git add .gitignore .dockerignore 246 | echo "✅ Synced ignore files" 247 | 248 | sync:taskfiles: 249 | desc: Sync Taskfiles with template-action 250 | cmds: 251 | - | 252 | echo "▶️ Syncing Taskfiles from template-action..." 253 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/Taskfile.yml -o ./Taskfile.yml 254 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/Taskfile.cicd.yml -o ./Taskfile.cicd.yml 255 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/Taskfile.docker.yml -o ./Taskfile.docker.yml 256 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/Taskfile.variables.yml -o ./Taskfile.variables.yml 257 | git add Taskfile*.yml 258 | echo "✅ Synced Taskfiles" 259 | 260 | sync:workflows: 261 | desc: Sync GitHub workflows with template-action 262 | cmds: 263 | - | 264 | echo "▶️ Syncing GitHub workflows from template-action..." 265 | mkdir -p .github/workflows 266 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/workflows/auto-create-pull-request.yml \ 267 | -o ./.github/workflows/auto-create-pull-request.yml 268 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/workflows/auto-create-release.yml \ 269 | -o ./.github/workflows/auto-create-release.yml 270 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/workflows/cron-check-dependencies.yml \ 271 | -o ./.github/workflows/cron-check-dependencies.yml 272 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/workflows/manual-sync-common-files.yml \ 273 | -o ./.github/workflows/manual-sync-common-files.yml 274 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/workflows/manual-update-version.yml \ 275 | -o ./.github/workflows/manual-update-version.yml 276 | curl -sL {{.TEMPLATE_REPO_BASE_URL}}/.github/dependabot.yml \ 277 | -o ./.github/dependabot.yml 278 | git add .github/workflows/ 279 | echo "✅ Synced GitHub workflows" 280 | --------------------------------------------------------------------------------