// // Images, optionally grouped by repository. // const { Element } = require("./Element"); const format = require("../../Utilities/Format"); const INFRA_PREFIXES = [ "ghcr.io/apple/container-builder-shim/", "ghcr.io/apple/containerization/vminit" ]; class ImageModel extends Element { constructor(json) { let configuration = (json && json.configuration) || {}; super(`image:${configuration.name}`); this.reference = configuration.name || ""; this.digest = (configuration.descriptor && configuration.descriptor.digest) || ""; // The CLI identifies images by the first twelve hex digits of the digest. this.shortDigest = this.digest.replace(/^[a-z0-9]+:/, "").slice(0, 12); this.id = json.id || ""; this.created = configuration.creationDate || null; let parts = format.reference(this.reference); this.registry = parts.registry; this.repository = parts.repository; this.tag = parts.tag; this.short = parts.short; this.variants = (json.variants || []).map((variant) => ({ architecture: (variant.platform && variant.platform.architecture) || "", os: (variant.platform && variant.platform.os) || "", size: variant.size || 0 })); this.size = this.variants.reduce((total, variant) => total + variant.size, 0); } get revision() { return `${this.digest}:${this.size}`; } get infrastructure() { return INFRA_PREFIXES.some((prefix) => this.reference.startsWith(prefix)); } get platforms() { return this.variants .map((variant) => (variant.os ? `${variant.os}/${variant.architecture}` : variant.architecture)) .filter((platform) => platform.length > 0) .join(" "); } get tooltip() { let lines = [`Image:\t${this.reference}`, `Digest:\t${this.shortDigest}`]; if (this.platforms) { lines.push(`Platform:\t${this.platforms}`); } if (this.size) { lines.push(`Size:\t${format.bytes(this.size)}`); } if (this.created) { lines.push(`Created:\t${format.age(this.created)}`); } return lines.join("\n"); } } class RepositoryModel extends Element { constructor(name) { super(`repository:${name}`); this.name = name; this.expanded = true; } get size() { return this.children.reduce((total, image) => total + image.size, 0); } get revision() { return String(this.size); } } function build(list, options = {}) { let images = (list || []).map((entry) => new ImageModel(entry)); if (!options.infrastructure) { images = images.filter((image) => !image.infrastructure); } let label = (image) => (options.registry ? image.reference : `${image.short}:${image.tag}`); images.sort((left, right) => label(left).localeCompare(label(right))); if (!options.group) { return images; } let repositories = new Map(); for (let image of images) { let name = options.registry && image.registry ? `${image.registry}/${image.repository}` : image.short; if (!repositories.has(name)) { repositories.set(name, new RepositoryModel(name)); } repositories.get(name).addChild(image); } return [...repositories.values()].sort((left, right) => left.name.localeCompare(right.name)); } module.exports = { ImageModel, RepositoryModel, build };