Skip to content

Commit

Permalink
Merge pull request #158 from ybezsonov/argocd-codecommit
Browse files Browse the repository at this point in the history
feat: add pattern for ArgoCD workloads in AWS CodeCommit
  • Loading branch information
shapirov103 authored Jan 9, 2024
2 parents 3aaf9af + 9fb1b55 commit b6ad0ca
Show file tree
Hide file tree
Showing 9 changed files with 374 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ Patterns:
snyk
starter
gmaestro
workloads-codecommit
```
- Bootstrap your CDK environment.
Expand Down
6 changes: 6 additions & 0 deletions bin/workloads-codecommit.ts
Original file line number Diff line number Diff line change
@@ -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');
Binary file added docs/patterns/images/argocd-cc-workloads.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/patterns/images/argocd-cc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 160 additions & 0 deletions docs/patterns/workloads-codecommit.md
Original file line number Diff line number Diff line change
@@ -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 <your 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 <<EOF
[
{
"destinationArn": "${LAMBDA_ARN}",
"branches": [],
"name": "${CC_REPO_NAME}-trigger",
"customData": "${ARGOCD_SERVER}",
"events": [
"all"
]
}
]
EOF

aws codecommit put-repository-triggers --repository-name $CC_REPO_NAME --triggers file://trigger.json --no-cli-pager
rm trigger.json
```

## Set AWS_REGION

```bash
export AWS_REGION=$(aws ec2 describe-availability-zones --output text --query 'AvailabilityZones[0].[RegionName]')
echo $AWS_REGION
```

## Populate AWS CodeCommit with Blueprint workloads Sample repository

```bash
pushd ..
git clone https://github.com/aws-samples/eks-blueprints-workloads.git
git clone codecommit::$AWS_REGION://$CC_REPO_NAME
cd $CC_REPO_NAME
git checkout -b main
cd ..
rsync -av eks-blueprints-workloads/ $CC_REPO_NAME --exclude .git
cd $CC_REPO_NAME
git add . && git commit -m "initial commit" && git push --set-upstream origin main
popd
```

ArgoCD will receive notification and will start sync.

![ArgoCD sync](./images/argocd-cc-workloads.png)

## Destroy

To teardown and remove the resources created in this example:

1. Delete "bootstrap-apps" project in ArgoCD UI and wait until ArgoCD delete workloads

2. Delete deployed resources

```sh
cd cdk-eks-blueprints-patterns
make pattern workloads-codecommit destroy
```

3. Delete cloned repositories (`if necessary`)

```sh
pushd ..
rm -rf eks-blueprints-workloads-cc
rm -rf eks-blueprints-workloads
popd
```
43 changes: 43 additions & 0 deletions lib/workloads-codecommit-construct/codecommit-credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId, PhysicalResourceIdReference } from 'aws-cdk-lib/custom-resources';
import { Construct } from 'constructs';

export class CodeCommitCredentials extends Construct {
readonly serviceSpecificCredentialId: string;
readonly serviceName: string;
readonly serviceUserName: string;
readonly servicePassword: string;
readonly status: string;

constructor(scope: Construct, id: string, userName: string) {
super(scope, id);

const codeCommitCredentialsResponse = new AwsCustomResource(this, "codecommit-credentials-custom-resource", {
onCreate: {
service: "IAM",
action: "createServiceSpecificCredential",
parameters: {
ServiceName: "codecommit.amazonaws.com",
UserName: userName
},
physicalResourceId: PhysicalResourceId.fromResponse("ServiceSpecificCredential.ServiceSpecificCredentialId")
},
onDelete: {
service: "IAM",
action: "deleteServiceSpecificCredential",
parameters: {
ServiceSpecificCredentialId: new PhysicalResourceIdReference(),
UserName: userName,
}
},
policy: AwsCustomResourcePolicy.fromSdkCalls({
resources: AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});

this.serviceSpecificCredentialId = codeCommitCredentialsResponse.getResponseField("ServiceSpecificCredential.ServiceSpecificCredentialId");
this.serviceName = codeCommitCredentialsResponse.getResponseField("ServiceSpecificCredential.ServiceName");
this.serviceUserName = codeCommitCredentialsResponse.getResponseField("ServiceSpecificCredential.ServiceUserName");
this.servicePassword = codeCommitCredentialsResponse.getResponseField("ServiceSpecificCredential.ServicePassword");
this.status = codeCommitCredentialsResponse.getResponseField("ServiceSpecificCredential.Status");
}
}
58 changes: 58 additions & 0 deletions lib/workloads-codecommit-construct/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as blueprints from '@aws-quickstart/eks-blueprints';
import { Construct } from 'constructs';
import WorkloadsCodeCommitRepoStack from './workloads-codecommit-repo-stack';

/**
* Demonstrates how to use AWS CodeCommmit as a repository for ArgoCD workloads.
*/

export default class WorkloadsCodeCommitConstruct extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);

const region = process.env.CDK_DEFAULT_REGION!;

const userName = 'argocd-cc';
const repoName = 'eks-blueprints-workloads-cc';

const repoUrl = 'https://git-codecommit.' + region + '.amazonaws.com/v1/repos/' + repoName;

const stackId = `${id}-blueprint`;

const bootstrapRepo : blueprints.ApplicationRepository = {
repoUrl,
targetRevision: 'main',
credentialsSecretName: repoName + '-codecommit-secret',
credentialsType: 'TOKEN'
};

const addOns: Array<blueprints.ClusterAddOn> = [
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);
}
}
52 changes: 52 additions & 0 deletions lib/workloads-codecommit-construct/lambda/index.js
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit b6ad0ca

Please sign in to comment.