graphql-transformer-core
Version:
A framework to transform from GraphQL SDL to AWS cloudFormation.
220 lines • 11.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const cloudform_types_1 = require("cloudform-types");
const getTemplateReferences_1 = require("./getTemplateReferences");
const getIn_1 = __importDefault(require("./getIn"));
const setIn_1 = __importDefault(require("./setIn"));
const blankTemplate_1 = __importDefault(require("./blankTemplate"));
function splitStack(opts) {
const stack = opts.stack;
const stackRules = opts.stackRules;
const rootStackName = opts.rootStackName || 'root';
const defaultParameterValues = opts.defaultParameterValues || {};
const defaultParameterDefinitions = opts.defaultParameterDefinitions || {};
const defaultDependencies = opts.defaultDependencies || [];
const importExportPrefix = opts.importExportPrefix;
function createMapByStackRules(keys) {
const stackMap = {};
for (const key of keys) {
const mappedTo = stackRules.get(key);
if (mappedTo) {
stackMap[key] = mappedTo;
}
else {
stackMap[key] = rootStackName;
}
}
return stackMap;
}
function mapResourcesToStack(template) {
return createMapByStackRules(Object.keys(template.Resources));
}
function mapMappingToStack(template) {
return createMapByStackRules(Object.keys(template.Mappings));
}
function mapOutputsToStack(template) {
return createMapByStackRules(Object.keys(template.Outputs));
}
function collectTemplates(template, resourceToStackMap, outputToStackMap, mappingsToStackMap) {
const resourceIds = Object.keys(resourceToStackMap);
const templateMap = {};
for (const resourceId of resourceIds) {
const stackName = resourceToStackMap[resourceId];
if (!templateMap[stackName]) {
templateMap[stackName] = (0, blankTemplate_1.default)({
Description: 'An auto-generated nested stack.',
Parameters: {
...template.Parameters,
...defaultParameterDefinitions,
},
Conditions: template.Conditions,
});
}
const resource = template.Resources[resourceId];
let depends = resource.DependsOn;
if (depends && Array.isArray(depends)) {
resource.DependsOn = depends.filter((id) => {
return resourceToStackMap[id] === stackName;
});
}
else if (depends && typeof depends === 'string') {
resource.DependsOn = resourceToStackMap[depends] === stackName ? depends : undefined;
}
templateMap[stackName].Resources[resourceId] = resource;
}
const outputIds = Object.keys(outputToStackMap);
for (const outputId of outputIds) {
const stackName = outputToStackMap[outputId];
const output = template.Outputs[outputId];
templateMap[stackName].Outputs[outputId] = output;
}
const mappingIds = Object.keys(mappingToStackMap);
for (const mappingId of mappingIds) {
const stackName = mappingsToStackMap[mappingId];
const mappings = template.Mappings[mappingId];
templateMap[stackName].Mappings[mappingId] = mappings;
}
templateMap[rootStackName].Parameters = template.Parameters;
templateMap[rootStackName].Conditions = template.Conditions;
return templateMap;
}
function replaceReferences(stacks, resourceToStackMap) {
const stackDependsOnMap = Object.keys(stacks).reduce((acc, k) => ({ ...acc, [k]: [] }), {});
const stackParamsMap = Object.keys(stacks).reduce((acc, k) => ({ ...acc, [k]: {} }), {});
for (const thisStackName of Object.keys(stacks)) {
const template = stacks[thisStackName];
const resourceToReferenceMap = (0, getTemplateReferences_1.getTemplateReferences)(template);
for (const resourceId of Object.keys(resourceToReferenceMap)) {
const references = resourceToReferenceMap[resourceId];
const referencedStackName = resourceToStackMap[resourceId];
for (const refList of references) {
const refNode = (0, getIn_1.default)(template, refList);
const refNeedsReplacing = refNode && refNode.Ref && referencedStackName && referencedStackName !== thisStackName;
const getAttNeedsReplacing = refNode && refNode['Fn::GetAtt'] && referencedStackName && referencedStackName !== thisStackName;
const isChildReferencingRoot = thisStackName !== rootStackName && referencedStackName === rootStackName;
if (refNeedsReplacing && isChildReferencingRoot) {
const parameterName = `Ref${resourceId}`;
stackParamsMap[thisStackName][parameterName] = refNode;
template.Parameters[parameterName] = new cloudform_types_1.StringParameter({
Description: `Auto-generated parameter that forwards Fn.Ref(${resourceId}) through to nested stacks.`,
});
(0, setIn_1.default)(template, refList, cloudform_types_1.Fn.Ref(parameterName));
}
else if (refNeedsReplacing) {
(0, setIn_1.default)(template, refList, makeImportValueForRef(resourceId));
const outputForInput = makeOutputForRef(resourceId);
const referencedStack = stacks[referencedStackName];
const exportLogicalId = `Ref${resourceId}`;
if (referencedStack && referencedStack.Outputs && !referencedStack.Outputs[exportLogicalId]) {
if (template.Outputs[exportLogicalId]) {
delete template.Outputs[exportLogicalId];
}
referencedStack.Outputs[exportLogicalId] = outputForInput;
}
if (stackDependsOnMap[thisStackName] && !stackDependsOnMap[thisStackName].find((s) => s === referencedStackName)) {
stackDependsOnMap[thisStackName].push(referencedStackName);
}
}
else if (getAttNeedsReplacing && isChildReferencingRoot) {
const [resId, attr] = refNode['Fn::GetAtt'];
const parameterName = `GetAtt${resourceId}${attr}`;
stackParamsMap[thisStackName][parameterName] = refNode;
template.Parameters[parameterName] = new cloudform_types_1.StringParameter({
Description: `Auto-generated parameter that forwards Fn.GetAtt(${resourceId}, ${attr}) through to nested stacks.`,
});
(0, setIn_1.default)(template, refList, cloudform_types_1.Fn.Ref(parameterName));
}
else if (getAttNeedsReplacing) {
const [resId, attr] = refNode['Fn::GetAtt'];
(0, setIn_1.default)(template, refList, makeImportValueForGetAtt(resourceId, attr));
const outputForInput = makeOutputForGetAtt(resourceId, attr);
const referencedStack = stacks[referencedStackName];
const exportLogicalId = `GetAtt${resourceId}${attr}`;
if (referencedStack && referencedStack.Outputs && !referencedStack.Outputs[exportLogicalId]) {
if (template.Outputs[exportLogicalId]) {
delete template.Outputs[exportLogicalId];
}
referencedStack.Outputs[exportLogicalId] = outputForInput;
}
if (stackDependsOnMap[thisStackName] && !stackDependsOnMap[thisStackName].find((s) => s === referencedStackName)) {
stackDependsOnMap[thisStackName].push(referencedStackName);
}
}
}
}
}
return {
stackDependencyMap: stackDependsOnMap,
stackParameterMap: stackParamsMap,
};
}
function makeImportValueForRef(resourceId) {
return cloudform_types_1.Fn.ImportValue(cloudform_types_1.Fn.Join(':', [importExportPrefix, 'Ref', resourceId]));
}
function makeImportValueForGetAtt(resourceId, attribute) {
return cloudform_types_1.Fn.ImportValue(cloudform_types_1.Fn.Join(':', [importExportPrefix, 'GetAtt', resourceId, attribute]));
}
function makeOutputForGetAtt(resourceId, attribute) {
return {
Value: cloudform_types_1.Fn.GetAtt(resourceId, attribute),
Export: {
Name: cloudform_types_1.Fn.Join(':', [importExportPrefix, 'GetAtt', resourceId, attribute]),
},
};
}
function makeOutputForRef(resourceId) {
return {
Value: cloudform_types_1.Fn.Ref(resourceId),
Export: {
Name: cloudform_types_1.Fn.Join(':', [importExportPrefix, 'Ref', resourceId]),
},
};
}
function updateRootWithNestedStacks(root, stacks, stackInfo) {
const stackFileNames = Object.keys(stacks);
const allParamNames = Object.keys(root.Parameters);
const allParamValues = allParamNames.reduce((acc, name) => ({
...acc,
[name]: cloudform_types_1.Fn.Ref(name),
}), defaultParameterValues);
for (const stackName of stackFileNames) {
const dependsOnStacks = stackInfo.stackDependencyMap[stackName] || [];
const extraParams = stackInfo.stackParameterMap[stackName] || {};
let stackResource = new cloudform_types_1.CloudFormation.Stack({
Parameters: {
...allParamValues,
...extraParams,
},
TemplateURL: cloudform_types_1.Fn.Join('/', [
'https://s3.amazonaws.com',
cloudform_types_1.Fn.Ref(opts.deployment.deploymentBucketParameterName),
cloudform_types_1.Fn.Ref(opts.deployment.deploymentKeyParameterName),
'stacks',
stackName + '.json',
]),
}).dependsOn([...defaultDependencies, ...dependsOnStacks]);
root.Resources[stackName] = stackResource;
}
return root;
}
const templateJson = JSON.parse(JSON.stringify(stack));
const resourceToStackMap = mapResourcesToStack(templateJson);
const outputToStackMap = mapOutputsToStack(templateJson);
const mappingToStackMap = mapMappingToStack(templateJson);
const stackMapping = { ...resourceToStackMap, ...outputToStackMap, ...mappingToStackMap };
const stacks = collectTemplates(templateJson, resourceToStackMap, outputToStackMap, stackMapping);
const stackInfo = replaceReferences(stacks, resourceToStackMap);
let rootStack = stacks[rootStackName];
delete stacks[rootStackName];
rootStack = updateRootWithNestedStacks(rootStack, stacks, stackInfo);
return {
rootStack,
stacks,
stackMapping,
};
}
exports.default = splitStack;
//# sourceMappingURL=splitStack.js.map