morpheus-cli 4.2.1 → 4.2.2
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/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
|