@puls-atlas/cli
Version:
The Puls Atlas CLI tool for managing Atlas projects
502 lines • 25.2 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { resolveRootPath } from '../../utils/atlas.js';
import { writeJsonFile } from '../../utils/file.js';
import { isGcloudResourceNotFoundError, runGcloudFileCommand } from '../../utils/gcloud.js';
import { SEARCH_RESOURCE_NAMES } from './resourceNames.js';
import { getEnabledSearchEventarcCollectionPlans } from './eventarc.js';
import { ATLAS_SEARCH_TASK_QUEUE_NAME } from '../../utils/taskQueue.js';
import { normalizeOptionalString, spreadIf } from '../../utils/value.js';
import { getEnabledScheduledSearchSourcePlans } from './sources/scheduled.js';
import { ensureTerraformWorkspace, createTerraformStringVariableArgument, runTerraformCommand } from '../../utils/terraform.js';
import { SEARCH_TERRAFORM_CONTRACT_VERSION, SEARCH_TERRAFORM_INPUT_VARIABLE } from './terraformAdapter.js';
import { DEFAULT_SEARCH_TERRAFORM_MODULE_SOURCE, DEFAULT_SEARCH_TERRAFORM_ROOT_DIR } from './config/searchConfig.js';
import { loadSearchTerraformRuntimeRootTemplateFiles, renderSearchTerraformRootTemplateFiles } from './terraformRootTemplates.js';
const SEARCH_TERRAFORM_IMPORT_ALREADY_MANAGED_PATTERN = /already managed by Terraform/iu;
const SEARCH_TERRAFORM_STATE_NOT_FOUND_PATTERN = /(no instance found for the given address|resource address .* does not exist in the state|no state file was found)/iu;
const GCLOUD_PERMISSION_DENIED_ERROR_PATTERN = /(permission[ -]?denied|PERMISSION_DENIED|does not have permission|insufficient permission|not authorized|forbidden|iam\.serviceAccounts\.get)/iu;
const SEARCH_TERRAFORM_ARTIFACT_BUCKET_STATE_ADDRESS_PATTERN = /^google_storage_bucket\.atlas_search_artifacts\["([^"]+)"\]$/u;
const SEARCH_TERRAFORM_DLQ_BUCKET_STATE_ADDRESS = 'google_storage_bucket.atlas_search_dlq[0]';
const SEARCH_TERRAFORM_PREREQUISITE_TARGET_KINDS = new Set(['secret-access']);
const resolveSearchTerraformTaskQueueSummary = searchConfig => ({
location: normalizeOptionalString(searchConfig?.deploy?.taskQueue?.region) ?? normalizeOptionalString(searchConfig?.deploy?.cloudRun?.region) ?? 'europe-west1',
name: normalizeOptionalString(searchConfig?.deploy?.taskQueue?.name) ?? ATLAS_SEARCH_TASK_QUEUE_NAME
});
const resolveSearchTerraformWorkspaceName = terraformArtifact => {
const workspaceName = normalizeOptionalString(terraformArtifact?.payload?.project_id);
if (workspaceName) {
return workspaceName;
}
throw new Error('Atlas search Terraform input must define payload.project_id before Terraform can select a workspace.');
};
const escapeTerraformString = value => String(value).replaceAll('\\', '\\\\').replaceAll('"', '\\"');
const readGcloudIamPolicy = (args, runGcloudFileCommandImpl = runGcloudFileCommand) => JSON.parse(runGcloudFileCommandImpl(args, {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
}));
const hasIamBinding = (policy, role, member) => (policy.bindings ?? []).some(binding => binding?.role === role && Array.isArray(binding.members) && binding.members.includes(member));
const renderSearchTerraformRootFiles = async (moduleSource, rootPath, options = {}) => {
const templateFiles = await loadSearchTerraformRuntimeRootTemplateFiles(moduleSource, rootPath, options);
const renderedFiles = renderSearchTerraformRootTemplateFiles(templateFiles, {
__ATLAS_SEARCH_TERRAFORM_CONTRACT_VERSION__: SEARCH_TERRAFORM_CONTRACT_VERSION,
__ATLAS_SEARCH_TERRAFORM_INPUT_VARIABLE__: SEARCH_TERRAFORM_INPUT_VARIABLE,
__ATLAS_SEARCH_TERRAFORM_MODULE_SOURCE__: escapeTerraformString(moduleSource)
});
return {
...renderedFiles
};
};
export const resolveSearchTerraformConfig = searchConfig => ({
moduleSource: searchConfig?.deploy?.terraform?.moduleSource ?? DEFAULT_SEARCH_TERRAFORM_MODULE_SOURCE,
rootDir: searchConfig?.deploy?.terraform?.rootDir ?? DEFAULT_SEARCH_TERRAFORM_ROOT_DIR
});
export const resolveSearchTerraformRootPath = (searchConfig, cwd = process.cwd()) => resolveRootPath(path.join(resolveSearchTerraformConfig(searchConfig).rootDir, 'runtime'), cwd);
export const createSearchTerraformWorkflowSummary = (searchConfig, cwd = process.cwd()) => {
const terraformConfig = resolveSearchTerraformConfig(searchConfig);
const eventarcCollectionPlans = getEnabledSearchEventarcCollectionPlans(searchConfig);
const scheduledSourcePlans = getEnabledScheduledSearchSourcePlans(searchConfig);
const taskQueueConfig = resolveSearchTerraformTaskQueueSummary(searchConfig);
return {
rootDir: terraformConfig.rootDir,
rootPath: resolveSearchTerraformRootPath(searchConfig, cwd),
moduleSource: terraformConfig.moduleSource,
managedResources: [{
description: 'Secret Manager accessor bindings for the configured Atlas search Cloud Run runtime identity.',
kind: 'secret-access',
name: 'atlas-search-runtime-secrets'
}, {
description: 'Static Cloud Run backfill job definition managed through the Atlas search Terraform workflow.',
kind: 'job',
name: 'atlas-search-backfill'
}, ...spreadIf(scheduledSourcePlans.length > 0, [{
description: 'Cloud Run job that executes scheduled Atlas search sources through the shared source adapter runtime.',
kind: 'job',
name: SEARCH_RESOURCE_NAMES.sourceRunnerJob
}]), {
description: 'Cloud Tasks queue that durably buffers Atlas search incremental sync work before atlas-search-sync processes it.',
kind: 'task-queue',
name: taskQueueConfig.name
}, ...scheduledSourcePlans.map(sourcePlan => ({
description: 'Cloud Scheduler job that triggers atlas-search-source-runner for one configured scheduled source.',
kind: 'scheduler-job',
name: sourcePlan.schedulerJobName
})), {
description: 'Eventarc trigger service account used to deliver Firestore direct events into atlas-search-sync.',
kind: 'service-account',
name: 'atlas-search-eventarc'
}, ...eventarcCollectionPlans.map(collectionPlan => ({
description: 'Firestore direct-event trigger routed to atlas-search-sync for the configured Atlas search collection.',
kind: 'eventarc-trigger',
name: collectionPlan.triggerName
}))]
};
};
export const ensureSearchTerraformRoot = async (searchConfig, cwd = process.cwd(), options = {}) => {
const {
existsSyncImpl = fs.existsSync,
mkdirSyncImpl = fs.mkdirSync,
readFileSyncImpl = fs.readFileSync,
writeFileSyncImpl = fs.writeFileSync
} = options;
const workflowSummary = createSearchTerraformWorkflowSummary(searchConfig, cwd);
const rootFiles = await renderSearchTerraformRootFiles(workflowSummary.moduleSource, workflowSummary.rootPath, options);
const createdFiles = [];
const updatedFiles = [];
if (!existsSyncImpl(workflowSummary.rootPath)) {
mkdirSyncImpl(workflowSummary.rootPath, {
recursive: true
});
}
for (const [fileName, content] of Object.entries(rootFiles)) {
const filePath = path.join(workflowSummary.rootPath, fileName);
if (!existsSyncImpl(filePath)) {
writeFileSyncImpl(filePath, content);
createdFiles.push(filePath);
continue;
}
if (readFileSyncImpl(filePath, 'utf-8') === content) {
continue;
}
writeFileSyncImpl(filePath, content);
updatedFiles.push(filePath);
}
return {
...workflowSummary,
createdFiles,
updatedFiles
};
};
const createSearchTerraformWorkflowCommand = (mode, terraformArtifact, options = {}) => {
const targetArgs = (options.targets ?? []).map(target => `-target=${target}`);
const baseArgs = [createTerraformStringVariableArgument(SEARCH_TERRAFORM_INPUT_VARIABLE, terraformArtifact.filePath), ...targetArgs, '-input=false'];
if (mode === 'plan') {
return ['plan', ...baseArgs];
}
return [mode, '-auto-approve', ...baseArgs];
};
const resolveSearchTerraformApplyTargets = (targets, importResults, options = {}) => {
if (!Array.isArray(targets)) {
return undefined;
}
if (options.applyTargetsMode !== 'missing') {
return targets;
}
const importStatusByAddress = new Map((importResults ?? []).map(result => [result.address, result.status]));
return targets.filter(target => {
const status = importStatusByAddress.get(target);
return status === undefined || status === 'missing' || status === 'unknown';
});
};
const createSearchTerraformImportCommand = (terraformArtifact, target) => ['import', createTerraformStringVariableArgument(SEARCH_TERRAFORM_INPUT_VARIABLE, terraformArtifact.filePath), '-input=false', target.address, target.id];
const createSearchTerraformStateShowCommand = address => ['state', 'show', address];
const createSearchTerraformStateListCommand = () => ['state', 'list'];
const parseSearchTerraformStateAddresses = stateListOutput => String(stateListOutput ?? '').split(/\r?\n/u).map(line => line.trim()).filter(Boolean);
const parseSearchTerraformBucketLocation = stateShowOutput => {
const locationMatch = String(stateShowOutput ?? '').match(/^location\s*=\s*"([^"]+)"$/mu);
return locationMatch?.[1] ?? null;
};
const parseSearchTerraformBucketName = stateShowOutput => {
const nameMatch = String(stateShowOutput ?? '').match(/^name\s*=\s*"([^"]+)"$/mu);
return nameMatch?.[1] ?? null;
};
const mergeSearchTerraformArtifactBuckets = (configuredBuckets, existingBuckets) => {
const bucketMap = new Map();
for (const bucket of configuredBuckets ?? []) {
if (typeof bucket?.bucket_name !== 'string' || bucket.bucket_name.length === 0) {
continue;
}
bucketMap.set(bucket.bucket_name, bucket);
}
for (const bucket of existingBuckets) {
if (!bucketMap.has(bucket.bucket_name)) {
bucketMap.set(bucket.bucket_name, bucket);
}
}
return [...bucketMap.values()].sort((left, right) => left.bucket_name.localeCompare(right.bucket_name));
};
const resolveSearchTerraformCreateOnlyArtifactBuckets = async (workflowSummary, runTerraformCommandImpl) => {
let stateAddressesOutput = '';
try {
stateAddressesOutput = (await runTerraformCommandImpl(createSearchTerraformStateListCommand(), {
captureOutput: true,
cwd: workflowSummary.rootPath
})) ?? '';
} catch (error) {
if (SEARCH_TERRAFORM_STATE_NOT_FOUND_PATTERN.test(error.message)) {
return [];
}
throw error;
}
const createOnlyBuckets = [];
for (const stateAddress of parseSearchTerraformStateAddresses(stateAddressesOutput)) {
const bucketName = stateAddress.match(SEARCH_TERRAFORM_ARTIFACT_BUCKET_STATE_ADDRESS_PATTERN)?.[1];
if (!bucketName) {
continue;
}
const stateShowOutput = await runTerraformCommandImpl(createSearchTerraformStateShowCommand(stateAddress), {
captureOutput: true,
cwd: workflowSummary.rootPath
});
createOnlyBuckets.push({
bucket_name: bucketName,
location: parseSearchTerraformBucketLocation(stateShowOutput),
uniform_bucket_level_access: true
});
}
return createOnlyBuckets;
};
const resolveSearchTerraformCreateOnlyDlqBucket = async (workflowSummary, runTerraformCommandImpl) => {
try {
const stateShowOutput = await runTerraformCommandImpl(createSearchTerraformStateShowCommand(SEARCH_TERRAFORM_DLQ_BUCKET_STATE_ADDRESS), {
captureOutput: true,
cwd: workflowSummary.rootPath
});
const bucketName = parseSearchTerraformBucketName(stateShowOutput);
if (!bucketName) {
return null;
}
return {
bucket_name: bucketName,
location: parseSearchTerraformBucketLocation(stateShowOutput),
uniform_bucket_level_access: true
};
} catch (error) {
if (SEARCH_TERRAFORM_STATE_NOT_FOUND_PATTERN.test(error.message)) {
return null;
}
throw error;
}
};
const preserveSearchTerraformCreateOnlyBuckets = async (terraformArtifact, workflowSummary, dependencies = {}) => {
const runTerraformCommandImpl = dependencies.runTerraformCommand ?? runTerraformCommand;
const writeJsonFileImpl = dependencies.writeJsonFile ?? writeJsonFile;
const payload = terraformArtifact?.payload;
if (!payload || typeof payload !== 'object') {
return terraformArtifact;
}
const existingArtifactBuckets = await resolveSearchTerraformCreateOnlyArtifactBuckets(workflowSummary, runTerraformCommandImpl);
const existingDlqBucket = await resolveSearchTerraformCreateOnlyDlqBucket(workflowSummary, runTerraformCommandImpl);
const mergedArtifactBuckets = mergeSearchTerraformArtifactBuckets(payload.artifact_buckets, existingArtifactBuckets);
const shouldPreserveDlqBucket = existingDlqBucket !== null && payload.dlq_bucket?.bucket_name !== existingDlqBucket.bucket_name;
if (JSON.stringify(mergedArtifactBuckets) === JSON.stringify(payload.artifact_buckets ?? []) && !shouldPreserveDlqBucket) {
return terraformArtifact;
}
const nextPayload = {
...payload,
artifact_buckets: mergedArtifactBuckets,
...(() => {
if (!shouldPreserveDlqBucket) {
return {};
}
return {
dlq_bucket: {
...(payload.dlq_bucket ?? {}),
bucket_name: existingDlqBucket.bucket_name,
location: existingDlqBucket.location ?? payload.dlq_bucket?.location
},
...spreadIf(payload.dlq_bucket_access, {
dlq_bucket_access: {
...payload.dlq_bucket_access,
bucket_name: existingDlqBucket.bucket_name
}
})
};
})()
};
writeJsonFileImpl(terraformArtifact.filePath, nextPayload);
return {
...terraformArtifact,
payload: nextPayload
};
};
const doesSecretAccessorBindingExist = (secretName, projectId, role, member, runGcloudFileCommandImpl = runGcloudFileCommand) => {
const policy = readGcloudIamPolicy(['secrets', 'get-iam-policy', secretName, `--project=${projectId}`, '--format=json'], runGcloudFileCommandImpl);
return hasIamBinding(policy, role, member);
};
const createSearchTerraformImportResult = (target, status) => ({
address: target.address,
kind: target.kind,
name: target.name,
status
});
const getSearchTerraformImportTargets = terraformArtifact => {
const payload = terraformArtifact?.payload;
if (!payload || typeof payload !== 'object') {
return [];
}
const projectId = payload.project_id;
const backfillJob = payload.backfill_job;
const firestoreEventarc = payload.firestore_eventarc;
const runtimeSecretAccess = payload.runtime_secret_access;
const sourceRunnerJob = payload.source_runner_job;
const sourceScheduler = payload.source_scheduler;
const syncTaskQueue = payload.sync_task_queue;
const eventarcTriggerRegion = firestoreEventarc?.trigger_region ?? firestoreEventarc?.region;
const targets = [];
if (typeof projectId === 'string' && typeof runtimeSecretAccess?.service_account_email === 'string' && typeof runtimeSecretAccess?.role === 'string' && Array.isArray(runtimeSecretAccess?.secret_names)) {
for (const secretName of runtimeSecretAccess.secret_names) {
if (typeof secretName !== 'string') {
continue;
}
const member = `serviceAccount:${runtimeSecretAccess.service_account_email}`;
targets.push({
address: `google_secret_manager_secret_iam_member.atlas_search_runtime["${secretName}"]`,
describeArgs: ['secrets', 'get-iam-policy', secretName, `--project=${projectId}`, '--format=json'],
existsImpl: runGcloudFileCommandImpl => doesSecretAccessorBindingExist(secretName, projectId, runtimeSecretAccess.role, member, runGcloudFileCommandImpl),
id: `projects/${projectId}/secrets/${secretName} ${runtimeSecretAccess.role} ${member}`,
kind: 'secret-access',
name: secretName
});
}
}
if (typeof projectId === 'string' && typeof backfillJob?.job_name === 'string' && typeof backfillJob?.region === 'string') {
targets.push({
address: 'module.atlas_search_backfill_job[0].google_cloud_run_v2_job.this',
describeArgs: ['run', 'jobs', 'describe', backfillJob.job_name, `--project=${projectId}`, `--region=${backfillJob.region}`, '--format="value(name)"'],
id: `projects/${projectId}/locations/${backfillJob.region}/jobs/${backfillJob.job_name}`,
kind: 'job',
name: backfillJob.job_name
});
}
if (typeof projectId === 'string' && typeof sourceRunnerJob?.job_name === 'string' && typeof sourceRunnerJob?.region === 'string') {
targets.push({
address: 'module.atlas_search_source_runner_job[0].google_cloud_run_v2_job.this',
describeArgs: ['run', 'jobs', 'describe', sourceRunnerJob.job_name, `--project=${projectId}`, `--region=${sourceRunnerJob.region}`, '--format="value(name)"'],
id: `projects/${projectId}/locations/${sourceRunnerJob.region}/jobs/${sourceRunnerJob.job_name}`,
kind: 'job',
name: sourceRunnerJob.job_name
});
}
if (typeof projectId === 'string' && typeof syncTaskQueue?.name === 'string' && typeof syncTaskQueue?.location === 'string') {
targets.push({
address: 'google_cloud_tasks_queue.atlas_search_sync[0]',
describeArgs: ['tasks', 'queues', 'describe', syncTaskQueue.name, `--location=${syncTaskQueue.location}`, `--project=${projectId}`, '--format="value(name)"'],
id: `projects/${projectId}/locations/${syncTaskQueue.location}/queues/${syncTaskQueue.name}`,
kind: 'task-queue',
name: syncTaskQueue.name
});
}
if (typeof projectId === 'string' && typeof firestoreEventarc?.trigger_service_account?.account_id === 'string') {
const serviceAccountEmail = `${firestoreEventarc.trigger_service_account.account_id}@${projectId}.iam.gserviceaccount.com`;
targets.push({
address: 'google_service_account.atlas_search_eventarc[0]',
describeArgs: ['iam', 'service-accounts', 'describe', serviceAccountEmail, `--project=${projectId}`, '--format="value(email)"'],
id: `projects/${projectId}/serviceAccounts/${serviceAccountEmail}`,
kind: 'service-account',
name: serviceAccountEmail
});
}
if (typeof projectId === 'string' && typeof sourceScheduler?.region === 'string' && Array.isArray(sourceScheduler.jobs)) {
for (const schedulerJob of sourceScheduler.jobs) {
if (typeof schedulerJob?.name !== 'string') {
continue;
}
targets.push({
address: `google_cloud_scheduler_job.atlas_search_source_runner["${schedulerJob.name}"]`,
describeArgs: ['scheduler', 'jobs', 'describe', schedulerJob.name, `--location=${sourceScheduler.region}`, `--project=${projectId}`, '--format="value(name)"'],
id: `projects/${projectId}/locations/${sourceScheduler.region}/jobs/${schedulerJob.name}`,
kind: 'scheduler-job',
name: schedulerJob.name
});
}
}
if (typeof projectId === 'string' && typeof eventarcTriggerRegion === 'string' && Array.isArray(firestoreEventarc.collections)) {
for (const collection of firestoreEventarc.collections) {
if (typeof collection?.collection_name !== 'string' || typeof collection?.trigger_name !== 'string') {
continue;
}
targets.push({
address: `google_eventarc_trigger.atlas_search_firestore["${collection.collection_name}"]`,
describeArgs: ['eventarc', 'triggers', 'describe', collection.trigger_name, `--project=${projectId}`, `--location=${eventarcTriggerRegion}`, '--format="value(name)"'],
id: `projects/${projectId}/locations/${eventarcTriggerRegion}/triggers/${collection.trigger_name}`,
kind: 'eventarc-trigger',
name: collection.trigger_name
});
}
}
return targets;
};
const normalizeCommandErrorMessage = error => {
const stderr = error?.stderr && Buffer.isBuffer(error.stderr) ? error.stderr.toString('utf8') : typeof error?.stderr === 'string' ? error.stderr : '';
return [error?.message, stderr].filter(Boolean).join('\n');
};
const isGcloudPermissionDeniedError = error => GCLOUD_PERMISSION_DENIED_ERROR_PATTERN.test(normalizeCommandErrorMessage(error));
const doesSearchTerraformImportTargetExist = (target, runGcloudFileCommandImpl = runGcloudFileCommand) => {
try {
if (typeof target.existsImpl === 'function') {
return target.existsImpl(runGcloudFileCommandImpl) ? 'exists' : 'missing';
}
runGcloudFileCommandImpl(target.describeArgs, {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
});
return 'exists';
} catch (error) {
const message = normalizeCommandErrorMessage(error);
if (isGcloudResourceNotFoundError(error)) {
return 'missing';
}
if (isGcloudPermissionDeniedError(error)) {
return 'unknown';
}
throw new Error(`Could not inspect existing Atlas search ${target.kind} ${target.name}. ${message}`);
}
};
export const importExistingSearchTerraformResources = async (terraformArtifact, workflowSummary, dependencies = {}) => {
const importResults = [];
const runTerraformCommandImpl = dependencies.runTerraformCommand ?? runTerraformCommand;
const runGcloudFileCommandImpl = dependencies.runGcloudFileCommand ?? runGcloudFileCommand;
const selectedImportTargets = Array.isArray(dependencies.importTargetAddresses) ? new Set(dependencies.importTargetAddresses) : null;
for (const target of getSearchTerraformImportTargets(terraformArtifact).filter(importTarget => !selectedImportTargets || selectedImportTargets.has(importTarget.address))) {
const existenceStatus = doesSearchTerraformImportTargetExist(target, runGcloudFileCommandImpl);
if (existenceStatus === 'missing') {
importResults.push(createSearchTerraformImportResult(target, 'missing'));
continue;
}
if (existenceStatus === 'unknown') {
importResults.push(createSearchTerraformImportResult(target, 'unknown'));
continue;
}
try {
await runTerraformCommandImpl(createSearchTerraformStateShowCommand(target.address), {
captureOutput: true,
cwd: workflowSummary.rootPath
});
importResults.push(createSearchTerraformImportResult(target, 'already-managed'));
continue;
} catch (error) {
if (!SEARCH_TERRAFORM_STATE_NOT_FOUND_PATTERN.test(error.message)) {
throw error;
}
}
try {
await runTerraformCommandImpl(createSearchTerraformImportCommand(terraformArtifact, target), {
captureOutput: true,
cwd: workflowSummary.rootPath
});
importResults.push(createSearchTerraformImportResult(target, 'imported'));
} catch (error) {
if (SEARCH_TERRAFORM_IMPORT_ALREADY_MANAGED_PATTERN.test(error.message)) {
importResults.push(createSearchTerraformImportResult(target, 'already-managed'));
continue;
}
throw error;
}
}
return importResults;
};
export const getSearchTerraformPrerequisiteTargets = terraformArtifact => getSearchTerraformImportTargets(terraformArtifact).filter(target => SEARCH_TERRAFORM_PREREQUISITE_TARGET_KINDS.has(target.kind)).map(target => target.address);
export const runSearchTerraformWorkflow = async (searchConfig, terraformArtifact, options = {}, dependencies = {}, cwd = process.cwd()) => {
const ensureTerraformRoot = dependencies.ensureSearchTerraformRoot ?? ensureSearchTerraformRoot;
const ensureTerraformWorkspaceImpl = dependencies.ensureTerraformWorkspace ?? ensureTerraformWorkspace;
const runTerraformCommandImpl = dependencies.runTerraformCommand ?? runTerraformCommand;
const workflowSummary = await ensureTerraformRoot(searchConfig, cwd, dependencies);
const mode = options.mode ?? (options.dryRun ? 'plan' : 'apply');
const initArgs = ['init', '-upgrade', '-input=false'];
const importResults = [];
await runTerraformCommandImpl(initArgs, {
cwd: workflowSummary.rootPath,
stdio: 'inherit'
});
const workspaceSelection = await ensureTerraformWorkspaceImpl(resolveSearchTerraformWorkspaceName(terraformArtifact), {
cwd: workflowSummary.rootPath
}, {
runTerraformCommand: runTerraformCommandImpl
});
const effectiveTerraformArtifact = mode === 'destroy' ? terraformArtifact : await preserveSearchTerraformCreateOnlyBuckets(terraformArtifact, workflowSummary, {
runTerraformCommand: runTerraformCommandImpl,
writeJsonFile: dependencies.writeJsonFile
});
if (mode !== 'plan') {
importResults.push(...(await importExistingSearchTerraformResources(effectiveTerraformArtifact, workflowSummary, {
importTargetAddresses: options.targets,
runGcloudFileCommand: dependencies.runGcloudFileCommand,
runTerraformCommand: runTerraformCommandImpl
})));
}
const applyTargets = resolveSearchTerraformApplyTargets(options.targets, importResults, options);
if (mode !== 'plan' && Array.isArray(options.targets) && applyTargets.length === 0) {
return {
...workflowSummary,
commands: [initArgs, workspaceSelection.command],
importResults,
mode,
workspaceName: workspaceSelection.name,
skippedApply: true
};
}
const workflowArgs = createSearchTerraformWorkflowCommand(mode, effectiveTerraformArtifact, {
targets: applyTargets
});
await runTerraformCommandImpl(workflowArgs, {
cwd: workflowSummary.rootPath,
stdio: 'inherit'
});
return {
...workflowSummary,
commands: [initArgs, workspaceSelection.command, workflowArgs],
importResults,
mode,
workspaceName: workspaceSelection.name
};
};