UNPKG

kubricate

Version:

A TypeScript framework for building reusable, type-safe Kubernetes infrastructure — without the YAML mess.

227 lines (195 loc) 7.18 kB
import type { BaseConnector, BaseLogger, BaseProvider, ProviderInjection } from '@kubricate/core'; import { SecretsInjectionContext } from '../secret/index.js'; import type { AnySecretManager, EnvOptions } from '../secret/types.js'; import type { AnyKey, FunctionLike, InferConfigureComposerFunc } from '../types.js'; import { ResourceComposer } from './ResourceComposer.js'; export interface UseSecretsOptions<Key extends AnyKey> { env?: EnvOptions<Key>[]; injectes?: ProviderInjection[]; } export type SecretManagerId = number; /** * BaseStack is the base class for all stacks. * * @note BaseStack fields and methods need to be public, type inference is not working with private fields when using with `createSt` */ export abstract class BaseStack< // eslint-disable-next-line @typescript-eslint/no-explicit-any ConfigureComposerFunc extends FunctionLike<any[], ResourceComposer> = FunctionLike<any, ResourceComposer>, SecretManager extends AnySecretManager = AnySecretManager, > { public _composer!: ReturnType<ConfigureComposerFunc>; public _secretManagers: Record<SecretManagerId, SecretManager> = {}; public _targetInjects: ProviderInjection[] = []; public readonly _defaultSecretManagerId = 'default'; public logger?: BaseLogger; /** * The name of the stack. * This is used to identify the stack, generally used with Stack. */ public _name?: string; /** * Registers a secret injection to be processed during stack build/render. */ registerSecretInjection(inject: ProviderInjection): void { this._targetInjects.push(inject); } /** * Retrieves all registered secret injections. */ getTargetInjects() { return this._targetInjects; } useSecrets<NewSecretManager extends AnySecretManager>( secretManager: NewSecretManager, builder: (injector: SecretsInjectionContext<NewSecretManager>) => void ): this { if (!secretManager) { throw new Error(`Cannot BaseStack.useSecrets, secret manager is not provided.`); } const secretManagerNextId = Object.keys(this._secretManagers).length; this._secretManagers[secretManagerNextId] = secretManager as unknown as SecretManager; const ctx = new SecretsInjectionContext(this, secretManager, secretManagerNextId); builder(ctx); // invoke builder ctx.resolveAll(); return this; } /** * Get the secret manager instance. * @param id The ID of the secret manager. defaults to 'default'. * @returns The secret manager instance. */ getSecretManager(id: number) { if (!this._secretManagers[id]) { throw new Error( `Secret manager with ID ${id} is not defined. Make sure to use the 'useSecrets' method to define it, and call before 'from' method in the stack.` ); } return this._secretManagers[id]; } /** * Get all secret managers in the stack. * @returns The secret managers in the stack. */ getSecretManagers() { return this._secretManagers; } /** * Configure the stack with the provided data. * @param data The configuration data for the stack. * @returns The Kubricate Composer instance. */ abstract from(data: unknown): unknown; override(data: Partial<InferConfigureComposerFunc<ConfigureComposerFunc>>) { this._composer.override(data); return this; } /** * Build the stack and return the resources. * @returns The resources in the stack. */ build() { this.logger?.debug('BaseStack.build: Starting to build the stack.'); type InjectionKey = string; const injectGroups = new Map< InjectionKey, { providerId: string; provider: BaseProvider; resourceId: string; path: string; injects: ProviderInjection[]; } >(); for (const inject of this._targetInjects) { const key = `${inject.providerId}:${inject.resourceId}:${inject.path}`; if (!injectGroups.has(key)) { injectGroups.set(key, { providerId: inject.providerId, provider: inject.provider, resourceId: inject.resourceId, path: inject.path, injects: [], }); } injectGroups.get(key)!.injects.push(inject); } for (const { providerId, provider, resourceId, path, injects } of injectGroups.values()) { const payload = provider.getInjectionPayload(injects); this.logger?.debug(`BaseStack.build: Injecting value into resource:`); this.logger?.debug( JSON.stringify( { providerId, resourceId, path, payload, }, null, 2 ) ); this._composer.inject(resourceId, path, payload); this.logger?.debug( `BaseStack.build: Injected secrets from provider "${providerId}" into resource "${resourceId}" at path "${path}".` ); } return this._composer.build(); // for (const targetInject of this._targetInjects) { // const provider = targetInject.provider; // const injectsForProvider = this._targetInjects.filter(i => i.provider === provider); // const targetValue = provider.getInjectionPayload(injectsForProvider); // this.logger?.debug(`BaseStack.build: Injecting value: ${JSON.stringify(targetValue, null, 2)}`); // this._composer.inject(targetInject.resourceId, targetInject.path, targetValue); // this.logger?.debug(` // BaseStack.build: Injected secrets into provider "${provider.constructor.name}" with ID "${targetInject.resourceId}" at path: ${targetInject.path}.`); // } // return this._composer.build(); } public setComposer(composer: ReturnType<ConfigureComposerFunc>) { this._composer = composer; } getComposer(): ReturnType<ConfigureComposerFunc> | undefined { return this._composer; } /** * Get the resources from the composer. * @returns The resources from the composer. */ get resources() { return this._composer; } getName() { return this._name; } setName(name: string) { this._name = name; } /** * @internal * This method is used to inject the logger into the stack. * It is called by the orchestrator to inject the logger into all components of the stack. * * Inject a logger instance into all components of the stack e.g. secret managers, connector, providers, etc. * This is useful for logging purposes and debugging. * @param logger The logger instance to be injected. */ injectLogger(logger: BaseLogger) { this.logger = logger; if (typeof this.getSecretManagers === 'function') { const managers = this.getSecretManagers(); for (const secretManager of Object.values(managers)) { // Inject into SecretManager secretManager.logger = logger; // Inject into each connector for (const connector of Object.values(secretManager.getConnectors())) { (connector as BaseConnector).logger = logger; } // Inject into each provider for (const provider of Object.values(secretManager.getProviders())) { (provider as BaseProvider).logger = logger; } } } } }