Skip to content

Commit

Permalink
feat(Telemetry): Report newStackCreatedDuringDeploy property
Browse files Browse the repository at this point in the history
  • Loading branch information
pgrzesik committed Dec 22, 2021
1 parent 0483ce4 commit fbba60c
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 14 deletions.
4 changes: 4 additions & 0 deletions lib/cli/interactive-setup/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ module.exports = {
delete serverless.isLocallyInstalled;
await serverless.run();
context.awsAccountId = serverless.getProvider('aws').accountId;
context.newStackCreatedDuringDeploy =
serverless.getProvider('aws').newStackCreatedDuringDeploy;
} else {
try {
await overrideStdoutWrite(
Expand All @@ -200,6 +202,8 @@ module.exports = {
delete serverless.isLocallyInstalled;
await serverless.run();
context.awsAccountId = serverless.getProvider('aws').accountId;
context.newStackCreatedDuringDeploy =
serverless.getProvider('aws').newStackCreatedDuringDeploy;
}
);
} catch (err) {
Expand Down
5 changes: 1 addition & 4 deletions lib/cli/interactive-setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,5 @@ module.exports = async (context) => {
}
}

return {
configuration: context.configuration,
awsAccountId: context.awsAccountId,
};
return context;
};
1 change: 1 addition & 0 deletions lib/plugins/aws/deploy/lib/createStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ module.exports = {
params.DisableRollback = this.serverless.service.provider.disableRollback;
}

this.provider.newStackCreatedDuringDeploy = true;
return this.provider
.request('CloudFormation', 'createStack', params)
.then((cfData) => this.monitorStack('create', cfData));
Expand Down
80 changes: 80 additions & 0 deletions lib/plugins/aws/lib/get-create-change-set-params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict';

module.exports = {
getCreateChangeSetParams({ changeSetType, templateUrl, templateBody }) {
let stackTags = { STAGE: this.provider.getStage() };
const stackName = this.provider.naming.getStackName();
const changeSetName = this.provider.naming.getStackChangeSetName();

// Merge additional stack tags
if (this.serverless.service.provider.stackTags) {
const customKeys = Object.keys(this.serverless.service.provider.stackTags);
const collisions = Object.keys(stackTags).filter((defaultKey) =>
customKeys.some((key) => defaultKey.toLowerCase() === key.toLowerCase())
);

// Delete collisions upfront
for (const key of collisions) {
delete stackTags[key];
}

stackTags = Object.assign(stackTags, this.serverless.service.provider.stackTags);
}

const createChangeSetParams = {
StackName: stackName,
ChangeSetName: changeSetName,
ChangeSetType: changeSetType,
Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
Parameters: [],
Tags: Object.keys(stackTags).map((key) => ({ Key: key, Value: stackTags[key] })),
};

if (templateUrl) {
createChangeSetParams.TemplateURL = templateUrl;
}

if (templateBody) {
createChangeSetParams.TemplateBody = JSON.stringify(templateBody);
}

if (
(templateUrl &&
this.serverless.service.provider.compiledCloudFormationTemplate &&
this.serverless.service.provider.compiledCloudFormationTemplate.Transform) ||
(templateBody && templateBody.Transform)
) {
createChangeSetParams.Capabilities.push('CAPABILITY_AUTO_EXPAND');
}

const customDeploymentRole = this.provider.getCustomDeploymentRole();
if (customDeploymentRole) {
createChangeSetParams.RoleARN = customDeploymentRole;
}

if (this.serverless.service.provider.notificationArns) {
createChangeSetParams.NotificationARNs = this.serverless.service.provider.notificationArns;
}

if (this.serverless.service.provider.stackParameters) {
createChangeSetParams.Parameters = this.serverless.service.provider.stackParameters;
}

// Policy must have at least one statement, otherwise no updates would be possible at all
if (
this.serverless.service.provider.stackPolicy &&
Object.keys(this.serverless.service.provider.stackPolicy).length
) {
createChangeSetParams.StackPolicyBody = JSON.stringify({
Statement: this.serverless.service.provider.stackPolicy,
});
}

if (this.serverless.service.provider.rollbackConfiguration) {
createChangeSetParams.RollbackConfiguration =
this.serverless.service.provider.rollbackConfiguration;
}

return createChangeSetParams;
},
};
1 change: 1 addition & 0 deletions lib/plugins/aws/lib/updateStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ module.exports = {
params.DisableRollback = this.serverless.service.provider.disableRollback;
}

this.provider.newStackCreatedDuringDeploy = true;
return this.provider
.request('CloudFormation', 'createStack', params)
.then((cfData) => this.monitorStack('create', cfData));
Expand Down
19 changes: 16 additions & 3 deletions lib/utils/telemetry/generatePayload.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ module.exports = ({
serverless,
commandUsage,
variableSources,
awsAccountId,
interactiveContext,
}) => {
let commandDurationMs;

Expand Down Expand Up @@ -303,19 +303,32 @@ module.exports = ({

if (
isAwsProvider &&
((serverless && command === 'deploy') || (command === '' && awsAccountId))
((serverless && command === 'deploy') ||
(command === '' && interactiveContext && interactiveContext.awsAccountId))
) {
const serviceName = isObject(configuration.service)
? configuration.service.name
: configuration.service;
const accountId = awsAccountId || (serverless && serverless.getProvider('aws').accountId);
const accountId =
interactiveContext.awsAccountId || (serverless && serverless.getProvider('aws').accountId);
if (serviceName && accountId) {
payload.projectId = crypto
.createHash('sha256')
.update(`${serviceName}-${accountId}`)
.digest('base64');
}
}

if (
isAwsProvider &&
((serverless && command === 'deploy') ||
(command === '' && interactiveContext && interactiveContext.awsAccountId))
) {
payload.newStackCreatedDuringDeploy = Boolean(
interactiveContext.newStackCreatedDuringDeploy ||
(serverless && serverless.getProvider('aws').newStackCreatedDuringDeploy)
);
}
}

if (commandUsage) {
Expand Down
10 changes: 5 additions & 5 deletions scripts/serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ const processSpanPromise = (async () => {
const isStandaloneCommand = notIntegratedCommands.has(command);

if (!isHelpRequest && (isInteractiveSetup || isStandaloneCommand)) {
let interactiveResult;
let interactiveContext;
if (configuration) require('../lib/cli/ensure-supported-command')(configuration);
if (isInteractiveSetup) {
if (!process.stdin.isTTY && !process.env.SLS_INTERACTIVE_SETUP_ENABLE) {
Expand All @@ -524,15 +524,15 @@ const processSpanPromise = (async () => {
'INTERACTIVE_SETUP_IN_NON_TTY'
);
}
interactiveResult = await require('../lib/cli/interactive-setup')({
interactiveContext = await require('../lib/cli/interactive-setup')({
configuration,
serviceDir,
configurationFilename,
options,
commandUsage,
});
if (interactiveResult.configuration) {
configuration = interactiveResult.configuration;
if (interactiveContext.configuration) {
configuration = interactiveContext.configuration;
}
} else {
await require(`../commands/${commands.join('-')}`)({
Expand All @@ -559,7 +559,7 @@ const processSpanPromise = (async () => {
configuration,
commandUsage,
variableSources: variableSourcesInConfig,
awsAccountId: interactiveResult.awsAccountId,
interactiveContext,
}),
outcome: 'success',
});
Expand Down
47 changes: 45 additions & 2 deletions test/unit/lib/utils/telemetry/generatePayload.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ describe('test/unit/lib/utils/telemetry/generatePayload.test.js', () => {
expect(payload.projectId).to.deep.equal('35dsFwCaexwLHppAP4uDsjKW4ci54q1AKcN5JTNaDtw=');
});

it('Should correctly resolve projectId property when account passed externally', async () => {
it('Should correctly resolve projectId property when account passed via interactiveContext', async () => {
const { serverless } = await runServerless({
fixture: 'httpApi',
command: 'print',
Expand All @@ -695,9 +695,52 @@ describe('test/unit/lib/utils/telemetry/generatePayload.test.js', () => {
commandSchema: commandsSchema.get('deploy'),
serviceDir: serverless.serviceDir,
configuration: serverless.configurationInput,
awsAccountId: '1234567890',
interactiveContext: { awsAccountId: '1234567890' },
});

expect(payload.projectId).to.deep.equal('35dsFwCaexwLHppAP4uDsjKW4ci54q1AKcN5JTNaDtw=');
});

it('Should correctly resolve `newStackCreatedDuringDeploy` property', async () => {
const { serverless } = await runServerless({
fixture: 'httpApi',
command: 'print',
configExt: {
service: 'to-ensure-unique-serivce-name',
},
});
serverless.getProvider('aws').newStackCreatedDuringDeploy = true;
const payload = generatePayload({
command: 'deploy',
options: {},
commandSchema: commandsSchema.get('deploy'),
serviceDir: serverless.serviceDir,
configuration: serverless.configurationInput,
serverless,
});

expect(payload.newStackCreatedDuringDeploy).to.be.true;
});

it('Should correctly resolve `newStackCreatedDuringDeploy` property when passed via interactiveContext', async () => {
const { serverless } = await runServerless({
fixture: 'httpApi',
command: 'print',
configExt: {
service: 'to-ensure-unique-serivce-name',
},
});
const payload = generatePayload({
command: 'deploy',
options: {},
commandSchema: commandsSchema.get('deploy'),
serviceDir: serverless.serviceDir,
configuration: serverless.configurationInput,
interactiveContext: {
newStackCreatedDuringDeploy: true,
},
});

expect(payload.newStackCreatedDuringDeploy).to.be.true;
});
});

0 comments on commit fbba60c

Please sign in to comment.