UNPKG

@puls-atlas/cli

Version:

The Puls Atlas CLI tool for managing Atlas projects

158 lines 7.56 kB
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);