From 1820843fc82d1fa80b8f7db93bad231678e818c9 Mon Sep 17 00:00:00 2001 From: Bret Fisher Date: Mon, 2 May 2022 15:40:42 -0400 Subject: [PATCH] Updates for allhands (#3) --- .github/linters/.hadolint.yaml | 34 ++- .github/linters/.markdown-lint.yml | 8 +- .github/linters/.yaml-lint.yml | 53 +++++ .../workflows/call-local-docker-build.yaml | 82 +++++++ .github/workflows/call-super-linter.yaml | 19 +- .github/workflows/docker-build-and-push.yaml | 147 ------------- .github/workflows/reusable-docker-build.yaml | 205 ++++++++++++++++++ Dockerfile | 12 + README.md | 34 ++- templates/call-docker-build.yaml | 80 +++++++ 10 files changed, 511 insertions(+), 163 deletions(-) create mode 100644 .github/linters/.yaml-lint.yml create mode 100644 .github/workflows/call-local-docker-build.yaml delete mode 100644 .github/workflows/docker-build-and-push.yaml create mode 100644 .github/workflows/reusable-docker-build.yaml create mode 100644 Dockerfile create mode 100644 templates/call-docker-build.yaml diff --git a/.github/linters/.hadolint.yaml b/.github/linters/.hadolint.yaml index 362b1aa..889d551 100644 --- a/.github/linters/.hadolint.yaml +++ b/.github/linters/.hadolint.yaml @@ -1,3 +1,11 @@ +# README: https://github.com/hadolint/hadolint + +# Often it's a good idea to do inline disables rather that repo-wide in this file. +# Example of inline Dockerfile rules: +# hadolint ignore=DL3018 +#RUN apk add --no-cache git + +# or just ignore rules repo-wide ignored: - DL3003 #ignore that we use cd sometimes - DL3006 #image pin versions @@ -7,4 +15,28 @@ ignored: - DL3028 #gem install pin versions - DL3059 #multiple consecutive runs - DL4006 #we don't need pipefail in this - - SC2016 #we want single quotes sometimes \ No newline at end of file + - SC2016 #we want single quotes sometimes + + +# FULL TEMPLATE +# failure-threshold: string # name of threshold level (error | warning | info | style | ignore | none) +# format: string # Output format (tty | json | checkstyle | codeclimate | gitlab_codeclimate | gnu | codacy) +# ignored: [string] # list of rules +# label-schema: # See Linting Labels below for specific label-schema details +# author: string # Your name +# contact: string # email address +# created: timestamp # rfc3339 datetime +# version: string # semver +# documentation: string # url +# git-revision: string # hash +# license: string # spdx +# no-color: boolean # true | false +# no-fail: boolean # true | false +# override: +# error: [string] # list of rules +# warning: [string] # list of rules +# info: [string] # list of rules +# style: [string] # list of rules +# strict-labels: boolean # true | false +# disable-ignore-pragma: boolean # true | false +# trustedRegistries: string | [string] # registry or list of registries \ No newline at end of file diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml index aef4e33..b1f767d 100644 --- a/.github/linters/.markdown-lint.yml +++ b/.github/linters/.markdown-lint.yml @@ -1,7 +1,9 @@ # MD013/line-length - Line length MD013: - # Number of characters - line_length: 150 + # Number of characters, default is 80 + # I'm OK with long lines. All editors now have wordwrap + line_length: 9999 # Number of characters for headings heading_line_length: 100 - code_blocks: false \ No newline at end of file + # check code blocks? + code_blocks: false diff --git a/.github/linters/.yaml-lint.yml b/.github/linters/.yaml-lint.yml new file mode 100644 index 0000000..030c37f --- /dev/null +++ b/.github/linters/.yaml-lint.yml @@ -0,0 +1,53 @@ +--- +########################################### +# These are the rules used for # +# linting all the yaml files in the stack # +# NOTE: # +# You can disable line with: # +# # yamllint disable-line # +########################################### +rules: + braces: + level: warning + min-spaces-inside: 0 + max-spaces-inside: 0 + min-spaces-inside-empty: 1 + max-spaces-inside-empty: 5 + brackets: + level: warning + min-spaces-inside: 0 + max-spaces-inside: 0 + min-spaces-inside-empty: 1 + max-spaces-inside-empty: 5 + colons: + level: warning + max-spaces-before: 0 + max-spaces-after: 1 + commas: + level: warning + max-spaces-before: 0 + min-spaces-after: 1 + max-spaces-after: 1 + comments: disable + comments-indentation: disable + document-end: disable + document-start: disable + empty-lines: + level: warning + max: 2 + max-start: 0 + max-end: 0 + hyphens: + level: warning + max-spaces-after: 1 + indentation: + level: warning + spaces: consistent + indent-sequences: true + check-multi-line-strings: false + key-duplicates: enable + line-length: disable + new-line-at-end-of-file: disable + new-lines: + type: unix + trailing-spaces: disable \ No newline at end of file diff --git a/.github/workflows/call-local-docker-build.yaml b/.github/workflows/call-local-docker-build.yaml new file mode 100644 index 0000000..b89c511 --- /dev/null +++ b/.github/workflows/call-local-docker-build.yaml @@ -0,0 +1,82 @@ +--- +# THIS IS NOT A TEMPLATE. +# This is just for linting the gha-workflows repo itself. +# We call the reusable workflow from its file path. +name: Docker Build + +on: + push: + branches: + - main + # don't rebuild image if someone only edited unrelated files + paths-ignore: + - 'README.md' + - '.github/linters/**' + pull_request: + # don't rebuild image if someone only edited unrelated files + paths-ignore: + - 'README.md' + - '.github/linters/**' + +jobs: + call-docker-build: + + name: Call Docker Build + + uses: ./.github/workflows/reusable-docker-build.yaml + + permissions: + contents: read + packages: write # needed to push docker image to ghcr.io + pull-requests: write # needed to create and update comments in PRs + + secrets: + + # Only needed if with:dockerhub-enable is true below + dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} + + # Only needed if with:dockerhub-enable is true below + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} + + with: + + ### REQUIRED + ### ENABLE ONE OR BOTH REGISTRIES + ### tell docker where to push. + ### NOTE if Docker Hub is set to true, you must set secrets above and also add account/repo/tags below + dockerhub-enable: true + ghcr-enable: true + + ### REQUIRED + ### A list of the account/repo names for docker build. List should match what's enabled above + ### defaults to: + image-names: | + ${{ github.repository }} + ghcr.io/${{ github.repository }} + + ### REQUIRED set rules for tagging images, based on special action syntax: + ### https://github.com/docker/metadata-action#tags-input + ### defaults to: + tag-rules: | + type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} + type=ref,event=pr + type=ref,event=branch + type=semver,pattern={{version}} + + ### path to where docker should copy files into image + ### defaults to root of repository (.) + # context: . + + ### Dockerfile alternate name. Default is Dockerfile (relative to context path) + # file: Containerfile + + ### build stage to target, defaults to empty, which builds to last stage in Dockerfile + # target: + + ### platforms to build for, defaults to linux/amd64 + ### other options: linux/amd64,linux/arm64,linux/arm/v7 + # platforms: linux/amd64 + + ### Create a PR comment with image tags and labels + ### defaults to false + # comment-enable: false \ No newline at end of file diff --git a/.github/workflows/call-super-linter.yaml b/.github/workflows/call-super-linter.yaml index 545652d..4674256 100644 --- a/.github/workflows/call-super-linter.yaml +++ b/.github/workflows/call-super-linter.yaml @@ -1,4 +1,5 @@ --- +# template source: https://github.com/bretfisher/super-linter-workflow/blob/main/templates/call-super-linter.yaml name: Lint Code Base on: @@ -12,21 +13,25 @@ jobs: call-super-linter: name: Call Super-Linter - - # use Reusable Workflows to call my linter config remotely - # https://docs.github.com/en/actions/learn-github-actions/reusing-workflows permissions: contents: read # clone the repo to lint statuses: write #read/write to repo custom statuses - #FIXME: customize uri to point to your forked linter repository - uses: bretfisher/super-linter-workflow/.github/workflows/super-linter.yaml@main + ### use Reusable Workflows to call my workflow remotely + ### https://docs.github.com/en/actions/learn-github-actions/reusing-workflows + ### you can also call workflows from inside the same repo via file path + + #FIXME: customize uri to point to your own linter repository + uses: bretfisher/super-linter-workflow/.github/workflows/reusable-super-linter.yaml@main - # Optional settings examples + ### Optional settings examples # with: - + ### For a DevOps-focused repository. Prevents some code-language linters from running + ### defaults to false # devops-only: false + ### A regex to exclude files from linting + ### defaults to empty # filter-regex-exclude: html/.* diff --git a/.github/workflows/docker-build-and-push.yaml b/.github/workflows/docker-build-and-push.yaml deleted file mode 100644 index 7f543ee..0000000 --- a/.github/workflows/docker-build-and-push.yaml +++ /dev/null @@ -1,147 +0,0 @@ ---- -name: Docker Build and Push Image - -on: - # we want pull requests so we can build(test) but not push to image registry - pull_request: - branches: - - 'main' - # only build when important files change - paths-ignore: - - 'README.md' - - '.github/workflows/linter.yml' - - '.github/linters/**' - push: - branches: - - 'main' - # only build when important files change - paths-ignore: - - 'README.md' - - '.github/workflows/linter.yml' - - '.github/linters/**' - schedule: - # re-run monthly to keep image fresh with upstream base images - # NOTE: GH will stop cron jobs in a stale repo (60 days) - # https://docs.github.com/en/actions/managing-workflow-runs/disabling-and-enabling-a-workflow - - cron: '0 12 15 * *' - # run whenever we want! - # workflow_dispatch: - # REUSABLE WORKFLOW with INPUTS - # to keep this workflow simple, assumptions are made: - # - only able to push to Docker Hub and or GHCR - # - Image name is name of GitHub repo - # - Dockerfile is in root of repo, named 'Dockerfile' - # - Builds on PR with tag of `prNUMBER` (same tag each PR push) - # - Builds on push to main branch with tag of `latest` - # ???? what else - workflow_call: - # allow reuse of this workflow in other repos - inputs: - # TODO: allow dynamic docker hub and ghcr - dockerhub-enabled: - description: Push images to Docker Hub - - type: boolean - ghcr-enabled: - dockerhub-username: - description: Docker Hub username - required: false - type: string - context: - description: Docker context (path) to start build from - required: false - type: string - default: . - target: - description: Build stage to target - required: false - type: string - platforms: - description: Platforms to build for - required: false - type: string - # common ones: linux/amd64,linux/arm64,linux/arm/v7 - default: linux/amd64 - # TODO: does this work in calling repos? - image-names: - description: A list of the account/repo names for docker build - required: false - type: string - # the default will tag it same as repo name (for hub.docker.com) and - # ghcr.io/ - default: | - ${{ github.repository }} - ghcr.io/${{ github.repository }} - secrets: - dockerhub-token: - description: Docker Hub token - required: false - - -jobs: - build-and-push-image: - name: Build+Push - runs-on: ubuntu-latest - steps: - - - name: Checkout - uses: actions/checkout@v3 - - - # we need qemu and buildx so we can build multiple platforms later - name: Set up QEMU - id: qemu - uses: docker/setup-qemu-action@v1.2.0 - - - # BuildKit (used with `docker buildx`) is the best way to build images - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 - - - name: Login to DockerHub - if: ${{ inputs.dockerhub-enabled }} - uses: docker/login-action@v1 - with: - username: ${{ inputs.dockerhub-username }} - password: ${{ secrets.dockerhub-token }} - - - name: Login to GHCR - if: ${{ inputs.ghcr-enabled }} - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Docker meta - id: docker_meta - uses: docker/metadata-action@v3.8.0 - with: - # list of Docker images to use as base name for tags - images: ${{ inputs.image-names }} - flavor: | - latest=false - tags: | - type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=pr,prefix=pr - - - # this will build the images, once per platform, - # then push to both Docker Hub and GHCR - name: Docker Build and Push - id: docker_build_and_push - uses: docker/build-push-action@v2 - with: - platforms: ${{ inputs.platforms }} - context: ${{ inputs.context }} - target: ${{ inputs.target }} - builder: ${{ steps.buildx.outputs.name }} - # it uses github cache API for faster builds: - # https://github.com/crazy-max/docker-build-push-action/blob/master/docs/advanced/cache.md#cache-backend-api - cache-from: type=gha - cache-to: type=gha,mode=max - # for an approved pull_request, only push pr-specific tags - push: true - tags: ${{ steps.docker_meta.outputs.tags }} - labels: ${{ steps.docker_meta.outputs.labels }} - - - name: Show image digest - run: echo ${{ steps.docker_build_and_push.outputs.digest }} \ No newline at end of file diff --git a/.github/workflows/reusable-docker-build.yaml b/.github/workflows/reusable-docker-build.yaml new file mode 100644 index 0000000..987fd7e --- /dev/null +++ b/.github/workflows/reusable-docker-build.yaml @@ -0,0 +1,205 @@ +--- +name: Docker Build and Push + +on: + + # REUSABLE WORKFLOW with INPUTS + # to keep this workflow simple, assumptions are made: + # - only able to push to Docker Hub and/or GHCR (GHCR by default) + # - Builds on PR with tag of `prNUMBER` (same tag each PR push) + # - Builds on push to main branch with tag of `latest` + # - Builds on tag push with semver + + workflow_call: + # allow reuse of this workflow in other repos + inputs: + dockerhub-enable: + description: Log into Docker Hub + required: false + default: false + type: boolean + ghcr-enable: + description: Log into GHCR + required: false + default: true + type: boolean + comment-enable: + description: Create a PR comment with image tags and labels + required: false + default: false + type: boolean + context: + description: Docker context (path) to start build from + required: false + type: string + default: . + file: + description: Dockerfile to build, relative to context path + required: false + type: string + target: + description: Build stage to target + required: false + type: string + platforms: + description: Platforms to build for + required: false + type: string + # common ones: linux/amd64,linux/arm64,linux/arm/v7 + default: linux/amd64 + # TODO: does this work in calling repos? + image-names: + description: A list of the account/repo names for docker build + required: true + type: string + tag-rules: + # https://github.com/marketplace/actions/docker-metadata-action#tags-input + description: Use docker-metadata action to create tags from a key-value pair list in CSV format + required: true + type: string + default: | + type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} + type=ref,event=pr + type=ref,event=branch + type=semver,pattern={{version}} + type=raw,value=gha-${{ github.run_id }} + flavor-rules: + # https://github.com/marketplace/actions/docker-metadata-action#flavor-input + description: Three rules to (optionally) set for tag-rules, latest, prefix, and suffix + required: false + type: string + default: | + latest=false + + secrets: + dockerhub-username: + description: Docker Hub username + required: false + dockerhub-token: + description: Docker Hub token + required: false + + outputs: + image-tags: + description: "all tags from docker build" + value: ${{ jobs.build-image.outputs.image-tags }} + ghcr-tag: + description: "single-use tag for ghcr.io" + value: ${{ jobs.build-image.outputs.ghcr-tag }} + + +# permissions: GITHUB_TOKEN are better set by the **calling** workflow +# but we'll set defaults here for reference +# https://docs.github.com/en/actions/using-workflows/reusing-workflows#supported-keywords-for-jobs-that-call-a-reusable-workflow + +permissions: + contents: read + packages: write # needed to push docker image to ghcr.io + pull-requests: write # needed to create and update comments in PRs + +jobs: + build-image: + + name: Build+Push + + runs-on: ubuntu-latest + + outputs: + # all tags from docker build + image-tags: ${{ steps.docker_meta.outputs.tags }} + # only outputs the unique gha- image tag that's unique to each build + ghcr-tag: ${{ steps.ghcr-tag.outputs.tag }} + + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + # we need qemu and buildx so we can build multiple platforms later + name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + - + # BuildKit (used with `docker buildx`) is the best way to build images + name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + - + name: Login to DockerHub + if: inputs.dockerhub-enable + uses: docker/login-action@v1 + with: + username: ${{ secrets.dockerhub-username }} + password: ${{ secrets.dockerhub-token }} + - + name: Login to GHCR + if: inputs.ghcr-enable + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Docker meta + id: docker_meta + uses: docker/metadata-action@v3 + with: + # list of Docker images to use as base name for tags + images: ${{ inputs.image-names }} + flavor: ${{ inputs.flavor-rules }} + tags: ${{ inputs.tag-rules }} + - + # this will build the images, once per platform, + # then push to one or more registries (based on image list above in docker_meta) + name: Docker Build and Push + id: build_image + uses: docker/build-push-action@v2 + with: + platforms: ${{ inputs.platforms }} + context: ${{ inputs.context }} + file: ${{ inputs.file }} + target: ${{ inputs.target }} + builder: ${{ steps.buildx.outputs.name }} + # it uses github cache API for faster builds: + # https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#cache-backend-api + cache-from: type=gha + cache-to: type=gha,mode=max + push: true + tags: ${{ steps.docker_meta.outputs.tags }} + labels: ${{ steps.docker_meta.outputs.labels }} + - + # If PR, put image tags in the PR comments + # from https://github.com/marketplace/actions/create-or-update-comment + name: Find comment for image tags + uses: peter-evans/find-comment@v1 + if: github.event_name == 'pull_request' && inputs.comment-enable + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Docker image tag(s) pushed + + # If PR, put image tags in the PR comments + - name: Create or update comment for image tags + uses: peter-evans/create-or-update-comment@v1 + if: github.event_name == 'pull_request' && inputs.comment-enable + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + Docker image tag(s) pushed: + ```text + ${{ steps.docker_meta.outputs.tags }} + ``` + + Labels added to images: + ```text + ${{ steps.docker_meta.outputs.labels }} + ``` + edit-mode: replace + + - name: Find the gha-run-based image tag we just pushed to ghcr.io + id: ghcr-tag + run: | + echo '::echo::on' + echo "::set-output name=tag::gha-${{ github.run_id }}" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4cfdf42 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +# sample dockerfile for testing call-docker-build.yaml +FROM alpine:edge + +RUN apk add --no-cache curl + +WORKDIR /test + +COPY . . + +ENTRYPOINT ["curl"] + +CMD ["--help"] \ No newline at end of file diff --git a/README.md b/README.md index 0ad7e65..bf38bbf 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,38 @@ A Reusable Workflow of the Docker GitHub Actions steps. Enhanced with learnings 1. Easier to start with than hand-building all the Docker Actions into a single workflow. 2. Provides inline docs based on real-world usage of this workflow. -3. Gives you inputs so you can reuse this workflow across many repositories -and only needing the full workflow stored in a central repository. +3. Gives you inputs so you can reuse this workflow across many repositories and only needing the full workflow stored in a central repository. -## Basic workflow steps +## Steps to adopt this workflow + +1. Fork this repository and tweak the reusable workflow to your liking: [.github/workflows/reusable-docker-build.yaml](.github/workflows/reusable-docker-build.yaml) +2. Copy my "calling" workflow [`templates/call-docker-build.yaml`](templates/call-docker-build.yaml) to all the repos you want to build images in, and change it to point to the forked workflow above. + +## "But what does this workflow really do beyond just `docker build`?" 1. Clone the repository 2. Setup QEMU for multi-platform building (buildx) via docker/setup-qemu-action -3. Setup buildx (the best image builder) via docker/setup-buildx-action +3. Setup buildx for awesome and fast building via docker/setup-buildx-action 4. Log into Docker Hub and/or GHCR 5. Add labels and tags via docker/metadata-action -6. Build and push image via docker/build-push-action +6. Build and push image via docker/build-push-action with layer caching +7. Reports tags and labels in the PR comments + +## This repository is part of my example repos on GitHub Actions + +- [bretfisher/github-actions-templates](https://github.com/BretFisher/github-actions-templates) - Main repository +- [bretfisher/super-linter-workflow](https://github.com/BretFisher/super-linter-workflow) - Reusable linter workflow +- (you are here) [bretfisher/docker-build-workflow](https://github.com/BretFisher/docker-build-workflow)- Reusable docker build workflow +- [bretfisher/allhands22](https://github.com/BretFisher/github-actions-templates) - Step by step example of a Docker workflow +- [My full list of container examples and tools](https://github.com/bretfisher) + +## More reading + +- [Docker Build/Push Action advanced examples](https://github.com/docker/build-push-action/tree/master/docs/advanced) +- [My full list of container examples and tools](https://github.com/bretfisher) + +## 🎉🎉🎉 Join my container DevOps community 🎉🎉🎉 + +- [My "Vital DevOps" Discord server](https://devops.fan) +- [My weekly YouTube Live show](https://bret.live) +- [My courses and coupons](https://www.bretfisher.com/courses) diff --git a/templates/call-docker-build.yaml b/templates/call-docker-build.yaml new file mode 100644 index 0000000..491adb4 --- /dev/null +++ b/templates/call-docker-build.yaml @@ -0,0 +1,80 @@ +--- +# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml +name: Docker Build + +on: + push: + branches: + - main + # don't rebuild image if someone only edited unrelated files + paths-ignore: + - 'README.md' + - '.github/linters/**' + pull_request: + # don't rebuild image if someone only edited unrelated files + paths-ignore: + - 'README.md' + - '.github/linters/**' + +jobs: + call-docker-build: + + name: Call Docker Build + + uses: bretfisher/docker-build-workflow/.github/workflows/reusable-docker-build.yaml@main + + permissions: + contents: read + packages: write # needed to push docker image to ghcr.io + pull-requests: write # needed to create and update comments in PRs + + secrets: + + # Only needed if with:dockerhub-enable is true below + dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} + + # Only needed if with:dockerhub-enable is true below + # https://hub.docker.com/settings/security + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} + + with: + + ### REQUIRED + ### ENABLE ONE OR BOTH REGISTRIES + ### tell docker where to push. + ### NOTE if Docker Hub is set to true, you must set secrets above and also add account/repo/tags below + dockerhub-enable: true + ghcr-enable: true + + ### REQUIRED + ### A list of the account/repo names for docker build. List should match what's enabled above + ### defaults to: + image-names: | + ghcr.io/${{ github.repository }} + + ### REQUIRED set rules for tagging images, based on special action syntax: + ### https://github.com/docker/metadata-action#tags-input + ### defaults to: + tag-rules: | + type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} + type=ref,event=pr + type=ref,event=branch + type=semver,pattern={{version}} + + ### path to where docker should copy files into image + ### defaults to root of repository (.) + # context: . + + ### Dockerfile alternate name. Default is Dockerfile (relative to context path) + # file: Containerfile + + ### build stage to target, defaults to empty, which builds to last stage in Dockerfile + # target: + + ### platforms to build for, defaults to linux/amd64 + ### other options: linux/amd64,linux/arm64,linux/arm/v7 + # platforms: linux/amd64 + + ### Create a PR comment with image tags and labels + ### defaults to false + # comment-enable: false \ No newline at end of file