MuranoCLI 3.2.0.beta.1 → 3.2.0.beta.5
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/.rubocop.yml +4 -1
- data/.trustme.plugin +137 -0
- data/.trustme.sh +217 -117
- data/.trustme.vim +9 -3
- data/Gemfile +9 -3
- data/MuranoCLI.gemspec +8 -5
- data/Rakefile +1 -0
- data/dockers/Dockerfile.2.2.9 +6 -3
- data/dockers/Dockerfile.2.3.6 +6 -3
- data/dockers/Dockerfile.2.4.3 +6 -3
- data/dockers/Dockerfile.2.5.0 +6 -3
- data/dockers/Dockerfile.GemRelease +10 -8
- data/dockers/Dockerfile.m4 +23 -5
- data/dockers/docker-test.sh +65 -28
- data/docs/completions/murano_completion-bash +751 -57
- data/docs/develop.rst +10 -9
- data/lib/MrMurano/AccountBase.rb +95 -6
- data/lib/MrMurano/Commander-Entry.rb +9 -4
- data/lib/MrMurano/Config-Migrate.rb +2 -0
- data/lib/MrMurano/Config.rb +94 -26
- data/lib/MrMurano/Content.rb +1 -1
- data/lib/MrMurano/Exchange.rb +77 -42
- data/lib/MrMurano/Gateway.rb +1 -1
- data/lib/MrMurano/HttpAuthed.rb +20 -7
- data/lib/MrMurano/Logs.rb +10 -1
- data/lib/MrMurano/ProjectFile.rb +1 -1
- data/lib/MrMurano/ReCommander.rb +129 -73
- data/lib/MrMurano/Solution-ServiceConfig.rb +18 -11
- data/lib/MrMurano/Solution-Services.rb +78 -50
- data/lib/MrMurano/Solution-Users.rb +1 -1
- data/lib/MrMurano/Solution.rb +13 -63
- data/lib/MrMurano/SyncUpDown-Core.rb +185 -77
- data/lib/MrMurano/SyncUpDown-Item.rb +29 -4
- data/lib/MrMurano/SyncUpDown.rb +11 -11
- data/lib/MrMurano/Webservice-Cors.rb +1 -1
- data/lib/MrMurano/Webservice-Endpoint.rb +28 -17
- data/lib/MrMurano/Webservice-File.rb +103 -43
- data/lib/MrMurano/commands/domain.rb +1 -0
- data/lib/MrMurano/commands/element.rb +585 -0
- data/lib/MrMurano/commands/exchange.rb +211 -204
- data/lib/MrMurano/commands/gb.rb +1 -0
- data/lib/MrMurano/commands/globals.rb +17 -7
- data/lib/MrMurano/commands/init.rb +115 -101
- data/lib/MrMurano/commands/keystore.rb +1 -1
- data/lib/MrMurano/commands/logs.rb +2 -1
- data/lib/MrMurano/commands/postgresql.rb +17 -7
- data/lib/MrMurano/commands/service.rb +572 -0
- data/lib/MrMurano/commands/show.rb +7 -3
- data/lib/MrMurano/commands/solution.rb +2 -1
- data/lib/MrMurano/commands/solution_picker.rb +31 -15
- data/lib/MrMurano/commands/status.rb +205 -169
- data/lib/MrMurano/commands/sync.rb +70 -38
- data/lib/MrMurano/commands/token.rb +59 -14
- data/lib/MrMurano/commands/usage.rb +1 -0
- data/lib/MrMurano/commands.rb +2 -0
- data/lib/MrMurano/hash.rb +91 -0
- data/lib/MrMurano/http.rb +55 -6
- data/lib/MrMurano/makePretty.rb +47 -0
- data/lib/MrMurano/optparse.rb +60 -45
- data/lib/MrMurano/variegated/TruthyFalsey.rb +48 -0
- data/lib/MrMurano/variegated/ruby_dig.rb +64 -0
- data/lib/MrMurano/verbosing.rb +113 -3
- data/lib/MrMurano/version.rb +1 -1
- data/spec/Account_spec.rb +34 -20
- data/spec/Business_spec.rb +12 -9
- data/spec/Config_spec.rb +7 -1
- data/spec/Content_spec.rb +17 -1
- data/spec/GatewayBase_spec.rb +5 -2
- data/spec/GatewayDevice_spec.rb +4 -2
- data/spec/GatewayResource_spec.rb +4 -1
- data/spec/GatewaySettings_spec.rb +4 -1
- data/spec/HttpAuthed_spec.rb +73 -0
- data/spec/Http_spec.rb +32 -35
- data/spec/ProjectFile_spec.rb +1 -1
- data/spec/Solution-ServiceConfig_spec.rb +4 -1
- data/spec/Solution-ServiceEventHandler_spec.rb +6 -3
- data/spec/Solution-ServiceModules_spec.rb +4 -1
- data/spec/Solution-UsersRoles_spec.rb +4 -1
- data/spec/Solution_spec.rb +4 -1
- data/spec/SyncUpDown_spec.rb +1 -1
- data/spec/Webservice-Cors_spec.rb +4 -1
- data/spec/Webservice-Endpoint_spec.rb +9 -6
- data/spec/Webservice-File_spec.rb +17 -4
- data/spec/Webservice-Setting_spec.rb +6 -2
- data/spec/_workspace.rb +2 -0
- data/spec/cmd_common.rb +42 -13
- data/spec/cmd_content_spec.rb +17 -7
- data/spec/cmd_device_spec.rb +1 -1
- data/spec/cmd_domain_spec.rb +2 -2
- data/spec/cmd_element_spec.rb +400 -0
- data/spec/cmd_exchange_spec.rb +2 -2
- data/spec/cmd_init_spec.rb +59 -25
- data/spec/cmd_keystore_spec.rb +6 -3
- data/spec/cmd_link_spec.rb +10 -5
- data/spec/cmd_logs_spec.rb +1 -1
- data/spec/cmd_setting_application_spec.rb +18 -15
- data/spec/cmd_setting_product_spec.rb +7 -7
- data/spec/cmd_status_spec.rb +27 -17
- data/spec/cmd_syncdown_application_spec.rb +30 -3
- data/spec/cmd_syncdown_both_spec.rb +72 -18
- data/spec/cmd_syncup_spec.rb +71 -5
- data/spec/cmd_token_spec.rb +2 -2
- data/spec/cmd_usage_spec.rb +2 -2
- data/spec/dry_run_formatter.rb +27 -0
- data/spec/fixtures/dumped_config +8 -0
- data/spec/fixtures/exchange_element/element-show.json +1 -0
- data/spec/fixtures/exchange_element/swagger-mur-6407__10k.yaml +282 -0
- data/spec/fixtures/exchange_element/swagger-mur-6407__20k.yaml +588 -0
- data/spec/variegated_TruthyFalsey_spec.rb +29 -0
- metadata +51 -25
|
@@ -38,7 +38,7 @@ module MrMurano
|
|
|
38
38
|
# :nocov:
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def fetch(name)
|
|
41
|
+
def fetch(name, _untainted=false)
|
|
42
42
|
raise 'Missing name!' if name.nil?
|
|
43
43
|
raise 'Empty name!' if name.empty?
|
|
44
44
|
ret = get('/' + CGI.escape(name))
|
|
@@ -102,6 +102,9 @@ module MrMurano
|
|
|
102
102
|
|
|
103
103
|
return unless upload_item_allowed(therealias)
|
|
104
104
|
|
|
105
|
+
get_again = false
|
|
106
|
+
create_it = false
|
|
107
|
+
|
|
105
108
|
put('/' + therealias, pst) do |request, http|
|
|
106
109
|
response = http.request(request)
|
|
107
110
|
isj, jsn = isJSON(response.body)
|
|
@@ -109,16 +112,14 @@ module MrMurano
|
|
|
109
112
|
# EXPLAIN: How come `case response ... when Net:HTTPNoContent` works?
|
|
110
113
|
# It seems magical, since response is a class and here we use is_a?.
|
|
111
114
|
if response.is_a?(Net::HTTPNoContent)
|
|
112
|
-
# 2017-08-07: When did Murano start returning 204?
|
|
115
|
+
# 2017-08-07: (lb): When did Murano start returning 204?
|
|
113
116
|
# This seems to happen when updating an existing service.
|
|
114
117
|
# Unfortunately, we don't get the latest updated_at, so
|
|
115
118
|
# a subsequent status will show this module as dirty.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
warning "Failed to verify updated_at: #{ret}"
|
|
121
|
-
end
|
|
119
|
+
# (lb): Don't call get() from the put() handler, it just seems
|
|
120
|
+
# like bad form to start a new call before the existing one is
|
|
121
|
+
# complete!
|
|
122
|
+
get_again = true
|
|
122
123
|
elsif response.is_a?(Net::HTTPSuccess)
|
|
123
124
|
# A first upload will see a 200 response and a JSON body.
|
|
124
125
|
# A subsequent upload of the same item sees 204 and no body.
|
|
@@ -127,18 +128,29 @@ module MrMurano
|
|
|
127
128
|
updated_at = jsn[:updated_at] unless jsn.nil?
|
|
128
129
|
elsif response == Net::HTTPNotFound
|
|
129
130
|
verbose "Doesn't exist, creating"
|
|
130
|
-
|
|
131
|
+
create_it = true
|
|
131
132
|
else
|
|
132
133
|
relpath = localpath.sub(File.join(Dir.pwd, ''), '')
|
|
133
134
|
if response.is_a?(Net::HTTPBadRequest) && isj && jsn[:message] == 'Validation errors'
|
|
134
135
|
warning "Validation errors detected in #{relpath}:"
|
|
135
|
-
puts MrMurano::Pretties.makeJsonPretty(
|
|
136
|
+
puts MrMurano::Pretties.makeJsonPretty(
|
|
137
|
+
jsn[:errors], Struct.new(:pretty).new(true),
|
|
138
|
+
)
|
|
136
139
|
else
|
|
137
140
|
showHttpError(request, response)
|
|
138
141
|
end
|
|
139
142
|
warning "Failed to upload: #{relpath}"
|
|
140
143
|
end
|
|
141
144
|
end
|
|
145
|
+
if get_again
|
|
146
|
+
ret = get('/' + CGI.escape(name))
|
|
147
|
+
if ret.is_a?(Hash) && ret.key?(:updated_at)
|
|
148
|
+
updated_at = ret[:updated_at]
|
|
149
|
+
else
|
|
150
|
+
warning "Failed to verify updated_at: #{ret}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
post('/', pst) if create_it
|
|
142
154
|
cache_update_time_for(localpath, updated_at)
|
|
143
155
|
end
|
|
144
156
|
|
|
@@ -228,13 +240,23 @@ module MrMurano
|
|
|
228
240
|
return nil unless cache_file.file?
|
|
229
241
|
ret = nil
|
|
230
242
|
cache_file.open('r') do |io|
|
|
231
|
-
|
|
243
|
+
begin
|
|
244
|
+
cache = YAML.load(io)
|
|
245
|
+
rescue StandardError => err
|
|
246
|
+
# This could be solely Psych::SyntaxError but let's catch 'em all.
|
|
247
|
+
# (lb): We could suggest to the user that they delete the
|
|
248
|
+
# cache file, but I'd rather understand what's going on.
|
|
249
|
+
# If anything, the user seeing this error should send us
|
|
250
|
+
# a copy of their cache files.
|
|
251
|
+
warning("ERROR: Could not load the cache file at: #{cache_file}")
|
|
252
|
+
warning(err.to_s)
|
|
253
|
+
end
|
|
232
254
|
return nil unless cache
|
|
233
255
|
if cache.key?(local_path.to_s)
|
|
234
256
|
entry = cache[local_path.to_s]
|
|
235
|
-
debug("For #{local_path}:")
|
|
236
|
-
debug("
|
|
237
|
-
debug("
|
|
257
|
+
debug("For path #{local_path}:")
|
|
258
|
+
debug(" cached: #{entry}")
|
|
259
|
+
debug(" cm: #{cksm}")
|
|
238
260
|
if entry.is_a?(Hash)
|
|
239
261
|
if entry[:sha1] == cksm && entry.key?(:updated_at)
|
|
240
262
|
ret = Time.parse(entry[:updated_at])
|
|
@@ -472,7 +494,7 @@ module MrMurano
|
|
|
472
494
|
end
|
|
473
495
|
end
|
|
474
496
|
|
|
475
|
-
def fetch(name)
|
|
497
|
+
def fetch(name, _untainted=false)
|
|
476
498
|
ret = get('/' + CGI.escape(name))
|
|
477
499
|
unless ret.is_a?(Hash) && !ret.key?(:error)
|
|
478
500
|
error "Fetch for #{name} returned nil or error; skipping"
|
|
@@ -480,7 +502,11 @@ module MrMurano
|
|
|
480
502
|
end
|
|
481
503
|
aheader = (ret[:script].lines.first || '').chomp
|
|
482
504
|
dheader = "--#EVENT #{ret[:service]} #{ret[:event]}"
|
|
505
|
+
# FIXME/2018-04-24: (lb): What about untainted ?? And this weird double-yield?
|
|
506
|
+
# We may want to yield/return unadulterated ret[:script] if just diffing.
|
|
483
507
|
if block_given?
|
|
508
|
+
# ANSWER/2018-04-24: (lb): If header's do not match, what about ret[:script]?
|
|
509
|
+
# Or do both yields get called?? Smells very strange.
|
|
484
510
|
yield dheader + "\n" if aheader != dheader
|
|
485
511
|
yield ret[:script]
|
|
486
512
|
else
|
|
@@ -515,52 +541,53 @@ module MrMurano
|
|
|
515
541
|
# This only finds the last event in a file.
|
|
516
542
|
# :legacy support doesn't allow for that. But that's ok.
|
|
517
543
|
path = Pathname.new(path) unless path.is_a?(Pathname)
|
|
544
|
+
items = []
|
|
518
545
|
cur = nil
|
|
519
546
|
lineno = 0
|
|
520
547
|
path.readlines.each do |line|
|
|
548
|
+
lineno += 1
|
|
521
549
|
# @match_header finds a service and an event string, e.g., "--EVENT svc evt\n"
|
|
522
550
|
md = @match_header.match(line)
|
|
523
551
|
if !md.nil?
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
svc_alias = config_var if service != md[:service]
|
|
528
|
-
cur = EventHandlerItem.new(
|
|
529
|
-
#name
|
|
530
|
-
local_path: path,
|
|
531
|
-
#id
|
|
532
|
-
script: line,
|
|
533
|
-
line: lineno,
|
|
534
|
-
#line_end
|
|
535
|
-
#diff
|
|
536
|
-
#selected
|
|
537
|
-
#synckey
|
|
538
|
-
#synctype
|
|
539
|
-
#type
|
|
540
|
-
#updated_at
|
|
541
|
-
#dup_count
|
|
542
|
-
#alias
|
|
543
|
-
#updated_at
|
|
544
|
-
#created_at
|
|
545
|
-
#solution_id
|
|
546
|
-
service: service,
|
|
547
|
-
event: event_event,
|
|
548
|
-
type: event_type,
|
|
549
|
-
#phantom
|
|
550
|
-
svc_alias: svc_alias,
|
|
551
|
-
)
|
|
552
|
+
cur[:line_end] = lineno - 1 unless cur.nil?
|
|
553
|
+
cur = to_remote_item_create(md, path, line, lineno)
|
|
554
|
+
items << cur
|
|
552
555
|
elsif !cur.nil? && !cur[:script].nil?
|
|
553
556
|
cur[:script] += line
|
|
554
557
|
end
|
|
555
|
-
|
|
558
|
+
# else, cur.nil?, or no :script (not used by item), so skip this line.
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
if !cur.nil?
|
|
562
|
+
cur[:line_end] = lineno
|
|
563
|
+
else
|
|
564
|
+
# If cur is nil here, then we need to do a :legacy check.
|
|
565
|
+
cur = to_remote_item_legacy_check(cur, path, from, lineno)
|
|
566
|
+
items << cur
|
|
556
567
|
end
|
|
557
|
-
cur[:line_end] = lineno unless cur.nil?
|
|
558
568
|
|
|
559
|
-
|
|
560
|
-
|
|
569
|
+
items
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def to_remote_item_create(md, path, line, lineno)
|
|
573
|
+
service, config_var = decode_config_var(md[:service])
|
|
574
|
+
event_event, event_type = resolve_event_type(service, md[:event])
|
|
575
|
+
# Header line.
|
|
576
|
+
svc_alias = config_var if service != md[:service]
|
|
577
|
+
EventHandlerItem.new(
|
|
578
|
+
# Skip: name, id, line_end, diff, selected, synckey, synctype, type,
|
|
579
|
+
# updated_at, dup_count, alias, updted_at, created_at, solution_id, phantom.
|
|
580
|
+
local_path: path,
|
|
581
|
+
script: line,
|
|
582
|
+
line_beg: lineno,
|
|
583
|
+
service: service,
|
|
584
|
+
event: event_event,
|
|
585
|
+
type: event_type,
|
|
586
|
+
svc_alias: svc_alias,
|
|
587
|
+
)
|
|
561
588
|
end
|
|
562
589
|
|
|
563
|
-
def to_remote_item_legacy_check(cur, path, from,
|
|
590
|
+
def to_remote_item_legacy_check(cur, path, from, line_end)
|
|
564
591
|
return cur unless cur.nil? && $project['services.legacy'].is_a?(Hash)
|
|
565
592
|
spath = path.relative_path_from(from)
|
|
566
593
|
debug "No headers: #{spath}"
|
|
@@ -574,8 +601,8 @@ module MrMurano
|
|
|
574
601
|
event: event,
|
|
575
602
|
type: nil,
|
|
576
603
|
local_path: path,
|
|
577
|
-
|
|
578
|
-
line_end:
|
|
604
|
+
line_beg: 1,
|
|
605
|
+
line_end: line_end,
|
|
579
606
|
script: path.read, # FIXME: ick, fix this.
|
|
580
607
|
)
|
|
581
608
|
end
|
|
@@ -805,6 +832,7 @@ module MrMurano
|
|
|
805
832
|
SyncRoot.instance.add(
|
|
806
833
|
'services', EventHandlerSolnApp, 'S', true, %w[eventhandlers]
|
|
807
834
|
)
|
|
835
|
+
|
|
808
836
|
# 2017-08-08: device2 and interface are now part of the skiplist, so no
|
|
809
837
|
# product event handlers will be found, unless the user modifies the skiplist.
|
|
810
838
|
SyncRoot.instance.add(
|
data/lib/MrMurano/Solution.rb
CHANGED
|
@@ -62,59 +62,6 @@ module MrMurano
|
|
|
62
62
|
end
|
|
63
63
|
# …
|
|
64
64
|
|
|
65
|
-
def get(path='', query=nil, &block)
|
|
66
|
-
aggregate = nil
|
|
67
|
-
total = nil
|
|
68
|
-
remaining = -1
|
|
69
|
-
orig_query = (query || []).dup
|
|
70
|
-
while remaining != 0
|
|
71
|
-
ret = super
|
|
72
|
-
if ret.nil? && !@suppress_error
|
|
73
|
-
warning "No solution with ID: #{@api_id}"
|
|
74
|
-
whirly_interject { say 'Run `murano show` to see the business and list of solutions.' }
|
|
75
|
-
MrMurano::SolutionBase.warn_configfile_env_maybe
|
|
76
|
-
exit 1
|
|
77
|
-
end
|
|
78
|
-
return nil if ret.nil?
|
|
79
|
-
# Pagination: Check if more data.
|
|
80
|
-
if ret.is_a?(Hash) && ret.key?(:total) && ret.key?(:items)
|
|
81
|
-
query = orig_query.dup
|
|
82
|
-
if total.nil?
|
|
83
|
-
total = ret[:total]
|
|
84
|
-
remaining = total - ret[:items].length
|
|
85
|
-
# The response also includes a hint of how to get the next page.
|
|
86
|
-
# ret[:next] == "/api/v1/eventhandler?query={\
|
|
87
|
-
# \"solution_id\":\"XXXXXXXXXXXXXXXX\"}&limit=20&offset=20"
|
|
88
|
-
# But note that the URL we use is a little different
|
|
89
|
-
# https://bizapi.hosted.exosite.io/api:1/solution/XXXXXXXXXXXXXXXXX/eventhandler
|
|
90
|
-
else
|
|
91
|
-
if total != ret[:total]
|
|
92
|
-
warning "Unexpected: subsequence :total not total: #{ret[:total]} != #{total}"
|
|
93
|
-
end
|
|
94
|
-
remaining -= ret[:items].length
|
|
95
|
-
end
|
|
96
|
-
if remaining > 0
|
|
97
|
-
#query.push ['limit', 20]
|
|
98
|
-
query.push ['offset', total - remaining]
|
|
99
|
-
elsif remaining != 0
|
|
100
|
-
warning "Unexpected: negative remaining: #{fancy_ticks(total)}"
|
|
101
|
-
remaining = 0
|
|
102
|
-
end
|
|
103
|
-
if aggregate.nil?
|
|
104
|
-
aggregate = ret
|
|
105
|
-
else
|
|
106
|
-
aggregate[:items].concat ret[:items]
|
|
107
|
-
end
|
|
108
|
-
else
|
|
109
|
-
# ret is not a hash, or it's missing :total or :items.
|
|
110
|
-
warning "Unexpected: aggregate set: #{aggregate} / ret: #{ret}" unless aggregate.nil?
|
|
111
|
-
aggregate = ret
|
|
112
|
-
remaining = 0
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
aggregate
|
|
116
|
-
end
|
|
117
|
-
|
|
118
65
|
# This at least works for EventHandler and ServiceConfig.
|
|
119
66
|
# - ServiceConfig overrides to fetch also 'script_key'.
|
|
120
67
|
def search(svc_name, path=nil)
|
|
@@ -133,16 +80,6 @@ module MrMurano
|
|
|
133
80
|
matches.select { |match| match[:service] == svc_name }
|
|
134
81
|
end
|
|
135
82
|
|
|
136
|
-
def self.warn_configfile_env_maybe
|
|
137
|
-
if !$cfg.get('business.id', :env).to_s.empty? &&
|
|
138
|
-
!$cfg.get('business.id', :project).to_s.empty? &&
|
|
139
|
-
$cfg.get('business.id', :env) != $cfg.get('business.id', :project)
|
|
140
|
-
MrMurano::Verbose.warning(
|
|
141
|
-
'NOTE: MURANO_CONFIGFILE specifies a different business.id than the local project file'
|
|
142
|
-
)
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
83
|
include SyncUpDown
|
|
147
84
|
end
|
|
148
85
|
|
|
@@ -155,6 +92,8 @@ module MrMurano
|
|
|
155
92
|
@meta = {}
|
|
156
93
|
@valid = false
|
|
157
94
|
self.meta = meta unless meta.nil?
|
|
95
|
+
@soln_services = nil
|
|
96
|
+
@soln_svc_cfgs = {}
|
|
158
97
|
end
|
|
159
98
|
|
|
160
99
|
# The Solution @name.
|
|
@@ -213,6 +152,17 @@ module MrMurano
|
|
|
213
152
|
get('/logs')
|
|
214
153
|
end
|
|
215
154
|
|
|
155
|
+
def service
|
|
156
|
+
return @soln_services unless @soln_services.nil?
|
|
157
|
+
@soln_services = get('/service')
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# See also Solution-ServiceConfig.rb's ServiceBase, which calls same endpoint.
|
|
161
|
+
def serviceconfig(query=nil)
|
|
162
|
+
return @soln_svc_cfgs[query] unless @soln_svc_cfgs[query].nil?
|
|
163
|
+
@soln_svc_cfgs[query] = get('/serviceconfig', query)
|
|
164
|
+
end
|
|
165
|
+
|
|
216
166
|
# *** Solution utils
|
|
217
167
|
|
|
218
168
|
def cfg_key_id
|