workato-connector-sdk 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE.md +20 -0
- data/README.md +1093 -0
- data/exe/workato +5 -0
- data/lib/workato/cli/edit_command.rb +71 -0
- data/lib/workato/cli/exec_command.rb +191 -0
- data/lib/workato/cli/generate_command.rb +105 -0
- data/lib/workato/cli/generators/connector_generator.rb +94 -0
- data/lib/workato/cli/generators/master_key_generator.rb +56 -0
- data/lib/workato/cli/main.rb +142 -0
- data/lib/workato/cli/push_command.rb +208 -0
- data/lib/workato/connector/sdk/account_properties.rb +60 -0
- data/lib/workato/connector/sdk/action.rb +88 -0
- data/lib/workato/connector/sdk/block_invocation_refinements.rb +30 -0
- data/lib/workato/connector/sdk/connector.rb +230 -0
- data/lib/workato/connector/sdk/dsl/account_property.rb +15 -0
- data/lib/workato/connector/sdk/dsl/call.rb +17 -0
- data/lib/workato/connector/sdk/dsl/error.rb +15 -0
- data/lib/workato/connector/sdk/dsl/http.rb +60 -0
- data/lib/workato/connector/sdk/dsl/lookup_table.rb +15 -0
- data/lib/workato/connector/sdk/dsl/time.rb +21 -0
- data/lib/workato/connector/sdk/dsl/workato_code_lib.rb +105 -0
- data/lib/workato/connector/sdk/dsl/workato_schema.rb +15 -0
- data/lib/workato/connector/sdk/dsl.rb +46 -0
- data/lib/workato/connector/sdk/errors.rb +30 -0
- data/lib/workato/connector/sdk/lookup_tables.rb +62 -0
- data/lib/workato/connector/sdk/object_definitions.rb +74 -0
- data/lib/workato/connector/sdk/operation.rb +217 -0
- data/lib/workato/connector/sdk/request.rb +399 -0
- data/lib/workato/connector/sdk/settings.rb +130 -0
- data/lib/workato/connector/sdk/summarize.rb +61 -0
- data/lib/workato/connector/sdk/trigger.rb +96 -0
- data/lib/workato/connector/sdk/version.rb +9 -0
- data/lib/workato/connector/sdk/workato_schemas.rb +37 -0
- data/lib/workato/connector/sdk/xml.rb +35 -0
- data/lib/workato/connector/sdk.rb +58 -0
- data/lib/workato/extension/array.rb +124 -0
- data/lib/workato/extension/case_sensitive_headers.rb +51 -0
- data/lib/workato/extension/currency.rb +15 -0
- data/lib/workato/extension/date.rb +14 -0
- data/lib/workato/extension/enumerable.rb +55 -0
- data/lib/workato/extension/extra_chain_cert.rb +40 -0
- data/lib/workato/extension/hash.rb +13 -0
- data/lib/workato/extension/integer.rb +17 -0
- data/lib/workato/extension/nil_class.rb +17 -0
- data/lib/workato/extension/object.rb +38 -0
- data/lib/workato/extension/phone.rb +14 -0
- data/lib/workato/extension/string.rb +268 -0
- data/lib/workato/extension/symbol.rb +13 -0
- data/lib/workato/extension/time.rb +13 -0
- data/lib/workato/testing/vcr_encrypted_cassette_serializer.rb +38 -0
- data/lib/workato/testing/vcr_multipart_body_matcher.rb +32 -0
- data/lib/workato-connector-sdk.rb +3 -0
- data/templates/.rspec.erb +3 -0
- data/templates/Gemfile.erb +10 -0
- data/templates/connector.rb.erb +37 -0
- data/templates/spec/action_spec.rb.erb +36 -0
- data/templates/spec/connector_spec.rb.erb +18 -0
- data/templates/spec/method_spec.rb.erb +13 -0
- data/templates/spec/object_definition_spec.rb.erb +18 -0
- data/templates/spec/pick_list_spec.rb.erb +13 -0
- data/templates/spec/spec_helper.rb.erb +38 -0
- data/templates/spec/trigger_spec.rb.erb +61 -0
- metadata +372 -0
data/exe/workato
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/encrypted_configuration'
|
4
|
+
|
5
|
+
module Workato
|
6
|
+
module CLI
|
7
|
+
class EditCommand
|
8
|
+
def initialize(path:, options:)
|
9
|
+
@encrypted_file_path = path
|
10
|
+
@key_path = options[:key] || Workato::Connector::Sdk::DEFAULT_MASTER_KEY_PATH
|
11
|
+
@encrypted_config ||= ActiveSupport::EncryptedConfiguration.new(
|
12
|
+
config_path: encrypted_file_path,
|
13
|
+
key_path: @key_path,
|
14
|
+
env_key: Workato::Connector::Sdk::DEFAULT_MASTER_KEY_ENV,
|
15
|
+
raise_if_missing_key: false
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
ensure_editor_available || return
|
21
|
+
ensure_encryption_key_present
|
22
|
+
|
23
|
+
catch_editing_exceptions do
|
24
|
+
encrypted_config.change do |tmp_path|
|
25
|
+
system("#{ENV['EDITOR']} #{tmp_path}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
puts 'File encrypted and saved.'
|
30
|
+
rescue ActiveSupport::MessageEncryptor::InvalidMessage
|
31
|
+
puts "Couldn't decrypt #{encrypted_file_path}. Perhaps you passed the wrong key?"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :key_path,
|
37
|
+
:encrypted_file_path,
|
38
|
+
:encrypted_config
|
39
|
+
|
40
|
+
def ensure_encryption_key_present
|
41
|
+
return if encrypted_config.key.present?
|
42
|
+
|
43
|
+
Generators::MasterKeyGenerator.new.call(@key_path)
|
44
|
+
end
|
45
|
+
|
46
|
+
def ensure_editor_available
|
47
|
+
return true if ENV['EDITOR'].to_s.present?
|
48
|
+
|
49
|
+
puts <<~HELP
|
50
|
+
No $EDITOR to open file in. Assign one like this:
|
51
|
+
|
52
|
+
EDITOR="mate --wait" workato edit #{encrypted_file_path}
|
53
|
+
|
54
|
+
For editors that fork and exit immediately, it's important to pass a wait flag,
|
55
|
+
otherwise the credentials will be saved immediately with no chance to edit.
|
56
|
+
|
57
|
+
HELP
|
58
|
+
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
def catch_editing_exceptions
|
63
|
+
yield
|
64
|
+
rescue Interrupt
|
65
|
+
puts 'Aborted changing file: nothing saved.'
|
66
|
+
rescue ActiveSupport::EncryptedFile::MissingKeyError => e
|
67
|
+
puts e.message
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'ruby-progressbar'
|
5
|
+
|
6
|
+
module Workato
|
7
|
+
module CLI
|
8
|
+
class ExecCommand
|
9
|
+
include Thor::Shell
|
10
|
+
|
11
|
+
def initialize(path:, options:)
|
12
|
+
@path = path
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
load_from_default_files
|
18
|
+
inspect_params(params)
|
19
|
+
output = with_progress { execute_path }
|
20
|
+
show_output(output)
|
21
|
+
output
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :path,
|
27
|
+
:options
|
28
|
+
|
29
|
+
# rubocop:disable Style/GuardClause
|
30
|
+
def load_from_default_files
|
31
|
+
if File.exist?(Workato::Connector::Sdk::DEFAULT_SCHEMAS_PATH)
|
32
|
+
Workato::Connector::Sdk::WorkatoSchemas.from_json
|
33
|
+
end
|
34
|
+
if File.exist?(Workato::Connector::Sdk::DEFAULT_LOOKUP_TABLES_PATH)
|
35
|
+
Workato::Connector::Sdk::LookupTables.from_yaml
|
36
|
+
end
|
37
|
+
if File.exist?(Workato::Connector::Sdk::DEFAULT_ACCOUNT_PROPERTIES_PATH)
|
38
|
+
Workato::Connector::Sdk::AccountProperties.from_yaml
|
39
|
+
end
|
40
|
+
if File.exist?(Workato::Connector::Sdk::DEFAULT_ENCRYPTED_ACCOUNT_PROPERTIES_PATH)
|
41
|
+
Workato::Connector::Sdk::AccountProperties.from_encrypted_yaml
|
42
|
+
end
|
43
|
+
end
|
44
|
+
# rubocop:enable Style/GuardClause
|
45
|
+
|
46
|
+
def params
|
47
|
+
@params ||= {
|
48
|
+
connector: connector,
|
49
|
+
settings: settings,
|
50
|
+
input: from_json(options[:input]),
|
51
|
+
output: from_json(options[:input]),
|
52
|
+
webhook_subscribe_output: from_json(options[:input]),
|
53
|
+
args: from_json(options[:args]).presence || [],
|
54
|
+
extended_input_schema: from_json(options[:extended_input_schema]).presence || [],
|
55
|
+
extended_output_schema: from_json(options[:extended_output_schema]).presence || [],
|
56
|
+
config_fields: from_json(options[:config_fields]),
|
57
|
+
closure: from_json(options[:closure], parse_json_times: true).presence,
|
58
|
+
payload: from_json(options[:webhook_payload]),
|
59
|
+
params: from_json(options[:webhook_params]),
|
60
|
+
headers: from_json(options[:webhook_headers]),
|
61
|
+
webhook_url: options[:webhook_url],
|
62
|
+
recipe_id: SecureRandom.uuid
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def connector
|
67
|
+
Workato::Connector::Sdk::Connector.from_file(
|
68
|
+
options[:connector] || Workato::Connector::Sdk::DEFAULT_CONNECTOR_PATH,
|
69
|
+
settings
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def settings
|
74
|
+
return @settings if defined?(@settings)
|
75
|
+
|
76
|
+
settings_store = Workato::Connector::Sdk::Settings.new(
|
77
|
+
path: options[:settings],
|
78
|
+
name: options[:connection],
|
79
|
+
key_path: options[:key]
|
80
|
+
)
|
81
|
+
@settings = settings_store.read
|
82
|
+
|
83
|
+
Workato::Connector::Sdk::Operation.on_settings_updated = lambda do |_, _, exception, new_settings|
|
84
|
+
$stdout.pause if verbose?
|
85
|
+
say('')
|
86
|
+
say("Refresh token triggered on response \"#{exception}\"")
|
87
|
+
loop do
|
88
|
+
answer = ask('Updated settings file with new connection attributes? (Yes or No)').to_s.downcase
|
89
|
+
break if %w[n no].include?(answer)
|
90
|
+
break settings_store.update(new_settings) if %w[y yes].include?(answer)
|
91
|
+
end
|
92
|
+
$stdout.resume if verbose?
|
93
|
+
end
|
94
|
+
|
95
|
+
@settings
|
96
|
+
end
|
97
|
+
|
98
|
+
def from_json(path, parse_json_times: false)
|
99
|
+
old_parse_json_times = ActiveSupport.parse_json_times
|
100
|
+
::ActiveSupport.parse_json_times = parse_json_times
|
101
|
+
result = path ? ::ActiveSupport::JSON.decode(File.read(path)) : {}
|
102
|
+
::ActiveSupport.parse_json_times = old_parse_json_times
|
103
|
+
result
|
104
|
+
end
|
105
|
+
|
106
|
+
def inspect_params(params)
|
107
|
+
return unless verbose?
|
108
|
+
|
109
|
+
if params[:settings].present?
|
110
|
+
say('SETTINGS')
|
111
|
+
jj params[:settings]
|
112
|
+
end
|
113
|
+
|
114
|
+
say('INPUT')
|
115
|
+
jj params[:input]
|
116
|
+
end
|
117
|
+
|
118
|
+
def execute_path
|
119
|
+
methods = path.split('.')
|
120
|
+
method = methods.pop
|
121
|
+
object = methods.inject(params[:connector]) { |obj, m| obj.public_send(m) }
|
122
|
+
parameters = object.method(method).parameters.reject { |p| p[0] == :block }.map(&:second)
|
123
|
+
args = params.values_at(*parameters)
|
124
|
+
if parameters.last == :args
|
125
|
+
args = args.take(args.length - 1) + Array.wrap(args.last).flatten(1)
|
126
|
+
end
|
127
|
+
object.public_send(method, *args)
|
128
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
129
|
+
raise e if options[:debug]
|
130
|
+
|
131
|
+
e.message
|
132
|
+
end
|
133
|
+
|
134
|
+
def show_output(output)
|
135
|
+
if options[:output].present?
|
136
|
+
File.open(options[:output], 'w') do |f|
|
137
|
+
f.write(JSON.dump(output))
|
138
|
+
end
|
139
|
+
elsif options[:output].nil?
|
140
|
+
say('OUTPUT') if verbose?
|
141
|
+
jj output
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def verbose?
|
146
|
+
!!options[:verbose]
|
147
|
+
end
|
148
|
+
|
149
|
+
def with_progress
|
150
|
+
return yield unless verbose?
|
151
|
+
|
152
|
+
say('')
|
153
|
+
|
154
|
+
old_stdout = $stdout
|
155
|
+
progress_bar = ProgressBar.create(total: nil, output: old_stdout)
|
156
|
+
$stdout = ProgressLogger.new(progress_bar)
|
157
|
+
RestClient.log = $stdout
|
158
|
+
|
159
|
+
dots = Thread.start do
|
160
|
+
loop do
|
161
|
+
progress_bar.increment unless progress_bar.paused?
|
162
|
+
sleep(0.2)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
output = yield
|
166
|
+
dots.kill
|
167
|
+
progress_bar.finish
|
168
|
+
|
169
|
+
$stdout = old_stdout
|
170
|
+
|
171
|
+
say('')
|
172
|
+
|
173
|
+
output
|
174
|
+
end
|
175
|
+
|
176
|
+
class ProgressLogger < SimpleDelegator
|
177
|
+
def initialize(progress)
|
178
|
+
super($stdout)
|
179
|
+
@progress = progress
|
180
|
+
end
|
181
|
+
|
182
|
+
delegate :log, :pause, :resume, to: :@progress
|
183
|
+
|
184
|
+
alias << log
|
185
|
+
alias write log
|
186
|
+
alias puts log
|
187
|
+
alias print log
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Workato
|
4
|
+
module CLI
|
5
|
+
class GenerateCommand < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
TEST_TYPES = %w[trigger action pick_list object_definition method].freeze
|
9
|
+
|
10
|
+
def self.source_root
|
11
|
+
File.expand_path('../../../templates', __dir__)
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'test', 'Generate empty test for connector'
|
15
|
+
|
16
|
+
method_option :connector, type: :string, aliases: '-c', desc: 'Path to connector source code',
|
17
|
+
lazy_default: Workato::Connector::Sdk::DEFAULT_CONNECTOR_PATH
|
18
|
+
method_option :action, type: :string, aliases: '-a', desc: 'Create test for Action with name'
|
19
|
+
method_option :trigger, type: :string, aliases: '-t', desc: 'Create test for Trigger with name'
|
20
|
+
method_option :pick_list, type: :string, aliases: '-p', desc: 'Create test for Pick List with name'
|
21
|
+
method_option :object_definition, type: :string, aliases: '-o',
|
22
|
+
desc: 'Create test for Object Definition with name'
|
23
|
+
method_option :method, type: :string, aliases: '-m', desc: 'Create test for Method with name'
|
24
|
+
|
25
|
+
long_desc <<~HELP
|
26
|
+
The 'workato generate test' command analyzes existing connector source and creates an empty test for
|
27
|
+
all connector's definitions.
|
28
|
+
|
29
|
+
Example:
|
30
|
+
|
31
|
+
workato generate test # This generates a skeletal tests for all connector's definitions.
|
32
|
+
|
33
|
+
workato generate test --action=test_action # This generates an empty test for `test_action` only.
|
34
|
+
|
35
|
+
workato generate test --method=some_method
|
36
|
+
HELP
|
37
|
+
|
38
|
+
def test
|
39
|
+
create_spec_files
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'schema', 'Generate schema by JSON example'
|
43
|
+
def schema
|
44
|
+
say <<~HELP
|
45
|
+
Generate schema is not implemented yet.
|
46
|
+
Use Workato UI "JSON to Schema" converter for now.
|
47
|
+
HELP
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def create_spec_files
|
53
|
+
definitions = options.slice(*TEST_TYPES)
|
54
|
+
if definitions.any?
|
55
|
+
definitions.each do |type, name|
|
56
|
+
create_spec_file(type, name)
|
57
|
+
end
|
58
|
+
else
|
59
|
+
ensure_connector_source
|
60
|
+
|
61
|
+
template('spec/connector_spec.rb.erb', 'spec/connector_spec.rb', skip: true)
|
62
|
+
TEST_TYPES.each do |type|
|
63
|
+
(connector.source[type.pluralize] || {}).each do |(name, _definition)|
|
64
|
+
create_spec_file(type, name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def ensure_connector_source
|
71
|
+
return if connector
|
72
|
+
|
73
|
+
error "Can't find connector source code"
|
74
|
+
exit
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_spec_file(type, name)
|
78
|
+
template(
|
79
|
+
partial(type),
|
80
|
+
"spec/#{type}s/#{sanitized_filename(name)}_spec.rb",
|
81
|
+
context: binding,
|
82
|
+
skip: true
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def connector
|
87
|
+
return @connector if @connector
|
88
|
+
|
89
|
+
@connector = Workato::Connector::Sdk::Connector.from_file(
|
90
|
+
options[:connector] || Workato::Connector::Sdk::DEFAULT_CONNECTOR_PATH
|
91
|
+
)
|
92
|
+
rescue StandardError
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def partial(type)
|
97
|
+
"spec/#{type.downcase}_spec.rb.erb"
|
98
|
+
end
|
99
|
+
|
100
|
+
def sanitized_filename(name)
|
101
|
+
name.downcase.gsub(/[^0-9A-z.\-]/, '_')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Workato
|
4
|
+
module CLI
|
5
|
+
module Generators
|
6
|
+
class ConnectorGenerator < Thor::Group
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
argument :path, type: :string
|
10
|
+
|
11
|
+
def self.source_root
|
12
|
+
File.expand_path('../../../../templates', __dir__)
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_root
|
16
|
+
self.destination_root = path
|
17
|
+
|
18
|
+
empty_directory '.'
|
19
|
+
FileUtils.cd(path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_gemfile
|
23
|
+
template('Gemfile.erb', 'Gemfile')
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_connector_file
|
27
|
+
template('connector.rb.erb', 'connector.rb')
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_spec_files
|
31
|
+
template('.rspec.erb', '.rspec')
|
32
|
+
|
33
|
+
@vcr_record_mode = ask(<<~HELP)
|
34
|
+
Please select default HTTP mocking behavior suitable for your project?
|
35
|
+
|
36
|
+
1 - secure. Cause an error to be raised for any unknown requests, all request recordings are encrypted.
|
37
|
+
To record a new cassette you need set VCR_RECORD_MODE environment variable
|
38
|
+
|
39
|
+
Example: VCR_RECORD_MODE=once bundle exec rspec spec/actions/test_action_spec.rb
|
40
|
+
|
41
|
+
2 - simple. Record new interaction if it is a new request, requests are stored as plain text and expose secret tokens.
|
42
|
+
HELP
|
43
|
+
|
44
|
+
MasterKeyGenerator.new.call if @vcr_record_mode == '1'
|
45
|
+
|
46
|
+
template('spec/spec_helper.rb.erb', 'spec/spec_helper.rb')
|
47
|
+
template('spec/connector_spec.rb.erb', 'spec/connector_spec.rb')
|
48
|
+
end
|
49
|
+
|
50
|
+
def bundle_install
|
51
|
+
bundle_command('install')
|
52
|
+
end
|
53
|
+
|
54
|
+
def show_next_steps
|
55
|
+
say <<~HELP
|
56
|
+
The new Workato Custom Connector project created at #{path}.
|
57
|
+
|
58
|
+
Now, edit the created file. Add actions, triggers or methods and generate tests for them
|
59
|
+
|
60
|
+
cd #{path} && workato generate test
|
61
|
+
HELP
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
attr_reader :vcr_record_mode
|
67
|
+
|
68
|
+
def name
|
69
|
+
File.basename(path)
|
70
|
+
end
|
71
|
+
|
72
|
+
def bundle_command(command)
|
73
|
+
say_status :run, "bundle #{command}"
|
74
|
+
|
75
|
+
bundle_command = Gem.bin_path('bundler', 'bundle')
|
76
|
+
|
77
|
+
require 'bundler'
|
78
|
+
Bundler.with_original_env do
|
79
|
+
exec_bundle_command(bundle_command, command)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def exec_bundle_command(bundle_command, command)
|
84
|
+
full_command = %("#{Gem.ruby}" "#{bundle_command}" #{command})
|
85
|
+
if options[:quiet]
|
86
|
+
system(full_command, out: File::NULL)
|
87
|
+
else
|
88
|
+
system(full_command)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|