|
| 1 | +const supportedTypes = require("./pipes-config.json"); |
| 2 | +const pipesSchema = require("./pipes-cfn-schema.json") |
| 3 | +const templateParser = require("../shared/template-parser"); |
| 4 | +const inputUtil = require("../shared/input-util") |
| 5 | +let cmd; |
| 6 | +async function build(command) { |
| 7 | + cmd = command; |
| 8 | + const templateName = cmd.template; |
| 9 | + const template = templateParser.load(templateName, true); |
| 10 | + if (!template) { |
| 11 | + throw new Error(`Template "${templateName}" does not exist.`); |
| 12 | + } |
| 13 | + const compatibleSources = await getSources(template); |
| 14 | + const sourceChoices = compatibleSources.map(p => { return { name: `[${template.Resources[p].Type}] ${p}`, value: { name: p, type: template.Resources[p].Type } } }).sort((a, b) => a.name > b.name ? 1 : -1); |
| 15 | + let source = await inputUtil.selectFrom([...sourceChoices, "Not templated"], "Select source", true); |
| 16 | + |
| 17 | + if (source === "Not templated") { |
| 18 | + const allTypes = supportedTypes.filter(p => !p.Type.includes("Serverless") && p.Source).map(p => p.Type) |
| 19 | + const type = await inputUtil.selectFrom(allTypes, "Select resource type", true); |
| 20 | + arn = await inputUtil.text("Enter ARN") |
| 21 | + source = { type: type, arn: arn, name: type.split(":")[1] } |
| 22 | + } |
| 23 | + const sourceConfig = supportedTypes.find(p => p.Type === source.type); |
| 24 | + const sourceObj = await buildParametersForSide(sourceConfig.SourceSchemaName); |
| 25 | + |
| 26 | + const compatibleTargets = await getTargets(template, source); |
| 27 | + const targetChoices = compatibleTargets.map(p => { return { name: `[${template.Resources[p].Type}] ${p}`, value: { name: p, type: template.Resources[p].Type } } }).sort((a, b) => a.name > b.name ? 1 : -1); |
| 28 | + let target = await inputUtil.selectFrom([...targetChoices, "Not templated"], "Select target", true); |
| 29 | + if (target === "Not templated") { |
| 30 | + const allTypes = supportedTypes.filter(p => !p.Type.includes("Serverless") && p.Target).map(p => p.Type) |
| 31 | + const type = await inputUtil.selectFrom(allTypes, "Select resource type", true); |
| 32 | + arn = await inputUtil.text("Enter ARN") |
| 33 | + target = { type: type, arn: arn, name: type.split(":")[1] } |
| 34 | + } |
| 35 | + const targetConfig = supportedTypes.find(p => p.Type === target.type); |
| 36 | + const targetObj = await buildParametersForSide(targetConfig.TargetSchemaName); |
| 37 | + const sourcePropertyName = sourceConfig.SourceSchemaName.replace("PipeSource", ""); |
| 38 | + const targetPropertyName = targetConfig.TargetSchemaName.replace("PipeTarget", ""); |
| 39 | + const pipeName = `${source.name}To${target.name}Pipe`; |
| 40 | + template.Resources[pipeName] = { |
| 41 | + Type: "AWS::Pipes::Pipe", |
| 42 | + Properties: { |
| 43 | + Name: { |
| 44 | + "Fn::Sub": "${AWS::StackName}-" + pipeName |
| 45 | + }, |
| 46 | + RoleArn: { "Fn::GetAtt": [`${pipeName}Role`, "Arn"] }, |
| 47 | + Source: source.arn || JSON.parse(JSON.stringify(sourceConfig.ArnGetter).replace("#RESOURCE_NAME#", source.name)), |
| 48 | + Target: target.arn || JSON.parse(JSON.stringify(targetConfig.ArnGetter).replace("#RESOURCE_NAME#", target.name)) |
| 49 | + } |
| 50 | + } |
| 51 | + if (Object.keys(sourceObj).length) |
| 52 | + template.Resources[pipeName].Properties["SourceParameters"] = { [sourcePropertyName]: sourceObj }; |
| 53 | + |
| 54 | + if (Object.keys(targetObj).length) |
| 55 | + template.Resources[pipeName].Properties["TargetParameters"] = { [targetPropertyName]: targetObj }; |
| 56 | + |
| 57 | + |
| 58 | + const role = { |
| 59 | + Type: "AWS::IAM::Role", |
| 60 | + Properties: { |
| 61 | + AssumeRolePolicyDocument: { |
| 62 | + Version: "2012-10-17", |
| 63 | + Statement: [ |
| 64 | + { |
| 65 | + Effect: "Allow", |
| 66 | + Principal: { |
| 67 | + Service: [ |
| 68 | + "pipes.amazonaws.com" |
| 69 | + ] |
| 70 | + }, |
| 71 | + Action: [ |
| 72 | + "sts:AssumeRole" |
| 73 | + ] |
| 74 | + } |
| 75 | + ] |
| 76 | + }, |
| 77 | + Policies: [ |
| 78 | + { |
| 79 | + PolicyName: "LogsPolicy", |
| 80 | + PolicyDocument: { |
| 81 | + Version: "2012-10-17", |
| 82 | + Statement: [ |
| 83 | + { |
| 84 | + Effect: "Allow", |
| 85 | + Action: [ |
| 86 | + "logs:CreateLogStream", |
| 87 | + "logs:CreateLogGroup", |
| 88 | + "logs:PutLogEvents"], |
| 89 | + Resource: "*" |
| 90 | + }, |
| 91 | + ], |
| 92 | + } |
| 93 | + } |
| 94 | + ] |
| 95 | + } |
| 96 | + }; |
| 97 | + sourceConfig.SourcePolicy.Statement[0].Resource = source.arn || JSON.parse(JSON.stringify((sourceConfig.SourcePolicy.Statement[0].Resource || sourceConfig.ArnGetter)).replace(/#RESOURCE_NAME#/g, source.name)); |
| 98 | + targetConfig.TargetPolicy.Statement[0].Resource = target.arn || JSON.parse(JSON.stringify((targetConfig.TargetPolicy.Statement[0].Resource || targetConfig.ArnGetter)).replace(/#RESOURCE_NAME#/g, target.name)); |
| 99 | + role.Properties.Policies.push({ |
| 100 | + PolicyName: "SourcePolicy", |
| 101 | + PolicyDocument: sourceConfig.SourcePolicy |
| 102 | + }, { |
| 103 | + PolicyName: "TargetPolicy", |
| 104 | + PolicyDocument: targetConfig.TargetPolicy |
| 105 | + }) |
| 106 | + template.Resources[`${pipeName}Role`] = role |
| 107 | + templateParser.saveTemplate(); |
| 108 | +} |
| 109 | + |
| 110 | +async function buildParametersForSide(definitionName) { |
| 111 | + const schema = pipesSchema.definitions[definitionName]; |
| 112 | + const obj = {}; |
| 113 | + if (schema) { |
| 114 | + await buildParameters(obj, schema); |
| 115 | + } |
| 116 | + |
| 117 | + return obj; |
| 118 | +} |
| 119 | + |
| 120 | +async function buildParameters(obj, schema, propName, prefix, isRequired) { |
| 121 | + prefix = prefix || ""; |
| 122 | + let settings = []; |
| 123 | + if (schema.type === "object") { |
| 124 | + settings.push(...Object.keys(schema.properties)); |
| 125 | + } else { |
| 126 | + settings = [schema]; |
| 127 | + } |
| 128 | + for (const setting of settings) { |
| 129 | + if (!propName) propName = setting; |
| 130 | + isRequired = isRequired || schema.required && schema.required.includes(setting); |
| 131 | + let optionalityString = "(leave blank to skip)" |
| 132 | + if (isRequired) { |
| 133 | + optionalityString = "(required)"; |
| 134 | + } else if (!cmd.guided) { |
| 135 | + continue; |
| 136 | + } |
| 137 | + let validationString = ""; |
| 138 | + const property = schema.properties && schema.properties[setting] || setting; |
| 139 | + if (property.maximum && property.minimum) { |
| 140 | + validationString += ` (${property.minimum} - ${property.maximum})`; |
| 141 | + } |
| 142 | + |
| 143 | + if (property["$ref"]) { |
| 144 | + const name = property.$ref.split("/").slice(-1)[0]; |
| 145 | + obj[setting] = obj[setting] || {}; |
| 146 | + const type = await buildParameters(obj[setting], pipesSchema.definitions[name], setting, prefix + setting + ".", isRequired); |
| 147 | + if (type === "enum") { |
| 148 | + obj[setting] = obj[setting][setting]; |
| 149 | + } |
| 150 | + } else if (property.enum) { |
| 151 | + if (!isRequired) { |
| 152 | + property.enum.push("Skip"); |
| 153 | + } |
| 154 | + |
| 155 | + const input = await inputUtil.selectFrom(property.enum, `Select value for ${propName}`, true) |
| 156 | + if (input === "Skip") { |
| 157 | + continue; |
| 158 | + } |
| 159 | + obj[propName] = input; |
| 160 | + return "enum"; |
| 161 | + } else if (property.type === "array") { |
| 162 | + const input = await inputUtil.text(`Enter values for ${prefix}${setting}${validationString}. Seperate with comma. ${optionalityString}`); |
| 163 | + if (input) { |
| 164 | + obj[setting] = input.split(",").map(x => x.trim()); |
| 165 | + } |
| 166 | + } |
| 167 | + else { |
| 168 | + let input = await inputUtil.text(`Enter value for ${prefix}${setting}${validationString} ${optionalityString}`) |
| 169 | + if (input) { |
| 170 | + if (property.type === "integer") { |
| 171 | + input = parseInt(input); |
| 172 | + } else if (property.type === "boolean") { |
| 173 | + input = input.toLowerCase() === "true"; |
| 174 | + } |
| 175 | + obj[setting] = input; |
| 176 | + } |
| 177 | + } |
| 178 | + } |
| 179 | +} |
| 180 | + |
| 181 | +async function getSources(template) { |
| 182 | + const types = supportedTypes.map(p => p.Type) |
| 183 | + return Object.keys(template.Resources).filter(p => types.includes(template.Resources[p].Type) && supportedTypes.find(q => q.Type === template.Resources[p].Type).Source) |
| 184 | +} |
| 185 | + |
| 186 | +async function getTargets(template, source) { |
| 187 | + const types = supportedTypes.map(p => p.Type) |
| 188 | + return Object.keys(template.Resources).filter(p => types.includes(template.Resources[p].Type) && supportedTypes.find(q => q.Type === template.Resources[p].Type).Target && p !== source) |
| 189 | +} |
| 190 | + |
| 191 | + |
| 192 | +module.exports = { |
| 193 | + build, |
| 194 | +}; |
0 commit comments