@puls-atlas/cli
Version:
The Puls Atlas CLI tool for managing Atlas projects
148 lines • 6.56 kB
JavaScript
import { execFileSync as nodeExecFileSync } from 'child_process';
import { hasScheduledSearchSources } from './scheduled.js';
import { normalizeOptionalString } from '../../../utils/value.js';
import { SEARCH_SOURCE_RUNNER_JOB_NAME } from '../resourceNames.js';
import { resolveSearchRuntimeContract } from '../planning.js';
import { resolveSearchCloudRunDeployConfig, resolveSearchTaskQueueConfig } from '../deploymentConfig.js';
import { PULS_ATLAS_DEFAULT_REGION_ENV, PULS_ATLAS_PROJECT_ID_ENV, PULS_ATLAS_SEARCH_CONFIG_URI_ENV, PULS_ATLAS_SEARCH_INDEXES_ENV, PULS_ATLAS_SEARCH_PROVIDER_ENV, PULS_ATLAS_SEARCH_REINDEX_SESSION_ID_ENV, PULS_ATLAS_SEARCH_SOURCE_NAME_ENV, SEARCH_SYNC_TASK_LOCATION_ENV, SEARCH_SYNC_TASK_QUEUE_ENV } from '../runtimeEnv.js';
import { createGcloudRunJobExecuteCommand, getCommandErrorMessage, isGcloudResourceNotFoundError, runGcloudFileCommand } from '../../../utils/gcloud.js';
export const SEARCH_SOURCE_RUNNER_CONTAINER_NAME = 'runtime';
const toEnvironmentVariableMap = environmentVariables => Object.fromEntries(environmentVariables.map(environmentVariable => [environmentVariable.name, environmentVariable.value]));
const toSecretEnvironmentVariableMap = secretEnvironmentVariables => Object.fromEntries(secretEnvironmentVariables.map(secretEnvironmentVariable => [secretEnvironmentVariable.env, {
secret_name: secretEnvironmentVariable.secretName,
version: secretEnvironmentVariable.version
}]));
const resolveSearchSourceRunnerEnvironmentVariables = context => {
const cloudRunConfig = resolveSearchCloudRunDeployConfig(context);
const taskQueueConfig = resolveSearchTaskQueueConfig(context);
return [{
name: PULS_ATLAS_PROJECT_ID_ENV,
required: true,
source: 'atlas search apply',
value: context.projectId
}, {
name: PULS_ATLAS_DEFAULT_REGION_ENV,
required: true,
source: 'atlas search apply',
value: cloudRunConfig.region
}, {
name: PULS_ATLAS_SEARCH_PROVIDER_ENV,
required: true,
source: 'atlas search apply',
value: context.config.provider
}, {
name: SEARCH_SYNC_TASK_LOCATION_ENV,
required: true,
source: 'atlas search apply',
value: taskQueueConfig.location
}, {
name: SEARCH_SYNC_TASK_QUEUE_ENV,
required: true,
source: 'atlas search apply',
value: taskQueueConfig.name
}, {
name: PULS_ATLAS_SEARCH_CONFIG_URI_ENV,
required: true,
source: 'atlas search apply',
value: cloudRunConfig.runtimeConfigUri ?? ''
}];
};
export const resolveSearchSourceRunnerJobDeployment = context => {
if (!hasScheduledSearchSources(context.config)) {
return null;
}
const cloudRunConfig = resolveSearchCloudRunDeployConfig(context);
const runtimeContract = resolveSearchRuntimeContract(context);
const runtimeSecretEnvironmentVariables = runtimeContract.requiredSecretEnvironmentVariables.filter(secretReference => secretReference.services?.includes(SEARCH_SOURCE_RUNNER_JOB_NAME));
return {
image: cloudRunConfig.sourceRunnerJobImage,
jobName: SEARCH_SOURCE_RUNNER_JOB_NAME,
projectId: context.projectId,
region: cloudRunConfig.region,
runtimeEnvironmentVariables: toEnvironmentVariableMap(resolveSearchSourceRunnerEnvironmentVariables(context)),
runtimeSecretEnvironmentVariables: toSecretEnvironmentVariableMap(runtimeSecretEnvironmentVariables),
serviceAccountEmail: cloudRunConfig.serviceAccountEmail
};
};
export const inspectSearchSourceRunnerJob = (context, options = {}) => {
const {
runCommand = nodeExecFileSync
} = options;
const deployment = resolveSearchSourceRunnerJobDeployment(context);
if (!deployment) {
return {
exists: false,
jobName: SEARCH_SOURCE_RUNNER_JOB_NAME,
projectId: context.projectId,
region: null
};
}
try {
const resolvedJobName = runGcloudFileCommand(['run', 'jobs', 'describe', deployment.jobName, `--project=${deployment.projectId}`, `--region=${deployment.region}`, '--format="value(metadata.name)"'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
}, runCommand).trim();
return {
exists: resolvedJobName.length > 0,
jobName: deployment.jobName,
projectId: deployment.projectId,
region: deployment.region
};
} catch (error) {
if (isGcloudResourceNotFoundError(error)) {
return {
exists: false,
jobName: deployment.jobName,
projectId: deployment.projectId,
region: deployment.region
};
}
throw new Error(`Failed to inspect Atlas search source runner job ${deployment.jobName} in project ${context.projectId}. ` + `${normalizeOptionalString(getCommandErrorMessage(error)) ?? error.message}`);
}
};
export const ensureSearchSourceRunnerJobExists = (context, options = {}) => {
const inspection = inspectSearchSourceRunnerJob(context, options);
if (!inspection.exists) {
throw new Error(`Atlas search source runner job ${inspection.jobName} was not found in project ${inspection.projectId}. ` + 'Run "atlas search apply" to deploy the source runner job before running "atlas search run source --source <name>".');
}
return inspection;
};
export const createSearchSourceRunnerJobExecutionCommand = ({
context,
indexNames = [],
reindexSessionId = null,
sourceName
}) => {
const deployment = resolveSearchSourceRunnerJobDeployment(context);
if (!deployment) {
throw new Error('Atlas search source runner is not configured. Ensure at least one scheduled source is enabled in the search config.');
}
const environmentVariables = {
[PULS_ATLAS_SEARCH_SOURCE_NAME_ENV]: sourceName
};
const normalizedIndexNames = [...new Set((Array.isArray(indexNames) ? indexNames : []).map(indexName => normalizeOptionalString(indexName)).filter(Boolean))];
const normalizedReindexSessionId = normalizeOptionalString(reindexSessionId);
if (normalizedIndexNames.length > 0) {
environmentVariables[PULS_ATLAS_SEARCH_INDEXES_ENV] = normalizedIndexNames.join(',');
}
if (normalizedReindexSessionId) {
environmentVariables[PULS_ATLAS_SEARCH_REINDEX_SESSION_ID_ENV] = normalizedReindexSessionId;
}
return {
environmentVariables,
jobName: deployment.jobName,
projectId: deployment.projectId,
region: deployment.region,
sourceName,
...createGcloudRunJobExecuteCommand({
environmentVariables,
jobName: deployment.jobName,
projectId: deployment.projectId,
region: deployment.region
})
};
};
export default {
SEARCH_SOURCE_RUNNER_CONTAINER_NAME,
resolveSearchSourceRunnerJobDeployment
};