diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..09422de --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,13 @@ +--- +name: Bug submission +about: There is a problem with the action +title: '' +labels: 'bug' +assignees: '' +--- + +**What is the problem you are experiencing? Please describe.** +A clear and concise description of what the problem you are experiencing is. + +**Paste your runner logs here** +A clear and concise description of what you want to happen. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..c357d17 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,13 @@ +--- +name: Feature request +about: I need additional functionality +title: '' +labels: 'feature' +assignees: '' +--- + +**What are you proposing we add or change? Please describe.** +A clear and concise description of what the end result of this request looks like. + +**Additional context** +Anything else to add. diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..6c852f7 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,8 @@ +version: 2 +updates: + + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..d0543da --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,21 @@ + + +# Summary of changes + + + +# Does it keep it simple? + +(**YES** / NO) + +## Breaking Changes? + +(**YES** / NO) + +## How changes have been tested?, link to your runs + +- + +## Any unknowns or heads ups? + +- diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..4e72994 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,19 @@ +name: dependency-review +on: [pull_request] + +permissions: + contents: read + +jobs: + + dependency-review: + runs-on: ubuntu-22.04 + steps: + + - uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 # v2.5.1 + with: + egress-policy: audit + + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - uses: actions/dependency-review-action@7d90b4f05fea31dde1c4a1fb3fa787e197ea93ab # v3.0.7 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..165e168 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,51 @@ +name: scorecard +on: + schedule: + - cron: '00 1 * * 1' + push: + branches: [ "master" ] + +permissions: + contents: read + +concurrency: + group: ${{ github.repository }}/${{ github.workflow }}/${{ github.ref }} + cancel-in-progress: true + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + security-events: write + id-token: write + + steps: + - uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 # v2.5.1 + with: + egress-policy: audit + + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + persist-credentials: false + + - uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0 + with: + results_file: results.sarif + results_format: sarif + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + publish_results: true + + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + - uses: github/codeql-action/upload-sarif@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4 + with: + sarif_file: results.sarif diff --git a/.github/workflows/simple.yml b/.github/workflows/simple.yml new file mode 100644 index 0000000..5ff230e --- /dev/null +++ b/.github/workflows/simple.yml @@ -0,0 +1,36 @@ +name: tag-and-release + +on: + push: + branches: + - master + +permissions: + contents: read + +concurrency: + group: ${{ github.repository }}/${{ github.workflow }}/${{ github.ref }} + cancel-in-progress: false + +jobs: + + flow: + runs-on: ubuntu-22.04 + permissions: + contents: write + steps: + - uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 # v2.5.1 + with: + egress-policy: audit + + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + fetch-depth: '0' + + - name: simple-tag-and-release + uses: sbe-arg/simple-tags-and-releases@fd774635aa3f4ea19faab61ad46a36e6c165ed24 # v0.3.2 + with: + autogenerated_notes: 'true' + version_file: 'VERSION' + env: + GH_TOKEN: ${{ github.token }} \ No newline at end of file diff --git a/.github/workflows/version-reminder.yml b/.github/workflows/version-reminder.yml new file mode 100644 index 0000000..5572257 --- /dev/null +++ b/.github/workflows/version-reminder.yml @@ -0,0 +1,69 @@ +name: version-reminder + +on: + pull_request: + types: + - opened + +permissions: + contents: read + +concurrency: + group: ${{ github.repository }}/${{ github.workflow }}/${{ github.ref }} + cancel-in-progress: true + +jobs: + + version-reminder: + name: version-reminder + runs-on: ubuntu-22.04 + timeout-minutes: 1 + permissions: + pull-requests: write + + steps: + - uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 # v2.5.1 + with: + egress-policy: audit + + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + ref: master + + - name: version-check + id: version_check + if: hashFiles('VERSION') + run: | + VERSION_FILE="VERSION" + setOutput() { + echo "${1}=${2}" >> "${GITHUB_OUTPUT}" + } + git fetch origin master + if ! git diff --unified=0 origin/master.. -- $VERSION_FILE | grep '^[+-]' | grep -i "version" >/dev/null + then + echo "remind bumping" + setOutput "remind" 'true' + else + echo "version is being BUMPED $(cat $VERSION_FILE)" + setOutput "remind" 'false' + fi + + - name: version-reminder-comment + if: hashFiles('VERSION') && steps.version_check.outputs.remind == 'true' + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const backtick = '`'; + const body = ` + ### :bulb: **Don't forget to bump the VERSION file** + + By bumping the version in ${backtick}VERSION${backtick} file, we trigger a repo TAG and repo RELEASE, else nothing happens. + `.trim(); + + github.rest.issues.createComment({ + issue_number: context.payload.pull_request.number, + owner: context.repo.owner, + repo: context.repo.repo, + body + }); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f4c94ae --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,6 @@ +## how to contribute + +- fork edit action.yml +- test locally point to your own fork +- open pr, link to runs from your fork +- explain in the pr why is keeping things simple while adding value \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..bc1456b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ + +The MIT License (MIT) + +Copyright (c) 2023 Santiago Bernhardt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 4edab2e..cc0e7b0 100644 --- a/README.md +++ b/README.md @@ -1 +1,72 @@ # simple-compose-service-updates + +## USAGE + +```yaml +with: + default_branch: 'main|master|other' # defaults to 'master' + skips: 'mongodb:6' # defaults to '', example: 'skip:1,other:3' +``` + +```yaml +name: compose-service-updates + +on: + push: + branches: + - master + +permissions: + contents: read + pull-requests: read + +jobs: + + flow: + runs-on: ubuntu-22.04 + permissions: + contents: write + pull-requests: write + steps: + + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + fetch-depth: '0' + + - name: setup-git + run: | + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + + - name: simple-compose-service-updates + uses: sbe-arg/simple-compose-service-updates@v0.1.0 # use sha pinning when possible + with: + default_branch: 'main' + skips: 'mongodb:6,postgresql-repmgr:15' # examples + env: + GH_TOKEN: ${{ github.token }} # required +``` + +## requirements: + +- your compose files must be on your repo root. +- your compose files must match '\*compose\*.yaml' or '\*compose\*.yml' +- your images in compose files must include the full registry: + - docker.io/somecompany/theimage:x.x.x + - mcr.microsoft.com/part/theimage:x.x.x + +## what for: + +- find compose services and bump them using prs + +## supported registries + +- dockerhub +- microsoft mcr +- other? open an issue or open pr + +## what does it look like + +- runs: [link](https://github.com/sbe-arg/simple-compose-service-updates/actions/workflows/simple.yml) +- releases: [link](https://github.com/sbe-arg/simple-compose-service-updates/releases) +- tags: [link](https://github.com/sbe-arg/simple-compose-service-updates/tags) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2edf3b1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,4 @@ +## Reporting a Vulnerability + +Please report (suspected) security vulnerabilities via issues **[advisory](https://github.com/sbe-arg/simple-compose-service-updates/security/advisories/new)** this allows transparent disclosure. +If the issue is valid and accepted, we will release a patch as soon as possible depending on complexity but ideally within 30 days. \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..9ff151c --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +v0.1.0 \ No newline at end of file diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..691df30 --- /dev/null +++ b/action.yml @@ -0,0 +1,21 @@ +name: 'Simple compose service updates' +description: 'Simple compose service update process for your repos' +inputs: + default_branch: + description: 'Path to file' + required: false + default: 'master' + skips: + description: 'Skip some services' + required: false + default: '' +runs: + using: composite + steps: + - name: compose service updates + shell: bash + run: | + default_branch=${{ inputs.default_branch }} + skips=${{ inputs.skips }} + + ${{ github.action_path }}/bin/pincher.sh "$default_branch" "$skips" \ No newline at end of file diff --git a/bin/pincher.sh b/bin/pincher.sh new file mode 100755 index 0000000..4e1f419 --- /dev/null +++ b/bin/pincher.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# Updates compose service versions. + +default_branch="$1" +# generate $skip_patterns +IFS=',' read -ra skip_patterns <<< "$2" + +# branching, pr, senver compare and sed logic +versions_magic() { + if [ "$latest_version_in_registry" != "$v_rematched" ] + then + skip=false + for skip_pattern in "${skip_patterns[@]}" + do + if [[ "$image:$latest_version_in_registry" == *"$skip_pattern"* ]] + then + skip=true + break + fi + done + if "$skip" + then + return + fi + echo "WARN: There is a new version [$latest_version_in_registry] for $image:$v_rematched" + # branch out and pr if changes + git checkout -B "compose/$image" + git pull origin "compose/$image" + # sed compose files on branch + for file in $(find -name '*compose*.yml' -o -name '*compose*.yaml' -type f) + do + if [[ -f "$file" ]] + then + sed -i -e "s|$image:$v_rematched|$image:$latest_version_in_registry|g" "$file" + sed -i -e "s|$image_orig:$v_rematched|$image_orig:$latest_version_in_registry|g" "$file" # hack for some of the images such as library/* nginx, prometheus, etc + else + echo "No compose file/s found." + exit 1 + fi + done + if [[ $(git status --porcelain) ]] + then + # git actions as there are commits pending + git add . + git commit -m "bump $image:$latest_version_in_registry over $v_rematched" + git push origin "compose/$image" --force + pr_number=$(gh pr list --head "compose/$image" --json number --jq '.[0].number') + if [ -n "$pr_number" ]; then + echo "Updating PR #$pr_number" + gh pr edit "$pr_number" --title "docker-compose: bump $image:$latest_version_in_registry" --body "Automated PR updated by GitHub Actions" + else + echo "No open PRs found for branch compose/$image. Creating a new PR." + gh pr create --title "docker-compose: bump $image:$latest_version_in_registry" --head "compose/$image" --base "$default_branch" --body "Automated PR created by GitHub Actions" + fi + else + echo "No porcelain, nothing to do." + fi + # go back to base branch + git checkout "$default_branch" + fi +} + +# dockerhub +versions_dockerio=$(yq '.services[].image' ./*compose*.y* | grep docker.io | sort | uniq) +for version in $versions_dockerio +do + versions_in_registry='' + latest_version_in_registry="" + + [[ $version =~ docker.io\/(.*)\:(.*) ]] + image=${BASH_REMATCH[1]} + v_rematched=${BASH_REMATCH[2]} + + # this registry has some images under library/ those do not match the compose structures + # images such as telegraf, nginx, prometheus, etc + if [[ "$image" != *'/'* ]] + then + image_orig="$image" + image="library/$image" + fi + + echo "image: $image, v: $v_rematched" + # read X number of tag pages + for page in 1 2 3 + do + versions_in_registry+="$(curl -s https://hub.docker.com/v2/repositories/$image/tags?page=$page | jq -r '.results[].name' | grep -oP '^v?[0-9]+\.[0-9]+\.[0-9]+$') " # needs the empty space after the ) before the " so gets split by tr 2 lines bellow + done + latest_version_in_registry=$(echo "$versions_in_registry" | tr ' ' "\n" | sort --version-sort | tail -n 1) + + # the magic + [ -n "$latest_version_in_registry" ] && versions_magic +done + +# microsoft mcr +versions_mcr=$(yq '.services[].image' ./*compose*.y* | grep mcr.microsoft.com | sort | uniq) +for version in $versions_mcr +do + latest_version_in_registry="" + + [[ $version =~ mcr.microsoft.com\/(.*)\:(.*) ]] + image=${BASH_REMATCH[1]} + v_rematched=${BASH_REMATCH[2]} + echo "image: $image, v: $v_rematched" + + latest_version_in_registry="$(curl -s https://mcr.microsoft.com/v2/$image/tags/list | jq -r '.tags[]' | sort -V -t. -k1,1 -k2,2 -k3,3 | grep -oP '^v?[0-9]+\.[0-9]+\.[0-9]+$' | tail -n 1)" + + # the magic + [ -n "$latest_version_in_registry" ] && versions_magic +done + +# considerations "how to edit/contribute" +# add each new registry in a separated block loop as per the existing ones +# authentication happens via env_vars in the action block if required +# follow the pattern of "latest_version_in_registry" var as as sole tag for evaluation logic following rematch +# consider the impact on all registries when modifiying the "versions_magic" function