workato-connector-sdk 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|