diff --git a/src/commands/foundation/TestModuleType.ts b/src/commands/foundation/TestModuleType.ts index 6a9b3b1..c10cdd0 100644 --- a/src/commands/foundation/TestModuleType.ts +++ b/src/commands/foundation/TestModuleType.ts @@ -9,6 +9,7 @@ export class TestModuleType extends StringType { const excludes = { testModules: false, tenantModules: true, + platformModules: false, }; const modules = await repo.processFilesGlob( diff --git a/src/commands/foundation/deploy.command.ts b/src/commands/foundation/deploy.command.ts index fbd6453..080a8cc 100644 --- a/src/commands/foundation/deploy.command.ts +++ b/src/commands/foundation/deploy.command.ts @@ -17,6 +17,7 @@ import { LiteralArgsParser } from "../LiteralArgsParser.ts"; import { TopLevelCommand } from "../TopLevelCommand.ts"; import { getCurrentWorkingFoundation } from "../../cli/commandOptionsConventions.ts"; import { NullProgressReporter } from "../../cli/NullProgressReporter.ts"; +import { FoundationDeployer } from "../../foundation/FoundationDeployer.ts"; interface DeployOptions { platform?: string; @@ -29,7 +30,7 @@ export function registerDeployCmd(program: TopLevelCommand) { const cmd = program .command("deploy [foundation:foundation]") .description( - "Deploy platform modules in your cloud foundations using terragrunt", + "Deploy foundation and platform modules in your cloud foundations using terragrunt", ) .type("module", new PlatformModuleType()) .option( @@ -121,6 +122,27 @@ export async function deployFoundation( const terragrunt = factory.buildTerragrunt(); + // unless targeting a specific platform, always deploy foundation modules + if (!opts.platform) { + const deployer = new FoundationDeployer( + repo, + foundation, + terragrunt, + logger, + ); + + await deployer.deployFoundationModules( + mode, + opts.module, + !!opts.autoApprove, + ); + + // if all we had to do was deploy a specific foundation module, we are done and can exit + if (opts.module) { + return; + } + } + const platforms = findPlatforms(opts.platform, foundation, logger); for (const platform of platforms) { diff --git a/src/foundation/FoundationDeployer.ts b/src/foundation/FoundationDeployer.ts new file mode 100644 index 0000000..0bc5eed --- /dev/null +++ b/src/foundation/FoundationDeployer.ts @@ -0,0 +1,109 @@ +import * as path from "std/path"; + +import { + TerragruntArguments, + TerragruntCliFacade, + toVerb, +} from "/api/terragrunt/TerragruntCliFacade.ts"; +import { FoundationRepository } from "/model/FoundationRepository.ts"; +import { Logger } from "../cli/Logger.ts"; +import { ProgressReporter } from "/cli/ProgressReporter.ts"; +import { + CollieRepository, + PLATFORM_MODULE_GLOB, +} from "/model/CollieRepository.ts"; + +export class FoundationDeployer { + constructor( + private readonly repo: CollieRepository, + protected readonly foundation: FoundationRepository, + private readonly terragrunt: TerragruntCliFacade, + private readonly logger: Logger, + ) {} + + async deployFoundationModules( + mode: TerragruntArguments, + module: string | undefined, + autoApprove: boolean, + ) { + const foundationOrModulePath = this.foundation.resolvePath( + module || "", + ); + + const relativePlatformOrModulePath = this.repo.relativePath( + foundationOrModulePath, + ); + + const progress = this.buildProgressReporter( + mode, + relativePlatformOrModulePath, + ); + + const tgfiles = await this.foundationModuleTerragruntFiles( + relativePlatformOrModulePath, + ); + if (tgfiles.length === 0) { + this.logger.warn( + (fmt) => + `detected no foundation modules at ${ + fmt.kitPath(foundationOrModulePath) + }, will skip invoking "terragrunt "`, + ); + + this.logger.tipCommand( + "Apply a kit module to this foundation to create a foundation module using", + "kit apply", + ); + } else if (tgfiles.length === 1) { + // we can't run terragrunt in the platform dir, so we have to infer the platformModule path from + // the discovered terragrunt file + const singleModulePath = path.dirname(tgfiles[0].path); + + this.logger.debug( + (fmt) => + `detected a single foundation module at ${ + fmt.kitPath(singleModulePath) + }, will deploy with "terragrunt "`, + ); + await this.terragrunt.run(singleModulePath, mode, { autoApprove }); + } else { + this.logger.debug( + (fmt) => + `detected a stack of foundation modules at ${ + fmt.kitPath( + foundationOrModulePath, + ) + }, will deploy with "terragrunt run-all "`, + ); + + await this.terragrunt.runAll(foundationOrModulePath, mode, { + excludeDirs: [PLATFORM_MODULE_GLOB], // if we let terragrunt run a run-all, need to explicitly exclude all platform modules + autoApprove, + }); + } + + progress.done(); + } + + private async foundationModuleTerragruntFiles(relativeModulePath: string) { + const excludes = { + testModules: true, + tenantModules: true, + platformModules: true, + }; + + const files = await this.repo.processFilesGlob( + // todo: exclude platforms/folder + `${relativeModulePath}/**/terragrunt.hcl`, + (file) => file, + excludes, + ); + + // a terragrunt stack conists of multiple executable terragrunt files + return files; + } + + private buildProgressReporter(mode: TerragruntArguments, id: string) { + return new ProgressReporter(toVerb(mode), id, this.logger); + } +} diff --git a/src/foundation/PlatformDeployer.ts b/src/foundation/PlatformDeployer.ts index 71110c1..f7dd396 100644 --- a/src/foundation/PlatformDeployer.ts +++ b/src/foundation/PlatformDeployer.ts @@ -173,6 +173,7 @@ export class PlatformDeployer { const excludes = { testModules: false, tenantModules: true, + platformModules: false, }; const files = await this.repo.processFilesGlob( diff --git a/src/kit/KitDependencyAnalyzer.ts b/src/kit/KitDependencyAnalyzer.ts index 9f47838..e54e26c 100644 --- a/src/kit/KitDependencyAnalyzer.ts +++ b/src/kit/KitDependencyAnalyzer.ts @@ -64,6 +64,7 @@ export class KitDependencyAnalyzer { const excludes = { tenantModules: true, testModules: true, + platformModules: false, }; const q = await this.collie.processFilesGlob( `${relativePlatformPath}/**/terragrunt.hcl`, diff --git a/src/model/CollieRepository.ts b/src/model/CollieRepository.ts index 79f16dd..3b57f1d 100644 --- a/src/model/CollieRepository.ts +++ b/src/model/CollieRepository.ts @@ -3,7 +3,7 @@ import * as path from "std/path"; export const TEST_MODULE_GLOB = "**/*.test"; export const TENANT_MODULE_GLOB = "**/tenants"; - +export const PLATFORM_MODULE_GLOB = "**/platforms"; export class CollieRepository { private constructor(private readonly repoDir: string) { if (!path.isAbsolute(repoDir)) { @@ -56,9 +56,11 @@ export class CollieRepository { excludes: { testModules: boolean; tenantModules: boolean; + platformModules: boolean; } = { testModules: true, tenantModules: false, + platformModules: false, }, ): Promise { const q: T[] = []; @@ -79,6 +81,7 @@ export class CollieRepository { ...(excludes.testModules ? [TEST_MODULE_GLOB] : []), ...(excludes.tenantModules ? [TENANT_MODULE_GLOB] : []), + ...(excludes.platformModules ? [PLATFORM_MODULE_GLOB] : []), ], }) ) {