MuranoCLI 3.2.0.beta.1 → 3.2.0.beta.5

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