Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

solve disk usage #79

Merged
merged 7 commits into from
Jan 26, 2024
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
20 changes: 17 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,19 @@ 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

- run: docker system df
- run: docker buildx du --verbose
- run: df -h
Comment on lines +111 to +114
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these blocks aren't necessary for main, but I'd like to leave it in this PR.

  1. to not delay merge by another run (taking over an hour)
  2. to have the diagnostics a little longer while the change settles

If #2 is not compelling, I'm open to cutting e8fce15 from this branch and merging without another green.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diagnostics of this sort are somewhere between cost-effective and priceless, IME. (unless they're very, very verbose; I haven't checked in this case).


# Test before pushing the images.
- name: Build and run proposal tests
if: ${{ matrix.platform == env.X86_PLATFORM }}
run: node_modules/.bin/synthetic-chain test
run: yarn test

- run: docker system df
- run: docker buildx du --verbose
- run: df -h

# 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
Expand All @@ -132,6 +138,10 @@ jobs:
${{ steps.meta.outputs.bake-file }}
targets: use

- run: docker system df
- run: docker buildx du --verbose
- run: df -h

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

- run: docker system df
- run: docker buildx du --verbose
- run: df -h

# Merge the default image from each platform into one multi-arch image,
# then publish that multiarch image.
docker-publish-multiarch:
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,21 @@ A known issue is that `yarn synthetic-chain` files with `Unknown file extension
To build the test images,

```
node_modules/.bin/synthetic-chain build
tsx packages/synthetic-chain build
```

To build the test images for particular proposals,

```
# build just upgrades
node_modules/.bin/synthetic-chain build --match upgrade
tsx packages/synthetic-chain build --match upgrade
```

To run the tests for particular proposals,

```
# build just upgrades
node_modules/.bin/synthetic-chain test --match upgrade
tsx packages/synthetic-chain test --match upgrade
```

## Debugging
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"packages/*"
],
"scripts": {
"build": "echo Use synthetic-chain to build proposal images",
"test": "echo Use synthetic-chain to test proposal images",
"build": "tsx packages/synthetic-chain build",
"test": "tsx packages/synthetic-chain test",
"format": "prettier --write ."
},
"dependencies": {
Expand Down
31 changes: 22 additions & 9 deletions packages/synthetic-chain/cli.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
#!/usr/bin/env tsx

import { parseArgs } from 'node:util';
import path from 'node:path';
import { execSync } from 'node:child_process';
import path from 'node:path';
import { parseArgs } from 'node:util';
import {
bakeTarget,
buildProposalSubmissions,
bakeImages,
readBuildConfig,
} from './src/cli/build.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';
import { imageNameForProposal, matchOneProposal, readProposals } from './src/cli/proposals.js';
import { debugTestImage, runTestImage } from './src/cli/run.js';

const { positionals, values } = parseArgs({
options: {
Expand Down Expand Up @@ -62,18 +62,31 @@ const prepareDockerBuild = () => {
switch (cmd) {
case 'build': {
prepareDockerBuild();
bakeImages('use', values.dry);
bakeTarget('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!));
const proposal = matchOneProposal(proposals, match!);
bakeTarget(imageNameForProposal(proposal, 'test').target, values.dry);
debugTestImage(proposal);
// don't bother to delete the test image because there's just one
// and the user probably wants to run it again.
} else {
runTestImages(proposals);
for (const proposal of proposals) {
const image = imageNameForProposal(proposal, 'test');
bakeTarget(image.target, values.dry);
runTestImage(proposal);
// delete the image to reclaim disk space. The next build
// will use the build cache.
execSync('docker system df', { stdio: 'inherit' });
execSync(`docker rmi ${image.name}`, { stdio: 'inherit' });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gather this docker rmi ... is the gist of it.

execSync('docker system df', { stdio: 'inherit' });
}
}
break;
case 'doctor':
Expand Down
2 changes: 1 addition & 1 deletion packages/synthetic-chain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.3",
"description": "Utilities to build a chain and test proposals atop it",
"bin": "./cli.ts",
"main": "index.js",
"main": "cli.ts",
"type": "module",
"files": [
"index.js",
Expand Down
11 changes: 7 additions & 4 deletions packages/synthetic-chain/src/cli/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,16 @@ export const buildProposalSubmissions = (proposals: ProposalInfo[]) => {

/**
* Bake images using the docker buildx bake command.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with docker buildx bake. But I gather it's working...

*
* Note this uses `--load` which pushes the completed images to the builder,
* consuming 2-3 GB per image.
* @see {@link https://docs.docker.com/engine/reference/commandline/buildx_build/#load}
*
* @param matrix - The group target
* @param target - The image or 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' : ''}`;
export const bakeTarget = (target: string, dry = false) => {
const cmd = `docker buildx bake --load ${target} ${dry ? '--print' : ''}`;
console.log(cmd);
execSync(cmd, { stdio: 'inherit' });
};
31 changes: 13 additions & 18 deletions packages/synthetic-chain/src/cli/dockerfileGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ FROM use-${lastProposal.proposalName} as prepare-${proposalName}
ENV UPGRADE_TO=${planName} UPGRADE_INFO=${JSON.stringify(
encodeUpgradeInfo(upgradeInfo),
)}
# base is a fresh sdk image so copy these supports
COPY --link --chmod=755 ./upgrade-test-scripts/env_setup.sh ./upgrade-test-scripts/start_to_to.sh /usr/src/upgrade-test-scripts/

WORKDIR /usr/src/upgrade-test-scripts
SHELL ["/bin/bash", "-c"]
Expand All @@ -76,17 +74,25 @@ RUN ./start_to_to.sh
* - 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({ proposalName, sdkImageTag }: SoftwareUpgradeProposal) {
EXECUTE({
proposalIdentifier,
proposalName,
sdkImageTag,
}: SoftwareUpgradeProposal) {
return `
# EXECUTE ${proposalName}
FROM ghcr.io/agoric/agoric-sdk:${sdkImageTag} as execute-${proposalName}

# base is a fresh sdk image so copy these supports
COPY --link --chmod=755 ./upgrade-test-scripts/env_setup.sh ./upgrade-test-scripts/start_to_to.sh /usr/src/upgrade-test-scripts/
WORKDIR /usr/src/upgrade-test-scripts

# base is a fresh sdk image so set up the proposal and its dependencies
COPY --link --chmod=755 ./proposals/${proposalIdentifier}:${proposalName} /usr/src/proposals/${proposalIdentifier}:${proposalName}
COPY --link --chmod=755 ./upgrade-test-scripts/env_setup.sh ./upgrade-test-scripts/start_to_to.sh ./upgrade-test-scripts/install_deps.sh /usr/src/upgrade-test-scripts/
# XXX this hits the network each time because the fresh base lacks the global Yarn cache from the previous proposal's build
RUN ./install_deps.sh ${proposalIdentifier}:${proposalName}

COPY --link --from=prepare-${proposalName} /root/.agoric /root/.agoric

WORKDIR /usr/src/upgrade-test-scripts
SHELL ["/bin/bash", "-c"]
RUN ./start_to_to.sh
`;
Expand All @@ -107,7 +113,7 @@ COPY --link --chmod=755 ./proposals/${proposalIdentifier}:${proposalName} /usr/s

WORKDIR /usr/src/upgrade-test-scripts

# install using global cache
# First stage of this proposal so install its deps.
COPY --link ./upgrade-test-scripts/install_deps.sh /usr/src/upgrade-test-scripts/
RUN --mount=type=cache,target=/root/.yarn ./install_deps.sh ${proposalIdentifier}:${proposalName}

Expand All @@ -128,15 +134,8 @@ RUN ./run_eval.sh ${proposalIdentifier}:${proposalName}
# USE ${proposalName}
FROM ${previousStage}-${proposalName} as use-${proposalName}

COPY --link --chmod=755 ./proposals/${proposalIdentifier}:${proposalName} /usr/src/proposals/${proposalIdentifier}:${proposalName}

WORKDIR /usr/src/upgrade-test-scripts

# TODO remove network dependencies in stages
# install using global cache
COPY --link ./upgrade-test-scripts/install_deps.sh /usr/src/upgrade-test-scripts/
RUN --mount=type=cache,target=/root/.yarn ./install_deps.sh ${proposalIdentifier}:${proposalName}

COPY --link --chmod=755 ./upgrade-test-scripts/run_use.sh /usr/src/upgrade-test-scripts/
SHELL ["/bin/bash", "-c"]
RUN ./run_use.sh ${proposalIdentifier}:${proposalName}
Expand All @@ -157,10 +156,6 @@ FROM use-${proposalName} as test-${proposalName}

WORKDIR /usr/src/upgrade-test-scripts

# install using global cache
COPY --link ./upgrade-test-scripts/install_deps.sh /usr/src/upgrade-test-scripts/
RUN --mount=type=cache,target=/root/.yarn ./install_deps.sh ${proposalIdentifier}:${proposalName}

# copy run_test for this entrypoint and start_agd for optional debugging
COPY --link --chmod=755 ./upgrade-test-scripts/run_test.sh ./upgrade-test-scripts/start_agd.sh /usr/src/upgrade-test-scripts/
SHELL ["/bin/bash", "-c"]
Expand Down
4 changes: 4 additions & 0 deletions packages/synthetic-chain/src/cli/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ const fixupProposal = (proposal: ProposalInfo) => {
}

// default to node-modules linker
// (The pnpm linker has little benefit because hard links can't cross
// volumes so each Docker layer will have copies of the deps anyway. The
// pnp linker might work but requires other changes.)
const yarnRc = path.join(proposalPath, '.yarnrc.yml');
if (!fs.existsSync(yarnRc)) {
console.log(`creating ${yarnRc} with node-modules linker`);
fs.writeFileSync(yarnRc, 'nodeLinker: node-modules\n');
}

// refresh install
execSync('rm -rf node_modules', { cwd: proposalPath });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

execSync('yarn install', { cwd: proposalPath });
}
}
Expand Down
14 changes: 6 additions & 8 deletions packages/synthetic-chain/src/cli/run.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { execSync } from 'node:child_process';
import { ProposalInfo, imageNameForProposal } from './proposals.js';

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the API change from runTestImages to runTestImage sets off my backward-compatibility sensor.
But I don't suppose that's all that relevant in this context.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the CLI's modules aren't part of the package API.

The package doesn't yet define "exports" so they technically could be imported somewhere, but I think it's a reasonable expectation that cli not be.

That said, this package doesn't claim backwards compatibility because, following semver, it's not yet 1.0.

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

export const debugTestImage = (proposal: ProposalInfo) => {
Expand Down
9 changes: 6 additions & 3 deletions packages/synthetic-chain/upgrade-test-scripts/install_deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ PROPOSAL_PATH=$1
export YARN_IGNORE_NODE=1

corepack enable
yarn --version

# Run where this script is
cd "$(dirname "$(realpath -- "$0")")"

# TODO consider yarn workspaces to install all in one command
if [ -n "$PROPOSAL_PATH" ]; then
cd "../proposals/$PROPOSAL_PATH"
# install only if the proposal has a yarn.lock
test -n "yarn.lock" && yarn install --immutable

if test -f "yarn.lock"; then
yarn --version # only Berry supported, so next commands will fail on classic
yarn config set --home enableTelemetry 0
yarn install --immutable
fi
fi