Skip to content

Commit

Permalink
initial implementation of fargate profile
Browse files Browse the repository at this point in the history
  • Loading branch information
Elad Ben-Israel committed Dec 30, 2019
1 parent 9dd1776 commit 2cde740
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// tslint:disable: no-console
// eslint-disable-next-line import/no-extraneous-dependencies
import * as aws from 'aws-sdk';

const eks = new aws.EKS();

export async function onEvent(event: AWSLambda.CloudFormationCustomResourceEvent) {
switch (event.RequestType) {
case 'Create': return onCreate(event);
case 'Update': return onUpdate(event);
case 'Delete': return onDelete(event);
}
}

async function onCreate(event: AWSLambda.CloudFormationCustomResourceCreateEvent) {
const fargateProfileName = event.ResourceProperties.Config.fargateProfileName ?? generateProfileName(event);

const createFargateProfile: aws.EKS.CreateFargateProfileRequest = {
fargateProfileName,
...event.ResourceProperties.Config
};

const response = await eks.createFargateProfile(createFargateProfile).promise();

return {
PhysicalResourceId: response.fargateProfile?.fargateProfileName,
Data: {
fargateProfileArn: response.fargateProfile?.fargateProfileArn,
}
};
}

async function onUpdate(event: AWSLambda.CloudFormationCustomResourceUpdateEvent) {
// all updates require a replacement. as long as name is generated, we are good. if name is explicit, we cant update
return onCreate({ ...event, RequestType: 'Create' });
}

async function onDelete(event: AWSLambda.CloudFormationCustomResourceDeleteEvent) {
const deleteFargateProfile: aws.EKS.DeleteFargateProfileRequest = {
clusterName: event.ResourceProperties.Config.clusterName,
fargateProfileName: event.PhysicalResourceId!
};

await eks.deleteFargateProfile(deleteFargateProfile).promise();
}

export async function isComplete(event: AWSCDKAsyncCustomResource.IsCompleteRequest): Promise<AWSCDKAsyncCustomResource.IsCompleteResponse> {
const status = await getProfileStatus(event);

if (event.RequestType === 'Create' || event.RequestType === 'Update') {
return {
IsComplete: status === 'ACTIVE'
};
}

if (event.RequestType === 'Delete') {
return {
IsComplete: status === 'NOT_FOUND'
};
}

throw new Error(`Invalid request type ${event.RequestType}`);
}

function generateProfileName(event: AWSLambda.CloudFormationCustomResourceCreateEvent) {
return `${event.LogicalResourceId}-${event.RequestId}`;
}

async function getProfileStatus(event: AWSCDKAsyncCustomResource.IsCompleteRequest): Promise<aws.EKS.FargateProfileStatus | 'NOT_FOUND' | undefined> {
const describeFargateProfile: aws.EKS.DescribeFargateProfileRequest = {
clusterName: event.ResourceProperties.Config.clusterName,
fargateProfileName: event.PhysicalResourceId!
};

try {

console.log(JSON.stringify({ describeFargateProfile }, undefined, 2));
const profile = await eks.describeFargateProfile(describeFargateProfile).promise();
console.log('describeFargateProfile returned:', JSON.stringify(profile, undefined, 2));
const status = profile.fargateProfile?.status;

if (status === 'CREATE_FAILED' || status === 'DELETE_FAILED') {
throw new Error(status);
}

return status;
} catch (e) {
if (e.code === 'ResourceNotFoundException') {
console.log('received ResourceNotFoundException, this means the profile has been deleted (or never existed)');
return 'NOT_FOUND';
}

console.log('describeFargateProfile error:', e);
throw e;
}
}
161 changes: 161 additions & 0 deletions packages/@aws-cdk/aws-eks/lib/fargate-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import * as cfn from '@aws-cdk/aws-cloudformation';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import { Construct, Resource, Stack } from "@aws-cdk/core";
import * as cr from '@aws-cdk/custom-resources';
import * as path from 'path';
import { Cluster } from './cluster';

export interface FargateProfileOptions {
/**
* The name of the Fargate profile.
* @default - generated
*/
readonly fargateProfileName?: string;

/**
* The Amazon Resource Name (ARN) of the pod execution role to use for pods
* that match the selectors in the Fargate profile. The pod execution role
* allows Fargate infrastructure to register with your cluster as a node, and
* it provides read access to Amazon ECR image repositories.
*
* @see https://docs.aws.amazon.com/eks/latest/userguide/pod-execution-role.html
* @default - a role will be automatically created
*/
readonly podExecutionRole?: iam.IRole;

/**
* The selectors to match for pods to use this Fargate profile. Each selector
* must have an associated namespace. Optionally, you can also specify labels
* for a namespace.
*
* You may specify up to five selectors in a Fargate profile.
*/
readonly selectors?: Selector[];

/**
* The VPC from which to select subnets to launch your pods into.
*
* By default, all private subnets are selected. You can customize this using
* `subnetSelection`.
*
* @default - pods will not be placed into a VPC
*/
readonly vpc?: ec2.IVpc;

/**
* Select which subnets to launch your pods into. At this time, pods running
* on Fargate are not assigned public IP addresses, so only private subnets
* (with no direct route to an Internet Gateway) are allowed.
*
* @default - all private subnets of the VPC are selected.
*/
readonly subnetSelection?: ec2.SubnetSelection;

/**
* The metadata to apply to the Fargate profile to assist with categorization
* and organization. Each tag consists of a key and an optional value, both of
* which you define. Fargate profile tags do not propagate to any other
* resources associated with the Fargate profile, such as the pods that are
* scheduled with it.
*/
readonly tags?: { [name: string]: string };
}

export interface FargateProfileProps extends FargateProfileOptions {
/**
* The EKS cluster to apply the Fargate profile to.
*/
readonly cluster: Cluster;
}

export interface Selector {
/**
* The Kubernetes namespace that the selector should match.
*/
readonly namespace?: string;

/**
* The Kubernetes labels that the selector should match. A pod must contain
* all of the labels that are specified in the selector for it to be
* considered a match.
*/
readonly labels?: { [key: string]: string };
}

export class FargateProfile extends Resource {

/**
* @attribute
*/
public readonly fargateProfileArn: string;

/**
* @attribute
*/
public readonly fargateProfileName: string;

constructor(scope: Construct, id: string, props: FargateProfileProps) {
super(scope, id, {
physicalName: props.fargateProfileName
});

const role = props.podExecutionRole ?? new iam.Role(this, 'PodExecutionRole', {
assumedBy: new iam.ServicePrincipal('eks-fargate-pods.amazonaws.com'),
managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSFargatePodExecutionRolePolicy') ]
});

let subnets: string[] | undefined;
if (props.vpc) {
const selection: ec2.SubnetSelection = props.subnetSelection ?? { subnetType: ec2.SubnetType.PRIVATE };
subnets = props.vpc.selectSubnets(selection).subnetIds;
}

const resource = new cfn.CustomResource(this, 'Resource', {
provider: Provider.getOrCreate(this).provider,
resourceType: 'Custom::AWSCDK-EKS-FargateProfile',
properties: {
Config: {
clusterName: props.cluster.clusterName,
fargateProfileName: this.physicalName,
podExecutionRole: role.roleArn,
selectors: props.selectors,
subnets,
tags: props.tags
}
}
});

this.fargateProfileArn = resource.getAttString('fargateProfileArn');
this.fargateProfileName = resource.ref;
}
}

export class Provider extends Construct {

public static getOrCreate(scope: Construct) {
const stack = Stack.of(scope);
const uid = '@aws-cdk/aws-eks.FargateProfileResourceProvider';
return stack.node.tryFindChild(uid) as Provider || new Provider(stack, uid);
}

public readonly provider: cr.Provider;

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

this.provider = new cr.Provider(this, 'Provider', {
onEventHandler: new lambda.Function(this, 'OnEventHandler', {
code: lambda.Code.fromAsset(path.join(__dirname, 'fargate-profile-resource-handler')),
runtime: lambda.Runtime.NODEJS_12_X,
handler: 'index.onEvent'
}),
isCompleteHandler: new lambda.Function(this, 'IsCompleteHandler', {
code: lambda.Code.fromAsset(path.join(__dirname, 'fargate-profile-resource-handler')),
runtime: lambda.Runtime.NODEJS_12_X,
handler: 'index.isComplete'
})
});
}
}

0 comments on commit 2cde740

Please sign in to comment.