workato-connector-sdk 0.1.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +57 -30
  3. data/exe/workato +12 -1
  4. data/lib/workato/cli/edit_command.rb +1 -3
  5. data/lib/workato/cli/exec_command.rb +23 -7
  6. data/lib/workato/cli/generate_command.rb +1 -2
  7. data/lib/workato/cli/generators/connector_generator.rb +4 -1
  8. data/lib/workato/cli/main.rb +50 -3
  9. data/lib/workato/cli/oauth2_command.rb +180 -0
  10. data/lib/workato/cli/push_command.rb +36 -24
  11. data/lib/workato/connector/sdk/action.rb +52 -4
  12. data/lib/workato/connector/sdk/connection.rb +144 -0
  13. data/lib/workato/connector/sdk/connector.rb +24 -9
  14. data/lib/workato/connector/sdk/dsl/aws.rb +225 -0
  15. data/lib/workato/connector/sdk/dsl/error.rb +1 -1
  16. data/lib/workato/connector/sdk/dsl/http.rb +19 -0
  17. data/lib/workato/connector/sdk/dsl/time.rb +8 -1
  18. data/lib/workato/connector/sdk/dsl/workato_code_lib.rb +21 -4
  19. data/lib/workato/connector/sdk/dsl.rb +2 -0
  20. data/lib/workato/connector/sdk/errors.rb +16 -1
  21. data/lib/workato/connector/sdk/object_definitions.rb +10 -10
  22. data/lib/workato/connector/sdk/operation.rb +38 -40
  23. data/lib/workato/connector/sdk/request.rb +11 -7
  24. data/lib/workato/connector/sdk/schema/field/array.rb +25 -0
  25. data/lib/workato/connector/sdk/schema/field/convertors.rb +189 -0
  26. data/lib/workato/connector/sdk/schema/field/date.rb +28 -0
  27. data/lib/workato/connector/sdk/schema/field/date_time.rb +28 -0
  28. data/lib/workato/connector/sdk/schema/field/integer.rb +27 -0
  29. data/lib/workato/connector/sdk/schema/field/number.rb +27 -0
  30. data/lib/workato/connector/sdk/schema/field/object.rb +25 -0
  31. data/lib/workato/connector/sdk/schema/field/string.rb +26 -0
  32. data/lib/workato/connector/sdk/schema/type/time.rb +53 -0
  33. data/lib/workato/connector/sdk/schema/type/unicode_string.rb +22 -0
  34. data/lib/workato/connector/sdk/schema.rb +230 -0
  35. data/lib/workato/connector/sdk/settings.rb +6 -3
  36. data/lib/workato/connector/sdk/trigger.rb +25 -0
  37. data/lib/workato/connector/sdk/version.rb +1 -1
  38. data/lib/workato/connector/sdk.rb +1 -0
  39. data/lib/workato/extension/string.rb +20 -10
  40. data/lib/workato/web/app.rb +23 -0
  41. data/templates/Gemfile.erb +1 -0
  42. data/templates/spec/action_spec.rb.erb +7 -1
  43. data/templates/spec/trigger_spec.rb.erb +6 -0
  44. metadata +102 -4
@@ -3,6 +3,7 @@
3
3
  require 'uri'
4
4
  require 'ruby-progressbar'
5
5
  require 'zip'
6
+ require 'fileutils'
6
7
 
7
8
  module Workato
8
9
  module CLI
@@ -19,6 +20,7 @@ module Workato
19
20
  'live-eu' => 'https://app.eu.workato.com'
20
21
  }.freeze
21
22
 
23
+ API_USER_PATH = '/api/users/me'
22
24
  API_IMPORT_PATH = '/api/packages/import'
23
25
  API_PACKAGE_PATH = '/api/packages'
24
26
  IMPORT_IN_PROGRESS = 'in_progress'
@@ -30,38 +32,33 @@ module Workato
30
32
  AWAIT_IMPORT_SLEEP_INTERVAL = 15 # seconds
31
33
  AWAIT_IMPORT_TIMEOUT_INTERVAL = 120 # seconds
32
34
 
33
- def initialize(folder:, options:)
34
- @folder_id = folder
35
+ def initialize(options:)
35
36
  @options = options
36
37
  @api_base_url = ENVIRONMENTS.fetch(options[:environment])
37
38
  @api_email = options[:api_email] || ENV[WORKATO_API_EMAIL_ENV]
38
39
  @api_token = options[:api_token] || ENV[WORKATO_API_TOKEN_ENV]
40
+ @folder_id = options[:folder]
39
41
  end
40
42
 
41
43
  def call
42
- zip_file = build_package
44
+ zip_file_path = build_package
43
45
  say_status :success, 'Build package' if verbose?
44
46
 
45
- import_id = import_package(zip_file)
47
+ import_id = import_package(zip_file_path)
46
48
  say_status :success, 'Upload package' if verbose?
47
49
  say_status :waiting, 'Process package' if verbose?
48
50
 
49
51
  result = await_import(import_id)
50
- if result.fetch('status') == 'failed'
51
- say result.fetch('error').gsub("#{PACKAGE_ENTRY_NAME}.json: ", '')
52
- else
53
- say "Connector was successfully uploaded to #{api_base_url}"
54
- end
55
- rescue StandardError => e
56
- say e.message
52
+ raise human_friendly_error(result) if result.fetch('status') == 'failed'
53
+
54
+ say "Connector was successfully uploaded to #{api_base_url}"
57
55
  ensure
58
- zip_file&.close(true)
56
+ FileUtils.rm_f(zip_file_path) if zip_file_path
59
57
  end
60
58
 
61
59
  private
62
60
 
63
61
  attr_reader :options,
64
- :folder_id,
65
62
  :api_token,
66
63
  :api_email,
67
64
  :api_base_url
@@ -78,16 +75,13 @@ module Workato
78
75
  end
79
76
 
80
77
  def build_package
81
- zip_file = Tempfile.new(['connector', '.zip'])
82
-
83
- ::Zip::OutputStream.open(zip_file.path) { |_| 'no-op' }
84
- ::Zip::File.open(zip_file.path, ::Zip::File::CREATE) do |archive|
85
- add_connector(archive)
86
- add_manifest(archive)
87
- add_logo(archive)
78
+ ::Dir::Tmpname.create(['connector', '.zip']) do |path|
79
+ ::Zip::File.open(path, ::Zip::File::CREATE) do |archive|
80
+ add_connector(archive)
81
+ add_manifest(archive)
82
+ add_logo(archive)
83
+ end
88
84
  end
89
-
90
- zip_file
91
85
  end
92
86
 
93
87
  def add_connector(archive)
@@ -110,11 +104,11 @@ module Workato
110
104
  end
111
105
  end
112
106
 
113
- def import_package(zip_file)
107
+ def import_package(zip_file_path)
114
108
  url = "#{api_base_url}#{API_IMPORT_PATH}/#{folder_id}"
115
109
  response = RestClient.post(
116
110
  url,
117
- File.open(zip_file.path),
111
+ File.open(zip_file_path),
118
112
  auth_headers.merge(
119
113
  'Content-Type' => 'application/zip'
120
114
  )
@@ -197,7 +191,25 @@ module Workato
197
191
  }
198
192
  end
199
193
 
194
+ def folder_id
195
+ @folder_id ||=
196
+ begin
197
+ url = "#{api_base_url}#{API_USER_PATH}"
198
+ response = RestClient.get(url, auth_headers)
199
+
200
+ json = JSON.parse(response.body)
201
+ json.fetch('root_folder_id').tap do |folder_id|
202
+ say_status :success, "Fetch root folder ID: #{folder_id}" if verbose?
203
+ end
204
+ end
205
+ end
206
+
207
+ def human_friendly_error(result)
208
+ result.fetch('error').gsub("#{PACKAGE_ENTRY_NAME}.json: ", '')
209
+ end
210
+
200
211
  private_constant :IMPORT_IN_PROGRESS,
212
+ :API_USER_PATH,
201
213
  :API_IMPORT_PATH,
202
214
  :API_PACKAGE_PATH,
203
215
  :PACKAGE_ENTRY_NAME,
@@ -14,6 +14,8 @@ module Workato
14
14
  RETRY_DELAY = 5.seconds
15
15
  MAX_RETRIES = 3
16
16
 
17
+ MAX_REINVOKES = 5
18
+
17
19
  def initialize(action:, connection: {}, methods: {}, settings: {}, object_definitions: nil)
18
20
  super(
19
21
  operation: action,
@@ -26,10 +28,32 @@ module Workato
26
28
  initialize_retry
27
29
  end
28
30
 
29
- def execute(settings = nil, input = {}, extended_input_schema = [], extended_output_schema = [], &block)
31
+ def execute(settings = nil, input = {}, extended_input_schema = [], extended_output_schema = [], continue = {},
32
+ &block)
30
33
  raise InvalidDefinitionError, "'execute' block is required for action" unless block || action[:execute]
31
34
 
32
- super(settings, input, extended_input_schema, extended_output_schema, &(block || action[:execute]))
35
+ loop do
36
+ if @reinvokes_remaining&.zero?
37
+ raise "Max number of reinvokes on SDK Gem reached. Current limit is #{reinvoke_limit}"
38
+ end
39
+
40
+ reinvoke_sleep if @reinvoke_after
41
+
42
+ @reinvoke_after = nil
43
+
44
+ result = super(
45
+ settings,
46
+ input,
47
+ extended_input_schema,
48
+ extended_output_schema,
49
+ continue,
50
+ &(block || action[:execute])
51
+ )
52
+
53
+ break result unless @reinvoke_after
54
+
55
+ continue = @reinvoke_after[:continue]
56
+ end
33
57
  rescue RequestError => e
34
58
  raise e unless retry?(e)
35
59
 
@@ -37,12 +61,28 @@ module Workato
37
61
  sleep(RETRY_DELAY) && retry
38
62
  end
39
63
 
64
+ def invoke(input = {})
65
+ extended_schema = extended_schema(nil, input)
66
+ config_schema = Schema.new(schema: config_fields_schema)
67
+ input_schema = Schema.new(schema: extended_schema[:input])
68
+ output_schema = Schema.new(schema: extended_schema[:output])
69
+
70
+ input = apply_input_schema(input, config_schema + input_schema)
71
+ output = execute(nil, input, input_schema, output_schema)
72
+ apply_output_schema(output, output_schema)
73
+ end
74
+
40
75
  def checkpoint!(continue:, temp_output: nil)
41
- raise NotImplementedError
76
+ # no-op
42
77
  end
43
78
 
44
79
  def reinvoke_after(seconds:, continue:, temp_output: nil)
45
- raise NotImplementedError
80
+ @reinvokes_remaining = (@reinvokes_remaining ? @reinvokes_remaining - 1 : reinvoke_limit)
81
+ @reinvoke_after = {
82
+ seconds: seconds,
83
+ continue: continue,
84
+ temp_output: temp_output
85
+ }
46
86
  end
47
87
 
48
88
  private
@@ -81,6 +121,14 @@ module Workato
81
121
  @retry_methods.include?(exception.method.to_s.downcase)
82
122
  end
83
123
 
124
+ def reinvoke_sleep
125
+ sleep((ENV['WAIT_REINVOKE_AFTER'].presence || @reinvoke_after[:seconds]).to_f)
126
+ end
127
+
128
+ def reinvoke_limit
129
+ @reinvoke_limit ||= (ENV['MAX_REINVOKES'].presence || MAX_REINVOKES).to_i
130
+ end
131
+
84
132
  alias action operation
85
133
  end
86
134
  end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './block_invocation_refinements'
4
+
5
+ module Workato
6
+ module Connector
7
+ module Sdk
8
+ class Connection
9
+ using BlockInvocationRefinements
10
+
11
+ attr_reader :source
12
+
13
+ def initialize(connection: {}, methods: {}, settings: {})
14
+ @methods_source = methods.with_indifferent_access
15
+ @source = connection.with_indifferent_access
16
+ @settings = settings
17
+ end
18
+
19
+ def authorization
20
+ @authorization ||= Authorization.new(
21
+ connection: source,
22
+ methods: methods_source,
23
+ settings: @settings
24
+ )
25
+ end
26
+
27
+ def base_uri(settings = {})
28
+ source[:base_uri]&.call(settings.with_indifferent_access)
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :methods_source
34
+
35
+ class Authorization
36
+ attr_reader :source
37
+
38
+ def initialize(connection: {}, methods: {}, settings: {})
39
+ @connection_source = connection.with_indifferent_access
40
+ @source = (connection[:authorization] || {}).with_indifferent_access
41
+ @methods_source = methods.with_indifferent_access
42
+ @settings = settings
43
+ end
44
+
45
+ def token_url?
46
+ source[:token_url].present?
47
+ end
48
+
49
+ def acquire?
50
+ source[:acquire].present?
51
+ end
52
+
53
+ def refresh?
54
+ source[:refresh].present?
55
+ end
56
+
57
+ def type
58
+ source[:type]
59
+ end
60
+
61
+ def refresh_on
62
+ Array.wrap(source[:refresh_on]).compact
63
+ end
64
+
65
+ def detect_on
66
+ Array.wrap(source[:detect_on]).compact
67
+ end
68
+
69
+ def client_id(settings = {})
70
+ client_id = source[:client_id]
71
+
72
+ if client_id.is_a?(Proc)
73
+ Dsl::WithDsl.execute(settings.with_indifferent_access, &client_id)
74
+ else
75
+ client_id
76
+ end
77
+ end
78
+
79
+ def client_secret(settings = {})
80
+ client_secret_source = source[:client_secret]
81
+
82
+ if client_secret_source.is_a?(Proc)
83
+ Dsl::WithDsl.execute(settings.with_indifferent_access, &client_secret_source)
84
+ else
85
+ client_secret_source
86
+ end
87
+ end
88
+
89
+ def authorization_url(settings = {})
90
+ source[:authorization_url]&.call(settings.with_indifferent_access)
91
+ end
92
+
93
+ def token_url(settings = {})
94
+ source[:token_url]&.call(settings.with_indifferent_access)
95
+ end
96
+
97
+ def acquire(settings = {}, oauth2_code = nil, redirect_url = nil)
98
+ acquire_proc = source[:acquire]
99
+ raise InvalidDefinitionError, "Expect 'acquire' block" unless acquire_proc
100
+
101
+ Workato::Connector::Sdk::Operation.new(
102
+ connection: Connection.new(
103
+ connection: connection_source.merge(
104
+ authorization: source.merge(
105
+ apply: nil
106
+ )
107
+ ),
108
+ methods: methods_source,
109
+ settings: @settings
110
+ ),
111
+ methods: methods_source,
112
+ settings: @settings
113
+ ).execute(settings, { auth_code: oauth2_code, redirect_url: redirect_url }) do |connection, input|
114
+ instance_exec(connection, input[:auth_code], input[:redirect_url], &acquire_proc)
115
+ end
116
+ end
117
+
118
+ def refresh(settings = {}, refresh_token = nil)
119
+ refresh_proc = source[:refresh]
120
+ raise InvalidDefinitionError, "Expect 'refresh' block" unless refresh_proc
121
+
122
+ Workato::Connector::Sdk::Operation.new(
123
+ connection: Connection.new(
124
+ methods: methods_source,
125
+ settings: @settings
126
+ ),
127
+ methods: methods_source,
128
+ settings: @settings
129
+ ).execute(settings, { refresh_token: refresh_token }) do |connection, input|
130
+ instance_exec(connection, input[:refresh_token], &refresh_proc)
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ attr_reader :connection_source,
137
+ :methods_source
138
+ end
139
+
140
+ private_constant :Authorization
141
+ end
142
+ end
143
+ end
144
+ end
@@ -24,9 +24,9 @@ module Workato
24
24
  def actions
25
25
  @actions ||= ActionsProxy.new(
26
26
  actions: source[:actions].presence || {},
27
- object_definitions: object_definitions,
28
27
  methods: methods_source,
29
- connection: connection_source,
28
+ object_definitions: object_definitions,
29
+ connection: connection,
30
30
  settings: settings
31
31
  )
32
32
  end
@@ -34,7 +34,7 @@ module Workato
34
34
  def methods
35
35
  @methods ||= MethodsProxy.new(
36
36
  methods: methods_source,
37
- connection: connection_source,
37
+ connection: connection,
38
38
  settings: settings
39
39
  )
40
40
  end
@@ -45,7 +45,7 @@ module Workato
45
45
  execute: source[:test]
46
46
  },
47
47
  methods: methods_source,
48
- connection: connection_source,
48
+ connection: connection,
49
49
  settings: send(:settings)
50
50
  ).execute(settings)
51
51
  end
@@ -53,9 +53,9 @@ module Workato
53
53
  def triggers
54
54
  @triggers ||= TriggersProxy.new(
55
55
  triggers: source[:triggers].presence || {},
56
- object_definitions: object_definitions,
57
56
  methods: methods_source,
58
- connection: connection_source,
57
+ connection: connection,
58
+ object_definitions: object_definitions,
59
59
  settings: settings
60
60
  )
61
61
  end
@@ -64,7 +64,7 @@ module Workato
64
64
  @object_definitions ||= ObjectDefinitions.new(
65
65
  object_definitions: source[:object_definitions].presence || {},
66
66
  methods: methods_source,
67
- connection: connection_source,
67
+ connection: connection,
68
68
  settings: settings
69
69
  )
70
70
  end
@@ -72,6 +72,14 @@ module Workato
72
72
  def pick_lists
73
73
  @pick_lists ||= PickListsProxy.new(
74
74
  pick_lists: source[:pick_lists].presence || {},
75
+ methods: methods_source,
76
+ connection: connection,
77
+ settings: settings
78
+ )
79
+ end
80
+
81
+ def connection
82
+ @connection ||= Connection.new(
75
83
  methods: methods_source,
76
84
  connection: connection_source,
77
85
  settings: settings
@@ -107,7 +115,7 @@ module Workato
107
115
 
108
116
  def define_action_methods(actions)
109
117
  actions.each do |action, definition|
110
- define_singleton_method(action) do
118
+ define_singleton_method(action) do |input_ = nil|
111
119
  @actions ||= {}
112
120
  @actions[action] ||= Action.new(
113
121
  action: definition,
@@ -116,6 +124,9 @@ module Workato
116
124
  connection: connection,
117
125
  settings: settings
118
126
  )
127
+ return @actions[action] if input_.nil?
128
+
129
+ @actions[action].invoke(input_)
119
130
  end
120
131
  end
121
132
  end
@@ -213,7 +224,7 @@ module Workato
213
224
 
214
225
  def define_trigger_methods(triggers)
215
226
  triggers.each do |trigger, definition|
216
- define_singleton_method(trigger) do
227
+ define_singleton_method(trigger) do |input_ = nil, payload = {}, headers = {}, params = {}|
217
228
  @triggers[trigger] ||= Trigger.new(
218
229
  trigger: definition,
219
230
  object_definitions: object_definitions,
@@ -221,6 +232,10 @@ module Workato
221
232
  connection: connection,
222
233
  settings: settings
223
234
  )
235
+
236
+ return @triggers[trigger] if input_.nil?
237
+
238
+ @triggers[trigger].invoke(input_, payload, headers, params)
224
239
  end
225
240
  end
226
241
  end
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Workato
4
+ module Connector
5
+ module Sdk
6
+ module Dsl
7
+ module AWS
8
+ TEMP_CREDENTIALS_REFRESH_TIMEOUT = 60 # seconds
9
+
10
+ DUMMY_AWS_IAM_EXTERNAL_ID = 'dummy-aws-iam-external-id'
11
+ DUMMY_AWS_WORKATO_ACCOUNT_ID = 'dummy-aws-workato-account-id'
12
+
13
+ AMAZON_ROLE_CLIENT_ID = ENV['AMAZON_ROLE_CLIENT_ID']
14
+ AMAZON_ROLE_CLIENT_KEY = ENV['AMAZON_ROLE_CLIENT_KEY']
15
+ AMAZON_ROLE_CLIENT_SECRET = ENV['AMAZON_ROLE_CLIENT_SECRET']
16
+
17
+ WWW_FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded; charset=utf-8'
18
+
19
+ def aws
20
+ AWS
21
+ end
22
+
23
+ class << self
24
+ def generate_signature(connection:,
25
+ service:,
26
+ region:,
27
+ host: "#{service}.#{region}.amazonaws.com",
28
+ path: '/',
29
+ method: 'GET',
30
+ params: {},
31
+ headers: {},
32
+ payload: '')
33
+
34
+ credentials = if connection[:aws_assume_role].present?
35
+ role_based_auth(settings: connection)
36
+ else
37
+ {
38
+ access_key_id: connection[:aws_api_key],
39
+ secret_access_key: connection[:aws_secret_key]
40
+ }
41
+ end
42
+
43
+ url, headers = create_signature(
44
+ credentials: credentials,
45
+ service: service,
46
+ host: host,
47
+ region: region,
48
+ method: method,
49
+ path: path,
50
+ params: params,
51
+ headers: (headers || {}).with_indifferent_access,
52
+ payload: payload
53
+ )
54
+
55
+ {
56
+ url: url,
57
+ headers: headers
58
+ }.with_indifferent_access
59
+ end
60
+
61
+ def iam_external_id
62
+ settings[:aws_external_id] || DUMMY_AWS_IAM_EXTERNAL_ID
63
+ end
64
+
65
+ def workato_account_id
66
+ settings[:aws_workato_account_id] || AMAZON_ROLE_CLIENT_ID || DUMMY_AWS_WORKATO_ACCOUNT_ID
67
+ end
68
+
69
+ private
70
+
71
+ def on_settings_updated
72
+ Workato::Connector::Sdk::Operation.on_settings_updated
73
+ end
74
+
75
+ def role_based_auth(settings:)
76
+ settings[:aws_external_id] ||= iam_external_id
77
+ temp_credentials = settings[:temp_credentials] || {}
78
+
79
+ # Refresh temp token that will expire within 60 seconds.
80
+ expiration = temp_credentials[:expiration]&.to_time(:utc)
81
+ if !expiration || expiration <= TEMP_CREDENTIALS_REFRESH_TIMEOUT.seconds.from_now
82
+ temp_credentials = refresh_temp_credentials(settings)
83
+ end
84
+ {
85
+ access_key_id: temp_credentials[:api_key],
86
+ secret_access_key: temp_credentials[:secret_key],
87
+ session_token: temp_credentials[:session_token]
88
+ }
89
+ end
90
+
91
+ def refresh_temp_credentials(settings)
92
+ sts_credentials = {
93
+ access_key_id: amazon_role_client_key(settings),
94
+ secret_access_key: amazon_role_client_secret(settings)
95
+ }
96
+
97
+ sts_params = {
98
+ 'Version' => '2011-06-15',
99
+ 'Action' => 'AssumeRole',
100
+ 'RoleSessionName' => 'workato',
101
+ 'RoleArn' => settings[:aws_assume_role],
102
+ 'ExternalId' => settings[:aws_external_id].presence
103
+ }.compact
104
+
105
+ sts_auth_url, sts_auth_headers = create_signature(
106
+ credentials: sts_credentials,
107
+ params: sts_params,
108
+ service: 'sts',
109
+ host: 'sts.amazonaws.com',
110
+ region: 'us-east-1',
111
+ headers: {
112
+ 'Accept' => 'application/xml',
113
+ 'Content-Type' => WWW_FORM_CONTENT_TYPE
114
+ }
115
+ )
116
+
117
+ request_temp_credentials(url: sts_auth_url, headers: sts_auth_headers).tap do |temp_credentials|
118
+ update_settings(settings, temp_credentials)
119
+ end
120
+ rescue StandardError => e
121
+ raise e if settings[:aws_external_id].blank?
122
+
123
+ settings[:aws_external_id] = nil
124
+ retry
125
+ end
126
+
127
+ def update_settings(settings, temp_credentials)
128
+ settings.merge!(temp_credentials: temp_credentials)
129
+ Workato::Connector::Sdk::Operation.on_settings_updated&.call(
130
+ 'Refresh AWS temporary credentials',
131
+ settings
132
+ )
133
+ end
134
+
135
+ def request_temp_credentials(url:, headers:)
136
+ response = RestClient::Request.execute(
137
+ url: url,
138
+ headers: headers,
139
+ method: :get
140
+ )
141
+ response = Workato::Connector::Sdk::Xml.parse_xml_to_hash(response.body)
142
+
143
+ temp_credentials = response.dig('AssumeRoleResponse', 0, 'AssumeRoleResult', 0, 'Credentials', 0)
144
+ {
145
+ session_token: temp_credentials.dig('SessionToken', 0, 'content!'),
146
+ api_key: temp_credentials.dig('AccessKeyId', 0, 'content!'),
147
+ secret_key: temp_credentials.dig('SecretAccessKey', 0, 'content!'),
148
+ expiration: temp_credentials.dig('Expiration', 0, 'content!')
149
+ }
150
+ end
151
+
152
+ def create_signature(credentials:,
153
+ service:,
154
+ host:,
155
+ region:,
156
+ path: '/',
157
+ method: 'GET',
158
+ params: {},
159
+ headers: {},
160
+ payload: '')
161
+ url = URI::HTTPS.build(host: host, path: path, query: params.presence.to_param&.gsub('+', '%20')).to_s
162
+ signer_options = {
163
+ service: service,
164
+ region: region,
165
+ access_key_id: amazon_role_client_key(credentials),
166
+ secret_access_key: amazon_role_client_secret(credentials),
167
+ session_token: credentials[:session_token]
168
+ }
169
+
170
+ apply_service_specific_options(service, headers, signer_options, payload)
171
+
172
+ signer = Aws::Sigv4::Signer.new(signer_options)
173
+ signature = signer.sign_request(http_method: method, url: url, headers: headers, body: payload)
174
+
175
+ headers_with_sig = merge_headers_with_sig_headers(headers, signature.headers)
176
+ headers_with_sig = headers_with_sig.transform_keys { |key| key.gsub(/\b[a-z]/, &:upcase) }
177
+ [url, headers_with_sig]
178
+ end
179
+
180
+ def apply_service_specific_options(service, headers, signer_options, payload)
181
+ accept_headers = headers.key?('Accept') || headers.key?('accept')
182
+ content_type = headers.key?('content-type') || headers.key?('Content-Type')
183
+
184
+ case service
185
+ when 'ec2'
186
+ signer_options[:apply_checksum_header] = false
187
+
188
+ headers.except!('Accept', 'Content-Type')
189
+ when 's3'
190
+ signer_options[:uri_escape_path] = false
191
+
192
+ headers['Accept'] = 'application/xml' unless accept_headers
193
+ headers['Content-Type'] = WWW_FORM_CONTENT_TYPE unless content_type
194
+ headers['X-Amz-Content-SHA256'] = 'UNSIGNED-PAYLOAD' if payload.blank?
195
+ when 'monitoring'
196
+ signer_options[:apply_checksum_header] = false
197
+
198
+ headers['Accept'] = 'application/json' unless accept_headers
199
+ headers['Content-Type'] = WWW_FORM_CONTENT_TYPE unless content_type
200
+ when 'lambda'
201
+ signer_options[:apply_checksum_header] = false
202
+
203
+ headers['Content-Type'] = WWW_FORM_CONTENT_TYPE unless content_type
204
+ end
205
+ end
206
+
207
+ def merge_headers_with_sig_headers(headers, sig_headers)
208
+ headers_keys = headers.transform_keys { |key| key.to_s.downcase }
209
+ sig_headers_to_merge = sig_headers.reject { |key| headers_keys.include?(key.downcase) }
210
+ headers.merge(sig_headers_to_merge)
211
+ end
212
+
213
+ def amazon_role_client_key(settings)
214
+ settings[:access_key_id] || AMAZON_ROLE_CLIENT_KEY
215
+ end
216
+
217
+ def amazon_role_client_secret(settings)
218
+ settings[:secret_access_key] || AMAZON_ROLE_CLIENT_SECRET
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
@@ -6,7 +6,7 @@ module Workato
6
6
  module Dsl
7
7
  module Error
8
8
  def error(message)
9
- raise message
9
+ raise Sdk::RuntimeError, message
10
10
  end
11
11
  end
12
12
  end