@puls-atlas/cli
Version:
The Puls Atlas CLI tool for managing Atlas projects
153 lines • 6.66 kB
JavaScript
import * as features from '../../utils/feature.js';
import { resolveSyncReleaseTarget } from './release.js';
import { getAtlasGeneratedFeatureConfigPath, loadAtlasFeatureCache, logger, readJsonFile } from '../../utils/index.js';
import { createSyncReadAliasEntryRows, createSyncReadAliasSummaryRows, createSyncSchemaPlanSummaryRows } from './schemaPlan.js';
const formatReleaseValue = value => value ? value : 'not set';
const formatBooleanValue = value => {
if (value === true) {
return 'yes';
}
if (value === false) {
return 'no';
}
return 'not set';
};
const formatRoutingTargetValue = routingTarget => {
if (!routingTarget) {
return 'not set';
}
if (routingTarget.dataset && routingTarget.table) {
return `${routingTarget.target ?? 'unknown'} (${routingTarget.dataset}.${routingTarget.table})`;
}
if (routingTarget.schema && routingTarget.table) {
return `${routingTarget.target ?? 'unknown'} (${routingTarget.schema}.${routingTarget.table})`;
}
return routingTarget.target ?? 'not set';
};
const createPipelineRoutingEntries = runtimeConfig => {
if (!Array.isArray(runtimeConfig?.pipelines) || runtimeConfig.pipelines.length === 0) {
return [];
}
return runtimeConfig.pipelines.map(pipeline => {
const routing = pipeline?.destination?.routing ?? {};
const writeTargets = Array.isArray(routing.writeTargets) ? routing.writeTargets.map(formatRoutingTargetValue).join(', ') : '';
return `${pipeline.workloadKey}: read=${formatRoutingTargetValue(routing.read)}, write=${formatRoutingTargetValue(routing.write)}, writeTargets=${writeTargets || 'not set'}, backfill=${formatRoutingTargetValue(routing.backfill)}`;
});
};
export const showSyncPromoteStatus = async (options = {}, dependencies = {}, cwd = process.cwd()) => {
const getAtlasGeneratedFeatureConfigPathImpl = dependencies.getAtlasGeneratedFeatureConfigPath ?? getAtlasGeneratedFeatureConfigPath;
const loadFeatureContextImpl = dependencies.loadFeatureContext ?? features.loadFeatureContext;
const loadFeatureCacheImpl = dependencies.loadFeatureCache ?? loadAtlasFeatureCache;
const loggerImpl = dependencies.logger ?? logger;
const readJsonFileImpl = dependencies.readJsonFile ?? readJsonFile;
const exit = dependencies.exit ?? (code => process.exit(code));
let spinner;
try {
spinner = loggerImpl.spinner('Loading Atlas sync promotion status...');
const context = await loadFeatureContextImpl('sync', options, {
cwd
});
const cacheArtifact = loadFeatureCacheImpl('sync', context.projectId, cwd);
const runtimeConfigPath = getAtlasGeneratedFeatureConfigPathImpl('sync', context.projectId, cwd);
const runtimeConfig = readJsonFileImpl(runtimeConfigPath, {
allowMissing: true
});
const release = cacheArtifact.cache?.release ?? runtimeConfig?.release ?? null;
const releaseTarget = cacheArtifact.cache?.releaseTarget ?? runtimeConfig?.releaseTarget ?? resolveSyncReleaseTarget(release);
const rollout = cacheArtifact.cache?.rollout ?? runtimeConfig?.rollout ?? null;
const readAliasPlan = cacheArtifact.cache?.readAliasPlan ?? runtimeConfig?.readAliasPlan ?? null;
const schemaPlan = cacheArtifact.cache?.schemaPlan ?? runtimeConfig?.schemaPlan ?? null;
spinner.succeed('Atlas sync promotion status loaded.');
loggerImpl.summary('Promotion status', [{
label: 'Project',
value: context.projectId
}, context.environment ? {
label: 'Environment',
value: context.environment
} : null, {
label: 'Config',
value: context.configPath
}, {
label: 'Local cache',
value: cacheArtifact.cache ? cacheArtifact.filePath : 'missing'
}, {
label: 'Runtime config',
value: runtimeConfig ? runtimeConfigPath : 'not generated'
}, {
label: 'Release strategy',
value: releaseTarget.strategy
}, {
label: 'Active release',
value: formatReleaseValue(releaseTarget.active)
}, {
label: 'Candidate release',
value: formatReleaseValue(releaseTarget.candidate)
}, {
label: 'Promotion target',
value: releaseTarget.target
}, {
label: 'Target release id',
value: formatReleaseValue(releaseTarget.releaseId)
}, {
label: 'Rollout mode',
value: rollout?.mode ?? 'not set'
}, {
label: 'Read target',
value: rollout?.readTarget ?? 'not set'
}, {
label: 'Write target',
value: rollout?.writeTarget ?? 'not set'
}, {
label: 'Backfill target',
value: rollout?.backfillTarget ?? 'not required'
}, {
label: 'Requires cutover',
value: formatBooleanValue(rollout?.requiresCutover)
}, {
label: 'Config fingerprint',
value: formatReleaseValue(rollout?.configFingerprint)
}, {
label: 'Active fingerprint',
value: formatReleaseValue(rollout?.activeConfigFingerprint)
}, {
label: 'Candidate fingerprint',
value: formatReleaseValue(rollout?.candidateConfigFingerprint)
}, runtimeConfig?.pipelines ? {
label: 'Enabled workloads',
value: runtimeConfig.pipelines.length
} : null, ...createSyncReadAliasSummaryRows(readAliasPlan), ...createSyncSchemaPlanSummaryRows(schemaPlan)]);
const pipelineRoutingEntries = createPipelineRoutingEntries(runtimeConfig);
if (pipelineRoutingEntries.length > 0) {
loggerImpl.section('Pipeline routing', pipelineRoutingEntries);
}
if (readAliasPlan?.entries?.length > 0) {
loggerImpl.summary('BigQuery read aliases', createSyncReadAliasEntryRows(readAliasPlan));
}
if (!cacheArtifact.cache) {
loggerImpl.warning('Atlas sync promotion status is missing the local rollout cache. Run "atlas sync apply" to materialize or refresh the local release state.');
}
if (!runtimeConfig) {
loggerImpl.warning('Atlas sync promotion status could not find a generated runtime config. Run "atlas sync apply" before inspecting pipeline routing.');
}
if (rollout?.requiresCutover === true) {
loggerImpl.warning('Atlas sync candidate release still requires cutover. Promote it with "atlas sync promote" after the candidate backfill has completed successfully.');
}
return {
cacheArtifact,
configPath: context.configPath,
releaseTarget,
rollout,
readAliasPlan,
runtimeConfig,
schemaPlan,
status: 'loaded'
};
} catch (error) {
if (spinner) {
spinner.fail('Failed to load Atlas sync promotion status.');
}
loggerImpl.error(error.message, false);
return exit(1);
}
};
export default async options => showSyncPromoteStatus(options);