wisco 0.4.3 → 0.4.4
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 +4 -4
- data/lib/wisco/commands/settings.rb +363 -0
- data/lib/wisco/settings_store.rb +133 -0
- data/lib/wisco/version.rb +1 -1
- data/lib/wisco.rb +13 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ab77bb62a7ce39484d205397fb9350d3b17f2fbdde6f11ce47517da942608a1
|
|
4
|
+
data.tar.gz: db26dddd97fba3b9f3471d408e0f80750375d0ab18111d36d20d3b603e447daf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5eb44d4bd05b130cfc4fb3f0c59325df601230e3c7be1e1e6b40f608f9ff4202bc0a99ee51a0fff177a2db71aba9e8ecb1dccad2fc7bf1b2a8a747f15a7c0d53
|
|
7
|
+
data.tar.gz: 6887090edc5bf94a7c7aa8d07878738da08ac009d498e18115e65952b2598fb2ec6a98610cf80f976fdecd66d996610874bb74fb81be6c581de7a7bf3973d35f
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require_relative '../config'
|
|
3
|
+
require_relative '../connector'
|
|
4
|
+
require_relative '../settings_store'
|
|
5
|
+
require_relative '../terminal_output'
|
|
6
|
+
|
|
7
|
+
module Wisco
|
|
8
|
+
module Commands
|
|
9
|
+
class Settings < Thor
|
|
10
|
+
def self.exit_on_failure?
|
|
11
|
+
true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
desc 'list', 'List connection sets in the settings file'
|
|
15
|
+
option :format, type: :string, enum: %w[json], desc: 'Machine-readable output (json = array of set names)'
|
|
16
|
+
def list
|
|
17
|
+
store = settings_store
|
|
18
|
+
data = read_settings!(store)
|
|
19
|
+
names = Wisco::SettingsStore.set_names(data)
|
|
20
|
+
|
|
21
|
+
if options[:format] == 'json'
|
|
22
|
+
puts JSON.generate(names)
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
case Wisco::SettingsStore.detect_structure(data)
|
|
27
|
+
when :nested, :mixed
|
|
28
|
+
active = active_connection
|
|
29
|
+
puts "Connection sets (#{store.filename}):\n\n"
|
|
30
|
+
width = names.map(&:length).max || 0
|
|
31
|
+
names.each do |n|
|
|
32
|
+
marker = n == active ? '*' : ' '
|
|
33
|
+
puts " #{marker} #{n.ljust(width)}"
|
|
34
|
+
end
|
|
35
|
+
if active && !active.empty?
|
|
36
|
+
puts
|
|
37
|
+
puts "Active connection (from #{Wisco::WISCO_DIR}/#{Wisco::CONFIG_FILENAME}): #{active}"
|
|
38
|
+
end
|
|
39
|
+
when :flat
|
|
40
|
+
puts "#{store.filename} contains a single, unnamed connection set."
|
|
41
|
+
puts "No named connection sets are defined. Use 'wisco settings add <name>' to create named sets."
|
|
42
|
+
when :none
|
|
43
|
+
puts "No settings file found in #{store.connector_path}."
|
|
44
|
+
puts "Run 'wisco settings add <name>' to create one, or 'workato edit' to create an encrypted file."
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
desc 'set CONNECTION', "Set the project's active connection set"
|
|
49
|
+
def set(connection)
|
|
50
|
+
store = settings_store
|
|
51
|
+
names = begin
|
|
52
|
+
Wisco::SettingsStore.set_names(store.read_all)
|
|
53
|
+
rescue Wisco::SettingsStore::MissingKeyError
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if names.nil?
|
|
58
|
+
Wisco::TerminalOutput.emit_warning("Warning: could not read #{store.filename} to validate (no master key).")
|
|
59
|
+
Wisco::TerminalOutput.emit_warning(' Config updated anyway.')
|
|
60
|
+
elsif !names.include?(connection)
|
|
61
|
+
Wisco::TerminalOutput.emit_warning("Warning: \"#{connection}\" is not currently defined in #{store.filename}.")
|
|
62
|
+
Wisco::TerminalOutput.emit_warning(" Defined sets: #{names.empty? ? '(none)' : names.join(', ')}")
|
|
63
|
+
Wisco::TerminalOutput.emit_warning(" Config updated anyway. Run 'wisco settings add #{connection}' to create it.")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
cfg = config
|
|
67
|
+
cfg['connection'] = connection
|
|
68
|
+
Wisco::Config.save_config(config_path, cfg)
|
|
69
|
+
puts "Active connection set to \"#{connection}\" in #{Wisco::WISCO_DIR}/#{Wisco::CONFIG_FILENAME}."
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
desc 'add CONNECTION', 'Scaffold a new connection set from connector.connection.fields'
|
|
73
|
+
long_desc <<~DESC
|
|
74
|
+
Reads connection.fields from the connector and writes a new named connection
|
|
75
|
+
set with those field names and blank values. Fill the values in afterwards
|
|
76
|
+
with 'workato edit'. If the settings file currently holds a single unnamed
|
|
77
|
+
(flat) connection set, you are prompted to name it so the file can be
|
|
78
|
+
converted to the named (nested) format.
|
|
79
|
+
DESC
|
|
80
|
+
def add(connection)
|
|
81
|
+
field_names = connection_field_names!
|
|
82
|
+
if field_names.empty?
|
|
83
|
+
Wisco::TerminalOutput.emit_error('Error: The connector defines no connection.fields to scaffold.')
|
|
84
|
+
exit 1
|
|
85
|
+
end
|
|
86
|
+
new_set = field_names.each_with_object({}) { |n, h| h[n] = '' }
|
|
87
|
+
|
|
88
|
+
store = settings_store
|
|
89
|
+
data = read_settings!(store)
|
|
90
|
+
structure = Wisco::SettingsStore.detect_structure(data)
|
|
91
|
+
|
|
92
|
+
if Wisco::SettingsStore.set_names(data).include?(connection)
|
|
93
|
+
Wisco::TerminalOutput.emit_error("Error: Connection set \"#{connection}\" already exists in #{store.filename}.")
|
|
94
|
+
Wisco::TerminalOutput.emit_error(" Use 'wisco settings show #{connection}' to view it, or edit it with 'workato edit'.")
|
|
95
|
+
exit 1
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
target =
|
|
99
|
+
case structure
|
|
100
|
+
when :nested
|
|
101
|
+
data.merge(connection => new_set)
|
|
102
|
+
when :none
|
|
103
|
+
{ connection => new_set }
|
|
104
|
+
when :flat
|
|
105
|
+
migrate_flat(data, store).merge(connection => new_set)
|
|
106
|
+
when :mixed
|
|
107
|
+
Wisco::TerminalOutput.emit_error("Error: #{store.filename} mixes flat keys and named sets; resolve it with 'workato edit' before adding.")
|
|
108
|
+
exit 1
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# A brand-new file is written plaintext; existing files keep their form.
|
|
112
|
+
encrypted = structure != :none && store.encrypted?
|
|
113
|
+
written = encrypted ? Wisco::SettingsStore::ENCRYPTED_FILENAME : Wisco::SettingsStore::PLAINTEXT_FILENAME
|
|
114
|
+
store.write_all(target, encrypted: encrypted)
|
|
115
|
+
|
|
116
|
+
puts "Added connection set \"#{connection}\" to #{written} with #{field_names.length} blank field(s):"
|
|
117
|
+
puts " #{field_names.join(', ')}"
|
|
118
|
+
puts "Fill in the values with 'workato edit', then run 'wisco settings set #{connection}'."
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
desc 'show [CONNECTION]', "Show a connection set's field values (passwords masked)"
|
|
122
|
+
option :format, type: :string, enum: %w[json], desc: 'Machine-readable output (json = connection fields with current values)'
|
|
123
|
+
def show(connection = nil)
|
|
124
|
+
store = settings_store
|
|
125
|
+
data = read_settings!(store)
|
|
126
|
+
|
|
127
|
+
name, set =
|
|
128
|
+
case Wisco::SettingsStore.detect_structure(data)
|
|
129
|
+
when :none
|
|
130
|
+
Wisco::TerminalOutput.emit_error("Error: No settings file found in #{store.connector_path}. Nothing to show.")
|
|
131
|
+
exit 1
|
|
132
|
+
when :flat
|
|
133
|
+
if connection
|
|
134
|
+
Wisco::TerminalOutput.emit_error("Error: #{store.filename} has a single unnamed connection set; there is no named set \"#{connection}\".")
|
|
135
|
+
exit 1
|
|
136
|
+
end
|
|
137
|
+
[nil, data]
|
|
138
|
+
when :nested, :mixed
|
|
139
|
+
names = Wisco::SettingsStore.set_names(data)
|
|
140
|
+
if connection.nil?
|
|
141
|
+
Wisco::TerminalOutput.emit_warning('Warning: This settings file has multiple connection sets; specify which one to show.')
|
|
142
|
+
Wisco::TerminalOutput.emit_warning(" Defined sets: #{names.join(', ')}")
|
|
143
|
+
return
|
|
144
|
+
end
|
|
145
|
+
unless names.include?(connection)
|
|
146
|
+
Wisco::TerminalOutput.emit_error("Error: Connection set \"#{connection}\" not found in #{store.filename}.")
|
|
147
|
+
Wisco::TerminalOutput.emit_error(" Defined sets: #{names.join(', ')}")
|
|
148
|
+
exit 1
|
|
149
|
+
end
|
|
150
|
+
[connection, data[connection]]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
if options[:format] == 'json'
|
|
154
|
+
render_set_json(set)
|
|
155
|
+
else
|
|
156
|
+
render_set(name, set, store)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
desc 'current', 'Show which connection set the project points at'
|
|
161
|
+
def current
|
|
162
|
+
active = active_connection.to_s.strip
|
|
163
|
+
store = settings_store
|
|
164
|
+
data = begin
|
|
165
|
+
store.read_all
|
|
166
|
+
rescue Wisco::SettingsStore::MissingKeyError
|
|
167
|
+
nil
|
|
168
|
+
end
|
|
169
|
+
names = data ? Wisco::SettingsStore.set_names(data) : []
|
|
170
|
+
|
|
171
|
+
if active.empty?
|
|
172
|
+
puts 'This project has no named connection selected (config.json has no "connection" key).'
|
|
173
|
+
puts "The connector will use the single connection set in #{store.filename}."
|
|
174
|
+
return
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
puts "This project uses connection set: #{active}"
|
|
178
|
+
if data.nil?
|
|
179
|
+
Wisco::TerminalOutput.emit_warning(" Could not read #{store.filename} to verify (no master key).")
|
|
180
|
+
elsif names.include?(active)
|
|
181
|
+
puts " Defined in: #{store.filename} ✓"
|
|
182
|
+
else
|
|
183
|
+
puts " Not found in #{store.filename}. Run 'wisco settings add #{active}' to create it."
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
desc 'fields', 'List the connector connection fields'
|
|
188
|
+
option :format, type: :string, enum: %w[json], desc: 'Machine-readable output (json)'
|
|
189
|
+
def fields
|
|
190
|
+
raw = connection_fields!
|
|
191
|
+
|
|
192
|
+
if options[:format] == 'json'
|
|
193
|
+
puts JSON.pretty_generate(raw)
|
|
194
|
+
return
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
if raw.empty?
|
|
198
|
+
puts 'Connector defines no connection fields.'
|
|
199
|
+
return
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
rows = raw.map do |f|
|
|
203
|
+
[
|
|
204
|
+
field_attr(f, :name).to_s,
|
|
205
|
+
field_attr(f, :label).to_s,
|
|
206
|
+
(field_attr(f, :control_type) || '(default)').to_s,
|
|
207
|
+
field_attr(f, :optional) ? 'no' : 'yes'
|
|
208
|
+
]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
headers = %w[Name Label Type Required]
|
|
212
|
+
widths = headers.each_index.map do |i|
|
|
213
|
+
([headers[i]] + rows.map { |r| r[i] }).map(&:length).max
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
puts "Connection fields (from #{connector_file_basename}):\n\n"
|
|
217
|
+
puts " #{headers.each_with_index.map { |h, i| h.ljust(widths[i]) }.join(' ')}"
|
|
218
|
+
rows.each do |r|
|
|
219
|
+
puts " #{r.each_with_index.map { |c, i| c.ljust(widths[i]) }.join(' ')}"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
no_commands do
|
|
224
|
+
def target_dir
|
|
225
|
+
Dir.pwd
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def config_path
|
|
229
|
+
Wisco.config_path(target_dir)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def config
|
|
233
|
+
@config ||= begin
|
|
234
|
+
unless File.exist?(config_path)
|
|
235
|
+
Wisco::TerminalOutput.emit_error("Error: No #{Wisco::WISCO_DIR}/#{Wisco::CONFIG_FILENAME} found in #{target_dir}.")
|
|
236
|
+
Wisco::TerminalOutput.emit_error(" Run 'wisco init' first.")
|
|
237
|
+
exit 1
|
|
238
|
+
end
|
|
239
|
+
Wisco::Config.load_config(config_path)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def connector_path
|
|
244
|
+
path = config.dig('connector', 'path')
|
|
245
|
+
if path.nil?
|
|
246
|
+
Wisco::TerminalOutput.emit_error("Error: #{Wisco::WISCO_DIR}/#{Wisco::CONFIG_FILENAME} is missing connector path. Run 'wisco init' again.")
|
|
247
|
+
exit 1
|
|
248
|
+
end
|
|
249
|
+
path
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def connector_file_basename
|
|
253
|
+
config.dig('connector', 'file') || 'connector.rb'
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def active_connection
|
|
257
|
+
config['connection']
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def settings_store
|
|
261
|
+
Wisco::SettingsStore.new(connector_path)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Reads the settings file, converting a missing master key into a clean
|
|
265
|
+
# error+exit (used by commands that cannot proceed without the contents).
|
|
266
|
+
def read_settings!(store)
|
|
267
|
+
store.read_all
|
|
268
|
+
rescue Wisco::SettingsStore::MissingKeyError => e
|
|
269
|
+
Wisco::TerminalOutput.emit_error("Error: #{e.message}")
|
|
270
|
+
exit 1
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# connection.fields array from the connector (symbol-keyed hashes).
|
|
274
|
+
# Exits with the connector's load error if it cannot be loaded.
|
|
275
|
+
def connection_fields!
|
|
276
|
+
connector = Wisco::Connector.load_connector_from_config(target_dir)
|
|
277
|
+
Array(connector.is_a?(Hash) ? connector.dig(:connection, :fields) : nil)
|
|
278
|
+
rescue StandardError => e
|
|
279
|
+
Wisco::TerminalOutput.emit_error("Error: #{e.message.strip}")
|
|
280
|
+
exit 1
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def connection_field_names!
|
|
284
|
+
connection_fields!.map { |f| field_attr(f, :name).to_s }.reject(&:empty?)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Password field names for masking. Degrades gracefully (returns []) if
|
|
288
|
+
# the connector cannot be loaded, so `show` still works.
|
|
289
|
+
def password_field_names
|
|
290
|
+
connector = Wisco::Connector.load_connector_from_config(target_dir)
|
|
291
|
+
fields = Array(connector.is_a?(Hash) ? connector.dig(:connection, :fields) : nil)
|
|
292
|
+
fields.select { |f| field_attr(f, :control_type).to_s == 'password' }
|
|
293
|
+
.map { |f| field_attr(f, :name).to_s }
|
|
294
|
+
rescue SystemExit, StandardError
|
|
295
|
+
Wisco::TerminalOutput.emit_warning('Warning: could not load connector to identify password fields; showing all values unmasked.')
|
|
296
|
+
[]
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def field_attr(field, key)
|
|
300
|
+
return nil unless field.is_a?(Hash)
|
|
301
|
+
|
|
302
|
+
field[key] || field[key.to_s]
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def render_set(name, set, store)
|
|
306
|
+
passwords = password_field_names
|
|
307
|
+
header = name || '(single/unnamed)'
|
|
308
|
+
puts "Connection set: #{header} (#{store.filename})\n\n"
|
|
309
|
+
|
|
310
|
+
if set.nil? || set.empty?
|
|
311
|
+
puts ' (no fields)'
|
|
312
|
+
return
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
width = set.keys.map { |k| k.to_s.length }.max
|
|
316
|
+
set.each do |k, v|
|
|
317
|
+
display =
|
|
318
|
+
if v.to_s.empty?
|
|
319
|
+
'(blank)'
|
|
320
|
+
elsif passwords.include?(k.to_s)
|
|
321
|
+
mask(v.to_s)
|
|
322
|
+
else
|
|
323
|
+
v.to_s
|
|
324
|
+
end
|
|
325
|
+
puts " #{k.to_s.ljust(width)} #{display}"
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def mask(value)
|
|
330
|
+
return '****' if value.length <= 4
|
|
331
|
+
|
|
332
|
+
"****#{value[-4..]}"
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Machine-readable form of a connection set: the connector's
|
|
336
|
+
# connection.fields array, each field augmented with a `value` key holding
|
|
337
|
+
# the current stored value (null when the field is unset or blank). Values
|
|
338
|
+
# are unmasked — this output is for tooling, not display.
|
|
339
|
+
def render_set_json(set)
|
|
340
|
+
set ||= {}
|
|
341
|
+
out = connection_fields!.map do |f|
|
|
342
|
+
raw = set[field_attr(f, :name).to_s]
|
|
343
|
+
value = raw.nil? || raw.to_s.empty? ? nil : raw
|
|
344
|
+
f.merge(value: value)
|
|
345
|
+
end
|
|
346
|
+
puts JSON.pretty_generate(out)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def migrate_flat(flat_data, store)
|
|
350
|
+
puts "#{store.filename} currently holds a single unnamed connection set."
|
|
351
|
+
puts 'Adding a named set requires converting the file to the named (nested) format.'
|
|
352
|
+
print 'Enter a name for the existing connection set (or press Enter to skip): '
|
|
353
|
+
name = $stdin.gets.to_s.strip
|
|
354
|
+
if name.empty?
|
|
355
|
+
Wisco::TerminalOutput.emit_error('Aborted: mixing flat and named connection sets is not supported.')
|
|
356
|
+
exit 1
|
|
357
|
+
end
|
|
358
|
+
{ name => flat_data }
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'active_support/encrypted_configuration'
|
|
3
|
+
require 'workato/connector/sdk'
|
|
4
|
+
require_relative 'terminal_output'
|
|
5
|
+
|
|
6
|
+
module Wisco
|
|
7
|
+
# Read/write wrapper over the Workato SDK settings file (settings.yaml /
|
|
8
|
+
# settings.yaml.enc) living in a connector directory. Handles encrypted vs
|
|
9
|
+
# plaintext auto-detection, master-key resolution, and flat-vs-nested
|
|
10
|
+
# connection-set detection. See doc-specs/wisco-10-settings.md.
|
|
11
|
+
class SettingsStore
|
|
12
|
+
PLAINTEXT_FILENAME = 'settings.yaml'.freeze
|
|
13
|
+
ENCRYPTED_FILENAME = 'settings.yaml.enc'.freeze
|
|
14
|
+
MASTER_KEY_FILENAME = 'master.key'.freeze
|
|
15
|
+
MASTER_KEY_ENV = 'WORKATO_CONNECTOR_MASTER_KEY'.freeze
|
|
16
|
+
|
|
17
|
+
class MissingKeyError < StandardError; end
|
|
18
|
+
|
|
19
|
+
attr_reader :connector_path
|
|
20
|
+
|
|
21
|
+
def initialize(connector_path)
|
|
22
|
+
@connector_path = File.expand_path(connector_path)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def plaintext_path
|
|
26
|
+
File.join(@connector_path, PLAINTEXT_FILENAME)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def encrypted_path
|
|
30
|
+
File.join(@connector_path, ENCRYPTED_FILENAME)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def master_key_path
|
|
34
|
+
File.join(@connector_path, MASTER_KEY_FILENAME)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# :encrypted (settings.yaml.enc present), :plaintext (settings.yaml present),
|
|
38
|
+
# or :none. Encrypted takes precedence when both exist.
|
|
39
|
+
def form
|
|
40
|
+
if File.exist?(encrypted_path)
|
|
41
|
+
:encrypted
|
|
42
|
+
elsif File.exist?(plaintext_path)
|
|
43
|
+
:plaintext
|
|
44
|
+
else
|
|
45
|
+
:none
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def encrypted?
|
|
50
|
+
form == :encrypted
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def exist?
|
|
54
|
+
form != :none
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Basename for user-facing messages; falls back to the plaintext name when
|
|
58
|
+
# no file exists yet (that is the form `add` would create).
|
|
59
|
+
def filename
|
|
60
|
+
encrypted? ? ENCRYPTED_FILENAME : PLAINTEXT_FILENAME
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def master_key_available?
|
|
64
|
+
!ENV[MASTER_KEY_ENV].to_s.strip.empty? || File.exist?(master_key_path)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Reads the entire settings file (all sets) as a plain string-keyed Hash.
|
|
68
|
+
# Returns {} when no file exists. Raises MissingKeyError if the file is
|
|
69
|
+
# encrypted and no master key is available.
|
|
70
|
+
def read_all
|
|
71
|
+
case form
|
|
72
|
+
when :none
|
|
73
|
+
{}
|
|
74
|
+
when :plaintext
|
|
75
|
+
parsed = YAML.safe_load(File.read(plaintext_path), permitted_classes: [::Symbol])
|
|
76
|
+
parsed.is_a?(Hash) ? parsed : {}
|
|
77
|
+
when :encrypted
|
|
78
|
+
require_master_key!
|
|
79
|
+
raw = Workato::Connector::Sdk::Settings.from_encrypted_file(encrypted_path, master_key_path)
|
|
80
|
+
raw.respond_to?(:to_hash) ? raw.to_hash : {}
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Replaces the whole settings file with `data`. When `encrypted:` is true the
|
|
85
|
+
# content is encrypted with the resolved master key; otherwise a plaintext
|
|
86
|
+
# settings.yaml is written. Rewriting wholesale (rather than the SDK's
|
|
87
|
+
# merge-only #update) is what lets `add` migrate a flat file to nested form.
|
|
88
|
+
def write_all(data, encrypted:)
|
|
89
|
+
if encrypted
|
|
90
|
+
require_master_key!
|
|
91
|
+
config = ActiveSupport::EncryptedConfiguration.new(
|
|
92
|
+
config_path: encrypted_path,
|
|
93
|
+
key_path: master_key_path,
|
|
94
|
+
env_key: MASTER_KEY_ENV,
|
|
95
|
+
raise_if_missing_key: true
|
|
96
|
+
)
|
|
97
|
+
config.write(YAML.dump(data))
|
|
98
|
+
else
|
|
99
|
+
File.write(plaintext_path, YAML.dump(data))
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def require_master_key!
|
|
104
|
+
return if master_key_available?
|
|
105
|
+
|
|
106
|
+
raise MissingKeyError,
|
|
107
|
+
"#{ENCRYPTED_FILENAME} is encrypted but no master key was found.\n" \
|
|
108
|
+
" Set #{MASTER_KEY_ENV} or provide a #{MASTER_KEY_FILENAME} file in #{@connector_path}."
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# :none, :flat (all top-level values scalar), :nested (all Hash), or :mixed.
|
|
112
|
+
def self.detect_structure(data)
|
|
113
|
+
return :none if data.nil? || data.empty?
|
|
114
|
+
|
|
115
|
+
values = data.values
|
|
116
|
+
if values.all? { |v| v.is_a?(Hash) }
|
|
117
|
+
:nested
|
|
118
|
+
elsif values.none? { |v| v.is_a?(Hash) }
|
|
119
|
+
:flat
|
|
120
|
+
else
|
|
121
|
+
:mixed
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Names of the named (nested) connection sets — the top-level keys whose
|
|
126
|
+
# value is a Hash. Empty for flat/none files.
|
|
127
|
+
def self.set_names(data)
|
|
128
|
+
return [] if data.nil?
|
|
129
|
+
|
|
130
|
+
data.select { |_k, v| v.is_a?(Hash) }.keys
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
data/lib/wisco/version.rb
CHANGED
data/lib/wisco.rb
CHANGED
|
@@ -25,6 +25,7 @@ require_relative 'wisco/commands/pull'
|
|
|
25
25
|
require_relative 'wisco/commands/push'
|
|
26
26
|
require_relative 'wisco/commands/schema'
|
|
27
27
|
require_relative 'wisco/commands/profile'
|
|
28
|
+
require_relative 'wisco/commands/settings'
|
|
28
29
|
require_relative 'wisco/commands/status'
|
|
29
30
|
|
|
30
31
|
module Wisco
|
|
@@ -217,6 +218,18 @@ module Wisco
|
|
|
217
218
|
DESC
|
|
218
219
|
subcommand 'profile', Wisco::Commands::Profile
|
|
219
220
|
|
|
221
|
+
desc 'settings SUBCOMMAND ...ARGS', 'Manage the connector settings file (credential sets)'
|
|
222
|
+
long_desc <<~DESC
|
|
223
|
+
Subcommands:
|
|
224
|
+
list List connection sets (--format=json for a JSON array of names)
|
|
225
|
+
set <connection> Set the project's active connection set
|
|
226
|
+
add <connection> Scaffold a new connection set from connector.connection.fields
|
|
227
|
+
show [<connection>] Show a connection set's field values (passwords masked)
|
|
228
|
+
current Show which connection set the project points at
|
|
229
|
+
fields List the connector's connection fields (--format=json for JSON)
|
|
230
|
+
DESC
|
|
231
|
+
subcommand 'settings', Wisco::Commands::Settings
|
|
232
|
+
|
|
220
233
|
desc 'schema INPUT_FILE [TARGET_DIR]', 'Generate a schema from a JSON or CSV sample file'
|
|
221
234
|
long_desc <<~DESC
|
|
222
235
|
Calls the Workato API to generate a schema from a sample JSON or CSV file.
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wisco
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- mbillington
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -75,12 +75,14 @@ files:
|
|
|
75
75
|
- lib/wisco/commands/pull.rb
|
|
76
76
|
- lib/wisco/commands/push.rb
|
|
77
77
|
- lib/wisco/commands/schema.rb
|
|
78
|
+
- lib/wisco/commands/settings.rb
|
|
78
79
|
- lib/wisco/commands/status.rb
|
|
79
80
|
- lib/wisco/config.rb
|
|
80
81
|
- lib/wisco/connector.rb
|
|
81
82
|
- lib/wisco/exec_script.rb
|
|
82
83
|
- lib/wisco/path_utils.rb
|
|
83
84
|
- lib/wisco/profile.rb
|
|
85
|
+
- lib/wisco/settings_store.rb
|
|
84
86
|
- lib/wisco/terminal_output.rb
|
|
85
87
|
- lib/wisco/version.rb
|
|
86
88
|
- lib/wisco/workato_api.rb
|