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