diff --git a/CHANGELOG.md b/CHANGELOG.md index df91a206b..eb6329a0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [6.3.1] - 2024-10-02 + +### Fixed + +- Base-64 encoded overlayWith call requiring strings in top/left options rather than numbers +- CloudFront anonymized metrics missing for deployments outside of us-east-1 + ## [6.3.0] - 2024-09-09 ### Added diff --git a/VERSION.txt b/VERSION.txt index e7e42a4b5..39ee137ba 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -6.3.0 \ No newline at end of file +6.3.1 \ No newline at end of file diff --git a/source/constructs/package-lock.json b/source/constructs/package-lock.json index 91eb1bc28..3f0daafba 100644 --- a/source/constructs/package-lock.json +++ b/source/constructs/package-lock.json @@ -1,12 +1,12 @@ { "name": "constructs", - "version": "6.3.0", + "version": "6.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "constructs", - "version": "6.3.0", + "version": "6.3.1", "license": "Apache-2.0", "dependencies": { "metrics-utils": "file:../metrics-utils", diff --git a/source/constructs/package.json b/source/constructs/package.json index e078459bb..71cb7ce36 100644 --- a/source/constructs/package.json +++ b/source/constructs/package.json @@ -1,6 +1,6 @@ { "name": "constructs", - "version": "6.3.0", + "version": "6.3.1", "description": "Serverless Image Handler Constructs", "license": "Apache-2.0", "author": { diff --git a/source/constructs/test/__snapshots__/constructs.test.ts.snap b/source/constructs/test/__snapshots__/constructs.test.ts.snap index 78ae817de..edd0040f0 100644 --- a/source/constructs/test/__snapshots__/constructs.test.ts.snap +++ b/source/constructs/test/__snapshots__/constructs.test.ts.snap @@ -81,7 +81,7 @@ exports[`Serverless Image Handler Stack Snapshot 1`] = ` "Config": { "AnonymousUsage": "Yes", "SolutionId": "S0ABC", - "Version": "v6.3.0", + "Version": "v6.3.1", }, }, }, @@ -408,7 +408,7 @@ exports[`Serverless Image Handler Stack Snapshot 1`] = ` "Solutions:ApplicationType": "AWS-Solutions", "Solutions:SolutionID": "S0ABC", "Solutions:SolutionName": "sih", - "Solutions:SolutionVersion": "v6.3.0", + "Solutions:SolutionVersion": "v6.3.1", }, }, "Type": "AWS::ServiceCatalogAppRegistry::Application", @@ -1277,7 +1277,7 @@ exports[`Serverless Image Handler Stack Snapshot 1`] = ` }, "S3Key": "Omitted to remove snapshot dependency on hash", }, - "Description": "sih (v6.3.0): Performs image edits and manipulations", + "Description": "sih (v6.3.1): Performs image edits and manipulations", "Environment": { "Variables": { "AUTO_WEBP": { @@ -1492,7 +1492,7 @@ exports[`Serverless Image Handler Stack Snapshot 1`] = ` { "Ref": "BackEndImageHandlerCloudFrontApiGatewayLambdaCloudFrontToApiGatewayCloudFrontDistribution03AA31B2", }, - ""},{"Name":"Region","Value":"Global"}],"MetricName":"Requests"},"Stat":"Sum","Period":604800},"Id":"id_", + ""},{"Name":"Region","Value":"Global"}],"MetricName":"Requests"},"Stat":"Sum","Period":604800},"region":"us-east-1","Id":"id_", { "Fn::Join": [ "_", @@ -1510,7 +1510,7 @@ exports[`Serverless Image Handler Stack Snapshot 1`] = ` { "Ref": "BackEndImageHandlerCloudFrontApiGatewayLambdaCloudFrontToApiGatewayCloudFrontDistribution03AA31B2", }, - ""},{"Name":"Region","Value":"Global"}],"MetricName":"BytesDownloaded"},"Stat":"Sum","Period":604800},"Id":"id_", + ""},{"Name":"Region","Value":"Global"}],"MetricName":"BytesDownloaded"},"Stat":"Sum","Period":604800},"region":"us-east-1","Id":"id_", { "Fn::Join": [ "_", @@ -1977,7 +1977,7 @@ exports[`Serverless Image Handler Stack Snapshot 1`] = ` }, "S3Key": "Omitted to remove snapshot dependency on hash", }, - "Description": "sih (v6.3.0): Custom resource", + "Description": "sih (v6.3.1): Custom resource", "Environment": { "Variables": { "RETRY_SECONDS": "5", @@ -2583,7 +2583,7 @@ exports[`Serverless Image Handler Stack Snapshot 1`] = ` "applicationType": "AWS-Solutions", "solutionID": "S0ABC", "solutionName": "sih", - "version": "v6.3.0", + "version": "v6.3.1", }, "Description": "Attribute group for solution information", "Name": { diff --git a/source/constructs/test/constructs.test.ts b/source/constructs/test/constructs.test.ts index 5716c5efb..bd61f06c9 100644 --- a/source/constructs/test/constructs.test.ts +++ b/source/constructs/test/constructs.test.ts @@ -11,14 +11,14 @@ test("Serverless Image Handler Stack Snapshot", () => { context: { solutionId: "SO0023", solutionName: "serverless-image-handler", - solutionVersion: "v6.3.0", + solutionVersion: "v6.3.1", }, }); const stack = new ServerlessImageHandlerStack(app, "TestStack", { solutionId: "S0ABC", solutionName: "sih", - solutionVersion: "v6.3.0", + solutionVersion: "v6.3.1", }); const template = Template.fromStack(stack); diff --git a/source/custom-resource/package-lock.json b/source/custom-resource/package-lock.json index 03cb8552e..f242c0bd5 100644 --- a/source/custom-resource/package-lock.json +++ b/source/custom-resource/package-lock.json @@ -1,12 +1,12 @@ { "name": "custom-resource", - "version": "6.3.0", + "version": "6.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "custom-resource", - "version": "6.3.0", + "version": "6.3.1", "license": "Apache-2.0", "dependencies": { "aws-sdk": "^2.1529.0", diff --git a/source/custom-resource/package.json b/source/custom-resource/package.json index 35c365d88..534173d4d 100644 --- a/source/custom-resource/package.json +++ b/source/custom-resource/package.json @@ -1,6 +1,6 @@ { "name": "custom-resource", - "version": "6.3.0", + "version": "6.3.1", "private": true, "description": "Serverless Image Handler custom resource", "license": "Apache-2.0", diff --git a/source/demo-ui/package-lock.json b/source/demo-ui/package-lock.json index 7e42c638d..d9952ca4c 100644 --- a/source/demo-ui/package-lock.json +++ b/source/demo-ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "demo-ui", - "version": "6.3.0", + "version": "6.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "demo-ui", - "version": "6.3.0", + "version": "6.3.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/source/demo-ui/package.json b/source/demo-ui/package.json index 289fbbd7e..315d25468 100644 --- a/source/demo-ui/package.json +++ b/source/demo-ui/package.json @@ -1,6 +1,6 @@ { "name": "demo-ui", - "version": "6.3.0", + "version": "6.3.1", "private": true, "description": "Serverless Image Handler demo ui", "license": "Apache-2.0", diff --git a/source/image-handler/image-handler.ts b/source/image-handler/image-handler.ts index 92efc18d2..9c90b0ef5 100644 --- a/source/image-handler/image-handler.ts +++ b/source/image-handler/image-handler.ts @@ -223,10 +223,15 @@ export class ImageHandler { * @param overlaySize the size of the overlay * @returns the calculated size */ - private calcOverlaySizeOption = (editSize: string | undefined, imageSize: number, overlaySize: number): number => { + private calcOverlaySizeOption = ( + editSize: string | number | undefined, + imageSize: number, + overlaySize: number + ): number => { let resultSize = NaN; if (editSize !== undefined) { + editSize = `${editSize}`; // if ends with p, it is a percentage if (editSize.endsWith("p")) { resultSize = parseInt(editSize.replace("p", "")); diff --git a/source/image-handler/package-lock.json b/source/image-handler/package-lock.json index 2601a3a44..0b5a770aa 100644 --- a/source/image-handler/package-lock.json +++ b/source/image-handler/package-lock.json @@ -1,12 +1,12 @@ { "name": "image-handler", - "version": "6.3.0", + "version": "6.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "image-handler", - "version": "6.3.0", + "version": "6.3.1", "license": "Apache-2.0", "dependencies": { "aws-sdk": "^2.1529.0", diff --git a/source/image-handler/package.json b/source/image-handler/package.json index 08be03214..ca940e5d7 100644 --- a/source/image-handler/package.json +++ b/source/image-handler/package.json @@ -1,6 +1,6 @@ { "name": "image-handler", - "version": "6.3.0", + "version": "6.3.1", "private": true, "description": "A Lambda function for performing on-demand image edits and manipulations.", "license": "Apache-2.0", diff --git a/source/image-handler/test/image-handler/overlay.spec.ts b/source/image-handler/test/image-handler/overlay.spec.ts index 357b6d733..79bbe77e8 100644 --- a/source/image-handler/test/image-handler/overlay.spec.ts +++ b/source/image-handler/test/image-handler/overlay.spec.ts @@ -387,6 +387,34 @@ describe("calcOverlaySizeOption", () => { expect(result).toEqual(50); }); + it("should return the specified parameter if param is a positive number and an integer", () => { + // Arrange + const imageSize = 100; + const editSize = 50; + const overlaySize = 50; + const imageHandler = new ImageHandler(s3Client, rekognitionClient); + + // Act + const result = imageHandler["calcOverlaySizeOption"](editSize, imageSize, overlaySize); + + // Assert + expect(result).toEqual(50); + }); + + it("should return the image size + specified parameter - overlay size if param is less than 0 and an integer", () => { + // Arrange + const imageSize = 100; + const editSize = -60; + const overlaySize = 50; + const imageHandler = new ImageHandler(s3Client, rekognitionClient); + + // Act + const result = imageHandler["calcOverlaySizeOption"](editSize, imageSize, overlaySize); + + // Assert + expect(result).toEqual(-10); + }); + it("should return the image size + specified parameter - overlay size if param is less than 0", () => { // Arrange const imageSize = 100; diff --git a/source/metrics-utils/lambda/helpers/client-helper.ts b/source/metrics-utils/lambda/helpers/client-helper.ts index 79bed704f..957ca56e2 100644 --- a/source/metrics-utils/lambda/helpers/client-helper.ts +++ b/source/metrics-utils/lambda/helpers/client-helper.ts @@ -7,10 +7,12 @@ import { CloudWatchLogsClient } from "@aws-sdk/client-cloudwatch-logs"; export class ClientHelper { private sqsClient: SQSClient; - private cwClient: CloudWatchClient; + private cwClients: {[key: string]: CloudWatchClient }; private cwLogsClient: CloudWatchLogsClient; - constructor() {} + constructor() { + this.cwClients = {}; + } getSqsClient(): SQSClient { if (!this.sqsClient) { @@ -19,11 +21,14 @@ export class ClientHelper { return this.sqsClient; } - getCwClient(): CloudWatchClient { - if (!this.cwClient) { - this.cwClient = new CloudWatchClient(); + getCwClient(region: string = "default"): CloudWatchClient { + if (region === "default") { + region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || "default" + } + if (!(region in this.cwClients)) { + this.cwClients[region] = region === "default" ? new CloudWatchClient({}) : new CloudWatchClient({ region }); } - return this.cwClient; + return this.cwClients[region]; } getCwLogsClient(): CloudWatchLogsClient { diff --git a/source/metrics-utils/lambda/helpers/metrics-helper.ts b/source/metrics-utils/lambda/helpers/metrics-helper.ts index a35f55bd6..7529800dc 100644 --- a/source/metrics-utils/lambda/helpers/metrics-helper.ts +++ b/source/metrics-utils/lambda/helpers/metrics-helper.ts @@ -18,7 +18,7 @@ import { StartQueryCommandInput, QueryDefinition, } from "@aws-sdk/client-cloudwatch-logs"; -import { EventBridgeQueryEvent, MetricPayload, MetricData, QueryProps, SQSEventBody, ExecutionDay } from "./types"; +import { EventBridgeQueryEvent, MetricPayload, MetricData, QueryProps, SQSEventBody, ExecutionDay, MetricDataProps } from "./types"; import { SQSEvent } from "aws-lambda"; import { ClientHelper } from "./client-helper"; import axios, { RawAxiosRequestConfig } from "axios"; @@ -35,22 +35,35 @@ export class MetricsHelper { } async getMetricsData(event: EventBridgeQueryEvent): Promise { - const metricsDataProps: MetricDataQuery[] = event["metrics-data-query"]; + const metricsDataProps: MetricDataProps[] = event["metrics-data-query"]; const endTime = new Date(event.time); - const input: GetMetricDataCommandInput = { - MetricDataQueries: metricsDataProps, - StartTime: new Date(endTime.getTime() - ((EXECUTION_DAY == ExecutionDay.DAILY ? 1 : 7) * 86400 * 1000)), // 7 or 1 day(s) previous - EndTime: endTime, - }; - return await this.fetchMetricsData(input); + const regionedMetricProps = {}; + for (const metric of metricsDataProps) { + const region = metric.region ?? "default"; + if (!regionedMetricProps[region]) regionedMetricProps[region] = []; + regionedMetricProps[region].push(metric); + } + let results: MetricData = {} + for (const region in regionedMetricProps) { + const metricProps = regionedMetricProps[region]; + const cloudFrontInput: GetMetricDataCommandInput = { + MetricDataQueries: metricProps, + StartTime: new Date(endTime.getTime() - ((EXECUTION_DAY == ExecutionDay.DAILY ? 1 : 7) * 86400 * 1000)), // 7 or 1 day(s) previous + EndTime: endTime, + }; + results = {...results, ...await this.fetchMetricsData(cloudFrontInput, region)}; + } + + return results; } - private async fetchMetricsData(input: GetMetricDataCommandInput): Promise { + private async fetchMetricsData(input: GetMetricDataCommandInput, region: string): Promise { let command = new GetMetricDataCommand(input); + let response: GetMetricDataCommandOutput; const results: MetricData = {}; do { - response = await this.clientHelper.getCwClient().send(command); + response = await this.clientHelper.getCwClient(region).send(command); console.info(response); input.MetricDataQueries?.forEach((item, index) => { diff --git a/source/metrics-utils/lambda/helpers/types.ts b/source/metrics-utils/lambda/helpers/types.ts index f06e5db08..72f7b61de 100644 --- a/source/metrics-utils/lambda/helpers/types.ts +++ b/source/metrics-utils/lambda/helpers/types.ts @@ -5,14 +5,15 @@ import { MetricDataQuery } from "@aws-sdk/client-cloudwatch"; import { StartQueryCommandInput } from "@aws-sdk/client-cloudwatch-logs"; import { QueryDefinitionProps } from "aws-cdk-lib/aws-logs"; import { EventBridgeEvent, SQSEvent } from "aws-lambda"; - export interface QueryProps extends Pick {} export interface EventBridgeQueryEvent extends Pick, "detail-type" | "time"> { - "metrics-data-query": MetricDataQuery[]; + "metrics-data-query": MetricDataProps[]; } export interface MetricDataProps - extends Pick {} + extends Pick { + region?: string + } export enum ExecutionDay { DAILY = "*", diff --git a/source/metrics-utils/lib/query-builders.ts b/source/metrics-utils/lib/query-builders.ts index 4e08e0e01..e4a055063 100644 --- a/source/metrics-utils/lib/query-builders.ts +++ b/source/metrics-utils/lib/query-builders.ts @@ -20,6 +20,7 @@ export function addLambdaInvocationCount(this: SolutionsMetrics, functionName: s Stat: "Sum", Period: period, }, + Id: undefined }); } @@ -48,6 +49,8 @@ export function addCloudFrontMetric( Stat: "Sum", Period: period, }, + region: "us-east-1", + Id: undefined }); } diff --git a/source/metrics-utils/test/lambda/helpers/client-helper.spec.ts b/source/metrics-utils/test/lambda/helpers/client-helper.spec.ts index 3e5b659e0..d91a60224 100644 --- a/source/metrics-utils/test/lambda/helpers/client-helper.spec.ts +++ b/source/metrics-utils/test/lambda/helpers/client-helper.spec.ts @@ -55,6 +55,28 @@ describe("ClientHelper", () => { expect(CloudWatchClient).toHaveBeenCalledTimes(1); }); + it("should return different CloudWatchClient instances on different provided regions", () => { + const cwClient1 = clientHelper.getCwClient(); + const cwClient2 = clientHelper.getCwClient("us-east-1"); + expect(cwClient1).not.toBe(cwClient2); + expect(CloudWatchClient).toHaveBeenCalledTimes(2); + }); + + it("should return identical CloudWatchClient instances when AWS_REGION is set", () => { + process.env.AWS_REGION = "us-east-1"; + const cwClient1 = clientHelper.getCwClient(); + const cwClient2 = clientHelper.getCwClient("us-east-1"); + expect(cwClient1).toBe(cwClient2); + expect(CloudWatchClient).toHaveBeenCalledTimes(1); + }); + + it("should return different CloudWatchClient instances when AWS_REGION is set", () => { + const cwClient1 = clientHelper.getCwClient("us-west-2"); + const cwClient2 = clientHelper.getCwClient("us-east-1"); + expect(cwClient1).not.toBe(cwClient2); + expect(CloudWatchClient).toHaveBeenCalledTimes(2); + }); + it("should initialize and return a CloudWatchLogsClient instance", () => { const cwLogsClient = clientHelper.getCwLogsClient(); expect(cwLogsClient).toBeInstanceOf(CloudWatchLogsClient); diff --git a/source/package-lock.json b/source/package-lock.json index eeef2b6c2..9d714da53 100644 --- a/source/package-lock.json +++ b/source/package-lock.json @@ -1,12 +1,12 @@ { "name": "source", - "version": "6.3.0", + "version": "6.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "source", - "version": "6.3.0", + "version": "6.3.1", "license": "Apache-2.0", "devDependencies": { "@types/node": "^20.10.4", diff --git a/source/package.json b/source/package.json index d246255a8..f23cd5b36 100644 --- a/source/package.json +++ b/source/package.json @@ -1,6 +1,6 @@ { "name": "source", - "version": "6.3.0", + "version": "6.3.1", "private": true, "description": "ESLint and prettier dependencies to be used within the solution", "license": "Apache-2.0", diff --git a/source/solution-utils/package-lock.json b/source/solution-utils/package-lock.json index dc3b2b290..b6e8b391f 100644 --- a/source/solution-utils/package-lock.json +++ b/source/solution-utils/package-lock.json @@ -1,12 +1,12 @@ { "name": "solution-utils", - "version": "6.3.0", + "version": "6.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "solution-utils", - "version": "6.3.0", + "version": "6.3.1", "license": "Apache-2.0", "devDependencies": { "@types/jest": "^29.5.5", diff --git a/source/solution-utils/package.json b/source/solution-utils/package.json index 7349c62f8..a2eb16319 100644 --- a/source/solution-utils/package.json +++ b/source/solution-utils/package.json @@ -1,6 +1,6 @@ { "name": "solution-utils", - "version": "6.3.0", + "version": "6.3.1", "private": true, "description": "Utilities to be used within this solution", "license": "Apache-2.0",