diff --git a/CHANGELOG.md b/CHANGELOG.md index ffb4f73..9ae3a94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [3.2.0] - 2024-01-04 + +### Fixed + + - Added plugin schema to fix serverless warning. Thank you @crunchie84 ([74](https://github.com/amplify-education/serverless-log-forwarding/pull/74)) + - Refactored integration tests + - Updated linting + - Updated packages + ## [3.1.0] - 2023-02-17 ### Added diff --git a/package.json b/package.json index be762bd..fcd5ab8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-log-forwarding", - "version": "3.1.0", + "version": "3.2.0", "description": "a serverless plugin to forward logs to given lambda function", "keywords": [ "serverless", diff --git a/src/index.ts b/src/index.ts index 3e2319c..15b437f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,42 +46,42 @@ class LogForwardingPlugin { } // schema for the function section of serverless.yml - this.serverless.configSchemaHandler.defineFunctionProperties('aws', { + this.serverless.configSchemaHandler.defineFunctionProperties("aws", { properties: { logForwarding: { - type: 'object', + type: "object", properties: { enabled: { - type: 'boolean', - }, + type: "boolean" + } }, - required: ['enabled'], - }, + required: ["enabled"] + } }, - required: [], + required: [] }); // schema for the custom props section of serverless.yml this.serverless.configSchemaHandler.defineCustomProperties({ properties: { logForwarding: { - type: 'object', + type: "object", properties: { - destinationARN: { type: 'string' }, - roleArn: { type: 'string' }, - filterPattern: { type: 'string' }, - normalizedFilterID: { type: 'string' }, + destinationARN: { type: "string" }, + roleArn: { type: "string" }, + filterPattern: { type: "string" }, + normalizedFilterID: { type: "string" }, stages: { - type: 'array', + type: "array", uniqueItems: true, - items: { type: 'string' }, + items: { type: "string" } }, - createLambdaPermission: { type: 'boolean' }, + createLambdaPermission: { type: "boolean" } }, - required: ['destinationARN'], - }, + required: ["destinationARN"] + } }, - required: ['logForwarding'], + required: ["logForwarding"] }); } diff --git a/src/types.ts b/src/types.ts index 02e899d..e772c84 100644 --- a/src/types.ts +++ b/src/types.ts @@ -30,6 +30,62 @@ export interface PluginConfig { destinationARN: string; } +/** + * If your plugin adds new properties to any level in the serverless.yml + * you can use these functions to add JSON ajv based schema validation for + * those properties + * + * @see https://www.serverless.com/framework/docs/guides/plugins/custom-configuration + */ +export interface ConfigSchemaHandler { + /** + * If your plugin requires additional top-level properties (like provider, custom, service...) + * you can use the defineTopLevelProperty helper to add their definition. + * @see https://www.serverless.com/framework/docs/guides/plugins/custom-configuration#top-level-properties-via-definetoplevelproperty + */ + defineTopLevelProperty( + providerName: string, + schema: Record + ): void; + /** + * If your plugin depends on properties defined in the custom: section, you can use the + * defineCustomProperties helper + * @see https://www.serverless.com/framework/docs/guides/plugins/custom-configuration#properties-in-custom-via-definecustomproperties + */ + defineCustomProperties(jsonSchema: object): void; + /** + * If your plugin adds support to a new function event, you can use the + * defineFunctionEvent helper + * @see https://www.serverless.com/framework/docs/guides/plugins/custom-configuration#function-events-via-definefunctionevent + */ + defineFunctionEvent( + providerName: string, + event: string, + jsonSchema: Record + ): void; + /** + * If your plugin adds new properties to a function event, you can use the + * defineFunctionEventProperties helper + * @see https://www.serverless.com/framework/docs/guides/plugins/custom-configuration#function-event-properties-via-definefunctioneventproperties + */ + defineFunctionEventProperties( + providerName: string, + existingEvent: string, + jsonSchema: object + ): void; + /** + * If your plugin adds new properties to functions, you can use the + * defineFunctionProperties helper. + * @see https://www.serverless.com/framework/docs/guides/plugins/custom-configuration#function-properties-via-definefunctionproperties + */ + defineFunctionProperties(providerName: string, schema: object): void; + /** + * If your plugin provides support for a new provider, register it via defineProvider + * @see https://www.serverless.com/framework/docs/guides/plugins/custom-configuration#new-provider-via-defineprovider + */ + defineProvider(providerName: string, options?: Record): void; +} + export interface ServerlessInstance { service: { service: string @@ -55,7 +111,7 @@ export interface ServerlessInstance { }, getProvider (name: string): AWSProvider, - + configSchemaHandler: ConfigSchemaHandler; cli: { log (str: string, entity?: string): void, consoleLog (str: string): void, diff --git a/test/integration-tests/utils/aws/lambda-wrap.ts b/test/integration-tests/utils/aws/lambda-wrap.ts index b9c0459..192faa6 100644 --- a/test/integration-tests/utils/aws/lambda-wrap.ts +++ b/test/integration-tests/utils/aws/lambda-wrap.ts @@ -10,8 +10,11 @@ import { GetPolicyResponse, LambdaClient, RemovePermissionCommand } from "@aws-sdk/client-lambda"; +import { sleep } from "../test-utilities"; const FUNC_ZIP_PATH = "test/integration-tests/fixtures/logs-receiver.zip"; +const MAX_FUNC_ACTIVE_RETRY = 3; +const FUNC_ACTIVE_TIMEOUT_SEC = 10; export default class LambdaWrap { private lambdaClient: LambdaClient; @@ -37,13 +40,18 @@ export default class LambdaWrap { })); } - async ensureFunctionActive (funcName: string): Promise { + async ensureFunctionActive (funcName: string, retry: number = 1): Promise { + if (retry > MAX_FUNC_ACTIVE_RETRY) { + throw new Error("Max retry reached"); + } + const resp: GetFunctionResponse = await this.lambdaClient.send(new GetFunctionCommand({ FunctionName: funcName })); if (resp.Configuration.State !== "Active") { - throw new Error("retry"); + await sleep(FUNC_ACTIVE_TIMEOUT_SEC * 1000); + await this.ensureFunctionActive(funcName, retry + 1); } } diff --git a/test/integration-tests/utils/aws/log-wrap.ts b/test/integration-tests/utils/aws/log-wrap.ts index 51e84da..5fb04e4 100644 --- a/test/integration-tests/utils/aws/log-wrap.ts +++ b/test/integration-tests/utils/aws/log-wrap.ts @@ -1,6 +1,5 @@ import { CloudWatchLogsClient, - DeleteLogGroupCommand, DescribeSubscriptionFiltersCommand, SubscriptionFilter } from "@aws-sdk/client-cloudwatch-logs"; @@ -12,12 +11,6 @@ export default class LogWrap { this.cwlClient = new CloudWatchLogsClient({ region }); } - async removeLambdaLogs (logGroupName: string) { - await this.cwlClient.send(new DeleteLogGroupCommand({ - logGroupName: logGroupName - })); - } - async getSubscriptionFilter (logGroupName: string, destinationArn: string): Promise { const { subscriptionFilters } = await this.cwlClient.send(new DescribeSubscriptionFiltersCommand({ logGroupName: logGroupName diff --git a/test/integration-tests/utils/log-receiver.ts b/test/integration-tests/utils/log-receiver.ts index 848aa5b..a54be83 100644 --- a/test/integration-tests/utils/log-receiver.ts +++ b/test/integration-tests/utils/log-receiver.ts @@ -8,6 +8,7 @@ import IamWrap from "./aws/iam-wrap"; import LambdaWrap from "./aws/lambda-wrap"; import LogWrap from "./aws/log-wrap"; +import { sleep } from "./test-utilities"; /** * Interface for the immutable objects with information about @@ -37,11 +38,11 @@ export default class LogReceiver { * that it's active. Returns the information about objects. */ public async setUpLogsReceiver (): Promise { + // setup lambda role const role = await this.iamWrap.setupLambdaRole(this.resourceName); - // eslint-disable-next-line no-promise-executor-return,promise/param-names - // await new Promise((r) => setTimeout(r, 10000)); + // we need to wait around 10 seconds to use the role + await sleep(10 * 1000); const funcData = await this.lambdaWrap.createDummyFunction(this.resourceName, role.Arn); - await this.lambdaWrap.ensureFunctionActive(funcData.FunctionName); return { @@ -59,21 +60,12 @@ export default class LogReceiver { await this.lambdaWrap.removeFunction(this.resourceName); console.debug("Logs receiver lambda was deleted"); } catch (e) { - console.error(e); console.debug("Failed to delete a logs receiver"); } - try { - await this.logWrap.removeLambdaLogs(`/aws/lambda/${this.resourceName}`); - console.log("Log receiver's log group was deleted"); - } catch (e) { - console.error(e); - console.debug("Failed to delete a logs receiver's log group"); - } try { await this.iamWrap.removeLambdaRole(this.resourceName); console.log("Log receiver's role was deleted"); } catch (e) { - console.error(e); console.debug("Failed to delete the execution role"); } } diff --git a/test/integration-tests/utils/test-utilities.ts b/test/integration-tests/utils/test-utilities.ts index ebe44bc..78e88df 100644 --- a/test/integration-tests/utils/test-utilities.ts +++ b/test/integration-tests/utils/test-utilities.ts @@ -86,6 +86,10 @@ export function getLogGroup (testName: string, stage: string, functionId: string return `/aws/lambda/${functionName}`; } +export function sleep (ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + export async function runTest (testName: string, assertFunc): Promise { try { const configFolder = `configs/${testName}`;