Skip to content

Commit

Permalink
Adding new pattern for Crossplane Argocd GitOps
Browse files Browse the repository at this point in the history
  • Loading branch information
ajpaws committed May 15, 2024
1 parent 21847d4 commit 4eb131d
Show file tree
Hide file tree
Showing 21 changed files with 1,588 additions and 63 deletions.
45 changes: 45 additions & 0 deletions bin/aws-addon-clusters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import {K8S_VERSIONS_DEV, MultiClusterOptions} from "./multi-cluster-options";
import {CapacityType, KubernetesVersion} from "aws-cdk-lib/aws-eks";
import MultiClusterPipelineConstruct from "./multi-cluster-pipeline";
import * as blueprints from "@aws-quickstart/eks-blueprints";
import * as eks from "aws-cdk-lib/aws-eks";
import * as ec2 from "aws-cdk-lib/aws-ec2";

const app = new cdk.App();

const account = process.env.CDK_DEFAULT_ACCOUNT ?? "";
const region = process.env.CDK_DEFAULT_REGION ?? "us-east-1";
const minSize = parseInt(process.env.NODEGROUP_MIN ?? "1");
const maxSize = parseInt(process.env.NODEGROUP_MAX ?? "3");
const desiredSize = parseInt(process.env.NODEGROUP_DESIRED ?? "1");
const gitHubSecret = process.env.GITHUB_SECRET ?? "cdk_blueprints_github_secret";

const env : MultiClusterOptions = {
account,
region,
minSize,
maxSize,
desiredSize,
gitHubSecret,
nodeGroupCapacityType: CapacityType.ON_DEMAND,
k8sVersions: K8S_VERSIONS_DEV // K8S_VERSIONS_PROD for full deploy
}

Check failure on line 28 in bin/aws-addon-clusters.ts

View workflow job for this annotation

GitHub Actions / build (18)

Missing semicolon


const mngProps: blueprints.MngClusterProviderProps = {
version: KubernetesVersion.V1_28,
instanceTypes: [ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE2)],
amiType: eks.NodegroupAmiType.AL2_X86_64,
desiredSize: 2,
maxSize: 3,
};

console.info("Running CDK with id: addon-tester" );
console.info("Running CDK with: " + JSON.stringify(env));

new MultiClusterPipelineConstruct().buildAsync(app, "addon-tester", env , mngProps).catch(
(e) => console.log("Pipeline construct failed because of error ", e)
);

54 changes: 54 additions & 0 deletions bin/get-ready-for-test-issues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as AWS from "@aws-sdk/client-secrets-manager";
import { Octokit } from '@octokit/rest'

Check failure on line 2 in bin/get-ready-for-test-issues.ts

View workflow job for this annotation

GitHub Actions / build (18)

Missing semicolon

export const READY_FOR_TEST= "Ready for test";

/**
* Invoke with
* @param region process.env.CDK_DEFAULT_REGION
* @param secretName process.env.GITHUB_SECRET
* @param repo "jalawala"
* @param owner "aws-eks-addon-publication"
*/
export async function getReadyForTestAddons(region: string, secretName: string, repo: string, owner: string){
const issues = await getReadyForTestIssues(region, secretName, repo, owner) as Issue[];
// TODO do something with this addon
issues.forEach(issue => console.log(issue.number + ", " + issue.body));
}

async function getReadyForTestIssues(region: string, secretName: string, repo: string, owner: string){
const sm = new AWS.SecretsManager({region});

const accessToken = await getGitHubAccessToken(sm, secretName);
const octokit = new Octokit({ auth: accessToken });

const getIssuesRequest = {
headers: {
'X-GitHub-Api-Version': '2022-11-28'
},
owner,
repo,
labels: READY_FOR_TEST
};

const responsePromise = octokit.request("GET /repos/{owner}/{repo}/issues", getIssuesRequest);

Check failure on line 34 in bin/get-ready-for-test-issues.ts

View workflow job for this annotation

GitHub Actions / build (18)

Expected indentation of 4 spaces but found 3

return responsePromise
.then((response)=> response.data as Issue[])
.catch((error)=>{console.error(`Create issue error: ${error}`)})

Check failure on line 38 in bin/get-ready-for-test-issues.ts

View workflow job for this annotation

GitHub Actions / build (18)

Missing semicolon

Check failure on line 38 in bin/get-ready-for-test-issues.ts

View workflow job for this annotation

GitHub Actions / build (18)

Missing semicolon
}

type Issue = {
number: number;
body: string;
}

async function getGitHubAccessToken(sm : AWS.SecretsManager, secretName : string) {
const secret = await sm.getSecretValue({ SecretId: secretName });
const secretString = secret.SecretString;
if (typeof secretString === 'string') {
return secretString;
} else {
throw new Error('SecretString is not a string.');
}
}
23 changes: 23 additions & 0 deletions bin/multi-cluster-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {CapacityType, KubernetesVersion} from "aws-cdk-lib/aws-eks";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as eks from "aws-cdk-lib/aws-eks";

export const K8S_VERSIONS_PROD : KubernetesVersion[] = [KubernetesVersion.V1_25, KubernetesVersion.V1_26,
KubernetesVersion.V1_27, KubernetesVersion.V1_28]; // KubernetesVersion.V1_29 // when the time comes
//export const K8S_VERSIONS_DEV : KubernetesVersion[] = [ KubernetesVersion.V1_26 ,KubernetesVersion.V1_27, KubernetesVersion.V1_28, KubernetesVersion.of("1.29")];

export const K8S_VERSIONS_DEV : KubernetesVersion[] = [ KubernetesVersion.of("1.29")];


export interface MultiClusterOptions {
readonly account: string;
readonly region: string;
minSize?: number;
maxSize?: number;
desiredSize?: number;
gitHubSecret?: string;
nodeGroupCapacityType: CapacityType;
instanceTypes?: ec2.InstanceType[];
amiType?: eks.NodegroupAmiType;
k8sVersions: KubernetesVersion[];
}
145 changes: 145 additions & 0 deletions bin/multi-cluster-pipeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { Construct } from "constructs";
import * as blueprints from '@aws-quickstart/eks-blueprints';
import {K8S_VERSIONS_DEV, MultiClusterOptions} from "./multi-cluster-options";
import {NodegroupAmiType} from "aws-cdk-lib/aws-eks";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import ManagementClusterBuilder from "../lib/crossplane-argocd-gitops/management-cluster-builder";
import {ProviderMgmtRoleTeam} from "../lib/crossplane-argocd-gitops/custom-addons/mgmt-role-teams";
import {GenericClusterProvider, LookupRoleProvider} from "@aws-quickstart/eks-blueprints";
import {IRole} from "aws-cdk-lib/aws-iam";
import * as iam from 'aws-cdk-lib/aws-iam';
import {ManagedNodeGroup} from "@aws-quickstart/eks-blueprints/dist/cluster-providers/types";

export default class MultiClusterPipelineConstruct {
async buildAsync(scope: Construct, id: string, props: MultiClusterOptions, mngProps: blueprints.MngClusterProviderProps) {
const k8sVersions = props.k8sVersions ?? K8S_VERSIONS_DEV;
const region :string = props.region;
const account : string = props.account;

const gitProps = {
owner :'jalawala',
secretName : props.gitHubSecret ?? 'github-access-eks-addon',
repoName : 'aws-addon-clusters-main',
revision : 'main' // use this to target a certain branch for deployment
};


await this.prevalidateSecrets(gitProps.secretName, region);

const addOns: Array<blueprints.ClusterAddOn> = [
new blueprints.ExternalsSecretsAddOn({
namespace: "external-secrets",
values: { webhook: { port: 9443 } }
})
]

Check failure on line 34 in bin/multi-cluster-pipeline.ts

View workflow job for this annotation

GitHub Actions / build (18)

Missing semicolon

const clusterProps: blueprints.MngClusterProviderProps = {
minSize: props.minSize,
maxSize: props.maxSize,
desiredSize: props.desiredSize,
nodeGroupCapacityType: props.nodeGroupCapacityType,
}

Check failure on line 41 in bin/multi-cluster-pipeline.ts

View workflow job for this annotation

GitHub Actions / build (18)

Missing semicolon

const stages : blueprints.StackStage[] = [];
const vpcProvider= new blueprints.VpcProvider();

const baseBlueprint = blueprints.EksBlueprint.builder()
.resourceProvider(blueprints.GlobalResources.Vpc, vpcProvider)
.resourceProvider('eks-connector-role', new LookupRoleProvider('eks-connector-role'))
.account(account)
.addOns(...addOns)
.teams(new ProviderMgmtRoleTeam(account))
.useDefaultSecretEncryption(true);

const mgmtCluster = new ManagementClusterBuilder(account, region)
.create(scope, 'management-cluster', mngProps)
.account(account)
.region(region)
.resourceProvider(blueprints.GlobalResources.Vpc, vpcProvider);

const mgmtStage = [{id: `mgmt-cluster-stage` , stackBuilder: mgmtCluster}];

for(const k8sVersion of k8sVersions) {
baseBlueprint.version(k8sVersion);

const blueprintAMD = baseBlueprint
.clusterProvider(
new GenericClusterProvider( {
version: k8sVersion,
mastersRole: blueprints.getNamedResource('eks-connector-role') as IRole,
managedNodeGroups : [addManagedNodeGroup( 'amd-tst-ng',{...clusterProps,
amiType : NodegroupAmiType.AL2_X86_64,
instanceTypes: [ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE)]})]
})
);
stages.push({
id: `amd-` + k8sVersion.version.replace(".", "-"),
stackBuilder : blueprintAMD.clone(props.region).id(`amd-` + k8sVersion.version.replace(".", "-"))
});

const blueprintARM = baseBlueprint
.clusterProvider(
new GenericClusterProvider( {
version: k8sVersion,
mastersRole: blueprints.getNamedResource('eks-connector-role') as IRole,
managedNodeGroups : [addManagedNodeGroup('arm-tst-ng',{...clusterProps,
amiType : NodegroupAmiType.AL2_ARM_64,
instanceTypes: [ec2.InstanceType.of(ec2.InstanceClass.M7G, ec2.InstanceSize.XLARGE)]})]
})
);
stages.push({
id: `arm-` + k8sVersion.version.replace(".", "-"),
stackBuilder : blueprintARM.clone(props.region).id(`arm-` + k8sVersion.version.replace(".", "-"))
});
}

blueprints.CodePipelineStack.builder()
.name(id)
.owner(gitProps.owner)
.codeBuildPolicies(
([
new iam.PolicyStatement({
resources: ["*"],
actions: [
"codebuild:*",
"sts:AssumeRole",
"secretsmanager:GetSecretValue",
"secretsmanager:ListSecrets",
"secretsmanager:DescribeSecret",
"cloudformation:*"
]
})
])
)
.repository({
targetRevision : gitProps.revision,

Check failure on line 115 in bin/multi-cluster-pipeline.ts

View workflow job for this annotation

GitHub Actions / build (18)

Expected indentation of 16 spaces but found 20
credentialsSecretName: gitProps.secretName,

Check failure on line 116 in bin/multi-cluster-pipeline.ts

View workflow job for this annotation

GitHub Actions / build (18)

Expected indentation of 16 spaces but found 20
repoUrl: gitProps.repoName

Check failure on line 117 in bin/multi-cluster-pipeline.ts

View workflow job for this annotation

GitHub Actions / build (18)

Expected indentation of 16 spaces but found 20
}
)
.wave({ id: `mgmt-cluster-stage`, stages: mgmtStage })
.wave({ id: `${id}-wave`, stages })
.build(scope, id, { env: { account, region } });
}

async prevalidateSecrets(secretName: string, region: string) {
try {
await blueprints.utils.validateSecret(secretName, region);
}
catch(error) {
throw new Error(`${secretName} secret must be setup in AWS Secrets Manager in ${region} for the GitHub pipeline.
* @see https://docs.aws.amazon.com/codepipeline/latest/userguide/GitHub-create-personal-token-CLI.html`);
}
}
}

function addManagedNodeGroup(id: string, clusterProps: blueprints.MngClusterProviderProps): ManagedNodeGroup {
return {
id,
minSize: clusterProps.minSize,
maxSize: clusterProps.maxSize,
amiType: clusterProps.amiType,
instanceTypes: clusterProps.instanceTypes,
desiredSize: clusterProps.desiredSize
};
}
Loading

0 comments on commit 4eb131d

Please sign in to comment.