Compare commits
No commits in common. "e9452dc659ecbce97b69f10d5130d9c172e7afee" and "67784da19994de510277497ae9d8b711c7d07122" have entirely different histories.
e9452dc659
...
67784da199
2 changed files with 39 additions and 74 deletions
|
@ -8,8 +8,7 @@ use std::{env, io, os::unix, path::Path, process};
|
|||
#[serde(rename_all = "PascalCase")]
|
||||
struct Config {
|
||||
env: Vec<String>,
|
||||
cmd: Option<Vec<String>>,
|
||||
entrypoint: Option<Vec<String>>,
|
||||
cmd: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
@ -40,20 +39,8 @@ fn main() -> Result<(), io::Error> {
|
|||
unix::fs::chroot(src_path)?;
|
||||
env::set_current_dir("/")?;
|
||||
|
||||
// https://github.com/opencontainers/image-spec/blob/b5ec432b1c946c09e1568b18ef70b654a93739f6/conversion.md#verbatim-fields
|
||||
let args = match (config.config.cmd, config.config.entrypoint) {
|
||||
(None, Some(args)) => args,
|
||||
(Some(args), None) => args,
|
||||
(None, None) => panic!("Invalid config"),
|
||||
(Some(cmd), Some(entrypoint)) => {
|
||||
let mut args = entrypoint.clone();
|
||||
args.append(&mut cmd.clone());
|
||||
args
|
||||
}
|
||||
};
|
||||
|
||||
let mut child = process::Command::new(&args[0])
|
||||
.args(&args[1..])
|
||||
let mut child = process::Command::new(&config.config.cmd[0])
|
||||
.args(&config.config.cmd[1..])
|
||||
.env_clear()
|
||||
.envs(config.config.env.iter().map(|s| s.split_once('=').unwrap()))
|
||||
.spawn()?;
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { execFile as _execFile } from "node:child_process";
|
||||
import { access, mkdir, open, writeFile } from "node:fs/promises";
|
||||
import { access, mkdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { tar2squashfs } from "./tar2squashfs.js";
|
||||
import { subtle } from "node:crypto";
|
||||
import { Writable } from "node:stream";
|
||||
|
||||
type RegistryName = string;
|
||||
type RegistrySecret = string;
|
||||
|
@ -28,7 +27,7 @@ export function parseImageRef(ref: string): { image: string; tag: string } {
|
|||
|
||||
export async function downloadImage(image: string, tag: string) {
|
||||
await mkdir(tmpDir, { recursive: true });
|
||||
const manifest = await getManifest(image, tag);
|
||||
const manifest = await getContainerManifest(image, tag);
|
||||
|
||||
// sanity check
|
||||
if (
|
||||
|
@ -80,14 +79,6 @@ async function saveSquashfs(
|
|||
await access(output);
|
||||
// ya está cacheado
|
||||
} catch {
|
||||
if (process.env.DEBUG_WRITE_BLOBS)
|
||||
await Promise.all(
|
||||
manifest.layers.map(async (layer) => {
|
||||
const res = await getBlob(image, layer.digest);
|
||||
const file = await open(layer.digest, "w");
|
||||
await res.body!.pipeTo(Writable.toWeb(file.createWriteStream()));
|
||||
})
|
||||
);
|
||||
const layerStreams = manifest.layers.map(async (layer) => {
|
||||
const res = await getBlob(image, layer.digest);
|
||||
return res.body!;
|
||||
|
@ -140,19 +131,6 @@ async function call(registryName: string, path: string, init: RequestInit) {
|
|||
return res;
|
||||
}
|
||||
|
||||
// https://github.com/opencontainers/image-spec/blob/v1.0.1/image-index.md
|
||||
type ManifestIndex = {
|
||||
schemaVersion: 2;
|
||||
mediaType: "application/vnd.oci.image.index.v1+json";
|
||||
manifests: ManifestPointer[];
|
||||
};
|
||||
type DockerManifestV2 = {
|
||||
schemaVersion: 2;
|
||||
mediaType: "application/vnd.docker.distribution.manifest.v2+json";
|
||||
config: Descriptor;
|
||||
layers: DockerManifestV2Layer[];
|
||||
};
|
||||
|
||||
type ManifestPointer = {
|
||||
mediaType: "application/vnd.oci.image.manifest.v1+json";
|
||||
digest: string;
|
||||
|
@ -163,9 +141,13 @@ type ManifestPointer = {
|
|||
};
|
||||
annotations?: { [k: string]: string };
|
||||
};
|
||||
type DockerManifestV2Layer = {
|
||||
mediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip";
|
||||
} & Descriptor;
|
||||
|
||||
// https://github.com/opencontainers/image-spec/blob/v1.0.1/image-index.md
|
||||
type ManifestIndex = {
|
||||
mediaType: "application/vnd.oci.image.index.v1+json";
|
||||
schemaVersion: 2;
|
||||
manifests: ManifestPointer[];
|
||||
};
|
||||
|
||||
function getImageParts(image: string): [string, string] {
|
||||
const parts = image.split("/");
|
||||
|
@ -174,43 +156,35 @@ function getImageParts(image: string): [string, string] {
|
|||
return [registryName, imgPath];
|
||||
}
|
||||
|
||||
async function getManifest(image: string, tag: string): Promise<Manifest> {
|
||||
async function getContainerManifest(image: string, tag: string) {
|
||||
const index = await getManifestIndex(image, tag);
|
||||
// badly behaved registries will return whatever they want
|
||||
if (
|
||||
(index.mediaType as string) === "application/vnd.oci.image.manifest.v1+json"
|
||||
) {
|
||||
return index as unknown as Manifest;
|
||||
}
|
||||
const arch = "amd64";
|
||||
const ptr = chooseManifest(index, arch);
|
||||
if (!ptr)
|
||||
throw new Error(`Image ${image}:${tag} doesn't exist for arch ${arch}`);
|
||||
const manifest = await getManifest(image, ptr);
|
||||
return manifest;
|
||||
}
|
||||
|
||||
async function getManifestIndex(
|
||||
image: string,
|
||||
tag: string
|
||||
): Promise<ManifestIndex> {
|
||||
const [registryName, imgPath] = getImageParts(image);
|
||||
|
||||
const res = await call(registryName, `/${imgPath}/manifests/${tag}`, {
|
||||
headers: {
|
||||
Accept: "application/vnd.oci.image.index.v1+json",
|
||||
},
|
||||
});
|
||||
const json = (await res.json()) as object;
|
||||
if (!("mediaType" in json)) throw new Error(`Received invalid manifest`);
|
||||
|
||||
if (
|
||||
json.mediaType === "application/vnd.docker.distribution.manifest.v2+json"
|
||||
) {
|
||||
const manifest = json as DockerManifestV2;
|
||||
return {
|
||||
mediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
config: {
|
||||
...manifest.config,
|
||||
mediaType: "application/vnd.oci.image.config.v1+json",
|
||||
},
|
||||
layers: manifest.layers.map((l) => ({
|
||||
...l,
|
||||
mediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
|
||||
})),
|
||||
};
|
||||
} else if (json.mediaType === "application/vnd.oci.image.manifest.v1+json") {
|
||||
return json as Manifest;
|
||||
} else if (json.mediaType === "application/vnd.oci.image.index.v1+json") {
|
||||
const index = json as ManifestIndex;
|
||||
// XXX: hardcoded amd64
|
||||
const arch = "amd64";
|
||||
const manifestPtr = chooseManifest(index, arch);
|
||||
if (!manifestPtr) throw new Error(`No manifest for arch ${arch}`);
|
||||
|
||||
const manifest = await jsonBlob<Manifest>(image, manifestPtr.digest);
|
||||
return manifest;
|
||||
} else throw new Error(`Received invalid manifest`);
|
||||
const json = await res.json();
|
||||
return json as ManifestIndex;
|
||||
}
|
||||
|
||||
function chooseManifest(list: ManifestIndex, arch: string) {
|
||||
|
@ -266,3 +240,7 @@ type Manifest = {
|
|||
| "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip";
|
||||
} & Descriptor)[];
|
||||
};
|
||||
|
||||
async function getManifest(image: string, ptr: ManifestPointer) {
|
||||
return await jsonBlob<Manifest>(image, ptr.digest);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue