Created
June 3, 2024 17:17
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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