From d554c874cbe0dd0f9b79cb6dca73554d2b46f089 Mon Sep 17 00:00:00 2001 From: Pavan Andhukuri Date: Sat, 29 Oct 2022 03:02:25 +0530 Subject: [PATCH] Add stage under apiGateway to override default --- docs/providers/aws/events/apigateway.md | 10 ++ .../api-gateway/lib/hack/update-stage.js | 10 +- lib/plugins/aws/provider.js | 6 + .../api-gateway/lib/hack/update-stage.test.js | 166 ++++++++++++++++++ 4 files changed, 187 insertions(+), 5 deletions(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index ae88c4061c5..987a0c4f4ff 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -1871,3 +1871,13 @@ provider: apiGateway: disableDefaultEndpoint: true ``` + +## Providing a custom stage name + +By default, the API Gateway stage will be same as the serverless stage. This can be overridden by passing stage under the apiGateway configuration under provider. + +```yml +provider: + apiGateway: + stage: customStageName +``` diff --git a/lib/plugins/aws/package/compile/events/api-gateway/lib/hack/update-stage.js b/lib/plugins/aws/package/compile/events/api-gateway/lib/hack/update-stage.js index 17956e737bd..6221788f47f 100644 --- a/lib/plugins/aws/package/compile/events/api-gateway/lib/hack/update-stage.js +++ b/lib/plugins/aws/package/compile/events/api-gateway/lib/hack/update-stage.js @@ -172,7 +172,7 @@ async function resolveStage() { return this.provider .request('APIGateway', 'getStage', { restApiId, - stageName: this.options.stage, + stageName: this.provider.getApiGatewayStage(), }) .then((res) => { this.apiGatewayStageState = res; @@ -215,7 +215,7 @@ async function ensureStage() { return this.provider.request('APIGateway', 'createStage', { deploymentId, restApiId, - stageName: this.options.stage, + stageName: this.provider.getApiGatewayStage(), }); } @@ -257,7 +257,7 @@ function handleLogs() { if (logs) { const service = this.state.service.service; - const stage = this.options.stage; + const stage = this.provider.getApiGatewayStage(); const region = this.options.region; const partition = this.partition; const logGroupName = `/aws/api-gateway/${service}-${stage}`; @@ -367,7 +367,7 @@ function applyUpdates() { if (patchOperations.length) { return this.provider.request('APIGateway', 'updateStage', { restApiId, - stageName: this.options.stage, + stageName: this.provider.getApiGatewayStage(), patchOperations, }); } @@ -378,7 +378,7 @@ function applyUpdates() { async function removeAccessLoggingLogGroup() { const service = this.state.service.service; const provider = this.state.service.provider; - const stage = this.options.stage; + const stage = this.provider.getApiGatewayStage(); const logGroupName = `/aws/api-gateway/${service}-${stage}`; let accessLogging = provider.logs && provider.logs.restApi; diff --git a/lib/plugins/aws/provider.js b/lib/plugins/aws/provider.js index 91644a56ffa..bf94e05c726 100644 --- a/lib/plugins/aws/provider.js +++ b/lib/plugins/aws/provider.js @@ -807,6 +807,7 @@ class AwsProvider { additionalProperties: false, }, shouldStartNameWithService: { type: 'boolean' }, + stage: { type: 'string' }, usagePlan: { anyOf: [ apiGatewayUsagePlan, @@ -1882,6 +1883,11 @@ class AwsProvider { return stageSourceValue.value || defaultStage; } + getApiGatewayStage() { + const apiGatewayConfig = this.serverless.service.provider.apiGateway; + return (apiGatewayConfig && apiGatewayConfig.stage) || this.getStage(); + } + /** * Get API Gateway Rest API ID from serverless config */ diff --git a/test/unit/lib/plugins/aws/package/compile/events/api-gateway/lib/hack/update-stage.test.js b/test/unit/lib/plugins/aws/package/compile/events/api-gateway/lib/hack/update-stage.test.js index f75d447d58a..456db77ae7a 100644 --- a/test/unit/lib/plugins/aws/package/compile/events/api-gateway/lib/hack/update-stage.test.js +++ b/test/unit/lib/plugins/aws/package/compile/events/api-gateway/lib/hack/update-stage.test.js @@ -884,4 +884,170 @@ describe('test/unit/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/u expect(getDeploymentsStub).to.have.been.calledOnce; expect(getDeploymentsStub.args[0][0].restApiId).to.equal('api-id'); }); + + it('should use stage defined under apiGateway config if passed', async () => { + const { serviceConfig, servicePath, updateConfig } = await fixtures.setup('api-gateway'); + const getDeploymentsStub = sinon.stub().returns({ items: [{ id: 'deployment-id' }] }); + const getStageStub = sinon.stub().resolves({}); + const createStageStub = sinon.stub(); + const updateStageStub = sinon.stub(); + const stage = 'dev'; + + await updateConfig({ + provider: { + stage, + apiGateway: { + shouldStartNameWithService: true, + stage: 'customStage', + }, + stackTags: { key: 'value' }, + logs: { restApi: true }, + }, + }); + + await runServerless({ + command: 'deploy', + cwd: servicePath, + lastLifecycleHookName: 'after:deploy:deploy', + awsRequestStubMap: { + APIGateway: { + createStage: createStageStub, + getStage: getStageStub, + updateStage: updateStageStub, + getDeployments: getDeploymentsStub, + getRestApis: { items: [{ id: 'api-id', name: `${serviceConfig.service}-${stage}` }] }, + tagResource: {}, + }, + CloudFormation: { + describeStacks: { Stacks: [{}] }, + describeStackEvents: { + StackEvents: [ + { + ResourceStatus: 'UPDATE_COMPLETE', + ResourceType: 'AWS::CloudFormation::Stack', + }, + ], + }, + describeStackResource: { + StackResourceDetail: { PhysicalResourceId: 'deployment-bucket' }, + }, + listStackResources: {}, + validateTemplate: {}, + deleteChangeSet: {}, + createChangeSet: {}, + executeChangeSet: {}, + describeChangeSet: { + ChangeSetName: 'new-service-dev-change-set', + ChangeSetId: 'some-change-set-id', + StackName: 'new-service-dev', + Status: 'CREATE_COMPLETE', + }, + }, + S3: { + listObjectsV2: { Contents: [] }, + upload: {}, + headBucket: {}, + }, + STS: { + getCallerIdentity: { + ResponseMetadata: { RequestId: 'ffffffff-ffff-ffff-ffff-ffffffffffff' }, + UserId: 'XXXXXXXXXXXXXXXXXXXXX', + Account: '999999999999', + Arn: 'arn:aws-us-gov:iam::999999999999:user/test', + }, + }, + }, + }); + + expect(getStageStub.args[0][0].stageName).to.be.equal('customStage'); + expect(createStageStub.args[0][0].stageName).to.be.equal('customStage'); + expect(updateStageStub.args[0][0].stageName).to.be.equal('customStage'); + const accessLogSettingsDestinationArn = updateStageStub.args[0][0].patchOperations.filter( + (op) => op.path === '/accessLogSettings/destinationArn' + )[0].value; + expect(accessLogSettingsDestinationArn) + .to.be.a('string') + .and.satisfy((arn) => arn.endsWith('customStage')); + }); + + it('should update stage, and remove log group using stage defined under apiGateway config if configured such', async () => { + const { serviceConfig, servicePath, updateConfig } = await fixtures.setup('api-gateway'); + const getDeploymentsStub = sinon.stub().returns({ items: [{ id: 'deployment-id' }] }); + const updateStageStub = sinon.stub(); + const deleteLogGroupStub = sinon.stub(); + const stage = 'dev'; + + await updateConfig({ + provider: { + stage, + apiGateway: { + shouldStartNameWithService: true, + stage: 'customStage', + }, + stackTags: { key: 'value' }, + tracing: { apiGateway: true }, + }, + }); + + await runServerless({ + command: 'deploy', + cwd: servicePath, + lastLifecycleHookName: 'after:deploy:deploy', + awsRequestStubMap: { + APIGateway: { + updateStage: updateStageStub, + getStage: 'dev', + getDeployments: getDeploymentsStub, + getRestApis: { items: [{ id: 'api-id', name: `${serviceConfig.service}-${stage}` }] }, + tagResource: {}, + }, + CloudFormation: { + describeStacks: { Stacks: [{}] }, + describeStackEvents: { + StackEvents: [ + { + ResourceStatus: 'UPDATE_COMPLETE', + ResourceType: 'AWS::CloudFormation::Stack', + }, + ], + }, + describeStackResource: { + StackResourceDetail: { PhysicalResourceId: 'deployment-bucket' }, + }, + listStackResources: {}, + validateTemplate: {}, + deleteChangeSet: {}, + createChangeSet: {}, + executeChangeSet: {}, + describeChangeSet: { + ChangeSetName: 'new-service-dev-change-set', + ChangeSetId: 'some-change-set-id', + StackName: 'new-service-dev', + Status: 'CREATE_COMPLETE', + }, + }, + S3: { + listObjectsV2: { Contents: [] }, + upload: {}, + headBucket: {}, + }, + STS: { + getCallerIdentity: { + ResponseMetadata: { RequestId: 'ffffffff-ffff-ffff-ffff-ffffffffffff' }, + UserId: 'XXXXXXXXXXXXXXXXXXXXX', + Account: '999999999999', + Arn: 'arn:aws-us-gov:iam::999999999999:user/test', + }, + }, + CloudWatchLogs: { + deleteLogGroup: deleteLogGroupStub, + }, + }, + }); + + expect(updateStageStub.args[0][0].stageName).to.be.equal('customStage'); + expect(deleteLogGroupStub.args[0][0].logGroupName) + .to.be.a('string') + .and.satisfy((logGroupName) => logGroupName.endsWith('customStage')); + }); });