morpheus-cli 4.2.1 → 4.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +17 -1
- data/lib/morpheus/api/datastores_interface.rb +18 -0
- data/lib/morpheus/api/invoices_interface.rb +24 -0
- data/lib/morpheus/api/provisioning_license_types_interface.rb +37 -0
- data/lib/morpheus/api/provisioning_licenses_interface.rb +66 -0
- data/lib/morpheus/cli.rb +3 -1
- data/lib/morpheus/cli/invoices_command.rb +339 -0
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +28 -8
- data/lib/morpheus/cli/option_types.rb +16 -3
- data/lib/morpheus/cli/provisioning_licenses_command.rb +506 -0
- data/lib/morpheus/cli/service_plans_command.rb +0 -3
- data/lib/morpheus/cli/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44226a558c2f02d9ca3aea75025930a2adb9c8b63e41fa6437d14ad55f34211b
|
4
|
+
data.tar.gz: debee18e47a2930a5da5cf2085d6463ce23171a70e875bbe81dc33d68ec1b640
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3d6087cb623b08e6adabe1bf9e8f214b38c9eef2a627a47442b30c3f9523c7a5487fdff61c63f4fef885176695fc595cd0d751287a3e42b8edbebbf984b06f8
|
7
|
+
data.tar.gz: ab8829f3d013e6ef4345447f90cd2defb13e2d91491f5b860ad44fe06ac022b3e2bf8baa3c12e525ab7e33f527e7b51b35043538ab82a52e79c5d628fff3dd0f
|
data/Dockerfile
CHANGED
@@ -319,6 +319,10 @@ class Morpheus::APIClient
|
|
319
319
|
Morpheus::CloudFoldersInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
|
320
320
|
end
|
321
321
|
|
322
|
+
def datastores
|
323
|
+
Morpheus::DatastoresInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
|
324
|
+
end
|
325
|
+
|
322
326
|
def servers
|
323
327
|
Morpheus::ServersInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
|
324
328
|
end
|
@@ -335,6 +339,14 @@ class Morpheus::APIClient
|
|
335
339
|
Morpheus::ProvisioningSettingsInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
|
336
340
|
end
|
337
341
|
|
342
|
+
def provisioning_licenses
|
343
|
+
Morpheus::ProvisioningLicensesInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
|
344
|
+
end
|
345
|
+
|
346
|
+
def provisioning_license_types
|
347
|
+
Morpheus::ProvisioningLicenseTypesInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
|
348
|
+
end
|
349
|
+
|
338
350
|
def containers
|
339
351
|
Morpheus::ContainersInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
|
340
352
|
end
|
@@ -670,6 +682,10 @@ class Morpheus::APIClient
|
|
670
682
|
Morpheus::HealthInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
|
671
683
|
end
|
672
684
|
|
673
|
-
|
685
|
+
def invoices
|
686
|
+
Morpheus::InvoicesInterface.new(@access_token, @refresh_token, @expires_at, @base_url).setopts(@options)
|
687
|
+
end
|
688
|
+
|
689
|
+
# add new interfaces here
|
674
690
|
|
675
691
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'morpheus/api/api_client'
|
2
|
+
|
3
|
+
class Morpheus::DatastoresInterface < Morpheus::APIClient
|
4
|
+
def initialize(access_token, refresh_token,expires_at = nil, base_url=nil, api='data-stores')
|
5
|
+
@access_token = access_token
|
6
|
+
@refresh_token = refresh_token
|
7
|
+
@base_url = base_url
|
8
|
+
@api_url = "#{base_url}/api/#{api}"
|
9
|
+
@expires_at = expires_at
|
10
|
+
end
|
11
|
+
|
12
|
+
def list(params={})
|
13
|
+
url = @api_url
|
14
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
15
|
+
execute(method: :get, url: url, headers: headers)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'morpheus/api/api_client'
|
2
|
+
|
3
|
+
class Morpheus::InvoicesInterface < Morpheus::APIClient
|
4
|
+
def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
|
5
|
+
@access_token = access_token
|
6
|
+
@refresh_token = refresh_token
|
7
|
+
@base_url = base_url
|
8
|
+
@expires_at = expires_at
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(id)
|
12
|
+
raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
|
13
|
+
url = "#{@base_url}/api/invoices/#{id}"
|
14
|
+
headers = { params: {}, authorization: "Bearer #{@access_token}" }
|
15
|
+
execute(method: :get, url: url, headers: headers)
|
16
|
+
end
|
17
|
+
|
18
|
+
def list(params={})
|
19
|
+
url = "#{@base_url}/api/invoices"
|
20
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
21
|
+
execute(method: :get, url: url, headers: headers)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'morpheus/api/api_client'
|
2
|
+
|
3
|
+
class Morpheus::ProvisioningLicenseTypesInterface < Morpheus::APIClient
|
4
|
+
def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
|
5
|
+
@access_token = access_token
|
6
|
+
@refresh_token = refresh_token
|
7
|
+
@base_url = base_url
|
8
|
+
@expires_at = expires_at
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(id, params={})
|
12
|
+
raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
|
13
|
+
url = build_url(id)
|
14
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
15
|
+
opts = {method: :get, url: url, headers: headers}
|
16
|
+
execute(opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
def list(params={})
|
20
|
+
url = build_url()
|
21
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
22
|
+
opts = {method: :get, url: url, headers: headers}
|
23
|
+
execute(opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def build_url(id=nil)
|
29
|
+
url = "#{@base_url}/api/provisioning-license-types"
|
30
|
+
if id
|
31
|
+
url += "/#{id}"
|
32
|
+
end
|
33
|
+
url
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'morpheus/api/api_client'
|
2
|
+
|
3
|
+
class Morpheus::ProvisioningLicensesInterface < Morpheus::APIClient
|
4
|
+
def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
|
5
|
+
@access_token = access_token
|
6
|
+
@refresh_token = refresh_token
|
7
|
+
@base_url = base_url
|
8
|
+
@expires_at = expires_at
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(id, params={})
|
12
|
+
raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
|
13
|
+
url = build_url(id)
|
14
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
15
|
+
opts = {method: :get, url: url, headers: headers}
|
16
|
+
execute(opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
def list(params={})
|
20
|
+
url = build_url()
|
21
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
22
|
+
opts = {method: :get, url: url, headers: headers}
|
23
|
+
execute(opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
def create(payload)
|
27
|
+
url = build_url()
|
28
|
+
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
29
|
+
opts = {method: :post, url: url, headers: headers, payload: payload.to_json}
|
30
|
+
execute(opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
def update(id, payload)
|
34
|
+
url = build_url(id)
|
35
|
+
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
36
|
+
opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
|
37
|
+
execute(opts)
|
38
|
+
end
|
39
|
+
|
40
|
+
def destroy(id, params={})
|
41
|
+
url = build_url(id)
|
42
|
+
headers = { :params => params, :authorization => "Bearer #{@access_token}"}
|
43
|
+
opts = {method: :delete, url: url, headers: headers}
|
44
|
+
execute(opts)
|
45
|
+
end
|
46
|
+
|
47
|
+
def reservations(id, params={})
|
48
|
+
raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
|
49
|
+
url = build_url(id) + "/reservations"
|
50
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
51
|
+
opts = {method: :get, url: url, headers: headers}
|
52
|
+
execute(opts)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def build_url(id=nil)
|
58
|
+
url = "#{@base_url}/api/provisioning-licenses"
|
59
|
+
if id
|
60
|
+
url += "/#{id}"
|
61
|
+
end
|
62
|
+
url
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
end
|
data/lib/morpheus/cli.rb
CHANGED
@@ -161,9 +161,11 @@ module Morpheus
|
|
161
161
|
load 'morpheus/cli/price_sets_command.rb'
|
162
162
|
load 'morpheus/cli/prices_command.rb'
|
163
163
|
load 'morpheus/cli/provisioning_settings_command.rb'
|
164
|
+
load 'morpheus/cli/provisioning_licenses_command.rb'
|
164
165
|
load 'morpheus/cli/budgets_command.rb'
|
165
166
|
load 'morpheus/cli/health_command.rb'
|
166
|
-
|
167
|
+
load 'morpheus/cli/invoices_command.rb'
|
168
|
+
# add new commands here...
|
167
169
|
|
168
170
|
end
|
169
171
|
|
@@ -0,0 +1,339 @@
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
class Morpheus::Cli::InvoicesCommand
|
5
|
+
include Morpheus::Cli::CliCommand
|
6
|
+
|
7
|
+
set_command_name :'invoices'
|
8
|
+
|
9
|
+
register_subcommands :list, :get
|
10
|
+
|
11
|
+
def connect(opts)
|
12
|
+
@api_client = establish_remote_appliance_connection(opts)
|
13
|
+
@invoices_interface = @api_client.invoices
|
14
|
+
end
|
15
|
+
|
16
|
+
def handle(args)
|
17
|
+
handle_subcommand(args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def list(args)
|
21
|
+
options = {}
|
22
|
+
params = {}
|
23
|
+
ref_ids = []
|
24
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
25
|
+
opts.banner = subcommand_usage()
|
26
|
+
opts.on('--type TYPE', String, "Find invoices for a Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
|
27
|
+
if val.to_s.downcase == 'cloud' || val.to_s.downcase == 'zone'
|
28
|
+
params['refType'] = 'ComputeZone'
|
29
|
+
elsif val.to_s.downcase == 'instance'
|
30
|
+
params['refType'] = 'Instance'
|
31
|
+
elsif val.to_s.downcase == 'server' || val.to_s.downcase == 'host'
|
32
|
+
params['refType'] = 'ComputeServer'
|
33
|
+
elsif val.to_s.downcase == 'cluster'
|
34
|
+
params['refType'] = 'ComputeServerGroup'
|
35
|
+
elsif val.to_s.downcase == 'group'
|
36
|
+
params['refType'] = 'ComputeSite'
|
37
|
+
elsif val.to_s.downcase == 'user'
|
38
|
+
params['refType'] = 'User'
|
39
|
+
else
|
40
|
+
params['refType'] = val
|
41
|
+
end
|
42
|
+
end
|
43
|
+
opts.on('--ref-id ID', String, "Find invoices for a Ref ID") do |val|
|
44
|
+
ref_ids << val
|
45
|
+
end
|
46
|
+
opts.on('--group ID', String, "Find invoices for a Group") do |val|
|
47
|
+
# params['siteId'] = val
|
48
|
+
params['refType'] = 'ComputeSite'
|
49
|
+
ref_ids << val
|
50
|
+
end
|
51
|
+
opts.on('--cloud ID', String, "Find invoices for a Cloud") do |val|
|
52
|
+
# params['zoneId'] = val
|
53
|
+
params['refType'] = 'ComputeZone'
|
54
|
+
ref_ids << val
|
55
|
+
end
|
56
|
+
opts.on('--instance ID', String, "Find invoices for an Instance") do |val|
|
57
|
+
# params['instanceId'] = val
|
58
|
+
params['refType'] = 'Instance'
|
59
|
+
ref_ids << val
|
60
|
+
end
|
61
|
+
opts.on('--container ID', String, "Find invoices for a Container") do |val|
|
62
|
+
# params['instanceId'] = val
|
63
|
+
params['refType'] = 'Container'
|
64
|
+
ref_ids << val
|
65
|
+
end
|
66
|
+
opts.on('--server ID', String, "Find invoices for a Server (Host)") do |val|
|
67
|
+
# params['serverId'] = val
|
68
|
+
params['refType'] = 'ComputeServer'
|
69
|
+
ref_ids << val
|
70
|
+
end
|
71
|
+
opts.on('--user ID', String, "Find invoices for a User") do |val|
|
72
|
+
# params['userId'] = val
|
73
|
+
params['refType'] = 'User'
|
74
|
+
ref_ids << val
|
75
|
+
end
|
76
|
+
# opts.on('--cluster ID', String, "Filter by Cluster") do |val|
|
77
|
+
# # params['clusterId'] = val
|
78
|
+
# params['refType'] = 'ComputeServerGroup'
|
79
|
+
# params['refId'] = val.to_i
|
80
|
+
# end
|
81
|
+
opts.on('--start DATE', String, "Start date in the format YYYY-MM-DD.") do |val|
|
82
|
+
params['startDate'] = val #parse_time(val).utc.iso8601
|
83
|
+
end
|
84
|
+
opts.on('--end DATE', String, "End date in the format YYYY-MM-DD. Default is now.") do |val|
|
85
|
+
params['endDate'] = val #parse_time(val).utc.iso8601
|
86
|
+
end
|
87
|
+
opts.on('--period PERIOD', String, "Period in the format YYYYMM. This can be used instead of start/end.") do |val|
|
88
|
+
params['period'] = parse_period(val)
|
89
|
+
end
|
90
|
+
opts.on('--active [true|false]',String, "Filter by active.") do |val|
|
91
|
+
params['active'] = (val.to_s != 'false' && val.to_s != 'off')
|
92
|
+
end
|
93
|
+
opts.on('--tenant ID', String, "View invoices for a tenant. Default is your own account.") do |val|
|
94
|
+
params['accountId'] = val
|
95
|
+
end
|
96
|
+
build_standard_list_options(opts, options)
|
97
|
+
opts.footer = "List invoices."
|
98
|
+
end
|
99
|
+
optparse.parse!(args)
|
100
|
+
connect(options)
|
101
|
+
if args.count > 0
|
102
|
+
print_error Morpheus::Terminal.angry_prompt
|
103
|
+
puts_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args.join(', ')}\n#{optparse}"
|
104
|
+
return 1
|
105
|
+
end
|
106
|
+
begin
|
107
|
+
params['refId'] = ref_ids unless ref_ids.empty?
|
108
|
+
params.merge!(parse_list_options(options))
|
109
|
+
@invoices_interface.setopts(options)
|
110
|
+
if options[:dry_run]
|
111
|
+
print_dry_run @invoices_interface.dry.list(params)
|
112
|
+
return
|
113
|
+
end
|
114
|
+
json_response = @invoices_interface.list(params)
|
115
|
+
render_result = render_with_format(json_response, options, 'invoice')
|
116
|
+
return 0 if render_result
|
117
|
+
invoices = json_response['invoices']
|
118
|
+
title = "Morpheus Invoices"
|
119
|
+
subtitles = []
|
120
|
+
if params['status']
|
121
|
+
subtitles << "Status: #{params['status']}"
|
122
|
+
end
|
123
|
+
if params['alarmStatus'] == 'acknowledged'
|
124
|
+
subtitles << "(Acknowledged)"
|
125
|
+
end
|
126
|
+
if params['startDate']
|
127
|
+
subtitles << "Start Date: #{params['startDate']}"
|
128
|
+
end
|
129
|
+
if params['endDate']
|
130
|
+
subtitles << "End Date: #{params['endDate']}"
|
131
|
+
end
|
132
|
+
subtitles += parse_list_subtitles(options)
|
133
|
+
print_h1 title, subtitles
|
134
|
+
if invoices.empty?
|
135
|
+
print cyan,"No invoices found.",reset,"\n"
|
136
|
+
else
|
137
|
+
print_invoices_table(invoices, options)
|
138
|
+
print_results_pagination(json_response, {:label => "invoice", :n_label => "invoices"})
|
139
|
+
end
|
140
|
+
print reset,"\n"
|
141
|
+
rescue RestClient::Exception => e
|
142
|
+
print_rest_exception(e, options)
|
143
|
+
return 1
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def get(args)
|
148
|
+
options = {}
|
149
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
150
|
+
opts.banner = subcommand_usage("[id]")
|
151
|
+
build_standard_get_options(opts, options)
|
152
|
+
opts.footer = "Get details about a specific invoice."
|
153
|
+
end
|
154
|
+
optparse.parse!(args)
|
155
|
+
if args.count < 1
|
156
|
+
puts optparse
|
157
|
+
return 1
|
158
|
+
end
|
159
|
+
connect(options)
|
160
|
+
id_list = parse_id_list(args)
|
161
|
+
return run_command_for_each_arg(id_list) do |arg|
|
162
|
+
_get(arg, options)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def _get(id, options)
|
167
|
+
|
168
|
+
begin
|
169
|
+
@invoices_interface.setopts(options)
|
170
|
+
if options[:dry_run]
|
171
|
+
print_dry_run @invoices_interface.dry.get(id)
|
172
|
+
return
|
173
|
+
end
|
174
|
+
json_response = @invoices_interface.get(id)
|
175
|
+
invoice = json_response['invoice']
|
176
|
+
render_result = render_with_format(json_response, options, 'invoice')
|
177
|
+
return 0 if render_result
|
178
|
+
|
179
|
+
print_h1 "Invoice Details"
|
180
|
+
print cyan
|
181
|
+
|
182
|
+
|
183
|
+
description_cols = {
|
184
|
+
"Invoice ID" => lambda {|it| it['id'] },
|
185
|
+
"Type" => lambda {|it| format_invoice_ref_type(it) },
|
186
|
+
"Ref ID" => lambda {|it| it['refId'] },
|
187
|
+
"Ref Name" => lambda {|it| it['refName'] },
|
188
|
+
"Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
|
189
|
+
"Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
|
190
|
+
"Active" => lambda {|it| format_boolean(it['active']) },
|
191
|
+
"Period" => lambda {|it| format_invoice_period(it) },
|
192
|
+
#"Interval" => lambda {|it| it['interval'] },
|
193
|
+
"Start" => lambda {|it| format_local_dt(it['startDate']) },
|
194
|
+
"End" => lambda {|it| it['endDate'] ? format_local_dt(it['endDate']) : '' },
|
195
|
+
"Estimate" => lambda {|it| format_boolean(it['estimate']) },
|
196
|
+
"Price" => lambda {|it| format_money(it['totalPrice']) },
|
197
|
+
"Cost" => lambda {|it| format_money(it['totalCost']) },
|
198
|
+
# "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
199
|
+
# "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
200
|
+
}
|
201
|
+
print_description_list(description_cols, invoice)
|
202
|
+
|
203
|
+
if invoice['rawData'] && !invoice['rawData'].empty?
|
204
|
+
print_h2 "Raw Data"
|
205
|
+
puts invoice['rawData']
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
print reset,"\n"
|
210
|
+
return 0
|
211
|
+
rescue RestClient::Exception => e
|
212
|
+
print_rest_exception(e, options)
|
213
|
+
return 1
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
# def find_invoice_by_name_or_id(val)
|
220
|
+
# if val.to_s =~ /\A\d{1,}\Z/
|
221
|
+
# return find_invoice_by_id(val)
|
222
|
+
# else
|
223
|
+
# return find_invoice_by_name(val)
|
224
|
+
# end
|
225
|
+
# end
|
226
|
+
|
227
|
+
def find_invoice_by_id(id)
|
228
|
+
begin
|
229
|
+
json_response = @invoices_interface.get(id.to_i)
|
230
|
+
return json_response['invoice']
|
231
|
+
rescue RestClient::Exception => e
|
232
|
+
if e.response && e.response.code == 404
|
233
|
+
print_red_alert "Invoice not found by id #{id}"
|
234
|
+
else
|
235
|
+
raise e
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# def find_invoice_by_name(name)
|
241
|
+
# invoices = @invoices_interface.list({name: name.to_s})['invoices']
|
242
|
+
# if invoices.empty?
|
243
|
+
# print_red_alert "Invoice not found by name #{name}"
|
244
|
+
# return nil
|
245
|
+
# elsif invoices.size > 1
|
246
|
+
# print_red_alert "#{invoices.size} invoices found by name #{name}"
|
247
|
+
# print_invoices_table(invoices, {color: red})
|
248
|
+
# print_red_alert "Try using ID instead"
|
249
|
+
# print reset,"\n"
|
250
|
+
# return nil
|
251
|
+
# else
|
252
|
+
# return invoices[0]
|
253
|
+
# end
|
254
|
+
# end
|
255
|
+
|
256
|
+
def print_invoices_table(invoices, opts={})
|
257
|
+
columns = [
|
258
|
+
{"INVOICE ID" => lambda {|it| it['id'] } },
|
259
|
+
{"TYPE" => lambda {|it| format_invoice_ref_type(it) } },
|
260
|
+
{"REF ID" => lambda {|it| it['refId'] } },
|
261
|
+
{"REF NAME" => lambda {|it| it['refName'] } },
|
262
|
+
#{"INTERVAL" => lambda {|it| it['interval'] } },
|
263
|
+
{"ACCOUNT" => lambda {|it| it['account'] ? it['account']['name'] : '' } },
|
264
|
+
{"ACTIVE" => lambda {|it| format_boolean(it['active']) } },
|
265
|
+
{"PERIOD" => lambda {|it| format_invoice_period(it) } },
|
266
|
+
{"START" => lambda {|it| format_local_dt(it['startDate']) } },
|
267
|
+
{"END" => lambda {|it| it['endDate'] ? format_local_dt(it['endDate']) : '' } },
|
268
|
+
{"PRICE" => lambda {|it| format_money(it['totalPrice']) } },
|
269
|
+
{"COST" => lambda {|it| format_money(it['totalCost']) } },
|
270
|
+
]
|
271
|
+
if opts[:include_fields]
|
272
|
+
columns = opts[:include_fields]
|
273
|
+
end
|
274
|
+
print as_pretty_table(invoices, columns, opts)
|
275
|
+
end
|
276
|
+
|
277
|
+
def format_invoice_ref_type(it)
|
278
|
+
if it['refType'] == 'ComputeZone'
|
279
|
+
"Cloud"
|
280
|
+
elsif it['refType'] == 'Instance'
|
281
|
+
"Instance"
|
282
|
+
elsif it['refType'] == 'ComputeServer'
|
283
|
+
"Host"
|
284
|
+
elsif it['refType'] == 'ComputeServerGroup'
|
285
|
+
"Cluster"
|
286
|
+
elsif it['refType'] == 'ComputeSite'
|
287
|
+
"Group"
|
288
|
+
else
|
289
|
+
it['refType']
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# convert "202003" to "March 2020"
|
294
|
+
def format_invoice_period(it)
|
295
|
+
interval = it['interval']
|
296
|
+
period = it['period']
|
297
|
+
if period
|
298
|
+
if interval == 'month'
|
299
|
+
year = period[0..3]
|
300
|
+
month = period[4..5]
|
301
|
+
if year && month
|
302
|
+
month_name = Date::MONTHNAMES[month.to_i] || "#{month}?"
|
303
|
+
return "#{month_name} #{year}"
|
304
|
+
else
|
305
|
+
return "#{year}"
|
306
|
+
end
|
307
|
+
else
|
308
|
+
return it['period']
|
309
|
+
end
|
310
|
+
else
|
311
|
+
return "n/a"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# convert "March 2020" to "202003"
|
316
|
+
def parse_period(period, interval='month')
|
317
|
+
if period
|
318
|
+
if interval == 'month'
|
319
|
+
if period.include?(" ")
|
320
|
+
period_parts = period.split(" ")
|
321
|
+
month = Date::MONTHNAMES.index(period_parts[0])
|
322
|
+
year = period_parts[1].to_i
|
323
|
+
if month
|
324
|
+
return "#{year}#{month.to_s.rjust(2, '0')}"
|
325
|
+
else
|
326
|
+
return "#{year}00" # meh, bad month name, raise error probably
|
327
|
+
end
|
328
|
+
else
|
329
|
+
return "#{period}"
|
330
|
+
end
|
331
|
+
else
|
332
|
+
return "#{period}"
|
333
|
+
end
|
334
|
+
else
|
335
|
+
return nil
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
@@ -41,6 +41,10 @@ module Morpheus::Cli::ProvisioningHelper
|
|
41
41
|
@api_client.cloud_datastores
|
42
42
|
end
|
43
43
|
|
44
|
+
def datastores_interface
|
45
|
+
@api_client.datastores
|
46
|
+
end
|
47
|
+
|
44
48
|
def accounts_interface
|
45
49
|
@api_client.accounts
|
46
50
|
end
|
@@ -323,10 +327,10 @@ module Morpheus::Cli::ProvisioningHelper
|
|
323
327
|
available_clouds = get_available_clouds(group_id)
|
324
328
|
cloud_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'selectOptions' => get_available_clouds(group_id), 'required' => true, 'description' => 'Select Cloud.', 'defaultValue' => options[:default_cloud] ? options[:default_cloud] : nil}],options[:options],api_client,{groupId: group_id})
|
325
329
|
cloud_id = cloud_prompt['cloud']
|
330
|
+
cloud = available_clouds.find {|it| it['value'] == cloud_id}
|
326
331
|
end
|
327
332
|
|
328
|
-
|
329
|
-
cloud_type = cloud['zoneType'] || {}
|
333
|
+
cloud_type = clouds_interface.cloud_type(cloud['zoneTypeId'])
|
330
334
|
|
331
335
|
# Instance Type
|
332
336
|
instance_type_code = nil
|
@@ -371,6 +375,7 @@ module Morpheus::Cli::ProvisioningHelper
|
|
371
375
|
|
372
376
|
# config
|
373
377
|
config = {}
|
378
|
+
=begin
|
374
379
|
if cloud_type['code'] == 'amazon' && (cloud['config'] || {})['isVpc'] == 'false' && (cloud['config'] || {})['vpc'] == ''
|
375
380
|
config['isEC2'] = true
|
376
381
|
else
|
@@ -382,6 +387,7 @@ module Morpheus::Cli::ProvisioningHelper
|
|
382
387
|
config['isVpcSelectable'] = true
|
383
388
|
end
|
384
389
|
end
|
390
|
+
=end
|
385
391
|
|
386
392
|
payload = {
|
387
393
|
'zoneId' => cloud_id,
|
@@ -490,7 +496,8 @@ module Morpheus::Cli::ProvisioningHelper
|
|
490
496
|
layout_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'select', 'fieldLabel' => 'Layout', 'optionSource' => 'layoutsForCloud', 'required' => true, 'description' => 'Select which configuration of the instance type to be provisioned.', 'defaultValue' => default_layout_value}],options[:options],api_client,{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id'], version: version_value, creatable: true})['layout']
|
491
497
|
end
|
492
498
|
|
493
|
-
layout = find_instance_type_layout_by_id(instance_type['id'], layout_id.to_i)
|
499
|
+
# layout = find_instance_type_layout_by_id(instance_type['id'], layout_id.to_i)
|
500
|
+
layout = (instance_type['instanceTypeLayouts'] || []).find {|it| it['id'] == layout_id.to_i }
|
494
501
|
if !layout
|
495
502
|
print_red_alert "Layout not found by id #{layout_id}"
|
496
503
|
exit 1
|
@@ -587,6 +594,7 @@ module Morpheus::Cli::ProvisioningHelper
|
|
587
594
|
elsif resource_pool_prompt[resource_pool_option_type['fieldName']]
|
588
595
|
pool_id = resource_pool_prompt[resource_pool_option_type['fieldName']]
|
589
596
|
end
|
597
|
+
resource_pool ||= resource_pool_options.find {|it| it['id'] == pool_id}
|
590
598
|
end
|
591
599
|
end
|
592
600
|
|
@@ -600,23 +608,33 @@ module Morpheus::Cli::ProvisioningHelper
|
|
600
608
|
# add selectable datastores for resource pool
|
601
609
|
if options[:select_datastore]
|
602
610
|
begin
|
603
|
-
|
604
|
-
|
605
|
-
['
|
611
|
+
selectable_datastores = datastores_interface.list({'zoneId' => cloud_id, 'siteId' => group_id, 'resourcePoolId' => resource_pool['id']})
|
612
|
+
service_plan['datastores'] = {'clusters' => [], 'datastores' => []}
|
613
|
+
['clusters', 'datastores'].each do |type|
|
606
614
|
service_plan['datastores'][type] ||= []
|
607
615
|
selectable_datastores[type].reject { |ds| service_plan['datastores'][type].find {|it| it['id'] == ds['id']} }.each { |ds|
|
608
616
|
service_plan['datastores'][type] << ds
|
609
617
|
}
|
610
618
|
end
|
611
619
|
rescue => error
|
620
|
+
Morpheus::Logging::DarkPrinter.puts "Unable to load available data-stores, using datastores option source instead." if Morpheus::Logging.debug?
|
612
621
|
end
|
613
622
|
|
614
623
|
if provision_type && provision_type['supportsAutoDatastore']
|
624
|
+
service_plan['supportsAutoDatastore'] = true
|
615
625
|
service_plan['autoOptions'] ||= []
|
616
|
-
if service_plan['datastores']
|
626
|
+
if service_plan['datastores'] && service_plan['datastores']['clusters']
|
627
|
+
if service_plan['datastores']['clusters'].count > 0 && !service_plan['autoOptions'].find {|it| it['id'] == 'autoCluster'}
|
628
|
+
service_plan['autoOptions'] << {'id' => 'autoCluster', 'name' => 'Auto - Cluster'}
|
629
|
+
end
|
630
|
+
else
|
617
631
|
service_plan['autoOptions'] << {'id' => 'autoCluster', 'name' => 'Auto - Cluster'}
|
618
632
|
end
|
619
|
-
if service_plan['datastores']
|
633
|
+
if service_plan['datastores'] && service_plan['datastores']['datastores']
|
634
|
+
if service_plan['datastores']['datastores'].count > 0 && !service_plan['autoOptions'].find {|it| it['id'] == 'auto'}
|
635
|
+
service_plan['autoOptions'] << {'id' => 'auto', 'name' => 'Auto - Datastore'}
|
636
|
+
end
|
637
|
+
else
|
620
638
|
service_plan['autoOptions'] << {'id' => 'auto', 'name' => 'Auto - Datastore'}
|
621
639
|
end
|
622
640
|
end
|
@@ -767,6 +785,8 @@ module Morpheus::Cli::ProvisioningHelper
|
|
767
785
|
plan_info['datastores'].each do |k, v|
|
768
786
|
v.each do |opt|
|
769
787
|
if !opt.nil?
|
788
|
+
k = 'datastores' if k == 'store'
|
789
|
+
k = 'clusters' if k == 'cluster'
|
770
790
|
datastore_options << {'name' => "#{k}: #{opt['name']}", 'value' => opt['id']}
|
771
791
|
end
|
772
792
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'term/ansicolor'
|
2
2
|
require 'readline'
|
3
|
+
require 'csv'
|
3
4
|
module Morpheus
|
4
5
|
module Cli
|
5
6
|
module OptionTypes
|
@@ -106,9 +107,21 @@ module Morpheus
|
|
106
107
|
input_value = ['select', 'multiSelect'].include?(option_type['type']) && option_type['fieldInput'] ? cur_namespace[option_type['fieldInput']] : nil
|
107
108
|
if option_type['type'] == 'number'
|
108
109
|
value = value.to_s.include?('.') ? value.to_f : value.to_i
|
109
|
-
|
110
|
-
|
110
|
+
# these select prompts should just fall down through below, with the extra params no_prompt, use_value
|
111
|
+
elsif option_type['type'] == 'select'
|
111
112
|
value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, (api_params || {}).merge(results), true)
|
113
|
+
elsif option_type['type'] == 'multiSelect'
|
114
|
+
value_list = value.is_a?(String) ? value.parse_csv : [value].flatten
|
115
|
+
input_value_list = input_value.is_a?(String) ? input_value.parse_csv : [input_value].flatten
|
116
|
+
if value_list.size > 1
|
117
|
+
select_value_list = []
|
118
|
+
value_list.each_with_index do |v, i|
|
119
|
+
select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, (api_params || {}).merge(results), true)
|
120
|
+
end
|
121
|
+
value = select_value_list
|
122
|
+
else
|
123
|
+
value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, (api_params || {}).merge(results), true)
|
124
|
+
end
|
112
125
|
end
|
113
126
|
if options[:always_prompt] != true
|
114
127
|
value_found = true
|
@@ -125,7 +138,7 @@ module Morpheus
|
|
125
138
|
no_prompt = no_prompt || options[:no_prompt]
|
126
139
|
if no_prompt
|
127
140
|
if !value_found
|
128
|
-
if option_type['defaultValue'] != nil && option_type['type']
|
141
|
+
if option_type['defaultValue'] != nil && !['select', 'multiSelect'].include?(option_type['type'])
|
129
142
|
value = option_type['defaultValue']
|
130
143
|
value_found = true
|
131
144
|
end
|
@@ -0,0 +1,506 @@
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
2
|
+
|
3
|
+
class Morpheus::Cli::ProvisioningLicensesCommand
|
4
|
+
include Morpheus::Cli::CliCommand
|
5
|
+
set_command_name :'provisioning-licenses'
|
6
|
+
register_subcommands :list, :get, :add, :update, :remove, :reservations, :'list-types'
|
7
|
+
|
8
|
+
set_command_hidden
|
9
|
+
|
10
|
+
def connect(opts)
|
11
|
+
@api_client = establish_remote_appliance_connection(opts)
|
12
|
+
@provisioning_licenses_interface = @api_client.provisioning_licenses
|
13
|
+
@provisioning_license_types_interface = @api_client.provisioning_license_types
|
14
|
+
@virtual_images_interface = @api_client.virtual_images
|
15
|
+
@options_interface = @api_client.options
|
16
|
+
end
|
17
|
+
|
18
|
+
def handle(args)
|
19
|
+
handle_subcommand(args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def list(args)
|
23
|
+
options = {}
|
24
|
+
params = {}
|
25
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
26
|
+
opts.banner = subcommand_usage()
|
27
|
+
build_standard_list_options(opts, options)
|
28
|
+
opts.footer = "List licenses."
|
29
|
+
end
|
30
|
+
optparse.parse!(args)
|
31
|
+
if args.count != 0
|
32
|
+
raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
|
33
|
+
end
|
34
|
+
connect(options)
|
35
|
+
begin
|
36
|
+
params.merge!(parse_list_options(options))
|
37
|
+
@provisioning_licenses_interface.setopts(options)
|
38
|
+
if options[:dry_run]
|
39
|
+
print_dry_run @provisioning_licenses_interface.dry.list(params)
|
40
|
+
return 0
|
41
|
+
end
|
42
|
+
json_response = @provisioning_licenses_interface.list(params)
|
43
|
+
render_result = render_with_format(json_response, options, 'licenses')
|
44
|
+
return 0 if render_result
|
45
|
+
licenses = json_response['licenses']
|
46
|
+
|
47
|
+
title = "Morpheus Licenses"
|
48
|
+
subtitles = []
|
49
|
+
subtitles += parse_list_subtitles(options)
|
50
|
+
print_h1 title, subtitles
|
51
|
+
if licenses.empty?
|
52
|
+
print cyan,"No licenses found.",reset,"\n"
|
53
|
+
else
|
54
|
+
columns = [
|
55
|
+
{"ID" => lambda {|license| license['id'] } },
|
56
|
+
{"NAME" => lambda {|license| license['name'] } },
|
57
|
+
{"LICENSE TYPE" => lambda {|license| license['licenseType']['name'] rescue license['licenseType'] } },
|
58
|
+
{"COPIES" => lambda {|license|
|
59
|
+
"#{license['reservationCount']}/#{license['copies']}"
|
60
|
+
} },
|
61
|
+
{"TENANTS" => lambda {|it| it['tenants'] ? it['tenants'].collect {|acnt| acnt['name']}.join(', ') : '' } },
|
62
|
+
{"VIRTUAL IMAGES" => lambda {|it| it['virtualImages'] ? it['virtualImages'].collect {|v| v['name']}.join(', ') : '' } },
|
63
|
+
]
|
64
|
+
if options[:include_fields]
|
65
|
+
columns = options[:include_fields]
|
66
|
+
end
|
67
|
+
print as_pretty_table(licenses, columns, options)
|
68
|
+
print_results_pagination(json_response)
|
69
|
+
end
|
70
|
+
print reset,"\n"
|
71
|
+
|
72
|
+
return 0
|
73
|
+
rescue RestClient::Exception => e
|
74
|
+
print_rest_exception(e, options)
|
75
|
+
exit 1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def get(args)
|
80
|
+
options = {}
|
81
|
+
params = {}
|
82
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
83
|
+
opts.banner = subcommand_usage("[license]")
|
84
|
+
build_standard_get_options(opts, options)
|
85
|
+
opts.footer = "Get details about a license.\n[license] is required. License ID or name"
|
86
|
+
end
|
87
|
+
optparse.parse!(args)
|
88
|
+
|
89
|
+
if args.count < 1
|
90
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(', ')}\n#{optparse}"
|
91
|
+
end
|
92
|
+
|
93
|
+
connect(options)
|
94
|
+
|
95
|
+
begin
|
96
|
+
@provisioning_licenses_interface.setopts(options)
|
97
|
+
if options[:dry_run]
|
98
|
+
if args[0].to_s =~ /\A\d{1,}\Z/
|
99
|
+
print_dry_run @provisioning_licenses_interface.dry.get(args[0], params)
|
100
|
+
else
|
101
|
+
print_dry_run @provisioning_licenses_interface.dry.list({name: args[0].to_s})
|
102
|
+
end
|
103
|
+
return 0
|
104
|
+
end
|
105
|
+
license = find_license_by_name_or_id(args[0])
|
106
|
+
return 1 if license.nil?
|
107
|
+
# skip reload if already fetched via get(id)
|
108
|
+
json_response = {'license' => license}
|
109
|
+
if args[0].to_s != license['id'].to_s
|
110
|
+
json_response = @provisioning_licenses_interface.get(license['id'], params)
|
111
|
+
license = json_response['license']
|
112
|
+
end
|
113
|
+
render_result = render_with_format(json_response, options, 'license')
|
114
|
+
return 0 if render_result
|
115
|
+
|
116
|
+
|
117
|
+
print_h1 "License Details"
|
118
|
+
print cyan
|
119
|
+
columns = [
|
120
|
+
{"ID" => lambda {|license| license['id'] } },
|
121
|
+
{"Name" => lambda {|license| license['name'] } },
|
122
|
+
{"License Type" => lambda {|license| license['licenseType']['name'] rescue license['licenseType'] } },
|
123
|
+
{"License Key" => lambda {|license| license['licenseKey'] } },
|
124
|
+
{"Copies" => lambda {|license|
|
125
|
+
"#{license['reservationCount']}/#{license['copies']}"
|
126
|
+
} },
|
127
|
+
{"Tenants" => lambda {|it| it['tenants'] ? it['tenants'].collect {|acnt| acnt['name']}.join(', ') : '' } },
|
128
|
+
{"Virtual Images" => lambda {|it| it['virtualImages'] ? it['virtualImages'].collect {|v| v['name']}.join(', ') : '' } },
|
129
|
+
]
|
130
|
+
print_description_list(columns, license, options)
|
131
|
+
print reset,"\n"
|
132
|
+
return 0
|
133
|
+
rescue RestClient::Exception => e
|
134
|
+
print_rest_exception(e, options)
|
135
|
+
return 1
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def add(args)
|
140
|
+
options = {}
|
141
|
+
params = {}
|
142
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
143
|
+
opts.banner = subcommand_usage("[name] [options]")
|
144
|
+
opts.on( '-t', '--type TYPE', "License Type Code eg. win" ) do |val|
|
145
|
+
options[:options]['licenseType'] ||= val
|
146
|
+
end
|
147
|
+
opts.add_hidden_option('--licenseType')
|
148
|
+
build_option_type_options(opts, options, add_license_option_types)
|
149
|
+
build_standard_add_options(opts, options)
|
150
|
+
opts.footer = "Create license."
|
151
|
+
end
|
152
|
+
optparse.parse!(args)
|
153
|
+
if args.count > 1
|
154
|
+
raise_command_error "wrong number of arguments, expected 0-1 and got (#{args.count}) #{args}\n#{optparse}"
|
155
|
+
end
|
156
|
+
if args[0]
|
157
|
+
options[:options]['name'] ||= args[0]
|
158
|
+
end
|
159
|
+
connect(options)
|
160
|
+
begin
|
161
|
+
# construct payload
|
162
|
+
passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
|
163
|
+
payload = nil
|
164
|
+
if options[:payload]
|
165
|
+
payload = options[:payload]
|
166
|
+
payload.deep_merge!({'license' => passed_options}) unless passed_options.empty?
|
167
|
+
else
|
168
|
+
payload = {
|
169
|
+
'license' => {
|
170
|
+
}
|
171
|
+
}
|
172
|
+
# allow arbitrary -O options
|
173
|
+
payload.deep_merge!({'license' => passed_options}) unless passed_options.empty?
|
174
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(add_license_option_types, options[:options], @api_client)
|
175
|
+
params.deep_merge!(v_prompt)
|
176
|
+
payload.deep_merge!({'license' => params}) unless params.empty?
|
177
|
+
end
|
178
|
+
|
179
|
+
@provisioning_licenses_interface.setopts(options)
|
180
|
+
if options[:dry_run]
|
181
|
+
print_dry_run @provisioning_licenses_interface.dry.create(payload)
|
182
|
+
return
|
183
|
+
end
|
184
|
+
json_response = @provisioning_licenses_interface.create(payload)
|
185
|
+
if options[:json]
|
186
|
+
print JSON.pretty_generate(json_response)
|
187
|
+
print "\n"
|
188
|
+
else
|
189
|
+
display_name = json_response['license'] ? json_response['license']['name'] : ''
|
190
|
+
print_green_success "License #{display_name} added"
|
191
|
+
get([json_response['license']['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
|
192
|
+
end
|
193
|
+
return 0
|
194
|
+
rescue RestClient::Exception => e
|
195
|
+
print_rest_exception(e, options)
|
196
|
+
exit 1
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def update(args)
|
201
|
+
options = {}
|
202
|
+
params = {}
|
203
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
204
|
+
opts.banner = subcommand_usage("[license] [options]")
|
205
|
+
build_option_type_options(opts, options, update_license_option_types)
|
206
|
+
build_standard_update_options(opts, options)
|
207
|
+
opts.footer = "Update license.\n[license] is required. License ID or name"
|
208
|
+
end
|
209
|
+
optparse.parse!(args)
|
210
|
+
|
211
|
+
if args.count != 1
|
212
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
|
213
|
+
end
|
214
|
+
|
215
|
+
connect(options)
|
216
|
+
begin
|
217
|
+
|
218
|
+
license = find_license_by_name_or_id(args[0])
|
219
|
+
return 1 if license.nil?
|
220
|
+
|
221
|
+
# construct payload
|
222
|
+
passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
|
223
|
+
payload = nil
|
224
|
+
if options[:payload]
|
225
|
+
payload = options[:payload]
|
226
|
+
payload.deep_merge!({'license' => passed_options}) unless passed_options.empty?
|
227
|
+
else
|
228
|
+
payload = {
|
229
|
+
'license' => {
|
230
|
+
}
|
231
|
+
}
|
232
|
+
# allow arbitrary -O options
|
233
|
+
# virtual_images = passed_options.delete('virtualImages')
|
234
|
+
# tenants = passed_options.delete('tenants')
|
235
|
+
payload.deep_merge!({'license' => passed_options}) unless passed_options.empty?
|
236
|
+
# prompt for options
|
237
|
+
#params = Morpheus::Cli::OptionTypes.prompt(update_license_option_types, options[:options], @api_client, options[:params])
|
238
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(update_license_option_types, options[:options].merge(:no_prompt => true), @api_client)
|
239
|
+
params.deep_merge!(v_prompt)
|
240
|
+
|
241
|
+
# if !virtual_images.empty?
|
242
|
+
# params['virtualImages'] = virtual_images # split(",") and lookup ?
|
243
|
+
# end
|
244
|
+
|
245
|
+
payload.deep_merge!({'license' => params}) unless params.empty?
|
246
|
+
|
247
|
+
if payload.empty? || payload['license'].empty?
|
248
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
@provisioning_licenses_interface.setopts(options)
|
252
|
+
if options[:dry_run]
|
253
|
+
print_dry_run @provisioning_licenses_interface.dry.update(license['id'], payload)
|
254
|
+
return
|
255
|
+
end
|
256
|
+
json_response = @provisioning_licenses_interface.update(license['id'], payload)
|
257
|
+
if options[:json]
|
258
|
+
print JSON.pretty_generate(json_response)
|
259
|
+
print "\n"
|
260
|
+
else
|
261
|
+
display_name = json_response['license'] ? json_response['license']['name'] : ''
|
262
|
+
print_green_success "License #{display_name} updated"
|
263
|
+
get([json_response['license']['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
|
264
|
+
end
|
265
|
+
return 0
|
266
|
+
rescue RestClient::Exception => e
|
267
|
+
print_rest_exception(e, options)
|
268
|
+
exit 1
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def remove(args)
|
273
|
+
options = {}
|
274
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
275
|
+
opts.banner = subcommand_usage("[name]")
|
276
|
+
build_standard_remove_options(opts, options)
|
277
|
+
opts.footer = "Delete license.\n[license] is required. License ID or name"
|
278
|
+
end
|
279
|
+
optparse.parse!(args)
|
280
|
+
|
281
|
+
if args.count != 1
|
282
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
|
283
|
+
end
|
284
|
+
|
285
|
+
connect(options)
|
286
|
+
begin
|
287
|
+
license = find_license_by_name_or_id(args[0])
|
288
|
+
return 1 if license.nil?
|
289
|
+
|
290
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the license #{license['name']}?")
|
291
|
+
return 9, "aborted command"
|
292
|
+
end
|
293
|
+
@provisioning_licenses_interface.setopts(options)
|
294
|
+
if options[:dry_run]
|
295
|
+
print_dry_run @provisioning_licenses_interface.dry.destroy(license['id'])
|
296
|
+
return
|
297
|
+
end
|
298
|
+
json_response = @provisioning_licenses_interface.destroy(license['id'])
|
299
|
+
if options[:json]
|
300
|
+
print JSON.pretty_generate(json_response)
|
301
|
+
print "\n"
|
302
|
+
else
|
303
|
+
print_green_success "License #{license['name']} removed"
|
304
|
+
# list([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
305
|
+
end
|
306
|
+
return 0
|
307
|
+
rescue RestClient::Exception => e
|
308
|
+
print_rest_exception(e, options)
|
309
|
+
exit 1
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def reservations(args)
|
314
|
+
params = {}
|
315
|
+
options = {}
|
316
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
317
|
+
opts.banner = subcommand_usage("[name]")
|
318
|
+
build_standard_list_options(opts, options)
|
319
|
+
opts.footer = "List reservations for a license.\n[license] is required. License ID or name"
|
320
|
+
end
|
321
|
+
optparse.parse!(args)
|
322
|
+
if args.count != 1
|
323
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
|
324
|
+
end
|
325
|
+
connect(options)
|
326
|
+
begin
|
327
|
+
license = find_license_by_name_or_id(args[0])
|
328
|
+
return 1 if license.nil?
|
329
|
+
params.merge!(parse_list_options(options))
|
330
|
+
@provisioning_licenses_interface.setopts(options)
|
331
|
+
if options[:dry_run]
|
332
|
+
print_dry_run @provisioning_licenses_interface.dry.reservations(license['id'], params)
|
333
|
+
return 0
|
334
|
+
end
|
335
|
+
json_response = @provisioning_licenses_interface.reservations(license['id'], params)
|
336
|
+
render_result = render_with_format(json_response, options, 'reservations')
|
337
|
+
return 0 if render_result
|
338
|
+
reservations = json_response['reservations']
|
339
|
+
|
340
|
+
title = "License Reservations: [#{license['id']}] #{license['name']}"
|
341
|
+
subtitles = []
|
342
|
+
subtitles += parse_list_subtitles(options)
|
343
|
+
print_h1 title, subtitles
|
344
|
+
if reservations.empty?
|
345
|
+
print cyan,"No reservations found.",reset,"\n"
|
346
|
+
else
|
347
|
+
columns = [
|
348
|
+
#{"ID" => lambda {|it| it['id'] } },
|
349
|
+
{"RESOURCE ID" => lambda {|it| it['resourceId'] } },
|
350
|
+
{"RESOURCE TYPE" => lambda {|it| it['resourceType'] } },
|
351
|
+
]
|
352
|
+
if options[:include_fields]
|
353
|
+
columns = options[:include_fields]
|
354
|
+
end
|
355
|
+
print as_pretty_table(reservations, columns, options)
|
356
|
+
print_results_pagination(json_response)
|
357
|
+
end
|
358
|
+
print reset,"\n"
|
359
|
+
|
360
|
+
return 0
|
361
|
+
rescue RestClient::Exception => e
|
362
|
+
print_rest_exception(e, options)
|
363
|
+
exit 1
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def list_types(args)
|
368
|
+
options = {}
|
369
|
+
params = {}
|
370
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
371
|
+
opts.banner = subcommand_usage()
|
372
|
+
build_standard_list_options(opts, options)
|
373
|
+
opts.footer = "List license types."
|
374
|
+
end
|
375
|
+
optparse.parse!(args)
|
376
|
+
if args.count != 0
|
377
|
+
raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
|
378
|
+
end
|
379
|
+
connect(options)
|
380
|
+
begin
|
381
|
+
params.merge!(parse_list_options(options))
|
382
|
+
@provisioning_license_types_interface.setopts(options)
|
383
|
+
if options[:dry_run]
|
384
|
+
print_dry_run @provisioning_license_types_interface.dry.list(params)
|
385
|
+
return 0
|
386
|
+
end
|
387
|
+
json_response = @provisioning_license_types_interface.list(params)
|
388
|
+
render_result = render_with_format(json_response, options, 'licenseTypes')
|
389
|
+
return 0 if render_result
|
390
|
+
license_types = json_response['licenseTypes']
|
391
|
+
|
392
|
+
title = "Morpheus License Types"
|
393
|
+
subtitles = []
|
394
|
+
subtitles += parse_list_subtitles(options)
|
395
|
+
print_h1 title, subtitles
|
396
|
+
if license_types.empty?
|
397
|
+
print cyan,"No license types found.",reset,"\n"
|
398
|
+
else
|
399
|
+
columns = [
|
400
|
+
{"ID" => lambda {|it| it['id'] } },
|
401
|
+
{"NAME" => lambda {|it| it['name'] } },
|
402
|
+
{"CODE" => lambda {|it| it['code'] } },
|
403
|
+
]
|
404
|
+
if options[:include_fields]
|
405
|
+
columns = options[:include_fields]
|
406
|
+
end
|
407
|
+
print as_pretty_table(license_types, columns, options)
|
408
|
+
print_results_pagination(json_response)
|
409
|
+
end
|
410
|
+
print reset,"\n"
|
411
|
+
|
412
|
+
return 0
|
413
|
+
rescue RestClient::Exception => e
|
414
|
+
print_rest_exception(e, options)
|
415
|
+
exit 1
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
private
|
420
|
+
|
421
|
+
def find_license_by_name_or_id(val)
|
422
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
423
|
+
return find_license_by_id(val)
|
424
|
+
else
|
425
|
+
return find_license_by_name(val)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
def find_license_by_id(id)
|
430
|
+
raise "#{self.class} has not defined @provisioning_licenses_interface" if @provisioning_licenses_interface.nil?
|
431
|
+
begin
|
432
|
+
json_response = @provisioning_licenses_interface.get(id)
|
433
|
+
return json_response['license']
|
434
|
+
rescue RestClient::Exception => e
|
435
|
+
if e.response && e.response.code == 404
|
436
|
+
print_red_alert "License not found by id #{id}"
|
437
|
+
else
|
438
|
+
raise e
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def find_license_by_name(name)
|
444
|
+
raise "#{self.class} has not defined @provisioning_licenses_interface" if @provisioning_licenses_interface.nil?
|
445
|
+
licenses = @provisioning_licenses_interface.list({name: name.to_s})['licenses']
|
446
|
+
if licenses.empty?
|
447
|
+
print_red_alert "License not found by name #{name}"
|
448
|
+
return nil
|
449
|
+
elsif licenses.size > 1
|
450
|
+
print_red_alert "#{licenses.size} Licenses found by name #{name}"
|
451
|
+
print as_pretty_table(licenses, [:id,:name], {color:red})
|
452
|
+
print reset,"\n"
|
453
|
+
return nil
|
454
|
+
else
|
455
|
+
return licenses[0]
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
# def get_license_types_dropdown()
|
460
|
+
# [{"name" => "Windows", "value" => "win"}]
|
461
|
+
# end
|
462
|
+
|
463
|
+
def get_license_types_dropdown()
|
464
|
+
@provisioning_license_types_interface.list({max:10000})['licenseTypes'].collect { |it|
|
465
|
+
{"name" => it["name"], "value" => it["code"]}
|
466
|
+
}
|
467
|
+
end
|
468
|
+
|
469
|
+
def get_virtual_images_dropdown()
|
470
|
+
@virtual_images_interface.list({max:10000})['virtualImages'].collect { |it|
|
471
|
+
{"name" => it["name"], "value" => it["id"]}
|
472
|
+
}
|
473
|
+
end
|
474
|
+
|
475
|
+
def add_license_option_types
|
476
|
+
[
|
477
|
+
{'fieldName' => 'licenseType', 'fieldLabel' => 'License Type', 'type' => 'select', 'optionSource' => lambda {
|
478
|
+
# @options_interface.options_for_source("licenseTypes", {})['data']
|
479
|
+
get_license_types_dropdown()
|
480
|
+
}, 'required' => true, 'displayOrder' => 1},
|
481
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 2},
|
482
|
+
{'fieldName' => 'licenseKey', 'fieldLabel' => 'License Key', 'type' => 'text', 'required' => true, 'displayOrder' => 3},
|
483
|
+
{'fieldName' => 'orgName', 'fieldLabel' => 'Org Name', 'type' => 'text', 'description' => "The Organization Name (if applicable) related to the license key", 'displayOrder' => 4},
|
484
|
+
{'fieldName' => 'fullName', 'fieldLabel' => 'Full Name', 'type' => 'text', 'description' => "The Full Name (if applicable) related to the license key", 'displayOrder' => 5},
|
485
|
+
{'fieldName' => 'licenseVersion', 'fieldLabel' => 'Version', 'type' => 'text', 'displayOrder' => 6},
|
486
|
+
{'fieldName' => 'copies', 'fieldLabel' => 'Copies', 'type' => 'number', 'required' => true, 'defaultValue' => 1, 'displayOrder' => 7},
|
487
|
+
{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 8},
|
488
|
+
{'fieldName' => 'virtualImages', 'fieldLabel' => 'Virtual Images', 'type' => 'multiSelect', 'optionSource' => lambda {
|
489
|
+
# @options_interface.options_for_source("virtualImages", {})['data']
|
490
|
+
get_virtual_images_dropdown()
|
491
|
+
}, 'displayOrder' => 9},
|
492
|
+
{'fieldName' => 'tenants', 'fieldLabel' => 'Tenants', 'type' => 'multiSelect', 'optionSource' => lambda {
|
493
|
+
@options_interface.options_for_source("allTenants", {})['data']
|
494
|
+
}, 'displayOrder' => 10},
|
495
|
+
]
|
496
|
+
end
|
497
|
+
|
498
|
+
def update_license_option_types
|
499
|
+
list = add_license_option_types()
|
500
|
+
list = list.reject {|it| ["licenseType", "licenseKey", "orgName", "fullName"].include? it['fieldName'] }
|
501
|
+
list.each {|it| it.delete('required') }
|
502
|
+
list.each {|it| it.delete('defaultValue') }
|
503
|
+
list
|
504
|
+
end
|
505
|
+
|
506
|
+
end
|
@@ -483,9 +483,6 @@ class Morpheus::Cli::ServicePlanCommand
|
|
483
483
|
opts.on('--active [on|off]', String, "Can be used to enable / disable the plan. Default is on") do |val|
|
484
484
|
params['active'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
|
485
485
|
end
|
486
|
-
opts.on('--editable [on|off]', String, "Can be used to enable / disable the editability of the service plan. Default is on") do |val|
|
487
|
-
params['editable'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
|
488
|
-
end
|
489
486
|
opts.on('--storage [AMOUNT]', String, "Storage size is required. Assumes GB unless optional modifier specified, ex: 512MB" ) do |val|
|
490
487
|
bytes = parse_bytes_param(val, '--storage', 'GB')
|
491
488
|
params['maxStorage'] = bytes[:bytes]
|
data/lib/morpheus/cli/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: morpheus-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.2.
|
4
|
+
version: 4.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Estes
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2020-03-
|
14
|
+
date: 2020-03-14 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -194,6 +194,7 @@ files:
|
|
194
194
|
- lib/morpheus/api/custom_instance_types_interface.rb
|
195
195
|
- lib/morpheus/api/cypher_interface.rb
|
196
196
|
- lib/morpheus/api/dashboard_interface.rb
|
197
|
+
- lib/morpheus/api/datastores_interface.rb
|
197
198
|
- lib/morpheus/api/deploy_interface.rb
|
198
199
|
- lib/morpheus/api/deployments_interface.rb
|
199
200
|
- lib/morpheus/api/environments_interface.rb
|
@@ -210,6 +211,7 @@ files:
|
|
210
211
|
- lib/morpheus/api/instance_types_interface.rb
|
211
212
|
- lib/morpheus/api/instances_interface.rb
|
212
213
|
- lib/morpheus/api/integrations_interface.rb
|
214
|
+
- lib/morpheus/api/invoices_interface.rb
|
213
215
|
- lib/morpheus/api/jobs_interface.rb
|
214
216
|
- lib/morpheus/api/key_pairs_interface.rb
|
215
217
|
- lib/morpheus/api/library_cluster_layouts_interface.rb
|
@@ -255,6 +257,8 @@ files:
|
|
255
257
|
- lib/morpheus/api/prices_interface.rb
|
256
258
|
- lib/morpheus/api/processes_interface.rb
|
257
259
|
- lib/morpheus/api/provision_types_interface.rb
|
260
|
+
- lib/morpheus/api/provisioning_license_types_interface.rb
|
261
|
+
- lib/morpheus/api/provisioning_licenses_interface.rb
|
258
262
|
- lib/morpheus/api/provisioning_settings_interface.rb
|
259
263
|
- lib/morpheus/api/reports_interface.rb
|
260
264
|
- lib/morpheus/api/roles_interface.rb
|
@@ -340,6 +344,7 @@ files:
|
|
340
344
|
- lib/morpheus/cli/instance_types.rb
|
341
345
|
- lib/morpheus/cli/instances.rb
|
342
346
|
- lib/morpheus/cli/integrations_command.rb
|
347
|
+
- lib/morpheus/cli/invoices_command.rb
|
343
348
|
- lib/morpheus/cli/jobs_command.rb
|
344
349
|
- lib/morpheus/cli/key_pairs.rb
|
345
350
|
- lib/morpheus/cli/library.rb
|
@@ -391,6 +396,7 @@ files:
|
|
391
396
|
- lib/morpheus/cli/price_sets_command.rb
|
392
397
|
- lib/morpheus/cli/prices_command.rb
|
393
398
|
- lib/morpheus/cli/processes_command.rb
|
399
|
+
- lib/morpheus/cli/provisioning_licenses_command.rb
|
394
400
|
- lib/morpheus/cli/provisioning_settings_command.rb
|
395
401
|
- lib/morpheus/cli/recent_activity_command.rb
|
396
402
|
- lib/morpheus/cli/remote.rb
|