From 35ffd0b1815d2dd45d5d5a9bb9dea6dbb0bcea5b Mon Sep 17 00:00:00 2001 From: Brendan Doyle Date: Wed, 17 Jul 2024 10:48:53 +0100 Subject: [PATCH 1/9] Add basic support for Enhanced Metrics --- doc/enhancedMetrics.md | 18 ++++++++++++++++++ doc/general-config.md | 1 + src/resources/Api.ts | 9 +++++++++ src/resources/Resolver.ts | 1 + src/types/cloudFormation.ts | 1 + src/types/common.ts | 6 ++++++ src/types/plugin.ts | 2 ++ 7 files changed, 38 insertions(+) create mode 100644 doc/enhancedMetrics.md diff --git a/doc/enhancedMetrics.md b/doc/enhancedMetrics.md new file mode 100644 index 00000000..3463bcfa --- /dev/null +++ b/doc/enhancedMetrics.md @@ -0,0 +1,18 @@ +# Enhanced Metrics + +AppSync supports [Enhanced metrics](https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html#cw-metrics). You can find the metrics configuration under the `appSync.enhancedMetrics` attribute. + +## Quick start + +```yaml +appSync: + name: my-api + enhancedMetrics: + DataSourceLevelMetricsBehavior: 'FULL_REQUEST_DATA_SOURCE_METRICS' + OperationLevelMetricsConfig: 'ENABLED' +``` + +## Configuration +See [official documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appsync-graphqlapi-enhancedmetricsconfig.html). + +Note `ResolverLevelMetricsBehavior` is fixed to `PER_RESOLVER_METRICS` with each resolver's `MetricsConfig` set to `DISABLED` diff --git a/doc/general-config.md b/doc/general-config.md index 8cb91965..7225943a 100644 --- a/doc/general-config.md +++ b/doc/general-config.md @@ -46,6 +46,7 @@ appSync: - `pipelineFunctions`: See [Pipeline functions](pipeline-functions.md) - `substitutions`: See [Substitutions](substitutions.md). Deprecated: Use environment variables. - `environment`: A list of environment variables for the API. See [Official Documentation](https://docs.aws.amazon.com/appsync/latest/devguide/environmental-variables.html) +- `enhancedMetrics`: See [enhanced metrics](enhancedMetrics.md) - `caching`: See [Cacing](caching.md) - `waf`: See [Web Application Firefall](WAF.md) - `logging`: See [Logging](#Logging) diff --git a/src/resources/Api.ts b/src/resources/Api.ts index 92a9663a..04e5d7af 100644 --- a/src/resources/Api.ts +++ b/src/resources/Api.ts @@ -112,6 +112,15 @@ export class Api { }); } + if (this.config.enhancedMetrics) { + merge(endpointResource.Properties, { + EnhancedMetricsConfig: { + "DataSourceLevelMetricsBehavior" : this.config.enhancedMetrics.DataSourceLevelMetricsBehavior, + "OperationLevelMetricsConfig" : this.config.enhancedMetrics.OperationLevelMetricsConfig, + "ResolverLevelMetricsBehavior": "PER_RESOLVER_METRICS" } + }); + } + if (this.config.introspection !== undefined) { merge(endpointResource.Properties, { IntrospectionConfig: this.config.introspection ? 'ENABLED' : 'DISABLED', diff --git a/src/resources/Resolver.ts b/src/resources/Resolver.ts index e7f11029..bf432438 100644 --- a/src/resources/Resolver.ts +++ b/src/resources/Resolver.ts @@ -29,6 +29,7 @@ export class Resolver { ApiId: this.api.getApiId(), TypeName: this.config.type, FieldName: this.config.field, + MetricsConfig: 'DISABLED' }; const isVTLResolver = 'request' in this.config || 'response' in this.config; diff --git a/src/types/cloudFormation.ts b/src/types/cloudFormation.ts index 6100d4e9..a0d211cf 100644 --- a/src/types/cloudFormation.ts +++ b/src/types/cloudFormation.ts @@ -117,6 +117,7 @@ export type CfnResolver = { }; }; MaxBatchSize?: number; + MetricsConfig? : 'ENABLED' | 'DISABLED'; }; }; diff --git a/src/types/common.ts b/src/types/common.ts index cc8157b9..4490438d 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -46,6 +46,7 @@ export type WafRuleThrottle = { }; export type WafRuleCustom = { + overrideAction?: any; name: string; priority?: number; action?: WafRuleAction; @@ -129,6 +130,11 @@ export type SyncConfig = { export type Substitutions = Record; export type EnvironmentVariables = Record; +export type EnhancedMetricsConfig = { + "DataSourceLevelMetricsBehavior" : 'FULL_REQUEST_DATA_SOURCE_METRICS'| 'PER_DATA_SOURCE_METRICS'; + "OperationLevelMetricsConfig" : 'ENABLED' | ' DISABLED'; + "ResolverLevelMetricsBehavior" : 'FULL_REQUEST_RESOLVER_METRICS' | 'PER_RESOLVER_METRICS' ; +}; export type DsDynamoDBConfig = { type: 'AMAZON_DYNAMODB'; diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 0b8bf8f0..e0767401 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -16,6 +16,7 @@ import { DsNone, Substitutions, EnvironmentVariables, + EnhancedMetricsConfig } from './common'; export * from './common'; @@ -31,6 +32,7 @@ export type AppSyncConfig = { pipelineFunctions: Record; substitutions?: Substitutions; environment?: EnvironmentVariables; + enhancedMetrics?: EnhancedMetricsConfig; xrayEnabled?: boolean; logging?: LoggingConfig; caching?: CachingConfig; From 6ca5a7eacebd02def8d6379d70afc94db4f2b325 Mon Sep 17 00:00:00 2001 From: Brendan Doyle Date: Wed, 17 Jul 2024 13:13:49 +0100 Subject: [PATCH 2/9] Update validations for enhancedMetrics and test snapshots for resolvers --- src/__tests__/resolvers.test.ts | 11 +++++++++++ src/validation.ts | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/__tests__/resolvers.test.ts b/src/__tests__/resolvers.test.ts index 55439d64..c2af4724 100644 --- a/src/__tests__/resolvers.test.ts +++ b/src/__tests__/resolvers.test.ts @@ -164,6 +164,7 @@ describe('Resolvers', () => { "FieldName": "user", "Kind": "UNIT", "MaxBatchSize": undefined, + "MetricsConfig": "DISABLED", "RequestMappingTemplate": "Content of path/to/mappingTemplates/Query.user.request.vtl", "ResponseMappingTemplate": "Content of path/to/mappingTemplates/Query.user.response.vtl", "TypeName": "Query", @@ -218,6 +219,7 @@ describe('Resolvers', () => { "FieldName": "user", "Kind": "UNIT", "MaxBatchSize": undefined, + "MetricsConfig": "DISABLED", "Runtime": Object { "Name": "APPSYNC_JS", "RuntimeVersion": "1.0.0", @@ -272,6 +274,7 @@ describe('Resolvers', () => { "FieldName": "user", "Kind": "UNIT", "MaxBatchSize": undefined, + "MetricsConfig": "DISABLED", "TypeName": "Query", }, "Type": "AWS::AppSync::Resolver", @@ -323,6 +326,7 @@ describe('Resolvers', () => { "FieldName": "user", "Kind": "UNIT", "MaxBatchSize": 200, + "MetricsConfig": "DISABLED", "TypeName": "Query", }, "Type": "AWS::AppSync::Resolver", @@ -380,6 +384,7 @@ describe('Resolvers', () => { "FieldName": "user", "Kind": "UNIT", "MaxBatchSize": undefined, + "MetricsConfig": "DISABLED", "SyncConfig": Object { "ConflictDetection": "VERSION", "ConflictHandler": "LAMBDA", @@ -478,6 +483,7 @@ describe('Resolvers', () => { ", "FieldName": "user", "Kind": "PIPELINE", + "MetricsConfig": "DISABLED", "PipelineConfig": Object { "Functions": Array [ Object { @@ -547,6 +553,7 @@ describe('Resolvers', () => { }, "FieldName": "user", "Kind": "PIPELINE", + "MetricsConfig": "DISABLED", "PipelineConfig": Object { "Functions": Array [ Object { @@ -615,6 +622,7 @@ describe('Resolvers', () => { "Code": "Bundled content of resolvers/getUserFunction.js", "FieldName": "user", "Kind": "PIPELINE", + "MetricsConfig": "DISABLED", "PipelineConfig": Object { "Functions": Array [ Object { @@ -1006,6 +1014,7 @@ describe('Resolvers', () => { "FieldName": "user", "Kind": "UNIT", "MaxBatchSize": undefined, + "MetricsConfig": "DISABLED", "TypeName": "Query", }, "Type": "AWS::AppSync::Resolver", @@ -1070,6 +1079,7 @@ describe('Resolvers', () => { "FieldName": "user", "Kind": "UNIT", "MaxBatchSize": undefined, + "MetricsConfig": "DISABLED", "TypeName": "Query", }, "Type": "AWS::AppSync::Resolver", @@ -1134,6 +1144,7 @@ describe('Resolvers', () => { "FieldName": "user", "Kind": "UNIT", "MaxBatchSize": undefined, + "MetricsConfig": "DISABLED", "TypeName": "Query", }, "Type": "AWS::AppSync::Resolver", diff --git a/src/validation.ts b/src/validation.ts index 4cc292e0..05c1d9e5 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -810,6 +810,21 @@ export const appSyncSchema = { }, required: ['level'], }, + enhancedMetrics: { + type: 'object', + properties: { + DataSourceLevelMetricsBehavior: { + type: 'string', + enum: ['FULL_REQUEST_DATA_SOURCE_METRICS', 'PER_DATA_SOURCE_METRICS'], + errorMessage: "must be 'FULL_REQUEST_DATA_SOURCE_METRICS' or 'PER_DATA_SOURCE_METRICS'", + }, + OperationLevelMetricsConfig: { + type: 'string', + enum: ['ENABLED', ' DISABLED'], + errorMessage: "must be 'ENABLED' or ' DISABLED'", + }, + } + }, dataSources: { oneOf: [ { From 75f2965bdd524d478e4f42bef9130df346838275 Mon Sep 17 00:00:00 2001 From: Brendan Doyle Date: Thu, 18 Jul 2024 10:33:59 +0100 Subject: [PATCH 3/9] fix lint issues --- src/resources/Api.ts | 11 +++++++---- src/resources/Resolver.ts | 2 +- src/types/cloudFormation.ts | 2 +- src/types/common.ts | 10 +++++++--- src/types/plugin.ts | 2 +- src/validation.ts | 5 +++-- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/resources/Api.ts b/src/resources/Api.ts index 04e5d7af..6da7433b 100644 --- a/src/resources/Api.ts +++ b/src/resources/Api.ts @@ -114,10 +114,13 @@ export class Api { if (this.config.enhancedMetrics) { merge(endpointResource.Properties, { - EnhancedMetricsConfig: { - "DataSourceLevelMetricsBehavior" : this.config.enhancedMetrics.DataSourceLevelMetricsBehavior, - "OperationLevelMetricsConfig" : this.config.enhancedMetrics.OperationLevelMetricsConfig, - "ResolverLevelMetricsBehavior": "PER_RESOLVER_METRICS" } + EnhancedMetricsConfig: { + DataSourceLevelMetricsBehavior: + this.config.enhancedMetrics.DataSourceLevelMetricsBehavior, + OperationLevelMetricsConfig: + this.config.enhancedMetrics.OperationLevelMetricsConfig, + ResolverLevelMetricsBehavior: 'PER_RESOLVER_METRICS', + }, }); } diff --git a/src/resources/Resolver.ts b/src/resources/Resolver.ts index bf432438..09027738 100644 --- a/src/resources/Resolver.ts +++ b/src/resources/Resolver.ts @@ -29,7 +29,7 @@ export class Resolver { ApiId: this.api.getApiId(), TypeName: this.config.type, FieldName: this.config.field, - MetricsConfig: 'DISABLED' + MetricsConfig: 'DISABLED', }; const isVTLResolver = 'request' in this.config || 'response' in this.config; diff --git a/src/types/cloudFormation.ts b/src/types/cloudFormation.ts index a0d211cf..a52572cf 100644 --- a/src/types/cloudFormation.ts +++ b/src/types/cloudFormation.ts @@ -117,7 +117,7 @@ export type CfnResolver = { }; }; MaxBatchSize?: number; - MetricsConfig? : 'ENABLED' | 'DISABLED'; + MetricsConfig?: 'ENABLED' | 'DISABLED'; }; }; diff --git a/src/types/common.ts b/src/types/common.ts index 4490438d..20660c78 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -131,9 +131,13 @@ export type SyncConfig = { export type Substitutions = Record; export type EnvironmentVariables = Record; export type EnhancedMetricsConfig = { - "DataSourceLevelMetricsBehavior" : 'FULL_REQUEST_DATA_SOURCE_METRICS'| 'PER_DATA_SOURCE_METRICS'; - "OperationLevelMetricsConfig" : 'ENABLED' | ' DISABLED'; - "ResolverLevelMetricsBehavior" : 'FULL_REQUEST_RESOLVER_METRICS' | 'PER_RESOLVER_METRICS' ; + DataSourceLevelMetricsBehavior: + | 'FULL_REQUEST_DATA_SOURCE_METRICS' + | 'PER_DATA_SOURCE_METRICS'; + OperationLevelMetricsConfig: 'ENABLED' | ' DISABLED'; + ResolverLevelMetricsBehavior: + | 'FULL_REQUEST_RESOLVER_METRICS' + | 'PER_RESOLVER_METRICS'; }; export type DsDynamoDBConfig = { diff --git a/src/types/plugin.ts b/src/types/plugin.ts index e0767401..4d04d5a3 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -16,7 +16,7 @@ import { DsNone, Substitutions, EnvironmentVariables, - EnhancedMetricsConfig + EnhancedMetricsConfig, } from './common'; export * from './common'; diff --git a/src/validation.ts b/src/validation.ts index 05c1d9e5..1cbe5959 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -816,14 +816,15 @@ export const appSyncSchema = { DataSourceLevelMetricsBehavior: { type: 'string', enum: ['FULL_REQUEST_DATA_SOURCE_METRICS', 'PER_DATA_SOURCE_METRICS'], - errorMessage: "must be 'FULL_REQUEST_DATA_SOURCE_METRICS' or 'PER_DATA_SOURCE_METRICS'", + errorMessage: + "must be 'FULL_REQUEST_DATA_SOURCE_METRICS' or 'PER_DATA_SOURCE_METRICS'", }, OperationLevelMetricsConfig: { type: 'string', enum: ['ENABLED', ' DISABLED'], errorMessage: "must be 'ENABLED' or ' DISABLED'", }, - } + }, }, dataSources: { oneOf: [ From 1a58298a2fb0e2f595b3b9053ee1354405926ab0 Mon Sep 17 00:00:00 2001 From: Yasuto Nishii <45926314+yasuto-nishii@users.noreply.github.com> Date: Sun, 11 Feb 2024 18:42:48 +0900 Subject: [PATCH 4/9] fix : support overrideAction for WAF (#582) --- doc/WAF.md | 16 +++++++++++++++ src/__tests__/__snapshots__/waf.test.ts.snap | 21 ++++++++++++++++++++ src/__tests__/waf.test.ts | 21 ++++++++++++++++++++ src/resources/Waf.ts | 9 ++++++++- 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/doc/WAF.md b/doc/WAF.md index 40d636f0..13916f40 100644 --- a/doc/WAF.md +++ b/doc/WAF.md @@ -119,6 +119,22 @@ waf: - US ``` +```yml +waf: + enabled: true + defaultAction: Block + rules: + # using ManagedRuleGroup + - name: "AWSManagedRulesCommonRuleSet" + priority: 20 + overrideAction: + None: {} + statement: + ManagedRuleGroupStatement: + VendorName: "AWS" + Name: "AWSManagedRulesCommonRuleSet" +``` + ### Per API Key rules In some cases, you might want to enable a rule for a given API key only. You can specify `wafRules` under the `appSync.apiKeys` attribute. The rules will apply only to that API key. diff --git a/src/__tests__/__snapshots__/waf.test.ts.snap b/src/__tests__/__snapshots__/waf.test.ts.snap index 4101842f..5d48bbc9 100644 --- a/src/__tests__/__snapshots__/waf.test.ts.snap +++ b/src/__tests__/__snapshots__/waf.test.ts.snap @@ -411,6 +411,27 @@ Object { } `; +exports[`Waf Custom rules should generate a custom rule with ManagedRuleGroup 1`] = ` +Object { + "Name": "MyRule1", + "OverrideAction": Object { + "None": Object {}, + }, + "Priority": 200, + "Statement": Object { + "ManagedRuleGroupStatement": Object { + "Name": "AWSManagedRulesCommonRuleSet", + "VendorName": "AWS", + }, + }, + "VisibilityConfig": Object { + "CloudWatchMetricsEnabled": true, + "MetricName": "MyRule1", + "SampledRequestsEnabled": true, + }, +} +`; + exports[`Waf Disable introspection should generate a preset rule 1`] = ` Object { "Action": Object { diff --git a/src/__tests__/waf.test.ts b/src/__tests__/waf.test.ts index 9575f66b..9cdc83c9 100644 --- a/src/__tests__/waf.test.ts +++ b/src/__tests__/waf.test.ts @@ -168,6 +168,27 @@ describe('Waf', () => { ), ).toMatchSnapshot(); }); + + it('should generate a custom rule with ManagedRuleGroup', () => { + expect( + waf.buildWafRule( + { + name: 'MyRule1', + priority: 200, + overrideAction: { + None: {}, + }, + statement: { + ManagedRuleGroupStatement: { + Name: 'AWSManagedRulesCommonRuleSet', + VendorName: 'AWS', + }, + }, + }, + 'Base', + ), + ).toMatchSnapshot(); + }); }); describe('ApiKey rules', () => { diff --git a/src/resources/Waf.ts b/src/resources/Waf.ts index b9a6dfa5..2d2e0bc7 100644 --- a/src/resources/Waf.ts +++ b/src/resources/Waf.ts @@ -14,6 +14,7 @@ import { WafThrottleConfig, } from '../types/plugin'; import { Api } from './Api'; +import { toCfnKeys } from '../utils'; export class Waf { constructor(private api: Api, private config: WafConfig) {} @@ -106,10 +107,10 @@ export class Waf { } const action: WafRuleAction = rule.action || 'Allow'; + const overrideAction = rule.overrideAction; const result: CfnWafRule = { Name: rule.name, - Action: { [action]: {} }, Priority: rule.priority, Statement: rule.statement, VisibilityConfig: this.getWafVisibilityConfig( @@ -118,6 +119,12 @@ export class Waf { ), }; + if (overrideAction) { + result.OverrideAction = toCfnKeys(overrideAction); + } else { + result.Action = { [action]: {} }; + } + return result; } From ca797b445407675ebaa25ae40bbc7d785dc747ac Mon Sep 17 00:00:00 2001 From: Max Marze Date: Mon, 12 Aug 2024 09:24:39 -0400 Subject: [PATCH 5/9] feat: support serverless v4 (#637) --- src/__tests__/commands.test.ts | 27 ---------- src/__tests__/given.ts | 15 +++++- src/index.ts | 94 +++++++++++++++++++++------------- src/resources/Schema.ts | 2 +- src/types/common.ts | 1 + 5 files changed, 74 insertions(+), 65 deletions(-) diff --git a/src/__tests__/commands.test.ts b/src/__tests__/commands.test.ts index 4de3ebc0..3fa9b2dc 100644 --- a/src/__tests__/commands.test.ts +++ b/src/__tests__/commands.test.ts @@ -4,33 +4,6 @@ import ServerlessError from 'serverless/lib/serverless-error'; jest.setTimeout(30000); -jest.mock('@serverless/utils/log', () => { - const dummyProgress = { - update: jest.fn(), - remove: jest.fn(), - }; - const logger = { - error: jest.fn(), - warning: jest.fn(), - notice: jest.fn(), - info: jest.fn(), - debug: jest.fn(), - success: jest.fn(), - }; - return { - writeText: jest.fn(), - progress: { - get: () => dummyProgress, - create: () => dummyProgress, - }, - log: { - get: () => logger, - ...logger, - }, - getPluginWriters: jest.fn(), - }; -}); - const confirmSpy = jest.spyOn(utils, 'confirmAction'); const describeStackResources = jest.fn().mockResolvedValue({ StackResources: [ diff --git a/src/__tests__/given.ts b/src/__tests__/given.ts index 63ab67c9..6fbed2d6 100644 --- a/src/__tests__/given.ts +++ b/src/__tests__/given.ts @@ -22,7 +22,20 @@ export const plugin = () => { stage: 'dev', region: 'us-east-1', }; - return new ServerlessAppsyncPlugin(createServerless(), options); + return new ServerlessAppsyncPlugin(createServerless(), options, { + log: { + error: jest.fn(), + warning: jest.fn(), + info: jest.fn(), + success: jest.fn(), + }, + progress: { + create: () => ({ + remove: jest.fn(), + }), + }, + writeText: jest.fn(), + }); }; export const appSyncConfig = (partial?: Partial) => { diff --git a/src/index.ts b/src/index.ts index 89e15d12..da62e3e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ -import { writeText, log, progress } from '@serverless/utils/log'; import Serverless from 'serverless/lib/Serverless'; import Provider from 'serverless/lib/plugins/aws/provider.js'; import { forEach, last, merge } from 'lodash'; @@ -68,6 +67,23 @@ import terminalLink from 'terminal-link'; const CONSOLE_BASE_URL = 'https://console.aws.amazon.com'; +type Progress = { + remove: () => void; +}; + +type ServerlessPluginUtils = { + log: { + success: (message: string) => void; + warning: (message: string) => void; + error: (message: string) => void; + info: (message: string) => void; + }; + progress: { + create: (params: { name?: string; message: string }) => Progress; + }; + writeText: (message: string) => void; +}; + class ServerlessAppsyncPlugin { private provider: Provider; private gatheredData: { @@ -90,6 +106,7 @@ class ServerlessAppsyncPlugin { constructor( public serverless: Serverless, private options: Record, + public utils: ServerlessPluginUtils, ) { this.gatheredData = { apis: [], @@ -98,7 +115,7 @@ class ServerlessAppsyncPlugin { this.serverless = serverless; this.options = options; this.provider = this.serverless.getProvider('aws'); - + this.utils = utils; // We are using a newer version of AJV than Serverless Framework // and some customizations (eg: custom errors, $merge, filter irrelevant errors) // For SF, just validate the type of input to allow us to use a custom @@ -304,7 +321,7 @@ class ServerlessAppsyncPlugin { 'appsync:validate-schema:run': () => { this.loadConfig(); this.validateSchemas(); - log.success('AppSync schema valid'); + this.utils.log.success('AppSync schema valid'); }, 'appsync:get-introspection:run': () => this.getIntrospection(), 'appsync:flush-cache:run': () => this.flushCache(), @@ -327,7 +344,7 @@ class ServerlessAppsyncPlugin { this.initDomainCommand(), 'appsync:domain:delete-record:run': async () => this.deleteRecord(), finalize: () => { - writeText( + this.utils.writeText( '\nLooking for a better AppSync development experience? Have you tried GraphBolt? https://graphbolt.dev', ); }, @@ -431,20 +448,22 @@ class ServerlessAppsyncPlugin { try { const filePath = path.resolve(this.options.output); fs.writeFileSync(filePath, schema.toString()); - log.success(`Introspection schema exported to ${filePath}`); + this.utils.log.success(`Introspection schema exported to ${filePath}`); } catch (error) { - log.error(`Could not save to file: ${(error as Error).message}`); + this.utils.log.error( + `Could not save to file: ${(error as Error).message}`, + ); } return; } - writeText(schema.toString()); + this.utils.writeText(schema.toString()); } async flushCache() { const apiId = await this.getApiId(); await this.provider.request('AppSync', 'flushApiCache', { apiId }); - log.success('Cache flushed successfully'); + this.utils.log.success('Cache flushed successfully'); } async openConsole() { @@ -486,7 +505,7 @@ class ServerlessAppsyncPlugin { events?.forEach((event) => { const { timestamp, message } = event; - writeText( + this.utils.writeText( `${chalk.gray( DateTime.fromMillis(timestamp || 0).toISO(), )}\t${message}`, @@ -512,7 +531,7 @@ class ServerlessAppsyncPlugin { const domain = this.getDomain(); if (domain.useCloudFormation !== false) { - log.warning( + this.utils.log.warning( 'You are using the CloudFormation integration for domain configuration.\n' + 'To avoid CloudFormation drifts, you should not use it in combination with this command.\n' + 'Set the `domain.useCloudFormation` attribute to false to use the CLI integration.\n' + @@ -568,7 +587,7 @@ class ServerlessAppsyncPlugin { ({ DomainName }) => DomainName === match, ); if (cert) { - log.info( + this.utils.log.info( `Found matching certificate for ${match}: ${cert.CertificateArn}`, ); return cert.CertificateArn; @@ -595,13 +614,13 @@ class ServerlessAppsyncPlugin { domainName: domain.name, certificateArn, }); - log.success(`Domain '${domain.name}' created successfully`); + this.utils.log.success(`Domain '${domain.name}' created successfully`); } catch (error) { if ( error instanceof this.serverless.classes.Error && this.options.quiet ) { - log.error(error.message); + this.utils.log.error(error.message); } else { throw error; } @@ -611,7 +630,7 @@ class ServerlessAppsyncPlugin { async deleteDomain() { try { const domain = this.getDomain(); - log.warning(`The domain '${domain.name} will be deleted.`); + this.utils.log.warning(`The domain '${domain.name} will be deleted.`); if (!this.options.yes && !(await confirmAction())) { return; } @@ -621,13 +640,13 @@ class ServerlessAppsyncPlugin { >('AppSync', 'deleteDomainName', { domainName: domain.name, }); - log.success(`Domain '${domain.name}' deleted successfully`); + this.utils.log.success(`Domain '${domain.name}' deleted successfully`); } catch (error) { if ( error instanceof this.serverless.classes.Error && this.options.quiet ) { - log.error(error.message); + this.utils.log.error(error.message); } else { throw error; } @@ -663,8 +682,7 @@ class ServerlessAppsyncPlugin { message: string; desiredStatus: 'SUCCESS' | 'NOT_FOUND'; }) { - const progressInstance = progress.create({ message }); - + const progressInstance = this.utils.progress.create({ message }); let status: string; do { status = @@ -683,14 +701,14 @@ class ServerlessAppsyncPlugin { const assoc = await this.getApiAssocStatus(domain.name); if (assoc?.associationStatus !== 'NOT_FOUND' && assoc?.apiId !== apiId) { - log.warning( + this.utils.log.warning( `The domain ${domain.name} is currently associated to another API (${assoc?.apiId})`, ); if (!this.options.yes && !(await confirmAction())) { return; } } else if (assoc?.apiId === apiId) { - log.success('The domain is already associated to this API'); + this.utils.log.success('The domain is already associated to this API'); return; } @@ -709,7 +727,9 @@ class ServerlessAppsyncPlugin { message, desiredStatus: 'SUCCESS', }); - log.success(`API successfully associated to domain '${domain.name}'`); + this.utils.log.success( + `API successfully associated to domain '${domain.name}'`, + ); } async disassocDomain() { @@ -718,7 +738,7 @@ class ServerlessAppsyncPlugin { const assoc = await this.getApiAssocStatus(domain.name); if (assoc?.associationStatus === 'NOT_FOUND') { - log.warning( + this.utils.log.warning( `The domain ${domain.name} is currently not associated to any API`, ); return; @@ -730,7 +750,7 @@ class ServerlessAppsyncPlugin { `Try running this command from that API's stack or stage, or use the --force / -f flag`, ); } - log.warning( + this.utils.log.warning( `The domain ${domain.name} will be disassociated from API '${apiId}'`, ); @@ -752,7 +772,9 @@ class ServerlessAppsyncPlugin { desiredStatus: 'NOT_FOUND', }); - log.success(`API successfully disassociated from domain '${domain.name}'`); + this.utils.log.success( + `API successfully disassociated from domain '${domain.name}'`, + ); } async getHostedZoneId() { @@ -798,7 +820,7 @@ class ServerlessAppsyncPlugin { } async createRecord() { - const progressInstance = progress.create({ + const progressInstance = this.utils.progress.create({ message: 'Creating route53 record', }); @@ -813,10 +835,10 @@ class ServerlessAppsyncPlugin { if (changeId) { await this.checkRoute53RecordStatus(changeId); progressInstance.remove(); - log.info( + this.utils.log.info( `Alias record for '${domain.name}' was created in Hosted Zone '${hostedZoneId}'`, ); - log.success('Route53 record created successfuly'); + this.utils.log.success('Route53 record created successfuly'); } } @@ -825,14 +847,14 @@ class ServerlessAppsyncPlugin { const appsyncDomainName = await this.getAppSyncDomainName(); const hostedZoneId = await this.getHostedZoneId(); - log.warning( + this.utils.log.warning( `Alias record for '${domain.name}' will be deleted from Hosted Zone '${hostedZoneId}'`, ); if (!this.options.yes && !(await confirmAction())) { return; } - const progressInstance = progress.create({ + const progressInstance = this.utils.progress.create({ message: 'Deleting route53 record', }); @@ -844,10 +866,10 @@ class ServerlessAppsyncPlugin { if (changeId) { await this.checkRoute53RecordStatus(changeId); progressInstance.remove(); - log.info( + this.utils.log.info( `Alias record for '${domain.name}' was deleted from Hosted Zone '${hostedZoneId}'`, ); - log.success('Route53 record deleted successfuly'); + this.utils.log.success('Route53 record deleted successfuly'); } } @@ -905,7 +927,7 @@ class ServerlessAppsyncPlugin { error instanceof this.serverless.classes.Error && this.options.quiet ) { - log.error(error.message); + this.utils.log.error(error.message); } else { throw error; } @@ -949,7 +971,7 @@ class ServerlessAppsyncPlugin { } loadConfig() { - log.info('Loading AppSync config'); + this.utils.log.info('Loading AppSync config'); const { appSync } = this.serverless.configurationInput; @@ -969,7 +991,7 @@ class ServerlessAppsyncPlugin { validateSchemas() { try { - log.info('Validating AppSync schema'); + this.utils.log.info('Validating AppSync schema'); if (!this.api) { throw new this.serverless.classes.Error( 'Could not load the API. This should not happen.', @@ -977,7 +999,7 @@ class ServerlessAppsyncPlugin { } this.api.compileSchema(); } catch (error) { - log.info('Error'); + this.utils.log.info('Error'); if (error instanceof GraphQLError) { this.handleError(error.message); } @@ -1057,7 +1079,7 @@ class ServerlessAppsyncPlugin { if (configValidationMode === 'error') { throw new this.serverless.classes.Error(message); } else if (configValidationMode === 'warn') { - log.warning(message); + this.utils.log.warning(message); } } } diff --git a/src/resources/Schema.ts b/src/resources/Schema.ts index 916c3fc3..1c958d13 100644 --- a/src/resources/Schema.ts +++ b/src/resources/Schema.ts @@ -50,7 +50,7 @@ export class Schema { valdiateSchema(schema: string) { const errors = validateSDL(parse(schema)); if (errors.length > 0) { - throw new ServerlessError( + throw new this.api.plugin.serverless.classes.Error( 'Invalid GraphQL schema:\n' + errors.map((error) => ` ${error.message}`).join('\n'), ); diff --git a/src/types/common.ts b/src/types/common.ts index 20660c78..9f1a32d9 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -52,6 +52,7 @@ export type WafRuleCustom = { action?: WafRuleAction; statement: CfnWafRuleStatement; visibilityConfig?: VisibilityConfig; + overrideAction?: Record; }; export type WafRuleDisableIntrospection = { From 92438f6dbf37c5fb8de8cebd1912469b500af2a1 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Mon, 14 Oct 2024 16:29:53 +0530 Subject: [PATCH 6/9] feat: add new log levels (#642) Co-authored-by: Mohammed Izzy --- src/types/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/common.ts b/src/types/common.ts index 9f1a32d9..4bf4d107 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -239,7 +239,7 @@ export type VisibilityConfig = { }; export type LoggingConfig = { - level: 'ERROR' | 'NONE' | 'ALL'; + level: 'ERROR' | 'NONE' | 'ALL' | 'DEBUG' | 'INFO'; enabled?: boolean; excludeVerboseContent?: boolean; retentionInDays?: number; From 041f64be81c49f198379ddb3a53547a6d6230b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Bour=C3=A9?= Date: Mon, 14 Oct 2024 13:01:37 +0200 Subject: [PATCH 7/9] doc: add new logLevel values --- doc/general-config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/general-config.md b/doc/general-config.md index 7225943a..ac0c75a5 100644 --- a/doc/general-config.md +++ b/doc/general-config.md @@ -187,7 +187,7 @@ appSync: retentionInDays: 14 ``` -- `level`: `ERROR`, `NONE`, or `ALL` +- `level`: `ERROR`, `NONE`, `INFO`, `DEBUG` or `ALL` - `enabled`: Boolean, Optional. Defaults to `true` when `logging` is present. - `excludeVerboseContent`: Boolean, Optional. Exclude or not verbose content (headers, response headers, context, and evaluated mapping templates), regardless of field logging level. Defaults to `false`. - `retentionInDays`: Optional. Number of days to retain the logs. Defaults to [`provider.logRetentionInDays`](https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml#general-function-settings). From eae312204395b81490ce2b0773e79530c1bbfa36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Bour=C3=A9?= Date: Thu, 17 Oct 2024 08:42:59 +0200 Subject: [PATCH 8/9] fix: log level validation (#643) --- src/__tests__/validation/__snapshots__/base.test.ts.snap | 2 +- src/validation.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/__tests__/validation/__snapshots__/base.test.ts.snap b/src/__tests__/validation/__snapshots__/base.test.ts.snap index b2b9d274..7afdb44d 100644 --- a/src/__tests__/validation/__snapshots__/base.test.ts.snap +++ b/src/__tests__/validation/__snapshots__/base.test.ts.snap @@ -30,7 +30,7 @@ exports[`Valdiation Domain Invalid should validate a useCloudFormation: not pres exports[`Valdiation Domain Invalid should validate a useCloudFormation: true, certificateArn or hostedZoneId is required 1`] = `"/domain: when using CloudFormation, you must provide either certificateArn or hostedZoneId."`; exports[`Valdiation Log Invalid should validate a Invalid 1`] = ` -"/logging/level: must be one of 'ALL', 'ERROR' or 'NONE' +"/logging/level: must be one of 'ALL', 'INFO', 'DEBUG', 'ERROR' or 'NONE' /logging/retentionInDays: must be integer /logging/excludeVerboseContent: must be boolean" `; diff --git a/src/validation.ts b/src/validation.ts index 1cbe5959..e859c753 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -801,8 +801,9 @@ export const appSyncSchema = { roleArn: { $ref: '#/definitions/stringOrIntrinsicFunction' }, level: { type: 'string', - enum: ['ALL', 'ERROR', 'NONE'], - errorMessage: "must be one of 'ALL', 'ERROR' or 'NONE'", + enum: ['ALL', 'INFO', 'DEBUG', 'ERROR', 'NONE'], + errorMessage: + "must be one of 'ALL', 'INFO', 'DEBUG', 'ERROR' or 'NONE'", }, retentionInDays: { type: 'integer' }, excludeVerboseContent: { type: 'boolean' }, From a839ccedd613c76a7ceacc8d3d8456fd8d9c633a Mon Sep 17 00:00:00 2001 From: Brendan Doyle Date: Mon, 21 Oct 2024 12:34:30 +0100 Subject: [PATCH 9/9] lint fix --- src/types/common.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/common.ts b/src/types/common.ts index 4bf4d107..4937c7bc 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -46,7 +46,6 @@ export type WafRuleThrottle = { }; export type WafRuleCustom = { - overrideAction?: any; name: string; priority?: number; action?: WafRuleAction;