diff --git a/README.md b/README.md index 8651234c..e803e4dd 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,7 @@ Patterns: snyk starter gmaestro + workloads-codecommit ``` - Bootstrap your CDK environment. diff --git a/bin/workloads-codecommit.ts b/bin/workloads-codecommit.ts new file mode 100644 index 00000000..2c853529 --- /dev/null +++ b/bin/workloads-codecommit.ts @@ -0,0 +1,6 @@ +import WorkloadsCodeCommitConstruct from '../lib/workloads-codecommit-construct'; +import { configureApp } from '../lib/common/construct-utils'; + +const app = configureApp(); + +new WorkloadsCodeCommitConstruct(app, 'workloads-codecommit'); diff --git a/docs/patterns/images/argocd-cc-workloads.png b/docs/patterns/images/argocd-cc-workloads.png new file mode 100644 index 00000000..ba4f8024 Binary files /dev/null and b/docs/patterns/images/argocd-cc-workloads.png differ diff --git a/docs/patterns/images/argocd-cc.png b/docs/patterns/images/argocd-cc.png new file mode 100644 index 00000000..2da385ad Binary files /dev/null and b/docs/patterns/images/argocd-cc.png differ diff --git a/docs/patterns/workloads-codecommit.md b/docs/patterns/workloads-codecommit.md new file mode 100644 index 00000000..b5544734 --- /dev/null +++ b/docs/patterns/workloads-codecommit.md @@ -0,0 +1,160 @@ +# EKS Cluster with ArgoCD and Workloads in private AWS CodeCommit repository + +## Objective + +This example shows how to provision an EKS cluster with: + +- ArgoCD +- Workloads deployed by ArgoCD +- Private AWS CodeCommit repository to store the configurations of workloads +- Setup to trigger ArgoCD projects sync on git push to AWS CodeCommit repository + +Pattern source: /lib/workloads-codecommit-construct/index.ts + +## Architecture + +![Architectural diagram](./images/argocd-cc.png) + +To better understand how ArgoCD works with EKS Blueprints, read the EKS Blueprints ArgoCD [Documentation](https://aws-quickstart.github.io/cdk-eks-blueprints/addons/argo-cd/) + +- After a push to AWS CodeCommit repository notification trigger calls AWS Lambda +- AWS Lambda calls ArgoCD webhook URL to trigger ArgoCD projects sync + +## Prerequisites + +Ensure that you have installed the following tools on your machine. + +1. [aws cli](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) +2. [kubectl](https://Kubernetes.io/docs/tasks/tools/) +3. [cdk](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_install) +4. [npm](https://docs.npmjs.com/cli/v8/commands/npm-install) +5. [jq](https://jqlang.github.io/jq/) +6. `make` + +## Deploy EKS Cluster with Amazon EKS Blueprints for CDK + +1. Clone the repository + +```sh +git clone https://github.com/aws-samples/cdk-eks-blueprints-patterns.git +cd cdk-eks-blueprints-patterns +``` + +2. Update npm + +```sh +npm install -g npm@latest +``` + +3. View patterns and deploy workloads-codecommit pattern + +```sh +make list +npx cdk bootstrap +make pattern workloads-codecommit deploy +``` + +## Verify the resources + +1. Run the update-kubeconfig command. You should be able to get the command from the CDK output message. More information can be found at https://aws-quickstart.github.io/cdk-eks-blueprints/getting-started/#cluster-access + +```sh +aws eks update-kubeconfig --name workloads-codecommit-blueprint --region --role-arn arn:aws:iam::xxxxxxxxx:role/workloads-codecommit-blue-workloadscodecommitbluepr-VH6YOKWPAt5H +``` + +2. Verify the resources created from the steps above. + +```bash +$ kubectl get po -n argocd +NAME READY STATUS RESTARTS AGE +blueprints-addon-argocd-application-controller-0 1/1 Running 0 1h +blueprints-addon-argocd-applicationset-controller-7b78c7fc5dmkx 1/1 Running 0 1h +blueprints-addon-argocd-dex-server-6cf94ddc54-p68pl 1/1 Running 0 1h +blueprints-addon-argocd-notifications-controller-6f6b7d95ckhf6p 1/1 Running 0 1h +blueprints-addon-argocd-redis-b8dbc7dc6-dvbkr 1/1 Running 0 1h +blueprints-addon-argocd-repo-server-66df7f448f-kvwmw 1/1 Running 0 1h +blueprints-addon-argocd-server-584db5f545-8xp48 1/1 Running 0 1h +``` + +## Get ArgoCD Url and credentials + +```bash +until kubectl get svc blueprints-addon-argocd-server -n argocd -o json | jq --raw-output '.status.loadBalancer.ingress[0].hostname' | grep -m 1 "elb.amazonaws.com"; do sleep 5 ; done; +export ARGOCD_SERVER=`kubectl get svc blueprints-addon-argocd-server -n argocd -o json | jq --raw-output '.status.loadBalancer.ingress[0].hostname'` +export CC_REPO_NAME=eks-blueprints-workloads-cc + +echo "ArgoCD URL: https://$ARGOCD_SERVER" +echo "ArgoCD server user: admin" +echo "ArgoCD admin password: $(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)" +``` + +## Create notification trigger from AWS CodeCommit push to ArgoCD Sync + +```bash +export LAMBDA_ARN=$(aws lambda get-function --function-name eks-blueprints-workloads-cc-webhook | jq -r .Configuration.FunctionArn) + +cat > trigger.json < = [ + new blueprints.NestedStackAddOn({ + builder: WorkloadsCodeCommitRepoStack.builder(userName, repoName), + id: repoName + "-codecommit-repo-nested-stack" + }), + new blueprints.SecretsStoreAddOn, + new blueprints.ArgoCDAddOn({ + bootstrapRepo: { + ...bootstrapRepo, + path: 'envs/dev' + }, + values: { + server: { + service: { + type: "LoadBalancer" + } + } + } + }) + ]; + + blueprints.EksBlueprint.builder() + .account(process.env.CDK_DEFAULT_ACCOUNT!) + .region(process.env.CDK_DEFAULT_REGION) + .addOns(...addOns) + + .version('auto') + .build(scope, stackId); + } +} diff --git a/lib/workloads-codecommit-construct/lambda/index.js b/lib/workloads-codecommit-construct/lambda/index.js new file mode 100644 index 00000000..7429be98 --- /dev/null +++ b/lib/workloads-codecommit-construct/lambda/index.js @@ -0,0 +1,52 @@ +/* eslint-env node */ +const https = require('https'); // eslint-disable-line + +/* webhook payload + { + "ref": "refs/heads/main", + "repository": { + "html_url": "https://git-codecommit.us-west-2.amazonaws.com/v1/repos/eks-blueprints-workloads-cc", + "default_branch": "main" + } + } +*/ + +exports.handler = async function(event) { + const eventSourceARNarray = event.Records[0].eventSourceARN.split(':'); + const repoName = eventSourceARNarray[eventSourceARNarray.length - 1]; + const ref = event.Records[0].codecommit.references[0].ref; + const refArray = ref.split('/'); + const branch = refArray[refArray.length - 1]; + const data = JSON.stringify({ + "ref": ref, + "repository": { + "html_url": "https://git-codecommit." + event.Records[0].awsRegion + ".amazonaws.com/v1/repos/" + repoName, + "default_branch": branch + } + }); + console.log(data); + + const options = { + hostname: event.Records[0].customData, + path: '/api/webhook', + method: 'POST', + port: 443, + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'push', + 'Content-Length': data.length, + }, + }; + + const promise = new Promise(function(resolve, reject) { + process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; + const req = https.request(options, (res) => { + resolve(res.statusCode); + }).on('error', (e) => { + reject(Error(e)); + }); + req.write(data); + req.end(); + }); + return promise; +}; \ No newline at end of file diff --git a/lib/workloads-codecommit-construct/workloads-codecommit-repo-stack.ts b/lib/workloads-codecommit-construct/workloads-codecommit-repo-stack.ts new file mode 100644 index 00000000..1b5be17c --- /dev/null +++ b/lib/workloads-codecommit-construct/workloads-codecommit-repo-stack.ts @@ -0,0 +1,54 @@ +import { Construct } from 'constructs'; +import { NestedStack, NestedStackProps, SecretValue } from 'aws-cdk-lib'; +import * as blueprints from '@aws-quickstart/eks-blueprints'; +import * as codecommit from 'aws-cdk-lib/aws-codecommit'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { CodeCommitCredentials } from './codecommit-credentials'; + +export default class WorkloadsCodeCommitRepoStack extends NestedStack { + public static builder(userName: string, repoName: string): blueprints.NestedStackBuilder { + return { + build(scope: Construct, id: string, props: NestedStackProps) { + return new WorkloadsCodeCommitRepoStack(scope, id, props, userName, repoName); + } + }; + } + + constructor(scope: Construct, id: string, props: NestedStackProps, userName: string, repoName: string) { + super(scope, id); + + const repo = new codecommit.Repository(this, repoName + '-codecommit-repo', { + repositoryName: repoName, + }); + + const user = new iam.User(this, userName + '-user-name', { + userName: userName, + }); + repo.grantPull(user); + + const credentials = new CodeCommitCredentials(this, "codecommit-credentials", user.userName); + credentials.node.addDependency(user); + + new secretsmanager.Secret(this, 'codecommit-secret', { + secretObjectValue: { + username: SecretValue.unsafePlainText(credentials.serviceUserName), + password: SecretValue.unsafePlainText(credentials.servicePassword), + url: SecretValue.unsafePlainText(repo.repositoryCloneUrlHttp) + }, + secretName: repoName + '-codecommit-secret' + }); + + const fn = new lambda.Function(this, repoName + '-webhook', { + runtime: lambda.Runtime.NODEJS_20_X, + functionName: repoName + '-webhook', + description: 'Webhook for ArgoCD on commit to AWS CodeCommit', + handler: 'index.handler', + code: lambda.Code.fromAsset("lib/workloads-codecommit-construct/lambda"), + }); + + const principal = new iam.ServicePrincipal('codecommit.amazonaws.com'); + fn.grantInvoke(principal); + } +}