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

Shell with config based machine #83

Merged
merged 4 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 3 additions & 2 deletions apps/cli/src/builder/directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const build = async (
name,
"--readjustment",
extraSize,
filename,
];
await execaDockerFallback(command, args, {
cwd: destination,
Expand All @@ -42,15 +43,15 @@ export const build = async (
const compression = "lzo"; // make customizable? default is gzip
const command = "mksquashfs";
const args = [
name,
filename,
"-all-time",
"0",
"-all-root", // XXX: should we use this?
"-noappend",
"-comp",
compression,
"-no-progress",
name,
filename,
];
await execaDockerFallback(command, args, {
cwd: destination,
Expand Down
12 changes: 7 additions & 5 deletions apps/cli/src/builder/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { tarToExt } from "./index.js";

type ImageBuildOptions = Pick<
DockerDriveConfig,
"dockerfile" | "tags" | "target"
"context" | "dockerfile" | "tags" | "target"
>;

type ImageInfo = {
Expand All @@ -22,7 +22,7 @@ type ImageInfo = {
* Build Docker image (linux/riscv64). Returns image id.
*/
const buildImage = async (options: ImageBuildOptions): Promise<string> => {
const { dockerfile, tags, target } = options;
const { context, dockerfile, tags, target } = options;
const buildResult = tmp.tmpNameSync();
const args = [
"buildx",
Expand All @@ -32,6 +32,7 @@ const buildImage = async (options: ImageBuildOptions): Promise<string> => {
"--load",
"--iidfile",
buildResult,
context,
];

// set tags for the image built
Expand All @@ -41,7 +42,7 @@ const buildImage = async (options: ImageBuildOptions): Promise<string> => {
args.push("--target", target);
}

await execa("docker", [...args, process.cwd()], { stdio: "inherit" });
await execa("docker", args, { stdio: "inherit" });
return fs.readFileSync(buildResult, "utf8");
};

Expand Down Expand Up @@ -123,6 +124,8 @@ export const build = async (
const compression = "lzo"; // make customizable? default is gzip
const command = "mksquashfs";
const args = [
"-",
filename,
"-tar",
"-all-time",
"0",
Expand All @@ -131,12 +134,11 @@ export const build = async (
"-comp",
compression,
"-no-progress",
filename,
];
await execaDockerFallback(command, args, {
cwd: destination,
image: sdkImage,
inputFile: tar,
inputFile: path.join(destination, tar),
});
break;
}
Expand Down
5 changes: 3 additions & 2 deletions apps/cli/src/builder/tar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export const build = async (
const compression = "lzo"; // make customizable? default is gzip
const command = "mksquashfs";
const args = [
"-",
filename,
"-tar",
"-all-time",
"0",
Expand All @@ -36,12 +38,11 @@ export const build = async (
"-comp",
compression,
"-no-progress",
filename,
];
await execaDockerFallback(command, args, {
cwd: destination,
image: sdkImage,
inputFile: tar,
inputFile: path.join(destination, tar),
});
break;
}
Expand Down
95 changes: 3 additions & 92 deletions apps/cli/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,8 @@ import {
buildNone,
buildTar,
} from "../builder/index.js";
import { Config, DriveConfig } from "../config.js";
import { execaDockerFallback } from "../exec.js";

type ImageInfo = {
cmd: string[];
entrypoint: string[];
env: string[];
workdir: string;
};

type DriveResult = ImageInfo | undefined | void;
import { DriveConfig, DriveResult } from "../config.js";
import { bootMachine } from "../machine.js";

const buildDrive = async (
name: string,
Expand All @@ -47,86 +38,6 @@ const buildDrive = async (
}
};

const bootMachine = async (
config: Config,
info: ImageInfo | undefined,
sdkImage: string,
destination: string,
) => {
const { machine } = config;
const { assertRollingTemplate, maxMCycle, noRollup, ramLength, ramImage } =
machine;

// list of environment variables of docker image
const env = info?.env ?? [];
const envs = env.map(
(variable) => `--append-entrypoint=export "${variable}"`,
);

// bootargs from config string array
const bootargs = machine.bootargs.map(
(arg) => `--append-bootargs="${arg}"`,
);

// entrypoint from config or image info (Docker ENTRYPOINT + CMD)
const entrypoint =
machine.entrypoint ?? // takes priority
(info ? [...info.entrypoint, ...info.cmd].join(" ") : undefined); // ENTRYPOINT and CMD as a space separated string

if (!entrypoint) {
throw new Error("Undefined machine entrypoint");
}

const flashDrives = Object.entries(config.drives).map(([label, drive]) => {
const { format, mount, shared, user } = drive;
// TODO: filename should be absolute dir inside docker container
const filename = `${label}.${format}`;
const vars = [`label:${label}`, `filename:${filename}`];
if (mount) {
vars.push(`mount:${mount}`);
}
if (user) {
vars.push(`user:${user}`);
}
if (shared) {
vars.push("shared");
}
// don't specify start and length
return `--flash-drive=${vars.join(",")}`;
});

// command to change working directory if WORKDIR is defined
const command = "cartesi-machine";
const args = [
...bootargs,
...envs,
...flashDrives,
`--ram-image=${ramImage}`,
`--ram-length=${ramLength}`,
"--final-hash",
"--store=image",
`--append-entrypoint=${entrypoint}`,
];
if (info?.workdir) {
args.push(`--append-init=WORKDIR="${info.workdir}"`);
}
if (noRollup) {
args.push("--no-rollup");
}
if (maxMCycle) {
args.push(`--max-mcycle=${maxMCycle.toString()}`);
}
if (assertRollingTemplate) {
args.push("--assert-rolling-template");
}

return execaDockerFallback(command, args, {
cwd: destination,
image: sdkImage,
stdio: "inherit",
});
};

export default class Build extends BaseCommand<typeof Build> {
static summary = "Build application.";

Expand Down Expand Up @@ -186,7 +97,7 @@ export default class Build extends BaseCommand<typeof Build> {
const snapshotPath = this.getContextPath("image");

// create machine snapshot
await bootMachine(config, imageInfo, config.sdk, destination);
await bootMachine(config, imageInfo, destination);

await fs.chmod(snapshotPath, 0o755);
}
Expand Down
86 changes: 45 additions & 41 deletions apps/cli/src/commands/shell.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Args, Flags } from "@oclif/core";
import { execa } from "execa";
import fs from "fs-extra";
import { lookpath } from "lookpath";
import path from "path";
import { BaseCommand } from "../baseCommand.js";
import { ImageInfo } from "../config.js";
import { bootMachine } from "../machine.js";

export default class Shell extends BaseCommand<typeof Shell> {
static description = "Start a shell in cartesi machine of application";
Expand All @@ -18,62 +18,66 @@ export default class Shell extends BaseCommand<typeof Shell> {
};

static flags = {
config: Flags.file({
char: "c",
default: "cartesi.toml",
summary: "path to the configuration file",
}),
"run-as-root": Flags.boolean({
description: "run as root user",
default: false,
}),
};

private async startShell(
ext2Path: string,
runAsRoot: boolean,
): Promise<void> {
const containerDir = "/mnt";
const bind = `${path.resolve(path.dirname(ext2Path))}:${containerDir}`;
const ext2 = path.join(containerDir, path.basename(ext2Path));
const ramSize = "128Mi";
const driveLabel = "root";
const sdkImage = "cartesi/sdk:0.10.0"; // XXX: how to resolve sdk version?
const args = [
"run",
"--interactive",
"--tty",
"--volume",
bind,
sdkImage,
"cartesi-machine",
`--ram-length=${ramSize}`,
"--append-bootargs=no4lvl",
`--flash-drive=label:${driveLabel},filename:${ext2}`,
];
public async run(): Promise<void> {
const { flags } = await this.parse(Shell);

// get application configuration from 'cartesi.toml'
const config = this.getApplicationConfig(flags.config);

// destination directory for image and intermediate files
const destination = path.resolve(this.getContextPath());

if (runAsRoot) {
args.push("--append-init=USER=root");
// check if all drives are built
for (const [name, drive] of Object.entries(config.drives)) {
const filename = `${name}.${drive.format}`;
const pathname = this.getContextPath(filename);
if (!fs.existsSync(pathname)) {
throw new Error(
`drive '${name}' not built, run '${this.config.bin} build'`,
);
}
}

// create shell entrypoint
const info: ImageInfo = {
cmd: [],
entrypoint: ["/bin/bash"],
tuler marked this conversation as resolved.
Show resolved Hide resolved
env: [],
workdir: "/",
};

// start with interactive mode on
config.machine.interactive = true;

/* why this?
tuler marked this conversation as resolved.
Show resolved Hide resolved
if (!(await lookpath("stty"))) {
args.push("-i");
} else {
args.push("-it");
}
*/

await execa("docker", [...args, "--", "/bin/bash"], {
stdio: "inherit",
});
}
// interactive mode can't have final hash
config.machine.finalHash = false;

public async run(): Promise<void> {
const { flags } = await this.parse(Shell);
// do not store machine in interactive mode
config.machine.store = undefined;

// use pre-existing image or build dapp image
const ext2Path = this.getContextPath("root.ext2");
if (!fs.existsSync(ext2Path)) {
throw new Error(
`machine not built, run '${this.config.bin} build'`,
);
}
// run as root if flag is set
config.machine.user = flags["run-as-root"] ? "root" : undefined;

// execute the machine and save snapshot
await this.startShell(ext2Path, flags["run-as-root"]);
// boot machine
await bootMachine(config, info, destination);
}
}
Loading
Loading