morpheus-cli 7.0.4.3 → 7.0.6
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/servers_interface.rb +21 -0
- data/lib/morpheus/cli/commands/clusters.rb +6 -2
- data/lib/morpheus/cli/commands/hosts.rb +228 -66
- data/lib/morpheus/cli/commands/provisioning_licenses_command.rb +0 -1
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +2 -0
- data/lib/morpheus/cli/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cbee8120b32418f3b60a0b82950f9a3fc1eb2ad2101a905aea6ba6ca4cc67eb
|
4
|
+
data.tar.gz: 98038ba726c03fa7aeb3af77efdedcb535e6f4506fb26f0ccd5cb70456dd55f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68c0c1c2c18173314f6d8fe73a184631609ad8b9a55d95bf35a2b797814ba5a65f2037b6638917c5c09640d7e91674a0356825849b04c375c4c88a35b715e5a3
|
7
|
+
data.tar.gz: 776cc9c571fac5fec546836d4ff6fd06e55c7635c7c63f8ed529e0880c96efa21c2d087fa7baac1bdf90c16b40ef4eaa847c7bc3fcf13c5092d4ebe3afcec303
|
data/Dockerfile
CHANGED
@@ -191,5 +191,26 @@ class Morpheus::ServersInterface < Morpheus::APIClient
|
|
191
191
|
execute(opts)
|
192
192
|
end
|
193
193
|
|
194
|
+
def maintenance(serverId, payload = {}, params = {})
|
195
|
+
url = "#{@base_url}/api/servers/#{serverId}/maintenance"
|
196
|
+
headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
197
|
+
opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
|
198
|
+
execute(opts)
|
199
|
+
end
|
200
|
+
|
201
|
+
def leave_maintenance(serverId, payload = {}, params = {})
|
202
|
+
url = "#{@base_url}/api/servers/#{serverId}/leave-maintenance"
|
203
|
+
headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
204
|
+
opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
|
205
|
+
execute(opts)
|
206
|
+
end
|
207
|
+
|
208
|
+
def placement(serverId, payload = {}, params = {})
|
209
|
+
url = "#{@base_url}/api/servers/#{serverId}/placement"
|
210
|
+
headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
211
|
+
opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
|
212
|
+
execute(opts)
|
213
|
+
end
|
214
|
+
|
194
215
|
|
195
216
|
end
|
@@ -626,8 +626,10 @@ class Morpheus::Cli::Clusters
|
|
626
626
|
service_plan = prompt_service_plan(api_params, options)
|
627
627
|
|
628
628
|
if service_plan
|
629
|
-
server_payload['plan'] = {'id' => service_plan['id'], 'code' => service_plan['code']
|
629
|
+
server_payload['plan'] = {'id' => service_plan['id'], 'code' => service_plan['code']}
|
630
630
|
api_params['planId'] = service_plan['id']
|
631
|
+
plan_opts = prompt_service_plan_options(service_plan, provision_type, options)
|
632
|
+
server_payload['servicePlanOptions'] = plan_opts if plan_opts && !plan_opts.empty?
|
631
633
|
end
|
632
634
|
|
633
635
|
# Multi-disk / prompt for volumes
|
@@ -1289,7 +1291,9 @@ class Morpheus::Cli::Clusters
|
|
1289
1291
|
service_plan = prompt_service_plan({zoneId: cloud_id, siteId: cluster['site']['id'], provisionTypeId: server_type['provisionType']['id'], groupTypeId: cluster_type['id'], }, options)
|
1290
1292
|
|
1291
1293
|
if service_plan
|
1292
|
-
server_payload['plan'] = {'code' => service_plan['code']
|
1294
|
+
server_payload['plan'] = {'code' => service_plan['code']}
|
1295
|
+
plan_opts = prompt_service_plan_options(service_plan, nil, options)
|
1296
|
+
server_payload['servicePlanOptions'] = plan_opts if plan_opts && !plan_opts.empty?
|
1293
1297
|
end
|
1294
1298
|
|
1295
1299
|
if resource_pool = prompt_resource_pool(cluster, cloud, service_plan, server_type['provisionType'], options)
|
@@ -11,7 +11,8 @@ class Morpheus::Cli::Hosts
|
|
11
11
|
:run_workflow, :make_managed, :upgrade_agent, :snapshots, :software, :software_sync, :update_network_label,
|
12
12
|
{:'types' => :list_types},
|
13
13
|
{:exec => :execution_request},
|
14
|
-
:wiki, :update_wiki
|
14
|
+
:wiki, :update_wiki,
|
15
|
+
:maintenance, :leave_maintenance, :placement
|
15
16
|
alias_subcommand :details, :get
|
16
17
|
set_default_subcommand :list
|
17
18
|
|
@@ -2149,6 +2150,232 @@ EOT
|
|
2149
2150
|
end
|
2150
2151
|
end
|
2151
2152
|
|
2153
|
+
def update_network_label(args)
|
2154
|
+
options = {}
|
2155
|
+
params = {}
|
2156
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2157
|
+
opts.banner = subcommand_usage("[server] [options]")
|
2158
|
+
opts.on('--network NETWORK', "Network Interface ID" ) do |val|
|
2159
|
+
options[:network] = val
|
2160
|
+
end
|
2161
|
+
opts.on('--label LABEL', "label") do |val|
|
2162
|
+
options[:label] = val
|
2163
|
+
end
|
2164
|
+
opts.footer = "Change the label of a Network Interface.\n" +
|
2165
|
+
"Editing an Interface will not apply changes to the physical hardware. The purpose is for a manual override or data correction (mostly for self managed or baremetal servers where cloud sync is not available)\n" +
|
2166
|
+
"[name or id] is required. The name or the id of the server.\n" +
|
2167
|
+
"[network] ID of the Network Interface. (optional).\n" +
|
2168
|
+
"[label] New Label name for the Network Interface (optional)"
|
2169
|
+
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
2170
|
+
end
|
2171
|
+
optparse.parse!(args)
|
2172
|
+
if args.count != 1
|
2173
|
+
puts_error "#{Morpheus::Terminal.angry_prompt}wrong number of arguments. Expected 1 and received #{args.count} #{args.inspect}\n#{optparse}"
|
2174
|
+
return 1
|
2175
|
+
end
|
2176
|
+
connect(options)
|
2177
|
+
|
2178
|
+
begin
|
2179
|
+
host = find_host_by_name_or_id(args[0])
|
2180
|
+
return 1 if host.nil?
|
2181
|
+
|
2182
|
+
network_id = options[:network]
|
2183
|
+
if network_id != nil && network_id.to_i == 0
|
2184
|
+
print_red_alert "network must be an ID/integer above 0, not a name/string value."
|
2185
|
+
network_id = nil
|
2186
|
+
end
|
2187
|
+
|
2188
|
+
|
2189
|
+
if !network_id
|
2190
|
+
available_networks = get_available_networks(host)
|
2191
|
+
network_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'network', 'fieldLabel' => 'Network', 'type' => 'select', 'selectOptions' => available_networks, 'required' => true, 'defaultValue' => available_networks[0], 'description' => "The networks available for relabeling"}], options[:options])
|
2192
|
+
network_id = network_prompt['network']
|
2193
|
+
end
|
2194
|
+
|
2195
|
+
label = options[:label]
|
2196
|
+
while label.nil? do
|
2197
|
+
label_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'label', 'fieldLabel' => 'Label', 'type' => 'text', 'required' => true}], options[:options])
|
2198
|
+
label = label_prompt['label']
|
2199
|
+
end
|
2200
|
+
payload = { "name" => label }
|
2201
|
+
if options[:dry_run]
|
2202
|
+
print_dry_run @servers_interface.dry.update_network_label(network_id, host["id"], payload)
|
2203
|
+
return
|
2204
|
+
end
|
2205
|
+
json_response = @servers_interface.update_network_label(network_id, host["id"], payload)
|
2206
|
+
if options[:json]
|
2207
|
+
puts as_json(json_response, options)
|
2208
|
+
else
|
2209
|
+
print_green_success "Updated label for host #{host['name']} network #{network_id} to #{label}"
|
2210
|
+
end
|
2211
|
+
return 0
|
2212
|
+
rescue RestClient::Exception => e
|
2213
|
+
print_rest_exception(e, options)
|
2214
|
+
exit 1
|
2215
|
+
end
|
2216
|
+
end
|
2217
|
+
|
2218
|
+
|
2219
|
+
def maintenance(args)
|
2220
|
+
options = {}
|
2221
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2222
|
+
opts.banner = subcommand_usage("[host]")
|
2223
|
+
build_standard_update_options(opts, options, [:auto_confirm])
|
2224
|
+
opts.footer = <<-EOT
|
2225
|
+
Enable maintenance mode for a host.
|
2226
|
+
[host] is required. This is the name or id of a host.
|
2227
|
+
EOT
|
2228
|
+
end
|
2229
|
+
optparse.parse!(args)
|
2230
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
2231
|
+
connect(options)
|
2232
|
+
|
2233
|
+
server = find_host_by_name_or_id(args[0])
|
2234
|
+
return 1 if server.nil?
|
2235
|
+
|
2236
|
+
# Just let the API return this error instead
|
2237
|
+
|
2238
|
+
# # load server type to determine if maintenance mode is supported
|
2239
|
+
# server_type_id = server['computeServerType']['id']
|
2240
|
+
# server_type = @server_types_interface.get(server_type_id)['serverType']
|
2241
|
+
# if !server_type['hasMaintenanceMode']
|
2242
|
+
# raise_command_error "Server type does not support maintenance mode"
|
2243
|
+
# end
|
2244
|
+
|
2245
|
+
payload = {}
|
2246
|
+
if options[:payload]
|
2247
|
+
payload = options[:payload]
|
2248
|
+
payload.deep_merge!(parse_passed_options(options))
|
2249
|
+
else
|
2250
|
+
payload.deep_merge!(parse_passed_options(options))
|
2251
|
+
end
|
2252
|
+
params = {}
|
2253
|
+
params.merge!(parse_query_options(options))
|
2254
|
+
|
2255
|
+
confirm!("Are you sure you would like to enable maintenance mode on host '#{server['name']}'?", options)
|
2256
|
+
|
2257
|
+
@servers_interface.setopts(options)
|
2258
|
+
if options[:dry_run]
|
2259
|
+
print_dry_run @servers_interface.dry.maintenance(server['id'], payload, params)
|
2260
|
+
return
|
2261
|
+
end
|
2262
|
+
json_response = @servers_interface.maintenance(server['id'], payload, params)
|
2263
|
+
render_response(json_response, options) do
|
2264
|
+
print_green_success "Maintenance mode enabled for host #{server['name']}"
|
2265
|
+
#get([server['id']])
|
2266
|
+
end
|
2267
|
+
return 0, nil
|
2268
|
+
end
|
2269
|
+
|
2270
|
+
def leave_maintenance(args)
|
2271
|
+
options = {}
|
2272
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2273
|
+
opts.banner = subcommand_usage("[host]")
|
2274
|
+
build_standard_update_options(opts, options, [:auto_confirm])
|
2275
|
+
opts.footer = <<-EOT
|
2276
|
+
Disable maintenance mode for a host.
|
2277
|
+
[host] is required. This is the name or id of a host.
|
2278
|
+
EOT
|
2279
|
+
end
|
2280
|
+
optparse.parse!(args)
|
2281
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
2282
|
+
connect(options)
|
2283
|
+
|
2284
|
+
server = find_host_by_name_or_id(args[0])
|
2285
|
+
return 1 if server.nil?
|
2286
|
+
|
2287
|
+
# Just let the API return this error instead
|
2288
|
+
|
2289
|
+
# # load server type to determine if maintenance mode is supported
|
2290
|
+
# server_type_id = server['computeServerType']['id']
|
2291
|
+
# server_type = @server_types_interface.get(server_type_id)['serverType']
|
2292
|
+
# if !server_type['hasMaintenanceMode']
|
2293
|
+
# raise_command_error "Server type does not support maintenance mode"
|
2294
|
+
# end
|
2295
|
+
|
2296
|
+
payload = {}
|
2297
|
+
if options[:payload]
|
2298
|
+
payload = options[:payload]
|
2299
|
+
payload.deep_merge!(parse_passed_options(options))
|
2300
|
+
else
|
2301
|
+
payload.deep_merge!(parse_passed_options(options))
|
2302
|
+
end
|
2303
|
+
params = {}
|
2304
|
+
params.merge!(parse_query_options(options))
|
2305
|
+
|
2306
|
+
confirm!("Are you sure you would like to leave maintenance mode on host '#{server['name']}'?", options)
|
2307
|
+
|
2308
|
+
@servers_interface.setopts(options)
|
2309
|
+
if options[:dry_run]
|
2310
|
+
print_dry_run @servers_interface.dry.leave_maintenance(server['id'], payload, params)
|
2311
|
+
return
|
2312
|
+
end
|
2313
|
+
json_response = @servers_interface.leave_maintenance(server['id'], payload, params)
|
2314
|
+
render_response(json_response, options) do
|
2315
|
+
print_green_success "Maintenance mode enabled for host #{server['name']}"
|
2316
|
+
#get([server['id']])
|
2317
|
+
end
|
2318
|
+
return 0, nil
|
2319
|
+
end
|
2320
|
+
|
2321
|
+
def placement(args)
|
2322
|
+
options = {}
|
2323
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2324
|
+
opts.banner = subcommand_usage("[host]")
|
2325
|
+
build_standard_update_options(opts, options, [:auto_confirm])
|
2326
|
+
opts.footer = <<-EOT
|
2327
|
+
Update placement for a host.
|
2328
|
+
[host] is required. This is the name or id of a host.
|
2329
|
+
EOT
|
2330
|
+
end
|
2331
|
+
optparse.parse!(args)
|
2332
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
2333
|
+
connect(options)
|
2334
|
+
|
2335
|
+
server = find_host_by_name_or_id(args[0])
|
2336
|
+
return 1 if server.nil?
|
2337
|
+
payload = {}
|
2338
|
+
if options[:payload]
|
2339
|
+
payload = options[:payload]
|
2340
|
+
payload.deep_merge!({'server' => parse_passed_options(options)})
|
2341
|
+
else
|
2342
|
+
payload.deep_merge!({'server' => parse_passed_options(options)})
|
2343
|
+
# prompt
|
2344
|
+
# Host (preferredParentServer.id)
|
2345
|
+
if payload['server']['host']
|
2346
|
+
options[:options] = payload['server'].remove('host')
|
2347
|
+
end
|
2348
|
+
default_host = (server['preferredParentServer'] ? server['preferredParentServer']['id'] : (server['parentServer'] ? server['parentServer']['id'] : nil))
|
2349
|
+
host = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'host', 'type' => 'select', 'fieldLabel' => 'Host', 'optionSource' => 'parentServers', 'required' => false, 'description' => 'Choose the preferred parent host for this virtual machine to be placed on.', 'defaultValue' => default_host}],options[:options],@api_client,{'serverId' => server['id']})['host']
|
2350
|
+
if !host.to_s.empty?
|
2351
|
+
payload['server']['preferredParentServer'] = {'id' => host}
|
2352
|
+
end
|
2353
|
+
|
2354
|
+
|
2355
|
+
# Placement Strategy (placementStrategy)
|
2356
|
+
placement_strategy = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'placementStrategy', 'type' => 'select', 'fieldLabel' => 'Placement Strategy', 'optionSource' => 'placementStrategies', 'required' => false, 'description' => 'Choose the placement strategy for this virtual machine.', 'defaultValue' => server['placementStrategy']}],options[:options],@api_client,{'serverId' => server['id']})['placementStrategy']
|
2357
|
+
if !placement_strategy.to_s.empty?
|
2358
|
+
payload['server']['placementStrategy'] = placement_strategy
|
2359
|
+
end
|
2360
|
+
end
|
2361
|
+
params = {}
|
2362
|
+
params.merge!(parse_query_options(options))
|
2363
|
+
|
2364
|
+
confirm!("Are you sure you would like to update placement for host '#{server['name']}'?", options)
|
2365
|
+
|
2366
|
+
@servers_interface.setopts(options)
|
2367
|
+
if options[:dry_run]
|
2368
|
+
print_dry_run @servers_interface.dry.placement(server['id'], payload, params)
|
2369
|
+
return
|
2370
|
+
end
|
2371
|
+
json_response = @servers_interface.placement(server['id'], payload, params)
|
2372
|
+
render_response(json_response, options) do
|
2373
|
+
print_green_success "Maintenance mode enabled for host #{server['name']}"
|
2374
|
+
#get([server['id']])
|
2375
|
+
end
|
2376
|
+
return 0, nil
|
2377
|
+
end
|
2378
|
+
|
2152
2379
|
private
|
2153
2380
|
|
2154
2381
|
def find_host_by_id(id)
|
@@ -2297,71 +2524,6 @@ EOT
|
|
2297
2524
|
out
|
2298
2525
|
end
|
2299
2526
|
|
2300
|
-
def update_network_label(args)
|
2301
|
-
options = {}
|
2302
|
-
params = {}
|
2303
|
-
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2304
|
-
opts.banner = subcommand_usage("[server] [options]")
|
2305
|
-
opts.on('--network NETWORK', "Network Interface ID" ) do |val|
|
2306
|
-
options[:network] = val
|
2307
|
-
end
|
2308
|
-
opts.on('--label LABEL', "label") do |val|
|
2309
|
-
options[:label] = val
|
2310
|
-
end
|
2311
|
-
opts.footer = "Change the label of a Network Interface.\n" +
|
2312
|
-
"Editing an Interface will not apply changes to the physical hardware. The purpose is for a manual override or data correction (mostly for self managed or baremetal servers where cloud sync is not available)\n" +
|
2313
|
-
"[name or id] is required. The name or the id of the server.\n" +
|
2314
|
-
"[network] ID of the Network Interface. (optional).\n" +
|
2315
|
-
"[label] New Label name for the Network Interface (optional)"
|
2316
|
-
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
2317
|
-
end
|
2318
|
-
optparse.parse!(args)
|
2319
|
-
if args.count != 1
|
2320
|
-
puts_error "#{Morpheus::Terminal.angry_prompt}wrong number of arguments. Expected 1 and received #{args.count} #{args.inspect}\n#{optparse}"
|
2321
|
-
return 1
|
2322
|
-
end
|
2323
|
-
connect(options)
|
2324
|
-
|
2325
|
-
begin
|
2326
|
-
host = find_host_by_name_or_id(args[0])
|
2327
|
-
return 1 if host.nil?
|
2328
|
-
|
2329
|
-
network_id = options[:network]
|
2330
|
-
if network_id != nil && network_id.to_i == 0
|
2331
|
-
print_red_alert "network must be an ID/integer above 0, not a name/string value."
|
2332
|
-
network_id = nil
|
2333
|
-
end
|
2334
|
-
|
2335
|
-
|
2336
|
-
if !network_id
|
2337
|
-
available_networks = get_available_networks(host)
|
2338
|
-
network_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'network', 'fieldLabel' => 'Network', 'type' => 'select', 'selectOptions' => available_networks, 'required' => true, 'defaultValue' => available_networks[0], 'description' => "The networks available for relabeling"}], options[:options])
|
2339
|
-
network_id = network_prompt['network']
|
2340
|
-
end
|
2341
|
-
|
2342
|
-
label = options[:label]
|
2343
|
-
while label.nil? do
|
2344
|
-
label_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'label', 'fieldLabel' => 'Label', 'type' => 'text', 'required' => true}], options[:options])
|
2345
|
-
label = label_prompt['label']
|
2346
|
-
end
|
2347
|
-
payload = { "name" => label }
|
2348
|
-
if options[:dry_run]
|
2349
|
-
print_dry_run @servers_interface.dry.update_network_label(network_id, host["id"], payload)
|
2350
|
-
return
|
2351
|
-
end
|
2352
|
-
json_response = @servers_interface.update_network_label(network_id, host["id"], payload)
|
2353
|
-
if options[:json]
|
2354
|
-
puts as_json(json_response, options)
|
2355
|
-
else
|
2356
|
-
print_green_success "Updated label for host #{host['name']} network #{network_id} to #{label}"
|
2357
|
-
end
|
2358
|
-
return 0
|
2359
|
-
rescue RestClient::Exception => e
|
2360
|
-
print_rest_exception(e, options)
|
2361
|
-
exit 1
|
2362
|
-
end
|
2363
|
-
end
|
2364
|
-
|
2365
2527
|
def get_available_networks(host)
|
2366
2528
|
results = @options_interface.options_for_source('availableNetworksForHost',{serverId: host['id'].to_i})
|
2367
2529
|
available_networks = results['data'].collect {|it|
|
@@ -127,7 +127,6 @@ class Morpheus::Cli::ProvisioningLicensesCommand
|
|
127
127
|
{"Copies" => lambda {|license|
|
128
128
|
"#{license['reservationCount']}/#{license['copies']}"
|
129
129
|
} },
|
130
|
-
{"Description" => lambda {|license| license['description'] } },
|
131
130
|
{"Virtual Images" => lambda {|it| it['virtualImages'] ? it['virtualImages'].collect {|v| v['name']}.join(', ') : '' } },
|
132
131
|
{"Tenants" => lambda {|it| it['tenants'] ? it['tenants'].collect {|acnt| acnt['name']}.join(', ') : '' } },
|
133
132
|
]
|
@@ -1722,6 +1722,8 @@ module Morpheus::Cli::ProvisioningHelper
|
|
1722
1722
|
|
1723
1723
|
if ip_required == false && network_interface['ipAddress'] == nil && selected_network['dhcpServer'] == true
|
1724
1724
|
network_interface['ipMode'] = 'dhcp'
|
1725
|
+
elsif network_interface['ipAddress']
|
1726
|
+
network_interface['ipMode'] = 'static'
|
1725
1727
|
end
|
1726
1728
|
|
1727
1729
|
network_interfaces << network_interface
|
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: 7.0.
|
4
|
+
version: 7.0.6
|
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: 2024-
|
14
|
+
date: 2024-09-11 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: public_suffix
|