Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions .github/workflows/docker-image-build-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
name: Build and publish ctsm-docs Docker image

on:
# Run this whenever something gets pushed to master
# Run this whenever a change to certain files gets pushed to master
push:
branches: ['master']
paths:
- 'doc/ctsm-docs_container/Dockerfile'
- 'doc/ctsm-docs_container/requirements.txt'
- 'doc/ctsm-docs_container/**'
- '!doc/ctsm-docs_container/README.md'

# Run this whenever it's manually called
workflow_dispatch:
Expand All @@ -31,7 +31,7 @@ jobs:
env:
REGISTRY: ${{ needs.build-image-and-test-docs.outputs.REGISTRY }}
IMAGE_NAME: ${{ needs.build-image-and-test-docs.outputs.IMAGE_NAME }}
IMAGE_TAG: ${{ needs.build-image-and-test-docs.outputs.image_tag }}
VERSION_TAG: ${{ needs.build-image-and-test-docs.outputs.version_tag }}

# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
Expand Down Expand Up @@ -61,6 +61,7 @@ jobs:
# This step uses the `docker/build-push-action` action to build the image, based on the ctsm-docs `Dockerfile`.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see [Usage](https://github.com/docker/build-push-action#usage) in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
# Note that we should avoid relying on the "latest" tag for anything, but it's good practice to have one.
# v6.15.0
- name: Push Docker image
id: push
Expand All @@ -70,7 +71,9 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
load: false
tags: ${{ env.IMAGE_TAG }}
Comment thread
samsrabin marked this conversation as resolved.
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.VERSION_TAG }}
labels: ""

# This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see [Using artifact attestations to establish provenance for builds](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds).
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/docker-image-build.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# Modified from https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#publishing-a-package-using-an-action (last accessed 2025-05-09)
name: Test building ctsm-docs Docker image and using it to build the docs

# Configures this workflow to run every time a change in the Docker container setup is pushed to the master branch
# Configures this workflow to run every time a change in the Docker container setup is pushed or included in a PR
on:
push:
paths:
- 'doc/ctsm-docs_container/**'
- '!doc/ctsm-docs_container/README.md'
Comment thread
samsrabin marked this conversation as resolved.
- '.github/workflows/docker-image-ctsm-docs-build.yml'
- '.github/workflows/docker-image-build-common.yml'

pull_request:
paths:
- 'doc/ctsm-docs_container/**'
- '!doc/ctsm-docs_container/README.md'
Comment thread
samsrabin marked this conversation as resolved.
- '.github/workflows/docker-image-ctsm-docs-build.yml'
- '.github/workflows/docker-image-build-common.yml'

Expand Down
30 changes: 24 additions & 6 deletions .github/workflows/docker-image-common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ on:
image_tag:
description: "First image tag"
value: ${{ jobs.build-image-and-test-docs.outputs.image_tag }}
version_tag:
description: "Version tag from Dockerfile"
value: ${{ jobs.check-version.outputs.VERSION_TAG }}

# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
# Defines custom environment variables for the workflow.
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}/ctsm-docs
IMAGE_BASENAME: ctsm-docs
REPO: ${{ github.repository }}
Comment thread
samsrabin marked this conversation as resolved.

# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
jobs:
Expand All @@ -25,8 +29,7 @@ jobs:
# Variables that might be needed by the calling workflow
outputs:
REGISTRY: ${{ env.REGISTRY }}
IMAGE_NAME: ${{ env.IMAGE_NAME }}
image_tag: ${{ steps.set-image-tag.outputs.IMAGE_TAG }}
IMAGE_NAME: ${{ steps.set-image-name.outputs.IMAGE_NAME }}
Comment thread
samsrabin marked this conversation as resolved.
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
Expand All @@ -39,6 +42,14 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4

# Ensure that the repository part of IMAGE_NAME is lowercase. This is needed because Docker requires image names to be entirely lowercase. Note that the *image name* part, set as IMAGE_BASENAME in the env block above, is *not* converted. This will cause the check-version job to fail if the IMAGE_BASENAME contains capitals. We don't want to silently fix that here; rather, we require the user to specify a lowercase IMAGE_BASENAME.
- name: Get image name with lowercase repo
id: set-image-name
run: |
lowercase_repo=$(echo $REPO | tr '[:upper:]' '[:lower:]')
echo "IMAGE_NAME=${lowercase_repo}/${IMAGE_BASENAME}" >> $GITHUB_ENV
echo "IMAGE_NAME=${lowercase_repo}/${IMAGE_BASENAME}" >> $GITHUB_OUTPUT

# Uses the `docker/login-action` action to log in to the Container registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
Expand All @@ -52,7 +63,7 @@ jobs:
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
images: ${{ env.REGISTRY }}/${{ steps.set-image-name.outputs.IMAGE_NAME }}

# This step uses the `docker/build-push-action` action to build the image, based on the ctsm-docs `Dockerfile`.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see [Usage](https://github.com/docker/build-push-action#usage) in the README of the `docker/build-push-action` repository.
Expand All @@ -76,8 +87,15 @@ jobs:
id: set-image-tag
run: |
echo "IMAGE_TAG=$(echo '${{ steps.meta.outputs.tags }}' | cut -d',' -f1)" >> $GITHUB_ENV
echo "IMAGE_TAG=$(echo '${{ steps.meta.outputs.tags }}' | cut -d',' -f1)" >> $GITHUB_OUTPUT
- name: Build docs using container
id: build-docs
run: |
cd doc && ./build_docs -b ${PWD}/_build -c -d -i $IMAGE_TAG
Comment thread
samsrabin marked this conversation as resolved.


check-version:
needs: build-image-and-test-docs
uses: ./.github/workflows/docker-image-get-version.yml
with:
registry: ${{ needs.build-image-and-test-docs.outputs.REGISTRY }}
image_name: ${{ needs.build-image-and-test-docs.outputs.IMAGE_NAME }}
72 changes: 72 additions & 0 deletions .github/workflows/docker-image-get-version.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Get and check version specified in a Dockerfile

on:
workflow_call:
inputs:
registry:
required: true # Require any workflows calling this one to provide input
type: string
default: 'ghcr.io' # Provide default so this workflow works standalone too
image_name:
required: true # Require any workflows calling this one to provide input
type: string
default: 'escomp/ctsm/ctsm-docs' # Provide default so this workflow works standalone too
outputs:
VERSION_TAG:
description: "Tag to be pushed to container registry"
value: ${{ jobs.get-check-version.outputs.VERSION_TAG }}
workflow_dispatch:
inputs:
registry:
description: 'Container registry'
required: false
type: string
default: 'ghcr.io'
image_name:
description: 'Image name'
required: false
type: string
default: 'escomp/ctsm/ctsm-docs'

# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
jobs:
get-check-version:
name: Get version number from Dockerfile and check it
runs-on: ubuntu-latest
outputs:
VERSION_TAG: ${{ steps.get-check-version.outputs.version_tag }}
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
packages: read

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Get version number from Dockerfile and check it
id: get-check-version
run: |
set -e
set -o pipefail
set -u
VERSION="$(doc/ctsm-docs_container/get_version.sh)"
VERSION_TAG="${{ inputs.registry }}/${{ inputs.image_name }}:${VERSION}"

# Store the manifest inspect result and output
set +e
INSPECT_RESULT="$(docker manifest inspect "$VERSION_TAG" 2>&1)"
INSPECT_STATUS=$?
set -e

if [[ "${INSPECT_RESULT}" == *"schemaVersion"* ]]; then
echo "Tag $VERSION_TAG already exists!" >&2
exit 123
elif [[ "${INSPECT_RESULT}" != "manifest unknown" ]]; then
# "manifest unknown" means the tag doesn't exist, which is what we want
echo -e "Error checking manifest for $VERSION_TAG:\n${INSPECT_RESULT}" >&2
exit $INSPECT_STATUS
fi

echo "Setting version_tag to $VERSION_TAG"
echo "version_tag=$VERSION_TAG" >> $GITHUB_OUTPUT
3 changes: 2 additions & 1 deletion doc/ctsm-docs_container/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ WORKDIR /home/user
CMD ["/bin/bash", "-l"]

LABEL org.opencontainers.image.title="Container for building CTSM documentation"
LABEL org.opencontainers.image.source=https://github.com/ESCOMP/CTSM
LABEL org.opencontainers.image.source=https://github.com/ESCOMP/CTSM
LABEL org.opencontainers.image.version="v1.0.2"
Comment thread
samsrabin marked this conversation as resolved.
27 changes: 23 additions & 4 deletions doc/ctsm-docs_container/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This Readme tells you how to update the ctsm-docs Docker container if a need to

## Building

If you actually want to build the container, make sure Docker is running. In the Docker Desktop settings, make sure you've enabled the [`continerd` image store](https://docs.docker.com/desktop/features/containerd/), which allows multi-platform builds. Then do:
If you actually want to build the container, make sure Docker is running. In the Docker Desktop settings, make sure you've enabled the [`containerd` image store](https://docs.docker.com/desktop/features/containerd/), which allows multi-platform builds. Then do:
```shell
docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/escomp/ctsm/ctsm-docs .
```
Expand All @@ -21,7 +21,26 @@ ghcr.io/escomp/ctsm/ctsm-docs latest ab51446519a4 3 seconds ago 233M

To test, you can tell `build_docs` to use your new version by adding `--docker-image IMAGE_ID` to your call, where in the example above `IMAGE_ID` is `ab51446519a4`.

## Publishing
## Publishing automatically

The `docker-image-build-publish.yml` workflow makes it so that new versions of the workflow will be published to the GitHub Container Registry whenever changes to the container setup are merged to CTSM's `master` branch. This will fail (as will a similar, no-publish workflow that happens on PRs) unless you specify exactly one new version number in the Dockerfile. This version number will be used as a tag that can be referenced by, e.g., doc-builder.

Lots of Docker instructions tell you to use the `latest` tag, and indeed the workflow will add that tag automatically. However, actually _using_ `latest` can lead to support headaches as users think they have the right version but actually don't. Instead, you'll make a new version number incremented from the [previous one](https://github.com/ESCOMP/CTSM/pkgs/container/ctsm%2Fctsm-docs/versions), in the `vX.Y.Z` format.

Here's where you need to specify the version number in the Dockerfile:
```docker
LABEL org.opencontainers.image.version="vX.Y.Z"
```
The string there can technically be anything as long as (a) it starts with a lowercase `v` and (b) it hasn't yet been used on a published version of the container.

You can check the results of the automatic publication on the [container's GitHub page](https://github.com/ESCOMP/CTSM/pkgs/container/ctsm%2Fctsm-docs).

### Updating doc-builder
After the new version of the container is published, you will probably want to tell [doc-builder](https://github.com/ESMCI/doc-builder) to use the new one. Open a PR where you change the tag (the part after the colon) in the definition of `DEFAULT_DOCKER_IMAGE` in `doc_builder/build_commands.py`. Remember, **use the version number**, not "latest".

## Publishing manually (NOT recommended)

It's vastly preferable to let GitHub build and publish the new repo using the `docker-image-build-publish.yml` workflow as described above. However, if you need to publish manually for some reason, here's how.

### Pushing to GitHub Container Registry
If you want to publish the container, you first need a [GitHub Personal Access Token (Classic)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#personal-access-tokens-classic) with the `write:packages` permissions. You can see your existing PAT(C)s [here](https://github.com/settings/tokens). If you don't have one with the right permissions, [this link](https://github.com/settings/tokens/new?scopes=write:packages) should start the setup process for you.
Expand All @@ -31,7 +50,7 @@ Once you have a PAT(C), you can authenticate in your shell session like so:
```shell
echo YOUR_PERSONAL_ACCESS_TOKEN_CLASSIC | docker login ghcr.io -u YOUR_USERNAME --password-stdin
```
The leading spaces are intended to prevent this command, which contains your secret PAT(C), from being written to your shell's history file. That at least works in bash... sometimes. To be extra safe, in bash you can do `history -c` and it will clear the session's history entirely.
The leading spaces are intended to prevent this command, which contains your secret PAT(C), from being written to your shell's history file. That at least works in bash... sometimes. To be extra safe, in bash you can do `history -c` and it will clear your entire bash history. That can be pretty disruptive, but fortunately you should only need to authenticate once.

### Tagging
You'll next need to tag the image. Lots of Docker instructions tell you to use the `latest` tag, and Docker may actually do that for you. However, `latest` can lead to support headaches as users think they have the right version but actually don't. Instead, you'll make a new version number incremented from the [previous one](https://github.com/ESCOMP/CTSM/pkgs/container/ctsm%2Fctsm-docs/versions), in the `vX.Y.Z` format.
Expand All @@ -49,7 +68,7 @@ docker push ghcr.io/escomp/ctsm/ctsm-docs:vX.Y.Z
Then browse to the [container's GitHub page](https://github.com/ESCOMP/CTSM/pkgs/container/ctsm%2Fctsm-docs) to make sure this all worked and the image is public.

### Updating doc-builder
Since you've updated the container, you will probably want to tell [doc-builder](https://github.com/ESMCI/doc-builder) to use the new one. Open a PR where you change the tag (the part after the colon) in the definition of `DEFAULT_DOCKER_IMAGE` in `doc_builder/build_commands.py`. Remember, **use the version number**, not "latest".
See "Updating doc-builder" in the "Publishing automatically" section above.

## See also

Expand Down
26 changes: 26 additions & 0 deletions doc/ctsm-docs_container/get_version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -e
cd doc/ctsm-docs_container

# Extract version from Dockerfile
version="$(grep "org.opencontainers.image.version" Dockerfile | cut -d'"' -f2 | sort |uniq)"
n_found=$(echo $version | wc -w)

# Error if anything other than exactly one version tag was found
if [[ ${n_found} -gt 1 ]]; then
echo -e "Multiple version tags found:\n${version}" >&2
exit 2
elif [[ ${n_found} -lt 1 ]]; then
echo "Expected 1 but found 0 version tags" >&2
exit -1
fi

# Error if version doesn't start with v
if [[ "${version}" != "v"* ]]; then
echo "Version '${version}' doesn't start with v" >&2
exit 22
fi

echo ${version}

exit 0