@puls-atlas/cli
Version:
The Puls Atlas CLI tool for managing Atlas projects
185 lines • 7.97 kB
JavaScript
import * as features from '../../utils/feature.js';
import { isGcloudResourceNotFoundError, logger, runGcloudFileCommand } from '../../utils/index.js';
import { compileSyncPlan, DEFAULT_SYNC_TASK_QUEUE_NAME } from './config/syncConfig.js';
import { buildSyncRuntimeConfig } from './apply.js';
import { resolveSyncCloudRunDeployConfig } from './deploymentConfig.js';
import { SYNC_BACKFILL_JOB_NAME, SYNC_SERVICE_NAME } from './resourceNames.js';
import { createSyncReadAliasEntryRows, createSyncReadAliasSummaryRows, createSyncSchemaPlanEntryRows, createSyncSchemaPlanSummaryRows, inspectSyncManagedSchemas, inspectSyncReadAliases } from './schemaPlan.js';
const checkGcsObjectAccessible = gsUri => {
try {
runGcloudFileCommand(['storage', 'cat', '--range=0-0', gsUri], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
});
return 'accessible';
} catch (error) {
if (isGcloudResourceNotFoundError(error)) {
return 'not-found';
}
return 'inaccessible';
}
};
const checkCloudRunServiceDeployed = (projectId, region, serviceName) => {
try {
runGcloudFileCommand(['run', 'services', 'describe', serviceName, `--project=${projectId}`, `--region=${region}`, '--format=value(name)'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
});
return 'deployed';
} catch (error) {
if (isGcloudResourceNotFoundError(error)) {
return 'not-deployed';
}
return 'unknown';
}
};
const checkCloudRunJobDeployed = (projectId, region, jobName) => {
try {
runGcloudFileCommand(['run', 'jobs', 'describe', jobName, `--project=${projectId}`, `--region=${region}`, '--format=value(name)'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
});
return 'deployed';
} catch (error) {
if (isGcloudResourceNotFoundError(error)) {
return 'not-deployed';
}
return 'unknown';
}
};
const checkCloudTasksQueueExists = (projectId, location, queueName) => {
try {
runGcloudFileCommand(['tasks', 'queues', 'describe', queueName, `--location=${location}`, `--project=${projectId}`, '--format=value(name)'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
});
return 'deployed';
} catch (error) {
if (isGcloudResourceNotFoundError(error)) {
return 'not-deployed';
}
return 'unknown';
}
};
const createRemoteVerificationRows = remoteChecks => [{
label: `Cloud Run service (${SYNC_SERVICE_NAME})`,
value: remoteChecks.service === 'deployed' ? 'OK' : `NOT OK (${remoteChecks.service})`
}, {
label: `Backfill job (${SYNC_BACKFILL_JOB_NAME})`,
value: remoteChecks.backfillJob === 'deployed' ? 'OK' : `NOT OK (${remoteChecks.backfillJob})`
}, {
label: 'Runtime config URI',
value: remoteChecks.runtimeConfigUri === 'accessible' ? 'OK' : `NOT OK (${remoteChecks.runtimeConfigUri})`
}, {
label: 'Mapper manifest URI',
value: remoteChecks.mapperManifestUri === 'accessible' ? 'OK' : `NOT OK (${remoteChecks.mapperManifestUri})`
}, {
label: 'Task queue',
value: remoteChecks.taskQueue === 'deployed' ? 'OK' : `NOT OK (${remoteChecks.taskQueue})`
}];
const createSyncVerificationRows = (context, syncPlan) => [{
label: 'Project',
value: context.projectId
}, context.environment ? {
label: 'Environment',
value: context.environment
} : null, {
label: 'Version',
value: context.config.version
}, {
label: 'Verified workloads',
value: Object.keys(context.config?.workloads ?? {}).length
}, {
label: 'Enabled pipelines',
value: syncPlan.pipelines.length
}, {
label: 'Sources',
value: Object.keys(context.config?.sources ?? {}).length
}, {
label: 'Destinations',
value: Object.keys(context.config?.destinations ?? {}).length
}];
export const verifySyncConfig = async (options, {
compileSyncPlanImpl = compileSyncPlan,
inspectSyncManagedSchemasImpl = inspectSyncManagedSchemas,
inspectSyncReadAliasesImpl = inspectSyncReadAliases,
loadFeatureContextImpl = features.loadFeatureContext,
loggerImpl = logger,
workingDirectory = process.cwd()
} = {}) => {
let spinner;
try {
const context = await loadFeatureContextImpl('sync', options, {
cwd: workingDirectory
});
spinner = loggerImpl.spinner('Verifying Atlas sync config...');
const syncPlan = compileSyncPlanImpl(context.config);
spinner.succeed('Atlas sync config verified.');
loggerImpl.summary('Verification summary', createSyncVerificationRows(context, syncPlan));
loggerImpl.summary('Local files', [{
label: 'Config',
value: context.configPath
}, {
label: 'Workload files',
value: context.configPaths.length
}]);
if (!options.localOnly) {
const deployConfig = resolveSyncCloudRunDeployConfig(context);
const remoteSpinner = loggerImpl.spinner('Checking remote Atlas sync resources...');
const previewRuntimeConfig = buildSyncRuntimeConfig({
...context,
syncPlan
});
const taskQueueName = context.config?.deploy?.taskQueue?.queueName ?? DEFAULT_SYNC_TASK_QUEUE_NAME;
const taskQueueLocation = context.config?.deploy?.taskQueue?.location ?? deployConfig.region;
const remoteChecks = {
service: checkCloudRunServiceDeployed(context.projectId, deployConfig.region, SYNC_SERVICE_NAME),
backfillJob: checkCloudRunJobDeployed(context.projectId, deployConfig.region, SYNC_BACKFILL_JOB_NAME),
runtimeConfigUri: deployConfig.runtimeConfigUri ? checkGcsObjectAccessible(deployConfig.runtimeConfigUri) : 'not-configured',
mapperManifestUri: deployConfig.mapperManifestUri ? checkGcsObjectAccessible(deployConfig.mapperManifestUri) : 'not-configured',
taskQueue: checkCloudTasksQueueExists(context.projectId, taskQueueLocation, taskQueueName)
};
const schemaPlan = await inspectSyncManagedSchemasImpl(context, previewRuntimeConfig, {
strict: true
});
const readAliasPlan = await inspectSyncReadAliasesImpl(context, previewRuntimeConfig, {
strict: true
});
remoteSpinner.succeed('Remote Atlas sync resource verification complete.');
loggerImpl.summary('Remote resource checks', createRemoteVerificationRows(remoteChecks));
if (schemaPlan.entries.length > 0) {
loggerImpl.summary('Managed schema targets', createSyncSchemaPlanEntryRows(schemaPlan));
loggerImpl.summary('Managed schema summary', createSyncSchemaPlanSummaryRows(schemaPlan));
}
if (readAliasPlan.entries.length > 0) {
loggerImpl.summary('BigQuery read aliases', createSyncReadAliasEntryRows(readAliasPlan));
loggerImpl.summary('BigQuery read alias summary', createSyncReadAliasSummaryRows(readAliasPlan));
}
for (const warning of schemaPlan.warnings ?? []) {
loggerImpl.warning(warning);
}
for (const warning of readAliasPlan.warnings ?? []) {
loggerImpl.warning(warning);
}
const failedChecks = Object.values(remoteChecks).filter(status => status !== 'deployed' && status !== 'accessible');
if (failedChecks.length > 0) {
throw new Error(`${failedChecks.length} remote resource check(s) failed. Run "atlas sync deploy" to provision missing resources.`);
}
if (schemaPlan.blockingIssues.length > 0) {
throw new Error(schemaPlan.blockingIssues.join(' '));
}
if (readAliasPlan.blockingIssues.length > 0) {
throw new Error(readAliasPlan.blockingIssues.join(' '));
}
}
if (syncPlan.pipelines.length === 0) {
loggerImpl.warning('Atlas sync config is valid, but no workloads are enabled for the selected project.');
return;
}
loggerImpl.info(`Atlas sync verified ${syncPlan.pipelines.length} enabled workload${syncPlan.pipelines.length === 1 ? '' : 's'}.`);
} catch (error) {
spinner?.fail('Failed to verify Atlas sync config.');
loggerImpl.error(error.message);
}
};
export default async options => verifySyncConfig(options);