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