├── .chainguard └── source.yaml ├── .github ├── dependabot.yml └── workflows │ ├── check-readme.yaml │ └── test.yaml ├── LICENSE ├── README.md ├── action.yml ├── hack └── update-inputs.sh └── testfiles ├── Dockerfile ├── Makefile ├── cloudbuild.yaml ├── k8s-job.yaml └── test-dir └── k8s-job-2.yaml /.chainguard/source.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Chainguard, Inc. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | spec: 5 | authorities: 6 | - keyless: 7 | # allow commits signed by users using GitHub or Google OIDC 8 | identities: 9 | - issuer: https://accounts.google.com 10 | - issuer: https://github.com/login/oauth 11 | - key: 12 | # allow commits signed by GitHub, e.g. the UI 13 | kms: https://github.com/web-flow.gpg 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Chainguard, Inc. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | version: 2 5 | updates: 6 | 7 | # workflows 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | groups: 13 | all: 14 | patterns: 15 | - "minor" 16 | - "patch" 17 | -------------------------------------------------------------------------------- /.github/workflows/check-readme.yaml: -------------------------------------------------------------------------------- 1 | name: Check README.md Updates 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - 'action.yml' 7 | - 'README.md' 8 | - '.github/workflows/check-readme.yaml' 9 | 10 | permissions: {} 11 | 12 | jobs: 13 | check-readme: 14 | runs-on: ubuntu-latest 15 | 16 | permissions: 17 | contents: read 18 | 19 | steps: 20 | - name: Harden the runner (Audit all outbound calls) 21 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 22 | with: 23 | egress-policy: audit 24 | 25 | - name: Checkout code 26 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 27 | 28 | - name: Create temporary README 29 | run: | 30 | cp README.md README.md.original 31 | 32 | - name: Update temporary README with current inputs/outputs 33 | shell: bash 34 | run: | 35 | ./hack/update-inputs.sh 36 | 37 | - name: Check for differences 38 | id: diff 39 | run: | 40 | if diff -q README.md README.md.original &>/dev/null; then 41 | echo "No differences found. README.md is up-to-date." 42 | echo "up_to_date=true" >> $GITHUB_OUTPUT 43 | else 44 | echo "Differences found. README.md needs to be updated." 45 | diff -u README.md.original README.md || true 46 | echo "up_to_date=false" >> $GITHUB_OUTPUT 47 | fi 48 | 49 | - name: Fail if README.md is not updated 50 | if: steps.diff.outputs.up_to_date == 'false' 51 | run: | 52 | echo "::error::README.md needs to be updated to reflect the current inputs/outputs in action.yml" 53 | echo "Please run the ./hack/update-inputs.sh script and commit the changes." 54 | exit 1 55 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Chainguard, Inc. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | name: Test 5 | 6 | on: 7 | pull_request: 8 | branches: 9 | - 'main' 10 | 11 | jobs: 12 | 13 | test: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Harden the runner (Audit all outbound calls) 18 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 19 | with: 20 | egress-policy: audit 21 | 22 | - name: Check out code 23 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 24 | 25 | - name: Run Digestabot 26 | uses: ./ 27 | id: digestabot 28 | with: 29 | create-pr: false 30 | 31 | - name: Check changes 32 | run: | 33 | if [[ $(git diff --stat) == '' ]]; then 34 | echo 'should exist changes' 35 | exit 1 36 | else 37 | exit 0 38 | fi 39 | shell: bash 40 | 41 | - name: Check json output 42 | shell: bash 43 | run: | 44 | checks=( 45 | '.updates[] | .file and .file != ""' 46 | '.updates[] | .image and .image != ""' 47 | '.updates[].digest | startswith("sha256:")' 48 | '.updates[].updated_digest | startswith("sha256:")' 49 | '.updates[] | .digest != .updated_digest' 50 | ) 51 | for check in "${checks[@]}"; do 52 | jq -e "${check}" <<<'${{ steps.digestabot.outputs.json }}' 53 | done 54 | 55 | - name: Check for makefile change with blank spaces/tabs 56 | shell: bash 57 | run: | 58 | if git diff --quiet HEAD -- testfiles/Makefile; then 59 | echo "testfiles/Makefile has not changed." 60 | exit 1 61 | fi 62 | 63 | test-working-dir: 64 | runs-on: ubuntu-latest 65 | 66 | steps: 67 | - name: Harden the runner (Audit all outbound calls) 68 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 69 | with: 70 | egress-policy: audit 71 | 72 | - name: Check out code 73 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 74 | 75 | - name: Run Digestabot 76 | uses: ./ 77 | with: 78 | create-pr: false 79 | working-dir: './testfiles/test-dir' 80 | 81 | - name: Check changes 82 | shell: bash 83 | run: | 84 | file_to_check='testfiles/test-dir/k8s-job-2.yaml' 85 | modified_files=$(git diff --name-only && git diff --cached --name-only) 86 | 87 | # Remove any duplicate entries from the list of modified files 88 | modified_files=$(echo "$modified_files" | sort | uniq) 89 | 90 | # Check if only the specified file is in the list of modified files 91 | if [ "$modified_files" == "$file_to_check" ]; then 92 | echo "Only $file_to_check has been modified" 93 | exit 0 94 | else 95 | echo "Other files have been modified or there are no changes" 96 | exit 1 97 | fi 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image Digest Update (digestabot) 2 | 3 | This action updates a image digest when using the tag+digest pattern. 4 | If the tag is mutable it will have a new digest when the tag is updated. 5 | If there is a change in the digest this action will update to the latest digest 6 | and open a PR. 7 | 8 | Given an image in the format `:@sha256:` 9 | e.g. `cgr.dev/chainguard/nginx:latest@sha256:81bed54c9e507503766c0f8f030f869705dae486f37c2a003bb5b12bcfcc713f`, digesta-bot 10 | will look up the digest of the tag on the registry and, 11 | if it doesn't match, open a PR to update it. 12 | This can be used to keep tags up-to-date whilst maintaining a reproducible build and providing an opportunity to test updates. 13 | 14 | ## Usage 15 | 16 | Basic usage: 17 | 18 | ```yaml 19 | - uses: chainguard-dev/digestabot@v1.0.2 20 | with: 21 | token: ${{ secrets.GITHUB_TOKEN }} 22 | ``` 23 | 24 | ## Scenarios 25 | 26 | Also you will need to enable the setting to allow GitHub Actions to create Pull Requests if you are not using a PAT Token 27 | 28 | ``` 29 | settings -> actions -> Allow GitHub Actions to create and approve pull requests 30 | ``` 31 | 32 | ```yaml 33 | name: Image digest update 34 | 35 | on: 36 | workflow_dispatch: 37 | schedule: 38 | # At the end of every day 39 | - cron: "0 0 * * *" 40 | 41 | jobs: 42 | image-update: 43 | name: Image digest update 44 | runs-on: ubuntu-latest 45 | 46 | permissions: 47 | contents: write # to push the updates 48 | pull-requests: write # to open Pull requests 49 | id-token: write # used to sign the commits using gitsign 50 | 51 | steps: 52 | - uses: actions/checkout@v4 53 | - uses: chainguard-dev/digestabot@v1.0.2 54 | with: 55 | token: ${{ secrets.GITHUB_TOKEN }} 56 | signoff: true # optional 57 | author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> # optional 58 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> # optional 59 | labels-for-pr: automated pr, kind/cleanup, release-note-none # optional 60 | branch-for-pr: update-digests # optional 61 | title-for-pr: Update images digests # optional 62 | description-for-pr: Update images digests # optional 63 | commit-message: Update images digests # optional 64 | ``` 65 | 66 | The `json` output describes the updates that `digestabot` has made and makes it 67 | possible to extend the functionality of the action and act on the updates in 68 | subsequent steps. 69 | 70 | The schema of the output is described in [`action.yml`](action.yml). 71 | 72 | ```yaml 73 | # Run digestabot 74 | - uses: chainguard-dev/digestabot@v1 75 | id: digestabot 76 | with: 77 | token: ${{ secrets.GITHUB_TOKEN }} 78 | 79 | # Iterate over the updates in the `json` output 80 | - shell: bash 81 | run: | 82 | while read -r update; do 83 | updated_image=$(jq -r '.image + "@" + .updated_digest' <<<"${update}") 84 | 85 | echo "Do something with ${updated_image} here." 86 | done < <(jq -c '.updates // [] | .[]' <<<'${{ steps.digestabot.outputs.json }}') 87 | ``` 88 | 89 | ## File examples 90 | 91 | Here are some examples of files that digestabot can update: 92 | 93 | - `.ko.yaml`: 94 | 95 | ```yaml 96 | defaultBaseImage: cgr.dev/chainguard/kubectl:latest-dev@sha256:d5f340d044438351413d6cb110f6f8a2abc45a7149aa53e6ade719f069fc3b0a 97 | ``` 98 | 99 | - any Kubernetes manifest with an image field e.g: Job: 100 | 101 | ```yaml 102 | apiVersion: batch/v1 103 | kind: Job 104 | metadata: 105 | namespace: default 106 | name: myjob 107 | spec: 108 | template: 109 | spec: 110 | restartPolicy: Never 111 | initContainers: 112 | - image: cgr.dev/chainguard/cosign:latest-dev@sha256:09653ac03c1ac1502c3e3a8831ee79252414e4d659b423b71fb7ed8b097e9c88 113 | ... 114 | ``` 115 | 116 | - Dockerfile: 117 | 118 | ``` 119 | FROM cgr.dev/chainguard/busybox:latest@sha256:257157f6c6aa88dd934dcf6c2f140e42c2653207302788c0ed3bebb91c5311e1 120 | ``` 121 | 122 | - Kustomizations: 123 | 124 | ``` 125 | apiVersion: kustomize.config.k8s.io/v1beta1 126 | kind: Kustomization 127 | resources: 128 | - "https://github.com/cert-manager/cert-manager/releases/download/v1.11.1/cert-manager.yaml" 129 | patchesJSON6902: 130 | - target: 131 | group: apps 132 | version: v1 133 | kind: Deployment 134 | name: cert-manager 135 | patch: |- 136 | - op: replace 137 | path: /spec/template/spec/containers/0/image 138 | value: cgr.dev/chainguard/cert-manager-controller:1.11.1@sha256:819a8714fc52fe3ecf3d046ba142e02ce2a95d1431b7047b358d23df6759de6c 139 | ... 140 | ``` 141 | 142 | ## Inputs / Outputs 143 | 144 | 145 | ### Inputs 146 | 147 | | Name | Description | Default | 148 | |------|-------------|--------| 149 | | `working-dir` | Working directory to run the digestabot, to run in a specific path, if not set will run from the root | `.` | 150 | | `token` | GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT) | `${{ github.token }}` | 151 | | `signoff` | Add `Signed-off-by` line by the committer at the end of the commit log message. | `false` | 152 | | `author` | The author name and email address in the format `Display Name `. Defaults to the user who triggered the workflow run. | `${{ github.actor }} <${{ github.actor_id }}+${{...` | 153 | | `committer` | The committer name and email address in the format `Display Name `. Defaults to the GitHub Actions bot user. | `github-actions[bot] <41898282+github-actions[bo...` | 154 | | `labels-for-pr` | A comma or newline separated list of labels to be used in the pull request. | `automated pr, kind/cleanup, release-note-none` | 155 | | `branch-for-pr` | The pull request branch name. | `update-digests` | 156 | | `title-for-pr` | The title of the pull request. | `Update images digests` | 157 | | `description-for-pr` | The description of the pull request. | `Update images digests ...` | 158 | | `commit-message` | The message to use when committing changes. | `Update images digests` | 159 | | `create-pr` | Create a PR or just keep the changes locally. | `true` | 160 | | `use-gitsign` | Use gitsign to sign commits. | `true` | 161 | 162 | ### Outputs 163 | 164 | | Name | Description | 165 | |------|-------------| 166 | | `pull_request_number` | Pull Request Number | 167 | | `json` | The changes made by this action, in JSON format. Contains information about updated files, images, and digests. | 168 | 169 | > **Note:** For complete details on inputs and outputs, please refer to the [action.yml](./action.yml) file. 170 | 171 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Chainguard, Inc. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | name: 'Update the image digest' 5 | description: 'Update the image digest when have a mutating tag' 6 | 7 | inputs: 8 | working-dir: 9 | description: Working directory to run the digestabot, to run in a specific path, if not set will run from the root 10 | required: false 11 | default: . 12 | token: 13 | description: 'GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT)' 14 | required: true 15 | default: ${{ github.token }} 16 | signoff: 17 | description: 'Add `Signed-off-by` line by the committer at the end of the commit log message.' 18 | default: false 19 | author: 20 | description: > 21 | The author name and email address in the format `Display Name `. 22 | Defaults to the user who triggered the workflow run. 23 | default: '${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>' 24 | committer: 25 | description: > 26 | The committer name and email address in the format `Display Name `. 27 | Defaults to the GitHub Actions bot user. 28 | default: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>' 29 | labels-for-pr: 30 | description: 'A comma or newline separated list of labels to be used in the pull request.' 31 | default: automated pr, kind/cleanup, release-note-none 32 | branch-for-pr: 33 | description: 'The pull request branch name.' 34 | default: 'update-digests' 35 | title-for-pr: 36 | description: 'The title of the pull request.' 37 | default: 'Update images digests' 38 | description-for-pr: 39 | description: 'The description of the pull request.' 40 | default: | 41 | Update images digests 42 | 43 | ```release-note 44 | NONE 45 | ``` 46 | commit-message: 47 | description: 'The message to use when committing changes.' 48 | default: 'Update images digests' 49 | create-pr: 50 | description: 'Create a PR or just keep the changes locally.' 51 | default: true 52 | use-gitsign: 53 | description: 'Use gitsign to sign commits.' 54 | default: 'true' 55 | 56 | outputs: 57 | pull_request_number: 58 | description: "Pull Request Number" 59 | value: ${{ steps.pull_request.outputs.pull-request-number }} 60 | json: 61 | description: | 62 | The changes made by this action, in json format. 63 | 64 | The output follows this structure: 65 | 66 | ``` 67 | { 68 | "updates": [ 69 | { 70 | "file": ".ko.yaml", 71 | "image": "cgr.dev/chainguard/kubectl:latest-dev", 72 | "digest": "sha256:d5f340d044438351413d6cb110f6f8a2abc45a7149aa53e6ade719f069fc3b0a", 73 | "updated_digest": "sha256:cd90036b16413eed13818500ae837e6e7710a59c74144c32e1e90b0e59d22efc" 74 | } 75 | ] 76 | } 77 | ``` 78 | value: ${{ steps.update_files.outputs.json }} 79 | 80 | runs: 81 | using: "composite" 82 | steps: 83 | - uses: imjasonh/setup-crane@31b88efe9de28ae0ffa220711af4b60be9435f6e # v0.4 84 | 85 | - shell: bash 86 | id: update_files 87 | run: | 88 | # disable the errexit github enable that by default 89 | set +o errexit 90 | json='{}' 91 | while IFS= read -r -d '' file; do 92 | if [[ "$file" == *testdata* ]]; then 93 | echo "Skipping testdata ${file}" 94 | continue 95 | fi 96 | 97 | # Extract all image references and their digests 98 | mapfile -t image_lines < <(grep -i -E '[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*@sha256:[a-z0-9]+' "$file" || true) 99 | 100 | for line in "${image_lines[@]}"; do 101 | image=$(echo "$line" | cut -d @ -f1 | rev | cut -d = -f1 | cut -d ' ' -f1 | cut -d '"' -f1 | rev | sed -e "s/^docker:\/\///" | tr -d '' | tr -d '\t') 102 | digest=$(echo "$line"| cut -d @ -f2 | cut -d ' ' -f1 | cut -d '"' -f1 | tr -d '' | tr -d '\t') 103 | 104 | if [[ "$image" != *":"* ]]; then 105 | echo "Image $image in file $file does not have a tag, ignoring..." 106 | continue 107 | fi 108 | if [[ "$image" == *\.local:* ]]; then 109 | echo "Skipping local registry image $image" 110 | continue 111 | fi 112 | 113 | echo "Processing $image in file $file" 114 | 115 | updated_digest= 116 | crane digest "$image" > digest.log 2> logerror.txt 117 | if [ $? -eq 0 ]; then 118 | updated_digest=$(cat digest.log) 119 | else 120 | ERRMSG="Failed to retrieve digest info for $image" 121 | echo "$ERRMSG" 122 | echo "$ERRMSG" >> "$GITHUB_STEP_SUMMARY" 123 | cat logerror.txt >> "$GITHUB_STEP_SUMMARY" 124 | fi 125 | rm -f logerror.txt 126 | rm -f digest.log 127 | 128 | if [ "$updated_digest" != "$digest" ] && [ -n "$updated_digest" ]; then 129 | echo "Digest $digest for image $image is different, new digest is $updated_digest, updating..." 130 | sed -i -e "s|$image@$digest|$image@$updated_digest|g" "$file" 131 | json=$(jq -c \ 132 | --arg file "${file}" \ 133 | --arg image "${image}" \ 134 | --arg digest "${digest}" \ 135 | --arg updated_digest "${updated_digest}" \ 136 | '.updates += [{file: $file, image: $image, digest: $digest, updated_digest: $updated_digest}]' <<<"${json}") 137 | fi 138 | done 139 | done < <(find "${{ inputs.working-dir }}" -type f \( -name "*.yaml" -o -name "*.yml" -o -name "Dockerfile*" -o -name "Makefile*" -o -name "*.sh" -o -name "*.tf" -o -name "*.tfvars" \) -print0) 140 | 141 | echo "json=${json}" >> "${GITHUB_OUTPUT}" 142 | 143 | - name: Check workspace 144 | id: create_pr_update 145 | env: 146 | CREATE_PR: ${{ inputs.create-pr }} 147 | shell: bash 148 | run: | 149 | git diff --stat 150 | echo "create_pr_update=false" >> $GITHUB_OUTPUT 151 | if [[ $(git diff --stat) != '' ]] && [[ "${CREATE_PR}" == 'true' ]]; then 152 | echo "create_pr_update=true" >> $GITHUB_OUTPUT 153 | echo "diff<> "${GITHUB_OUTPUT}" 154 | git diff >> "${GITHUB_OUTPUT}" 155 | echo "EOF" >> "${GITHUB_OUTPUT}" 156 | fi 157 | 158 | # Configure gitsign for signed commits 159 | - uses: chainguard-dev/actions/setup-gitsign@57cb0b7560d9b9b081c15ac5ef689f73f4dda03e # main branch as of 2024-08-02 160 | if: ${{ steps.create_pr_update.outputs.create_pr_update == 'true' && inputs.use-gitsign == 'true' }} 161 | 162 | - name: Create Pull Request 163 | uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5 164 | if: ${{ steps.create_pr_update.outputs.create_pr_update == 'true' }} 165 | id: pull_request 166 | with: 167 | token: ${{ inputs.token }} 168 | commit-message: ${{ inputs.commit-message }} 169 | title: ${{ inputs.title-for-pr }} 170 | body: | 171 | ${{ inputs.description-for-pr }} 172 | 173 | ## Changes 174 |
175 | 176 | ```diff 177 | ${{ steps.create_pr_update.outputs.diff }} 178 | ``` 179 | 180 |
181 | labels: ${{ inputs.labels-for-pr }} 182 | branch: ${{ inputs.branch-for-pr }} 183 | signoff: ${{ inputs.signoff }} 184 | committer: ${{ inputs.committer }} 185 | author: ${{ inputs.author }} 186 | sign-commits: ${{ inputs.use-gitsign != 'true' }} # Sign commits with github if gitsign is not configured 187 | delete-branch: true 188 | -------------------------------------------------------------------------------- /hack/update-inputs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Path to your action.yml file and README.md 4 | ACTION_YML="action.yml" 5 | README_MD="README.md" 6 | 7 | # Check if yq is installed 8 | if ! command -v yq &> /dev/null; then 9 | echo "Error: yq is not installed. Please install it first." 10 | exit 1 11 | fi 12 | 13 | # Generate table for inputs 14 | generate_inputs_table() { 15 | local table="| Name | Description | Default |\n" 16 | table+="|------|-------------|--------|\n" 17 | 18 | # Get all input names 19 | local input_names= 20 | input_names=$(yq e '.inputs | keys | .[]' "${ACTION_YML}") 21 | 22 | for name in ${input_names}; do 23 | # Get description and properly format it 24 | local description= 25 | description=$(yq e ".inputs.${name}.description" "${ACTION_YML}") 26 | # Compress multi-line descriptions and escape pipes 27 | description=$(echo "${description}" | tr '\n' ' ' | sed 's/|/\\|/g') 28 | 29 | # Get default value 30 | local default= 31 | default=$(yq e ".inputs.${name}.default" "${ACTION_YML}") 32 | 33 | # For multi-line default values, show only first line 34 | if [[ $(echo "${default}" | wc -l) -gt 1 ]]; then 35 | local first_line= 36 | first_line=$(echo "${default}" | head -n 1 | sed 's/|/\\|/g') 37 | default="${first_line} ..." 38 | else 39 | default=$(echo "${default}" | sed 's/|/\\|/g') 40 | fi 41 | 42 | # Truncate long default values 43 | if [[ ${#default} -gt 50 ]]; then 44 | default="${default:0:47}..." 45 | fi 46 | 47 | table+="| \`${name}\` | ${description} | \`${default}\` |\n" 48 | done 49 | 50 | echo -e "${table}" 51 | } 52 | 53 | # Generate table for outputs 54 | generate_outputs_table() { 55 | local table="| Name | Description |\n" 56 | table+="|------|-------------|\n" 57 | 58 | # Get all output names 59 | local output_names= 60 | output_names=$(yq e '.outputs | keys | .[]' "${ACTION_YML}") 61 | 62 | for name in ${output_names}; do 63 | local description= 64 | description=$(yq e ".outputs.${name}.description" "${ACTION_YML}") 65 | 66 | # For complex descriptions like JSON examples, use a simplified approach 67 | if [[ "${name}" == "json" ]]; then 68 | description="The changes made by this action, in JSON format. Contains information about updated files, images, and digests." 69 | else 70 | # For regular descriptions, flatten them 71 | description=$(echo "${description}" | tr '\n' ' ' | sed 's/|/\\|/g') 72 | fi 73 | 74 | table+="| \`${name}\` | ${description} |\n" 75 | done 76 | 77 | echo -e "${table}" 78 | } 79 | 80 | # Generate tables for inputs and outputs 81 | inputs_table=$(generate_inputs_table) 82 | outputs_table=$(generate_outputs_table) 83 | 84 | # Combine the tables with headers 85 | markdown_table="### Inputs\n\n${inputs_table}\n\n### Outputs\n\n${outputs_table}\n\n> **Note:** For complete details on inputs and outputs, please refer to the [action.yml](./action.yml) file." 86 | 87 | # Check if the end placeholder exists, if not add it 88 | if ! grep -q "" "${README_MD}"; then 89 | echo "" >> "${README_MD}" 90 | echo "Added missing end placeholder to README.md" 91 | fi 92 | 93 | # Create a temporary file 94 | temp_file=$(mktemp) 95 | 96 | # Extract the content before the begin placeholder 97 | sed -n '1,//p' "${README_MD}" > "${temp_file}" 98 | 99 | # Add our markdown table 100 | echo -e "${markdown_table}" >> "${temp_file}" 101 | 102 | # Extract the content after the end placeholder 103 | sed -n '//,$p' "${README_MD}" >> "${temp_file}" 104 | 105 | # Replace the original file with the temporary file 106 | mv "${temp_file}" "${README_MD}" 107 | 108 | echo "README.md has been updated with the inputs and outputs tables." 109 | -------------------------------------------------------------------------------- /testfiles/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Chainguard, Inc. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | FROM cgr.dev/chainguard/static:latest@sha256:a8aeacbaf0a1176ab5dbcf9b73a517665d8db5e1495ba97d64c73b3821deb0d8 5 | -------------------------------------------------------------------------------- /testfiles/Makefile: -------------------------------------------------------------------------------- 1 | dev-container: 2 | docker run --privileged --rm -it \ 3 | -v "${PWD}:${PWD}" \ 4 | -v "${HOME}/.cache/wolfictl/dev-container-enterprise/root:/root" \ 5 | -v "${HOME}/.config/chainctl:/root/.config/chainctl" \ 6 | -w "${PWD}" \ 7 | ghcr.io/wolfi-dev/sdk:latest@sha256:16e05ea526a250ddb3e24167ecc6494b29961d0e5fc02b716b9eea31e82439f8 8 | 9 | # The next two targets are mostly copies from the local-wolfi and 10 | # dev-container-wolfi targets from wolfi-dev/os: 11 | # https://github.com/wolfi-dev/os/blob/main/Makefile 12 | 13 | PACKAGES_CONTAINER_FOLDER ?= /work/packages 14 | TMP_REPOSITORIES_DIR := $(shell mktemp -d) 15 | TMP_REPOSITORIES_FILE := $(TMP_REPOSITORIES_DIR)/repositories 16 | # This target spins up a docker container that is helpful for testing local 17 | # changes to the packages. It mounts the local packages folder as a read-only, 18 | # and sets up the necessary keys for you to run `apk add` commands, and then 19 | # test the packages however you see fit. 20 | local-wolfi: ${KEY} 21 | @echo "https://packages.wolfi.dev/os" > $(TMP_REPOSITORIES_FILE) 22 | @echo "https://apk.cgr.dev/chainguard-private" >> $(TMP_REPOSITORIES_FILE) 23 | @echo "https://packages.cgr.dev/extras" >> $(TMP_REPOSITORIES_FILE) 24 | @echo "$(PACKAGES_CONTAINER_FOLDER)" >> $(TMP_REPOSITORIES_FILE) 25 | @mkdir -p ${PWD}/packages 26 | docker run --rm -it \ 27 | -e HTTP_AUTH="basic:apk.cgr.dev:user:$(shell chainctl auth token --audience apk.cgr.dev)" \ 28 | --mount type=bind,source="${PWD}/packages",destination="$(PACKAGES_CONTAINER_FOLDER)",readonly \ 29 | --mount type=bind,source="${PWD}/local-melange-enterprise.rsa.pub",destination="/etc/apk/keys/local-melange-enterprise.rsa.pub",readonly \ 30 | --mount type=bind,source="$(TMP_REPOSITORIES_FILE)",destination="/etc/apk/repositories",readonly \ 31 | -w "$(PACKAGES_CONTAINER_FOLDER)" \ 32 | cgr.dev/chainguard-private/chainguard-base:latest 33 | @rm "$(TMP_REPOSITORIES_FILE)" 34 | @rmdir "$(TMP_REPOSITORIES_DIR)" 35 | 36 | dev-container-wolfi: 37 | @echo "https://packages.wolfi.dev/os" > $(TMP_REPOSITORIES_FILE) 38 | @echo "$(PACKAGES_CONTAINER_FOLDER)" >> $(TMP_REPOSITORIES_FILE) 39 | docker run --rm -it \ 40 | --mount type=bind,source="${OUT_DIR}",destination="$(OUT_LOCAL_DIR)" \ 41 | --mount type=bind,source="${OS_DIR}",destination="$(OS_LOCAL_DIR)",readonly \ 42 | --mount type=bind,source="${PWD}/packages",destination="$(PACKAGES_CONTAINER_FOLDER)",readonly \ 43 | --mount type=bind,source="${PWD}/local-melange-enterprise.rsa.pub",destination="/etc/apk/keys/local-melange-enterprise.rsa.pub",readonly \ 44 | --mount type=bind,source="$(TMP_REPOSITORIES_FILE)",destination="/etc/apk/repositories",readonly \ 45 | -w "$(PACKAGES_CONTAINER_FOLDER)" \ 46 | ghcr.io/wolfi-dev/sdk:latest@sha256:16e05ea526a250ddb3e24167ecc6494b29961d0e5fc02b716b9eea31e82439f8 47 | @rm "$(TMP_REPOSITORIES_FILE)" 48 | @rmdir "$(TMP_REPOSITORIES_DIR)" 49 | -------------------------------------------------------------------------------- /testfiles/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | source: 2 | repoSource: 3 | repoName: testrepo 4 | branchName: main 5 | steps: 6 | 7 | # Build 8 | - id: update-node 9 | name: 'cgr.dev/chainguard/node-lts:latest@sha256:00d83d15ae5777d64fa2552989f893355e7f91341396f84769db3d84902441e1' 10 | 11 | - id: update-bash 12 | name: 'cgr.dev/chainguard/bash:latest@sha256:099e4b9adb13a94e6f25d6bb9bfe69fd5ba734a615e62bb0e1efba6650c6b23d' 13 | -------------------------------------------------------------------------------- /testfiles/k8s-job.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Chainguard, Inc. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: batch/v1 5 | kind: Job 6 | metadata: 7 | namespace: test 8 | name: test 9 | spec: 10 | backoffLimit: 25 11 | template: 12 | metadata: 13 | annotations: 14 | spec: 15 | restartPolicy: Never 16 | - name: cosign 17 | image: cgr.dev/chainguard/cosign:latest@sha256:a8aeacbaf0a1176ab5dbcf9b73a517665d8db5e1495ba97d64c73b3821deb0d8 18 | -------------------------------------------------------------------------------- /testfiles/test-dir/k8s-job-2.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Chainguard, Inc. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: batch/v1 5 | kind: Job2 6 | metadata: 7 | namespace: test 8 | name: test 9 | spec: 10 | backoffLimit: 25 11 | template: 12 | metadata: 13 | annotations: 14 | spec: 15 | restartPolicy: Never 16 | - name: cosign 17 | image: cgr.dev/chainguard/cosign:latest@sha256:a8aeacbaf0a1176ab5dbcf9b73a517665d8db5e1495ba97d64c73b3821deb0d8 18 | --------------------------------------------------------------------------------