Skip to content

Commit

Permalink
feat(synthetic-chain): allow overriding Docker repo/tag
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Sep 9, 2024
1 parent 45c6389 commit 4ff1ddc
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 46 deletions.
4 changes: 1 addition & 3 deletions packages/synthetic-chain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
"name": "@agoric/synthetic-chain",
"version": "0.2.1",
"description": "Utilities to build a chain and test proposals atop it",
"bin": {
"synthetic-chain": "dist/cli/cli.js"
},
"bin": "dist/cli/cli.js",
"main": "./dist/lib/index.js",
"type": "module",
"module": "./dist/lib/index.js",
Expand Down
6 changes: 3 additions & 3 deletions packages/synthetic-chain/public/docker-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ target "use" {
proposal = PROPOSALS
}
// TODO proposal *number* would be immutable
tags = ["ghcr.io/agoric/agoric-3-proposals:use-${proposal}"]
tags = ["${REPOSITORY_COLON}use-${proposal}"]
labels = {
"org.opencontainers.image.title": "Use ${proposal}",
"org.opencontainers.image.description": "Use agoric-3 synthetic chain after ${proposal} proposal",
Expand All @@ -37,7 +37,7 @@ target "test" {
matrix = {
proposal = PROPOSALS
}
tags = ["ghcr.io/agoric/agoric-3-proposals:test-${proposal}"]
tags = ["${REPOSITORY_COLON}test-${proposal}"]
labels = {
"org.opencontainers.image.title": "Test ${proposal}",
}
Expand All @@ -49,7 +49,7 @@ target "test" {
target "latest" {
inherits = ["docker-metadata-action"]
platforms = PLATFORMS
tags = ["ghcr.io/agoric/agoric-3-proposals:latest"]
tags = ["${REPOSITORY_COLON}latest"]
labels = {
"org.opencontainers.image.title": "All passed proposals",
"org.opencontainers.image.description": "Use agoric-3 synthetic chain including all passed proposals",
Expand Down
11 changes: 10 additions & 1 deletion packages/synthetic-chain/src/cli/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,22 @@ export type AgoricSyntheticChainConfig = {
* Defaults to `latest`, which containing all passed proposals
*/
fromTag: string | null;
platforms?: Platform[];
repositoryColon?: string;
sdkRepositoryColon?: string;
platforms?: Platform[] | null;
ag0FromTag?: string;
ag0RepositoryColon?: string;
};

const defaultConfig: AgoricSyntheticChainConfig = {
// Tag of the agoric-3 image containing all passed proposals
// Must match the Bake file and CI config
fromTag: 'latest',
repositoryColon: 'ghcr.io/agoric/agoric-3-proposals:',
sdkRepositoryColon: 'ghcr.io/agoric/agoric-sdk:',
platforms: null,
ag0FromTag: 'agoric-upgrade-7-2',
ag0RepositoryColon: 'ghcr.io/agoric/ag0:',
};

export function readBuildConfig(root: string): AgoricSyntheticChainConfig {
Expand Down
40 changes: 33 additions & 7 deletions packages/synthetic-chain/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { execSync } from 'node:child_process';
import path from 'node:path';
import { parseArgs } from 'node:util';
import {
AgoricSyntheticChainConfig,
bakeTarget,
buildProposalSubmissions,
readBuildConfig,
Expand All @@ -27,7 +28,9 @@ const { positionals, values } = parseArgs({
});

const root = path.resolve('.');
const buildConfig = readBuildConfig(root);
const buildConfig = readBuildConfig(
root,
) as Required<AgoricSyntheticChainConfig>;
const allProposals = readProposals(root);

const { match } = values;
Expand Down Expand Up @@ -66,8 +69,23 @@ const prepareDockerBuild = () => {
const publicDir = path.resolve(cliPath, '..', '..');
// copy and generate files of the build context that aren't in the build contents
execSync(`cp -r ${path.resolve(publicDir, 'docker-bake.hcl')} .`);
writeDockerfile(allProposals, buildConfig.fromTag);
writeBakefileProposals(allProposals, buildConfig.platforms);
writeDockerfile(
allProposals,
buildConfig.fromTag,
buildConfig.repositoryColon,
buildConfig.sdkRepositoryColon,
buildConfig.ag0FromTag,
buildConfig.ag0RepositoryColon,
);
writeBakefileProposals(
allProposals,
buildConfig.platforms,
buildConfig.fromTag,
buildConfig.repositoryColon,
buildConfig.sdkRepositoryColon,
buildConfig.ag0FromTag,
buildConfig.ag0RepositoryColon,
);
// copy and generate files to include in the build
execSync(`cp -r ${path.resolve(publicDir, 'upgrade-test-scripts')} .`);
buildProposalSubmissions(proposals);
Expand Down Expand Up @@ -103,16 +121,24 @@ switch (cmd) {
assert(proposals.length === 1, 'too many proposals match');
const proposal = proposals[0];
console.log(chalk.yellow.bold(`Debugging ${proposal.proposalName}`));
bakeTarget(imageNameForProposal(proposal, 'test').target, values.dry);
debugTestImage(proposal);
bakeTarget(
imageNameForProposal(proposal, 'test', buildConfig.repositoryColon)
.target,
values.dry,
);
debugTestImage(proposal, buildConfig.repositoryColon);
// don't bother to delete the test image because there's just one
// and the user probably wants to run it again.
} else {
for (const proposal of proposals) {
console.log(chalk.cyan.bold(`Testing ${proposal.proposalName}`));
const image = imageNameForProposal(proposal, 'test');
const image = imageNameForProposal(
proposal,
'test',
buildConfig.repositoryColon,
);
bakeTarget(image.target, values.dry);
runTestImage(proposal);
runTestImage(proposal, buildConfig.repositoryColon);
// delete the image to reclaim disk space. The next build
// will use the build cache.
execSync('docker system df', { stdio: 'inherit' });
Expand Down
90 changes: 67 additions & 23 deletions packages/synthetic-chain/src/cli/dockerfileGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ const stage = {
/**
* Prepare an upgrade from ag0, start of the chain
*/
PREPARE_ZERO(proposalName: string, to: string) {
const agZeroUpgrade = 'agoric-upgrade-7-2';
PREPARE_ZERO(
proposalName: string,
to: string,
ag0FromTag: string,
ag0RepositoryColon: string,
) {
return `
## START
# on ${agZeroUpgrade}, with upgrade to ${to}
FROM ghcr.io/agoric/ag0:${agZeroUpgrade} as prepare-${proposalName}
# on ${ag0FromTag}, with upgrade to ${to}
FROM \${AG0_REPOSITORY_COLON:-${ag0RepositoryColon}\}\${AG0_FROM_TAG:-${ag0FromTag}\} as prepare-${proposalName}
ENV UPGRADE_TO=${to}
# put env functions into shell environment
Expand All @@ -39,10 +43,10 @@ RUN /usr/src/upgrade-test-scripts/run_prepare_zero.sh
* Resume from state of an existing image.
* Creates a "use" stage upon which a PREPARE or EVAL can stack.
*/
RESUME(fromTag: string) {
RESUME(fromTag: string, repositoryColon: string) {
return `
## RESUME
FROM ghcr.io/agoric/agoric-3-proposals:${fromTag} as use-${fromTag}
FROM \${REPOSITORY_COLON:-${repositoryColon}\}\${FROM_TAG:-${fromTag}\} as use-${fromTag}
`;
},

Expand Down Expand Up @@ -84,15 +88,13 @@ RUN ./run_prepare.sh ${path}
* - Start agd with the SDK that has the upgradeHandler
* - Run any core-evals associated with the proposal (either the ones specified in prepare, or straight from the proposal)
*/
EXECUTE({
path,
planName,
proposalName,
sdkImageTag,
}: SoftwareUpgradePackage) {
EXECUTE(
{ path, planName, proposalName, sdkImageTag }: SoftwareUpgradePackage,
sdkRepositoryColon: string,
) {
return `
# EXECUTE ${proposalName}
FROM ghcr.io/agoric/agoric-sdk:${sdkImageTag} as execute-${proposalName}
FROM \${SDK_REPOSITORY_COLON:-${sdkRepositoryColon}\}\${SDK_TAG:-${sdkImageTag}\} as execute-${proposalName}
WORKDIR /usr/src/upgrade-test-scripts
Expand Down Expand Up @@ -175,12 +177,16 @@ ENTRYPOINT ./run_test.sh ${path}
/**
* The last target in the file, for untargeted `docker build`
*/
LAST(lastProposal: ProposalInfo) {
LAST(lastProposal: ProposalInfo, repositoryColon: string) {
// 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;
const useImage = imageNameForProposal(
lastProposal,
'use',
repositoryColon,
).name;
return `
# LAST
FROM ${useImage} as latest
Expand All @@ -190,34 +196,67 @@ FROM ${useImage} as latest

export function writeBakefileProposals(
allProposals: ProposalInfo[],
platforms?: Platform[],
platforms: Platform[] | null,
fromTag: string | null,
repositoryColon: string,
sdkRepositoryColon: string,
ag0FromTag: string,
ag0RepositoryColon: string,
) {
const json = {
variable: {
PLATFORMS: {
default: platforms || null,
default: platforms,
},
PROPOSALS: {
default: allProposals.map(p => p.proposalName),
},
FROM_TAG: {
default: fromTag,
},
REPOSITORY_COLON: {
default: repositoryColon,
},
SDK_REPOSITORY_COLON: {
default: sdkRepositoryColon,
},
AG0_FROM_TAG: {
default: ag0FromTag,
},
AG0_REPOSITORY_COLON: {
default: ag0RepositoryColon,
},
},
};
fs.writeFileSync('docker-bake.json', JSON.stringify(json, null, 2));
}

export function writeDockerfile(
allProposals: ProposalInfo[],
fromTag?: string | null,
fromTag: string | null,
repositoryColon: string,
sdkRepositoryColon: string,
ag0FromTag: string,
ag0RepositoryColon: string,
) {
// Each stage tests something about the left argument and prepare an upgrade to the right side (by passing the proposal and halting the chain.)
// The upgrade doesn't happen until the next stage begins executing.
const blocks: string[] = ['# syntax=docker/dockerfile:1.4'];
const blocks: string[] = [
`\
# syntax=docker/dockerfile:1.4
ARG FROM_TAG
ARG REPOSITORY_COLON
ARG SDK_TAG
ARG SDK_REPOSITORY_COLON
ARG AG0_FROM_TAG
ARG AG0_REPOSITORY_COLON`,
];

let previousProposal: ProposalInfo | null = null;

// appending to a previous image, so set up the 'use' stage
if (fromTag) {
blocks.push(stage.RESUME(fromTag));
blocks.push(stage.RESUME(fromTag, repositoryColon));
// define a previous proposal that matches what later stages expect
previousProposal = {
proposalName: fromTag,
Expand Down Expand Up @@ -245,10 +284,15 @@ export function writeDockerfile(
blocks.push(stage.PREPARE(proposal, previousProposal));
} else {
blocks.push(
stage.PREPARE_ZERO(proposal.proposalName, proposal.planName),
stage.PREPARE_ZERO(
proposal.proposalName,
proposal.planName,
ag0FromTag,
ag0RepositoryColon,
),
);
}
blocks.push(stage.EXECUTE(proposal));
blocks.push(stage.EXECUTE(proposal, sdkRepositoryColon));
break;
default:
// UNTIL https://github.com/Agoric/agoric-3-proposals/issues/77
Expand All @@ -265,7 +309,7 @@ export function writeDockerfile(
// If one of the proposals is a passed proposal, make the latest one the default entrypoint
const lastPassed = allProposals.findLast(isPassed);
if (lastPassed) {
blocks.push(stage.LAST(lastPassed));
blocks.push(stage.LAST(lastPassed, repositoryColon));
}

const contents = blocks.join('\n');
Expand Down
5 changes: 2 additions & 3 deletions packages/synthetic-chain/src/cli/proposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import assert from 'node:assert';
import fs from 'node:fs';
import * as path from 'node:path';

export const repository = 'ghcr.io/agoric/agoric-3-proposals';

type ProposalCommon = {
path: string; // in the proposals directory
proposalName: string;
Expand Down Expand Up @@ -89,10 +87,11 @@ export function readProposals(proposalsParent: string): ProposalInfo[] {
export function imageNameForProposal(
proposal: Pick<ProposalCommon, 'proposalName'>,
stage: 'test' | 'use',
repositoryColon: string,
) {
const target = `${stage}-${proposal.proposalName}`;
return {
name: `${repository}:${target}`,
name: `${repositoryColon}${target}`,
target,
};
}
Expand Down
8 changes: 4 additions & 4 deletions packages/synthetic-chain/src/cli/run.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { execSync } from 'node:child_process';
import { ProposalInfo, imageNameForProposal } from './proposals.js';

export const runTestImage = (proposal: ProposalInfo) => {
export const runTestImage = (proposal: ProposalInfo, repositoryColon: string) => {
console.log(`Running test image for proposal ${proposal.proposalName}`);
const { name } = imageNameForProposal(proposal, 'test');
const { name } = imageNameForProposal(proposal, 'test', repositoryColon);
// 'rm' to remove the container when it exits
const cmd = `docker run --rm ${name}`;
execSync(cmd, { stdio: 'inherit' });
};

export const debugTestImage = (proposal: ProposalInfo) => {
const { name } = imageNameForProposal(proposal, 'test');
export const debugTestImage = (proposal: ProposalInfo, repositoryColon: string) => {
const { name } = imageNameForProposal(proposal, 'test', repositoryColon);
console.log(
`
Starting chain of test image for proposal ${proposal.proposalName}
Expand Down
4 changes: 2 additions & 2 deletions packages/synthetic-chain/test/test-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ test('imageNameForProposal', t => {
source: 'build',
buildScript: 'n/a',
};
t.deepEqual(imageNameForProposal(proposal, 'test'), {
name: 'ghcr.io/agoric/agoric-3-proposals:test-foo',
t.deepEqual(imageNameForProposal(proposal, 'test', 'example.net/org/repo:'), {
name: 'example.net/org/repo:test-foo',
target: 'test-foo',
});
});

0 comments on commit 4ff1ddc

Please sign in to comment.