mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 05:20:02 +00:00
fix(ci): avoid duplicate agent latest assets
This commit is contained in:
@@ -23,8 +23,14 @@ async function main() {
|
||||
return;
|
||||
}
|
||||
|
||||
const localAssetPaths = localAssets(args.assetsDir);
|
||||
const localAssetNames = new Set(localAssetPaths.map((assetPath) => basename(assetPath)));
|
||||
if (args.pruneAssets) {
|
||||
await pruneReleaseAssets(client, release, localAssetNames);
|
||||
}
|
||||
|
||||
const existingAssets = new Set((release.assets || []).map((asset) => asset.name));
|
||||
const assets = localAssets(args.assetsDir).filter((assetPath) => {
|
||||
const assets = localAssetPaths.filter((assetPath) => {
|
||||
const name = basename(assetPath);
|
||||
if (existingAssets.has(name) && !args.overwriteExisting) {
|
||||
console.log(`Skipping existing CNB asset: ${name}`);
|
||||
@@ -49,6 +55,7 @@ function parseArgs(argv) {
|
||||
githubReleasePath: "",
|
||||
assetsDir: "",
|
||||
metadataOnly: false,
|
||||
pruneAssets: false,
|
||||
};
|
||||
for (let index = 0; index < argv.length; index++) {
|
||||
const arg = argv[index];
|
||||
@@ -56,12 +63,13 @@ function parseArgs(argv) {
|
||||
else if (arg === "--assets-dir") args.assetsDir = argv[++index];
|
||||
else if (arg === "--metadata-only") args.metadataOnly = true;
|
||||
else if (arg === "--overwrite-existing") args.overwriteExisting = true;
|
||||
else if (arg === "--prune-assets") args.pruneAssets = true;
|
||||
else throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
if (!args.token) throw new Error("CNB_TOKEN is required.");
|
||||
if (!args.githubReleasePath || (!args.metadataOnly && !args.assetsDir)) {
|
||||
throw new Error(
|
||||
"Usage: sync-cnb-release.mjs --github-release <release.json> (--assets-dir <dir> | --metadata-only)",
|
||||
"Usage: sync-cnb-release.mjs --github-release <release.json> (--assets-dir <dir> | --metadata-only) [--prune-assets]",
|
||||
);
|
||||
}
|
||||
if (!Number.isInteger(args.concurrency) || args.concurrency < 1) {
|
||||
@@ -70,6 +78,16 @@ function parseArgs(argv) {
|
||||
return args;
|
||||
}
|
||||
|
||||
export async function pruneReleaseAssets(client, release, localAssetNames) {
|
||||
for (const asset of release.assets || []) {
|
||||
if (localAssetNames.has(asset.name)) continue;
|
||||
if (!asset.id) throw new Error(`CNB asset ${asset.name} is missing its id.`);
|
||||
// Mutable release aliases must not retain files removed from the source release.
|
||||
await client.deleteAsset(release.id, asset.id);
|
||||
console.log(`Deleted stale CNB asset: ${asset.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadWithRetry(client, releaseId, filePath, overwriteExisting) {
|
||||
const name = basename(filePath);
|
||||
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
||||
@@ -139,6 +157,13 @@ export class CnbClient {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAsset(releaseId, assetId) {
|
||||
await this.request(
|
||||
"DELETE",
|
||||
`/${this.repository}/-/releases/${encodeURIComponent(releaseId)}/assets/${encodeURIComponent(assetId)}`,
|
||||
);
|
||||
}
|
||||
|
||||
async request(method, path, body = null, allow404 = false) {
|
||||
const response = await fetch(`${this.apiBase}${path}`, {
|
||||
method,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
|
||||
import { CnbClient } from "./sync-cnb-release.mjs";
|
||||
import { CnbClient, pruneReleaseAssets } from "./sync-cnb-release.mjs";
|
||||
|
||||
const scriptPath = fileURLToPath(new URL("./sync-cnb-release.mjs", import.meta.url));
|
||||
|
||||
@@ -125,3 +125,59 @@ test("metadata-only sync updates release without reading an assets directory", a
|
||||
{ method: "PATCH", url: "/dbxio.com/dbx/-/releases/release-2" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("pruneReleaseAssets deletes files missing from the source release", async () => {
|
||||
const deleted = [];
|
||||
const client = {
|
||||
async deleteAsset(releaseId, assetId) {
|
||||
deleted.push({ releaseId, assetId });
|
||||
},
|
||||
};
|
||||
|
||||
await pruneReleaseAssets(
|
||||
client,
|
||||
{
|
||||
id: "release-latest",
|
||||
assets: [
|
||||
{ id: "registry", name: "agent-registry.json" },
|
||||
{ id: "old-jre", name: "dbx-jre-8-linux-x64.tar.gz" },
|
||||
{ id: "old-driver", name: "dbx-agent-h2-0.1.0.jar" },
|
||||
],
|
||||
},
|
||||
new Set(["agent-registry.json"]),
|
||||
);
|
||||
|
||||
assert.deepEqual(deleted, [
|
||||
{ releaseId: "release-latest", assetId: "old-jre" },
|
||||
{ releaseId: "release-latest", assetId: "old-driver" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("deleteAsset accepts CNB's empty successful response", async (t) => {
|
||||
const requests = [];
|
||||
const server = createServer((request, response) => {
|
||||
requests.push({ method: request.method, url: request.url });
|
||||
response.statusCode = 204;
|
||||
response.end();
|
||||
});
|
||||
|
||||
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
t.after(() => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))));
|
||||
|
||||
const address = server.address();
|
||||
assert.ok(address && typeof address !== "string");
|
||||
const client = new CnbClient({
|
||||
apiBase: `http://127.0.0.1:${address.port}`,
|
||||
repository: "dbxio.com/dbx",
|
||||
token: "test-token",
|
||||
});
|
||||
|
||||
await client.deleteAsset("release/latest", "asset old");
|
||||
|
||||
assert.deepEqual(requests, [
|
||||
{
|
||||
method: "DELETE",
|
||||
url: "/dbxio.com/dbx/-/releases/release%2Flatest/assets/asset%20old",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -565,27 +565,24 @@ jobs:
|
||||
git push origin refs/tags/agents-latest --force
|
||||
|
||||
if gh release view agents-latest --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
# Versioned driver assets change names every release, so remove the
|
||||
# previous set before uploading to keep agents-latest unambiguous.
|
||||
# agents-latest is only the stable registry entry point. Actual artifacts
|
||||
# stay on the immutable agents-v* release referenced by the registry.
|
||||
gh release view agents-latest --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' |
|
||||
while IFS= read -r asset; do
|
||||
case "$asset" in
|
||||
dbx-agent-*.jar|dbx-agent-*.zip|dbx-agent-oracle-*|dbx-agent-xugu-*|dbx-agent-kingbase-*)
|
||||
gh release delete-asset agents-latest "$asset" --repo "$GITHUB_REPOSITORY" --yes
|
||||
;;
|
||||
esac
|
||||
[ "$asset" = "agent-registry.json" ] && continue
|
||||
gh release delete-asset agents-latest "$asset" --repo "$GITHUB_REPOSITORY" --yes
|
||||
done
|
||||
gh release upload agents-latest release/* --repo "$GITHUB_REPOSITORY" --clobber
|
||||
gh release upload agents-latest release/agent-registry.json --repo "$GITHUB_REPOSITORY" --clobber
|
||||
gh release edit agents-latest \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "Agents latest" \
|
||||
--notes "Moving release for the latest DBX agent registry, driver artifacts, managed JRE archives, and offline bundles." \
|
||||
--notes "Moving entry point for the latest DBX agent registry. Artifacts are stored on the referenced versioned agents release." \
|
||||
--latest=false
|
||||
else
|
||||
gh release create agents-latest release/* \
|
||||
gh release create agents-latest release/agent-registry.json \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "Agents latest" \
|
||||
--notes "Moving release for the latest DBX agent registry, driver artifacts, managed JRE archives, and offline bundles." \
|
||||
--notes "Moving entry point for the latest DBX agent registry. Artifacts are stored on the referenced versioned agents release." \
|
||||
--latest=false
|
||||
fi
|
||||
|
||||
@@ -690,6 +687,6 @@ jobs:
|
||||
)
|
||||
# agents-latest is mutable, so its stable asset names must replace the previous release contents.
|
||||
if [[ "$OVERWRITE_EXISTING" == "true" ]]; then
|
||||
args+=(--overwrite-existing)
|
||||
args+=(--overwrite-existing --prune-assets)
|
||||
fi
|
||||
node .github/scripts/sync-cnb-release.mjs "${args[@]}"
|
||||
|
||||
@@ -17,10 +17,10 @@ test("offline download catalog includes the JDBC plugin ZIP", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("release assets expose GitHub and CNB download links", () => {
|
||||
assert.deepEqual(downloadLinksFor("https://github.com/t8y2/dbx/releases/download/agents-latest/dbx-agents-offline-macos-aarch64.zip"), [
|
||||
{ source: "github", url: "https://github.com/t8y2/dbx/releases/download/agents-latest/dbx-agents-offline-macos-aarch64.zip" },
|
||||
{ source: "cnb", url: "https://cnb.cool/dbxio.com/dbx/-/releases/download/agents-latest/dbx-agents-offline-macos-aarch64.zip" },
|
||||
test("versioned release assets expose GitHub and CNB download links", () => {
|
||||
assert.deepEqual(downloadLinksFor("https://github.com/t8y2/dbx/releases/download/agents-v0.2.64/dbx-agents-offline-macos-aarch64.zip"), [
|
||||
{ source: "github", url: "https://github.com/t8y2/dbx/releases/download/agents-v0.2.64/dbx-agents-offline-macos-aarch64.zip" },
|
||||
{ source: "cnb", url: "https://cnb.cool/dbxio.com/dbx/-/releases/download/agents-v0.2.64/dbx-agents-offline-macos-aarch64.zip" },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -37,15 +37,34 @@ test("catalog falls back from GitHub to CNB", async () => {
|
||||
vi.fn(async (input: string | URL | Request) => {
|
||||
const url = String(input);
|
||||
requestedUrls.push(url);
|
||||
if (url.includes("api.github.com")) return new Response("rate limited", { status: 403 });
|
||||
if (url.includes("api.github.com")) {
|
||||
return Response.json({
|
||||
assets: [
|
||||
{
|
||||
name: "agent-registry.json",
|
||||
size: 1,
|
||||
browser_download_url: "https://github.com/t8y2/dbx/releases/download/agents-latest/agent-registry.json",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return Response.json({
|
||||
drivers: {
|
||||
access: { jar: { url: "https://dl.dbxio.com/agents/dbx-agent-access.jar", size: 1 } },
|
||||
access: {
|
||||
version: "0.1.30",
|
||||
jar: {
|
||||
url: "https://github.com/t8y2/dbx/releases/download/agents-v0.2.64/dbx-agent-access-0.1.30.jar",
|
||||
size: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
jres: {
|
||||
"21": {
|
||||
platforms: {
|
||||
"macos-aarch64": { url: "https://dl.dbxio.com/jres/dbx-jre-21-macos-aarch64.tar.gz", size: 1 },
|
||||
"macos-aarch64": {
|
||||
url: "https://github.com/t8y2/dbx/releases/download/agents-v0.2.64/dbx-jre-21-macos-aarch64.tar.gz",
|
||||
size: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -62,6 +81,10 @@ test("catalog falls back from GitHub to CNB", async () => {
|
||||
assert.equal(catalog?.drivers[0]?.key, "access");
|
||||
assert.equal(catalog?.jres[0]?.platformKey, "macos-aarch64");
|
||||
assert.equal(catalog?.bundles[0]?.platformKey, "macos-aarch64");
|
||||
assert.equal(
|
||||
catalog?.bundles[0]?.url,
|
||||
"https://github.com/t8y2/dbx/releases/download/agents-v0.2.64/dbx-agents-offline-macos-aarch64.zip",
|
||||
);
|
||||
});
|
||||
|
||||
test("unknown fallback asset sizes render as unavailable", () => {
|
||||
|
||||
+11
-11
@@ -175,18 +175,12 @@ function siblingReleaseAssetUrl(url: string, filename: string): string {
|
||||
return separator >= 0 ? `${url.slice(0, separator + 1)}${filename}` : filename;
|
||||
}
|
||||
|
||||
function githubReleaseAsset(name: string, size: number, tag = "agents-latest"): GitHubReleaseAsset {
|
||||
return {
|
||||
name,
|
||||
size,
|
||||
browser_download_url: `${GITHUB_RELEASE_DOWNLOAD_PREFIX}${tag}/${name}`,
|
||||
};
|
||||
}
|
||||
|
||||
function registryReleaseAssets(registry: AgentRegistry): GitHubReleaseAsset[] {
|
||||
const assets = new Map<string, GitHubReleaseAsset>();
|
||||
let releaseAssetUrl: string | undefined;
|
||||
const addArtifact = (artifact: AgentRegistryArtifact | undefined, fallbackName: string) => {
|
||||
if (!artifact?.url) return;
|
||||
releaseAssetUrl ??= artifact.url.startsWith(GITHUB_RELEASE_DOWNLOAD_PREFIX) ? artifact.url : undefined;
|
||||
const name = releaseAssetName(artifact.url, fallbackName);
|
||||
assets.set(name, { name, size: artifact.size || 0, browser_download_url: artifact.url });
|
||||
};
|
||||
@@ -221,9 +215,15 @@ function registryReleaseAssets(registry: AgentRegistry): GitHubReleaseAsset[] {
|
||||
}
|
||||
}
|
||||
|
||||
for (const platformKey of Object.keys(registry.jres?.["21"]?.platforms ?? {})) {
|
||||
const name = `dbx-agents-offline-${platformKey}.zip`;
|
||||
assets.set(name, githubReleaseAsset(name, 0));
|
||||
if (releaseAssetUrl) {
|
||||
for (const platformKey of Object.keys(registry.jres?.["21"]?.platforms ?? {})) {
|
||||
const name = `dbx-agents-offline-${platformKey}.zip`;
|
||||
assets.set(name, {
|
||||
name,
|
||||
size: 0,
|
||||
browser_download_url: siblingReleaseAssetUrl(releaseAssetUrl, name),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(assets.values());
|
||||
|
||||
Reference in New Issue
Block a user