@puls-atlas/cli
Version:
The Puls Atlas CLI tool for managing Atlas projects
158 lines • 7.56 kB
JavaScript
import { loadFeatureContext } from '../../utils/feature.js';
import { logger } from '../../utils/index.js';
import { normalizeOptionalString } from '../../utils/value.js';
import { resolveRuntimeEnvironmentBinding } from './configShape.js';
import { resolveLatestRuntimeServiceCatalogVersion, resolveRuntimeServiceCatalogService, resolveRuntimeServiceCatalogVersions } from './runtimeServiceCatalog.js';
import { findService } from './serviceRegistry.js';
const DEFAULT_RUNTIME_SERVICE_DESCRIBE_VERSION_LIMIT = 10;
const createBuiltInServiceDescribeError = serviceName => new Error('Atlas service describe currently supports only runtime Cloud Run services. ' + `"${serviceName}" is a built-in service and cannot be resolved from the runtime catalog.`);
const createRuntimeServiceDescribeError = message => {
throw new Error(message);
};
const normalizeRuntimeServiceDescribeVersionLimit = value => {
if (value === undefined || value === null || value === '') {
return DEFAULT_RUNTIME_SERVICE_DESCRIBE_VERSION_LIMIT;
}
const parsedValue = Number(value);
if (!Number.isInteger(parsedValue) || parsedValue < 1) {
createRuntimeServiceDescribeError('Atlas runtime service describe option --limit must be a positive integer.');
}
return parsedValue;
};
const getCurrentRuntimeServiceRootConfig = (context, serviceName) => context.config?.services?.[serviceName] ?? {};
const getCurrentRuntimeServiceBinding = (rootConfig, context) => resolveRuntimeEnvironmentBinding(rootConfig, context.environment) ?? {};
const resolveRuntimeServiceCurrentCatalogVersion = (rootConfig, context) => normalizeOptionalString(getCurrentRuntimeServiceBinding(rootConfig, context).release?.catalogVersion);
const assertProjectScopedRuntimeCatalogService = service => {
const scope = normalizeOptionalString(service?.serviceDeploy?.scope);
if (scope === 'project') {
return;
}
createRuntimeServiceDescribeError(`Atlas runtime service ${service?.id ?? '(unknown)'} must define serviceDeploy.scope as "project".`);
};
export const createRuntimeServiceDescribePlan = async (serviceName, options = {}, dependencies = {}, cwd = process.cwd()) => {
const loadFeatureContextImpl = dependencies.loadFeatureContext ?? loadFeatureContext;
const resolveRuntimeServiceCatalogServiceImpl = dependencies.resolveRuntimeServiceCatalogService ?? resolveRuntimeServiceCatalogService;
const resolveLatestRuntimeServiceCatalogVersionImpl = dependencies.resolveLatestRuntimeServiceCatalogVersion ?? resolveLatestRuntimeServiceCatalogVersion;
const resolveRuntimeServiceCatalogVersionsImpl = dependencies.resolveRuntimeServiceCatalogVersions ?? resolveRuntimeServiceCatalogVersions;
const context = await loadFeatureContextImpl('services', options, {
cwd
});
const currentRootServiceConfig = getCurrentRuntimeServiceRootConfig(context, serviceName);
const currentBindingConfig = getCurrentRuntimeServiceBinding(currentRootServiceConfig, context);
const configuredCatalogVersion = resolveRuntimeServiceCurrentCatalogVersion(currentRootServiceConfig, context);
const versionLimit = normalizeRuntimeServiceDescribeVersionLimit(options.limit);
const availableVersions = resolveRuntimeServiceCatalogVersionsImpl({
limit: versionLimit,
runCommand: dependencies.runCommand
});
const latestCatalogVersion = availableVersions[0] ?? resolveLatestRuntimeServiceCatalogVersionImpl({
runCommand: dependencies.runCommand
});
const selectedCatalogVersion = configuredCatalogVersion ?? latestCatalogVersion;
const resolvedCatalog = resolveRuntimeServiceCatalogServiceImpl(serviceName, {
...(selectedCatalogVersion ? {
catalogVersion: selectedCatalogVersion
} : {}),
runCommand: dependencies.runCommand
});
const catalogService = resolvedCatalog.service;
assertProjectScopedRuntimeCatalogService(catalogService);
return {
availableVersions,
catalogService,
configuredCatalogVersion,
context,
currentBindingConfig,
currentRootServiceConfig,
latestCatalogVersion,
selectedCatalogVersion: resolvedCatalog.catalogVersion,
serviceName,
updateAvailable: Boolean(configuredCatalogVersion) && Boolean(latestCatalogVersion) && configuredCatalogVersion !== latestCatalogVersion,
versionLimit
};
};
export const runRuntimeServiceDescribe = async (serviceName, options = {}, dependencies = {}, cwd = process.cwd()) => {
const loggerImpl = dependencies.logger ?? logger;
let spinner;
try {
const plan = await createRuntimeServiceDescribePlan(serviceName, options, dependencies, cwd);
spinner = loggerImpl.spinner(`Preparing Atlas ${serviceName} runtime service details...`);
spinner.succeed('Atlas runtime service details are ready.');
loggerImpl.summary('Runtime service details', [{
label: 'Service',
value: plan.catalogService.displayName
}, {
label: 'Project',
value: plan.context.projectId
}, {
label: 'Environment',
value: plan.context.environment ?? 'unmapped'
}, {
label: 'Cloud Run service',
value: plan.catalogService.image.imageName
}, {
label: 'Runtime role',
value: plan.catalogService.runtime.role
}, {
label: 'Configured catalog version',
value: plan.configuredCatalogVersion ?? 'not configured'
}, {
label: 'Selected catalog version',
value: plan.selectedCatalogVersion
}, {
label: 'Latest catalog version',
value: plan.latestCatalogVersion
}, {
label: 'Service URL',
value: normalizeOptionalString(plan.currentBindingConfig.runtime?.serviceUrl) ?? 'unknown'
}, {
label: 'Available catalog versions',
value: plan.availableVersions.length > 0 ? plan.availableVersions.join(', ') : 'No catalog versions were found.'
}, {
label: 'Update available',
value: plan.updateAvailable ? 'yes' : 'no'
}]);
if (plan.updateAvailable) {
loggerImpl.warning(`A newer catalog version is available. Run atlas service deploy ${plan.catalogService.id} --latest to upgrade.`);
}
return {
availableVersions: plan.availableVersions,
configuredCatalogVersion: plan.configuredCatalogVersion,
latestCatalogVersion: plan.latestCatalogVersion,
selectedCatalogVersion: plan.selectedCatalogVersion,
serviceName: plan.catalogService.id,
status: 'described',
updateAvailable: plan.updateAvailable
};
} catch (error) {
spinner?.fail('Failed to describe Atlas runtime service.');
throw error;
}
};
export const createServiceDescribeHandlers = (dependencies = {}) => {
const findConfiguredService = dependencies.findService ?? findService;
const runConfiguredRuntimeServiceDescribe = dependencies.runRuntimeServiceDescribe ?? runRuntimeServiceDescribe;
return {
describe: (serviceName, options) => {
const registeredService = findConfiguredService(serviceName);
if (registeredService) {
throw createBuiltInServiceDescribeError(registeredService.id);
}
return runConfiguredRuntimeServiceDescribe(serviceName, options);
}
};
};
export const describeService = async (serviceName, options, {
exitImpl = code => process.exit(code),
loggerImpl = logger,
...dependencies
} = {}) => {
try {
return await createServiceDescribeHandlers(dependencies).describe(serviceName, options);
} catch (error) {
loggerImpl.error(error.message, false);
exitImpl(1);
return undefined;
}
};
export default async (serviceName, options) => describeService(serviceName, options);