@puls-atlas/cli
Version:
The Puls Atlas CLI tool for managing Atlas projects
1,015 lines (1,014 loc) • 77.3 kB
JavaScript
import { randomBytes } from 'node:crypto';
import fs from 'fs';
import path from 'path';
import inquirer from 'inquirer';
import { isEqual } from 'es-toolkit';
import { isPlainObject } from 'es-toolkit/compat';
import { omit } from 'es-toolkit/object';
import * as features from '../../utils/feature.js';
import { readJsonFile, writeJsonFile } from '../../utils/file.js';
import { upsertSecret } from '../../utils/secrets.js';
import { logger } from '../../utils/logger.js';
import { getAtlasGeneratedTerraformDir, resolveRootPath } from '../../utils/atlas.js';
import { getSearchProvider } from './providers/index.js';
import { resolveSearchCloudRunDeployConfig } from './planning.js';
import { dedupeMessages, normalizeOptionalString, spreadIf } from '../../utils/value.js';
import { isGcloudResourceNotFoundError, parseGcloudJsonOutput, runGcloudFileCommand } from '../../utils/gcloud.js';
import { ensureTerraformWorkspace, createTerraformStringVariableArgument, runTerraformCommand } from '../../utils/terraform.js';
import { loadSearchProviderGkeTerraformRootTemplateFiles, loadSearchProviderPlatformTerraformRootTemplateFiles, loadSearchProviderTerraformRootTemplateFiles, resolveSiblingTerraformModuleSource, renderSearchTerraformRootTemplateFiles } from './terraformRootTemplates.js';
import { DEFAULT_SEARCH_TERRAFORM_MODULE_RELEASE_TAG, DEFAULT_SEARCH_TERRAFORM_ROOT_DIR, resolveSearchCloudRunServiceAccountEmail } from './config/searchConfig.js';
const TERRAFORM_HEARTBEAT_INTERVAL_MS = 30_000;
const DEFAULT_CLOUD_RUN_PRIVATE_EGRESS = 'private-ranges-only';
const IAP_TCP_FORWARDING_SOURCE_RANGE = '35.235.240.0/20';
const DEFAULT_PRIVATE_INGRESS_SOURCE_RANGES = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', IAP_TCP_FORWARDING_SOURCE_RANGE];
const PROVIDER_TERRAFORM_IMPORT_ALREADY_MANAGED_PATTERN = /already managed by Terraform/iu;
const PROVIDER_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 resolveSearchProviderTerraformWorkspaceName = terraformArtifact => {
const workspaceName = normalizeOptionalString(terraformArtifact?.payload?.project_id);
if (workspaceName) {
return workspaceName;
}
throw new Error('Atlas search provider Terraform input must define payload.project_id before Terraform can select a workspace.');
};
export const SEARCH_PROVIDER_TERRAFORM_CONTRACT_VERSION = 1;
export const SEARCH_PROVIDER_TERRAFORM_INPUT_VARIABLE = 'atlas_search_provider_tfvars_path';
export const SEARCH_PROVIDER_PLATFORM_TERRAFORM_CONTRACT_VERSION = 1;
export const SEARCH_PROVIDER_PLATFORM_TERRAFORM_INPUT_VARIABLE = 'atlas_search_provider_platform_tfvars_path';
export const DEFAULT_SEARCH_PROVIDER_TERRAFORM_ROOT_DIR = 'services/search/terraform/provider';
export const DEFAULT_SEARCH_PROVIDER_PLATFORM_TERRAFORM_ROOT_DIR = 'services/search/terraform/provider-platform';
export const DEFAULT_SEARCH_PROVIDER_TERRAFORM_MODULE_SOURCE = `git::https://github.com/limebooth/atlas-terraform-modules.git//search?ref=${DEFAULT_SEARCH_TERRAFORM_MODULE_RELEASE_TAG}`;
const DEFAULT_SEARCH_PROVIDER_KUBECONFIG_ROOT_DIR = path.join('.atlas', 'kubeconfig');
const EMPTY_KUBECONFIG_CONTENT = `apiVersion: v1
kind: Config
preferences: {}
clusters: []
contexts: []
current-context: ""
users: []
`;
const PROVIDER_PLATFORM_CHOICES = [{
name: 'Compute Engine instance',
value: 'compute'
}, {
name: 'GKE workload',
value: 'gke'
}];
const GKE_CLUSTER_STRATEGY_CHOICES = [{
name: 'Use an existing GKE cluster',
value: 'existing'
}, {
name: 'Create a managed GKE cluster first',
value: 'managed'
}];
const COMPUTE_CONFIG_FIELD_MAPPINGS = [['machineType', 'machine_type'], ['bootDiskSizeGb', 'boot_disk_size_gb'], ['bootDiskImage', 'boot_disk_image'], ['dataDiskSizeGb', 'data_disk_size_gb'], ['dataDiskType', 'data_disk_type'], ['dataMountPath', 'data_mount_path'], ['assignPublicIp', 'assign_public_ip'], ['createNatGateway', 'create_nat_gateway'], ['createFirewallRule', 'create_firewall_rule'], ['ingressSourceRanges', 'ingress_source_ranges'], ['tags', 'tags'], ['metadata', 'metadata']];
const GKE_CONFIG_FIELD_MAPPINGS = [['namespace', 'namespace'], ['createNamespace', 'create_namespace'], ['replicas', 'replicas'], ['serviceType', 'service_type'], ['storageClassName', 'storage_class_name'], ['storageSize', 'storage_size'], ['nodeSelector', 'node_selector'], ['annotations', 'annotations'], ['resourceRequests', 'resource_requests'], ['resourceLimits', 'resource_limits']];
const normalizeKubeconfigPathSegment = value => String(value).trim().replace(/[^A-Za-z0-9._-]+/gu, '-').replace(/-{2,}/gu, '-').replace(/^-+|-+$/gu, '');
const createKubernetesHost = clusterEndpoint => clusterEndpoint.startsWith('http') ? clusterEndpoint : `https://${clusterEndpoint}`;
const resolveSearchProviderClusterTarget = (providerRuntimeConfig, platformOutputs = {}) => ({
clusterLocation: normalizeOptionalString(platformOutputs.cluster_location) ?? normalizeOptionalString(providerRuntimeConfig.gke?.cluster?.location),
clusterName: normalizeOptionalString(platformOutputs.cluster_name) ?? normalizeOptionalString(providerRuntimeConfig.gke?.cluster?.name)
});
const resolveSearchProviderKubeconfigPath = (context, clusterTarget, cwd = process.cwd(), options = {}) => {
const configuredPath = normalizeOptionalString(options.kubeconfigPath);
if (configuredPath) {
return resolveRootPath(configuredPath, cwd);
}
const fileName = [context.projectId, clusterTarget.clusterLocation, clusterTarget.clusterName].map(normalizeKubeconfigPathSegment).filter(Boolean).join('.');
return resolveRootPath(path.join(DEFAULT_SEARCH_PROVIDER_KUBECONFIG_ROOT_DIR, `${fileName || 'search-provider'}.yaml`), cwd);
};
const ensureSearchProviderKubeconfigFile = (kubeconfigPath, dependencies = {}) => {
if (!kubeconfigPath) {
return null;
}
const {
existsSyncImpl = fs.existsSync,
mkdirSyncImpl = fs.mkdirSync,
writeFileSyncImpl = fs.writeFileSync
} = dependencies;
mkdirSyncImpl(path.dirname(kubeconfigPath), {
recursive: true
});
if (!existsSyncImpl(kubeconfigPath)) {
writeFileSyncImpl(kubeconfigPath, EMPTY_KUBECONFIG_CONTENT);
}
return kubeconfigPath;
};
const formatDuration = elapsedMs => `${Math.max(1, Math.round(elapsedMs / 1000))}s`;
const describeTerraformStep = args => `terraform ${args[0]}`;
const getTerraformStepGuardMessage = (args, providerRuntimeConfig) => {
switch (args[0]) {
case 'init':
return 'Terraform init may still be downloading modules or providers. ' + 'First runs and restricted networks can make this step noticeably slower.';
case 'apply':
case 'plan':
case 'destroy':
if (providerRuntimeConfig.platform === 'compute') {
return 'Compute Engine provisioning can take several minutes while Google Cloud creates the VM, ' + 'persistent disk and firewall, then boots the instance.';
}
if (providerRuntimeConfig.platform === 'gke') {
return 'GKE-backed provider changes can take several minutes while Terraform waits on cluster-side resources ' + 'and Kubernetes provider calls.';
}
return 'Terraform is still applying provider infrastructure changes.';
case 'output':
return 'Terraform is still reading provider outputs from the local state.';
default:
return 'Terraform is still running.';
}
};
const createTerraformLifecycleHooks = (loggerImpl, providerRuntimeConfig) => ({
onHeartbeat: ({
args,
elapsedMs
}) => {
loggerImpl.warning(`${describeTerraformStep(args)} is still running after ${formatDuration(elapsedMs)}. ` + `${getTerraformStepGuardMessage(args, providerRuntimeConfig)}`);
}
});
const createSearchProviderTerraformContext = (context, providerRuntimeConfig) => ({
...context.config,
deploy: {
...(context.config.deploy ?? {}),
providerRuntime: providerRuntimeConfig
}
});
const resolveProviderTerraformLifecycleHooks = (dependencies, providerRuntimeConfig) => dependencies.terraformLifecycleHooks ?? createTerraformLifecycleHooks(dependencies.logger ?? logger, providerRuntimeConfig);
const runTerraformStepWithLogging = async (args, workflowSummary, runTerraformCommandImpl, terraformLifecycleHooks, options = {}) => {
const {
logger: loggerOverride,
logCompletion = true,
logStart = true,
...terraformCommandOptions
} = options;
const loggerImpl = loggerOverride ?? logger;
const startedAt = Date.now();
if (logStart) {
loggerImpl.info(`Starting ${describeTerraformStep(args)} in ${workflowSummary.rootPath}${terraformCommandOptions.captureOutput ? ' (capturing output)' : ''}.`);
}
const result = await runTerraformCommandImpl(args, {
cwd: workflowSummary.rootPath,
heartbeatIntervalMs: TERRAFORM_HEARTBEAT_INTERVAL_MS,
...terraformLifecycleHooks,
...terraformCommandOptions
});
if (logCompletion) {
loggerImpl.info(`${describeTerraformStep(args)} completed in ${formatDuration(Date.now() - startedAt)}.`);
}
return result;
};
const normalizePlatform = platform => {
const normalized = normalizeOptionalString(platform)?.toLowerCase();
if (!normalized) {
return null;
}
if (!['compute', 'gke'].includes(normalized)) {
throw new Error('Atlas search provider deploy supports only --platform compute or --platform gke.');
}
return normalized;
};
const getProviderRuntimeConfig = searchConfig => searchConfig?.deploy?.providerRuntime ?? {};
const normalizeStringArray = value => {
if (!Array.isArray(value)) {
return [];
}
return value.map(entry => normalizeOptionalString(entry)).filter(Boolean);
};
const ensureIapIngressSourceRange = ingressSourceRanges => {
const normalizedIngressSourceRanges = normalizeStringArray(ingressSourceRanges);
if (normalizedIngressSourceRanges.includes(IAP_TCP_FORWARDING_SOURCE_RANGE)) {
return normalizedIngressSourceRanges;
}
return [...normalizedIngressSourceRanges, IAP_TCP_FORWARDING_SOURCE_RANGE];
};
const hasOwnProperty = (object, propertyName) => Object.prototype.hasOwnProperty.call(object, propertyName);
const assignMappedProperties = (source, fieldMappings) => {
const mappedProperties = {};
for (const [sourceKey, targetKey] of fieldMappings) {
if (!hasOwnProperty(source, sourceKey)) {
continue;
}
const value = source[sourceKey];
if (value === undefined) {
continue;
}
mappedProperties[targetKey] = value;
}
return mappedProperties;
};
const createDefaultPrivateComputeConfig = currentConfig => ({
assignPublicIp: currentConfig.compute?.assignPublicIp ?? false,
createNatGateway: currentConfig.compute?.createNatGateway ?? true,
createFirewallRule: currentConfig.compute?.createFirewallRule ?? true,
ingressSourceRanges: (() => {
const ingressSourceRanges = normalizeStringArray(currentConfig.compute?.ingressSourceRanges);
return ingressSourceRanges.length > 0 ? ensureIapIngressSourceRange(ingressSourceRanges) : [...DEFAULT_PRIVATE_INGRESS_SOURCE_RANGES];
})()
});
const createDefaultGkeClusterName = providerRuntimeConfig => `${providerRuntimeConfig.namePrefix ?? 'atlas-search'}-gke`;
const createDefaultGkeNodePoolName = providerRuntimeConfig => `${providerRuntimeConfig.namePrefix ?? 'atlas-search'}-workload`;
const createDefaultGkeNodePoolMachineType = providerName => providerName === 'elasticsearch' ? 'e2-standard-4' : 'e2-standard-2';
const createDefaultGkeClusterConfig = (context, providerRuntimeConfig = {}, currentClusterConfig = {}) => ({
create: currentClusterConfig.create ?? false,
deletionProtection: currentClusterConfig.deletionProtection ?? false,
location: currentClusterConfig.location ?? resolveSearchCloudRunDeployConfig(context).region,
name: currentClusterConfig.name ?? createDefaultGkeClusterName(providerRuntimeConfig),
kubernetesVersion: currentClusterConfig.kubernetesVersion ?? null,
privateCluster: currentClusterConfig.privateCluster ?? false,
releaseChannel: currentClusterConfig.releaseChannel ?? 'REGULAR'
});
const createDefaultGkeNodePoolConfig = (providerName, providerRuntimeConfig = {}, currentNodePoolConfig = {}) => ({
diskSizeGb: currentNodePoolConfig.diskSizeGb ?? 100,
diskType: currentNodePoolConfig.diskType ?? 'pd-balanced',
machineType: currentNodePoolConfig.machineType ?? createDefaultGkeNodePoolMachineType(providerName),
maxNodeCount: currentNodePoolConfig.maxNodeCount ?? 3,
minNodeCount: currentNodePoolConfig.minNodeCount ?? 1,
name: currentNodePoolConfig.name ?? createDefaultGkeNodePoolName(providerRuntimeConfig),
nodeLabels: currentNodePoolConfig.nodeLabels ?? {},
nodeTaints: currentNodePoolConfig.nodeTaints ?? [],
serviceAccountEmail: currentNodePoolConfig.serviceAccountEmail ?? null,
spot: currentNodePoolConfig.spot ?? false
});
const isManagedGkeCluster = providerRuntimeConfig => providerRuntimeConfig.platform === 'gke' && providerRuntimeConfig.gke?.cluster?.create === true;
const normalizeTerraformRootDir = rootDir => (normalizeOptionalString(rootDir) ?? DEFAULT_SEARCH_TERRAFORM_ROOT_DIR).replaceAll('\\', '/').replace(/\/+$/u, '');
const getDefaultSearchProviderTerraformRootDir = searchConfig => `${normalizeTerraformRootDir(searchConfig?.deploy?.terraform?.rootDir)}/provider`;
const getDefaultSearchProviderPlatformTerraformRootDir = searchConfig => `${normalizeTerraformRootDir(searchConfig?.deploy?.terraform?.rootDir)}/provider-platform`;
const createManagedCloudRunVpcAccessConfig = providerRuntimeConfig => {
if (providerRuntimeConfig.platform !== 'compute' || providerRuntimeConfig.compute?.assignPublicIp === true) {
return null;
}
return {
egress: DEFAULT_CLOUD_RUN_PRIVATE_EGRESS,
network: providerRuntimeConfig.network ?? 'default',
subnetwork: providerRuntimeConfig.subnetwork ?? null
};
};
export const resolveSearchProviderTerraformConfig = searchConfig => {
const providerRuntimeConfig = getProviderRuntimeConfig(searchConfig);
return {
moduleSource: providerRuntimeConfig.moduleSource ?? DEFAULT_SEARCH_PROVIDER_TERRAFORM_MODULE_SOURCE,
rootDir: providerRuntimeConfig.rootDir ?? getDefaultSearchProviderTerraformRootDir(searchConfig)
};
};
export const resolveSearchProviderTerraformRootPath = (searchConfig, cwd = process.cwd()) => resolveRootPath(resolveSearchProviderTerraformConfig(searchConfig).rootDir, cwd);
export const createSearchProviderTerraformWorkflowSummary = (context, providerRuntimeConfig, cwd = process.cwd()) => {
const terraformConfig = resolveSearchProviderTerraformConfig({
deploy: {
providerRuntime: providerRuntimeConfig
}
});
return {
managedResources: [{
description: `Self-hosted ${context.config.provider} runtime managed through the Atlas search provider Terraform workflow.`,
kind: providerRuntimeConfig.platform ?? 'provider-runtime',
name: `${providerRuntimeConfig.namePrefix ?? 'atlas-search'}-${context.config.provider}`
}],
moduleSource: terraformConfig.moduleSource,
rootDir: terraformConfig.rootDir,
rootPath: resolveRootPath(terraformConfig.rootDir, cwd)
};
};
export const ensureSearchProviderTerraformRoot = async (searchConfig, cwd = process.cwd(), options = {}) => {
const {
existsSyncImpl = fs.existsSync,
mkdirSyncImpl = fs.mkdirSync,
readFileSyncImpl = fs.readFileSync,
templateDirectory,
writeFileSyncImpl = fs.writeFileSync
} = options;
const providerRuntimeConfig = getProviderRuntimeConfig(searchConfig);
const workflowSummary = createSearchProviderTerraformWorkflowSummary({
config: {
provider: searchConfig.provider
}
}, providerRuntimeConfig, cwd);
const loadTemplateFiles = providerRuntimeConfig.platform === 'gke' ? loadSearchProviderGkeTerraformRootTemplateFiles : loadSearchProviderTerraformRootTemplateFiles;
const resolvedTemplateDirectory = providerRuntimeConfig.platform === 'gke' ? options.gkeTemplateDirectory ?? templateDirectory : templateDirectory;
const templateFiles = await loadTemplateFiles(workflowSummary.moduleSource, cwd, {
...options,
templateDirectory: resolvedTemplateDirectory
});
const rootFiles = renderSearchTerraformRootTemplateFiles(templateFiles, {
__ATLAS_SEARCH_PROVIDER_TERRAFORM_CONTRACT_VERSION__: SEARCH_PROVIDER_TERRAFORM_CONTRACT_VERSION,
__ATLAS_SEARCH_PROVIDER_TERRAFORM_INPUT_VARIABLE__: SEARCH_PROVIDER_TERRAFORM_INPUT_VARIABLE,
__ATLAS_SEARCH_PROVIDER_TERRAFORM_MODULE_SOURCE__: workflowSummary.moduleSource
});
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 createRandomHex = randomBytesImpl => randomBytesImpl(24).toString('hex');
const createRandomPassword = randomBytesImpl => randomBytesImpl(24).toString('base64url');
const createSearchProviderCredentialSeed = (providerName, providerAccess, randomBytesImpl) => {
if (providerName === 'typesense') {
return {
adminApiKey: providerAccess.adminApiKey ?? createRandomHex(randomBytesImpl),
host: providerAccess.host ?? null
};
}
return {
apiKey: null,
node: providerAccess.node ?? null,
password: providerAccess.password ?? createRandomPassword(randomBytesImpl),
username: providerAccess.username ?? 'elastic'
};
};
const createProviderRuntimeConfigUpdate = (currentConfig, update) => ({
...currentConfig,
...update,
compute: update.compute ? {
...(currentConfig.compute ?? {}),
...update.compute
} : currentConfig.compute,
gke: update.gke ? {
...(currentConfig.gke ?? {}),
...update.gke,
cluster: update.gke.cluster ? {
...(currentConfig.gke?.cluster ?? {}),
...update.gke.cluster
} : currentConfig.gke?.cluster,
nodePool: update.gke.nodePool ? {
...(currentConfig.gke?.nodePool ?? {}),
...update.gke.nodePool
} : currentConfig.gke?.nodePool
} : currentConfig.gke
});
const normalizeCloudRunVpcAccessValue = vpcAccess => {
if (!isPlainObject(vpcAccess)) {
return null;
}
return {
egress: normalizeOptionalString(vpcAccess.egress),
network: normalizeOptionalString(vpcAccess.network),
subnetwork: normalizeOptionalString(vpcAccess.subnetwork)
};
};
const areCloudRunVpcAccessConfigsEqual = (left, right) => isEqual(normalizeCloudRunVpcAccessValue(left), normalizeCloudRunVpcAccessValue(right));
const resolveSearchProviderGkeSelection = async (context, currentConfig, options, dependencies) => {
const promptImpl = dependencies.prompt ?? inquirer.prompt;
const currentGkeConfig = currentConfig.gke ?? {};
const currentClusterConfig = currentGkeConfig.cluster ?? {};
const currentNodePoolConfig = currentGkeConfig.nodePool ?? {};
let clusterCreate = currentClusterConfig.create;
let clusterName = normalizeOptionalString(options.clusterName ?? currentClusterConfig.name);
if (options.createCluster === true) {
clusterCreate = true;
}
if (clusterCreate !== true && clusterCreate !== false) {
const answer = await promptImpl([{
choices: GKE_CLUSTER_STRATEGY_CHOICES,
default: currentClusterConfig.name ? 'existing' : 'managed',
message: 'How should Atlas obtain the GKE cluster for the provider runtime?',
name: 'gkeClusterStrategy',
type: 'select'
}]);
clusterCreate = answer.gkeClusterStrategy === 'managed';
}
const clusterDefaults = createDefaultGkeClusterConfig(context, currentConfig, {
...currentClusterConfig,
create: clusterCreate
});
const nodePoolDefaults = createDefaultGkeNodePoolConfig(context.config.provider, currentConfig, currentNodePoolConfig);
const clusterLocation = normalizeOptionalString(options.clusterLocation ?? currentClusterConfig.location) ?? clusterDefaults.location;
if (clusterCreate && !clusterName) {
const answer = await promptImpl([{
default: clusterDefaults.name,
message: 'Which GKE cluster name should Atlas manage?',
name: 'clusterName',
type: 'input'
}]);
clusterName = normalizeOptionalString(answer.clusterName);
}
return {
gke: {
...currentGkeConfig,
cluster: {
...clusterDefaults,
...(clusterName ? {
name: clusterName
} : {}),
...(clusterLocation ? {
location: clusterLocation
} : {})
},
nodePool: clusterCreate ? {
...nodePoolDefaults
} : currentNodePoolConfig
}
};
};
export const resolveSearchProviderRuntimeSelection = async (context, options = {}, dependencies = {}) => {
const promptImpl = dependencies.prompt ?? inquirer.prompt;
const currentConfig = getProviderRuntimeConfig(context.config);
let platform = normalizePlatform(options.platform ?? currentConfig.platform);
if (!platform) {
const answer = await promptImpl([{
choices: PROVIDER_PLATFORM_CHOICES,
message: `How should Atlas deploy the ${context.config.provider} provider runtime?`,
name: 'platform',
type: 'select'
}]);
platform = normalizePlatform(answer.platform);
}
const selection = {
platform
};
if (platform === 'compute') {
let zone = normalizeOptionalString(options.zone ?? currentConfig.compute?.zone);
if (!zone) {
const answer = await promptImpl([{
default: 'europe-west1-b',
message: 'Which Compute Engine zone should host the search provider runtime?',
name: 'zone',
type: 'input'
}]);
zone = normalizeOptionalString(answer.zone);
}
if (!zone) {
throw new Error('Atlas search provider deploy requires a Compute Engine zone when platform=compute.');
}
selection.compute = {
...createDefaultPrivateComputeConfig(currentConfig),
zone
};
} else if (platform === 'gke') {
Object.assign(selection, await resolveSearchProviderGkeSelection(context, currentConfig, options, {
prompt: promptImpl
}));
}
return createProviderRuntimeConfigUpdate(currentConfig, selection);
};
export const persistSearchProviderRuntimeConfig = (context, providerRuntimeConfig, dependencies = {}) => {
const readJsonFileImpl = dependencies.readJsonFile ?? readJsonFile;
const writeJsonFileImpl = dependencies.writeJsonFile ?? writeJsonFile;
const currentRootConfig = readJsonFileImpl(context.configPath);
const currentResolvedConfig = context.config ?? currentRootConfig;
const currentCloudRunConfig = currentResolvedConfig.deploy?.cloudRun ?? {};
const currentCloudRunVpcAccess = currentCloudRunConfig.vpcAccess;
const previousProviderRuntimeConfig = currentResolvedConfig.deploy?.providerRuntime ?? {};
const managedCloudRunVpcAccess = createManagedCloudRunVpcAccessConfig(providerRuntimeConfig);
const previousManagedCloudRunVpcAccess = createManagedCloudRunVpcAccessConfig(previousProviderRuntimeConfig);
let nextCloudRunConfig = {
...currentCloudRunConfig
};
if (managedCloudRunVpcAccess) {
if (currentCloudRunVpcAccess === undefined || areCloudRunVpcAccessConfigsEqual(currentCloudRunVpcAccess, previousManagedCloudRunVpcAccess) || areCloudRunVpcAccessConfigsEqual(currentCloudRunVpcAccess, managedCloudRunVpcAccess)) {
nextCloudRunConfig.vpcAccess = managedCloudRunVpcAccess;
}
} else if (previousManagedCloudRunVpcAccess && areCloudRunVpcAccessConfigsEqual(currentCloudRunVpcAccess, previousManagedCloudRunVpcAccess)) {
nextCloudRunConfig = omit(nextCloudRunConfig, ['vpcAccess']);
}
let nextRootConfig;
if (currentRootConfig.projects?.[context.projectId]) {
nextRootConfig = {
...currentRootConfig,
projects: {
...(currentRootConfig.projects ?? {}),
[context.projectId]: {
...(currentRootConfig.projects?.[context.projectId] ?? {}),
...(context.environment ? {
environment: context.environment
} : {}),
deploy: {
...(currentRootConfig.projects?.[context.projectId]?.deploy ?? {}),
cloudRun: nextCloudRunConfig,
providerRuntime: providerRuntimeConfig
}
}
}
};
} else {
nextRootConfig = {
...currentRootConfig,
deploy: {
...(currentRootConfig.deploy ?? {}),
cloudRun: nextCloudRunConfig,
providerRuntime: providerRuntimeConfig
}
};
}
if (!isEqual(currentRootConfig, nextRootConfig)) {
writeJsonFileImpl(context.configPath, nextRootConfig);
}
return nextRootConfig;
};
export const resolveSearchProviderPlatformTerraformConfig = searchConfig => {
const providerRuntimeConfig = getProviderRuntimeConfig(searchConfig);
const providerModuleSource = providerRuntimeConfig.moduleSource ?? DEFAULT_SEARCH_PROVIDER_TERRAFORM_MODULE_SOURCE;
return {
clusterModuleSource: resolveSiblingTerraformModuleSource(providerModuleSource, 'platform/gke-cluster'),
nodePoolModuleSource: resolveSiblingTerraformModuleSource(providerModuleSource, 'platform/gke-node-pool'),
rootDir: providerRuntimeConfig.gke?.platformRootDir ?? getDefaultSearchProviderPlatformTerraformRootDir(searchConfig)
};
};
export const resolveSearchProviderPlatformTerraformRootPath = (searchConfig, cwd = process.cwd()) => resolveRootPath(resolveSearchProviderPlatformTerraformConfig(searchConfig).rootDir, cwd);
export const createSearchProviderPlatformTerraformWorkflowSummary = (_context, providerRuntimeConfig, cwd = process.cwd()) => {
const terraformConfig = resolveSearchProviderPlatformTerraformConfig({
deploy: {
providerRuntime: providerRuntimeConfig
}
});
const clusterName = providerRuntimeConfig.gke?.cluster?.name ?? createDefaultGkeClusterName(providerRuntimeConfig);
const nodePoolName = providerRuntimeConfig.gke?.nodePool?.name ?? createDefaultGkeNodePoolName(providerRuntimeConfig);
return {
clusterModuleSource: terraformConfig.clusterModuleSource,
managedResources: [{
description: 'Managed GKE cluster foundation for the Atlas search provider runtime.',
kind: 'gke-cluster',
name: clusterName
}, {
description: 'Managed GKE node pool that hosts the Atlas search provider workload.',
kind: 'gke-node-pool',
name: nodePoolName
}],
nodePoolModuleSource: terraformConfig.nodePoolModuleSource,
rootDir: terraformConfig.rootDir,
rootPath: resolveRootPath(terraformConfig.rootDir, cwd)
};
};
export const ensureSearchProviderPlatformTerraformRoot = async (searchConfig, cwd = process.cwd(), options = {}) => {
const {
existsSyncImpl = fs.existsSync,
mkdirSyncImpl = fs.mkdirSync,
readFileSyncImpl = fs.readFileSync,
templateDirectory,
writeFileSyncImpl = fs.writeFileSync
} = options;
const workflowSummary = createSearchProviderPlatformTerraformWorkflowSummary({
config: {
provider: searchConfig.provider
}
}, getProviderRuntimeConfig(searchConfig), cwd);
const templateFiles = await loadSearchProviderPlatformTerraformRootTemplateFiles(getProviderRuntimeConfig(searchConfig).moduleSource ?? DEFAULT_SEARCH_PROVIDER_TERRAFORM_MODULE_SOURCE, cwd, {
...options,
templateDirectory
});
const rootFiles = renderSearchTerraformRootTemplateFiles(templateFiles, {
__ATLAS_SEARCH_PROVIDER_GKE_CLUSTER_MODULE_SOURCE__: workflowSummary.clusterModuleSource,
__ATLAS_SEARCH_PROVIDER_GKE_NODE_POOL_MODULE_SOURCE__: workflowSummary.nodePoolModuleSource,
__ATLAS_SEARCH_PROVIDER_PLATFORM_TERRAFORM_CONTRACT_VERSION__: SEARCH_PROVIDER_PLATFORM_TERRAFORM_CONTRACT_VERSION,
__ATLAS_SEARCH_PROVIDER_PLATFORM_TERRAFORM_INPUT_VARIABLE__: SEARCH_PROVIDER_PLATFORM_TERRAFORM_INPUT_VARIABLE
});
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 createSearchProviderResourceLabels = (context, platform) => ({
'atlas-component': 'search',
...spreadIf(context.environment, {
'atlas-environment': context.environment
}),
'atlas-managed-by': 'terraform',
'atlas-platform': platform,
'atlas-project': context.projectId,
'atlas-provider': context.config.provider,
'atlas-runtime': 'provider'
});
export const createSearchProviderPlatformTerraformPayload = (context, providerRuntimeConfig) => {
if (!isManagedGkeCluster(providerRuntimeConfig)) {
throw new Error('Atlas search provider platform payload requires platform=gke with gke.cluster.create=true.');
}
const clusterConfig = createDefaultGkeClusterConfig(context, providerRuntimeConfig, providerRuntimeConfig.gke?.cluster ?? {});
const nodePoolConfig = createDefaultGkeNodePoolConfig(context.config.provider, providerRuntimeConfig, providerRuntimeConfig.gke?.nodePool ?? {});
const serviceAccountEmail = providerRuntimeConfig.serviceAccountEmail ?? resolveSearchCloudRunServiceAccountEmail(context.config, context.projectId);
return {
cluster: {
deletion_protection: clusterConfig.deletionProtection,
kubernetes_version: clusterConfig.kubernetesVersion,
location: clusterConfig.location,
name: clusterConfig.name,
private_cluster: clusterConfig.privateCluster,
release_channel: clusterConfig.releaseChannel
},
contract_version: SEARCH_PROVIDER_PLATFORM_TERRAFORM_CONTRACT_VERSION,
environment: context.environment ?? null,
name_prefix: providerRuntimeConfig.namePrefix ?? 'atlas-search',
network: providerRuntimeConfig.network ?? 'default',
node_pool: {
disk_size_gb: nodePoolConfig.diskSizeGb,
disk_type: nodePoolConfig.diskType,
machine_type: nodePoolConfig.machineType,
max_node_count: nodePoolConfig.maxNodeCount,
min_node_count: nodePoolConfig.minNodeCount,
name: nodePoolConfig.name,
node_labels: {
...(nodePoolConfig.nodeLabels ?? {}),
...(providerRuntimeConfig.gke?.nodeSelector ?? {})
},
node_taints: nodePoolConfig.nodeTaints,
service_account_email: nodePoolConfig.serviceAccountEmail ?? serviceAccountEmail,
spot: nodePoolConfig.spot
},
project_id: context.projectId,
provider: context.config.provider,
region: resolveSearchCloudRunDeployConfig(context).region,
resource_labels: createSearchProviderResourceLabels(context, 'gke-platform'),
service_account_email: serviceAccountEmail,
subnetwork: providerRuntimeConfig.subnetwork ?? null
};
};
const mapComputeConfig = computeConfig => {
if (!computeConfig) {
return null;
}
return {
zone: computeConfig.zone,
...assignMappedProperties(computeConfig, COMPUTE_CONFIG_FIELD_MAPPINGS)
};
};
const mapGkeConfig = gkeConfig => {
if (!gkeConfig) {
return null;
}
return assignMappedProperties(gkeConfig, GKE_CONFIG_FIELD_MAPPINGS);
};
const createSearchProviderTerraformConnectionPayload = terraformConnection => {
if (!terraformConnection?.clusterEndpoint || !terraformConnection?.clusterCaCertificate) {
return null;
}
return {
cluster_ca_certificate: terraformConnection.clusterCaCertificate,
host: createKubernetesHost(terraformConnection.clusterEndpoint)
};
};
const createProviderRuntimeName = (context, providerRuntimeConfig) => `${providerRuntimeConfig.namePrefix ?? 'atlas-search'}-${context.config.provider}`;
const isPrivateComputeProviderRuntime = providerRuntimeConfig => providerRuntimeConfig.platform === 'compute' && providerRuntimeConfig.compute?.assignPublicIp !== true;
const getCommandErrorMessage = error => [error?.stderr?.toString?.() ?? '', error?.message ?? ''].filter(Boolean).join('\n');
const doesReferenceMatch = (reference, expectedValue, resourceType) => {
const trimmedReference = normalizeOptionalString(reference);
const trimmedExpectedValue = normalizeOptionalString(expectedValue);
if (!trimmedReference || !trimmedExpectedValue) {
return false;
}
return trimmedReference === trimmedExpectedValue || trimmedReference.endsWith(`/${resourceType}/${trimmedExpectedValue}`);
};
const parseJsonCommandOutput = (output, description) => {
try {
return JSON.parse(output);
} catch (error) {
throw new Error(`Could not parse ${description}: ${error.message}`);
}
};
const listExistingRegionalCloudNats = (context, providerRuntimeConfig, runGcloudFileCommandImpl = runGcloudFileCommand) => {
const network = providerRuntimeConfig.network ?? 'default';
const {
region
} = resolveSearchCloudRunDeployConfig(context);
const routersOutput = runGcloudFileCommandImpl(['compute', 'routers', 'list', `--project=${context.projectId}`, '--format=json'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
});
const routers = parseJsonCommandOutput(routersOutput, 'Cloud Router list').filter(router => doesReferenceMatch(router?.region, region, 'regions') && doesReferenceMatch(router?.network, network, 'networks'));
return routers.flatMap(router => {
const routerName = normalizeOptionalString(router?.name);
if (!routerName) {
return [];
}
const routerOutput = runGcloudFileCommandImpl(['compute', 'routers', 'describe', routerName, `--project=${context.projectId}`, `--region=${region}`, '--format=json'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
});
const describedRouter = parseJsonCommandOutput(routerOutput, `Cloud Router ${routerName}`);
return Array.isArray(describedRouter?.nats) ? describedRouter.nats.map(nat => normalizeOptionalString(nat?.name)).filter(Boolean).map(natName => ({
natName,
routerName
})) : [];
});
};
const verifyExistingRegionalCloudNat = (context, region, candidate, runGcloudFileCommandImpl = runGcloudFileCommand) => {
const natOutput = runGcloudFileCommandImpl(['compute', 'routers', 'nats', 'describe', candidate.natName, `--project=${context.projectId}`, `--router=${candidate.routerName}`, `--region=${region}`, '--format=json'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
});
const describedNat = parseJsonCommandOutput(natOutput, `Cloud NAT ${candidate.natName} on router ${candidate.routerName}`);
return normalizeOptionalString(describedNat?.name) === candidate.natName;
};
const listVerifiedRegionalCloudNats = (context, providerRuntimeConfig, runGcloudFileCommandImpl = runGcloudFileCommand) => {
const verifiedNats = [];
const {
region
} = resolveSearchCloudRunDeployConfig(context);
const candidates = listExistingRegionalCloudNats(context, providerRuntimeConfig, runGcloudFileCommandImpl);
for (const candidate of candidates) {
try {
if (verifyExistingRegionalCloudNat(context, region, candidate, runGcloudFileCommandImpl)) {
verifiedNats.push(candidate);
}
} catch (error) {
if (isGcloudResourceNotFoundError(error)) {
continue;
}
throw error;
}
}
return verifiedNats;
};
const createMissingNatBlockingIssue = (context, providerRuntimeConfig) => {
const network = providerRuntimeConfig.network ?? 'default';
const {
region
} = resolveSearchCloudRunDeployConfig(context);
return 'Private Compute Engine provider runtimes require outbound egress during startup, ' + 'but managed Cloud NAT is disabled and no reusable Cloud NAT was found. ' + `Project=${context.projectId}, network=${network}, region=${region}. ` + 'Enable deploy.providerRuntime.compute.createNatGateway or provision a Cloud NAT on a router for this region/network, then rerun "atlas search apply".';
};
const createUnverifiedNatBlockingIssue = (context, providerRuntimeConfig, error) => {
const network = providerRuntimeConfig.network ?? 'default';
const {
region
} = resolveSearchCloudRunDeployConfig(context);
return 'Private Compute Engine provider runtimes require outbound egress during startup, ' + 'but Atlas could not verify reusable Cloud NAT while managed NAT creation is disabled. ' + `Project=${context.projectId}, network=${network}, region=${region}. ${getCommandErrorMessage(error)} ` + 'Either enable deploy.providerRuntime.compute.createNatGateway or ensure Cloud NAT exists and your account ' + 'can inspect routers/nats, then rerun "atlas search apply".';
};
export const reuseExistingCloudNatForProviderRuntime = (context, providerRuntimeConfig, dependencies = {}) => {
if (!isPrivateComputeProviderRuntime(providerRuntimeConfig) || providerRuntimeConfig.compute?.createNatGateway === false) {
return {
blockingIssues: [],
providerRuntimeConfig,
warnings: []
};
}
const runGcloudFileCommandImpl = dependencies.runGcloudFileCommand ?? runGcloudFileCommand;
const {
region
} = resolveSearchCloudRunDeployConfig(context);
const network = providerRuntimeConfig.network ?? 'default';
try {
const existingNats = listVerifiedRegionalCloudNats(context, providerRuntimeConfig, runGcloudFileCommandImpl);
if (existingNats.length === 0) {
return {
blockingIssues: [],
providerRuntimeConfig,
warnings: []
};
}
const [{
natName,
routerName
}] = existingNats;
return {
blockingIssues: [],
providerRuntimeConfig: createProviderRuntimeConfigUpdate(providerRuntimeConfig, {
compute: {
createNatGateway: false
}
}),
warnings: [`Detected existing Cloud NAT ${natName} on router ${routerName} for project ${context.projectId}, ` + `network ${network}, region ${region}. Atlas verified this NAT through "gcloud compute routers nats describe" ` + 'and will reuse that egress path, so Atlas will skip creating a managed NAT gateway for this provider runtime.']
};
} catch (error) {
return {
blockingIssues: [],
providerRuntimeConfig,
warnings: [`Could not inspect existing Cloud NAT configuration for project ${context.projectId}, ` + `network ${network}, region ${region}. Atlas will continue with managed NAT creation. ${getCommandErrorMessage(error)}`]
};
}
};
const resolvePrivateNatReuseForProviderRuntime = (context, providerRuntimeConfig, dependencies = {}) => {
if (!isPrivateComputeProviderRuntime(providerRuntimeConfig)) {
return {
blockingIssues: [],
persistedProviderRuntimeConfig: providerRuntimeConfig,
runtimeProviderRuntimeConfig: providerRuntimeConfig,
warnings: []
};
}
if (providerRuntimeConfig.compute?.createNatGateway === false) {
const runGcloudFileCommandImpl = dependencies.runGcloudFileCommand ?? runGcloudFileCommand;
try {
const existingNats = listVerifiedRegionalCloudNats(context, providerRuntimeConfig, runGcloudFileCommandImpl);
if (existingNats.length === 0) {
return {
blockingIssues: [createMissingNatBlockingIssue(context, providerRuntimeConfig)],
persistedProviderRuntimeConfig: providerRuntimeConfig,
runtimeProviderRuntimeConfig: providerRuntimeConfig,
warnings: []
};
}
return {
blockingIssues: [],
persistedProviderRuntimeConfig: providerRuntimeConfig,
runtimeProviderRuntimeConfig: providerRuntimeConfig,
warnings: []
};
} catch (error) {
return {
blockingIssues: [createUnverifiedNatBlockingIssue(context, providerRuntimeConfig, error)],
persistedProviderRuntimeConfig: providerRuntimeConfig,
runtimeProviderRuntimeConfig: providerRuntimeConfig,
warnings: []
};
}
}
const natReuse = reuseExistingCloudNatForProviderRuntime(context, providerRuntimeConfig, {
runGcloudFileCommand: dependencies.runGcloudFileCommand
});
return {
blockingIssues: natReuse.blockingIssues ?? [],
persistedProviderRuntimeConfig: providerRuntimeConfig,
runtimeProviderRuntimeConfig: natReuse.providerRuntimeConfig,
warnings: natReuse.warnings ?? []
};
};
export const createSearchProviderTerraformPayload = (context, providerRuntimeConfig, managedAccess, options = {}) => {
const basePayload = {
contract_version: SEARCH_PROVIDER_TERRAFORM_CONTRACT_VERSION,
deploy_backfill_job: false,
environment: context.environment ?? null,
name_prefix: providerRuntimeConfig.namePrefix ?? 'atlas-search',
network: providerRuntimeConfig.network ?? 'default',
platform: providerRuntimeConfig.platform,
project_id: context.projectId,
provider: context.config.provider,
region: resolveSearchCloudRunDeployConfig(context).region,
resource_labels: createSearchProviderResourceLabels(context, providerRuntimeConfig.platform),
service_account_email: providerRuntimeConfig.serviceAccountEmail ?? resolveSearchCloudRunServiceAccountEmail(context.config, context.projectId),
subnetwork: providerRuntimeConfig.subnetwork ?? null
};
if (providerRuntimeConfig.platform === 'compute') {
basePayload.compute = mapComputeConfig(providerRuntimeConfig.compute);
}
if (providerRuntimeConfig.platform === 'gke') {
basePayload.gke = mapGkeConfig(providerRuntimeConfig.gke);
const kubernetes = createSearchProviderTerraformConnectionPayload(options.terraformConnection);
if (kubernetes) {
basePayload.kubernetes = kubernetes;
}
}
if (context.config.provider === 'typesense') {
if (!managedAccess.adminApiKey) {
throw new Error('Atlas search provider deploy could not resolve a Typesense admin API key.');
}
basePayload.typesense = {
admin_api_key: managedAccess.adminApiKey
};
return basePayload;
}
if (!managedAccess.password) {
throw new Error('Atlas search provider deploy could not resolve an Elasticsearch admin password.');
}
basePayload.elasticsearch = {
admin_password: managedAccess.password
};
return basePayload;
};
export const getSearchProviderTerraformArtifactPath = (projectId, cwd = process.cwd()) => path.join(getAtlasGeneratedTerraformDir(cwd), 'search', `provider.${projectId}.tfvars.json`);
export const getSearchProviderPlatformTerraformArtifactPath = (projectId, cwd = process.cwd()) => path.join(getAtlasGeneratedTerraformDir(cwd), 'search', `provider-platform.${projectId}.tfvars.json`);
export const writeSearchProviderTerraformArtifact = (context, providerRuntimeConfig, managedAccess, options = {}, cwd = process.cwd()) => {
const writeJsonFileImpl = options.writeJsonFile ?? writeJsonFile;
const filePath = getSearchProviderTerraformArtifactPath(context.projectId, cwd);
const payload = createSearchProviderTerraformPayload(context, providerRuntimeConfig, managedAccess, options);
writeJsonFileImpl(filePath, payload);
return {
contractVersion: payload.contract_version,
filePath,
payload
};
};
export const writeSearchProviderPlatformTerraformArtifact = (context, providerRuntimeConfig, options = {}, cwd = process.cwd()) => {
const writeJsonFileImpl = options.writeJsonFile ?? writeJsonFile;
const filePath = getSearchProviderPlatformTerraformArtifactPath(context.projectId, cwd);
const payload = createSearchProviderPlatformTerraformPayload(context, providerRuntimeConfig);
writeJsonFileImpl(filePath, payload);
return {
contractVersion: payload.contract_version,
filePath,
payload
};
};
const createSearchProviderTerraformWorkflowCommand = (mode, workflowSummary, terraformArtifact) => {
const baseArgs = [createTerraformStringVariableArgument(SEARCH_PROVIDER_TERRAFORM_INPUT_VARIABLE, terraformArtifact.filePath), '-input=false'];
if (mode === 'plan') {
return ['plan', ...baseArgs];
}
return [mode, '-auto-approve', ...baseArgs];
};
const createSearchProviderPlatformTerraformWorkflowCommand = (mode, terraformArtifact) => {
const baseArgs = [createTerraformStringVariableArgument(SEARCH_PROVIDER_PLATFORM_TERRAFORM_INPUT_VARIABLE, terraformArtifact.filePath), '-input=false'];
if (mode === 'plan') {
return ['plan', ...baseArgs];
}
return [mode, '-auto-approve', ...baseArgs];
};
const createSearchProviderTerraformImportCommand = (terraformArtifact, target) => ['import', createTerraformStringVariableArgument(SEARCH_PROVIDER_TERRAFORM_INPUT_VARIABLE, terraformArtifact.filePath), '-input=false', target.address, target.id];
const createSearchProviderTerraformStateShowCommand = target => ['state', 'show', target.address];
const createSearchProviderPlatformTerraformImportCommand = (terraformArtifact, target) => ['import', createTerraformStringVariableArgument(SEARCH_PROVIDER_PLATFORM_TERRAFORM_INPUT_VARIABLE, terraformArtifact.filePath), '-input=false', target.address, target.id];
const createSearchProviderPlatformTerraformStateShowCommand = target => ['state', 'show', target.address];
const doesSearchProviderImportTargetExist = (target, runGcloudFileCommandImpl = runGcloudFileCommand) => {
try {
runGcloudFileCommandImpl(target.describeArgs, {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
});
return true;
} catch (error) {
if (isGcloudResourceNotFoundError(error)) {
return false;
}
throw new Error(`Could not inspect existing Atlas search provider ${target.kind} ${target.name}. ${getCommandErrorMessage(error)}`);
}
};
const getSearchProviderTerraformImportTargets = (context, providerRuntimeConfig) => {
if (providerRuntimeConfig.platform !== 'compute') {
return [];
}
const runtimeName = createProviderRuntimeName(context, providerRuntimeConfig);
const {
projectId
} = context;
const zone = providerRuntimeConfig.compute?.zone;
if (!normalizeOptionalString(zone)) {
return [];
}
const providerModuleName = context.config.provider === 'typesense' ? 'typesense_compute' : 'elasticsearch_compute';
const firewallResourceName = context.config.provider === 'typesense' ? 'typesense' : 'elasticsearch';
const targets = [{
address: `module.atlas_search_provider.module.${providerModuleName}[0].google_compute_instance.this`,
describeArgs: ['compute', 'instances', 'describe', runtimeName, `--project=${projectId}`, `--zone=${zone}`, '--format="value(name)"'],
id: `projects/${projectId}/zones/${zone}/instances/${runtimeName}`,
kind: 'instance',
name: runtimeName
}, {
address: `module.atlas_search_provider.module.${providerModuleName}[0].google_compute_disk.data`,
describeArgs: ['compute', 'disks', 'describe', `${runtimeName}-data`, `--project=${projectId}`, `--zone=${zone}`, '--format="value(name)"'],
id: `projects/${projectId}/zones/${zone}/disks/${runtimeName}-data`,
kind: 'disk',
name: `${runtimeName}-data`
}];
if (providerRuntimeConfig.compute?.createFirewallRule !== false) {
targets.push({
address: `module.atlas_search_provider.module.${providerModuleName}[0].google_compute_firewall.${firewallResourceName}[0]`,
describeArgs: ['compute', 'firewall-rules', 'describe', `${runtimeName}-${context.config.provider}-ingress`, `--project=${projectId}`, '--format="value(name)"'],
id: `projects/${projectId}/global/firewalls/${runtimeName}-${context.config.provider}-ingress`,
kind: 'firewall',
name: `${runtimeName}-${context.config.provider}-ingress`
});
}
return targets;
};
const getSearchProviderPlatformTerraformImportTargets = (context, providerRuntimeConfig) => {
if (!isManagedGkeCluster(providerRuntimeConfig)) {
return [];
}
const clusterConfig = createDefaultGkeClusterConfig(context, providerRuntimeConfig, providerRuntimeConfig.gke?.cluster ?? {});
const nodePoolConfig = createDefaultGkeNodePoolConfig(context.config.provider, providerRuntimeConfig, providerRuntimeConfig.gke?.nodePool ?? {});
return [{
address: 'module.atlas_search_provider_gke_cluster.google_container_cluster.this',
describeArgs: ['container', 'clusters', 'describe', clusterConfig.name, `--project=${context.projectId}`, `--location=${clusterConfig.location}`, '--format=value(name)'],
id: `projects/${context.projectId}/locations/${clusterConfig.location}/clusters/${clusterConfig.name}`,
kind: 'gke-cluster',
name: clusterConfig.name
}, {
address: 'module.atlas_search_provider_gke_node_pool.google_container_node_pool.this',
describeArgs: ['container', 'node-pools', 'describe', nodePoolConfig.name, `--project=${context.projectId}`, `--cluster=${clusterConfig.name}`, `--location=${clusterConfig.location}`, '--format=value(name)'],
id: `projects/${context.projectId}/locations/${clusterConfig.location}/clusters/${clusterConfig.name}/nodePools/${nodePoolConfig.name}`,
kind: 'gke-node-pool',
name: nodePoolConfig.name
}];
};
export const importExistingSearchProviderTerraformResources = async (context, providerRuntimeConfig, terraformArtifact, workflowSummary, dependencies = {}) => {
const runTerraformCommandImpl = dependencies.runTerraformCommand ?? runTerraformCommand;
const runGcloudFileCommandImpl = dependencies.runGcloudFileCommand ?? runGcloudFileCommand;
const importResults = [];
for (const target of getSearchProviderTerraformImportTargets(context, providerRuntimeConfig)) {
if (!doesSearchProviderImportTargetExist(target, runGcloudFileCommandImpl)) {
importResults.push({
address: target.address,
kind: target.kind,