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
|
@@ -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
|