Skip to content

Commit

Permalink
fix(amplify-provider-awscloudformation): use prev deployment vars
Browse files Browse the repository at this point in the history
create deployment record which includes previously deployed parameters and capabilities - add action
to bubble up error within state machine
  • Loading branch information
SwaySway committed Jan 26, 2021
1 parent 5e4648c commit 3665572
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DeploymentOp, DeploymentStep } from '../iterative-deployment/deployment
import { DiffChanges, DiffableProject, getGQLDiff } from './utils';
import { DynamoDB, Template } from 'cloudform-types';
import { GSIChange, getGSIDiffs } from './gsi-diff-helpers';
import { GSIRecord, TemplateState, getStackParameters, getTableNames } from '../utils/amplify-resource-state-utils';
import { GSIRecord, TemplateState, getPreviousDeploymentRecord, getTableNames } from '../utils/amplify-resource-state-utils';
import { ROOT_APPSYNC_S3_KEY, hashDirectory } from '../upload-appsync-files';
import { addGSI, getGSIDetails, removeGSI } from './dynamodb-gsi-helpers';
import {
Expand Down Expand Up @@ -118,7 +118,7 @@ export class GraphQLResourceManager {

const tableNameMap = await getTableNames(this.cfnClient, this.templateState.getKeys(), this.resourceMeta.stackId);

const parameters = await getStackParameters(this.cfnClient, this.resourceMeta.stackId);
const { parameters, capabilities } = await getPreviousDeploymentRecord(this.cfnClient, this.resourceMeta.stackId);

const buildHash = await hashDirectory(this.backendApiProjectRoot);

Expand Down Expand Up @@ -148,6 +148,7 @@ export class GraphQLResourceManager {
parameters: { ...parameters, S3DeploymentRootKey: deploymentRootKey },
stackName: this.resourceMeta.stackId,
tableNames: tableNames,
capabilities,
// clientRequestToken: `${buildHash}-step-${stepNumber}`,
};

Expand All @@ -171,7 +172,7 @@ export class GraphQLResourceManager {
const cloudBuildDir = path.join(this.cloudBackendApiProjectRoot, 'build');
const stateFileDir = this.getStateFilesDirectory();

const parameters = await getStackParameters(this.cfnClient, this.resourceMeta.stackId);
const { parameters, capabilities } = await getPreviousDeploymentRecord(this.cfnClient, this.resourceMeta.stackId);
const buildHash = await hashDirectory(this.backendApiProjectRoot);

const stepNumber = 'initial-stack';
Expand All @@ -184,6 +185,7 @@ export class GraphQLResourceManager {
stackTemplatePathOrUrl: `${deploymentRootKey}/cloudformation-template.json`,
parameters: { ...parameters, S3DeploymentRootKey: deploymentRootKey },
stackName: this.resourceMeta.stackId,
capabilities,
tableNames: [],
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export class DeploymentManager {
await this.s3Client.headObject({ Bucket: this.deploymentBucket, Key: bucketKey }).promise();
return true;
} catch (e) {
if (e.ccode === 'NotFound') {
if (e.code === 'NotFound') {
throw new Error(`The cloudformation template ${templatePath} was not found in deployment bucket ${this.deploymentBucket}`);
}
throw e;
Expand All @@ -220,11 +220,13 @@ export class DeploymentManager {
assert(tableName, 'table name should be passed');

const dbClient = new aws.DynamoDB({ region });

const response = await dbClient.describeTable({ TableName: tableName }).promise();
const gsis = response.Table?.GlobalSecondaryIndexes;

return gsis ? gsis.every(idx => idx.IndexStatus === 'ACTIVE') : true;
try {
const response = await dbClient.describeTable({ TableName: tableName }).promise();
const gsis = response.Table?.GlobalSecondaryIndexes;
return gsis ? gsis.every(idx => idx.IndexStatus === 'ACTIVE') : true;
} catch (err) {
throw err;
}
};

private waitForIndices = async (stackParams: DeploymentMachineOp) => {
Expand All @@ -243,10 +245,11 @@ export class DeploymentManager {
});
});

await Promise.all(waiters);
try {
await Promise.all(waiters);
await this.deploymentStateManager?.advanceStep();
} catch {
} catch (err) {
throw err;
// deployment should not fail because saving status failed
}
return Promise.resolve();
Expand All @@ -269,32 +272,32 @@ export class DeploymentManager {
private doDeploy = async (currentStack: DeploymentMachineOp): Promise<void> => {
try {
await this.deploymentStateManager?.startCurrentStep();
} catch {
// deployment should not fail because status could not be saved
}
const cfn = this.cfnClient;
const cfn = this.cfnClient;

assert(currentStack.stackName, 'stack name should be passed to doDeploy');
assert(currentStack.stackTemplateUrl, 'stackTemplateUrl must be passed to doDeploy');
assert(currentStack.stackName, 'stack name should be passed to doDeploy');
assert(currentStack.stackTemplateUrl, 'stackTemplateUrl must be passed to doDeploy');

await this.ensureStack(currentStack.stackName);
await this.ensureStack(currentStack.stackName);

const parameters = Object.entries(currentStack.parameters).map(([key, val]) => {
return {
ParameterKey: key,
ParameterValue: val.toString(),
};
});
const parameters = Object.entries(currentStack.parameters).map(([key, val]) => {
return {
ParameterKey: key,
ParameterValue: val.toString(),
};
});

await cfn
.updateStack({
StackName: currentStack.stackName,
Parameters: parameters,
TemplateURL: currentStack.stackTemplateUrl,
Capabilities: currentStack.capabilities,
ClientRequestToken: currentStack.clientRequestToken,
})
.promise();
await cfn
.updateStack({
StackName: currentStack.stackName,
Parameters: parameters,
TemplateURL: currentStack.stackTemplateUrl,
Capabilities: currentStack.capabilities,
ClientRequestToken: currentStack.clientRequestToken,
})
.promise();
} catch (err) {
throw err;
}
};

private waitForDeployment = async (stackParams: DeploymentMachineOp): Promise<void> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ export function createDeploymentMachine(initialContext: DeployMachineContext, he
},
onError: {
target: '#rollback',
actions: assign((context: DeployMachineContext, error: any) => {
return {
...context,
error: error.data,
};
}),
},
},
},
Expand All @@ -134,6 +140,12 @@ export function createDeploymentMachine(initialContext: DeployMachineContext, he
},
onError: {
target: '#rollback',
actions: assign((context: DeployMachineContext, error: any) => {
return {
...context,
error: error.data,
};
}),
},
},
activities: ['deployPoll'],
Expand All @@ -146,6 +158,14 @@ export function createDeploymentMachine(initialContext: DeployMachineContext, he
target: 'triggerDeploy',
actions: send('NEXT'),
},
onError: {
actions: assign((context: DeployMachineContext, error: any) => {
return {
...context,
error: error.data,
};
}),
},
},
},
},
Expand Down Expand Up @@ -178,6 +198,12 @@ export function createDeploymentMachine(initialContext: DeployMachineContext, he
},
onError: {
target: '#failed',
actions: assign((context: DeployMachineContext, error: any) => {
return {
...context,
error: error.data,
};
}),
},
},
},
Expand All @@ -190,6 +216,12 @@ export function createDeploymentMachine(initialContext: DeployMachineContext, he
},
onError: {
target: '#failed',
actions: assign((context: DeployMachineContext, error: any) => {
return {
...context,
error: error.data,
};
}),
},
},
activities: ['rollbackPoll'],
Expand All @@ -202,6 +234,14 @@ export function createDeploymentMachine(initialContext: DeployMachineContext, he
target: 'triggerRollback',
actions: send('NEXT'),
},
onError: {
actions: assign((context: DeployMachineContext, error: any) => {
return {
...context,
error: error.data,
};
}),
},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
import { Template } from 'cloudform-types';
import { GlobalSecondaryIndex, AttributeDefinition } from 'cloudform-types/types/dynamoDb/table';
import { CloudFormation } from 'aws-sdk';
import { Capabilities } from 'aws-sdk/clients/cloudformation';
import _ from 'lodash';
import { JSONUtilities } from 'amplify-cli-core';

export interface GSIRecord {
attributeDefinition: AttributeDefinition[];
gsi: GlobalSecondaryIndex;
}
/**
* Use previously deployed variables
*/
export interface DeploymentRecord {
parameters?: Record<string, string>;
capabilities?: Capabilities;
}

export const getStackParameters = async (cfnClient: CloudFormation, StackId: string): Promise<any> => {
export const getPreviousDeploymentRecord = async (cfnClient: CloudFormation, stackId: string): Promise<DeploymentRecord> => {
let depRecord: DeploymentRecord = {};
const apiStackInfo = await cfnClient
.describeStacks({
StackName: StackId,
StackName: stackId,
})
.promise();
return apiStackInfo.Stacks[0].Parameters.reduce((acc, param) => {
depRecord.parameters = apiStackInfo.Stacks[0].Parameters.reduce((acc, param) => {
acc[param.ParameterKey] = param.ParameterValue;
return acc;
}, {});
}, {}) as Record<string, string>;
depRecord.capabilities = apiStackInfo.Stacks[0].Capabilities;
return depRecord;
};

export const getStackInfo = async (cfn: CloudFormation, StackId: string): Promise<any> => {
return await cfn
.describeStacks({
StackName: StackId,
})
.promise();
};

export const getTableNames = async (cfnClient: CloudFormation, tables: string[], StackId: string): Promise<Map<string, string>> => {
Expand Down

0 comments on commit 3665572

Please sign in to comment.