fix: 保留 catalog bootstrap 并同步 PaC pipeline
This commit is contained in:
@@ -17,7 +17,27 @@ export function artifactCatalogSourceCommitId(catalog) {
|
||||
return serviceCommits.length === 1 ? serviceCommits[0] : null;
|
||||
}
|
||||
|
||||
export function createHydratedArtifactCatalogAuthority({ catalog, catalogPath, gitopsBranch, gitopsCommitId }) {
|
||||
export function artifactCatalogServiceCoverage(catalog, selectedServices) {
|
||||
if (!Array.isArray(catalog?.services)) throw new Error("artifact catalog services must be an array");
|
||||
const selectedServiceIds = normalizedServiceIds(selectedServices, "selected services");
|
||||
const catalogServiceIds = normalizedServiceIds(catalog.services.map((service) => service?.serviceId), "artifact catalog services");
|
||||
const missingSelectedServices = selectedServiceIds.filter((serviceId) => !catalogServiceIds.includes(serviceId));
|
||||
const extraCatalogServices = catalogServiceIds.filter((serviceId) => !selectedServiceIds.includes(serviceId));
|
||||
return {
|
||||
status: missingSelectedServices.length > 0
|
||||
? (extraCatalogServices.length > 0 ? "divergent" : "partial")
|
||||
: (extraCatalogServices.length > 0 ? "superset" : "exact"),
|
||||
selectedServiceCount: selectedServiceIds.length,
|
||||
selectedServicePresentCount: selectedServiceIds.length - missingSelectedServices.length,
|
||||
catalogServiceCount: catalogServiceIds.length,
|
||||
selectedServiceIds,
|
||||
catalogServiceIds,
|
||||
missingSelectedServices,
|
||||
extraCatalogServices
|
||||
};
|
||||
}
|
||||
|
||||
export function createHydratedArtifactCatalogAuthority({ catalog, catalogPath, gitopsBranch, gitopsCommitId, selectedServices }) {
|
||||
return {
|
||||
schemaVersion: "v1",
|
||||
status: "hydrated",
|
||||
@@ -27,7 +47,23 @@ export function createHydratedArtifactCatalogAuthority({ catalog, catalogPath, g
|
||||
gitopsCommitId: text(gitopsCommitId),
|
||||
catalogSourceCommitId: artifactCatalogSourceCommitId(catalog),
|
||||
serviceCount: Array.isArray(catalog?.services) ? catalog.services.length : 0,
|
||||
catalogSha256: artifactCatalogSha256(catalog)
|
||||
catalogSha256: artifactCatalogSha256(catalog),
|
||||
coverage: artifactCatalogServiceCoverage(catalog, selectedServices)
|
||||
};
|
||||
}
|
||||
|
||||
export function createBootstrapArtifactCatalogAuthority({ catalog, catalogPath, sourceCommitId, reason, selectedServices }) {
|
||||
return {
|
||||
schemaVersion: "v1",
|
||||
status: "bootstrap",
|
||||
authority: "source-bootstrap",
|
||||
catalogPath: normalizeCatalogPath(catalogPath),
|
||||
sourceCommitId: text(sourceCommitId),
|
||||
bootstrapReason: text(reason),
|
||||
catalogSourceCommitId: artifactCatalogSourceCommitId(catalog),
|
||||
serviceCount: Array.isArray(catalog?.services) ? catalog.services.length : 0,
|
||||
catalogSha256: artifactCatalogSha256(catalog),
|
||||
coverage: artifactCatalogServiceCoverage(catalog, selectedServices)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,7 +82,9 @@ export async function readArtifactCatalogSummary({ repoRoot, catalogPath, author
|
||||
gitopsCommitId: null,
|
||||
catalogSourceCommitId: null,
|
||||
serviceCount: 0,
|
||||
catalogSha256: null
|
||||
catalogSha256: null,
|
||||
coverage: null,
|
||||
bootstrapReason: null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,27 +101,49 @@ export async function readArtifactCatalogSummary({ repoRoot, catalogPath, author
|
||||
gitopsCommitId: null,
|
||||
catalogSourceCommitId,
|
||||
serviceCount,
|
||||
catalogSha256
|
||||
catalogSha256,
|
||||
coverage: null,
|
||||
bootstrapReason: null
|
||||
};
|
||||
}
|
||||
|
||||
validateHydratedAuthority({ authority, catalogPath: normalizedCatalogPath, serviceCount, catalogSha256 });
|
||||
const coverage = validateCatalogAuthority({ authority, catalog: loadedCatalog, catalogPath: normalizedCatalogPath, serviceCount, catalogSha256 });
|
||||
if (authority.authority === "gitops-branch") {
|
||||
return {
|
||||
status: "hydrated",
|
||||
authority: "gitops-branch",
|
||||
catalogPath: normalizedCatalogPath,
|
||||
authorityPath: normalizeCatalogPath(authorityPath),
|
||||
gitopsBranch: text(authority.gitopsBranch),
|
||||
gitopsCommitId: text(authority.gitopsCommitId),
|
||||
catalogSourceCommitId: text(authority.catalogSourceCommitId) || catalogSourceCommitId,
|
||||
serviceCount,
|
||||
catalogSha256,
|
||||
coverage,
|
||||
bootstrapReason: null
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: "hydrated",
|
||||
authority: "gitops-branch",
|
||||
status: "bootstrap",
|
||||
authority: "source-bootstrap",
|
||||
catalogPath: normalizedCatalogPath,
|
||||
authorityPath: normalizeCatalogPath(authorityPath),
|
||||
gitopsBranch: text(authority.gitopsBranch),
|
||||
gitopsCommitId: text(authority.gitopsCommitId),
|
||||
gitopsBranch: null,
|
||||
gitopsCommitId: null,
|
||||
sourceCommitId: text(authority.sourceCommitId),
|
||||
catalogSourceCommitId: text(authority.catalogSourceCommitId) || catalogSourceCommitId,
|
||||
serviceCount,
|
||||
catalogSha256
|
||||
catalogSha256,
|
||||
coverage,
|
||||
bootstrapReason: text(authority.bootstrapReason)
|
||||
};
|
||||
}
|
||||
|
||||
function validateHydratedAuthority({ authority, catalogPath, serviceCount, catalogSha256 }) {
|
||||
if (authority.schemaVersion !== "v1" || authority.status !== "hydrated" || authority.authority !== "gitops-branch") {
|
||||
throw new Error(`artifact catalog authority for ${catalogPath} is not a hydrated gitops-branch record`);
|
||||
function validateCatalogAuthority({ authority, catalog, catalogPath, serviceCount, catalogSha256 }) {
|
||||
const gitopsAuthority = authority.schemaVersion === "v1" && authority.status === "hydrated" && authority.authority === "gitops-branch";
|
||||
const bootstrapAuthority = authority.schemaVersion === "v1" && authority.status === "bootstrap" && authority.authority === "source-bootstrap";
|
||||
if (!gitopsAuthority && !bootstrapAuthority) {
|
||||
throw new Error(`artifact catalog authority for ${catalogPath} is not a supported authority record`);
|
||||
}
|
||||
if (normalizeCatalogPath(authority.catalogPath) !== catalogPath) {
|
||||
throw new Error(`artifact catalog authority path mismatch: expected ${catalogPath}, got ${authority.catalogPath ?? "missing"}`);
|
||||
@@ -94,9 +154,21 @@ function validateHydratedAuthority({ authority, catalogPath, serviceCount, catal
|
||||
if (authority.catalogSha256 !== catalogSha256) {
|
||||
throw new Error(`artifact catalog authority fingerprint mismatch for ${catalogPath}`);
|
||||
}
|
||||
if (!text(authority.gitopsBranch) || !/^[a-f0-9]{40}$/u.test(text(authority.gitopsCommitId) ?? "")) {
|
||||
const coverage = artifactCatalogServiceCoverage(catalog, authority.coverage?.selectedServiceIds);
|
||||
validateCoverageRecord(authority.coverage, coverage, catalogPath);
|
||||
if (gitopsAuthority && (!text(authority.gitopsBranch) || !/^[a-f0-9]{40}$/u.test(text(authority.gitopsCommitId) ?? ""))) {
|
||||
throw new Error(`artifact catalog authority gitops provenance is incomplete for ${catalogPath}`);
|
||||
}
|
||||
if (bootstrapAuthority) {
|
||||
const sourceCommitId = text(authority.sourceCommitId);
|
||||
if (!/^[a-f0-9]{40}$/u.test(sourceCommitId ?? "") || !text(authority.bootstrapReason)) {
|
||||
throw new Error(`artifact catalog bootstrap authority is incomplete for ${catalogPath}`);
|
||||
}
|
||||
if (sourceCommitId !== artifactCatalogSourceCommitId(catalog)) {
|
||||
throw new Error(`artifact catalog bootstrap source commit mismatch for ${catalogPath}`);
|
||||
}
|
||||
}
|
||||
return coverage;
|
||||
}
|
||||
|
||||
async function readJsonIfPresent(repoRoot, filePath) {
|
||||
@@ -121,3 +193,38 @@ function text(value) {
|
||||
function unique(values) {
|
||||
return [...new Set(values)];
|
||||
}
|
||||
|
||||
function normalizedServiceIds(values, label) {
|
||||
if (!Array.isArray(values) || values.length === 0) throw new Error(`${label} must be a non-empty array`);
|
||||
const normalized = values.map(text);
|
||||
if (normalized.some((value) => value === null)) throw new Error(`${label} contains an empty service id`);
|
||||
const deduplicated = unique(normalized);
|
||||
if (deduplicated.length !== normalized.length) throw new Error(`${label} contains duplicate service ids`);
|
||||
return deduplicated.sort();
|
||||
}
|
||||
|
||||
function validateCoverageRecord(actual, expected, catalogPath) {
|
||||
const scalarFields = ["status", "selectedServiceCount", "selectedServicePresentCount", "catalogServiceCount"];
|
||||
const arrayFields = ["selectedServiceIds", "catalogServiceIds", "missingSelectedServices", "extraCatalogServices"];
|
||||
const expectedFields = [...scalarFields, ...arrayFields].sort();
|
||||
const actualFields = actual && typeof actual === "object" && !Array.isArray(actual)
|
||||
? Object.keys(actual).sort()
|
||||
: [];
|
||||
if (!sameArray(actualFields, expectedFields)) {
|
||||
throw new Error(`artifact catalog authority coverage fields mismatch for ${catalogPath}`);
|
||||
}
|
||||
for (const field of scalarFields) {
|
||||
if (actual[field] !== expected[field]) {
|
||||
throw new Error(`artifact catalog authority coverage ${field} mismatch for ${catalogPath}`);
|
||||
}
|
||||
}
|
||||
for (const field of arrayFields) {
|
||||
if (!sameArray(actual[field], expected[field])) {
|
||||
throw new Error(`artifact catalog authority coverage ${field} mismatch for ${catalogPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sameArray(left, right) {
|
||||
return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => value === right[index]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user