ci(release): skip if git tag already exists (#26)
This commit is contained in:
parent
3bcdef40e2
commit
66adc32ff7
2 changed files with 82 additions and 45 deletions
|
@ -31,21 +31,26 @@ describe("push-charts", () => {
|
||||||
describe("main", () => {
|
describe("main", () => {
|
||||||
test("should push single chart", async () => {
|
test("should push single chart", async () => {
|
||||||
fs.readdir.mockResolvedValue([myChartDirentMock]);
|
fs.readdir.mockResolvedValue([myChartDirentMock]);
|
||||||
await main(["node", "push-chart.ts", "my-chart"]);
|
// mock `git tag` to return empty string
|
||||||
expect(child_process.exec).toHaveBeenCalledWith(
|
child_process.execSync.mockReturnValue(Buffer.from(""));
|
||||||
|
await main(["node", "push-chart.ts", "charts/my-chart"]);
|
||||||
|
expect(child_process.execSync).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
`helm push ".cr-release-packages/my-chart-1.0.0.tgz" "oci://ghcr.io/lore/ipsum"`,
|
`helm push ".cr-release-packages/my-chart-1.0.0.tgz" "oci://ghcr.io/lore/ipsum"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should push multiple charts", async () => {
|
test("should push multiple charts", async () => {
|
||||||
fs.readdir.mockResolvedValue([myChartDirentMock, fooChartDirentMock]);
|
fs.readdir.mockResolvedValue([myChartDirentMock, fooChartDirentMock]);
|
||||||
await main(["node", "push-chart.ts", "my-chart,foo"]);
|
child_process.execSync.mockReturnValue(Buffer.from(""));
|
||||||
expect(child_process.exec).toHaveBeenNthCalledWith(
|
|
||||||
1,
|
await main(["node", "push-chart.ts", "charts/my-chart,charts/foo"]);
|
||||||
|
expect(child_process.execSync).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
`helm push ".cr-release-packages/my-chart-1.0.0.tgz" "oci://ghcr.io/lore/ipsum"`,
|
`helm push ".cr-release-packages/my-chart-1.0.0.tgz" "oci://ghcr.io/lore/ipsum"`,
|
||||||
);
|
);
|
||||||
expect(child_process.exec).toHaveBeenNthCalledWith(
|
expect(child_process.execSync).toHaveBeenNthCalledWith(
|
||||||
2,
|
3,
|
||||||
`helm push ".cr-release-packages/foo-2.0.0.tgz" "oci://ghcr.io/lore/ipsum"`,
|
`helm push ".cr-release-packages/foo-2.0.0.tgz" "oci://ghcr.io/lore/ipsum"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -54,24 +59,56 @@ describe("push-charts", () => {
|
||||||
describe("getChangedCharts", () => {
|
describe("getChangedCharts", () => {
|
||||||
test("should return changed chart if only one exists", async () => {
|
test("should return changed chart if only one exists", async () => {
|
||||||
fs.readdir.mockResolvedValue([myChartDirentMock]);
|
fs.readdir.mockResolvedValue([myChartDirentMock]);
|
||||||
|
child_process.execSync.mockReturnValue(Buffer.from(""));
|
||||||
await expect(getChangedChartsArchives(["my-chart"])).resolves.toEqual([
|
await expect(getChangedChartsArchives(["my-chart"])).resolves.toEqual([
|
||||||
".cr-release-packages/my-chart-1.0.0.tgz",
|
{
|
||||||
|
path: ".cr-release-packages/my-chart-1.0.0.tgz",
|
||||||
|
fileName: "my-chart-1.0.0",
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return changed chart if multiple exists", async () => {
|
test("should return multiple", async () => {
|
||||||
fs.readdir.mockResolvedValue([fooChartDirentMock, myChartDirentMock]);
|
fs.readdir.mockResolvedValue([fooChartDirentMock, myChartDirentMock]);
|
||||||
|
child_process.execSync.mockReturnValue(Buffer.from(""));
|
||||||
await expect(getChangedChartsArchives(["foo"])).resolves.toEqual([
|
await expect(getChangedChartsArchives(["foo"])).resolves.toEqual([
|
||||||
".cr-release-packages/foo-2.0.0.tgz",
|
{
|
||||||
|
path: ".cr-release-packages/foo-2.0.0.tgz",
|
||||||
|
fileName: "foo-2.0.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ".cr-release-packages/my-chart-1.0.0.tgz",
|
||||||
|
fileName: "my-chart-1.0.0",
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return multiple changed charts if multiple exists and changed", async () => {
|
test("should return multiple changed charts if multiple exists and changed", async () => {
|
||||||
fs.readdir.mockResolvedValue([fooChartDirentMock, myChartDirentMock]);
|
fs.readdir.mockResolvedValue([fooChartDirentMock, myChartDirentMock]);
|
||||||
|
child_process.execSync.mockReturnValue(Buffer.from(""));
|
||||||
await expect(getChangedChartsArchives(["my-chart,foo"])).resolves.toEqual(
|
await expect(getChangedChartsArchives(["my-chart,foo"])).resolves.toEqual(
|
||||||
[
|
[
|
||||||
".cr-release-packages/my-chart-1.0.0.tgz",
|
{
|
||||||
".cr-release-packages/foo-2.0.0.tgz",
|
path: ".cr-release-packages/foo-2.0.0.tgz",
|
||||||
|
fileName: "foo-2.0.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ".cr-release-packages/my-chart-1.0.0.tgz",
|
||||||
|
fileName: "my-chart-1.0.0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should skip already existing image", async () => {
|
||||||
|
fs.readdir.mockResolvedValue([fooChartDirentMock, myChartDirentMock]);
|
||||||
|
child_process.execSync.mockReturnValue(Buffer.from("my-chart-1.0.0\n"));
|
||||||
|
await expect(getChangedChartsArchives(["my-chart,foo"])).resolves.toEqual(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: ".cr-release-packages/foo-2.0.0.tgz",
|
||||||
|
fileName: "foo-2.0.0",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,73 +1,73 @@
|
||||||
import * as fs from "node:fs/promises";
|
import * as fs from "node:fs/promises";
|
||||||
import * as process from "node:process";
|
import * as process from "node:process";
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import { exec } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
|
|
||||||
const CR_RELEASE_PACKAGE_PATH = ".cr-release-packages";
|
const CR_RELEASE_PACKAGE_PATH = ".cr-release-packages";
|
||||||
|
|
||||||
|
interface Archive {
|
||||||
|
path: string;
|
||||||
|
fileName: string;
|
||||||
|
}
|
||||||
|
|
||||||
export async function main(rawArgs: string[]) {
|
export async function main(rawArgs: string[]) {
|
||||||
// remove file path node and script name
|
// remove file path node and script name
|
||||||
const args = rawArgs.slice(2);
|
const args = rawArgs.slice(2);
|
||||||
const archives = await getChangedChartsArchives(args);
|
const archives = await getChangedChartsArchives(args);
|
||||||
|
|
||||||
console.log("Pushing charts:");
|
console.log("Pushing charts");
|
||||||
for (const archive of archives) {
|
for (const archive of archives) {
|
||||||
console.log(`- ${archive}`);
|
const cmd = `helm push "${archive.path}" "oci://ghcr.io/${process.env.GITHUB_REPOSITORY}"`;
|
||||||
exec(
|
console.log(cmd);
|
||||||
`helm push "${archive}" "oci://ghcr.io/${process.env.GITHUB_REPOSITORY}"`,
|
console.log(execSync(cmd).toString());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getChangedChartsArchives(
|
export async function getChangedChartsArchives(
|
||||||
args: string[],
|
args: string[],
|
||||||
): Promise<string[]> {
|
): Promise<Archive[]> {
|
||||||
if (args.length === 0) {
|
|
||||||
console.log("Usage: push-charts <chart-list>");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const changedCharts = parseChartList(args[0]);
|
|
||||||
const chartArchives = await getChartArchives();
|
const chartArchives = await getChartArchives();
|
||||||
|
const tags = getGitTags();
|
||||||
|
|
||||||
// translate chart names to chart archives
|
// translate chart names to chart archives
|
||||||
const changedChartArchives: string[] = [];
|
const changedChartArchives: Archive[] = [];
|
||||||
for (const chart of changedCharts) {
|
for (const [, value] of Object.entries(chartArchives)) {
|
||||||
if (chartArchives[chart]) {
|
if (tags.includes(value.fileName)) {
|
||||||
const archive = chartArchives[chart];
|
// the chart has already pushed
|
||||||
changedChartArchives.push(archive);
|
console.log(
|
||||||
|
`Skipping ${value.path}. Tag ${value.fileName} already exists`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
changedChartArchives.push(value);
|
||||||
}
|
}
|
||||||
return changedChartArchives;
|
return changedChartArchives;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseChartList(chartListArg: string): string[] {
|
async function getChartArchives(): Promise<Record<string, Archive>> {
|
||||||
const parsed: string[] = [];
|
|
||||||
for (const chartPath of chartListArg.split(",")) {
|
|
||||||
const name = path.parse(chartPath).name;
|
|
||||||
parsed.push(name);
|
|
||||||
}
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getChartArchives(): Promise<Record<string, string>> {
|
|
||||||
const archiveList = await fs.readdir(CR_RELEASE_PACKAGE_PATH, {
|
const archiveList = await fs.readdir(CR_RELEASE_PACKAGE_PATH, {
|
||||||
withFileTypes: true,
|
withFileTypes: true,
|
||||||
});
|
});
|
||||||
const chartNames: Record<string, string> = {};
|
const chartNames: Record<string, Archive> = {};
|
||||||
for (const dirent of archiveList) {
|
for (const dirent of archiveList) {
|
||||||
if (dirent.isFile() && dirent.name.endsWith(".tgz")) {
|
if (dirent.isFile() && dirent.name.endsWith(".tgz")) {
|
||||||
const parsed = /^(?<name>[\w-]+?)-\d/.exec(dirent.name);
|
const parsed = /^(?<name>[\w-]+?)-\d/.exec(dirent.name);
|
||||||
// immich --> .cr-release-packages/immich-1.0.0.tgz
|
// immich --> .cr-release-packages/immich-1.0.0.tgz
|
||||||
chartNames[parsed.groups.name] = path.join(
|
chartNames[parsed.groups.name] = {
|
||||||
CR_RELEASE_PACKAGE_PATH,
|
path: path.join(CR_RELEASE_PACKAGE_PATH, dirent.name),
|
||||||
dirent.name,
|
fileName: path.parse(dirent.name).name,
|
||||||
);
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return chartNames;
|
return chartNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getGitTags(): string[] {
|
||||||
|
const result = execSync("git tag");
|
||||||
|
const tagString = result.toString();
|
||||||
|
return tagString.split("\n");
|
||||||
|
}
|
||||||
|
|
||||||
// do not run main if this script is being tested
|
// do not run main if this script is being tested
|
||||||
if (!process.env.VITEST_WORKER_ID) {
|
if (!process.env.VITEST_WORKER_ID) {
|
||||||
main(process.argv);
|
main(process.argv);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue