workato-connector-sdk 0.1.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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