Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stage under apiGateway to override default. #11487

Merged
merged 1 commit into from Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/providers/aws/events/apigateway.md
Expand Up @@ -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
```
Expand Up @@ -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;
Expand Down Expand Up @@ -215,7 +215,7 @@ async function ensureStage() {
return this.provider.request('APIGateway', 'createStage', {
deploymentId,
restApiId,
stageName: this.options.stage,
stageName: this.provider.getApiGatewayStage(),
});
}

Expand Down Expand Up @@ -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}`;
Expand Down Expand Up @@ -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,
});
}
Expand All @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions lib/plugins/aws/provider.js
Expand Up @@ -807,6 +807,7 @@ class AwsProvider {
additionalProperties: false,
},
shouldStartNameWithService: { type: 'boolean' },
stage: { type: 'string' },
usagePlan: {
anyOf: [
apiGatewayUsagePlan,
Expand Down Expand Up @@ -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
*/
Expand Down
Expand Up @@ -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'));
});
});