Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
7039c9e
NikolaRHristov Oct 4, 2024
c7ff2cc
NikolaRHristov Oct 5, 2024
06afe3a
NikolaRHristov Oct 5, 2024
f32d6a9
NikolaRHristov Oct 5, 2024
d4d6b47
NikolaRHristov Oct 6, 2024
3ebe66c
NikolaRHristov Oct 6, 2024
bdd7123
NikolaRHristov Oct 6, 2024
7fdb5ce
NikolaRHristov Oct 7, 2024
e8fde92
NikolaRHristov Oct 7, 2024
4908e1d
NikolaRHristov Oct 7, 2024
b4dc75d
NikolaRHristov Oct 7, 2024
75473d5
NikolaRHristov Oct 7, 2024
968d0ed
NikolaRHristov Oct 8, 2024
cd16d51
NikolaRHristov Oct 8, 2024
2e1bb67
NikolaRHristov Oct 8, 2024
051383b
NikolaRHristov Oct 9, 2024
940c7c0
NikolaRHristov Oct 9, 2024
cee05cf
NikolaRHristov Oct 9, 2024
e1a7a64
NikolaRHristov Oct 10, 2024
295664d
NikolaRHristov Oct 10, 2024
0378c7c
NikolaRHristov Oct 12, 2024
ee5eb7e
NikolaRHristov Oct 15, 2024
3d3f40d
NikolaRHristov Oct 16, 2024
3dbd0df
NikolaRHristov Oct 16, 2024
4c5c9b2
NikolaRHristov Oct 17, 2024
7d3441d
NikolaRHristov Oct 17, 2024
38842de
NikolaRHristov Oct 19, 2024
54624c4
NikolaRHristov Oct 22, 2024
96346e4
NikolaRHristov Oct 23, 2024
64aa4bc
NikolaRHristov Oct 29, 2024
cbc9b6d
NikolaRHristov Oct 29, 2024
5474512
NikolaRHristov Oct 30, 2024
4f0cf03
NikolaRHristov Oct 31, 2024
e3df535
Nov 7, 2024
3d7548d
Nov 14, 2024
98ae408
Nov 19, 2024
79b198a
Nov 20, 2024
f51ccc7
Nov 20, 2024
702213f
Nov 20, 2024
c114d3d
Nov 20, 2024
9e533f8
Nov 20, 2024
226ab45
Nov 20, 2024
7b6eaa8
Nov 21, 2024
32ab614
Nov 22, 2024
d222e15
Nov 26, 2024
367c043
Nov 28, 2024
7dd233b
Nov 29, 2024
fb8bb78
Nov 30, 2024
401919c
Dec 2, 2024
227468f
Dec 4, 2024
c8f0148
Dec 5, 2024
6a6c57e
Dec 5, 2024
d92d0c0
Dec 8, 2024
c6d9e75
Dec 8, 2024
72f6417
Dec 9, 2024
eb65ab7
Dec 10, 2024
8f833e3
Dec 13, 2024
d9ff355
Dec 13, 2024
cfca0cd
Dec 19, 2024
f95ca94
Jan 6, 2025
ae49c1b
Jan 14, 2025
a8f583a
Jan 17, 2025
344eebb
Jan 23, 2025
62b71aa
Jan 23, 2025
010282c
Feb 1, 2025
77f6e57
Feb 1, 2025
96a194f
Feb 10, 2025
81fae15
Feb 12, 2025
d8d68b4
Feb 13, 2025
0b5ffdd
Feb 15, 2025
566a352
Feb 17, 2025
81a1c73
Feb 17, 2025
946d790
Feb 24, 2025
f0d4bb1
Feb 24, 2025
a688d14
Mar 2, 2025
8acad25
Mar 4, 2025
03f490e
Reorder dependencies in package.json
Mar 21, 2025
a864b3a
Mar 28, 2025
709bea2
Update @types/node to 22.13.14
Mar 29, 2025
fa72c9d
Mar 30, 2025
b3864b3
Apr 1, 2025
790fe98
Apr 3, 2025
6eaf3e6
Apr 4, 2025
47e08d3
Apr 7, 2025
750938e
Apr 15, 2025
cebec8c
Apr 15, 2025
48a2fa9
Apr 15, 2025
23a5228
Apr 15, 2025
25a2b63
Apr 22, 2025
6ee6cfc
Apr 24, 2025
c5a5e3b
Apr 26, 2025
73cac6e
chore: Update TypeScript and Node type definitions
May 1, 2025
73762e1
Merge remote-tracking branch 'Parent/main' into Current
May 5, 2025
a859ec5
Bump tar-fs in /containers/codespaces-linux/test-project
dependabot[bot] May 5, 2025
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
377 changes: 377 additions & 0 deletions build/Source/image-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,377 @@
const path = require("path");
const push = require("./push").push;
const asyncUtils = require("./utils/async");
const configUtils = require("./utils/config");
const imageContentUtils = require("./utils/image-content-extractor");
const componentFormatterFactory = require("./utils/component-formatter-factory");
const markdownFormatterFactory = require("./utils/markdown-formatter-factory");
const handlebars = require("handlebars");
let releaseNotesHeaderTemplate, releaseNotesVariantPartTemplate;

// Register helper for anchors - Adapted from https://github.com/gjtorikian/html-pipeline/blob/main/lib/html/pipeline/toc_filter.rb
handlebars.registerHelper("anchor", (value) =>
value
.toLowerCase()
.replace(/[^\w\- ]/g, "")
.replace(/ /g, "-"),
);

async function generateImageInformationFiles(
repo,
release,
registry,
registryPath,
stubRegistry,
stubRegistryPath,
buildFirst,
pruneBetweenDefinitions,
generateCgManifest,
generateMarkdown,
overwrite,
outputPath,
definitionId,
) {
// Load config files
await configUtils.loadConfig();

const alreadyRegistered = {};
const cgManifest = {
"Registrations": [],
"Version": 1,
};

// cgmanifest file path and whether it exists
const cgManifestPath = path.join(outputPath, "cgmanifest.json");
const cgManifestExists = await asyncUtils.exists(cgManifestPath);

console.log("(*) Generating image information files...");
const definitions = definitionId
? [definitionId]
: configUtils.getSortedDefinitionBuildList();
await asyncUtils.forEach(definitions, async (currentDefinitionId) => {
// Target file paths and whether they exist
const definitionRelativePath = configUtils.getDefinitionPath(
currentDefinitionId,
true,
);
const historyFolder = path.join(
outputPath,
definitionRelativePath,
configUtils.getConfig("historyFolderName", "history"),
);
const version = configUtils.getVersionFromRelease(
release,
currentDefinitionId,
);
const markdownPath = path.join(historyFolder, `${version}.md`);
const markdownExists = await asyncUtils.exists(markdownPath);

// Skip if not overwriting and all files exist
if (
!overwrite &&
(!generateMarkdown || markdownExists) &&
(!generateCgManifest || cgManifestExists)
) {
console.log(
`(*) Skipping ${currentDefinitionId}. Not in overwrite mode and content already exists.`,
);
return;
}

// Extract information
const definitionInfo = await getDefinitionImageContent(
repo,
release,
registry,
registryPath,
stubRegistry,
stubRegistryPath,
currentDefinitionId,
alreadyRegistered,
buildFirst,
);

// Write markdown file as appropriate
if (generateMarkdown && (overwrite || !markdownExists)) {
console.log("(*) Writing image history markdown...");
await asyncUtils.mkdirp(historyFolder);
await asyncUtils.writeFile(markdownPath, definitionInfo.markdown);
}

// Add component registrations if we're using them
if (generateCgManifest) {
cgManifest.Registrations = cgManifest.Registrations.concat(
definitionInfo.registrations,
);
}
// Prune images if setting enabled
if (pruneBetweenDefinitions) {
await asyncUtils.spawn("docker", ["image", "prune", "-a", "-f"]);
}
});

// Write final cgmanifest.json file if needed
if (generateCgManifest && (overwrite || !cgManifestExists)) {
console.log("(*) Writing cgmanifest.json...");
await asyncUtils.writeFile(
path.join(outputPath, "cgmanifest.json"),
JSON.stringify(cgManifest, undefined, 4),
);
}
console.log("(*) Done!");
}

async function getDefinitionImageContent(
repo,
release,
registry,
registryPath,
stubRegistry,
stubRegistryPath,
definitionId,
alreadyRegistered,
buildFirst,
) {
const dependencies = configUtils.getDefinitionDependencies(definitionId);
if (typeof dependencies !== "object") {
return [];
}

let registrations = [];

const variants = configUtils.getVariants(definitionId) || [null];
const version = configUtils.getVersionFromRelease(release, definitionId);

// Create header for markdown
let markdown = await generateReleaseNotesHeader(
repo,
release,
definitionId,
variants,
dependencies,
);

await asyncUtils.forEach(variants, async (variant) => {
if (variant) {
console.log(`\n(*) Processing variant ${variant}...`);
}

const imageTag = configUtils.getTagsForVersion(
definitionId,
version,
registry,
registryPath,
variant,
)[0];
if (buildFirst) {
// Build but don't push images
console.log("(*) Building image...");
await push(
repo,
release,
false,
registry,
registryPath,
registry,
registryPath,
false,
false,
[],
1,
1,
false,
definitionId,
);
} else {
console.log(`(*) Pulling image ${imageTag}...`);
await asyncUtils.spawn("docker", ["pull", imageTag]);
}

// Extract content information
const contents = await imageContentUtils.getAllContentInfo(
imageTag,
dependencies,
);

// Update markdown content
markdown =
markdown +
(await generateReleaseNotesPart(
contents,
release,
stubRegistry,
stubRegistryPath,
definitionId,
variant,
));

// Add to registrations
registrations = registrations.concat(
getUniqueComponents(alreadyRegistered, contents),
);
});

// Register upstream images
await asyncUtils.forEach(dependencies.imageVariants, async (imageTag) => {
if (typeof alreadyRegistered[imageTag] === "undefined") {
const [image, imageVersion] = imageTag.split(":");
registrations.push({
"Component": {
"Type": "other",
"Other": {
"Name": `Docker Image: ${image}`,
"Version": imageVersion,
"DownloadUrl": dependencies.imageLink,
},
},
});
alreadyRegistered[dependencies.image] = [imageVersion];
}
});

return {
registrations: registrations,
markdown: markdown,
version: version,
};
}

// Filter out components already in the registration list and format output returns an array of formatted and filtered contents
function getUniqueComponents(alreadyRegistered, contents) {
let componentList = [];

const contentFormatter = componentFormatterFactory.getFormatter(
contents.distro,
);
for (let contentType in contents) {
const formatterFn = contentFormatter[contentType];
let content = contents[contentType];
if (formatterFn && content) {
if (!Array.isArray(content)) {
content = [content];
}
componentList = componentList.concat(
content.reduce((prev, next) => {
const uniqueId = JSON.stringify(next);
if (!alreadyRegistered[uniqueId]) {
alreadyRegistered[uniqueId] = true;
const component = formatterFn(next);
if (component) {
prev.push(component);
}
}
return prev;
}, []),
);
}
}

return componentList;
}

// Use template to generate header of version markdown content
async function generateReleaseNotesHeader(
repo,
release,
definitionId,
variants,
dependencies,
) {
releaseNotesHeaderTemplate =
releaseNotesHeaderTemplate ||
handlebars.compile(
await asyncUtils.readFile(
path.join(__dirname, "..", "assets", "release-notes-header.md"),
),
);
const data = {
version: configUtils.getVersionFromRelease(release, definitionId),
definition: definitionId,
release: release,
annotation: dependencies.annotation,
repository: repo,
variants: variants,
hasVariants: variants && variants[0],
};
return releaseNotesHeaderTemplate(data);
}

// Generate release notes section for variant
async function generateReleaseNotesPart(
contents,
release,
stubRegistry,
stubRegistryPath,
definitionId,
variant,
) {
releaseNotesVariantPartTemplate =
releaseNotesVariantPartTemplate ||
handlebars.compile(
await asyncUtils.readFile(
path.join(
__dirname,
"..",
"assets",
"release-notes-variant-part.md",
),
),
);
const markdownFormatter = markdownFormatterFactory.getFormatter();
const formattedContents = getFormattedContents(contents, markdownFormatter);
formattedContents.hasPip =
formattedContents.pip.length > 0 || formattedContents.pipx.length > 0;
formattedContents.tags = configUtils.getTagList(
definitionId,
release,
"full-only",
stubRegistry,
stubRegistryPath,
variant,
);
formattedContents.variant = variant;

// architecture property could be a single string, an array, or an object of arrays by variant
let architectures = configUtils.getBuildSettings(definitionId)
.architectures || ["linux/amd64"];
if (!Array.isArray(architectures)) {
architectures = architectures[variant];
}
formattedContents.architectures = architectures.reduce(
(prev, current, index) => (index > 0 ? `${prev}, ${current}` : current),
"",
);
return releaseNotesVariantPartTemplate(formattedContents);
}

// Return all contents as an object of formatted values
function getFormattedContents(contents, contentFormatter) {
let formattedContents = {};
for (let contentType in contents) {
formattedContents[contentType] = getFormattedContent(
contents[contentType],
contentFormatter[contentType],
);
}
return formattedContents;
}

function getFormattedContent(content, formatterFn) {
if (!formatterFn || !content) {
return null;
}
if (!Array.isArray(content)) {
return formatterFn(content);
}
return content.reduce((prev, next) => {
const formattedContent = formatterFn(next);
if (formattedContent) {
prev.push(formattedContent);
}
return prev;
}, []);
}

module.exports = {
generateImageInformationFiles: generateImageInformationFiles,
};
Loading