diff --git a/lib/addons/ack/index.ts b/lib/addons/ack/index.ts index fd2cfc84f..a5c979c17 100644 --- a/lib/addons/ack/index.ts +++ b/lib/addons/ack/index.ts @@ -133,3 +133,6 @@ function populateDefaults(defaultProps: AckAddOnProps, props?: AckAddOnProps): A tempProps.saName = tempProps.saName ?? `${tempProps.chart}-sa`; return tempProps as AckAddOnProps; } + +// export at end since classes inside extend AckAddOn +export * from "./service"; \ No newline at end of file diff --git a/lib/addons/ack/service.ts b/lib/addons/ack/service.ts new file mode 100644 index 000000000..ff21980bd --- /dev/null +++ b/lib/addons/ack/service.ts @@ -0,0 +1,348 @@ +import { AckAddOnProps, AckAddOn } from "."; +import { supportsALL, supportsX86 } from "../../utils"; +import { AckServiceName } from "./serviceMappings"; + +export interface ServiceAckAddOnProps + extends Omit { } + +@supportsALL +export class ACMAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.ACM, + }; + super(ackProps); + } +} +@supportsALL +export class ACMPCAAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.ACMPCA, + }; + super(ackProps); + } +} +@supportsALL +export class APIGatewayV2AckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.APIGATEWAYV2, + }; + super(ackProps); + } +} +@supportsALL +export class ApplicationAutoScalingAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.APPLICATIONAUTOSCALING, + }; + super(ackProps); + } +} +@supportsALL +export class CloudtrailAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.CLOUDTRAIL, + }; + super(ackProps); + } +} +@supportsALL +export class CloudwatchAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.CLOUDWATCH, + }; + super(ackProps); + } +} +@supportsALL +export class CloudwatchLogsAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.CLOUDWATCHLOGS, + }; + super(ackProps); + } +} +@supportsALL +export class DynamoDBAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.DYNAMODB, + }; + super(ackProps); + } +} +@supportsALL +export class EC2AckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.EC2, + }; + super(ackProps); + } +} +@supportsALL +export class ECRAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.ECR, + }; + super(ackProps); + } +} +@supportsALL +export class EKSAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.EKS, + }; + super(ackProps); + } +} +@supportsALL +export class ElasticacheAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.ELASTICACHE, + }; + super(ackProps); + } +} +@supportsALL +export class ElasticSearchAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.ELASTICSEARCHSERVICE, + }; + super(ackProps); + } +} +@supportsALL +export class EMRContainersAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.EMRCONTAINERS, + }; + super(ackProps); + } +} +@supportsALL +export class EventBridgeAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.EVENTBRIDGE, + }; + super(ackProps); + } +} +@supportsALL +export class IAMAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.IAM, + }; + super(ackProps); + } +} +@supportsALL +export class KafkaAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.KAFKA, + }; + super(ackProps); + } +} +@supportsALL +export class KinesisAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.KINESIS, + }; + super(ackProps); + } +} +@supportsALL +export class KMSAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.KMS, + }; + super(ackProps); + } +} +@supportsALL +export class LambdaAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.LAMBDA, + }; + super(ackProps); + } +} +// per https://gallery.ecr.aws/aws-controllers-k8s/memorydb-chart on 04/05/2024 +@supportsX86 +export class MemoryDBAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.MEMORYDB, + }; + super(ackProps); + } +} +@supportsALL +export class MQAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.MQ, + }; + super(ackProps); + } +} +@supportsALL +export class OpensearchServiceAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.OPENSEARCHSERVICE, + }; + super(ackProps); + } +} +@supportsALL +export class PipesAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.PIPES, + }; + super(ackProps); + } +} +@supportsALL +export class PrometheusServiceAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.PROMETHEUSSERVICE, + }; + super(ackProps); + } +} +@supportsALL +export class RDSAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.RDS, + }; + super(ackProps); + } +} +@supportsALL +export class Route53AckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.ROUTE53, + }; + super(ackProps); + } +} +@supportsALL +export class Route53ResolverAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.ROUTE53RESOLVER, + }; + super(ackProps); + } +} +@supportsALL +export class S3AckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.S3, + }; + super(ackProps); + } +} +@supportsALL +export class SagemakerAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.SAGEMAKER, + }; + super(ackProps); + } +} +@supportsALL +export class SecretsManagerAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.SECRETSMANAGER, + }; + super(ackProps); + } +} +@supportsALL +export class SFNAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.SFN, + }; + super(ackProps); + } +} +@supportsALL +export class SNSAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.SNS, + }; + super(ackProps); + } +} +@supportsALL +export class SQSAckAddOn extends AckAddOn { + constructor(props?: ServiceAckAddOnProps) { + const ackProps: AckAddOnProps = { + ...props, + serviceName: AckServiceName.SQS, + }; + super(ackProps); + } +} diff --git a/test/service-ack.test.ts b/test/service-ack.test.ts new file mode 100644 index 000000000..0796362a8 --- /dev/null +++ b/test/service-ack.test.ts @@ -0,0 +1,148 @@ +import * as cdk from "aws-cdk-lib"; +import * as blueprints from "../lib"; + +import { AssertionError } from "assert"; +import { Template } from "aws-cdk-lib/assertions"; +import { AckServiceName } from "../lib"; + +const stackName = "stack-test"; + +const region = "us-west-1"; +const account = "123567891"; +const clusterVersion: "auto" | cdk.aws_eks.KubernetesVersion = "auto"; + +const serviceAddonSupportMapping: { [key in AckServiceName]: blueprints.AckAddOn } = { + [AckServiceName.ACM]: new blueprints.ACMAckAddOn(), + [AckServiceName.ACMPCA]: new blueprints.ACMPCAAckAddOn(), + [AckServiceName.APIGATEWAYV2]: new blueprints.APIGatewayV2AckAddOn(), + [AckServiceName.APPLICATIONAUTOSCALING]: new blueprints.ApplicationAutoScalingAckAddOn(), + [AckServiceName.CLOUDTRAIL]: new blueprints.CloudtrailAckAddOn(), + [AckServiceName.CLOUDWATCH]: new blueprints.CloudwatchAckAddOn(), + [AckServiceName.CLOUDWATCHLOGS]: new blueprints.CloudwatchLogsAckAddOn(), + [AckServiceName.DYNAMODB]: new blueprints.DynamoDBAckAddOn(), + [AckServiceName.EC2]: new blueprints.EC2AckAddOn(), + [AckServiceName.ECR]: new blueprints.ECRAckAddOn(), + [AckServiceName.EKS]: new blueprints.EKSAckAddOn(), + [AckServiceName.ELASTICACHE]: new blueprints.ElasticacheAckAddOn(), + [AckServiceName.ELASTICSEARCHSERVICE]: new blueprints.ElasticSearchAckAddOn(), + [AckServiceName.EMRCONTAINERS]: new blueprints.EMRContainersAckAddOn(), + [AckServiceName.EVENTBRIDGE]: new blueprints.EventBridgeAckAddOn(), + [AckServiceName.IAM]: new blueprints.IAMAckAddOn(), + [AckServiceName.KAFKA]: new blueprints.KafkaAckAddOn(), + [AckServiceName.KINESIS]: new blueprints.KinesisAckAddOn(), + [AckServiceName.KMS]: new blueprints.KMSAckAddOn(), + [AckServiceName.LAMBDA]: new blueprints.LambdaAckAddOn(), + [AckServiceName.MEMORYDB]: new blueprints.MemoryDBAckAddOn(), + [AckServiceName.MQ]: new blueprints.MQAckAddOn(), + [AckServiceName.OPENSEARCHSERVICE]: new blueprints.OpensearchServiceAckAddOn(), + [AckServiceName.PIPES]: new blueprints.PipesAckAddOn(), + [AckServiceName.PROMETHEUSSERVICE]: new blueprints.PrometheusServiceAckAddOn(), + [AckServiceName.RDS]: new blueprints.RDSAckAddOn(), + [AckServiceName.ROUTE53]: new blueprints.Route53AckAddOn(), + [AckServiceName.ROUTE53RESOLVER]: new blueprints.Route53ResolverAckAddOn(), + [AckServiceName.S3]: new blueprints.S3AckAddOn(), + [AckServiceName.SAGEMAKER]: new blueprints.SagemakerAckAddOn(), + [AckServiceName.SECRETSMANAGER]: new blueprints.SecretsManagerAckAddOn(), + [AckServiceName.SFN]: new blueprints.SFNAckAddOn(), + [AckServiceName.SNS]: new blueprints.SNSAckAddOn(), + [AckServiceName.SQS]: new blueprints.SQSAckAddOn(), +} + +async function getStack(builder: blueprints.BlueprintBuilder, addons: blueprints.ClusterAddOn[]): Promise { + const app = new cdk.App(); + return await builder.account(account) + .region(region) + .version(clusterVersion) + .addOns(...addons) + .buildAsync(app, stackName) +} + +describe("Unit tests for ServiceAckAddOn", () => { + describe("Stack creation", () => { + let armBuilder: blueprints.BlueprintBuilder, x86Builder: blueprints.BlueprintBuilder; + beforeEach(() => { + armBuilder = new blueprints.GravitonBuilder(); + x86Builder = blueprints.EksBlueprint.builder(); + }) + for (const [service, addon] of Object.entries(serviceAddonSupportMapping)) { + const chartName = blueprints.serviceMappings[service as AckServiceName]?.chart; + // list of AckServiceNames which do not support ARM + if (Object.values([ + AckServiceName.MEMORYDB + ]).includes(service as AckServiceName)) { + describe("ServiceAckAddOn that does not support ARM architectures: " + service, () => { + test("ARM", async () => { + try { + const stack = await getStack(armBuilder, [addon]); + expect(stack).toBeUndefined(); + } catch (error) { + expect(error).toBeInstanceOf(Error); + } + }); + test("X86", async () => { + const stack = await getStack(x86Builder, [addon]); + expect(stack).toBeDefined(); + + Template.fromStack(stack).hasResourceProperties("Custom::AWSCDK-EKS-HelmChart", { + Chart: chartName + }); + }); + }); + } else { + describe("ServiceAckAddOn that supports all architectures: " + service, () => { + test("ARM", async () => { + const stack = await getStack(armBuilder, [addon]); + expect(stack).toBeDefined(); + + Template.fromStack(stack).hasResourceProperties("Custom::AWSCDK-EKS-HelmChart", { + Chart: chartName + }); + }); + test("X86", async () => { + const stack = await getStack(x86Builder, [addon]); + expect(stack).toBeDefined(); + + Template.fromStack(stack).hasResourceProperties("Custom::AWSCDK-EKS-HelmChart", { + Chart: chartName + }); + }); + }); + } + } + describe("With mixed ServiceACKAddOns (ie 1 supports ARM, 1 does not", () => { + const services = [ + AckServiceName.MEMORYDB, + AckServiceName.S3 + ]; + // need more control over addons to avoid them both trying to create the namespace + const addons = [ + new blueprints.MemoryDBAckAddOn({ + createNamespace: true + }), + new blueprints.S3AckAddOn({ + createNamespace: false + }), + ]; + const chartNames = services.map((service) => { return blueprints.serviceMappings[service as AckServiceName]?.chart }); + + test("ARM", async () => { + try { + const stack = await getStack(armBuilder, addons); + expect(stack).toBeUndefined(); + } catch (error) { + expect(error).toBeInstanceOf(Error); + } + }); + test("X86", async () => { + const stack = await getStack(x86Builder, addons); + expect(stack).toBeDefined(); + + chartNames.forEach((chartName) => { + Template.fromStack(stack).hasResourceProperties("Custom::AWSCDK-EKS-HelmChart", { + Chart: chartName + }); + }); + }); + }) + }); +}); \ No newline at end of file