1 | 'use strict';
|
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3 | return new (P || (P = Promise))(function (resolve, reject) {
|
4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
7 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8 | });
|
9 | };
|
10 | const _ = require('lodash');
|
11 | const ram = require('./ram');
|
12 | const util = require('util');
|
13 | const debug = require('debug')('fun:trigger');
|
14 | const getProfile = require('./profile').getProfile;
|
15 | const { red, yellow } = require('colors');
|
16 | const { getFcClient } = require('./client');
|
17 | const { iterateResources } = require('./definition');
|
18 | const triggerTypeMapping = {
|
19 | 'Datahub': 'datahub',
|
20 | 'Timer': 'timer',
|
21 | 'HTTP': 'http',
|
22 | 'Log': 'log',
|
23 | 'OSS': 'oss',
|
24 | 'RDS': 'rds',
|
25 | 'MNSTopic': 'mns_topic',
|
26 | 'TableStore': 'tablestore',
|
27 | 'CDN': 'cdn_events'
|
28 | };
|
29 | function getSourceArn(triggerType, triggerProperties) {
|
30 | return __awaiter(this, void 0, void 0, function* () {
|
31 | const profile = yield getProfile();
|
32 | if (triggerType === 'Log') {
|
33 | return `acs:log:${profile.defaultRegion}:${profile.accountId}:project/${triggerProperties.LogConfig.Project}`;
|
34 | }
|
35 | else if (triggerType === 'RDS') {
|
36 | return `acs:rds:${profile.defaultRegion}:${profile.accountId}:dbinstance/${triggerProperties.InstanceId}`;
|
37 | }
|
38 | else if (triggerType === 'MNSTopic') {
|
39 | if (triggerProperties.Region !== undefined) {
|
40 | return `acs:mns:${triggerProperties.Region}:${profile.accountId}:/topics/${triggerProperties.TopicName}`;
|
41 | }
|
42 | return `acs:mns:${profile.defaultRegion}:${profile.accountId}:/topics/${triggerProperties.TopicName}`;
|
43 | }
|
44 | else if (triggerType === 'TableStore') {
|
45 | return `acs:ots:${profile.defaultRegion}:${profile.accountId}:instance/${triggerProperties.InstanceName}/table/${triggerProperties.TableName}`;
|
46 | }
|
47 | else if (triggerType === 'OSS') {
|
48 | return `acs:oss:${profile.defaultRegion}:${profile.accountId}:${triggerProperties.BucketName || triggerProperties.bucketName}`;
|
49 | }
|
50 | else if (triggerType === 'CDN') {
|
51 | return `acs:cdn:*:${profile.accountId}`;
|
52 | }
|
53 | return;
|
54 | });
|
55 | }
|
56 | function getTriggerNameList({ serviceName, functionName }) {
|
57 | return __awaiter(this, void 0, void 0, function* () {
|
58 | const fc = yield getFcClient();
|
59 | var listTriggerResponse = yield fc.listTriggers(serviceName, functionName);
|
60 | var triggerNameArray = [];
|
61 | if (listTriggerResponse && listTriggerResponse.data.triggers) {
|
62 | triggerNameArray = listTriggerResponse.data.triggers.map(p => p.triggerName);
|
63 | }
|
64 | return triggerNameArray;
|
65 | });
|
66 | }
|
67 | function getTriggerConfig(triggerType, triggerProperties) {
|
68 | if (triggerType === 'Timer') {
|
69 | return {
|
70 | payload: triggerProperties.Payload,
|
71 | cronExpression: triggerProperties.CronExpression,
|
72 | enable: triggerProperties.Enable
|
73 | };
|
74 | }
|
75 | else if (triggerType === 'HTTP') {
|
76 | return {
|
77 | authType: (triggerProperties.AuthType).toLowerCase(),
|
78 | methods: triggerProperties.Methods
|
79 | };
|
80 | }
|
81 | else if (triggerType === 'Log') {
|
82 | const logConfig = triggerProperties.LogConfig;
|
83 | const jobConfig = triggerProperties.JobConfig;
|
84 | const sourceConfig = triggerProperties.SourceConfig;
|
85 | return {
|
86 | sourceConfig: {
|
87 | logstore: sourceConfig.Logstore
|
88 | },
|
89 | jobConfig: {
|
90 | maxRetryTime: jobConfig.MaxRetryTime,
|
91 | triggerInterval: jobConfig.TriggerInterval
|
92 | },
|
93 | logConfig: {
|
94 | project: logConfig.Project,
|
95 | logstore: logConfig.Logstore
|
96 | },
|
97 | functionParameter: triggerProperties.FunctionParameter || {},
|
98 | Enable: !(triggerProperties.Enable === false)
|
99 | };
|
100 | }
|
101 | else if (triggerType === 'RDS') {
|
102 | return {
|
103 | subscriptionObjects: triggerProperties.SubscriptionObjects,
|
104 | retry: triggerProperties.Retry,
|
105 | concurrency: triggerProperties.Concurrency,
|
106 | eventFormat: triggerProperties.EventFormat
|
107 | };
|
108 | }
|
109 | else if (triggerType === 'MNSTopic') {
|
110 | var notifyContentFormat = 'STREAM';
|
111 | if (triggerProperties.NotifyContentFormat !== undefined) {
|
112 | notifyContentFormat = triggerProperties.NotifyContentFormat;
|
113 | }
|
114 | var notifyStrategy = 'BACKOFF_RETRY';
|
115 | if (triggerProperties.NotifyStrategy !== undefined) {
|
116 | notifyStrategy = triggerProperties.NotifyStrategy;
|
117 | }
|
118 | var triggerCfg = {
|
119 | NotifyContentFormat: notifyContentFormat,
|
120 | NotifyStrategy: notifyStrategy
|
121 | };
|
122 | if (triggerProperties.FilterTag !== undefined) {
|
123 | triggerCfg.FilterTag = triggerProperties.FilterTag;
|
124 | }
|
125 | return triggerCfg;
|
126 | }
|
127 | else if (triggerType === 'TableStore') {
|
128 | return {};
|
129 | }
|
130 | else if (triggerType === 'OSS') {
|
131 | return {
|
132 | events: triggerProperties.Events || triggerProperties.events,
|
133 | filter: triggerProperties.Filter || triggerProperties.filter
|
134 | };
|
135 | }
|
136 | else if (triggerType === 'CDN') {
|
137 | return {
|
138 | eventName: triggerProperties.EventName,
|
139 | eventVersion: triggerProperties.EventVersion,
|
140 | notes: triggerProperties.Notes,
|
141 | filter: _.mapKeys(triggerProperties.Filter, (value, key) => {
|
142 | return _.lowerFirst(key);
|
143 | })
|
144 | };
|
145 | }
|
146 | console.error(`trigger type is ${triggerType} not supported.`);
|
147 | }
|
148 | function makeInvocationRole(serviceName, functionName, triggerType, qualifier) {
|
149 | return __awaiter(this, void 0, void 0, function* () {
|
150 | if (triggerType === 'Log') {
|
151 | const invocationRoleName = ram.normalizeRoleOrPoliceName(`AliyunFcGeneratedInvocationRole-${serviceName}-${functionName}`);
|
152 | const invocationRole = yield ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
|
153 | 'Statement': [{
|
154 | 'Action': 'sts:AssumeRole',
|
155 | 'Effect': 'Allow',
|
156 | 'Principal': {
|
157 | 'Service': [
|
158 | 'log.aliyuncs.com'
|
159 | ]
|
160 | }
|
161 | }],
|
162 | 'Version': '1'
|
163 | });
|
164 | const policyName = ram.normalizeRoleOrPoliceName(`AliyunFcGeneratedInvocationPolicy-${serviceName}-${functionName}`);
|
165 | yield ram.makePolicy(policyName, {
|
166 | 'Version': '1',
|
167 | 'Statement': [{
|
168 | 'Action': [
|
169 | 'fc:InvokeFunction'
|
170 | ],
|
171 | 'Resource': `acs:fc:*:*:services/${serviceName}/functions/*`,
|
172 | 'Effect': 'Allow'
|
173 | },
|
174 | {
|
175 | 'Action': [
|
176 | 'log:Get*',
|
177 | 'log:List*',
|
178 | 'log:PostLogStoreLogs',
|
179 | 'log:CreateConsumerGroup',
|
180 | 'log:UpdateConsumerGroup',
|
181 | 'log:DeleteConsumerGroup',
|
182 | 'log:ListConsumerGroup',
|
183 | 'log:ConsumerGroupUpdateCheckPoint',
|
184 | 'log:ConsumerGroupHeartBeat',
|
185 | 'log:GetConsumerGroupCheckPoint'
|
186 | ],
|
187 | 'Resource': '*',
|
188 | 'Effect': 'Allow'
|
189 | }
|
190 | ]
|
191 | });
|
192 | yield ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
|
193 | return invocationRole.Role;
|
194 | }
|
195 | else if (triggerType === 'RDS' || triggerType === 'MNSTopic') {
|
196 | const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
|
197 | var tMap = {
|
198 | 'RDS': 'rds',
|
199 | 'MNSTopic': 'mns'
|
200 | };
|
201 | var principalService = util.format('%s.aliyuncs.com', tMap[triggerType]);
|
202 | const invocationRole = yield ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
|
203 | 'Statement': [{
|
204 | 'Action': 'sts:AssumeRole',
|
205 | 'Effect': 'Allow',
|
206 | 'Principal': {
|
207 | 'Service': [
|
208 | principalService
|
209 | ]
|
210 | }
|
211 | }],
|
212 | 'Version': '1'
|
213 | });
|
214 | const policyName = ram.normalizeRoleOrPoliceName(`FunCreatePolicy-${serviceName}-${functionName}`);
|
215 | yield ram.makePolicy(policyName, {
|
216 | 'Version': '1',
|
217 | 'Statement': [{
|
218 | 'Action': [
|
219 | 'fc:InvokeFunction'
|
220 | ],
|
221 | 'Resource': `acs:fc:*:*:services/${serviceName}/functions/*`,
|
222 | 'Effect': 'Allow'
|
223 | }]
|
224 | });
|
225 | yield ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
|
226 | return invocationRole.Role;
|
227 | }
|
228 | else if (triggerType === 'TableStore') {
|
229 | const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
|
230 | const invocationRole = yield ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
|
231 | 'Statement': [{
|
232 | 'Action': 'sts:AssumeRole',
|
233 | 'Effect': 'Allow',
|
234 | 'Principal': {
|
235 | 'RAM': [
|
236 | 'acs:ram::1604337383174619:root'
|
237 | ]
|
238 | }
|
239 | }],
|
240 | 'Version': '1'
|
241 | });
|
242 | const invkPolicyName = ram.normalizeRoleOrPoliceName(`FunCreateInvkPolicy-${serviceName}-${functionName}`);
|
243 | yield ram.makePolicy(invkPolicyName, {
|
244 | 'Version': '1',
|
245 | 'Statement': [{
|
246 | 'Action': [
|
247 | 'fc:InvokeFunction'
|
248 | ],
|
249 | 'Resource': '*',
|
250 | 'Effect': 'Allow'
|
251 | }]
|
252 | });
|
253 | yield ram.attachPolicyToRole(invkPolicyName, invocationRoleName, 'Custom');
|
254 | const otsReadPolicyName = ram.normalizeRoleOrPoliceName(`FunCreateOtsReadPolicy-${serviceName}-${functionName}`);
|
255 | yield ram.makePolicy(otsReadPolicyName, {
|
256 | 'Version': '1',
|
257 | 'Statement': [{
|
258 | 'Action': [
|
259 | 'ots:BatchGet*',
|
260 | 'ots:Describe*',
|
261 | 'ots:Get*',
|
262 | 'ots:List*'
|
263 | ],
|
264 | 'Resource': '*',
|
265 | 'Effect': 'Allow'
|
266 | }]
|
267 | });
|
268 | yield ram.attachPolicyToRole(otsReadPolicyName, invocationRoleName, 'Custom');
|
269 | return invocationRole.Role;
|
270 | }
|
271 | else if (triggerType === 'OSS') {
|
272 | const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
|
273 | const invocationRole = yield ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
|
274 | 'Statement': [
|
275 | {
|
276 | 'Action': 'sts:AssumeRole',
|
277 | 'Effect': 'Allow',
|
278 | 'Principal': {
|
279 | 'Service': [
|
280 | 'oss.aliyuncs.com'
|
281 | ]
|
282 | }
|
283 | }
|
284 | ],
|
285 | 'Version': '1'
|
286 | });
|
287 | const policyName = ram.normalizeRoleOrPoliceName(`FunCreateOSSPolicy-${serviceName}-${functionName}`);
|
288 | yield ram.makePolicy(policyName, {
|
289 | 'Version': '1',
|
290 | 'Statement': [{
|
291 | 'Action': [
|
292 | 'fc:InvokeFunction'
|
293 | ],
|
294 | 'Resource': qualifier ? `acs:fc:*:*:services/${serviceName}.*/functions/*` : `acs:fc:*:*:services/${serviceName}/functions/*`,
|
295 | 'Effect': 'Allow'
|
296 | }]
|
297 | });
|
298 | yield ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
|
299 | return invocationRole.Role;
|
300 | }
|
301 | else if (triggerType === 'CDN') {
|
302 | const invocationRoleName = ram.normalizeRoleOrPoliceName(`FunCreateRole-${serviceName}-${functionName}`);
|
303 | const invocationRole = yield ram.makeRole(invocationRoleName, true, 'Used for fc invocation', {
|
304 | 'Statement': [
|
305 | {
|
306 | 'Action': 'sts:AssumeRole',
|
307 | 'Effect': 'Allow',
|
308 | 'Principal': {
|
309 | 'Service': [
|
310 | 'cdn.aliyuncs.com'
|
311 | ]
|
312 | }
|
313 | }
|
314 | ],
|
315 | 'Version': '1'
|
316 | });
|
317 | const policyName = ram.normalizeRoleOrPoliceName(`FunCreateCDNPolicy-${serviceName}-${functionName}`);
|
318 | yield ram.makePolicy(policyName, {
|
319 | 'Version': '1',
|
320 | 'Statement': [{
|
321 | 'Action': [
|
322 | 'fc:InvokeFunction'
|
323 | ],
|
324 | 'Resource': `acs:fc:*:*:services/${serviceName}/functions/*`,
|
325 | 'Effect': 'Allow'
|
326 | }]
|
327 | });
|
328 | yield ram.attachPolicyToRole(policyName, invocationRoleName, 'Custom');
|
329 | return invocationRole.Role;
|
330 | }
|
331 | return;
|
332 | });
|
333 | }
|
334 | function deleteTrigger(serviceName, functionName, triggerName) {
|
335 | return __awaiter(this, void 0, void 0, function* () {
|
336 | const fc = yield getFcClient();
|
337 | yield fc.deleteTrigger(serviceName, functionName, triggerName);
|
338 | });
|
339 | }
|
340 | function makeTrigger({ serviceName, functionName, triggerName, triggerType, triggerProperties }) {
|
341 | return __awaiter(this, void 0, void 0, function* () {
|
342 | const fc = yield getFcClient();
|
343 | var trigger;
|
344 | try {
|
345 | trigger = yield fc.getTrigger(serviceName, functionName, triggerName);
|
346 | }
|
347 | catch (ex) {
|
348 | if (ex.code !== 'TriggerNotFound') {
|
349 | throw ex;
|
350 | }
|
351 | }
|
352 | const params = {
|
353 | triggerType: triggerTypeMapping[triggerType],
|
354 | triggerConfig: getTriggerConfig(triggerType, triggerProperties)
|
355 | };
|
356 | debug('serviceName is %s, functionName is %s, trigger params is %j', serviceName, functionName, params);
|
357 | let invocationRoleArn = triggerProperties.InvocationRole;
|
358 | if (!invocationRoleArn) {
|
359 | const invocationRole = yield makeInvocationRole(serviceName, functionName, triggerType, triggerProperties.Qualifier);
|
360 | if (invocationRole) {
|
361 | invocationRoleArn = invocationRole.Arn;
|
362 | }
|
363 | }
|
364 | if (invocationRoleArn) {
|
365 | Object.assign(params, {
|
366 | 'invocationRole': invocationRoleArn
|
367 | });
|
368 | }
|
369 | const sourceArn = yield getSourceArn(triggerType, triggerProperties);
|
370 | if (sourceArn) {
|
371 | Object.assign(params, {
|
372 | 'sourceArn': sourceArn
|
373 | });
|
374 | }
|
375 | if (triggerProperties.Qualifier) {
|
376 | Object.assign(params, {
|
377 | 'qualifier': triggerProperties.Qualifier
|
378 | });
|
379 | }
|
380 | if (!trigger) {
|
381 | params.triggerName = triggerName;
|
382 | trigger = yield fc.createTrigger(serviceName, functionName, params);
|
383 | }
|
384 | else {
|
385 | if (triggerType === 'TableStore' || triggerType === 'MNSTopic') {
|
386 |
|
387 |
|
388 |
|
389 |
|
390 | console.log(red(`\t\tWarning: TableStore and MNSTopic Trigger cann't update`));
|
391 | return;
|
392 | }
|
393 | trigger = yield fc.updateTrigger(serviceName, functionName, triggerName, params);
|
394 | }
|
395 | return trigger;
|
396 | });
|
397 | }
|
398 | function findFunctionsInCustomDomain(tpl) {
|
399 | const functions = [];
|
400 | iterateResources(tpl.Resources, 'Aliyun::Serverless::CustomDomain', (domainLogicId, domainDefinition) => {
|
401 | const properties = (domainDefinition.Properties || {});
|
402 | const routeConfig = properties.RouteConfig || {};
|
403 | const routes = routeConfig.Routes || routeConfig.routes;
|
404 | if (_.isEmpty(routes)) {
|
405 | return;
|
406 | }
|
407 | for (const route of Object.entries(routes)) {
|
408 | const serviceName = route[1].ServiceName || route[1].serviceName;
|
409 | const functionName = route[1].FunctionName || route[1].functionName;
|
410 | functions.push({
|
411 | serviceName,
|
412 | functionName
|
413 | });
|
414 | }
|
415 | });
|
416 | return functions;
|
417 | }
|
418 | function functionBindCustomDomain(serviceName, functionName, tpl) {
|
419 | const functions = findFunctionsInCustomDomain(tpl);
|
420 | const bindFunction = _.find(functions, { serviceName, functionName });
|
421 | return !_.isEmpty(bindFunction);
|
422 | }
|
423 | function displayTriggerInfo(serviceName, functionName, triggerName, triggerType, triggerProperties, wrap, tpl) {
|
424 | return __awaiter(this, void 0, void 0, function* () {
|
425 | if (triggerType === 'HTTP' || triggerType === 'http') {
|
426 | const profile = yield getProfile();
|
427 | const accountId = profile.accountId;
|
428 | const region = profile.defaultRegion;
|
429 | const resolveWrap = wrap || '';
|
430 | if (triggerName) {
|
431 | console.log(`${resolveWrap}triggerName: ${yellow(triggerName)}`);
|
432 | }
|
433 | console.log(`${resolveWrap}methods: ${yellow(triggerProperties.Methods || triggerProperties.methods)}`);
|
434 | if (!functionBindCustomDomain(serviceName, functionName, tpl)) {
|
435 | const enable = profile.enableCustomEndpoint === true || profile.enableCustomEndpoint === 'true';
|
436 | const endpoint = enable ? profile.endpoint : `https://${accountId}.${region}.fc.aliyuncs.com`;
|
437 | console.log(`${resolveWrap}url: ` + yellow(`${endpoint}/2016-08-15/proxy/${serviceName}/${functionName}/`));
|
438 | console.log(red(`${resolveWrap}Http Trigger will forcefully add a 'Content-Disposition: attachment' field to the response header, which cannot be overwritten \n${resolveWrap}and will cause the response to be downloaded as an attachment in the browser. This issue can be avoided by using CustomDomain.\n`));
|
439 | }
|
440 | }
|
441 | });
|
442 | }
|
443 | module.exports = {
|
444 | getTriggerNameList,
|
445 | getTriggerConfig,
|
446 | makeTrigger,
|
447 | deleteTrigger,
|
448 | makeInvocationRole,
|
449 | displayTriggerInfo
|
450 | };
|