docker #16
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: docker | |
| run-name: ${{ inputs.run-name }} | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| run-name: | |
| description: 'set run-name for workflow (multiple calls)' | |
| type: string | |
| required: false | |
| default: 'docker' | |
| release: | |
| description: 'set WORKFLOW_GITHUB_RELEASE' | |
| required: false | |
| default: 'false' | |
| readme: | |
| description: 'set WORKFLOW_GITHUB_README' | |
| required: false | |
| default: 'false' | |
| image: | |
| description: 'set IMAGE' | |
| required: false | |
| uid: | |
| description: 'set IMAGE_UID' | |
| required: false | |
| gid: | |
| description: 'set IMAGE_GID' | |
| required: false | |
| semverprefix: | |
| description: 'prefix for semver tags' | |
| required: false | |
| semversuffix: | |
| description: 'suffix for semver tags' | |
| required: false | |
| jobs: | |
| docker: | |
| runs-on: ubuntu-22.04 | |
| services: | |
| registry: | |
| image: registry:2 | |
| ports: | |
| - 5000:5000 | |
| permissions: | |
| actions: read | |
| contents: write | |
| packages: write | |
| security-events: write | |
| steps: | |
| - name: init / checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 | |
| with: | |
| ref: ${{ github.ref_name }} | |
| fetch-depth: 0 | |
| - name: init / setup environment | |
| uses: actions/github-script@62c3794a3eb6788d9a2a72b219504732c0c9a298 | |
| with: | |
| script: | | |
| const { existsSync, readFileSync } = require('node:fs'); | |
| const { resolve } = require('node:path'); | |
| const inputs = `${{ toJSON(github.event.inputs) }}`; | |
| const opt = {input:{}, dot:{}}; | |
| try{ | |
| if(inputs.length > 0){ | |
| opt.input = JSON.parse(inputs); | |
| } | |
| }catch(e){ | |
| core.warning('could not parse github.event.inputs'); | |
| } | |
| try{ | |
| const path = resolve('.json'); | |
| if(existsSync(path)){ | |
| try{ | |
| opt.dot = JSON.parse(readFileSync(path).toString()); | |
| }catch(e){ | |
| throw new Error('could not parse .json'); | |
| } | |
| }else{ | |
| throw new Error('.json does not exist'); | |
| } | |
| }catch(e){ | |
| core.setFailed(e); | |
| } | |
| const docker = { | |
| image:{ | |
| name:(opt.input?.image || opt.dot.image), | |
| arch:(opt.dot.arch || 'linux/amd64,linux/arm64'), | |
| prefix:((opt.input?.semverprefix) ? `${opt.input?.semverprefix}-` : ''), | |
| suffix:((opt.input?.semversuffix) ? `-${opt.input?.semversuffix}` : ''), | |
| description:(opt.dot?.readme?.description || ''), | |
| tags:[], | |
| }, | |
| app:{ | |
| image:opt.dot.image, | |
| name:opt.dot.name, | |
| version:opt.dot.semver.version, | |
| root:opt.dot.root, | |
| UID:(opt.input?.uid || 1000), | |
| GID:(opt.input?.gid || 1000), | |
| no_cache:new Date().getTime(), | |
| }, | |
| cache:{ | |
| registry:'localhost:5000/', | |
| } | |
| }; | |
| docker.cache.name = `${docker.image.name}:${docker.image.prefix}buildcache${docker.image.suffix}`; | |
| docker.cache.grype = `${docker.cache.registry}${docker.image.name}:${docker.image.prefix}grype${docker.image.suffix}`; | |
| docker.app.prefix = docker.image.prefix; | |
| docker.app.suffix = docker.image.suffix; | |
| // setup tags | |
| const semver = opt.dot.semver.version.split('.'); | |
| docker.image.tags.push(`${context.sha.substring(0,7)}`); | |
| if(Array.isArray(semver)){ | |
| if(semver.length >= 1) docker.image.tags.push(`${semver[0]}`); | |
| if(semver.length >= 2) docker.image.tags.push(`${semver[0]}.${semver[1]}`); | |
| if(semver.length >= 3) docker.image.tags.push(`${semver[0]}.${semver[1]}.${semver[2]}`); | |
| } | |
| if(opt.dot.semver?.stable && new RegExp(opt.dot.semver.stable, 'ig').test(docker.image.tags.join(','))) docker.image.tags.push('stable'); | |
| if(opt.dot.semver?.latest && new RegExp(opt.dot.semver.latest, 'ig').test(docker.image.tags.join(','))) docker.image.tags.push('latest'); | |
| for(let i=0; i<docker.image.tags.length; i++){ | |
| docker.image.tags[i] = `${docker.image.name}:${docker.image.prefix}${docker.image.tags[i]}${docker.image.suffix}`; | |
| } | |
| // setup build arguments | |
| const arguments = []; | |
| for(const argument in docker.app){ | |
| arguments.push(`APP_${argument.toUpperCase()}=${docker.app[argument]}`); | |
| } | |
| // export to environment | |
| core.exportVariable('DOCKER_CACHE_REGISTRY', docker.cache.registry); | |
| core.exportVariable('DOCKER_CACHE_NAME', docker.cache.name); | |
| core.exportVariable('DOCKER_CACHE_GRYPE', docker.cache.grype); | |
| core.exportVariable('DOCKER_IMAGE_NAME', docker.image.name); | |
| core.exportVariable('DOCKER_IMAGE_ARCH', docker.image.arch); | |
| core.exportVariable('DOCKER_IMAGE_TAGS', docker.image.tags.join(',')); | |
| core.exportVariable('DOCKER_IMAGE_DESCRIPTION', docker.image.description); | |
| core.exportVariable('DOCKER_IMAGE_ARGUMENTS', arguments.join("\r\n")); | |
| core.exportVariable('WORKFLOW_CREATE_RELEASE', (opt.input?.release || true)); | |
| core.exportVariable('WORKFLOW_CREATE_README', (opt.input?.readme || true)); | |
| core.exportVariable('WORKFLOW_GRYPE_FAIL_ON_SEVERITY', (opt.json?.grpye?.fail || true)); | |
| core.exportVariable('WORKFLOW_GRYPE_SEVERITY_CUTOFF', (opt.json?.grpye?.severity || 'high')); | |
| # DOCKER | |
| - name: docker / login to hub | |
| uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 | |
| with: | |
| username: 11notes | |
| password: ${{ secrets.DOCKER_TOKEN }} | |
| - name: docker / setup qemu | |
| uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a | |
| - name: docker / setup buildx | |
| uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 | |
| with: | |
| driver-opts: network=host | |
| - name: docker / build & push & tag grype | |
| id: docker-build | |
| uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d | |
| with: | |
| context: . | |
| file: arch.dockerfile | |
| push: true | |
| platforms: ${{ env.DOCKER_IMAGE_ARCH }} | |
| cache-from: type=registry,ref=${{ env.DOCKER_CACHE_NAME }} | |
| cache-to: type=registry,ref=${{ env.DOCKER_CACHE_REGISTRY }}${{ env.DOCKER_CACHE_NAME }},mode=max,compression=zstd,force-compression=true | |
| build-args: | | |
| ${{ env.DOCKER_IMAGE_ARGUMENTS }} | |
| tags: | | |
| ${{ env.DOCKER_CACHE_GRYPE }} | |
| - name: grype / scan | |
| id: grype | |
| uses: anchore/scan-action@abae793926ec39a78ab18002bc7fc45bbbd94342 | |
| with: | |
| image: ${{ env.DOCKER_CACHE_GRYPE }} | |
| fail-build: ${{ env.WORKFLOW_GRYPE_FAIL_ON_SEVERITY }} | |
| severity-cutoff: ${{ env.WORKFLOW_GRYPE_SEVERITY_CUTOFF }} | |
| output-format: 'sarif' | |
| by-cve: true | |
| cache-db: true | |
| - name: grype / fail | |
| if: failure() || steps.grype.outcome == 'failure' | |
| uses: anchore/scan-action@abae793926ec39a78ab18002bc7fc45bbbd94342 | |
| with: | |
| image: ${{ env.DOCKER_CACHE_GRYPE }} | |
| fail-build: false | |
| severity-cutoff: ${{ env.WORKFLOW_GRYPE_SEVERITY_CUTOFF }} | |
| output-format: 'table' | |
| by-cve: true | |
| cache-db: true | |
| - name: docker / build & push | |
| uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d | |
| with: | |
| context: . | |
| file: arch.dockerfile | |
| push: true | |
| sbom: true | |
| provenance: mode=max | |
| platforms: ${{ env.DOCKER_IMAGE_ARCH }} | |
| cache-from: type=registry,ref=${{ env.DOCKER_CACHE_REGISTRY }}${{ env.DOCKER_CACHE_NAME }} | |
| cache-to: type=registry,ref=${{ env.DOCKER_CACHE_NAME }},mode=max,compression=zstd,force-compression=true | |
| build-args: | | |
| ${{ env.DOCKER_IMAGE_ARGUMENTS }} | |
| tags: | | |
| ${{ env.DOCKER_IMAGE_TAGS }} | |
| # RELEASE | |
| - name: github / release / log | |
| continue-on-error: true | |
| id: git-log | |
| run: | | |
| LOCAL_LAST_TAG=$(git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`) | |
| echo "using last tag: ${LOCAL_LAST_TAG}" | |
| LOCAL_COMMITS=$(git log ${LOCAL_LAST_TAG}..HEAD --oneline) | |
| EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) | |
| echo "commits<<${EOF}" >> ${GITHUB_OUTPUT} | |
| echo "${LOCAL_COMMITS}" >> ${GITHUB_OUTPUT} | |
| echo "${EOF}" >> ${GITHUB_OUTPUT} | |
| - name: github / release / markdown | |
| if: env.WORKFLOW_CREATE_RELEASE == 'true' && steps.git-log.outcome == 'success' | |
| id: git-release | |
| uses: 11notes/action-docker-release@v1 | |
| # WHY IS THIS ACTION NOT SHA256 PINNED? SECURITY MUCH?!?!?! | |
| # --------------------------------------------------------------------------------- | |
| # the next step "github / release / create" creates a new release based on the code | |
| # in the repo. This code is not modified and can't be modified by this action. | |
| # It does create the markdown for the release, which could be abused, but to what | |
| # extend? Adding a link to a malicious repo? | |
| with: | |
| git_log: ${{ steps.git-log.outputs.commits }} | |
| - name: github / release / create | |
| if: env.WORKFLOW_CREATE_RELEASE == 'true' && steps.git-release.outcome == 'success' | |
| uses: actions/create-release@4c11c9fe1dcd9636620a16455165783b20fc7ea0 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| tag_name: ${{ github.ref }} | |
| release_name: ${{ github.ref }} | |
| body: ${{ steps.git-release.outputs.release }} | |
| draft: false | |
| prerelease: false | |
| # LICENSE | |
| - name: license / update year | |
| continue-on-error: true | |
| uses: actions/github-script@62c3794a3eb6788d9a2a72b219504732c0c9a298 | |
| with: | |
| script: | | |
| const { existsSync, readFileSync, writeFileSync } = require('node:fs'); | |
| const { resolve } = require('node:path'); | |
| const file = 'LICENSE'; | |
| try{ | |
| const path = resolve(file); | |
| if(existsSync(path)){ | |
| let license = readFileSync(file).toString(); | |
| license = license.replace(/Copyright \(c\) \d{4} /i, `Copyright (c) ${new Date().getFullYear()} `); | |
| writeFileSync(path, license); | |
| }else{ | |
| throw new Error(`file ${file} does not exist`); | |
| } | |
| }catch(e){ | |
| core.setFailed(e); | |
| } | |
| # README | |
| - name: github / checkout master | |
| continue-on-error: true | |
| run: | | |
| git checkout master | |
| - name: github / create README.md | |
| id: github-readme | |
| continue-on-error: true | |
| if: env.WORKFLOW_CREATE_README == 'true' && steps.docker-build.outcome == 'success' | |
| uses: 11notes/action-docker-readme@v1 | |
| # WHY IS THIS ACTION NOT SHA256 PINNED? SECURITY MUCH?!?!?! | |
| # --------------------------------------------------------------------------------- | |
| # the next step "github / commit & push" only adds the README and LICENSE as well as | |
| # compose.yaml to the repository. This does not pose a security risk if this action | |
| # would be compromised. The code of the app can't be changed by this action. Since | |
| # only the files mentioned are commited to the repo. Sure, someone could make a bad | |
| # compose.yaml, but since this serves only as an example I see no harm in that. | |
| with: | |
| sarif_file: ${{ steps.grype.outputs.sarif }} | |
| build_output_metadata: ${{ steps.docker-build.outputs.metadata }} | |
| - name: github / commit & push | |
| continue-on-error: true | |
| if: steps.github-readme.outcome == 'success' && hashFiles('README.md') != '' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add README.md | |
| if [ -f compose.yaml ]; then | |
| git add compose.yaml | |
| fi | |
| if [ -f LICENSE ]; then | |
| git add LICENSE | |
| fi | |
| git commit -m "auto update README.md" | |
| git push | |
| - name: docker / push README.md to docker hub | |
| continue-on-error: true | |
| if: steps.github-readme.outcome == 'success' && hashFiles('README_DOCKER.md') != '' | |
| uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 | |
| env: | |
| DOCKER_USER: 11notes | |
| DOCKER_PASS: ${{ secrets.DOCKER_TOKEN }} | |
| with: | |
| destination_container_repo: ${{ env.DOCKER_IMAGE_NAME }} | |
| provider: dockerhub | |
| short_description: ${{ env.DOCKER_IMAGE_DESCRIPTION }} | |
| readme_file: 'README_DOCKER.md' | |
| # REPOSITORY SETTINGS | |
| - name: github / update description and set repo defaults | |
| run: | | |
| curl --request PATCH \ | |
| --url https://api.github.com/repos/${{ github.repository }} \ | |
| --header 'authorization: Bearer ${{ secrets.REPOSITORY_TOKEN }}' \ | |
| --header 'content-type: application/json' \ | |
| --data '{ | |
| "description":"${{ env.DOCKER_IMAGE_DESCRIPTION }}", | |
| "homepage":"", | |
| "has_issues":true, | |
| "has_discussions":true, | |
| "has_projects":false, | |
| "has_wiki":false | |
| }' \ | |
| --fail |