1 | 'use strict';
|
2 | const _ = require('lodash');
|
3 | const YAML = require('js-yaml');
|
4 | const { validateParameters } = require('./deploy/deploy-support-ros');
|
5 | const { SERVICE_RESOURCE, iterateResources, iterateFunctions } = require('./definition');
|
6 | const transformFunctionInDefinition = (definition = {}, tpl = {}, parameterOverride = {}, useRos = false) => {
|
7 | if (!_.isEmpty(parameterOverride)) {
|
8 | validateParameters(tpl.Parameters, parameterOverride);
|
9 | }
|
10 | const prefixMap = {};
|
11 | const dependsOn = [];
|
12 | const generatePrefix = (prefix, key) => prefix ? `${prefix}.${key}` : key;
|
13 | const refFuncConverter = (value) => {
|
14 | if (!_.isString(value)) {
|
15 | throw new Error('Value of !Ref should be string');
|
16 | }
|
17 | const resourcePath = value.split('/');
|
18 | const resources = tpl.Resources || {};
|
19 | const parameters = tpl.Parameters || {};
|
20 | let isParam = false;
|
21 | if (!_.has(resources, resourcePath)) {
|
22 | if (resourcePath.length === 1 && !_.has(parameters, value)) {
|
23 | throw new Error(`Did not find resource or parameter '${value}'`);
|
24 | }
|
25 | else if (resourcePath.length !== 1) {
|
26 | throw new Error(`Did not find resource '${value}'`);
|
27 | }
|
28 | else {
|
29 | isParam = true;
|
30 | }
|
31 | }
|
32 | if (isParam) {
|
33 | if (useRos) {
|
34 | return `\${${value}}`;
|
35 | }
|
36 | if (parameterOverride[value]) {
|
37 | return parameterOverride[value];
|
38 | }
|
39 | else if (_.has(parameters, [value, 'Default'])) {
|
40 | return _.get(parameters, [value, 'Default']);
|
41 | }
|
42 | throw new Error(`Parameter '${value}' has not been set value`);
|
43 | }
|
44 | const resource = _.get(resources, resourcePath);
|
45 | const resourceType = resource.Type || '';
|
46 | if (resourcePath.length === 1) {
|
47 | if (resourceType === 'Aliyun::Serverless::Service') {
|
48 | dependsOn.push(resourcePath[0]);
|
49 | return useRos ? `\${${resourcePath[0]}.ARN}` : `acs:fc:::services/${resourcePath[0]}`;
|
50 | }
|
51 | }
|
52 | if (resourcePath.length === 2) {
|
53 | if (resourceType === 'Aliyun::Serverless::Function') {
|
54 | dependsOn.push(`${resourcePath[0]}${resourcePath[1]}`);
|
55 | return useRos ? `\${${resourcePath[0]}${resourcePath[1]}.ARN}`
|
56 | : `acs:fc:::services/${resourcePath[0]}/functions/${resourcePath[1]}`;
|
57 | }
|
58 | }
|
59 | throw new Error(`Can not convert resource '${value}' to arn`);
|
60 | };
|
61 | const getAttFuncConverter = (value) => {
|
62 | if (!_.isArray(value) || value.length !== 2) {
|
63 | throw new Error('Value of !GetAtt should be the following form: aaa/bbb/ccc.p1.p2');
|
64 | }
|
65 | const resourcePath = value[0].split('/');
|
66 | const resources = tpl.Resources || {};
|
67 | if (!_.has(resources, resourcePath)) {
|
68 | throw new Error(`Did not find resource '${value}'`);
|
69 | }
|
70 | const resource = _.get(resources, resourcePath);
|
71 | const resourceProperties = resource.Properties || {};
|
72 | if (useRos) {
|
73 | return `\${${resourcePath.join('')}.${value[1]}}`;
|
74 | }
|
75 | if (!_.has(resourceProperties, value[1])) {
|
76 | throw new Error(`Did not find '${value[0]}' resource's property '${value[1]}'`);
|
77 | }
|
78 | return _.get(resourceProperties, value[1]);
|
79 | };
|
80 | const functionConverters = {
|
81 | 'Ref': refFuncConverter,
|
82 | 'Fn::GetAtt': getAttFuncConverter
|
83 | };
|
84 | const iterateObject = (obj, prefix) => {
|
85 | _.forIn(obj, (value, key, obj) => {
|
86 | if (_.keys(functionConverters).includes(key)) {
|
87 | const convertedValue = functionConverters[key](value);
|
88 | const { obj: o, key: k } = prefixMap[prefix];
|
89 | o[k] = convertedValue;
|
90 | delete prefixMap[prefix];
|
91 | }
|
92 | else if (_.isObjectLike(value)) {
|
93 | if (_.keys(value).length === 1 && _.keys(functionConverters).includes(_.keys(value)[0])) {
|
94 | prefixMap[generatePrefix(prefix, key)] = {
|
95 | obj,
|
96 | key
|
97 | };
|
98 | }
|
99 | iterateObject(value, generatePrefix(prefix, key));
|
100 | }
|
101 | });
|
102 | };
|
103 | iterateObject(definition);
|
104 | return {
|
105 | definition: YAML.dump(definition),
|
106 | dependsOn: dependsOn
|
107 | };
|
108 | };
|
109 | const transformFlowDefinition = (definition, tpl = {}, parameterOverride = {}) => {
|
110 | if (_.isString(definition)) {
|
111 | return definition;
|
112 | }
|
113 | if (!_.isObject(definition) ||
|
114 | !_.has(definition, 'Fn::Sub') ||
|
115 | !_.isString(_.get(definition, 'Fn::Sub'))) {
|
116 | throw new Error('The flow definition in this format can not be converted');
|
117 | }
|
118 | const resourceMap = generateResourceMap(tpl);
|
119 | const parameterMap = generateParameterMap(tpl, parameterOverride);
|
120 | const replaceMap = {};
|
121 | definition = _.get(definition, 'Fn::Sub');
|
122 | const regex = new RegExp(/\${(.*)}/g);
|
123 | let execRes;
|
124 | while ((execRes = regex.exec(definition))) {
|
125 | const indexKey = execRes[1];
|
126 | if (indexKey.split('.').length > 1) {
|
127 | const [first, ...tail] = indexKey.split('.');
|
128 | replaceMap[execRes[0]] = _.get(resourceMap[first], tail.join('.'), 'NONE');
|
129 | }
|
130 | else {
|
131 | replaceMap[execRes[0]] = parameterMap[indexKey] || 'NONE';
|
132 | }
|
133 | }
|
134 | for (const [src, target] of Object.entries(replaceMap)) {
|
135 | definition = definition.split(src).join(target);
|
136 | }
|
137 | return definition;
|
138 | };
|
139 | const generateResourceMap = (tpl = {}) => {
|
140 | const resourceMap = new Map();
|
141 | iterateResources(tpl.Resources, SERVICE_RESOURCE, (name, res) => {
|
142 | const properties = res.Properties || {};
|
143 | properties.ARN = `acs:fc:::services/${name}`;
|
144 | properties.ServiceName = name;
|
145 | resourceMap[name] = properties;
|
146 | });
|
147 | iterateFunctions(tpl, (serviceName, serviceRes, functionName, functionRes) => {
|
148 | const properties = functionRes.Properties || {};
|
149 | properties.ARN = `acs:fc:::services/${serviceName}/functions/${functionName}`;
|
150 | properties.ServiceName = serviceName;
|
151 | properties.FunctionName = functionName;
|
152 | resourceMap[`${serviceName}${functionName}`] = properties;
|
153 | });
|
154 | return resourceMap;
|
155 | };
|
156 | const generateParameterMap = (tpl = {}, parameterOverride = {}) => {
|
157 | const parameterMap = new Map();
|
158 | const parameters = tpl.Parameters || {};
|
159 | for (const [name, def] of Object.entries(parameters)) {
|
160 | if (parameterOverride[name]) {
|
161 | parameterMap[name] = parameterOverride[name];
|
162 | }
|
163 | else if (def.Default) {
|
164 | parameterMap[name] = def.Default;
|
165 | }
|
166 | }
|
167 | return parameterMap;
|
168 | };
|
169 | module.exports = {
|
170 | transformFunctionInDefinition,
|
171 | transformFlowDefinition
|
172 | };
|