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
86 changes: 30 additions & 56 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -1,85 +1,59 @@
name: Docker

# This will run when:
# - when new code is pushed to main/develop to push the tags
# latest and develop
# - when a pull request is created and updated to make sure the
# Dockerfile is still valid.
# To be able to push to dockerhub, this execpts the following
# secrets to be set in the project:
# - DOCKERHUB_USERNAME : username that can push to the org
# - DOCKERHUB_PASSWORD : password asscoaited with the username
# Builds the frontend image and pushes it to GitHub Container Registry, as
# ghcr.io/<org>/<id>-frontend (derived from app.yaml):
# pull requests pr-<N> and staging (staging runs the most recently built PR)
# main main (prod runs it) and sha-<short>
# The repo must live in the org its family deploys from (moleculemaker for
# alphasynthesis, iBioFoundry for alphasynbio), because GITHUB_TOKEN can only push
# packages to its own org.
#
# The first push creates the package with the repo's visibility: public for a public
# repo (as new apps are, so the cluster needs no pull secret), private for an internal
# or private one. A private package needs an org admin to make it public in the
# package settings before the cluster can pull it (the chart has no imagePullSecrets).
on:
push:
branches:
- main
- develop

pull_request:

# Trigger the workflow on release activity
release:
# Only use the types keyword to narrow down the activity types that will trigger your workflow.
types:
- published
- edited
- created

env:
DOCKERHUB_ORG: moleculemaker
permissions:
contents: read
packages: write

jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- uses: docker/setup-buildx-action@v3

- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Docker meta
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: moleculemaker/test-frontend # app:sync image
images: ghcr.io/moleculemaker/test-frontend # app:sync image
flavor: latest=false
tags: |
# set latest tag for default branch
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=ref,event=pr
type=raw,value=staging,enable=true
type=ref,event=tag
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}

- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}

type=raw,value=staging,enable=${{ github.event_name == 'pull_request' }}
type=raw,value=main,enable={{is_default_branch}}
type=sha,enable={{is_default_branch}}

- name: Build and push Docker image (production)
if: github.ref == 'refs/heads/main'
uses: docker/build-push-action@v3
- name: Log in to ghcr
uses: docker/login-action@v3
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args:
configuration=production

registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image (staging)
if: github.event_name == 'pull_request'
uses: docker/build-push-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args:
configuration=staging
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: End-to-end Tests
on:
push:
branches: [main, develop]
branches: [main]
pull_request:
release:
types: [published, edited, created]
Expand Down
2 changes: 1 addition & 1 deletion chart/values.prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ ingress:
traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.middlewares: alphasynthesis-test-prod-domain-redirect@kubernetescrd
controller:
image: moleculemaker/test-frontend:main
image: ghcr.io/moleculemaker/test-frontend:main
config:
hostname: https://mmli.fastapi.mmli2.ncsa.illinois.edu
basePath: ""
Expand Down
2 changes: 1 addition & 1 deletion chart/values.staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ingress:
kubernetes.io/tls-acme: "true"
traefik.ingress.kubernetes.io/router.tls: "true"
controller:
image: moleculemaker/test-frontend:staging
image: ghcr.io/moleculemaker/test-frontend:staging
config:
hostname: https://mmli.fastapi.staging.mmli2.ncsa.illinois.edu
basePath: ""
Expand Down
2 changes: 1 addition & 1 deletion chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ ingress:
tls: false
annotations: {}
controller:
image: moleculemaker/test-frontend
image: ghcr.io/moleculemaker/test-frontend:main
config:
hostname: https://mmli.fastapi.staging.mmli2.ncsa.illinois.edu
basePath: ""
Expand Down
8 changes: 5 additions & 3 deletions scripts/app-sync.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');

export const FAMILIES = {
alphasynthesis: {
// GitHub org the app's repo and ghcr package live in
ghcrOrg: 'moleculemaker',
namespace: 'alphasynthesis',
prodDomain: 'platform.moleculemaker.org',
feedbackDomain: 'moleculemaker.org',
},
alphasynbio: {
ghcrOrg: 'ibiofoundry',
namespace: 'ibiofoundry',
prodDomain: 'platform.ibiofoundry.illinois.edu',
feedbackDomain: 'ibiofoundry.illinois.edu',
Expand Down Expand Up @@ -88,8 +91,7 @@ export function derive(app) {
prod_host: prodHost,
// The cluster's own name for the prod app, redirected to prod_host
prod_alias_host: `${app.id}.frontend.mmli2.ncsa.illinois.edu`,
// Docker Hub for every family until the template moves to ghcr (#13)
image: `moleculemaker/${app.id}-frontend`,
image: `ghcr.io/${family.ghcrOrg}/${app.id}-frontend`,
feedback_email: `${app.id}-feedback@${family.feedbackDomain}`,
};
}
Expand Down Expand Up @@ -142,7 +144,7 @@ export function render(app) {
// Local cluster defaults; staging and prod override all of it
'chart/values.yaml': toYaml({
ingress: { hostname: d.local_host, tls: false, annotations: {} },
controller: { image: d.image },
controller: { image: `${d.image}:main` },
config: oauthConfig('staging', d.local_host, BACKEND.staging),
extraDeploy: [],
}),
Expand Down
33 changes: 23 additions & 10 deletions scripts/app-sync.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,30 @@ const generated = (app, file) => parse(render(app)[file]);
const EZSPEC = { id: 'ezspecificity', name: 'EZSpecificity', family: 'alphasynthesis', matomo_site_id: 1 };
const MUTAGENESIS = { id: 'mutagenesis', name: 'Mutagenesis', family: 'alphasynbio', matomo_site_id: 21 };

test('alphasynthesis chart values match ezspecificity-frontend as deployed', () => {
assert.deepEqual(generated(EZSPEC, 'chart/values.staging.yaml'), fixture('ezspecificity.values.staging.yaml'));
assert.deepEqual(generated(EZSPEC, 'chart/values.prod.yaml'), fixture('ezspecificity.values.prod.yaml'));
// Images are the expected difference from the live apps: the template pushes every app
// to public ghcr with pr-N/staging and main tags. ezspecificity is still on Docker Hub,
// and mutagenesis on private ghcr with :latest and a pull secret.
const TAG = { staging: 'staging', prod: 'main' };

test('alphasynthesis chart values match ezspecificity-frontend as deployed, apart from the image', () => {
for (const env of ['staging', 'prod']) {
const expected = fixture(`ezspecificity.values.${env}.yaml`);
const actual = generated(EZSPEC, `chart/values.${env}.yaml`);
assert.equal(actual.controller.image, `ghcr.io/moleculemaker/ezspecificity-frontend:${TAG[env]}`);
delete expected.controller;
delete actual.controller;
assert.deepEqual(actual, expected, env);
}
});

test('alphasynbio chart values match mutagenesis-frontend except the registry', () => {
// Expected differences: mutagenesis pushes to private ghcr with a pull secret; the
// template pushes to Docker Hub until #13 and needs no pull secret for public images.
test('alphasynbio chart values match mutagenesis-frontend, apart from the image and pull secret', () => {
for (const env of ['staging', 'prod']) {
const expected = fixture(`mutagenesis.values.${env}.yaml`);
delete expected.imagePullSecrets;
delete expected.imagePullSecret;
delete expected.controller;
const actual = generated(MUTAGENESIS, `chart/values.${env}.yaml`);
assert.equal(actual.controller.image, `moleculemaker/mutagenesis-frontend:${env === 'prod' ? 'main' : 'staging'}`);
assert.equal(actual.controller.image, `ghcr.io/ibiofoundry/mutagenesis-frontend:${TAG[env]}`);
delete actual.controller;
assert.deepEqual(actual, expected, env);
}
Expand All @@ -41,7 +50,7 @@ test('the identity module carries the family and the derived feedback address',
test('managed lines take the derived value and keep their marker', () => {
const text = [
'env:',
' MAIN_REPO: moleculemaker/old-frontend # app:sync image',
' IMAGE: moleculemaker/old-frontend # app:sync image',
' OTHER: untouched',
' - old.frontend.staging.example # app:sync staging_host',
].join('\n');
Expand All @@ -50,7 +59,7 @@ test('managed lines take the derived value and keep their marker', () => {

assert.equal(out, [
'env:',
' MAIN_REPO: moleculemaker/ezspecificity-frontend # app:sync image',
' IMAGE: ghcr.io/moleculemaker/ezspecificity-frontend # app:sync image',
' OTHER: untouched',
' - ezspecificity.frontend.staging.mmli2.ncsa.illinois.edu # app:sync staging_host',
].join('\n'));
Expand Down Expand Up @@ -86,7 +95,7 @@ test('a tagged line the rewriter cannot parse is an error, not a silent skip', (

test('a "- key: value" list item keeps its key', () => {
const out = updateManagedLines(' - image: moleculemaker/old # app:sync image', derive(EZSPEC), 'x.yml');
assert.equal(out, ' - image: moleculemaker/ezspecificity-frontend # app:sync image');
assert.equal(out, ' - image: ghcr.io/moleculemaker/ezspecificity-frontend # app:sync image');
});

test('replacement patterns in the name are inserted literally', () => {
Expand Down Expand Up @@ -120,3 +129,7 @@ test('no tagged files is an empty list, not an error; other git failures still t
const notARepo = () => { throw Object.assign(new Error('not a git repository'), { status: 128 }); };
assert.throws(() => managedFiles(notARepo), /not a git repository/);
});

test('local chart values pin a tag that is actually pushed', () => {
assert.equal(generated(EZSPEC, 'chart/values.yaml').controller.image, 'ghcr.io/moleculemaker/ezspecificity-frontend:main');
});
Loading