Skip to content

Commit

Permalink
feat: add experimental support for deploying foundation level modules
Browse files Browse the repository at this point in the history
these have become a useful pattern to manage things that are
consistent across your entire foundation like managing a common
terraform state across multiple platforms, a central CI/CD pipeline
for your IAC or generating docs (v2 coming soon...)
  • Loading branch information
JohannesRudolph committed May 22, 2024
1 parent f897106 commit 8e43ec5
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/commands/foundation/TestModuleType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export class TestModuleType extends StringType {
const excludes = {
testModules: false,
tenantModules: true,
platformModules: false,
};

const modules = await repo.processFilesGlob(
Expand Down
24 changes: 23 additions & 1 deletion src/commands/foundation/deploy.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand Down Expand Up @@ -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) {
Expand Down
109 changes: 109 additions & 0 deletions src/foundation/FoundationDeployer.ts
Original file line number Diff line number Diff line change
@@ -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 <cmd>"`,
);

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 <cmd>"`,
);
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 <cmd>"`,
);

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);
}
}
1 change: 1 addition & 0 deletions src/foundation/PlatformDeployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export class PlatformDeployer<T extends PlatformConfig> {
const excludes = {
testModules: false,
tenantModules: true,
platformModules: false,
};

const files = await this.repo.processFilesGlob(
Expand Down
1 change: 1 addition & 0 deletions src/kit/KitDependencyAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export class KitDependencyAnalyzer {
const excludes = {
tenantModules: true,
testModules: true,
platformModules: false,
};
const q = await this.collie.processFilesGlob(
`${relativePlatformPath}/**/terragrunt.hcl`,
Expand Down
5 changes: 4 additions & 1 deletion src/model/CollieRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -56,9 +56,11 @@ export class CollieRepository {
excludes: {
testModules: boolean;
tenantModules: boolean;
platformModules: boolean;
} = {
testModules: true,
tenantModules: false,
platformModules: false,
},
): Promise<T[]> {
const q: T[] = [];
Expand All @@ -79,6 +81,7 @@ export class CollieRepository {

...(excludes.testModules ? [TEST_MODULE_GLOB] : []),
...(excludes.tenantModules ? [TENANT_MODULE_GLOB] : []),
...(excludes.platformModules ? [PLATFORM_MODULE_GLOB] : []),
],
})
) {
Expand Down

0 comments on commit 8e43ec5

Please sign in to comment.