Skip to content

Instantly share code, notes, and snippets.

@thpham
Created June 3, 2024 17:17
Show Gist options
  • Save thpham/49fbc437f3e08de6702acf473320ef00 to your computer and use it in GitHub Desktop.
Save thpham/49fbc437f3e08de6702acf473320ef00 to your computer and use it in GitHub Desktop.
Re-Implementation of https://github.com/aws-quickstart/cdk-eks-blueprints to make it works with an AWS Service Catalog product.
import { IVpc } from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";
import {
BlueprintPropsConstraints,
DEFAULT_VERSION,
EksBlueprintProps,
utils,
MngClusterProvider,
} from "@aws-quickstart/eks-blueprints";
import { VpcProvider } from "../eks-blueprints/resource-providers/vpc";
import * as spi from "@aws-quickstart/eks-blueprints";
import * as constraints from "@aws-quickstart/eks-blueprints/dist/utils/constraints-utils";
import { IKey } from "aws-cdk-lib/aws-kms";
import * as blueprints from "@aws-quickstart/eks-blueprints";
import {
ProductStack,
ProductStackProps,
} from "aws-cdk-lib/aws-servicecatalog";
export class BlueprintBuilder extends blueprints.BlueprintBuilder {
public cloneConstruct(region?: string, account?: string): BlueprintBuilder {
return new BlueprintBuilder()
.withBlueprintProps(this.props)
.account(account ?? this.env.account)
.region(region ?? this.env.region);
}
public buildConstruct(
scope: ProductStack,
id: string,
stackProps?: ProductStackProps
): EksBlueprint {
return new EksBlueprint(
scope,
{ ...this.props, ...{ id } },
{ ...{ env: this.env }, ...stackProps }
);
}
public async buildConstructAsync(
scope: ProductStack,
id: string,
stackProps?: ProductStackProps
): Promise<EksBlueprint> {
return this.buildConstruct(scope, id, stackProps).waitForAsyncTasks();
}
}
/**
* Entry point to the platform provisioning. Creates a CFN stack based on the provided configuration
* and orchestrates provisioning of add-ons, teams and post deployment hooks.
*/
export class EksBlueprint extends Construct {
private asyncTasks: Promise<void | Construct[]>;
private clusterInfo: spi.ClusterInfo;
public static builder(): BlueprintBuilder {
return new BlueprintBuilder();
}
constructor(
scope: ProductStack,
blueprintProps: blueprints.EksBlueprintProps,
props?: ProductStackProps
) {
super(scope, blueprintProps.id);
this.validateInput(blueprintProps);
const resourceContext = this.provideNamedResources(scope, blueprintProps);
let vpcResource: IVpc | undefined = resourceContext.get(
spi.GlobalResources.Vpc
);
if (!vpcResource) {
vpcResource = resourceContext.add(
spi.GlobalResources.Vpc,
new VpcProvider()
);
}
let version = blueprintProps.version;
if (version == "auto") {
version = DEFAULT_VERSION;
}
let kmsKeyResource: IKey | undefined = resourceContext.get(
spi.GlobalResources.KmsKey
);
if (!kmsKeyResource && blueprintProps.useDefaultSecretEncryption != false) {
kmsKeyResource = resourceContext.add(
spi.GlobalResources.KmsKey,
new blueprints.CreateKmsKeyProvider()
);
}
blueprintProps = this.resolveDynamicProxies(
blueprintProps,
resourceContext
);
const clusterProvider =
blueprintProps.clusterProvider ??
new MngClusterProvider({
id: `${blueprintProps.name ?? blueprintProps.id}-ng`,
version,
});
this.clusterInfo = clusterProvider.createCluster(
this,
vpcResource!,
kmsKeyResource,
version,
blueprintProps.enableControlPlaneLogTypes
);
this.clusterInfo.setResourceContext(resourceContext);
if (blueprintProps.enableGitOpsMode == spi.GitOpsMode.APPLICATION) {
blueprints.ArgoGitOpsFactory.enableGitOps();
} else if (blueprintProps.enableGitOpsMode == spi.GitOpsMode.APP_OF_APPS) {
blueprints.ArgoGitOpsFactory.enableGitOpsAppOfApps();
}
const postDeploymentSteps = Array<spi.ClusterPostDeploy>();
for (let addOn of blueprintProps.addOns ?? []) {
// must iterate in the strict order
const result = addOn.deploy(this.clusterInfo);
if (result) {
const addOnKey = utils.getAddOnNameOrId(addOn);
this.clusterInfo.addScheduledAddOn(
addOnKey,
result,
utils.isOrderedAddOn(addOn)
);
}
const postDeploy: any = addOn;
if ((postDeploy as spi.ClusterPostDeploy).postDeploy !== undefined) {
postDeploymentSteps.push(<spi.ClusterPostDeploy>postDeploy);
}
}
const scheduledAddOns = this.clusterInfo.getAllScheduledAddons();
const addOnKeys = [...scheduledAddOns.keys()];
const promises = scheduledAddOns.values();
this.asyncTasks = Promise.all(promises).then((constructs) => {
constructs.forEach((construct, index) => {
this.clusterInfo.addProvisionedAddOn(addOnKeys[index], construct);
});
if (blueprintProps.teams != null) {
for (let team of blueprintProps.teams) {
team.setup(this.clusterInfo);
}
}
for (let step of postDeploymentSteps) {
step.postDeploy(this.clusterInfo, blueprintProps.teams ?? []);
}
});
this.asyncTasks.catch((err) => {
console.error(err);
throw new Error(err);
});
}
/**
* Since constructor cannot be marked as async, adding a separate method to wait
* for async code to finish.
* @returns Promise that resolves to the blueprint
*/
public async waitForAsyncTasks(): Promise<EksBlueprint> {
if (this.asyncTasks) {
return this.asyncTasks.then(() => {
return this;
});
}
return Promise.resolve(this);
}
/**
* This method returns all the constructs produced by during the cluster creation (e.g. add-ons).
* May be used in testing for verification.
* @returns cluster info object
*/
getClusterInfo(): spi.ClusterInfo {
return this.clusterInfo;
}
private provideNamedResources(
scope: ProductStack,
blueprintProps: EksBlueprintProps
): spi.ResourceContext {
const result = new spi.ResourceContext(scope, blueprintProps);
for (let [key, value] of blueprintProps.resourceProviders ?? []) {
result.add(key, value);
}
return result;
}
/**
* Resolves all dynamic proxies, that substitutes resource provider proxies with the resolved values.
* @param blueprintProps
* @param resourceContext
* @returns a copy of blueprint props with resolved values
*/
private resolveDynamicProxies(
blueprintProps: EksBlueprintProps,
resourceContext: spi.ResourceContext
): EksBlueprintProps {
return utils.cloneDeep(blueprintProps, (value) => {
return utils.resolveTarget(value, resourceContext);
});
}
/**
* Validates input against basic defined constraints.
* @param blueprintProps
*/
private validateInput(blueprintProps: EksBlueprintProps) {
const teamNames = new Set<string>();
constraints.validateConstraints(
new BlueprintPropsConstraints(),
EksBlueprintProps.name,
blueprintProps
);
if (blueprintProps.teams) {
blueprintProps.teams.forEach((e) => {
if (teamNames.has(e.name)) {
throw new Error(`Team ${e.name} is registered more than once`);
}
teamNames.add(e.name);
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment