UNPKG

@puls-atlas/cli

Version:

The Puls Atlas CLI tool for managing Atlas projects

127 lines 6.2 kB
import { isString } from 'es-toolkit/compat'; import { discoverIndexes } from './discovery.js'; import { isPlainObject } from '../../../../utils/value.js'; import { SearchProviderDescriptor } from '../contracts/SearchProviderDescriptor.js'; import { SearchLifecycleProvider } from '../contracts/SearchLifecycleProvider.js'; import { createIndexSchema, inspectIndexSchemas, reconcileIndexSchemas } from './schema.js'; import { resolveAccess, hasResolvedAuth, ensureManagedAccess, getConfigSecretName } from './access.js'; export class ElasticsearchLifecycleProvider extends SearchLifecycleProvider { constructor() { super(new SearchProviderDescriptor({ id: 'elasticsearch', displayName: 'Elasticsearch', configSectionKey: 'elasticsearch', capabilities: { supportsRemoteTargetDiscovery: true, supportsSchemaInspection: true, supportsSchemaReconciliation: true, supportsZeroDowntimeReindex: true }, labels: { access: 'Elasticsearch access', targetGroup: 'Elasticsearch indexes' } })); } createCollectionSchema(context, options) { return createIndexSchema(context, options); } createDefaultConfigSection() { return { configSecret: 'puls-atlas-elasticsearch-config' }; } async ensureManagedAccess(context, options = {}) { return ensureManagedAccess(context, options); } async resolveAccess(context) { return resolveAccess(context); } async discoverRemoteTargets(context) { return discoverIndexes(context); } getAccessReadiness(access) { const hasAuth = hasResolvedAuth(access); return { hasAuth, hasNode: Boolean(access.node), summary: `node=${access.node ? 'yes' : 'no'}, auth=${hasAuth ? 'yes' : 'no'}.` }; } getConfigSecretName(config) { return getConfigSecretName(config); } getDeployAccessIssues(access) { const issues = []; if (!access.node) { issues.push('Atlas search apply requires Elasticsearch node access so provider schemas can be reconciled before runtime deployment. ' + 'Run "atlas search apply" first or configure the Elasticsearch secret manually.'); } if (!hasResolvedAuth(access)) { issues.push('Atlas search apply requires Elasticsearch authentication so provider schemas can be reconciled before runtime deployment. ' + 'Run "atlas search apply" first or configure the Elasticsearch secret manually.'); } return issues; } getDestroyNotice() { return 'Elasticsearch engine infrastructure and remote Elasticsearch indexes are not ' + 'deleted by this command because they are not managed by atlas search apply.'; } getEngineRuntimeContract(context) { return { engineType: 'elasticsearch', providerSecret: getConfigSecretName(context.config), responsibility: 'Atlas search provider infrastructure is managed through atlas search apply, or can be managed externally for Elasticsearch.', requiredEnvironmentVariables: [], requiredSecretFields: [{ name: 'node', description: 'Reachable base URL for the externally managed Elasticsearch endpoint, for example https://elasticsearch.example.com.' }, { name: 'apiKey', description: 'Optional API key for Elasticsearch authentication. Use this or provide username and password.' }, { name: 'username', description: 'Optional basic-auth username for Elasticsearch when apiKey is not used. Must be paired with password.' }, { name: 'password', description: 'Optional basic-auth password for Elasticsearch when apiKey is not used. Must be paired with username.' }], manualDeploymentInstructions: ['Run "atlas search apply" to provision an Elasticsearch runtime through Terraform on Compute Engine or on an existing GKE cluster, or keep managing the runtime externally.', 'Expose an HTTPS endpoint that Atlas search runtimes can reach from Cloud Run and grant it credentials with index management and document write access.', 'Store the endpoint and credentials in the configured Elasticsearch secret before running atlas search apply.', 'Keep the configured secret payload aligned with the externally reachable Elasticsearch node and authentication method.'] }; } getLocalDevelopmentFallbackEnvVars() { return ['ELASTICSEARCH_NODE', 'ELASTICSEARCH_API_KEY', 'ELASTICSEARCH_USERNAME', 'ELASTICSEARCH_PASSWORD']; } getProvisionAccessIssues(access) { const issues = []; if (!access.node) { issues.push('Elasticsearch node could not be resolved. Run "atlas search apply" to bootstrap a managed Elasticsearch runtime, ' + 'or set ELASTICSEARCH_NODE / provide "node" in the configured Elasticsearch secret.'); } if (!hasResolvedAuth(access)) { issues.push('Elasticsearch authentication could not be resolved. Run "atlas search apply" to bootstrap managed credentials, ' + 'or set ELASTICSEARCH_API_KEY / ELASTICSEARCH_USERNAME and ELASTICSEARCH_PASSWORD, or provide those values in the configured Elasticsearch secret.'); } return issues; } getRuntimeSecretReferences(context) { return [{ env: 'PULS_ATLAS_ELASTICSEARCH_CONFIG_SECRET', secretName: getConfigSecretName(context.config), version: 'latest' }]; } isRelevantDeployWarning(warning) { return !['Elasticsearch index discovery', 'Could not inspect Elasticsearch indexes', 'Could not resolve Elasticsearch access'].some(phrase => warning.includes(phrase)); } async inspectSchemas(context) { return inspectIndexSchemas(context); } async reconcileSchemas(context, options = {}) { return reconcileIndexSchemas(context, options); } validateConfigSection(configSection) { if (!isPlainObject(configSection)) { throw new Error('Invalid Atlas search config. The "elasticsearch" property must be an object when provided.'); } if (configSection.configSecret !== undefined && !isString(configSection.configSecret)) { throw new Error('Invalid Atlas search config. The "elasticsearch.configSecret" property must be a string when provided.'); } } } export default new ElasticsearchLifecycleProvider();