From 11c23b976b2e604ff30136917fb6fa7f783926f7 Mon Sep 17 00:00:00 2001 From: Toby Buckley Date: Thu, 9 May 2024 10:11:07 -0700 Subject: [PATCH 1/2] updated telemetry with tags --- src/control-plane/auth/cognito-auth.ts | 2 + src/control-plane/billing/billing-provider.ts | 3 +- src/control-plane/control-plane-api.ts | 3 +- src/control-plane/control-plane.ts | 4 +- .../firehose-ingestor-aggregator.ts | 2 + src/control-plane/services.ts | 3 +- src/control-plane/tables.ts | 3 +- .../tenant-config/tenant-config-service.ts | 2 + src/core-app-plane/bash-job-orchestrator.ts | 2 + src/core-app-plane/bash-job-runner.ts | 3 ++ src/core-app-plane/core-app-plane.ts | 4 +- src/utils/event-manager.ts | 2 + src/utils/utils.ts | 41 +++++++++++++++++- test/control-plane.test.ts | 21 --------- test/utils.test.ts | 43 +++++++++++++++++++ 15 files changed, 107 insertions(+), 31 deletions(-) create mode 100644 test/utils.test.ts diff --git a/src/control-plane/auth/cognito-auth.ts b/src/control-plane/auth/cognito-auth.ts index 8319b41..65e81d5 100644 --- a/src/control-plane/auth/cognito-auth.ts +++ b/src/control-plane/auth/cognito-auth.ts @@ -16,6 +16,7 @@ import { Runtime, IFunction, LayerVersion } from 'aws-cdk-lib/aws-lambda'; import { NagSuppressions } from 'cdk-nag'; import { Construct } from 'constructs'; import { IAuth } from './auth-interface'; +import { addTemplateTag } from '../../utils'; /** * Properties for the CognitoAuth construct. @@ -105,6 +106,7 @@ export class CognitoAuth extends Construct implements IAuth { constructor(scope: Construct, id: string, props: CognitoAuthProps) { super(scope, id); + addTemplateTag(this, 'CognitoAuth'); const idpName = 'COGNITO'; const systemAdminRoleName = props.systemAdminRoleName ?? 'SystemAdmin'; diff --git a/src/control-plane/billing/billing-provider.ts b/src/control-plane/billing/billing-provider.ts index eef0a74..e10fb21 100644 --- a/src/control-plane/billing/billing-provider.ts +++ b/src/control-plane/billing/billing-provider.ts @@ -9,7 +9,7 @@ import { IFunction } from 'aws-cdk-lib/aws-lambda'; import { NagSuppressions } from 'cdk-nag'; import { Construct } from 'constructs'; import { IBilling, IFunctionTrigger } from './billing-interface'; -import { DetailType, IEventManager } from '../../utils'; +import { DetailType, IEventManager, addTemplateTag } from '../../utils'; /** * Encapsulates the list of properties for a BillingProvider. @@ -54,6 +54,7 @@ export class BillingProvider extends Construct { */ constructor(scope: Construct, id: string, props: BillingProviderProps) { super(scope, id); + addTemplateTag(this, 'BillingProvider'); this.createEventTarget( props.eventManager, diff --git a/src/control-plane/control-plane-api.ts b/src/control-plane/control-plane-api.ts index e85c644..f1544eb 100644 --- a/src/control-plane/control-plane-api.ts +++ b/src/control-plane/control-plane-api.ts @@ -12,6 +12,7 @@ import { NagSuppressions } from 'cdk-nag'; import { Construct } from 'constructs'; import { IAuth } from './auth/auth-interface'; import { Services } from './services'; +import { addTemplateTag } from '../utils'; export interface ControlPlaneAPIProps { readonly services: Services; @@ -26,7 +27,7 @@ export class ControlPlaneAPI extends Construct { public readonly tenantUpdateServiceTarget: targets.ApiGateway; constructor(scope: Construct, id: string, props: ControlPlaneAPIProps) { super(scope, id); - + addTemplateTag(this, 'ControlPlaneAPI'); let options: any = { defaultCorsPreflightOptions: { allowOrigins: apigateway.Cors.ALL_ORIGINS, diff --git a/src/control-plane/control-plane.ts b/src/control-plane/control-plane.ts index 4acf0e3..eb19cc0 100644 --- a/src/control-plane/control-plane.ts +++ b/src/control-plane/control-plane.ts @@ -11,7 +11,7 @@ import { Services } from './services'; import { Tables } from './tables'; import { TenantConfigService } from './tenant-config/tenant-config-service'; import { DestroyPolicySetter } from '../cdk-aspect/destroy-policy-setter'; -import { setTemplateDesc, DetailType, EventManager, IEventManager } from '../utils'; +import { addTemplateTag, DetailType, EventManager, IEventManager } from '../utils'; export interface ControlPlaneProps { /** @@ -55,7 +55,7 @@ export class ControlPlane extends Construct { constructor(scope: Construct, id: string, props: ControlPlaneProps) { super(scope, id); - setTemplateDesc(this, 'SaaS Builder Toolkit - CoreApplicationPlane (uksb-1tupboc57)'); + addTemplateTag(this, 'ControlPlane'); cdk.Aspects.of(this).add(new DestroyPolicySetter()); diff --git a/src/control-plane/ingestor-aggregator/firehose-ingestor-aggregator.ts b/src/control-plane/ingestor-aggregator/firehose-ingestor-aggregator.ts index 10af423..3edad7a 100644 --- a/src/control-plane/ingestor-aggregator/firehose-ingestor-aggregator.ts +++ b/src/control-plane/ingestor-aggregator/firehose-ingestor-aggregator.ts @@ -13,6 +13,7 @@ import * as s3n from 'aws-cdk-lib/aws-s3-notifications'; import { NagSuppressions } from 'cdk-nag'; import { Construct } from 'constructs'; import { IDataIngestorAggregator } from './ingestor-aggregator-interface'; +import { addTemplateTag } from '../../utils'; /** * Encapsulates the list of properties for a FirehoseAggregator construct. @@ -68,6 +69,7 @@ export class FirehoseAggregator extends Construct implements IDataIngestorAggreg constructor(scope: Construct, id: string, props: FirehoseAggregatorProps) { super(scope, id); + addTemplateTag(this, 'FirehoseAggregator'); const serviceName = 'FirehoseAggregator'; diff --git a/src/control-plane/services.ts b/src/control-plane/services.ts index 4eee0f4..3ca745b 100644 --- a/src/control-plane/services.ts +++ b/src/control-plane/services.ts @@ -9,7 +9,7 @@ import { Runtime, LayerVersion, Function } from 'aws-cdk-lib/aws-lambda'; import { NagSuppressions } from 'cdk-nag'; import { Construct } from 'constructs'; import { Tables } from './tables'; -import { DetailType, IEventManager } from '../utils'; +import { DetailType, IEventManager, addTemplateTag } from '../utils'; export interface ServicesProps { readonly tables: Tables; @@ -21,6 +21,7 @@ export class Services extends Construct { constructor(scope: Construct, id: string, props: ServicesProps) { super(scope, id); + addTemplateTag(this, 'Services'); const tenantManagementExecRole = new Role(this, 'tenantManagementExecRole', { assumedBy: new ServicePrincipal('lambda.amazonaws.com'), diff --git a/src/control-plane/tables.ts b/src/control-plane/tables.ts index 2ee1e04..bd10686 100644 --- a/src/control-plane/tables.ts +++ b/src/control-plane/tables.ts @@ -3,6 +3,7 @@ import { Table, AttributeType, ProjectionType } from 'aws-cdk-lib/aws-dynamodb'; import { Construct } from 'constructs'; +import { addTemplateTag } from '../utils'; export class Tables extends Construct { public readonly tenantDetails: Table; @@ -14,7 +15,7 @@ export class Tables extends Construct { public readonly tenantIdColumn: string = 'tenantId'; constructor(scope: Construct, id: string) { super(scope, id); - + addTemplateTag(this, 'Tables'); this.tenantDetails = new Table(this, 'TenantDetails', { partitionKey: { name: this.tenantIdColumn, type: AttributeType.STRING }, pointInTimeRecovery: true, diff --git a/src/control-plane/tenant-config/tenant-config-service.ts b/src/control-plane/tenant-config/tenant-config-service.ts index 018ad38..a4b1e50 100644 --- a/src/control-plane/tenant-config/tenant-config-service.ts +++ b/src/control-plane/tenant-config/tenant-config-service.ts @@ -8,6 +8,7 @@ import { Table } from 'aws-cdk-lib/aws-dynamodb'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import { NagSuppressions } from 'cdk-nag'; import { Construct } from 'constructs'; +import { addTemplateTag } from '../../utils'; export interface TenantConfigServiceProps { readonly tenantDetails: Table; @@ -20,6 +21,7 @@ export class TenantConfigService extends Construct { public readonly tenantConfigServiceLambda: lambda.Function; constructor(scope: Construct, id: string, props: TenantConfigServiceProps) { super(scope, id); + addTemplateTag(this, 'TenantConfigService'); // https://docs.powertools.aws.dev/lambda/python/2.31.0/#lambda-layer const lambdaPowerToolsLayerARN = `arn:aws:lambda:${ diff --git a/src/core-app-plane/bash-job-orchestrator.ts b/src/core-app-plane/bash-job-orchestrator.ts index 1287420..8ea82ce 100644 --- a/src/core-app-plane/bash-job-orchestrator.ts +++ b/src/core-app-plane/bash-job-orchestrator.ts @@ -12,6 +12,7 @@ import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; import { NagSuppressions } from 'cdk-nag'; import { Construct } from 'constructs'; import { BashJobRunner } from './bash-job-runner'; +import { addTemplateTag } from '../utils'; /** * Encapsulates the list of properties for a BashJobOrchestrator. @@ -67,6 +68,7 @@ export class BashJobOrchestrator extends Construct { constructor(scope: Construct, id: string, props: BashJobOrchestratorProps) { super(scope, id); + addTemplateTag(this, 'BashJobOrchestrator'); const eventSource = props.eventSource; const detailType = props.detailType; diff --git a/src/core-app-plane/bash-job-runner.ts b/src/core-app-plane/bash-job-runner.ts index e17934c..09c06e1 100644 --- a/src/core-app-plane/bash-job-runner.ts +++ b/src/core-app-plane/bash-job-runner.ts @@ -8,6 +8,7 @@ import * as iam from 'aws-cdk-lib/aws-iam'; import * as kms from 'aws-cdk-lib/aws-kms'; import { NagSuppressions } from 'cdk-nag'; import { Construct } from 'constructs'; +import { addTemplateTag } from '../utils'; /** * Encapsulates the list of properties for a BashJobRunner. @@ -75,6 +76,8 @@ export class BashJobRunner extends Construct { constructor(scope: Construct, id: string, props: BashJobRunnerProps) { super(scope, id); + addTemplateTag(this, 'BashJobRunner'); + const environmentVariablesOverride: { name: string; value: string; diff --git a/src/core-app-plane/core-app-plane.ts b/src/core-app-plane/core-app-plane.ts index df49d4d..3be6028 100644 --- a/src/core-app-plane/core-app-plane.ts +++ b/src/core-app-plane/core-app-plane.ts @@ -6,7 +6,7 @@ import * as iam from 'aws-cdk-lib/aws-iam'; import { Construct } from 'constructs'; import { BashJobOrchestrator } from './bash-job-orchestrator'; import { BashJobRunner } from './bash-job-runner'; -import { EventManager, IEventManager, DetailType, setTemplateDesc } from '../utils'; +import { EventManager, IEventManager, DetailType, addTemplateTag } from '../utils'; /** * Encapsulates the list of properties for a CoreApplicationPlaneJobRunner. @@ -95,7 +95,7 @@ export class CoreApplicationPlane extends Construct { constructor(scope: Construct, id: string, props: CoreApplicationPlaneProps) { super(scope, id); - setTemplateDesc(this, 'SaaS Builder Toolkit - CoreApplicationPlane (uksb-1tupboc57)'); + addTemplateTag(this, 'CoreApplicationPlane'); this.eventManager = props.eventManager ?? new EventManager(this, 'EventManager'); const eventBus = EventBus.fromEventBusArn(this, 'EventBus', this.eventManager.busArn); diff --git a/src/utils/event-manager.ts b/src/utils/event-manager.ts index 636c2fd..dafdabd 100644 --- a/src/utils/event-manager.ts +++ b/src/utils/event-manager.ts @@ -4,6 +4,7 @@ import { IEventBus, Rule, IRuleTarget, EventBus } from 'aws-cdk-lib/aws-events'; import { IGrantable } from 'aws-cdk-lib/aws-iam'; import { Construct } from 'constructs'; +import { addTemplateTag } from './utils'; /** * Provides an easy way of accessing event detail types. @@ -221,6 +222,7 @@ export class EventManager extends Construct implements IEventManager { constructor(scope: Construct, id: string, props?: EventManagerProps) { super(scope, id); + addTemplateTag(this, 'EventManager'); this.eventBus = props?.eventBus ?? new EventBus(this, 'SbtEventBus'); this.busName = this.eventBus.eventBusName; this.busArn = this.eventBus.eventBusArn; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index c7695c4..f31c41a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -4,8 +4,45 @@ import { Stack } from 'aws-cdk-lib'; import { Construct } from 'constructs'; -export const setTemplateDesc = (construct: Construct, sbtDesc: string) => { +export const addTemplateTag = (construct: Construct, tag: string) => { const stackDesc = Stack.of(construct).templateOptions.description; - let description = stackDesc === undefined ? sbtDesc : stackDesc + ' - ' + sbtDesc; + const baseTelemetry = 'SaaS Builder Toolkit (uksb-1tupboc57)'; + let description = stackDesc; + // There is no description, just make it telemetry + tags + if (stackDesc === undefined) { + description = appendTagToDescription(baseTelemetry, tag); + } + // There is a description, and it doesn't contain telemetry. We need to append telemetry + tags to it + else if (!stackDesc.includes(baseTelemetry)) { + description = appendTagToDescription(`${stackDesc} - ${baseTelemetry}`, tag); + } + // There is a telemetry description already + else { + description = appendTagToDescription(stackDesc, tag); + } Stack.of(construct).templateOptions.description = description; }; + +const appendTagToDescription = (existingDescription: string, newTag: string): string => { + // Check if the existing description already has tags + if (existingDescription.includes('(Tags:')) { + // Extract the existing tags + const startIndex = existingDescription.indexOf('(Tags:') + 6; + const endIndex = existingDescription.lastIndexOf(')'); + const existingTags = existingDescription.substring(startIndex, endIndex).split(', '); + + // Check if the new tag already exists + if (!existingTags.includes(newTag)) { + // Append the new tag to the existing tags + existingTags.push(newTag); + const newDescription = `${existingDescription.substring(0, startIndex)}${existingTags.join(', ')})`; + return newDescription; + } else { + // The new tag already exists, return the original description + return existingDescription; + } + } else { + // Append the new tag to the description + return `${existingDescription} (Tags: ${newTag})`; + } +}; diff --git a/test/control-plane.test.ts b/test/control-plane.test.ts index 9644709..45eaa77 100644 --- a/test/control-plane.test.ts +++ b/test/control-plane.test.ts @@ -91,27 +91,6 @@ describe('ControlPlane Targets', () => { }); }); -describe('ControlPlane description', () => { - it('should have a fixed template description, when the containing stack does not have description', () => { - const stackWithoutDescription = new TestStack(app, 'stackWithoutDescription', { - systemAdminEmail: 'test@example.com', - }); - const actual = stackWithoutDescription.templateOptions.description; - const expected = 'SaaS Builder Toolkit - CoreApplicationPlane (uksb-1tupboc57)'; - expect(actual).toStrictEqual(expected); - }); - - it('should have a concatenated template description, when the containing stack has an existing desc', () => { - const stackWithDescription = new TestStack(app, 'stackWithDescription', { - systemAdminEmail: 'test@example.com', - description: 'ABC', - }); - const actual = stackWithDescription.templateOptions.description; - const expected = 'ABC - SaaS Builder Toolkit - CoreApplicationPlane (uksb-1tupboc57)'; - expect(expected).toStrictEqual(actual); - }); -}); - describe('ControlPlane cloudwatch roles', () => { it('should create the cloudwatch IAM apigw push to cw role by default', () => { const stackWithLogging = new TestStack(new cdk.App(), 'stackWithLogging', { diff --git a/test/utils.test.ts b/test/utils.test.ts new file mode 100644 index 0000000..da84aa4 --- /dev/null +++ b/test/utils.test.ts @@ -0,0 +1,43 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { App, Stack } from 'aws-cdk-lib'; +import { addTemplateTag } from '../src/utils'; +const app = new App(); +const telemetryConst = 'SaaS Builder Toolkit (uksb-1tupboc57)'; + +describe('addTemplateTag', () => { + it('should append telemetry and tags to existing template description', () => { + const stackA = new Stack(app, 'TestStackA', { description: 'test' }); + const newTag = 'new-tag'; + const expectedDescription = `test - ${telemetryConst} (Tags: new-tag)`; + addTemplateTag(stackA, newTag); + expect(stackA.templateOptions.description).toBe(expectedDescription); + }); + + it('should append a new tag to the description if it does not have any tags', () => { + const stackB = new Stack(app, 'TestStackB'); + const newTag = 'new-tag'; + const expectedDescription = `${telemetryConst} (Tags: new-tag)`; + addTemplateTag(stackB, newTag); + expect(stackB.templateOptions.description).toBe(expectedDescription); + }); + + it('should append a new tag to the existing tags in the description', () => { + const existingDescription = `This is a sample description - ${telemetryConst} (Tags: tag1, tag2)`; + const stackC = new Stack(app, 'TestStackC', { description: existingDescription }); + const newTag = 'new-tag'; + const expectedDescription = `This is a sample description - ${telemetryConst} (Tags: tag1, tag2, new-tag)`; + addTemplateTag(stackC, newTag); + expect(stackC.templateOptions.description).toBe(expectedDescription); + }); + + it('should not append a new tag if it already exists in the description', () => { + const existingDescription = `This is a sample description - ${telemetryConst} (Tags: tag1, tag2, new-tag)`; + const stackD = new Stack(app, 'TestStackD', { description: existingDescription }); + const newTag = 'new-tag'; + const expectedDescription = `This is a sample description - ${telemetryConst} (Tags: tag1, tag2, new-tag)`; + addTemplateTag(stackD, newTag); + expect(stackD.templateOptions.description).toBe(expectedDescription); + }); +}); From a946b6b3c6ba66038d53fc073ad7008eea92ae4c Mon Sep 17 00:00:00 2001 From: Toby Buckley Date: Thu, 9 May 2024 14:10:56 -0700 Subject: [PATCH 2/2] pr suggestions --- src/control-plane/services.ts | 3 +-- src/utils/utils.ts | 2 +- test/utils.test.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/control-plane/services.ts b/src/control-plane/services.ts index 3ca745b..4eee0f4 100644 --- a/src/control-plane/services.ts +++ b/src/control-plane/services.ts @@ -9,7 +9,7 @@ import { Runtime, LayerVersion, Function } from 'aws-cdk-lib/aws-lambda'; import { NagSuppressions } from 'cdk-nag'; import { Construct } from 'constructs'; import { Tables } from './tables'; -import { DetailType, IEventManager, addTemplateTag } from '../utils'; +import { DetailType, IEventManager } from '../utils'; export interface ServicesProps { readonly tables: Tables; @@ -21,7 +21,6 @@ export class Services extends Construct { constructor(scope: Construct, id: string, props: ServicesProps) { super(scope, id); - addTemplateTag(this, 'Services'); const tenantManagementExecRole = new Role(this, 'tenantManagementExecRole', { assumedBy: new ServicePrincipal('lambda.amazonaws.com'), diff --git a/src/utils/utils.ts b/src/utils/utils.ts index f31c41a..2a1df19 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -6,7 +6,7 @@ import { Construct } from 'constructs'; export const addTemplateTag = (construct: Construct, tag: string) => { const stackDesc = Stack.of(construct).templateOptions.description; - const baseTelemetry = 'SaaS Builder Toolkit (uksb-1tupboc57)'; + const baseTelemetry = 'sbt-aws (uksb-1tupboc57)'; let description = stackDesc; // There is no description, just make it telemetry + tags if (stackDesc === undefined) { diff --git a/test/utils.test.ts b/test/utils.test.ts index da84aa4..d46a4e0 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -4,7 +4,7 @@ import { App, Stack } from 'aws-cdk-lib'; import { addTemplateTag } from '../src/utils'; const app = new App(); -const telemetryConst = 'SaaS Builder Toolkit (uksb-1tupboc57)'; +const telemetryConst = 'sbt-aws (uksb-1tupboc57)'; describe('addTemplateTag', () => { it('should append telemetry and tags to existing template description', () => {