Repository: aliyun/fun (js)

Issue:
trigger 支持配置手动配置 role
背景

> 我没有权限登陆页面添加触发器。并且我的账号没有创建角色的权限，因为创建触发器的时候只能选择管理员帮我建立好的角色。但是用fun创建触发器的时候，就自动会创建角色，导致我无法创建触发器。


Pull request description:
sunfeiyu/trigger_support_manual_configuration_role
fix #253 

Patch:
diff --git a/docs/specs/2018-04-03-zh-cn.md b/docs/specs/2018-04-03-zh-cn.md
index b730dae..4cc3455 100644
--- a/docs/specs/2018-04-03-zh-cn.md
+++ b/docs/specs/2018-04-03-zh-cn.md
@@ -328,6 +328,7 @@ Properties:
 ---|:---:|---
 AuthType | `string` | **必填。** 鉴权类型，可选值：ANONYMOUS、FUNCTION。
 Methods | `array` | **必填。** HTTP 触发器支持的访问方法。可选值为：GET、POST、PUT、DELETE、HEAD。
+InvocationRole | `string` | **可选。** 使用一个 RAM 角色的 ARN 为函数指定执行角色，事件源会使用该角色触发函数执行，请确保该角色有调用函数的权限。ARN 的形式如下：`acs:ram::1234567890:role/fc-invoke-test`
 
 ##### 示例：HTTP 事件源对象
 
@@ -351,6 +352,7 @@ SourceConfig | `object` | **必填。** 数据源的 Logstore 名称。触发器
 JobConfig | `object` | **必填。** 包含两个可配置属性，一个是 MaxRetryTime，表示日志服务触发函数执行时，如果遇到错误，所允许的最大尝试次数。另一个是 TriggerInterval，表示日志服务触发函数执行的间隔。
 LogConfig | `object` | **必填。** 包含三个可配置属性。第一个是 Project，表示日志服务 Project 名称。第二个是 Logstore，表示触发函数执行时，产生的日志会记录到该 Logstore。第三个是可选属性 FunctionParameter，它本身也是一个 object，当事件触发时，会连同它的内容一起发送给函数。
 Enable | `boolean` | 表示是否启用该触发器。
+InvocationRole | `string` | **可选。** 使用一个 RAM 角色的 ARN 为函数指定执行角色，事件源会使用该角色触发函数执行，请确保该角色有调用函数的权限。ARN 的形式如下：`acs:ram::1234567890:role/fc-invoke-test`
 
 ##### 示例：Log 事件源对象
 
@@ -381,6 +383,7 @@ SubscriptionObjects | `array` | **必填。** 订阅对象，当前支持到表
 Retry | `string` | **可选。** 重试次数，可选值：[0,3], 默认值为3。
 Concurrency | `string` | **可选。** 调用并发量，可选值：[1，5], 默认值为1
 EventFormat | `string` | **可选。** event格式，可选值：json, protobuf, 默认值为 protobuf。
+InvocationRole | `string` | **可选。** 使用一个 RAM 角色的 ARN 为函数指定执行角色，事件源会使用该角色触发函数执行，请确保该角色有调用函数的权限。ARN 的形式如下：`acs:ram::1234567890:role/fc-invoke-test`
 
 
 ##### 示例：RDS 事件源对象
@@ -410,6 +413,7 @@ Region   | `string` | **可选。** mns topic 所在的 region，如果不填，
 NotifyContentFormat   | `string` | **可选。** 推送给函数入参 event 的格式，可选值：STREAM, JSON, 默认值为: STREAM
 NotifyStrategy   | `string` | **可选。** 调用函数的重试策略，可选值：BACKOFF_RETRY, EXPONENTIAL_DECAY_RETRY, 默认值为: BACKOFF_RETRY, 详情参考 [NotifyStrategy](https://help.aliyun.com/document_detail/27481.html)
 FilterTag   | `string` | **可选。** 描述了该订阅中消息过滤的标签（标签一致的消息才会被推送）,不超过 16 个字符的字符串，默认不进行消息过滤，即默认不填写该字段
+InvocationRole | `string` | **可选。** 使用一个 RAM 角色的 ARN 为函数指定执行角色，事件源会使用该角色触发函数执行，请确保该角色有调用函数的权限。ARN 的形式如下：`acs:ram::1234567890:role/fc-invoke-test`
 
 ##### 示例：MNSTopic 事件源对象
 
@@ -433,6 +437,7 @@ mns-topic-trigger-test: # trigger name
 ---|:---:|---
 InstanceName| `string` | **必填。** 表格存储实例的名字
 TableName   | `string` | **必填。** 实例中的表名
+InvocationRole | `string` | **可选。** 使用一个 RAM 角色的 ARN 为函数指定执行角色，事件源会使用该角色触发函数执行，请确保该角色有调用函数的权限。ARN 的形式如下：`acs:ram::1234567890:role/fc-invoke-test`
 
 ##### 示例：TableStore 事件源对象
 
@@ -455,6 +460,7 @@ Events| `array` | **必填。** 为 OSS 端触发函数执行的事件，比如
 BucketName| `stirng` | **必填。** 为 OSS 中对应的 bucket 名称。可参照[OSS 示例](https://help.aliyun.com/document_detail/74762.html)
 Filter   | `object` | **必填。** 为 OSS 对象过滤参数，满足过滤条件的 OSS 对象才可以触发函数，包含一个配置属性 key，表示过滤器支持过滤的对象键 (key)。 
 Key | [OSS Key 对象](#OSS-Key-配置对象) | **必填。** 过滤器支持过滤的对象键
+InvocationRole | `string` | **可选。** 使用一个 RAM 角色的 ARN 为函数指定执行角色，事件源会使用该角色触发函数执行，请确保该角色有调用函数的权限。ARN 的形式如下：`acs:ram::1234567890:role/fc-invoke-test`
 
 ##### 示例：OSS 事件源对象
 
@@ -484,6 +490,7 @@ EventName| `stirng` | **必填。** 为 CDN 端触发函数执行的事件，一
 EventVersion| `stirng` | **必填。** 为 CDN 端触发函数执行事件的版本，一经创建不能更改
 Notes| `stirng` | **必填。** 备注信息
 Filter   | [CDN Filter 对象](#CDN-Filter-配置对象) | **必填。** 过滤器（至少需要一个过滤器）
+InvocationRole | `string` | **可选。** 使用一个 RAM 角色的 ARN 为函数指定执行角色，事件源会使用该角色触发函数执行，请确保该角色有调用函数的权限。ARN 的形式如下：`acs:ram::1234567890:role/fc-invoke-test`
 
 ##### 示例：CDN 事件源对象
 
diff --git a/docs/specs/2018-04-03.md b/docs/specs/2018-04-03.md
index 3485d2a..6637ecf 100644
--- a/docs/specs/2018-04-03.md
+++ b/docs/specs/2018-04-03.md
@@ -280,6 +280,7 @@ Property Name | Type | Description
 CronExpression | `string` | **Required.** The time when the function was triggered supports two settings: @every, cron expression.
 Payload | `string` | Represents the input of the trigger event itself and can be customized by the user.
 Enable | `boolean` | Whether to enable the Time Trigger.
+InvocationRole | `string` | **Optional.** Use the ARN of a RAM role to specify the invocation role for the function, and the event source will use this role to trigger the function execution. Make sure that the role has permission to invoke the function. The form of ARN is as follows: `acs:ram::1234567890:role/fc-invoke-test`
 
 ##### Example: Timer event source object
 
@@ -301,6 +302,7 @@ Property Name | Type | Description
 ---|:---:|---
 AuthType | `string` | **Required.** Authentication type, optional values: ANONYMOUS, FUNCTION.
 Methods | `array` | **Required.** HTTP triggers support access methods. The optional values ​​are: GET, POST, PUT, DELETE, HEAD.
+InvocationRole | `string` | **Optional.** Use the ARN of a RAM role to specify the invocation role for the function, and the event source will use this role to trigger the function execution. Make sure that the role has permission to invoke the function. The form of ARN is as follows: `acs:ram::1234567890:role/fc-invoke-test`
 
 ##### Example: HTTP event source object
 
@@ -324,6 +326,7 @@ SourceConfig | `object` | **Required.** The datasource's Logstore name. Triggers
 JobConfig | `object` | **Required.** There are two configurable properties, one of which is MaxRetryTime, which represents the maximum number of attempts allowed if the logging service triggers a function execution. The other is TriggerInterval, which indicates the interval at which the logging service triggers the execution of the function.
 LogConfig | `object` | **Required.** There are three configurable properties. The first is Project, which represents the log service Project name. The second is the Logstore, which means that when the trigger function is executed, the resulting log will be logged to the Logstore. The third is an optional property, FunctionParameter, which is itself an object that, when the event fires, is sent to the function along with its contents.
 Enable | `boolean` | Indicates whether the trigger is enabled. 
+InvocationRole | `string` | **Optional.** Use the ARN of a RAM role to specify the invocation role for the function, and the event source will use this role to trigger the function execution. Make sure that the role has permission to invoke the function. The form of ARN is as follows: `acs:ram::1234567890:role/fc-invoke-test`
 
 ##### Example: Log event source object
 
@@ -354,6 +357,7 @@ SubscriptionObjects | `array` | **Required.** subscription objects are those obj
 Retry | `string` | **Optional.** invoke concurrency is the max number of functions invoked concurrently, set this option to 1 then the function will be invoked sequentially in the same order as the transaction is committed in binlog.。
 Concurrency | `string` | **Optional.** invoke concurrency, If you care about the order of events, set this option to 1 , the function will be called in the order in which the transaction is committed in binlog, otherwise you can increase concurrency to improve performance.
 EventFormat | `string` | **Optional.** notification event format, currently support json and protobuf
+InvocationRole | `string` | **Optional.** Use the ARN of a RAM role to specify the invocation role for the function, and the event source will use this role to trigger the function execution. Make sure that the role has permission to invoke the function. The form of ARN is as follows: `acs:ram::1234567890:role/fc-invoke-test`
 
 
 ##### Example: RDS event source object
@@ -383,6 +387,7 @@ Region   | `string` | **Optional.** Region of MNS topic, if not filled in, defau
 NotifyContentFormat   | `string` | **Optional.** notification event format, currently support STREAM and JSON， default is STREAM
 NotifyStrategy   | `string` | **Optional.** Retry strategy for invoking function, currently support BACKOFF_RETRY and EXPONENTIAL_DECAY_RETRY,  default is BACKOFF_RETRY,  details refer to [NotifyStrategy](https://help.aliyun.com/document_detail/27481.html)
 FilterTag   | `string` | **Optional.** Message filter tag in the created subscription (Only messages with consistent tags are pushed.), The value is a string of no more than 16 characters, default is no message filter. 
+InvocationRole | `string` | **Optional.** Use the ARN of a RAM role to specify the invocation role for the function, and the event source will use this role to trigger the function execution. Make sure that the role has permission to invoke the function. The form of ARN is as follows: `acs:ram::1234567890:role/fc-invoke-test`
 
 ##### Example: MNSTopic event source object
 
@@ -406,6 +411,7 @@ Property Name | Type | Description
 ---|:---:|---
 InstanceName| `string` | **Required.** InstanceName
 TableName   | `string` | **Required.** TableName
+InvocationRole | `string` | **Optional.** Use the ARN of a RAM role to specify the invocation role for the function, and the event source will use this role to trigger the function execution. Make sure that the role has permission to invoke the function. The form of ARN is as follows: `acs:ram::1234567890:role/fc-invoke-test`
 
 ##### Example: TableStore event source object
 
@@ -429,6 +435,7 @@ Events | `array` | **Required.** Indicates the type of an OSS event.
 BucketName | `string` | **Required.** the corresponding bucket name in OSS. can refer to [OSS example](https://www.alibabacloud.com/help/doc-detail/31885.html).
 Filter | `object` | **Required.** Filters OSS events. Only OSS objects that meet the filtering condition can trigger a function. This parameter has a key attribute:
 Key | [`OSS Key configuration object`](#OSS-Key-configuration-object) | **Required** Indicates the key supported by the filter.
+InvocationRole | `string` | **Optional.** Use the ARN of a RAM role to specify the invocation role for the function, and the event source will use this role to trigger the function execution. Make sure that the role has permission to invoke the function. The form of ARN is as follows: `acs:ram::1234567890:role/fc-invoke-test`
 
 ##### Example: OSS event source object
 
@@ -458,6 +465,7 @@ EventName | `string` | **Required.** is CDN event which invokes the function exe
 EventVersion | `string` | **Required.** is CDN event version which invokes the function execution, can not be changed after creation.
 Notes | `string` | **Required.** Descriptions.
 Filter   | [`CDN Filter configuration object`](#CDN-Filter-configuration-object) | **Required** Filters (Need to have at least one filter).
+InvocationRole | `string` | **Optional.** Use the ARN of a RAM role to specify the invocation role for the function, and the event source will use this role to trigger the function execution. Make sure that the role has permission to invoke the function. The form of ARN is as follows: `acs:ram::1234567890:role/fc-invoke-test`
 
 ##### Example: CDN event source object
 
diff --git a/lib/deploy/deploy-support.js b/lib/deploy/deploy-support.js
index 48c32d5..9dfeec6 100644
--- a/lib/deploy/deploy-support.js
+++ b/lib/deploy/deploy-support.js
@@ -896,12 +896,17 @@ async function makeTrigger({
 
   debug('serviceName is %s, functionName is %s, trigger params is %j', serviceName, functionName, params);
 
-  const invocationRole = await makeInvocationRole(serviceName, functionName, triggerType);
-  if (invocationRole) { 
-    Object.assign(params, {
-      'invocationRole': invocationRole.Arn
-    });
-  }
+  let invocationRoleArn = triggerProperties.InvocationRole;
+
+  if (!invocationRoleArn) { 
+    const invocationRole = await makeInvocationRole(serviceName, functionName, triggerType);
+    invocationRoleArn = invocationRole.Arn;
+  } 
+
+  Object.assign(params, {
+    'invocationRole': invocationRoleArn
+  });
+
   const sourceArn = await getSourceArn(triggerType, triggerProperties);
   if (sourceArn) {
     Object.assign(params, {
diff --git a/lib/validate/schema/cdn-event.js b/lib/validate/schema/cdn-event.js
index 159cc32..499def4 100644
--- a/lib/validate/schema/cdn-event.js
+++ b/lib/validate/schema/cdn-event.js
@@ -11,6 +11,9 @@ const cdnEventSchema = {
     'Properties': {
       'type': 'object',
       'properties': {
+        'InvocationRole': {
+          'type': 'string'
+        },
         'EventName': {
           'type': 'string'
         },
diff --git a/lib/validate/schema/http-event.js b/lib/validate/schema/http-event.js
index bc55db9..b5ad3f2 100644
--- a/lib/validate/schema/http-event.js
+++ b/lib/validate/schema/http-event.js
@@ -11,6 +11,9 @@ const httpEventSchema = {
     'Properties': {
       'type': 'object',
       'properties': {
+        'InvocationRole': {
+          'type': 'string'
+        },
         'AuthType': {
           'type': 'string',
           'enum': ['ANONYMOUS', 'FUNCTION', 'anonymous', 'function']
diff --git a/lib/validate/schema/log-event.js b/lib/validate/schema/log-event.js
index c78ae86..415e8b1 100644
--- a/lib/validate/schema/log-event.js
+++ b/lib/validate/schema/log-event.js
@@ -11,6 +11,9 @@ const logEventSchema = {
     'Properties': {
       'type': 'object',
       'properties': {
+        'InvocationRole': {
+          'type': 'string'
+        },
         'SourceConfig': {
           'type': 'object',
           'properties': {
diff --git a/lib/validate/schema/mnsTopic-event.js b/lib/validate/schema/mnsTopic-event.js
index 56270b3..c9d3fd0 100644
--- a/lib/validate/schema/mnsTopic-event.js
+++ b/lib/validate/schema/mnsTopic-event.js
@@ -11,6 +11,9 @@ const MNSTopicEventSchema = {
     'Properties': {
       'type': 'object',
       'properties': {
+        'InvocationRole': {
+          'type': 'string'
+        },
         'TopicName': {
           'type': 'string'
         },
diff --git a/lib/validate/schema/oss-event.js b/lib/validate/schema/oss-event.js
index 733e333..ab25cd8 100644
--- a/lib/validate/schema/oss-event.js
+++ b/lib/validate/schema/oss-event.js
@@ -11,6 +11,9 @@ const ossEventSchema = {
     'Properties': {
       'type': 'object',
       'properties': {
+        'InvocationRole': {
+          'type': 'string'
+        },
         'events': {
           'type': 'array',
           'items': {
diff --git a/lib/validate/schema/ots-event.js b/lib/validate/schema/ots-event.js
index 832d74e..293c414 100644
--- a/lib/validate/schema/ots-event.js
+++ b/lib/validate/schema/ots-event.js
@@ -11,6 +11,9 @@ const tableStoreEventSchema = {
     'Properties': {
       'type': 'object',
       'properties': {
+        'InvocationRole': {
+          'type': 'string'
+        },
         'InstanceName': {
           'type': 'string'
         },
diff --git a/lib/validate/schema/rds-event.js b/lib/validate/schema/rds-event.js
index 1f46c36..c490481 100644
--- a/lib/validate/schema/rds-event.js
+++ b/lib/validate/schema/rds-event.js
@@ -11,6 +11,9 @@ const rdsEventSchema = {
     'Properties': {
       'type': 'object',
       'properties': {
+        'InvocationRole': {
+          'type': 'string'
+        },
         'InstanceId': {
           'type': 'string'
         },
diff --git a/lib/validate/schema/timer-event.js b/lib/validate/schema/timer-event.js
index d426c4f..6886352 100644
--- a/lib/validate/schema/timer-event.js
+++ b/lib/validate/schema/timer-event.js
@@ -11,6 +11,9 @@ const timerEventSchema = {
     'Properties': {
       'type': 'object',
       'properties': {
+        'InvocationRole': {
+          'type': 'string'
+        },
         'Payload': {
           'type': 'string'
         },


Test patch:
diff --git a/test/deploy/deploy-support.test.js b/test/deploy/deploy-support.test.js
index 694c300..f6894e1 100644
--- a/test/deploy/deploy-support.test.js
+++ b/test/deploy/deploy-support.test.js
@@ -697,4 +697,77 @@ describe('test getFunCodeAsBase64', () => {
     assert.calledWith(zip.pack, './index.js', null);
   });
   
+});
+
+describe('test config invovationRole fot trigger', ()=> {
+  let restoreProcess;
+
+  beforeEach(async () => {
+    sandbox.stub(FC.prototype, 'getTrigger').resolves(undefined);
+    sandbox.stub(FC.prototype, 'createTrigger').resolves({});
+
+    Object.keys(ram).forEach(m => {
+      if (m === 'makeRole') {
+        sandbox.stub(ram, m).resolves({
+          'Role': {
+            'Arn': 'acs:ram::123:role/aliyunfcgeneratedrole-fc'
+          }
+        });
+      } else if (m === 'normalizeRoleOrPoliceName') {
+        sandbox.stub(ram, 'normalizeRoleOrPoliceName').returns('');
+      } else {
+        sandbox.stub(ram, m).resolves({});
+      }
+    });
+
+    deploySupport = await proxyquire('../../lib/deploy/deploy-support', {
+      '@alicloud/fc2': FC,
+      '../ram': ram
+    });
+
+    restoreProcess = setProcess({
+      ACCOUNT_ID: 'ACCOUNT_ID',
+      ACCESS_KEY_ID: 'ACCESS_KEY_ID',
+      ACCESS_KEY_SECRET: 'ACCESS_KEY_SECRET',
+      DEFAULT_REGION: 'cn-shanghai'
+    });
+  });
+
+  afterEach(() => {
+    sandbox.restore();
+    restoreProcess();
+  });
+
+  it('invocationRole  ots trigger ', async ()=> {
+    await deploySupport.makeTrigger(
+      {
+        serviceName: 'serviceName', 
+        functionName: 'functionName', 
+        triggerName: 'triggerName', 
+        triggerType: 'TableStore',
+        triggerProperties: {
+          'InstanceName': 'fc-test-inst',
+          'TableName': 'fc_test_tbl',
+          'InvocationRole': 'acs:ram::987:role/invocation-role-test'
+        }
+      });    
+
+    assert.notCalled(ram.makeRole);
+    assert.notCalled(ram.makePolicy);
+    assert.notCalled(ram.makeAndAttachPolicy);
+    assert.notCalled(ram.attachPolicyToRole);  
+
+    assert.calledWith(
+      FC.prototype.createTrigger,
+      'serviceName',
+      'functionName',
+      {
+        'triggerName': 'triggerName',
+        'triggerType': 'tablestore',
+        'triggerConfig': {},
+        'invocationRole': 'acs:ram::987:role/invocation-role-test',
+        'sourceArn': 'acs:ots:cn-shanghai:ACCOUNT_ID:instance/fc-test-inst/table/fc_test_tbl'
+
+      });
+  });
 });
\ No newline at end of file