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.
- 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
@@ -0,0 +1,572 @@
|
|
1
|
+
# Copyright © 2016-2017 Exosite LLC. All Rights Reserved
|
2
|
+
# License: PROPRIETARY. See LICENSE.txt.
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
# vim:tw=0:ts=2:sw=2:et:ai
|
6
|
+
# Unauthorized copying of this file is strictly prohibited.
|
7
|
+
|
8
|
+
require 'ostruct'
|
9
|
+
require 'time'
|
10
|
+
require 'MrMurano/makePretty'
|
11
|
+
require 'MrMurano/verbosing'
|
12
|
+
require 'MrMurano/Exchange'
|
13
|
+
require 'MrMurano/ReCommander'
|
14
|
+
require 'MrMurano/Solution-ServiceConfig'
|
15
|
+
require 'MrMurano/SubCmdGroupContext'
|
16
|
+
|
17
|
+
# *** A `service` command helper class.
|
18
|
+
|
19
|
+
class ServiceConsolidator
|
20
|
+
include MrMurano::Verbose
|
21
|
+
|
22
|
+
def initialize(options)
|
23
|
+
@include_all = options.show_all
|
24
|
+
@abbrev_desc = options.abbrev_desc
|
25
|
+
@include_type = options.show_type
|
26
|
+
@exclude_added = options.hide_added
|
27
|
+
@exclude_avail = options.only_added
|
28
|
+
end
|
29
|
+
|
30
|
+
# SYNC_ME: See Yeti UI's removeServiceIconBlacklist.
|
31
|
+
SERVICE_BLACKLIST = {
|
32
|
+
asset: true,
|
33
|
+
device: true,
|
34
|
+
device2: true,
|
35
|
+
gateway: true,
|
36
|
+
products: true,
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
# NOTE:
|
40
|
+
# svc_cfgs is from /solution/<sid>/serviceconfig/
|
41
|
+
# svc_defs is from /solution/<sid>/service/
|
42
|
+
# purchased is from /exchange/<bid>/purchased/
|
43
|
+
def reconcile_services(svc_cfgs, svc_defs, purchased)
|
44
|
+
# The 3 API calls have 3 distinct responses, which we transform into 1.
|
45
|
+
sol_services = {}
|
46
|
+
# Add Exchange purchases to the list first.
|
47
|
+
add_simplify_svc_exchange(sol_services, purchased)
|
48
|
+
# Next add Platforms services, and possibly update sol_services.
|
49
|
+
svc_standard = services_from_definitions(sol_services, svc_defs)
|
50
|
+
add_simplify_svc_definitions(sol_services, svc_standard)
|
51
|
+
# Combine the active service data with the Exchange and Platform lists.
|
52
|
+
services_combine_with_active(sol_services, svc_cfgs)
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_simplify_svc_exchange(sol_services, elements)
|
56
|
+
elements.each do |elem|
|
57
|
+
svc_alias = elem[:element][:source][:name]
|
58
|
+
svc_meta = {
|
59
|
+
name: elem[:element][:name],
|
60
|
+
alias: svc_alias,
|
61
|
+
description: elem[:element][:description],
|
62
|
+
status: '',
|
63
|
+
state: '',
|
64
|
+
source: 'Exchange',
|
65
|
+
}
|
66
|
+
sol_services[svc_alias] = svc_meta
|
67
|
+
# (lb): Just curious:
|
68
|
+
if svc_meta[:approval] != 'approved'
|
69
|
+
debug %(Unexpected: Exchange element !approved: #{svc_meta})
|
70
|
+
end
|
71
|
+
debug %(Unexpected: Exchange element !active: #{svc_meta}) unless svc_meta[:active]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_simplify_svc_definitions(sol_services, svc_standard)
|
76
|
+
svc_standard.each do |defn|
|
77
|
+
svc_alias = defn[:alias]
|
78
|
+
svc_meta = {
|
79
|
+
name: defn[:name],
|
80
|
+
alias: svc_alias,
|
81
|
+
description: defn[:description],
|
82
|
+
status: '',
|
83
|
+
state: defn[:status],
|
84
|
+
source: 'Platform',
|
85
|
+
}
|
86
|
+
svc_meta[:state] = add_type(svc_meta[:state], defn[:type])
|
87
|
+
unless sol_services[svc_alias].nil?
|
88
|
+
warning %(Unexpectedly overwriting entry with /service/: #{svc_alias})
|
89
|
+
end
|
90
|
+
sol_services[svc_alias] = svc_meta
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def add_simplify_svc_configuration(sol_services, scfg)
|
95
|
+
svc_alias = scfg[:service]
|
96
|
+
svc_meta = {
|
97
|
+
name: scfg[:name],
|
98
|
+
alias: svc_alias,
|
99
|
+
description: '',
|
100
|
+
status: scfg[:status],
|
101
|
+
state: '',
|
102
|
+
source: 'Solution',
|
103
|
+
}
|
104
|
+
svc_meta[:status] = add_type(svc_meta[:status], scfg[:type])
|
105
|
+
unless sol_services[svc_alias].nil?
|
106
|
+
warning %(Unexpectedly overwriting entry with /serviceconfig/: #{svc_alias})
|
107
|
+
end
|
108
|
+
sol_services[svc_alias] = svc_meta
|
109
|
+
end
|
110
|
+
|
111
|
+
def add_type(state, type)
|
112
|
+
return state if !@include_type || type.to_s.empty?
|
113
|
+
%(#{state} (#{type}))
|
114
|
+
end
|
115
|
+
|
116
|
+
def service_blacklisted?(svc_alias)
|
117
|
+
!@include_all && !SERVICE_BLACKLIST[svc_alias.to_sym].nil?
|
118
|
+
end
|
119
|
+
|
120
|
+
def services_from_definitions(sol_services, svc_defs)
|
121
|
+
svc_defs.select do |svc|
|
122
|
+
# (lb): Sanity checking.
|
123
|
+
# Check if service blacklisted/!!whitelisted.
|
124
|
+
if service_blacklisted?(svc[:alias])
|
125
|
+
sol_services.delete(svc[:alias])
|
126
|
+
next(false)
|
127
|
+
end
|
128
|
+
# Check if service (from Exchange) already in the list we are building.
|
129
|
+
sxch = sol_services[svc[:alias]]
|
130
|
+
# The /exchange/ and /service/ endpoints do not use same name or description,
|
131
|
+
# e.g., Exchange name might be "Websocket Service" and /service/ name might
|
132
|
+
# be "WebSocket Gateway Service".
|
133
|
+
unless sxch.nil?
|
134
|
+
if sxch[:description] != svc[:description]
|
135
|
+
if !@abbrev_desc
|
136
|
+
sxch[:description] += ' / ' + svc[:description]
|
137
|
+
elsif sxch[:description].to_s.empty?
|
138
|
+
sxch[:description] = svc[:description]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
sxch[:state] = svc[:status]
|
142
|
+
sxch[:state] = add_type(sxch[:state], sxch[:type])
|
143
|
+
sxch[:source] = 'Platform/' + sxch[:source]
|
144
|
+
next(false)
|
145
|
+
end
|
146
|
+
true
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def services_combine_with_active(sol_services, svc_cfgs)
|
151
|
+
# Fill in status for active services.
|
152
|
+
svc_cfgs.each do |svc|
|
153
|
+
# (lb): Sanity check. Just clarifying my understanding of APIs and data.
|
154
|
+
if svc[:service] != svc[:script_key] && svc[:type] != 'P'
|
155
|
+
debug("MISMATCH: :service != :scriptkey / #{svc}")
|
156
|
+
end
|
157
|
+
if @exclude_added || service_blacklisted?(svc[:service])
|
158
|
+
sol_services.delete(svc[:service])
|
159
|
+
next
|
160
|
+
end
|
161
|
+
# The :alias is <sid>_<service>.
|
162
|
+
# Did we learn about this service from Exchange or the Platform?
|
163
|
+
svc_meta = sol_services[svc[:service]]
|
164
|
+
if !svc_meta.nil?
|
165
|
+
# Check if 'available', meaning... not 'hidden'.
|
166
|
+
# Note that /service/ calls it :status, but so as not to conflict
|
167
|
+
# with attr from /seviceconfig/ of same name, we called it :state.
|
168
|
+
if !@include_all && svc_meta[:state] == 'hidden'
|
169
|
+
sol_services.delete(svc[:service])
|
170
|
+
next
|
171
|
+
end
|
172
|
+
warning('Status not null?') unless svc_meta[:status] == ''
|
173
|
+
svc_meta[:status] = svc[:status]
|
174
|
+
svc_meta[:source] = add_type(svc_meta[:source], svc[:type])
|
175
|
+
# The /serviceconfig/ is generally personalized for the solution, e.g.,
|
176
|
+
# /service/ might have name "Timer Service" but /serviceconfig/ will
|
177
|
+
# have "<App Name> Timer and Scheduling Service".
|
178
|
+
svc_meta[:name] = svc[:name] unless svc[:name].to_s.empty?
|
179
|
+
else
|
180
|
+
# Make sure the service is not blacklisted/!!whitelisted.
|
181
|
+
add_simplify_svc_configuration(sol_services, svc)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
if @exclude_avail
|
185
|
+
sol_services.reject! { |_svc_alias, svc| svc[:status].to_s.empty? }
|
186
|
+
end
|
187
|
+
# This was a weird bit of plumbing. And now we're done.
|
188
|
+
sol_services.values
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# *** The `service` command class.
|
193
|
+
|
194
|
+
class ServiceCmd
|
195
|
+
include MrMurano::Verbose
|
196
|
+
|
197
|
+
def command_service(cmd)
|
198
|
+
cmd.syntax = %(murano service)
|
199
|
+
cmd.summary = %(About service)
|
200
|
+
cmd.description = %(
|
201
|
+
Commands for working with Solution Services.
|
202
|
+
).strip
|
203
|
+
cmd.project_not_required = true
|
204
|
+
cmd.subcmdgrouphelp = true
|
205
|
+
|
206
|
+
cmd.action do |_args, _options|
|
207
|
+
::Commander::UI.enable_paging unless $cfg['tool.no-page']
|
208
|
+
say MrMurano::SubCmdGroupHelp.new(cmd).get_help
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# *** `service add` command and helpers.
|
213
|
+
|
214
|
+
def command_service_add(cmd)
|
215
|
+
cmd.syntax = %(murano service add [--options] <service-name>)
|
216
|
+
cmd.summary = %(Add a service to a solution)
|
217
|
+
cmd.description = %(
|
218
|
+
Add a service to a solution.
|
219
|
+
).strip
|
220
|
+
cmd.project_not_required = true
|
221
|
+
|
222
|
+
# FIXME/2018-03-21: See comment under command_service_delete:
|
223
|
+
# We need to add multi-solution support so by default this
|
224
|
+
# command works on a single solution, rather than both the
|
225
|
+
# application and product (unless user specifies sol'n --type).
|
226
|
+
cmd_add_solntype_pickers(cmd)
|
227
|
+
|
228
|
+
cmd.action do |args, options|
|
229
|
+
cmd.verify_arg_count!(args, 1, ['Missing argument: Service alias or ID'])
|
230
|
+
cmd_defaults_solntype_pickers(options)
|
231
|
+
alias_or_id = args[0]
|
232
|
+
solz = must_fetch_solutions!(options)
|
233
|
+
cmd_service_add_sol_services(alias_or_id, solz)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def cmd_service_add_sol_services(alias_or_id, solz)
|
238
|
+
solz.each do |sol|
|
239
|
+
fancy_alias = fancy_ticks(alias_or_id)
|
240
|
+
fancy_name = fancy_ticks(sol.name)
|
241
|
+
MrMurano::Verbose.whirly_start %(
|
242
|
+
Adding #{fancy_alias} serviceconfig to #{fancy_name}...
|
243
|
+
).strip
|
244
|
+
suc = cmd_service_add_sol_service(alias_or_id, sol)
|
245
|
+
warning %(Unable to add service #{fancy_alias} to #{fancy_name}.) unless suc
|
246
|
+
end
|
247
|
+
MrMurano::Verbose.whirly_stop
|
248
|
+
end
|
249
|
+
|
250
|
+
def cmd_service_add_sol_service(alias_or_id, sol)
|
251
|
+
svc_cfg = MrMurano::ServiceConfig.new(sol)
|
252
|
+
svc_cfg.create(alias_or_id) do |request, http|
|
253
|
+
response = http.request(request)
|
254
|
+
cmd_service_add_blather_result(response, alias_or_id, sol)
|
255
|
+
case response
|
256
|
+
when Net::HTTPOK
|
257
|
+
true
|
258
|
+
when Net::HTTPConflict
|
259
|
+
true
|
260
|
+
when Net::HTTPPaymentRequired
|
261
|
+
false
|
262
|
+
else
|
263
|
+
svc_cfg.showHttpError(request, response)
|
264
|
+
false
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def cmd_service_add_blather_result(response, alias_or_id, sol)
|
270
|
+
fancy_alias = fancy_ticks(alias_or_id)
|
271
|
+
fancy_name = fancy_ticks(sol.name)
|
272
|
+
case response
|
273
|
+
when Net::HTTPOK
|
274
|
+
verbose %(Added service #{fancy_alias} to #{fancy_name}.)
|
275
|
+
when Net::HTTPConflict
|
276
|
+
verbose %(Service #{fancy_alias} was already added to #{fancy_name}.)
|
277
|
+
when Net::HTTPPaymentRequired
|
278
|
+
warning %(Use exchange to add the service to your business first.)
|
279
|
+
else
|
280
|
+
warning %(Unexpected response: #{response})
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# *** `service delete` command and helpers.
|
285
|
+
|
286
|
+
def command_service_delete(cmd)
|
287
|
+
cmd.syntax = %(murano service delete [--options] <service-name>)
|
288
|
+
cmd.summary = %(Delete service from solution)
|
289
|
+
cmd.description = %(
|
290
|
+
Delete service from solution.
|
291
|
+
).strip
|
292
|
+
cmd.project_not_required = true
|
293
|
+
|
294
|
+
# Add flag: --type [application|product|all].
|
295
|
+
# FIXME/2018-03-21: The way the CLI works, it chooses all solutions
|
296
|
+
# unless told otherwise; but we probably do not want to delete the
|
297
|
+
# same service from all solutions! So we really need multi-solution
|
298
|
+
# support! I.e., one solution active at a time, not 2!
|
299
|
+
cmd_add_solntype_pickers(cmd)
|
300
|
+
|
301
|
+
cmd.action do |args, options|
|
302
|
+
cmd.verify_arg_count!(args, 1, ['Missing argument: Service alias or ID'])
|
303
|
+
cmd_defaults_solntype_pickers(options)
|
304
|
+
alias_or_id = args[0]
|
305
|
+
solz = must_fetch_solutions!(options)
|
306
|
+
cmd_service_delete_sol_services(alias_or_id, solz)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def cmd_service_delete_sol_services(alias_or_id, solz)
|
311
|
+
solz.each do |sol|
|
312
|
+
fancy_alias = fancy_ticks(alias_or_id)
|
313
|
+
fancy_name = fancy_ticks(sol.name)
|
314
|
+
MrMurano::Verbose.whirly_start %(
|
315
|
+
Deleting #{fancy_alias} serviceconfig from #{fancy_name}...
|
316
|
+
).strip
|
317
|
+
suc = cmd_service_delete_sol_service(alias_or_id, sol)
|
318
|
+
warning %(Service #{fancy_alias} not found for #{fancy_name}.) unless suc
|
319
|
+
end
|
320
|
+
MrMurano::Verbose.whirly_stop
|
321
|
+
end
|
322
|
+
|
323
|
+
def cmd_service_delete_sol_service(alias_or_id, sol)
|
324
|
+
svc_cfg = MrMurano::ServiceConfig.new(sol)
|
325
|
+
svc_cfg.remove(alias_or_id) do |request, http|
|
326
|
+
response = http.request(request)
|
327
|
+
cmd_service_delete_blather_result(response, alias_or_id, sol)
|
328
|
+
case response
|
329
|
+
when Net::HTTPNoContent
|
330
|
+
true
|
331
|
+
when Net::HTTPNotFound
|
332
|
+
false
|
333
|
+
else
|
334
|
+
svc_cfg.showHttpError(request, response)
|
335
|
+
false
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def cmd_service_delete_blather_result(response, alias_or_id, sol)
|
341
|
+
fancy_alias = fancy_ticks(alias_or_id)
|
342
|
+
fancy_name = fancy_ticks(sol.name)
|
343
|
+
case response
|
344
|
+
when Net::HTTPNoContent
|
345
|
+
verbose %(Deleted service #{fancy_alias} from #{fancy_name}.)
|
346
|
+
when Net::HTTPNotFound
|
347
|
+
verbose %(Service #{fancy_alias} was already removed from #{fancy_name}.)
|
348
|
+
else
|
349
|
+
warning %(Unexpected response: #{response})
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# *** `service get` command and helpers.
|
354
|
+
|
355
|
+
def command_service_get(cmd)
|
356
|
+
cmd.syntax = %(murano service get [--options] <service-name>)
|
357
|
+
cmd.summary = %(Get information about an added service)
|
358
|
+
cmd.description = %(
|
359
|
+
Get information about an added service.
|
360
|
+
).strip
|
361
|
+
cmd.project_not_required = true
|
362
|
+
|
363
|
+
# Add flag: --type [application|product|all].
|
364
|
+
cmd_add_solntype_pickers(cmd)
|
365
|
+
|
366
|
+
cmd.action do |args, options|
|
367
|
+
cmd.verify_arg_count!(args, 1, ['Missing argument: Service alias or ID'])
|
368
|
+
cmd_defaults_solntype_pickers(options)
|
369
|
+
alias_or_id = args[0]
|
370
|
+
solz = must_fetch_solutions!(options)
|
371
|
+
sol_infos = cmd_service_get_sol_infos(alias_or_id, solz)
|
372
|
+
cmd_service_get_sol_info_report(sol_infos, solz.first)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def cmd_service_get_sol_infos(alias_or_id, solz)
|
377
|
+
sol_infos = []
|
378
|
+
solz.each do |sol|
|
379
|
+
MrMurano::Verbose.whirly_start %(
|
380
|
+
Fetching #{fancy_ticks(alias_or_id)} serviceconfig for #{fancy_ticks(sol.name)}...
|
381
|
+
).strip
|
382
|
+
svc_info = cmd_service_get_sol_info(alias_or_id, sol)
|
383
|
+
svc_usage = svc_info['usage']
|
384
|
+
svc_usage = '<none reported>' if svc_usage.empty?
|
385
|
+
sol_infos += [[sol.sid, sol.name, svc_usage]]
|
386
|
+
end
|
387
|
+
MrMurano::Verbose.whirly_stop
|
388
|
+
sol_infos
|
389
|
+
end
|
390
|
+
|
391
|
+
def cmd_service_get_sol_info(alias_or_id, sol)
|
392
|
+
svc_cfg = MrMurano::ServiceConfig.new(sol)
|
393
|
+
svc_info = svc_cfg.info(alias_or_id) do |request, http|
|
394
|
+
response = http.request(request)
|
395
|
+
case response
|
396
|
+
when Net::HTTPSuccess
|
397
|
+
JSON.parse(response.body)
|
398
|
+
when Net::HTTPNotFound
|
399
|
+
# Info returns very basic info, e.g., { "usage": {} }
|
400
|
+
{ 'usage' => '<no such service>' }
|
401
|
+
else
|
402
|
+
svc_cfg.showHttpError(request, response)
|
403
|
+
false
|
404
|
+
end
|
405
|
+
end
|
406
|
+
svc_info
|
407
|
+
end
|
408
|
+
|
409
|
+
def cmd_service_get_sol_info_report(sol_infos, any_sol)
|
410
|
+
headers = ['solution id', 'solution name', 'service usage']
|
411
|
+
any_sol.outf(sol_infos) do |dd, ios|
|
412
|
+
MrMurano::Verbose.tabularize(
|
413
|
+
{ headers: headers, rows: dd },
|
414
|
+
ios
|
415
|
+
)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# *** `service list` command and helpers.
|
420
|
+
|
421
|
+
def cmd_service_fetch_solution_services(solz, purchased, options)
|
422
|
+
sol_svcs = []
|
423
|
+
MrMurano::Verbose.whirly_start('Fetching solution services...')
|
424
|
+
solz.each do |sol|
|
425
|
+
MrMurano::Verbose.whirly_start('Fetching solution serviceconfig list...')
|
426
|
+
# We could get everything without a query, e.g.,
|
427
|
+
# svc_cfgs = sol.serviceconfig()
|
428
|
+
# but maybe it's faster to only ask for what we need.
|
429
|
+
svc_cfgs = sol.serviceconfig(
|
430
|
+
select: 'id,service,name,alias,status,type,script_key'
|
431
|
+
)
|
432
|
+
|
433
|
+
MrMurano::Verbose.whirly_start('Fetching solution service list...')
|
434
|
+
svc_defs = sol.service
|
435
|
+
|
436
|
+
MrMurano::Verbose.whirly_start('Assembling solution service list...')
|
437
|
+
svc_reducer = ServiceConsolidator.new(options)
|
438
|
+
sol_svcs += [
|
439
|
+
[
|
440
|
+
sol,
|
441
|
+
svc_reducer.reconcile_services(
|
442
|
+
svc_cfgs[:items],
|
443
|
+
svc_defs[:items],
|
444
|
+
purchased,
|
445
|
+
),
|
446
|
+
],
|
447
|
+
]
|
448
|
+
end
|
449
|
+
MrMurano::Verbose.whirly_stop
|
450
|
+
sol_svcs
|
451
|
+
end
|
452
|
+
|
453
|
+
def sol_svc_rowify(dd, headers, width_avail)
|
454
|
+
rows = []
|
455
|
+
# Sort by status, then alias. (Yeti orders Exchange elements first.)
|
456
|
+
sorted = dd.sort do |lhs, rhs|
|
457
|
+
next(lhs[:status] <=> rhs[:status]) unless lhs[:status] == rhs[:status]
|
458
|
+
lhs[:alias] <=> rhs[:alias]
|
459
|
+
end
|
460
|
+
sorted.each do |svc_cfg|
|
461
|
+
row = headers.map do |key|
|
462
|
+
full = svc_cfg[key]
|
463
|
+
next(full) if full.to_s.empty?
|
464
|
+
# Apparently descriptions have unpredictable whitespace and HTML tags.
|
465
|
+
full = full.tr("\n", ' ')
|
466
|
+
next(full) if width_avail.nil? || key != :description
|
467
|
+
MrMurano::Pretties.split_text_on_whitespace(full, width_avail)
|
468
|
+
end
|
469
|
+
rows << row
|
470
|
+
end
|
471
|
+
rows
|
472
|
+
end
|
473
|
+
|
474
|
+
def cmd_service_output_solution_services(sols_and_svcs, options)
|
475
|
+
headers = %i[name alias status]
|
476
|
+
headers += [:id] if options.show_id
|
477
|
+
headers += [:state] if options.show_state
|
478
|
+
headers += [:source] if options.show_source
|
479
|
+
headers += [:description] if options.show_desc || options.abbrev_desc
|
480
|
+
|
481
|
+
sol_svcs = sols_and_svcs.map { |_sol, svcs| svcs.map { |svc| OpenStruct.new(svc) } }
|
482
|
+
width_avail = MrMurano::Pretties.width_last_column(headers, sol_svcs.flatten)
|
483
|
+
sols_and_svcs.each do |sol, sol_svc|
|
484
|
+
sol.outf(sol_svc) do |dd, ios|
|
485
|
+
ios.puts(sol.pretty_desc(add_type: true)) if options.header
|
486
|
+
rows = sol_svc_rowify(dd, headers, width_avail)
|
487
|
+
sol.tabularize(
|
488
|
+
{ headers: headers, rows: rows },
|
489
|
+
ios
|
490
|
+
)
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
def command_service_list(cmd)
|
496
|
+
cmd.syntax = %(murano service list)
|
497
|
+
cmd.summary = %(List all services previously added to a solution)
|
498
|
+
cmd.description = %(
|
499
|
+
List all services previously added to a solution.
|
500
|
+
).strip
|
501
|
+
cmd.project_not_required = true
|
502
|
+
|
503
|
+
# Add flag: --type [application|product|all].
|
504
|
+
cmd_add_solntype_pickers(cmd)
|
505
|
+
|
506
|
+
cmd.option(
|
507
|
+
'--[no-]header', %(Output solution descriptions (default: true))
|
508
|
+
)
|
509
|
+
|
510
|
+
cmd.option '--show-all', 'Show all services, including hidden'
|
511
|
+
cmd.option '--show-id', 'Show service IDs'
|
512
|
+
cmd.option '--show-type', 'Show service types (Core, Product, Application, External)'
|
513
|
+
cmd.option '--show-state', 'Show service definition status'
|
514
|
+
cmd.option '--show-source', 'Show service source'
|
515
|
+
cmd.option '--show-desc', 'Show service descriptions'
|
516
|
+
cmd.option '--abbrev-desc', 'Show abbreviated service descriptions'
|
517
|
+
|
518
|
+
cmd.option '--hide-added', 'Show only services available to add to Solution'
|
519
|
+
cmd.option '--only-added', 'Show only services already added to Solution'
|
520
|
+
|
521
|
+
cmd.action do |args, options|
|
522
|
+
cmd.verify_arg_count!(args)
|
523
|
+
options.default(
|
524
|
+
all: false,
|
525
|
+
header: true,
|
526
|
+
show_all: false,
|
527
|
+
show_id: false,
|
528
|
+
show_type: false,
|
529
|
+
show_state: false,
|
530
|
+
show_source: false,
|
531
|
+
show_desc: false,
|
532
|
+
abbrev_desc: false,
|
533
|
+
hide_added: false,
|
534
|
+
only_added: false,
|
535
|
+
)
|
536
|
+
cmd_defaults_solntype_pickers(options)
|
537
|
+
|
538
|
+
solz = must_fetch_solutions!(options)
|
539
|
+
# Prepare the list of purchased Exchange services.
|
540
|
+
biz_exg = MrMurano::Exchange.new
|
541
|
+
MrMurano::Verbose.whirly_start('Fetching Exchange purchases list...')
|
542
|
+
purchased = biz_exg.fetch_purchased(services_only: true)
|
543
|
+
sols_and_svcs = cmd_service_fetch_solution_services(solz, purchased, options)
|
544
|
+
|
545
|
+
cmd_service_output_solution_services(sols_and_svcs, options)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
def wire_cmd_service
|
551
|
+
service_cmd = ServiceCmd.new
|
552
|
+
|
553
|
+
command(:service) { |cmd| service_cmd.command_service(cmd) }
|
554
|
+
|
555
|
+
command('service add') { |cmd| service_cmd.command_service_add(cmd) }
|
556
|
+
alias_command 'service create', 'service add'
|
557
|
+
|
558
|
+
command('service delete') { |cmd| service_cmd.command_service_delete(cmd) }
|
559
|
+
alias_command 'service remove', 'service delete'
|
560
|
+
|
561
|
+
command('service get') { |cmd| service_cmd.command_service_get(cmd) }
|
562
|
+
|
563
|
+
command('service list') { |cmd| service_cmd.command_service_list(cmd) }
|
564
|
+
alias_command 'services list', 'service list'
|
565
|
+
alias_command 'service list available', 'service list', '--hide-added'
|
566
|
+
alias_command 'service list all', 'service list', '--show-all'
|
567
|
+
alias_command 'service list full', 'service list', '--show-all', '--show-type', '--show-state', '--show-source', '--show-desc'
|
568
|
+
alias_command 'service status', 'service list', '--only-added'
|
569
|
+
end
|
570
|
+
|
571
|
+
wire_cmd_service
|
572
|
+
|
@@ -6,9 +6,9 @@
|
|
6
6
|
# Unauthorized copying of this file is strictly prohibited.
|
7
7
|
|
8
8
|
require 'MrMurano/verbosing'
|
9
|
+
require 'MrMurano/AccountBase'
|
9
10
|
require 'MrMurano/Business'
|
10
11
|
require 'MrMurano/ReCommander'
|
11
|
-
require 'MrMurano/Solution'
|
12
12
|
|
13
13
|
command :show do |c|
|
14
14
|
c.syntax = %(murano show)
|
@@ -111,7 +111,7 @@ Show readable information about the current configuration.
|
|
111
111
|
puts 'product ID not found in config'
|
112
112
|
end
|
113
113
|
|
114
|
-
MrMurano::
|
114
|
+
MrMurano::AccountBase.warn_configfile_env_maybe if id_not_in_biz
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
@@ -126,7 +126,11 @@ Show readable information about the current configuration.
|
|
126
126
|
c.action do |args, _options|
|
127
127
|
c.verify_arg_count!(args)
|
128
128
|
puts %(base: #{$cfg['location.base']})
|
129
|
-
$cfg['location'].each
|
129
|
+
$cfg['location'].keys.sort.each do |key|
|
130
|
+
next if key == 'base'
|
131
|
+
value = $cfg['location'][key]
|
132
|
+
puts %(#{key}: #{$cfg['location.base']}/#{value})
|
133
|
+
end
|
130
134
|
end
|
131
135
|
end
|
132
136
|
|
@@ -6,7 +6,7 @@
|
|
6
6
|
# Unauthorized copying of this file is strictly prohibited.
|
7
7
|
|
8
8
|
require 'MrMurano/verbosing'
|
9
|
-
require 'MrMurano/
|
9
|
+
require 'MrMurano/Business'
|
10
10
|
require 'MrMurano/ReCommander'
|
11
11
|
require 'MrMurano/SubCmdGroupContext'
|
12
12
|
require 'MrMurano/commands/business'
|
@@ -53,6 +53,7 @@ Create a new solution in the current business.
|
|
53
53
|
if args.count.zero?
|
54
54
|
sol = biz.solution_from_type!(options.type)
|
55
55
|
name = solution_ask_for_name(sol)
|
56
|
+
next if name.nil?
|
56
57
|
names = [name]
|
57
58
|
else
|
58
59
|
names = args
|
@@ -5,10 +5,12 @@
|
|
5
5
|
# vim:tw=0:ts=2:sw=2:et:ai
|
6
6
|
# Unauthorized copying of this file is strictly prohibited.
|
7
7
|
|
8
|
+
require 'rainbow'
|
8
9
|
require 'MrMurano/verbosing'
|
9
10
|
require 'MrMurano/Business'
|
10
11
|
require 'MrMurano/Config'
|
11
12
|
require 'MrMurano/Solution'
|
13
|
+
require 'MrMurano/variegated/TruthyFalsey'
|
12
14
|
|
13
15
|
MSG_SOLUTIONS_NONE_FOUND = 'No solutions found' unless defined? MSG_SOLUTIONS_NONE_FOUND
|
14
16
|
|
@@ -252,29 +254,32 @@ def solution_get_solutions(biz, type, api_id: nil, name: nil, fuzzy: nil)
|
|
252
254
|
solz
|
253
255
|
end
|
254
256
|
|
255
|
-
def solution_ask_for_name(
|
256
|
-
|
257
|
-
while
|
258
|
-
solname = ask("
|
257
|
+
def solution_ask_for_name(sol_model)
|
258
|
+
solname = ''
|
259
|
+
while solname == ''
|
260
|
+
solname = ask("Enter the #{sol_model.type_name} name [leave blank to skip]: ")
|
259
261
|
puts ''
|
260
262
|
if solname == ''
|
261
|
-
confirmed = ask(
|
262
|
-
|
263
|
+
confirmed = ask(
|
264
|
+
Rainbow("Really skip the #{sol_model.type_name}").bright.underline + '? [Y/n] '
|
265
|
+
)
|
266
|
+
if confirmed == '' || TruthyFalsey.new(confirmed).truthy?
|
263
267
|
puts ''
|
264
|
-
|
268
|
+
solname = nil
|
265
269
|
end
|
266
270
|
else
|
267
|
-
#
|
271
|
+
# Try setting the name on the dummy solution; warn if fails, and keep looping.
|
268
272
|
begin
|
269
|
-
sol.
|
270
|
-
|
273
|
+
# Test sol.name_validate_regex.
|
274
|
+
sol_model.set_name!(solname)
|
271
275
|
rescue MrMurano::ConfigError => _err
|
272
|
-
say(
|
276
|
+
say(sol_model.name_validate_help)
|
273
277
|
# keep looping
|
278
|
+
solname = ''
|
274
279
|
end
|
275
280
|
end
|
276
281
|
end
|
277
|
-
|
282
|
+
solname
|
278
283
|
rescue EOFError
|
279
284
|
# E.g., the user pressed Ctrl-D.
|
280
285
|
# "error: The input stream is exhausted."
|
@@ -491,13 +496,24 @@ module MrMurano
|
|
491
496
|
if solname.nil?
|
492
497
|
#say "You do not have any #{type}s. Let's create one."
|
493
498
|
if @verbose
|
494
|
-
|
499
|
+
plural = Inflecto.pluralize(sol.type.to_s)
|
500
|
+
say("This business does not have any #{plural}. Let's create one")
|
495
501
|
puts ''
|
496
502
|
end
|
497
|
-
solution_ask_for_name(sol)
|
498
|
-
|
503
|
+
solname = solution_ask_for_name(sol)
|
504
|
+
end
|
505
|
+
return nil if solname.nil?
|
506
|
+
|
507
|
+
# The solution name, if we asked for it, will have been verified;
|
508
|
+
# otherwise, if supplied as --option but an invalid solution name,
|
509
|
+
# raise a fuss.
|
510
|
+
begin
|
499
511
|
sol.set_name!(solname)
|
512
|
+
rescue MrMurano::ConfigError => _err
|
513
|
+
MrMurano::Verbose.error(sol.name_validate_help)
|
514
|
+
exit 1
|
500
515
|
end
|
516
|
+
|
501
517
|
# MAYBE/2017-07-20: Detect if Business is ADC enabled. If not,
|
502
518
|
# creating a solution fails, e.g.,
|
503
519
|
# Request Failed: 409: [409] upgrade
|