-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): build based on configuration
- Loading branch information
Showing
28 changed files
with
1,232 additions
and
281 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import fs from "fs-extra"; | ||
import path from "path"; | ||
import { DirectoryDriveConfig } from "../config.js"; | ||
import { execaDockerFallback } from "../exec.js"; | ||
|
||
export const build = async ( | ||
name: string, | ||
drive: DirectoryDriveConfig, | ||
sdkImage: string, | ||
destination: string, | ||
): Promise<void> => { | ||
const filename = `${name}.${drive.format}`; | ||
const blockSize = 4096; // fixed at 4k | ||
const extraBlocks = Math.ceil(drive.extraSize / blockSize); | ||
const extraSize = `+${extraBlocks}`; | ||
|
||
// copy directory to destination | ||
const dest = path.join(destination, name); | ||
await fs.mkdirp(dest); | ||
await fs.copy(drive.directory, dest); | ||
|
||
try { | ||
switch (drive.format) { | ||
case "ext2": { | ||
const command = "xgenext2fs"; | ||
const args = [ | ||
"--block-size", | ||
blockSize.toString(), | ||
"--faketime", | ||
"--root", | ||
name, | ||
"--readjustment", | ||
extraSize, | ||
]; | ||
await execaDockerFallback(command, args, { | ||
cwd: destination, | ||
image: sdkImage, | ||
}); | ||
break; | ||
} | ||
case "sqfs": { | ||
const compression = "lzo"; // make customizable? default is gzip | ||
const command = "mksquashfs"; | ||
const args = [ | ||
"-all-time", | ||
"0", | ||
"-all-root", // XXX: should we use this? | ||
"-noappend", | ||
"-comp", | ||
compression, | ||
"-no-progress", | ||
name, | ||
filename, | ||
]; | ||
await execaDockerFallback(command, args, { | ||
cwd: destination, | ||
image: sdkImage, | ||
}); | ||
} | ||
} | ||
} finally { | ||
// delete copied | ||
await fs.remove(dest); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import { execa } from "execa"; | ||
import fs from "fs-extra"; | ||
import path from "path"; | ||
import tmp from "tmp"; | ||
import { DockerDriveConfig } from "../config.js"; | ||
import { execaDockerFallback, spawnSyncDockerFallback } from "../exec.js"; | ||
import { tarToExt } from "./index.js"; | ||
|
||
type ImageBuildOptions = Pick< | ||
DockerDriveConfig, | ||
"dockerfile" | "tags" | "target" | ||
>; | ||
|
||
type ImageInfo = { | ||
cmd: string[]; | ||
entrypoint: string[]; | ||
env: string[]; | ||
workdir: string; | ||
}; | ||
|
||
/** | ||
* Build Docker image (linux/riscv64). Returns image id. | ||
*/ | ||
const buildImage = async (options: ImageBuildOptions): Promise<string> => { | ||
const { dockerfile, tags, target } = options; | ||
const buildResult = tmp.tmpNameSync(); | ||
const args = [ | ||
"buildx", | ||
"build", | ||
"--file", | ||
dockerfile, | ||
"--load", | ||
"--iidfile", | ||
buildResult, | ||
]; | ||
|
||
// set tags for the image built | ||
args.push(...tags.map((tag) => ["--tag", tag]).flat()); | ||
|
||
if (target) { | ||
args.push("--target", target); | ||
} | ||
|
||
await execa("docker", [...args, process.cwd()], { stdio: "inherit" }); | ||
return fs.readFileSync(buildResult, "utf8"); | ||
}; | ||
|
||
/** | ||
* Query the image using docker image inspect | ||
* @param image image id or name | ||
* @returns Information about the image | ||
*/ | ||
const getImageInfo = async (image: string): Promise<ImageInfo> => { | ||
const { stdout: jsonStr } = await execa("docker", [ | ||
"image", | ||
"inspect", | ||
image, | ||
]); | ||
// parse image info from docker inspect output | ||
const [imageInfo] = JSON.parse(jsonStr); | ||
|
||
// validate image architecture (must be riscv64) | ||
if (imageInfo["Architecture"] !== "riscv64") { | ||
throw new Error( | ||
`Invalid image Architecture: ${imageInfo["Architecture"]}. Expected riscv64`, | ||
); | ||
} | ||
|
||
const info: ImageInfo = { | ||
cmd: imageInfo["Config"]["Cmd"] ?? [], | ||
entrypoint: imageInfo["Config"]["Entrypoint"] ?? [], | ||
env: imageInfo["Config"]["Env"] || [], | ||
workdir: imageInfo["Config"]["WorkingDir"], | ||
}; | ||
|
||
return info; | ||
}; | ||
|
||
export const build = async ( | ||
name: string, | ||
drive: DockerDriveConfig, | ||
sdkImage: string, | ||
destination: string, | ||
): Promise<ImageInfo | undefined> => { | ||
const { format } = drive; | ||
|
||
const ocitar = `${name}.oci.tar`; | ||
const tar = `${name}.tar`; | ||
const filename = `${name}.${format}`; | ||
|
||
// use pre-existing image or build docker image | ||
const image = drive.image || (await buildImage(drive)); | ||
|
||
// get image info | ||
const imageInfo = await getImageInfo(image); | ||
|
||
try { | ||
// create OCI Docker tarball from Docker image | ||
await execa("docker", ["image", "save", image, "-o", ocitar], { | ||
cwd: destination, | ||
}); | ||
|
||
// create rootfs tar from OCI tar | ||
await spawnSyncDockerFallback("crane", ["export", "-", "-"], { | ||
stdio: [ | ||
fs.openSync(path.join(destination, ocitar), "r"), | ||
fs.openSync(path.join(destination, tar), "w"), | ||
"inherit", | ||
], | ||
image: sdkImage, | ||
}); | ||
|
||
switch (format) { | ||
case "ext2": { | ||
// create ext2 | ||
await tarToExt(tar, filename, format, drive.extraSize, { | ||
cwd: destination, | ||
image: sdkImage, | ||
}); | ||
break; | ||
} | ||
case "sqfs": { | ||
const compression = "lzo"; // make customizable? default is gzip | ||
const command = "mksquashfs"; | ||
const args = [ | ||
"-tar", | ||
"-all-time", | ||
"0", | ||
"-all-root", // XXX: should we use this? | ||
"-noappend", | ||
"-comp", | ||
compression, | ||
"-no-progress", | ||
filename, | ||
]; | ||
await execaDockerFallback(command, args, { | ||
cwd: destination, | ||
image: sdkImage, | ||
inputFile: tar, | ||
}); | ||
break; | ||
} | ||
} | ||
} finally { | ||
// delete intermediate files | ||
// await fs.remove(path.join(destination, ocitar)); | ||
// await fs.remove(path.join(destination, tar)); | ||
} | ||
|
||
return imageInfo; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import fs from "fs-extra"; | ||
import path from "path"; | ||
import { EmptyDriveConfig } from "../config.js"; | ||
import { execaDockerFallback } from "../exec.js"; | ||
|
||
export const build = async ( | ||
name: string, | ||
drive: EmptyDriveConfig, | ||
sdkImage: string, | ||
destination: string, | ||
): Promise<void> => { | ||
const filename = `${name}.${drive.format}`; | ||
switch (drive.format) { | ||
case "ext2": { | ||
const blockSize = 4096; // fixed at 4k | ||
const size = Math.ceil(drive.size / blockSize); // size in blocks | ||
const command = "xgenext2fs"; | ||
const args = [ | ||
"--block-size", | ||
blockSize.toString(), | ||
"--faketime", | ||
"--size-in-blocks", | ||
size.toString(), | ||
filename, | ||
]; | ||
await execaDockerFallback(command, args, { | ||
cwd: destination, | ||
image: sdkImage, | ||
}); | ||
break; | ||
} | ||
case "raw": { | ||
await fs.writeFile( | ||
path.join(destination, filename), | ||
Buffer.alloc(drive.size), | ||
); | ||
break; | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { execaDockerFallback, ExecaOptionsDockerFallback } from "../exec.js"; | ||
|
||
export { build as buildDirectory } from "./directory.js"; | ||
export { build as buildDocker } from "./docker.js"; | ||
export { build as buildEmpty } from "./empty.js"; | ||
export { build as buildNone } from "./none.js"; | ||
export { build as buildTar } from "./tar.js"; | ||
|
||
export const tarToExt = async ( | ||
input: string, | ||
output: string, | ||
format: "ext2", | ||
extraSize: number, | ||
options: ExecaOptionsDockerFallback, | ||
) => { | ||
const blockSize = 4096; // fixed at 4k | ||
const extraBlocks = Math.ceil(extraSize / blockSize); | ||
const adjustment = `+${extraBlocks}`; | ||
|
||
const command = "xgenext2fs"; | ||
const args = [ | ||
"--block-size", | ||
blockSize.toString(), | ||
"--faketime", | ||
"--readjustment", | ||
adjustment.toString(), | ||
"--tarball", | ||
input, | ||
output, | ||
]; | ||
return execaDockerFallback(command, args, options); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import fs from "fs-extra"; | ||
import path from "path"; | ||
import { ExistingDriveConfig, getDriveFormat } from "../config.js"; | ||
|
||
export const build = async ( | ||
name: string, | ||
drive: ExistingDriveConfig, | ||
destination: string, | ||
): Promise<void> => { | ||
// no need to build, drive already exists | ||
const src = drive.filename; | ||
const format = getDriveFormat(src); | ||
const filename = path.join(destination, `${name}.${format}`); | ||
|
||
// just copy it | ||
await fs.copyFile(src, filename); | ||
}; |
Oops, something went wrong.