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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/.trustme.plugin +137 -0
  4. data/.trustme.sh +217 -117
  5. data/.trustme.vim +9 -3
  6. data/Gemfile +9 -3
  7. data/MuranoCLI.gemspec +8 -5
  8. data/Rakefile +1 -0
  9. data/dockers/Dockerfile.2.2.9 +6 -3
  10. data/dockers/Dockerfile.2.3.6 +6 -3
  11. data/dockers/Dockerfile.2.4.3 +6 -3
  12. data/dockers/Dockerfile.2.5.0 +6 -3
  13. data/dockers/Dockerfile.GemRelease +10 -8
  14. data/dockers/Dockerfile.m4 +23 -5
  15. data/dockers/docker-test.sh +65 -28
  16. data/docs/completions/murano_completion-bash +751 -57
  17. data/docs/develop.rst +10 -9
  18. data/lib/MrMurano/AccountBase.rb +95 -6
  19. data/lib/MrMurano/Commander-Entry.rb +9 -4
  20. data/lib/MrMurano/Config-Migrate.rb +2 -0
  21. data/lib/MrMurano/Config.rb +94 -26
  22. data/lib/MrMurano/Content.rb +1 -1
  23. data/lib/MrMurano/Exchange.rb +77 -42
  24. data/lib/MrMurano/Gateway.rb +1 -1
  25. data/lib/MrMurano/HttpAuthed.rb +20 -7
  26. data/lib/MrMurano/Logs.rb +10 -1
  27. data/lib/MrMurano/ProjectFile.rb +1 -1
  28. data/lib/MrMurano/ReCommander.rb +129 -73
  29. data/lib/MrMurano/Solution-ServiceConfig.rb +18 -11
  30. data/lib/MrMurano/Solution-Services.rb +78 -50
  31. data/lib/MrMurano/Solution-Users.rb +1 -1
  32. data/lib/MrMurano/Solution.rb +13 -63
  33. data/lib/MrMurano/SyncUpDown-Core.rb +185 -77
  34. data/lib/MrMurano/SyncUpDown-Item.rb +29 -4
  35. data/lib/MrMurano/SyncUpDown.rb +11 -11
  36. data/lib/MrMurano/Webservice-Cors.rb +1 -1
  37. data/lib/MrMurano/Webservice-Endpoint.rb +28 -17
  38. data/lib/MrMurano/Webservice-File.rb +103 -43
  39. data/lib/MrMurano/commands/domain.rb +1 -0
  40. data/lib/MrMurano/commands/element.rb +585 -0
  41. data/lib/MrMurano/commands/exchange.rb +211 -204
  42. data/lib/MrMurano/commands/gb.rb +1 -0
  43. data/lib/MrMurano/commands/globals.rb +17 -7
  44. data/lib/MrMurano/commands/init.rb +115 -101
  45. data/lib/MrMurano/commands/keystore.rb +1 -1
  46. data/lib/MrMurano/commands/logs.rb +2 -1
  47. data/lib/MrMurano/commands/postgresql.rb +17 -7
  48. data/lib/MrMurano/commands/service.rb +572 -0
  49. data/lib/MrMurano/commands/show.rb +7 -3
  50. data/lib/MrMurano/commands/solution.rb +2 -1
  51. data/lib/MrMurano/commands/solution_picker.rb +31 -15
  52. data/lib/MrMurano/commands/status.rb +205 -169
  53. data/lib/MrMurano/commands/sync.rb +70 -38
  54. data/lib/MrMurano/commands/token.rb +59 -14
  55. data/lib/MrMurano/commands/usage.rb +1 -0
  56. data/lib/MrMurano/commands.rb +2 -0
  57. data/lib/MrMurano/hash.rb +91 -0
  58. data/lib/MrMurano/http.rb +55 -6
  59. data/lib/MrMurano/makePretty.rb +47 -0
  60. data/lib/MrMurano/optparse.rb +60 -45
  61. data/lib/MrMurano/variegated/TruthyFalsey.rb +48 -0
  62. data/lib/MrMurano/variegated/ruby_dig.rb +64 -0
  63. data/lib/MrMurano/verbosing.rb +113 -3
  64. data/lib/MrMurano/version.rb +1 -1
  65. data/spec/Account_spec.rb +34 -20
  66. data/spec/Business_spec.rb +12 -9
  67. data/spec/Config_spec.rb +7 -1
  68. data/spec/Content_spec.rb +17 -1
  69. data/spec/GatewayBase_spec.rb +5 -2
  70. data/spec/GatewayDevice_spec.rb +4 -2
  71. data/spec/GatewayResource_spec.rb +4 -1
  72. data/spec/GatewaySettings_spec.rb +4 -1
  73. data/spec/HttpAuthed_spec.rb +73 -0
  74. data/spec/Http_spec.rb +32 -35
  75. data/spec/ProjectFile_spec.rb +1 -1
  76. data/spec/Solution-ServiceConfig_spec.rb +4 -1
  77. data/spec/Solution-ServiceEventHandler_spec.rb +6 -3
  78. data/spec/Solution-ServiceModules_spec.rb +4 -1
  79. data/spec/Solution-UsersRoles_spec.rb +4 -1
  80. data/spec/Solution_spec.rb +4 -1
  81. data/spec/SyncUpDown_spec.rb +1 -1
  82. data/spec/Webservice-Cors_spec.rb +4 -1
  83. data/spec/Webservice-Endpoint_spec.rb +9 -6
  84. data/spec/Webservice-File_spec.rb +17 -4
  85. data/spec/Webservice-Setting_spec.rb +6 -2
  86. data/spec/_workspace.rb +2 -0
  87. data/spec/cmd_common.rb +42 -13
  88. data/spec/cmd_content_spec.rb +17 -7
  89. data/spec/cmd_device_spec.rb +1 -1
  90. data/spec/cmd_domain_spec.rb +2 -2
  91. data/spec/cmd_element_spec.rb +400 -0
  92. data/spec/cmd_exchange_spec.rb +2 -2
  93. data/spec/cmd_init_spec.rb +59 -25
  94. data/spec/cmd_keystore_spec.rb +6 -3
  95. data/spec/cmd_link_spec.rb +10 -5
  96. data/spec/cmd_logs_spec.rb +1 -1
  97. data/spec/cmd_setting_application_spec.rb +18 -15
  98. data/spec/cmd_setting_product_spec.rb +7 -7
  99. data/spec/cmd_status_spec.rb +27 -17
  100. data/spec/cmd_syncdown_application_spec.rb +30 -3
  101. data/spec/cmd_syncdown_both_spec.rb +72 -18
  102. data/spec/cmd_syncup_spec.rb +71 -5
  103. data/spec/cmd_token_spec.rb +2 -2
  104. data/spec/cmd_usage_spec.rb +2 -2
  105. data/spec/dry_run_formatter.rb +27 -0
  106. data/spec/fixtures/dumped_config +8 -0
  107. data/spec/fixtures/exchange_element/element-show.json +1 -0
  108. data/spec/fixtures/exchange_element/swagger-mur-6407__10k.yaml +282 -0
  109. data/spec/fixtures/exchange_element/swagger-mur-6407__20k.yaml +588 -0
  110. data/spec/variegated_TruthyFalsey_spec.rb +29 -0
  111. 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
- ret = get('/' + CGI.escape(name))
117
- if ret.is_a?(Hash) && ret.key?(:updated_at)
118
- updated_at = ret[:updated_at]
119
- else
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
- post('/', pst)
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(jsn[:errors], Struct.new(:pretty).new(true))
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
- cache = YAML.load(io)
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(" cached: #{entry}")
237
- debug(" cm: #{cksm}")
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
- service, config_var = decode_config_var(md[:service])
525
- event_event, event_type = resolve_event_type(service, md[:event])
526
- # Header line.
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
- lineno += 1
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
- # If cur is nil here, then we need to do a :legacy check.
560
- to_remote_item_legacy_check(cur, path, from, lineno)
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, lineno)
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
- line: 0,
578
- line_end: lineno,
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(
@@ -30,7 +30,7 @@ module MrMurano
30
30
  # sort_by_name(ret)
31
31
  end
32
32
 
33
- def fetch(id)
33
+ def fetch(id, _untainted=false)
34
34
  get('/' + id.to_s)
35
35
  end
36
36
 
@@ -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