mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-04 18:36:54 +02:00
style(cso): format lib/cso TypeScript with pinned Prettier
Mechanical reformat only. Minified transpile output is byte-identical for 21 of 22 files; witness.ts differs only in three regex flag orders (/mi -> /im), which JavaScript canonicalizes. Source-text assertions over lib/cso now compare whitespace-insensitively with the same tokens.
This commit is contained in:
1 parent
dcaea52800
commit
d93d61f7ba
27 files changed
+17942
-4164
No files matched your search
+360
-122
@@ -8,175 +8,413 @@ import { validateScannerCatalog, type ScannerCatalog } from './scanner-catalog';
|
||||
import { secureDirectory } from './state';
|
||||
|
||||
export interface QualifiedCatalogImage {
|
||||
kind:'runtime'|'scanner';
|
||||
id:string;
|
||||
image:string;
|
||||
platform:RuntimePlatform;
|
||||
kind: 'runtime' | 'scanner';
|
||||
id: string;
|
||||
image: string;
|
||||
platform: RuntimePlatform;
|
||||
}
|
||||
export interface CatalogImageSession {
|
||||
readonly docker:{endpoint:string;version:string;security:string[]};
|
||||
present(entry:QualifiedCatalogImage,deadline?:number):Promise<boolean>;
|
||||
pull(entry:QualifiedCatalogImage,deadline?:number):Promise<void>;
|
||||
close():void;
|
||||
readonly docker: { endpoint: string; version: string; security: string[] };
|
||||
present(entry: QualifiedCatalogImage, deadline?: number): Promise<boolean>;
|
||||
pull(entry: QualifiedCatalogImage, deadline?: number): Promise<void>;
|
||||
close(): void;
|
||||
}
|
||||
/** Doctor performs concurrent, read-only checks inside its 30-second contract. */
|
||||
export const CATALOG_IMAGE_INSPECTION_BUDGET_MS=30_000;
|
||||
export const DEFAULT_CATALOG_IMAGE_BUDGET_MS=30_000;
|
||||
export const MIN_CATALOG_IMAGE_BUDGET_SECONDS=5;
|
||||
export const MAX_CATALOG_IMAGE_BUDGET_SECONDS=300;
|
||||
export const MAX_CATALOG_IMAGE_PROVISIONING_BUDGET_MS=60*60_000;
|
||||
const CATALOG_IMAGE_ADMISSION_BUDGET_MS=30_000;
|
||||
export interface CatalogImageProvisioningPolicy {perImageMs:number;aggregateMs:number;}
|
||||
export const CATALOG_IMAGE_INSPECTION_BUDGET_MS = 30_000;
|
||||
export const DEFAULT_CATALOG_IMAGE_BUDGET_MS = 30_000;
|
||||
export const MIN_CATALOG_IMAGE_BUDGET_SECONDS = 5;
|
||||
export const MAX_CATALOG_IMAGE_BUDGET_SECONDS = 300;
|
||||
export const MAX_CATALOG_IMAGE_PROVISIONING_BUDGET_MS = 60 * 60_000;
|
||||
const CATALOG_IMAGE_ADMISSION_BUDGET_MS = 30_000;
|
||||
export interface CatalogImageProvisioningPolicy {
|
||||
perImageMs: number;
|
||||
aggregateMs: number;
|
||||
}
|
||||
/**
|
||||
* Give every declared native-platform image a bounded opportunity to download.
|
||||
* The one-hour ceiling admits the current eleven-image catalog even at the
|
||||
* maximum configurable five-minute allowance.
|
||||
*/
|
||||
export function catalogImageProvisioningPolicy(imageCount:number,requestedSeconds?:string):CatalogImageProvisioningPolicy{
|
||||
if(!Number.isSafeInteger(imageCount)||imageCount<0)throw new CsoError('INVALID_ARGUMENT','Catalog image count is invalid');
|
||||
let seconds=DEFAULT_CATALOG_IMAGE_BUDGET_MS/1000;
|
||||
if(requestedSeconds!==undefined){
|
||||
if(!/^[0-9]+$/.test(requestedSeconds))throw new CsoError('INVALID_ARGUMENT','--per-image-seconds requires a whole number');
|
||||
seconds=Number(requestedSeconds);
|
||||
if(seconds<MIN_CATALOG_IMAGE_BUDGET_SECONDS||seconds>MAX_CATALOG_IMAGE_BUDGET_SECONDS)throw new CsoError('INVALID_ARGUMENT',`--per-image-seconds must be ${MIN_CATALOG_IMAGE_BUDGET_SECONDS}..${MAX_CATALOG_IMAGE_BUDGET_SECONDS}`);
|
||||
export function catalogImageProvisioningPolicy(
|
||||
imageCount: number,
|
||||
requestedSeconds?: string,
|
||||
): CatalogImageProvisioningPolicy {
|
||||
if (!Number.isSafeInteger(imageCount) || imageCount < 0)
|
||||
throw new CsoError('INVALID_ARGUMENT', 'Catalog image count is invalid');
|
||||
let seconds = DEFAULT_CATALOG_IMAGE_BUDGET_MS / 1000;
|
||||
if (requestedSeconds !== undefined) {
|
||||
if (!/^[0-9]+$/.test(requestedSeconds))
|
||||
throw new CsoError('INVALID_ARGUMENT', '--per-image-seconds requires a whole number');
|
||||
seconds = Number(requestedSeconds);
|
||||
if (seconds < MIN_CATALOG_IMAGE_BUDGET_SECONDS || seconds > MAX_CATALOG_IMAGE_BUDGET_SECONDS)
|
||||
throw new CsoError(
|
||||
'INVALID_ARGUMENT',
|
||||
`--per-image-seconds must be ${MIN_CATALOG_IMAGE_BUDGET_SECONDS}..${MAX_CATALOG_IMAGE_BUDGET_SECONDS}`,
|
||||
);
|
||||
}
|
||||
const perImageMs=seconds*1000,aggregateMs=CATALOG_IMAGE_ADMISSION_BUDGET_MS+imageCount*perImageMs;
|
||||
if(!Number.isSafeInteger(aggregateMs)||aggregateMs>MAX_CATALOG_IMAGE_PROVISIONING_BUDGET_MS)throw new CsoError('INCOMPATIBLE_INPUT','Qualified image catalog exceeds the bounded setup preload capacity');
|
||||
return{perImageMs,aggregateMs};
|
||||
const perImageMs = seconds * 1000,
|
||||
aggregateMs = CATALOG_IMAGE_ADMISSION_BUDGET_MS + imageCount * perImageMs;
|
||||
if (!Number.isSafeInteger(aggregateMs) || aggregateMs > MAX_CATALOG_IMAGE_PROVISIONING_BUDGET_MS)
|
||||
throw new CsoError(
|
||||
'INCOMPATIBLE_INPUT',
|
||||
'Qualified image catalog exceeds the bounded setup preload capacity',
|
||||
);
|
||||
return { perImageMs, aggregateMs };
|
||||
}
|
||||
export type CatalogImageSessionFactory=(deadline:number)=>Promise<CatalogImageSession>;
|
||||
export type CatalogImageSessionFactory = (deadline: number) => Promise<CatalogImageSession>;
|
||||
export interface CatalogImageAvailability extends QualifiedCatalogImage {
|
||||
status:'available'|'unavailable';
|
||||
reason?:string;
|
||||
status: 'available' | 'unavailable';
|
||||
reason?: string;
|
||||
}
|
||||
export interface CatalogImageInspection {
|
||||
docker:{status:'ready'|'missing';detail:unknown};
|
||||
images:CatalogImageAvailability[];
|
||||
docker: { status: 'ready' | 'missing'; detail: unknown };
|
||||
images: CatalogImageAvailability[];
|
||||
}
|
||||
export interface CatalogImageProvisionResult {
|
||||
schemaVersion:1;
|
||||
status:'complete'|'partial'|'not_available';
|
||||
downloads:true;
|
||||
platform:RuntimePlatform;
|
||||
requested:number;
|
||||
inspected:number;
|
||||
alreadyPresent:number;
|
||||
downloaded:number;
|
||||
deadlineReached:boolean;
|
||||
unavailable:CatalogImageAvailability[];
|
||||
summary:string;
|
||||
schemaVersion: 1;
|
||||
status: 'complete' | 'partial' | 'not_available';
|
||||
downloads: true;
|
||||
platform: RuntimePlatform;
|
||||
requested: number;
|
||||
inspected: number;
|
||||
alreadyPresent: number;
|
||||
downloaded: number;
|
||||
deadlineReached: boolean;
|
||||
unavailable: CatalogImageAvailability[];
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export function qualifiedCatalogImages(runtimeCatalog:RuntimeCatalog,scannerCatalog:ScannerCatalog,platform:RuntimePlatform):QualifiedCatalogImage[]{
|
||||
validateRuntimeCatalog(runtimeCatalog);validateScannerCatalog(scannerCatalog);
|
||||
const entries:QualifiedCatalogImage[]=[
|
||||
...runtimeCatalog.runtimes.filter(item=>item.platform===platform).map(item=>({kind:'runtime' as const,id:item.id,image:item.image,platform:item.platform})),
|
||||
...scannerCatalog.scanners.filter(item=>item.platform===platform).map(item=>({kind:'scanner' as const,id:item.id,image:item.image,platform:item.platform})),
|
||||
export function qualifiedCatalogImages(
|
||||
runtimeCatalog: RuntimeCatalog,
|
||||
scannerCatalog: ScannerCatalog,
|
||||
platform: RuntimePlatform,
|
||||
): QualifiedCatalogImage[] {
|
||||
validateRuntimeCatalog(runtimeCatalog);
|
||||
validateScannerCatalog(scannerCatalog);
|
||||
const entries: QualifiedCatalogImage[] = [
|
||||
...runtimeCatalog.runtimes
|
||||
.filter((item) => item.platform === platform)
|
||||
.map((item) => ({ kind: 'runtime' as const, id: item.id, image: item.image, platform: item.platform })),
|
||||
...scannerCatalog.scanners
|
||||
.filter((item) => item.platform === platform)
|
||||
.map((item) => ({ kind: 'scanner' as const, id: item.id, image: item.image, platform: item.platform })),
|
||||
];
|
||||
const identities=new Set<string>();
|
||||
for(const entry of entries){
|
||||
const identity=`${entry.kind}:${entry.id}`;
|
||||
if(identities.has(identity))throw new CsoError('INCOMPATIBLE_INPUT','Qualified image catalogs contain a duplicate identity');
|
||||
const identities = new Set<string>();
|
||||
for (const entry of entries) {
|
||||
const identity = `${entry.kind}:${entry.id}`;
|
||||
if (identities.has(identity))
|
||||
throw new CsoError('INCOMPATIBLE_INPUT', 'Qualified image catalogs contain a duplicate identity');
|
||||
identities.add(identity);
|
||||
}
|
||||
return entries.sort((left,right)=>`${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`));
|
||||
return entries.sort((left, right) => `${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`));
|
||||
}
|
||||
|
||||
function controlledReason(error:unknown,fallback:string):string{
|
||||
return error instanceof CsoError?error.message:fallback;
|
||||
function controlledReason(error: unknown, fallback: string): string {
|
||||
return error instanceof CsoError ? error.message : fallback;
|
||||
}
|
||||
export async function inspectCatalogImages(entries:QualifiedCatalogImage[],open:CatalogImageSessionFactory,deadline=Date.now()+CATALOG_IMAGE_INSPECTION_BUDGET_MS):Promise<CatalogImageInspection>{
|
||||
let session:CatalogImageSession;
|
||||
try{session=await open(deadline);}catch(error){
|
||||
const detail=controlledReason(error,'Local Docker is unavailable for exact catalog image inspection');
|
||||
return{docker:{status:'missing',detail},images:entries.map(entry=>({...entry,status:'unavailable',reason:detail}))};
|
||||
export async function inspectCatalogImages(
|
||||
entries: QualifiedCatalogImage[],
|
||||
open: CatalogImageSessionFactory,
|
||||
deadline = Date.now() + CATALOG_IMAGE_INSPECTION_BUDGET_MS,
|
||||
): Promise<CatalogImageInspection> {
|
||||
let session: CatalogImageSession;
|
||||
try {
|
||||
session = await open(deadline);
|
||||
} catch (error) {
|
||||
const detail = controlledReason(error, 'Local Docker is unavailable for exact catalog image inspection');
|
||||
return {
|
||||
docker: { status: 'missing', detail },
|
||||
images: entries.map((entry) => ({ ...entry, status: 'unavailable', reason: detail })),
|
||||
};
|
||||
}
|
||||
try{
|
||||
try {
|
||||
// Read-only daemon lookups run together so doctor remains within its
|
||||
// 30-second contract even when a local Docker client is slow to fail.
|
||||
const images=await Promise.all(entries.map(async(entry):Promise<CatalogImageAvailability>=>{
|
||||
try{const present=await session.present(entry);if(Date.now()>=deadline)throw new CsoError('DEADLINE','Exact image inspection reached the aggregate image-provisioning deadline');return{...entry,status:present?'available':'unavailable',...(present?{}:{reason:'Exact qualified image is not present in the local Docker daemon'})};}
|
||||
catch(error){return{...entry,status:'unavailable',reason:controlledReason(error,'Exact qualified image could not be inspected safely')};}
|
||||
}));
|
||||
return{docker:{status:'ready',detail:session.docker},images};
|
||||
}finally{session.close();}
|
||||
const images = await Promise.all(
|
||||
entries.map(async (entry): Promise<CatalogImageAvailability> => {
|
||||
try {
|
||||
const present = await session.present(entry);
|
||||
if (Date.now() >= deadline)
|
||||
throw new CsoError(
|
||||
'DEADLINE',
|
||||
'Exact image inspection reached the aggregate image-provisioning deadline',
|
||||
);
|
||||
return {
|
||||
...entry,
|
||||
status: present ? 'available' : 'unavailable',
|
||||
...(present ? {} : { reason: 'Exact qualified image is not present in the local Docker daemon' }),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
...entry,
|
||||
status: 'unavailable',
|
||||
reason: controlledReason(error, 'Exact qualified image could not be inspected safely'),
|
||||
};
|
||||
}
|
||||
}),
|
||||
);
|
||||
return { docker: { status: 'ready', detail: session.docker }, images };
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function provisionCatalogImages(entries:QualifiedCatalogImage[],platform:RuntimePlatform,open:CatalogImageSessionFactory,deadline=Date.now()+catalogImageProvisioningPolicy(entries.length).aggregateMs,perImageBudgetMs=DEFAULT_CATALOG_IMAGE_BUDGET_MS):Promise<CatalogImageProvisionResult>{
|
||||
if(!entries.length)return{schemaVersion:1,status:'complete',downloads:true,platform,requested:0,inspected:0,alreadyPresent:0,downloaded:0,deadlineReached:false,unavailable:[],summary:'No qualified CSO images are published for this platform; static audits remain available.'};
|
||||
if(!Number.isSafeInteger(perImageBudgetMs)||perImageBudgetMs<1||perImageBudgetMs>MAX_CATALOG_IMAGE_BUDGET_SECONDS*1000)throw new CsoError('INVALID_ARGUMENT','Catalog per-image budget is invalid');
|
||||
const deadlineReason='The bounded aggregate CSO image preload deadline was reached';
|
||||
if(Date.now()>=deadline){const unavailable=entries.map(entry=>({...entry,status:'unavailable' as const,reason:deadlineReason}));return{schemaVersion:1,status:'partial',downloads:true,platform,requested:entries.length,inspected:0,alreadyPresent:0,downloaded:0,deadlineReached:true,unavailable,summary:`Qualified CSO image preload partial: 0/${entries.length} available; ${deadlineReason.toLowerCase()}. Rerun setup to continue.`};}
|
||||
let session:CatalogImageSession;
|
||||
try{session=await open(deadline);}catch(error){
|
||||
const reason=controlledReason(error,'Local Docker is unavailable for qualified image provisioning'),unavailable=entries.map(entry=>({...entry,status:'unavailable' as const,reason}));
|
||||
const deadlineReached=error instanceof CsoError&&error.code==='DEADLINE';
|
||||
return{schemaVersion:1,status:deadlineReached?'partial':'not_available',downloads:true,platform,requested:entries.length,inspected:0,alreadyPresent:0,downloaded:0,deadlineReached,unavailable,summary:deadlineReached?`Qualified CSO image preload partial: 0/${entries.length} available; ${reason}. Rerun setup to continue.`:`Qualified CSO images were not preloaded: ${reason}. Rerun setup after the prerequisite is available.`};
|
||||
export async function provisionCatalogImages(
|
||||
entries: QualifiedCatalogImage[],
|
||||
platform: RuntimePlatform,
|
||||
open: CatalogImageSessionFactory,
|
||||
deadline = Date.now() + catalogImageProvisioningPolicy(entries.length).aggregateMs,
|
||||
perImageBudgetMs = DEFAULT_CATALOG_IMAGE_BUDGET_MS,
|
||||
): Promise<CatalogImageProvisionResult> {
|
||||
if (!entries.length)
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
status: 'complete',
|
||||
downloads: true,
|
||||
platform,
|
||||
requested: 0,
|
||||
inspected: 0,
|
||||
alreadyPresent: 0,
|
||||
downloaded: 0,
|
||||
deadlineReached: false,
|
||||
unavailable: [],
|
||||
summary: 'No qualified CSO images are published for this platform; static audits remain available.',
|
||||
};
|
||||
if (
|
||||
!Number.isSafeInteger(perImageBudgetMs) ||
|
||||
perImageBudgetMs < 1 ||
|
||||
perImageBudgetMs > MAX_CATALOG_IMAGE_BUDGET_SECONDS * 1000
|
||||
)
|
||||
throw new CsoError('INVALID_ARGUMENT', 'Catalog per-image budget is invalid');
|
||||
const deadlineReason = 'The bounded aggregate CSO image preload deadline was reached';
|
||||
if (Date.now() >= deadline) {
|
||||
const unavailable = entries.map((entry) => ({
|
||||
...entry,
|
||||
status: 'unavailable' as const,
|
||||
reason: deadlineReason,
|
||||
}));
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
status: 'partial',
|
||||
downloads: true,
|
||||
platform,
|
||||
requested: entries.length,
|
||||
inspected: 0,
|
||||
alreadyPresent: 0,
|
||||
downloaded: 0,
|
||||
deadlineReached: true,
|
||||
unavailable,
|
||||
summary: `Qualified CSO image preload partial: 0/${entries.length} available; ${deadlineReason.toLowerCase()}. Rerun setup to continue.`,
|
||||
};
|
||||
}
|
||||
let inspected=0,alreadyPresent=0,downloaded=0,pullBlocked='',deadlineReached=false,perImageTimeouts=0;const unavailable:CatalogImageAvailability[]=[];
|
||||
try{
|
||||
for(let index=0;index<entries.length;index++){
|
||||
const entry=entries[index];
|
||||
if(Date.now()>=deadline){deadlineReached=true;for(const remaining of entries.slice(index))unavailable.push({...remaining,status:'unavailable',reason:deadlineReason});break;}
|
||||
const imageDeadline=Math.min(deadline,Date.now()+perImageBudgetMs),perImageReason=`The ${Math.ceil(perImageBudgetMs/1000)}-second per-image CSO preload deadline was reached`;
|
||||
let present=false;
|
||||
try{
|
||||
present=await session.present(entry,imageDeadline);if(Date.now()>=imageDeadline)throw new CsoError('DEADLINE',imageDeadline===deadline?'Exact image inspection reached the aggregate image-provisioning deadline':perImageReason);inspected++;
|
||||
if(present){alreadyPresent++;continue;}
|
||||
}catch(error){
|
||||
if(error instanceof CsoError&&error.code==='DEADLINE'){
|
||||
if(Date.now()>=deadline){deadlineReached=true;unavailable.push({...entry,status:'unavailable',reason:error.message});for(const remaining of entries.slice(index+1))unavailable.push({...remaining,status:'unavailable',reason:deadlineReason});break;}
|
||||
perImageTimeouts++;unavailable.push({...entry,status:'unavailable',reason:perImageReason});continue;
|
||||
let session: CatalogImageSession;
|
||||
try {
|
||||
session = await open(deadline);
|
||||
} catch (error) {
|
||||
const reason = controlledReason(error, 'Local Docker is unavailable for qualified image provisioning'),
|
||||
unavailable = entries.map((entry) => ({ ...entry, status: 'unavailable' as const, reason }));
|
||||
const deadlineReached = error instanceof CsoError && error.code === 'DEADLINE';
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
status: deadlineReached ? 'partial' : 'not_available',
|
||||
downloads: true,
|
||||
platform,
|
||||
requested: entries.length,
|
||||
inspected: 0,
|
||||
alreadyPresent: 0,
|
||||
downloaded: 0,
|
||||
deadlineReached,
|
||||
unavailable,
|
||||
summary: deadlineReached
|
||||
? `Qualified CSO image preload partial: 0/${entries.length} available; ${reason}. Rerun setup to continue.`
|
||||
: `Qualified CSO images were not preloaded: ${reason}. Rerun setup after the prerequisite is available.`,
|
||||
};
|
||||
}
|
||||
let inspected = 0,
|
||||
alreadyPresent = 0,
|
||||
downloaded = 0,
|
||||
pullBlocked = '',
|
||||
deadlineReached = false,
|
||||
perImageTimeouts = 0;
|
||||
const unavailable: CatalogImageAvailability[] = [];
|
||||
try {
|
||||
for (let index = 0; index < entries.length; index++) {
|
||||
const entry = entries[index];
|
||||
if (Date.now() >= deadline) {
|
||||
deadlineReached = true;
|
||||
for (const remaining of entries.slice(index))
|
||||
unavailable.push({ ...remaining, status: 'unavailable', reason: deadlineReason });
|
||||
break;
|
||||
}
|
||||
const imageDeadline = Math.min(deadline, Date.now() + perImageBudgetMs),
|
||||
perImageReason = `The ${Math.ceil(perImageBudgetMs / 1000)}-second per-image CSO preload deadline was reached`;
|
||||
let present = false;
|
||||
try {
|
||||
present = await session.present(entry, imageDeadline);
|
||||
if (Date.now() >= imageDeadline)
|
||||
throw new CsoError(
|
||||
'DEADLINE',
|
||||
imageDeadline === deadline
|
||||
? 'Exact image inspection reached the aggregate image-provisioning deadline'
|
||||
: perImageReason,
|
||||
);
|
||||
inspected++;
|
||||
if (present) {
|
||||
alreadyPresent++;
|
||||
continue;
|
||||
}
|
||||
unavailable.push({...entry,status:'unavailable',reason:controlledReason(error,'Exact qualified image could not be inspected safely')});continue;
|
||||
} catch (error) {
|
||||
if (error instanceof CsoError && error.code === 'DEADLINE') {
|
||||
if (Date.now() >= deadline) {
|
||||
deadlineReached = true;
|
||||
unavailable.push({ ...entry, status: 'unavailable', reason: error.message });
|
||||
for (const remaining of entries.slice(index + 1))
|
||||
unavailable.push({ ...remaining, status: 'unavailable', reason: deadlineReason });
|
||||
break;
|
||||
}
|
||||
perImageTimeouts++;
|
||||
unavailable.push({ ...entry, status: 'unavailable', reason: perImageReason });
|
||||
continue;
|
||||
}
|
||||
unavailable.push({
|
||||
...entry,
|
||||
status: 'unavailable',
|
||||
reason: controlledReason(error, 'Exact qualified image could not be inspected safely'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
// A registry failure blocks further network attempts, but read-only local
|
||||
// inspection continues so the setup summary never calls a cached digest
|
||||
// unavailable merely because it sorts after the failed pull.
|
||||
if(pullBlocked){unavailable.push({...entry,status:'unavailable',reason:`Network provisioning stopped after an anonymous registry prerequisite failed: ${pullBlocked}`});continue;}
|
||||
if(Date.now()>=deadline){deadlineReached=true;unavailable.push({...entry,status:'unavailable',reason:deadlineReason});for(const remaining of entries.slice(index+1))unavailable.push({...remaining,status:'unavailable',reason:deadlineReason});break;}
|
||||
try{await session.pull(entry,imageDeadline);if(Date.now()>=imageDeadline)throw new CsoError('DEADLINE',imageDeadline===deadline?'Qualified image pull reached the aggregate preload deadline':perImageReason);downloaded++;}
|
||||
catch(error){
|
||||
if(error instanceof CsoError&&error.code==='DEADLINE'){
|
||||
if(Date.now()>=deadline){deadlineReached=true;unavailable.push({...entry,status:'unavailable',reason:error.message});for(const remaining of entries.slice(index+1))unavailable.push({...remaining,status:'unavailable',reason:deadlineReason});break;}
|
||||
perImageTimeouts++;unavailable.push({...entry,status:'unavailable',reason:perImageReason});continue;
|
||||
if (pullBlocked) {
|
||||
unavailable.push({
|
||||
...entry,
|
||||
status: 'unavailable',
|
||||
reason: `Network provisioning stopped after an anonymous registry prerequisite failed: ${pullBlocked}`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (Date.now() >= deadline) {
|
||||
deadlineReached = true;
|
||||
unavailable.push({ ...entry, status: 'unavailable', reason: deadlineReason });
|
||||
for (const remaining of entries.slice(index + 1))
|
||||
unavailable.push({ ...remaining, status: 'unavailable', reason: deadlineReason });
|
||||
break;
|
||||
}
|
||||
try {
|
||||
await session.pull(entry, imageDeadline);
|
||||
if (Date.now() >= imageDeadline)
|
||||
throw new CsoError(
|
||||
'DEADLINE',
|
||||
imageDeadline === deadline
|
||||
? 'Qualified image pull reached the aggregate preload deadline'
|
||||
: perImageReason,
|
||||
);
|
||||
downloaded++;
|
||||
} catch (error) {
|
||||
if (error instanceof CsoError && error.code === 'DEADLINE') {
|
||||
if (Date.now() >= deadline) {
|
||||
deadlineReached = true;
|
||||
unavailable.push({ ...entry, status: 'unavailable', reason: error.message });
|
||||
for (const remaining of entries.slice(index + 1))
|
||||
unavailable.push({ ...remaining, status: 'unavailable', reason: deadlineReason });
|
||||
break;
|
||||
}
|
||||
perImageTimeouts++;
|
||||
unavailable.push({ ...entry, status: 'unavailable', reason: perImageReason });
|
||||
continue;
|
||||
}
|
||||
pullBlocked=controlledReason(error,'Qualified image provisioning failed');unavailable.push({...entry,status:'unavailable',reason:pullBlocked});
|
||||
pullBlocked = controlledReason(error, 'Qualified image provisioning failed');
|
||||
unavailable.push({ ...entry, status: 'unavailable', reason: pullBlocked });
|
||||
}
|
||||
}
|
||||
}finally{session.close();}
|
||||
const status=deadlineReached?'partial':unavailable.length?(alreadyPresent||downloaded?'partial':'not_available'):'complete';
|
||||
const summary=deadlineReached
|
||||
?`Qualified CSO image preload partial: ${alreadyPresent+downloaded}/${entries.length} available; inspected ${inspected}/${entries.length}; the bounded aggregate deadline was reached. Rerun setup to continue.`
|
||||
:perImageTimeouts
|
||||
?`Qualified CSO image preload ${status}: ${alreadyPresent+downloaded}/${entries.length} available; inspected ${inspected}/${entries.length}; ${perImageTimeouts} exceeded the ${Math.ceil(perImageBudgetMs/1000)}-second per-image deadline. Increase GSTACK_CSO_IMAGE_PULL_TIMEOUT_SECONDS within 5..300 or rerun setup to continue.`
|
||||
:unavailable.length
|
||||
?`Qualified CSO image preload ${status}: ${alreadyPresent+downloaded}/${entries.length} available; inspected ${inspected}/${entries.length}; ${unavailable.length} require local Docker and anonymous public registry access. Rerun setup after the prerequisite is available.`
|
||||
:`Qualified CSO images ready: ${entries.length} available (${downloaded} downloaded, ${alreadyPresent} already local).`;
|
||||
return{schemaVersion:1,status,downloads:true,platform,requested:entries.length,inspected,alreadyPresent,downloaded,deadlineReached,unavailable,summary};
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
const status = deadlineReached
|
||||
? 'partial'
|
||||
: unavailable.length
|
||||
? alreadyPresent || downloaded
|
||||
? 'partial'
|
||||
: 'not_available'
|
||||
: 'complete';
|
||||
const summary = deadlineReached
|
||||
? `Qualified CSO image preload partial: ${alreadyPresent + downloaded}/${entries.length} available; inspected ${inspected}/${entries.length}; the bounded aggregate deadline was reached. Rerun setup to continue.`
|
||||
: perImageTimeouts
|
||||
? `Qualified CSO image preload ${status}: ${alreadyPresent + downloaded}/${entries.length} available; inspected ${inspected}/${entries.length}; ${perImageTimeouts} exceeded the ${Math.ceil(perImageBudgetMs / 1000)}-second per-image deadline. Increase GSTACK_CSO_IMAGE_PULL_TIMEOUT_SECONDS within 5..300 or rerun setup to continue.`
|
||||
: unavailable.length
|
||||
? `Qualified CSO image preload ${status}: ${alreadyPresent + downloaded}/${entries.length} available; inspected ${inspected}/${entries.length}; ${unavailable.length} require local Docker and anonymous public registry access. Rerun setup after the prerequisite is available.`
|
||||
: `Qualified CSO images ready: ${entries.length} available (${downloaded} downloaded, ${alreadyPresent} already local).`;
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
status,
|
||||
downloads: true,
|
||||
platform,
|
||||
requested: entries.length,
|
||||
inspected,
|
||||
alreadyPresent,
|
||||
downloaded,
|
||||
deadlineReached,
|
||||
unavailable,
|
||||
summary,
|
||||
};
|
||||
}
|
||||
|
||||
export async function openLocalCatalogImageSession(env:Record<string,string|undefined>=process.env,deadline=Date.now()+CATALOG_IMAGE_INSPECTION_BUDGET_MS):Promise<CatalogImageSession>{
|
||||
let home='';
|
||||
try{
|
||||
home=secureDirectory(fs.mkdtempSync(join(fs.realpathSync(os.tmpdir()),'gstack-cso-images-')));
|
||||
export async function openLocalCatalogImageSession(
|
||||
env: Record<string, string | undefined> = process.env,
|
||||
deadline = Date.now() + CATALOG_IMAGE_INSPECTION_BUDGET_MS,
|
||||
): Promise<CatalogImageSession> {
|
||||
let home = '';
|
||||
try {
|
||||
home = secureDirectory(fs.mkdtempSync(join(fs.realpathSync(os.tmpdir()), 'gstack-cso-images-')));
|
||||
// Endpoint discovery and the daemon probe must not borrow the download
|
||||
// allowance. A slow or hostile local Docker endpoint gets the same bounded
|
||||
// admission window in doctor and setup; successful pulls keep the caller's
|
||||
// larger aggregate deadline below.
|
||||
const admissionDeadline=Math.min(deadline,Date.now()+CATALOG_IMAGE_ADMISSION_BUDGET_MS);
|
||||
const endpoint=await dockerEndpoint(home,env,admissionDeadline),config=secureDirectory(join(home,'docker-config'));
|
||||
const admissionDeadline = Math.min(deadline, Date.now() + CATALOG_IMAGE_ADMISSION_BUDGET_MS);
|
||||
const endpoint = await dockerEndpoint(home, env, admissionDeadline),
|
||||
config = secureDirectory(join(home, 'docker-config'));
|
||||
// dockerEnvironment pins both HOME and DOCKER_CONFIG here. An explicit
|
||||
// empty auth map prevents inherited credential stores/helpers from being
|
||||
// consulted during installation-time public pulls.
|
||||
fs.writeFileSync(join(config,'config.json'),'{"auths":{}}\n',{encoding:'utf8',mode:0o600,flag:'wx'});
|
||||
const probe=await dockerProbe(endpoint,home,admissionDeadline),docker={endpoint:endpoint.uri,...probe};
|
||||
let closed=false;
|
||||
return{
|
||||
fs.writeFileSync(join(config, 'config.json'), '{"auths":{}}\n', {
|
||||
encoding: 'utf8',
|
||||
mode: 0o600,
|
||||
flag: 'wx',
|
||||
});
|
||||
const probe = await dockerProbe(endpoint, home, admissionDeadline),
|
||||
docker = { endpoint: endpoint.uri, ...probe };
|
||||
let closed = false;
|
||||
return {
|
||||
docker,
|
||||
present:(entry,operationDeadline=deadline)=>{if(closed)throw new CsoError('ISOLATION_FAILED','Catalog image session is closed');return dockerExactImagePresent(endpoint,home,entry.image,entry.platform,Math.min(deadline,operationDeadline));},
|
||||
pull:(entry,operationDeadline=deadline)=>{if(closed)throw new CsoError('ISOLATION_FAILED','Catalog image session is closed');return dockerPullExactCatalogImage(endpoint,home,entry.image,entry.platform,Math.min(deadline,operationDeadline));},
|
||||
close:()=>{if(closed)return;closed=true;fs.rmSync(home,{recursive:true,force:true});},
|
||||
present: (entry, operationDeadline = deadline) => {
|
||||
if (closed) throw new CsoError('ISOLATION_FAILED', 'Catalog image session is closed');
|
||||
return dockerExactImagePresent(
|
||||
endpoint,
|
||||
home,
|
||||
entry.image,
|
||||
entry.platform,
|
||||
Math.min(deadline, operationDeadline),
|
||||
);
|
||||
},
|
||||
pull: (entry, operationDeadline = deadline) => {
|
||||
if (closed) throw new CsoError('ISOLATION_FAILED', 'Catalog image session is closed');
|
||||
return dockerPullExactCatalogImage(
|
||||
endpoint,
|
||||
home,
|
||||
entry.image,
|
||||
entry.platform,
|
||||
Math.min(deadline, operationDeadline),
|
||||
);
|
||||
},
|
||||
close: () => {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
fs.rmSync(home, { recursive: true, force: true });
|
||||
},
|
||||
};
|
||||
}catch(error){if(home)fs.rmSync(home,{recursive:true,force:true});throw error;}
|
||||
} catch (error) {
|
||||
if (home) fs.rmSync(home, { recursive: true, force: true });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user