Skip to content

Commit

Permalink
Merge pull request #69 from Agoric/25-publish-use
Browse files Browse the repository at this point in the history
publish 'use' images
  • Loading branch information
turadg authored Jan 19, 2024
2 parents 1e0452c + 9285e9b commit 9223d43
Show file tree
Hide file tree
Showing 13 changed files with 138 additions and 70 deletions.
52 changes: 30 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@ concurrency:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
# Name these so they look less similar than AMD/ARM; omit 64 as uninformative.
X86_PLATFORM: linux/amd64
ARM_PLATFORM: linux/arm64/v8

jobs:
platforms:
runs-on: ubuntu-latest
outputs:
default: '${{ steps.platforms.outputs.default }}'
platforms: '${{ steps.platforms.outputs.platforms }}'
steps:
- name: Compute Docker platforms
id: platforms
run: |
DEFAULT_PLATFORM=linux/amd64
echo "default=$DEFAULT_PLATFORM" >> $GITHUB_OUTPUT
if ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}; then
# JSON-encoded list consisting only of the default platform.
platforms='["'"$DEFAULT_PLATFORM"'"]'
platforms='["'"$X86_PLATFORM"'"]'
else
platforms='["linux/amd64","linux/arm64/v8"]'
platforms='["$X86_PLATFORM","$ARM_PLATFORM"]'
fi
echo "platforms=$platforms" >> $GITHUB_OUTPUT
Expand Down Expand Up @@ -62,10 +62,6 @@ jobs:
echo "=== After cleanup:"
df -h
- name: Set environment
run: |
echo "DEFAULT_PLATFORM=${{ needs.platforms.outputs.default }}" >> $GITHUB_ENV
- name: Checkout repository
uses: actions/checkout@v4

Expand Down Expand Up @@ -112,20 +108,31 @@ jobs:
# Enable corepack for packageManager config
- run: corepack enable || sudo corepack enable
- run: yarn install
# Set up docker-bake files used by docker/bake-action
- run: node_modules/.bin/synthetic-chain prepare-ci

- name: build test images ${{ matrix.platform }} == ${{ env.DEFAULT_PLATFORM }}
run: |
docker info
node_modules/.bin/synthetic-chain build ${{ matrix.platform == env.DEFAULT_PLATFORM && ' ' || '--dry' }}
- name: run test images
if: ${{ matrix.platform == env.DEFAULT_PLATFORM }}
# Test before pushing the images.
- name: Build and run proposal tests
if: ${{ matrix.platform == env.X86_PLATFORM }}
run: node_modules/.bin/synthetic-chain test

# XXX this should be instant for the local platform because all the stages
# were already built in the steps above but it's re-building the last
# stage. This is deemed good enough for now. see
# https://github.com/moby/moby/issues/34715
- name: Build and push images
# Build a "use" image for each proposal. This uses Docker Bake's
# matrix feature. We could have each "use" image built in a different runner
# by including https://github.com/docker/bake-action?tab=readme-ov-file#list-targets
# in the GHA matrix, but that wouldn't be able to resolve the DAG of what to build first.
- name: Push proposal "use" images
uses: docker/bake-action@v4
# If we pushed from PRs, each one would overwrite main's (e.g. use-upgrade-8)
# To push PR "use" images we'll need to qualify the tag (e.g. use-upgrade-8-pr-2).
if: ${{ github.event_name != 'pull_request' }}
with:
files: |
./docker-bake.json
./docker-bake.hcl
${{ steps.meta.outputs.bake-file }}
targets: use

- name: Build and push default image
uses: docker/build-push-action@v5
with:
context: .
Expand All @@ -136,7 +143,8 @@ jobs:
tags: ${{ steps.docker-tags.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

# Publish the build's multiarch images to Docker Registry.
# Merge the default image from each platform into one multi-arch image,
# then publish that multiarch image.
docker-publish-multiarch:
needs: [test-proposals, platforms]
runs-on: ubuntu-latest
Expand All @@ -160,7 +168,7 @@ jobs:
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Login to Docker Registry
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
username: ${{ github.actor }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,5 @@ dist

# build in CI
Dockerfile
docker-bake.json
/upgrade-test-scripts
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ All proposals then have two additional stages:

The `TEST` stage does not RUN as part of the build. It only defines the ENTRYPOINT and CI runs them all.

The `USE` stage is built into images that are pushed to the image repository. These can be used by release branches to source a particular state of the synthetic chain.

Finally there is a `DEFAULT` target which take the last `USE` image and sets its entrypoing to `./start_agd.sh` which runs the chain indefinitely. This makes it easy to source that image as a way to _run_ the synthetic chain with all passed proposals.

## Proposals

### Types
Expand Down Expand Up @@ -54,6 +58,8 @@ If the proposal is _pending_ and does not yet have a number, use a letter. The p

## Development

A known issue is that `yarn synthetic-chain` files with `Unknown file extension ".ts"`. To work around it, run from the bin dir as below.

To build the test images,

```
Expand Down
42 changes: 42 additions & 0 deletions docker-bake.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// If you're new to Bake:
// https://blog.kubesimplify.com/bake-your-container-images-with-bake

// for https://github.com/docker/metadata-action?tab=readme-ov-file#bake-definition
target "docker-metadata-action" {}

// PROPOSALS variable is filled by docker-bake.json, which this config merges upon
// https://docs.docker.com/build/bake/reference/#file-format

group "default" {
targets = [
"use",
"test"
]
}

target "use" {
inherits = ["docker-metadata-action"]
name = "use-${proposal}"
matrix = {
proposal = PROPOSALS
}
// TODO proposal *number* would be immutable
tags = ["ghcr.io/agoric/agoric-3-proposals:use-${proposal}"]
labels = {
"org.opencontainers.image.title": "Use ${proposal}",
"org.opencontainers.image.description": "Use agoric-3 synthetic chain after ${proposal} proposal",
}
target = "use-${proposal}"
}

target "test" {
name = "test-${proposal}"
matrix = {
proposal = PROPOSALS
}
tags = ["ghcr.io/agoric/agoric-3-proposals:test-${proposal}"]
labels = {
"org.opencontainers.image.title": "Test ${proposal}",
}
target = "test-${proposal}"
}
11 changes: 7 additions & 4 deletions packages/synthetic-chain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

Utilities to build a synthetic chain and test running proposals atop it. The chain approximates agoric-3 (Mainnet) using the state from https://github.com/Agoric/agoric-3-proposals (It could trivially support other Agoric chains, if we scale horizontally.)

```sh
node_modules/.bin/synthetic-chain build
## Usage

node_modules/.bin/synthetic-chain test
```
build - build the synthetic-chain "use" images
test [--debug] - build the "test" images and run them
test -m <name> - target a particular proposal by substring match
node_modules/.bin/synthetic-chain test --debug -m <substring of proposal name>
doctor - diagnostics and quick fixes
```

## Design
Expand Down
29 changes: 20 additions & 9 deletions packages/synthetic-chain/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import path from 'node:path';
import { execSync } from 'node:child_process';
import {
buildProposalSubmissions,
buildTestImages,
bakeImages,
readBuildConfig,
} from './src/cli/build.js';
import { writeDockerfile } from './src/cli/dockerfileGen.js';
import {
writeBakefileProposals,
writeDockerfile,
} from './src/cli/dockerfileGen.js';
import { matchOneProposal, readProposals } from './src/cli/proposals.js';
import { debugTestImage, runTestImages } from './src/cli/run.js';
import { runDoctor } from './src/cli/doctor.js';
Expand All @@ -35,30 +38,38 @@ const [cmd] = positionals;

// TODO consider a lib like Commander for auto-gen help
const usage = `USAGE:
build - build the synthetic-chain
build - build the synthetic-chain "use" images
test [--debug] - run each proposal's test image
test [--debug] - build the "test" images and run them
test -m <name> - target a particular proposal by substring match
doctor - diagnostics and quick fixes
`;

const buildImages = () => {
/**
* Put into places files that building depends upon.
*/
const prepareDockerBuild = () => {
execSync(
// XXX very brittle
'cp -r node_modules/@agoric/synthetic-chain/upgrade-test-scripts .',
);
writeDockerfile(allProposals, buildConfig.fromTag);
writeBakefileProposals(allProposals);
buildProposalSubmissions(proposals);
buildTestImages(proposals, values.dry);
};

switch (cmd) {
case 'build': {
const { fromTag } = buildConfig;
writeDockerfile(allProposals, fromTag);
buildImages();
prepareDockerBuild();
bakeImages('use', values.dry);
break;
}
case 'test':
// Always rebuild all test images to keep it simple. With the "use" stages
// cached, these are pretty fast building doesn't run agd.
prepareDockerBuild();
bakeImages('test', values.dry);
if (values.debug) {
debugTestImage(matchOneProposal(proposals, match!));
} else {
Expand Down
30 changes: 12 additions & 18 deletions packages/synthetic-chain/src/cli/build.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { ProposalInfo, imageNameForProposal } from './proposals.js';
import { ProposalInfo } from './proposals.js';

export type AgoricSyntheticChainConfig = {
/**
Expand Down Expand Up @@ -62,21 +62,15 @@ export const buildProposalSubmissions = (proposals: ProposalInfo[]) => {
}
};

export const buildTestImages = (proposals: ProposalInfo[], dry = false) => {
for (const proposal of proposals) {
if (!dry) {
console.log(
`\nBuilding test image for proposal ${proposal.proposalName}`,
);
}
const { name, target } = imageNameForProposal(proposal, 'test');
// 'load' to ensure the images are output to the Docker client. Seems to be necessary
// for the CI docker/build-push-action to re-use the cached stages.
const cmd = `docker buildx build --load --tag ${name} --target ${target} .`;
console.log(cmd);
if (!dry) {
// `time` to output how long each build takes
execSync(`time ${cmd}`, { stdio: 'inherit' });
}
}
/**
* Bake images using the docker buildx bake command.
*
* @param matrix - The group target
* @param [dry] - Whether to skip building and just print the build config.
*/
export const bakeImages = (matrix: 'test' | 'use', dry = false) => {
// https://docs.docker.com/engine/reference/commandline/buildx_build/#load
const cmd = `docker buildx bake --load ${matrix} ${dry ? '--print' : ''}`;
console.log(cmd);
execSync(cmd, { stdio: 'inherit' });
};
21 changes: 18 additions & 3 deletions packages/synthetic-chain/src/cli/dockerfileGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type ProposalInfo,
type SoftwareUpgradeProposal,
encodeUpgradeInfo,
imageNameForProposal,
} from './proposals.js';

/**
Expand Down Expand Up @@ -170,11 +171,14 @@ ENTRYPOINT ./run_test.sh ${proposalIdentifier}:${proposalName}
* The last target in the file, for untargeted `docker build`
*/
DEFAULT(lastProposal: ProposalInfo) {
// Assumes the 'use' image is built and tagged.
// This isn't necessary for a multi-stage build, but without it CI
// rebuilds the last "use" image during the "default" image step
// Some background: https://github.com/moby/moby/issues/34715
const useImage = imageNameForProposal(lastProposal, 'use').name;
return `
# DEFAULT
FROM use-${lastProposal.proposalName}
COPY --link --chmod=755 ./upgrade-test-scripts/start_agd.sh /usr/src/upgrade-test-scripts/
FROM ${useImage}
WORKDIR /usr/src/upgrade-test-scripts
SHELL ["/bin/bash", "-c"]
Expand All @@ -183,6 +187,17 @@ ENTRYPOINT ./start_agd.sh
},
};

export function writeBakefileProposals(allProposals: ProposalInfo[]) {
const json = {
variable: {
PROPOSALS: {
default: allProposals.map(p => p.proposalName),
},
},
};
fs.writeFileSync('docker-bake.json', JSON.stringify(json, null, 2));
}

export function writeDockerfile(
allProposals: ProposalInfo[],
fromTag?: string | null,
Expand Down
4 changes: 2 additions & 2 deletions packages/synthetic-chain/src/cli/proposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ export function lastPassedProposal(
}

export function imageNameForProposal(
proposal: ProposalCommon,
stage: 'test' | 'eval',
proposal: Pick<ProposalCommon, 'proposalName'>,
stage: 'test' | 'use',
) {
const target = `${stage}-${proposal.proposalName}`;
return {
Expand Down
4 changes: 0 additions & 4 deletions proposals/16:upgrade-8/use.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,6 @@ for i in "${govaccounts[@]}"; do
agops perf satisfaction --from $i --executeOffer $VOTE_OFFER --keyring-backend=test
done

## wait for the election to be resolved (1m default in commands/psm.js)
echo "waiting 1 minute for election to be resolved"
sleep 65

echo DEBUG print mint limit
agops psm info --pair ${PSM_PAIR}

Expand Down
1 change: 0 additions & 1 deletion proposals/34:upgrade-10/performActions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env node

// FIXME get TypeScript to resolve these, probably with lib:ES2022
import assert from 'node:assert/strict';
import {
provisionWallet,
Expand Down
1 change: 0 additions & 1 deletion proposals/43:upgrade-11/performActions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env node

// FIXME get TypeScript to resolve these, probably with lib:ES2022
import assert from 'node:assert/strict';

import { agoric, agops } from '@agoric/synthetic-chain/src/lib/cliHelper.js';
Expand Down
6 changes: 0 additions & 6 deletions proposals/b:zoe1/use.sh

This file was deleted.

0 comments on commit 9223d43

Please sign in to comment.