This documentation is intended to give a rationale for design decisions, and a more in-depth look into the build-ci pipelines.
This repository contains three main pipelines:
- Dependency Image Pipeline: uses
dep-image-1-start.yml
anddep-image-2-build.yml
workflows. - Model Test Pipeline: uses
model-1-build.yml
workflow. - JSON Validate Pipeline: uses
json-1-validate.yml
workflow.
How they are used can be found in the CI Run Through section.
This pipeline has explicit inputs, defined in the on.workflow_call.inputs
section:
spack-packages-version
: A tag or branch of theaccess-nri/spack-packages
repository. This allows provenance of the build process of models.model
: a coupled model name (such asaccess-om2
oraccess-om3
) orall
if we want to build dependency images for all coupled models defined inconfig/models.json
.
It also indirectly uses:
config/compilers.json
: This is a data structure containing all the compilers we want to test against.config/models.json
: This is a data structure containing all the coupled models (and their associated model components) that we want to test against.containers/Dockefile.base-spack
,containers/Dockerfile.dependency
: uses these Dockerfiles to create thebase-spack
anddependency
images.
This pipeline creates two docker image outputs:
base-spack
Docker image: Of the formbase-spack-<compiler name><compiler version>-<spack-packages version>:latest
. A docker image that contains aspack
install,access-nri/spack-packages
repo at the specified version, and a site-wide compiler for spack to use to build dependencies. An example of this package isbase-spack-intel2021.2.0-main
.dependency
Docker image: Of the formbuild-<coupled model>-<compiler name><compiler version>-<spack-packages version>:latest
: A docker image based on the abovebase-spack
image, that contains all the model components dependencies (separated byspack env
s), but not the models themselves. The models are added on top of the install in a different pipeline, negating the need for a costly install of the dependencies again (in most cases). An example of this package isbuild-access-om3-intel2021.2.0-main
.
There are no explicit inputs to this workflow. The information required is inferred by the model repository that calls the model-1-build.yml
workflow.
However, there are indirect inputs into this pipeline:
- Appropriate
dependency
Docker images of the form:ghcr.io/access-nri/build-<coupled model>-<compiler name><compiler version>-<spack-packages version>:latest
. config/compilers.json
: This is a data structure containing all the compilers we want to test against.config/models.json
: This is a data structure containing all the coupled models (and their associated model components) that we want to test against.
<env>.original.spack.lock
: Aspack.lock
file from thespack env
associated with the modified model repository (eg.mom5
), before the installation of the modified model. If the install succeeds then thisspack.lock
file was unmodified during the installation and can be used to recreate thisspack env
. This file is uploaded as an artifact.- Optionally, a
<env>.force.spack.lock
: If the installation fails (namely, if installing the modified model would change thespack.lock
file) we force a regeneration of thespack.lock
file and upload this as an artifact as well.
This workflow finds all the JSON Schema files in the project, i.e. those with the '.schema.json' extension, and then runs jsonschema
on the matching json files. e.g. config/models.json
is validated against config/models.schema.json
.
This pipeline has no explicit inputs.
However, there are indirect inputs into this pipeline:
*.json
: All JSON files that need to be validated.*.schema.json
: All JSON schemas that validate the above*.json
files.
There are no specific outputs from this pipeline. Only the normal output to the terminal and status checks reported by GitHub workflows.
The rationale for this pipeline is the creation of a model-dependency docker image. This image contains spack, the spack env
s, and the dependencies for the install of a model, but not the model itself. This allows modified models/model components to be 'dropped in' to the dependency image and installed quickly, without needing to install the dependencies again.
As an overview, this workflow, given a access-nri/spack-packages
repo version and coupled model(s):
- Generates a staggered
compiler x model
matrix based on thecompilers.json
andmodels.json
. This allows generation and testing of multiple different compiler and model image combinations in parallel. - Uses an existing
base-spack
docker image (or creates it if it doesn't exist) that contains an install of spack,access-nri/spack-packages
and a given compiler. - Using the above
base-spack
image, creates a spack-based model-dependency docker image that separates each model (and it's components) intospack env
s. This has all the dependencies of the model installed, but not the model itself.
There will be diagrams that seek to explain the calling and matrix structure of the pipeline, in parts. They will look like this:
workflow.yml [component comp1 comp2 comp3]
|- [comp1] another-workflow.yml
|- [comp2] another-workflow.yml
|- [comp3] another-workflow.yml
In the above diagram, the first line means that we initially call workflow.yml
. Within that workflow, we have a matrix strategy in which we call another-workflow.yml
with each part of the component
matrix in parallel (with these components being comp1
, comp2
and comp3
).
In the example we will be using for this pipeline, we have a compiler matrix with two compilers (c1, c2
) and a model matrix with two (coupled) models (m1, m2
).
This pipeline begins at dep-image-1-start.yml
:
dep-image-1-start.yml [compilers c1 c2]
This workflow is responsible for generating the matrix of compilers (from config/compilers.json
) and the information necessary for creating a matrix of coupled models for a future matrix (from models.json
).
Rather than doing the compiler x model
matrix at the beginning of the workflow, we do the model matrix later in a staggered approach. We do this because it removes duplication of effort and effectively uses the cache, rather than thrashing it. The differences between a compiler x model
and a staggered compiler
then matrix
model strategy are explained below.
Imagine we have a compiler x model
matrix with 2 compilers c1, c2
and two models m1, m2
. The matrix strategy would be:
[compiler x model] --- [c1, m1] --- base-spack-c1 image --- dep-c1-m1 image
|- [c1, m2] --- base-spack-c1 image --- dep-c1-m2 image
|- [c2, m1] --- base-spack-c2 image --- dep-c2-m1 image
|- [c2, m2] --- base-spack-c2 image --- dep-c2-m2 image
The two copies of base-spack-c1
and base-spack-c2
images are created in parallel, which duplicates effort and makes the cache unusable. Instead, if we stagger the creation of the matrix, as noted below:
[compiler] --- [c1] --- base-spack-c1 image --- [model] --- [m1] --- dep-c1-m1 image
| |- [m2] --- dep-c1-m2 image
|- [c2] --- base-spack-c2 image --- [model] --- [m1] --- dep-c2-m1 image
|- [m2] --- dep-c1-m2 image
We instead only create one copy of base-spack-c1
and base-spack-c2
, leveraging the various caches we use.
After the compiler
matrix is created, we call the dep-image-2-build.yml
workflow on each of the compilers, parallelizing the pipeline like so:
dep-image-1-start.yml [compilers c1 c2]
|- [c1] dep-image-2-build.yml
|- [c2] dep-image-2-build.yml
In this workflow, given the specs for a given compiler, a spack-packages
version, and a list of models for a future model
matrix strategy, we seek to:
- Check that a suitable
base-spack
image doesn't already exists. This would be one that has the same compiler and same version ofspack-packages
. - If it doesn't exist, create and push it using the
access-nri/actions docker-build-push
action. - After those steps, create the aforementioned
model
matrix strategy for each of the models. At this point, the pipeline looks like this:
dep-image-1-start.yml [compilers c1 c2]
|- [c1] dep-image-2-build.yml
| |- access-nri/actions docker-build-push (base-spack-c1)
| |- dependency-image [models m1 m2]
|- [c2] dep-image-2-build.yml
|- access-nri/actions docker-build-push (base-spack-c2)
|- dependency-image [models m1 m2]
In the dependency-image
job, we:
- Get the associated model components of our coupled model from the
config/models.json
file. - Build and push the dependency image given the existing
base-spack
image as a base, and the list of model components from the previous job, using theaccess-nri/actions docker-build-push
action.
This leads to a final pipeline looking like the following:
dep-image-1-start.yml [compilers c1 c2]
|- [c1] dep-image-2-build.yml
| |- access-nri/actions docker-build-push (base-spack-c1)
| |- dependency-image [models m1 m2]
| |- [m1] access-nri/actions docker-build-push (dep-image-c1-m1)
| |- [m2] access-nri/actions docker-build-push (dep-image-c1-m2)
|- [c2] dep-image-2-build-base.yml
| |- access-nri/actions docker-build-push (base-spack-c2)
| |- dependency-image [models m1 m2]
| |- [m1] access-nri/actions docker-build-push (dep-image-c2-m1)
| |- [m2] access-nri/actions docker-build-push (dep-image-c2-m2)
This workflow seeks to build upon the Dependency Image Pipeline as explained above by taking an appropriate dependency image and attempting to install a modified (most likely from a PR) model over the top. This allows further testing and runs of a modified model without the excessive overhead of installing dependencies from scratch.
Model repositories that implement the model-build-test-ci.yml
starter workflow (such as the access-nri/MOM5 repo) will call build-ci
s model-1-build.yml
workflow.
This workflow begins by inferring the 'appropriate dependency image' based on a number of factors, mostly coming from the name of the dependency image (which is of the form build-<coupled model>-<compiler name><compiler version>-<spack-packages version>:latest
).
In order to find:
spack-packages version
: In thesetup-spack-packages
job, we take the latest tagged version of theaccess-nri/spack-packages
repo.coupled model
: In thesetup-model
andsetup-build-ci
jobs, we use the name of the calling repository (eg.cice5
) andbuild-ci
sconfig/models.json
to infer the overarchingcoupled model
name.compiler name
/compiler version
: In thesetup-build-ci
job, we use all compilers from theconfig/compilers.json
file. This would mean that another matrix strategy would be in order.
Given those inferences, we are able to find the appropriate dependency images to use to build the modified models .
We then use those containers to upload the original spack.lock
files (also known as lockfiles) and then attempt to install the modified model in the appropriate spack env
. If this fails, we force a recreation of the lockfile and upload this one as well, for reference.
This is a relatively simple pipeline (found in json-1-validate.yml
) that looks for *.schema.json
files in a config
directory and matches them up with their associated *.json
files and tests that they comply with the given schema.