morpheus-cli 3.4.1.10 → 3.5.1
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 +5 -5
- data/lib/morpheus/api/api_client.rb +4 -0
- data/lib/morpheus/api/cypher_interface.rb +55 -0
- data/lib/morpheus/api/storage_providers_interface.rb +113 -0
- data/lib/morpheus/cli.rb +1 -0
- data/lib/morpheus/cli/archives_command.rb +5 -6
- data/lib/morpheus/cli/cli_command.rb +10 -1
- data/lib/morpheus/cli/clouds.rb +1 -0
- data/lib/morpheus/cli/cypher_command.rb +412 -0
- data/lib/morpheus/cli/echo_command.rb +1 -1
- data/lib/morpheus/cli/library_option_lists_command.rb +109 -30
- data/lib/morpheus/cli/policies_command.rb +208 -118
- data/lib/morpheus/cli/set_prompt_command.rb +1 -1
- data/lib/morpheus/cli/shell.rb +3 -2
- data/lib/morpheus/cli/storage_providers_command.rb +788 -5
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/terminal.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dd3b5e29881772dd9ef4e80eb5226176fa814c4661464f68ffe9249e0ccda99c
|
4
|
+
data.tar.gz: cdbca169540925cfa847c48c12aad367bd4626a733e2844323d6078f5d351a44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f094793c1f8e11ffc84c9dea26e2e094881efb22d5b0d71b65ed5d206df40b16e6fc80ddbdf6e028c12184749f485113aba555c2d6fd595c2643e1d6f8547cb
|
7
|
+
data.tar.gz: 4715322bcc111e6e82fa8fcd98e60b0123ebc50afd6b22a7719cc108cf36e2c02c095456e0fa3dad2800e0fb4b65724c9bbad668eed46e966d2db2389a5dec1d
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'morpheus/api/api_client'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
class Morpheus::CypherInterface < Morpheus::APIClient
|
5
|
+
def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
|
6
|
+
@access_token = access_token
|
7
|
+
@refresh_token = refresh_token
|
8
|
+
@base_url = base_url
|
9
|
+
@expires_at = expires_at
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(id, params={})
|
13
|
+
raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
|
14
|
+
url = "#{@base_url}/api/cypher/#{id}"
|
15
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
16
|
+
opts = {method: :get, url: url, headers: headers}
|
17
|
+
execute(opts)
|
18
|
+
end
|
19
|
+
|
20
|
+
def list(params={})
|
21
|
+
url = "#{@base_url}/api/cypher"
|
22
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
23
|
+
opts = {method: :get, url: url, headers: headers}
|
24
|
+
execute(opts)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create(payload)
|
28
|
+
url = "#{@base_url}/api/cypher"
|
29
|
+
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
30
|
+
opts = {method: :post, url: url, headers: headers, payload: payload.to_json}
|
31
|
+
execute(opts)
|
32
|
+
end
|
33
|
+
|
34
|
+
def update(id, payload)
|
35
|
+
url = "#{@base_url}/api/cypher/#{id}"
|
36
|
+
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
37
|
+
opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
|
38
|
+
execute(opts)
|
39
|
+
end
|
40
|
+
|
41
|
+
def destroy(id, params={})
|
42
|
+
url = "#{@base_url}/api/cypher/#{id}"
|
43
|
+
headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
44
|
+
opts = {method: :delete, url: url, timeout: 30, headers: headers}
|
45
|
+
execute(opts)
|
46
|
+
end
|
47
|
+
|
48
|
+
def decrypt(id, params={})
|
49
|
+
url = "#{@base_url}/api/cypher/#{id}/decrypt"
|
50
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
51
|
+
opts = {method: :get, url: url, headers: headers}
|
52
|
+
execute(opts)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'morpheus/api/api_client'
|
2
|
+
require 'uri'
|
2
3
|
|
3
4
|
class Morpheus::StorageProvidersInterface < Morpheus::APIClient
|
4
5
|
def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
|
@@ -44,4 +45,116 @@ class Morpheus::StorageProvidersInterface < Morpheus::APIClient
|
|
44
45
|
execute(opts)
|
45
46
|
end
|
46
47
|
|
48
|
+
def list_files(id, file_path, params={})
|
49
|
+
if file_path.to_s.strip == "/"
|
50
|
+
file_path = ""
|
51
|
+
end
|
52
|
+
url = "#{@base_url}/api/storage/providers/#{URI.escape(id.to_s)}" + "/files/#{URI.escape(file_path)}".squeeze('/')
|
53
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
54
|
+
opts = {method: :get, url: url, headers: headers}
|
55
|
+
execute(opts)
|
56
|
+
end
|
57
|
+
|
58
|
+
# upload a file without multipart
|
59
|
+
def upload_file(id, local_file, destination, params={})
|
60
|
+
# puts "upload_file #{local_file} to destination #{destination}"
|
61
|
+
# destination should be the full filePath, but the api looks like directory?filename=
|
62
|
+
path = destination.to_s.squeeze("/")
|
63
|
+
if !path || path == "" || path == "/" || path == "."
|
64
|
+
raise "#{self.class}.upload_file() passed a bad destination: '#{destination}'"
|
65
|
+
end
|
66
|
+
if path[0].chr == "/"
|
67
|
+
path = path[1..-1]
|
68
|
+
end
|
69
|
+
path_chunks = path.split("/")
|
70
|
+
filename = path_chunks.pop
|
71
|
+
safe_dirname = path_chunks.collect {|it| URI.escape(it) }.join("/")
|
72
|
+
# filename = File.basename(destination)
|
73
|
+
# dirname = File.dirname(destination)
|
74
|
+
# if filename == "" || filename == "/"
|
75
|
+
# filename = File.basename(local_file)
|
76
|
+
# end
|
77
|
+
url = "#{@base_url}/api/storage/providers/#{URI.escape(id.to_s)}" + "/files/#{safe_dirname}".squeeze('/')
|
78
|
+
headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/octet-stream'}
|
79
|
+
headers[:params][:filename] = filename # File.basename(destination)
|
80
|
+
if !local_file.kind_of?(File)
|
81
|
+
local_file = File.new(local_file, 'rb')
|
82
|
+
end
|
83
|
+
payload = local_file
|
84
|
+
headers['Content-Length'] = local_file.size # File.size(local_file)
|
85
|
+
execute(method: :post, url: url, headers: headers, payload: payload)
|
86
|
+
end
|
87
|
+
|
88
|
+
def download_file(id, file_path, params={})
|
89
|
+
raise "#{self.class}.download_file() passed a blank id!" if id.to_s == ''
|
90
|
+
raise "#{self.class}.download_file() passed a blank file path!" if file_path.to_s == ''
|
91
|
+
escaped_file_path = file_path.split("/").collect {|it| URI.escape(it) }.join("/")
|
92
|
+
url = "#{@base_url}/api/storage/providers/#{URI.escape(id.to_s)}" + "/download-file/#{escaped_file_path}".squeeze('/')
|
93
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
94
|
+
opts = {method: :get, url: url, headers: headers}
|
95
|
+
execute(opts, false)
|
96
|
+
end
|
97
|
+
|
98
|
+
def download_file_chunked(id, file_path, outfile, params={})
|
99
|
+
raise "#{self.class}.download_file() passed a blank id!" if id.to_s == ''
|
100
|
+
raise "#{self.class}.download_file() passed a blank file path!" if file_path.to_s == ''
|
101
|
+
escaped_file_path = file_path.split("/").collect {|it| URI.escape(it) }.join("/")
|
102
|
+
url = "#{@base_url}/api/storage/providers/#{URI.escape(id.to_s)}" + "/download-file/#{escaped_file_path}".squeeze('/')
|
103
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
104
|
+
opts = {method: :get, url: url, headers: headers}
|
105
|
+
# execute(opts, false)
|
106
|
+
if Dir.exists?(outfile)
|
107
|
+
raise "outfile is invalid. It is the name of an existing directory: #{outfile}"
|
108
|
+
end
|
109
|
+
# if @verify_ssl == false
|
110
|
+
# opts[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE
|
111
|
+
# end
|
112
|
+
if @dry_run
|
113
|
+
return opts
|
114
|
+
end
|
115
|
+
http_response = nil
|
116
|
+
File.open(outfile, 'w') {|f|
|
117
|
+
block = proc { |response|
|
118
|
+
response.read_body do |chunk|
|
119
|
+
# writing to #{outfile} ..."
|
120
|
+
f.write chunk
|
121
|
+
end
|
122
|
+
}
|
123
|
+
opts[:block_response] = block
|
124
|
+
http_response = RestClient::Request.new(opts).execute
|
125
|
+
# RestClient::Request.execute(opts)
|
126
|
+
}
|
127
|
+
return http_response
|
128
|
+
end
|
129
|
+
|
130
|
+
def download_zip_chunked(id, outfile, params={})
|
131
|
+
#url = "#{@base_url}/api/storage/providers/#{URI.escape(id.to_s)}" + ".zip"
|
132
|
+
url = "#{@base_url}/api/storage/providers/#{URI.escape(id.to_s)}/download-zip"
|
133
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
134
|
+
opts = {method: :get, url: url, headers: headers}
|
135
|
+
# execute(opts, false)
|
136
|
+
if Dir.exists?(outfile)
|
137
|
+
raise "outfile is invalid. It is the name of an existing directory: #{outfile}"
|
138
|
+
end
|
139
|
+
# if @verify_ssl == false
|
140
|
+
# opts[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE
|
141
|
+
# end
|
142
|
+
if @dry_run
|
143
|
+
return opts
|
144
|
+
end
|
145
|
+
http_response = nil
|
146
|
+
File.open(outfile, 'w') {|f|
|
147
|
+
block = proc { |response|
|
148
|
+
response.read_body do |chunk|
|
149
|
+
# writing to #{outfile} ..."
|
150
|
+
f.write chunk
|
151
|
+
end
|
152
|
+
}
|
153
|
+
opts[:block_response] = block
|
154
|
+
http_response = RestClient::Request.new(opts).execute
|
155
|
+
# RestClient::Request.execute(opts)
|
156
|
+
}
|
157
|
+
return http_response
|
158
|
+
end
|
159
|
+
|
47
160
|
end
|
data/lib/morpheus/cli.rb
CHANGED
@@ -138,6 +138,7 @@ module Morpheus
|
|
138
138
|
load 'morpheus/cli/network_pool_servers_command.rb'
|
139
139
|
load 'morpheus/cli/network_domains_command.rb'
|
140
140
|
load 'morpheus/cli/network_proxies_command.rb'
|
141
|
+
load 'morpheus/cli/cypher_command.rb'
|
141
142
|
load 'morpheus/cli/image_builder_command.rb'
|
142
143
|
load 'morpheus/cli/preseed_scripts_command.rb'
|
143
144
|
load 'morpheus/cli/boot_scripts_command.rb'
|
@@ -820,7 +820,6 @@ class Morpheus::Cli::ArchivesCommand
|
|
820
820
|
groups_str = owner_str
|
821
821
|
end
|
822
822
|
end
|
823
|
-
groups_str =
|
824
823
|
file_info << truncate_string(groups_str, 15).ljust(15, " ")
|
825
824
|
# File Type
|
826
825
|
content_type = archive_file['contentType'].to_s
|
@@ -848,7 +847,7 @@ class Morpheus::Cli::ArchivesCommand
|
|
848
847
|
mtime = format_local_dt(last_updated, {format: "%b %e %Y"})
|
849
848
|
end
|
850
849
|
end
|
851
|
-
file_info << mtime
|
850
|
+
file_info << mtime.ljust(12, " ")
|
852
851
|
if params[:fullTree]
|
853
852
|
file_info << file_color + archive_file["filePath"].to_s + cyan
|
854
853
|
else
|
@@ -871,7 +870,7 @@ class Morpheus::Cli::ArchivesCommand
|
|
871
870
|
if do_one_file_per_line
|
872
871
|
print file_names.join("\n")
|
873
872
|
else
|
874
|
-
print file_names.join("
|
873
|
+
print file_names.join("\t")
|
875
874
|
end
|
876
875
|
print "\n"
|
877
876
|
end
|
@@ -1287,11 +1286,11 @@ class Morpheus::Cli::ArchivesCommand
|
|
1287
1286
|
if options[:dry_run]
|
1288
1287
|
# print_dry_run @archive_files_interface.dry.download_file_by_path(full_file_path), full_command_string
|
1289
1288
|
if use_public_url
|
1290
|
-
print_dry_run @archive_files_interface.dry.download_file_by_path_chunked(full_file_path, outfile), full_command_string
|
1291
|
-
else
|
1292
1289
|
print_dry_run @archive_files_interface.dry.download_public_file_by_path_chunked(full_file_path, outfile), full_command_string
|
1290
|
+
else
|
1291
|
+
print_dry_run @archive_files_interface.dry.download_file_by_path_chunked(full_file_path, outfile), full_command_string
|
1293
1292
|
end
|
1294
|
-
return
|
1293
|
+
return 0
|
1295
1294
|
end
|
1296
1295
|
if !options[:quiet]
|
1297
1296
|
print cyan + "Downloading archive file #{bucket_id}:#{file_path} to #{outfile} ... "
|
@@ -318,7 +318,16 @@ module Morpheus
|
|
318
318
|
k, v = k.split(":")
|
319
319
|
end
|
320
320
|
if (!k.to_s.empty?)
|
321
|
-
options[:query_filters]
|
321
|
+
if options[:query_filters].key?(k.to_s.strip)
|
322
|
+
cur_val = options[:query_filters][k.to_s.strip]
|
323
|
+
if cur_val.instance_of?(Array)
|
324
|
+
options[:query_filters][k.to_s.strip] << v.to_s.strip
|
325
|
+
else
|
326
|
+
options[:query_filters][k.to_s.strip] = [cur_val, v.to_s.strip]
|
327
|
+
end
|
328
|
+
else
|
329
|
+
options[:query_filters][k.to_s.strip] = v.to_s.strip
|
330
|
+
end
|
322
331
|
end
|
323
332
|
end
|
324
333
|
end
|
data/lib/morpheus/cli/clouds.rb
CHANGED
@@ -198,6 +198,7 @@ class Morpheus::Cli::Clouds
|
|
198
198
|
"Location" => 'location',
|
199
199
|
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
200
200
|
"Groups" => lambda {|it| it['groups'].collect {|g| g.instance_of?(Hash) ? g['name'] : g.to_s }.join(', ') },
|
201
|
+
"Enabled" => lambda {|it| format_boolean(it['enabled']) },
|
201
202
|
"Status" => lambda {|it| format_cloud_status(it) }
|
202
203
|
}
|
203
204
|
print_description_list(description_cols, cloud)
|
@@ -0,0 +1,412 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'yaml'
|
3
|
+
require 'table_print'
|
4
|
+
require 'morpheus/cli/cli_command'
|
5
|
+
|
6
|
+
class Morpheus::Cli::CypherCommand
|
7
|
+
include Morpheus::Cli::CliCommand
|
8
|
+
|
9
|
+
set_command_name :cypher
|
10
|
+
|
11
|
+
register_subcommands :list, :get, :add, :remove, :decrypt
|
12
|
+
|
13
|
+
def initialize()
|
14
|
+
# @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect(opts)
|
18
|
+
@api_client = establish_remote_appliance_connection(opts)
|
19
|
+
@cypher_interface = @api_client.cypher
|
20
|
+
end
|
21
|
+
|
22
|
+
def handle(args)
|
23
|
+
handle_subcommand(args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def list(args)
|
27
|
+
options = {}
|
28
|
+
params = {}
|
29
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
30
|
+
opts.banner = subcommand_usage()
|
31
|
+
build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :json, :dry_run, :remote])
|
32
|
+
opts.footer = "List cypher items."
|
33
|
+
end
|
34
|
+
optparse.parse!(args)
|
35
|
+
connect(options)
|
36
|
+
begin
|
37
|
+
params.merge!(parse_list_options(options))
|
38
|
+
if options[:dry_run]
|
39
|
+
print_dry_run @cypher_interface.dry.list(params)
|
40
|
+
return 0
|
41
|
+
end
|
42
|
+
json_response = @cypher_interface.list(params)
|
43
|
+
cypher_items = json_response["cyphers"]
|
44
|
+
if options[:json]
|
45
|
+
puts as_json(json_response, options, "cyphers")
|
46
|
+
return 0
|
47
|
+
elsif options[:yaml]
|
48
|
+
puts as_yaml(json_response, options, "cyphers")
|
49
|
+
return 0
|
50
|
+
elsif options[:csv]
|
51
|
+
puts records_as_csv(cypher_items, options)
|
52
|
+
return 0
|
53
|
+
end
|
54
|
+
title = "Morpheus Cypher List"
|
55
|
+
subtitles = []
|
56
|
+
subtitles += parse_list_subtitles(options)
|
57
|
+
print_h1 title, subtitles
|
58
|
+
if cypher_items.empty?
|
59
|
+
print cyan,"No cypher items found.",reset,"\n"
|
60
|
+
else
|
61
|
+
cypher_columns = {
|
62
|
+
"ID" => 'id',
|
63
|
+
"KEY" => lambda {|it| it["itemKey"] || it["key"] },
|
64
|
+
"LEASE REMAINING" => lambda {|it| it['expireDate'] ? format_local_dt(it['expireDate']) : "" },
|
65
|
+
"DATED CREATED" => lambda {|it| format_local_dt(it["dateCreated"]) },
|
66
|
+
"LAST ACCESSED" => lambda {|it| format_local_dt(it["lastAccessed"]) }
|
67
|
+
}
|
68
|
+
if options[:include_fields]
|
69
|
+
cypher_columns = options[:include_fields]
|
70
|
+
end
|
71
|
+
print cyan
|
72
|
+
print as_pretty_table(cypher_items, cypher_columns, options)
|
73
|
+
print reset
|
74
|
+
print_results_pagination(json_response)
|
75
|
+
end
|
76
|
+
print reset,"\n"
|
77
|
+
return 0
|
78
|
+
rescue RestClient::Exception => e
|
79
|
+
print_rest_exception(e, options)
|
80
|
+
exit 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def get(args)
|
85
|
+
options = {}
|
86
|
+
do_decrypt = false
|
87
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
88
|
+
opts.banner = subcommand_usage("[id]")
|
89
|
+
opts.on(nil, '--decrypt', 'Display the decrypted value') do
|
90
|
+
do_decrypt = true
|
91
|
+
end
|
92
|
+
build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
|
93
|
+
opts.footer = "Get details about a cypher." + "\n" +
|
94
|
+
"[id] is required. This is the id or key of a cypher."
|
95
|
+
end
|
96
|
+
optparse.parse!(args)
|
97
|
+
if args.count != 1
|
98
|
+
print_error Morpheus::Terminal.angry_prompt
|
99
|
+
puts_error "wrong number of arguments, expected 1 and got #{args.count}\n#{optparse}"
|
100
|
+
return 1
|
101
|
+
end
|
102
|
+
connect(options)
|
103
|
+
begin
|
104
|
+
if options[:dry_run]
|
105
|
+
if args[0].to_s =~ /\A\d{1,}\Z/
|
106
|
+
print_dry_run @cypher_interface.dry.get(args[0].to_i)
|
107
|
+
else
|
108
|
+
print_dry_run @cypher_interface.dry.list({name:args[0]})
|
109
|
+
end
|
110
|
+
return
|
111
|
+
end
|
112
|
+
cypher_item = find_cypher_by_name_or_id(args[0])
|
113
|
+
return 1 if cypher_item.nil?
|
114
|
+
json_response = {'cypher' => cypher_item} # skip redundant request
|
115
|
+
decrypt_json_response = nil
|
116
|
+
if do_decrypt
|
117
|
+
decrypt_json_response = @cypher_interface.decrypt(cypher_item["id"])
|
118
|
+
end
|
119
|
+
# json_response = @cypher_interface.get(cypher_item['id'])
|
120
|
+
cypher_item = json_response['cypher']
|
121
|
+
if options[:json]
|
122
|
+
puts as_json(json_response, options, "cypher")
|
123
|
+
return 0
|
124
|
+
elsif options[:yaml]
|
125
|
+
puts as_yaml(json_response, options, "cypher")
|
126
|
+
return 0
|
127
|
+
elsif options[:csv]
|
128
|
+
puts records_as_csv([cypher_item], options)
|
129
|
+
return 0
|
130
|
+
end
|
131
|
+
print_h1 "Cypher Details"
|
132
|
+
print cyan
|
133
|
+
description_cols = {
|
134
|
+
"ID" => 'id',
|
135
|
+
# what here
|
136
|
+
"Key" => lambda {|it| it["itemKey"] },
|
137
|
+
#"Value" => lambda {|it| it["value"] || "************" },
|
138
|
+
"Lease Remaining" => lambda {|it| format_local_dt(it["expireDate"]) },
|
139
|
+
"Date Created" => lambda {|it| format_local_dt(it["dateCreated"]) },
|
140
|
+
"Last Accessed" => lambda {|it| format_local_dt(it["lastAccessed"]) }
|
141
|
+
}
|
142
|
+
print_description_list(description_cols, cypher_item)
|
143
|
+
if decrypt_json_response
|
144
|
+
print_h2 "Decrypted Value"
|
145
|
+
print cyan
|
146
|
+
puts decrypt_json_response["cypher"] ? decrypt_json_response["cypher"]["itemValue"] : ""
|
147
|
+
end
|
148
|
+
print reset, "\n"
|
149
|
+
|
150
|
+
return 0
|
151
|
+
rescue RestClient::Exception => e
|
152
|
+
print_rest_exception(e, options)
|
153
|
+
return 1
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def decrypt(args)
|
158
|
+
params = {}
|
159
|
+
options = {}
|
160
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
161
|
+
opts.banner = subcommand_usage("[id]")
|
162
|
+
build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
|
163
|
+
opts.footer = "Decrypt the value of a cypher." + "\n" +
|
164
|
+
"[id] is required. This is the id or key of a cypher."
|
165
|
+
end
|
166
|
+
optparse.parse!(args)
|
167
|
+
if args.count != 1
|
168
|
+
print_error Morpheus::Terminal.angry_prompt
|
169
|
+
puts_error "wrong number of arguments, expected 1 and got #{args.count}\n#{optparse}"
|
170
|
+
return 1
|
171
|
+
end
|
172
|
+
connect(options)
|
173
|
+
begin
|
174
|
+
cypher_item = find_cypher_by_name_or_id(args[0])
|
175
|
+
return 1 if cypher_item.nil?
|
176
|
+
if options[:dry_run]
|
177
|
+
print_dry_run @cypher_interface.dry.decrypt(cypher_item["id"], params)
|
178
|
+
return
|
179
|
+
end
|
180
|
+
|
181
|
+
cypher_item = find_cypher_by_name_or_id(args[0])
|
182
|
+
return 1 if cypher_item.nil?
|
183
|
+
|
184
|
+
json_response = @cypher_interface.decrypt(cypher_item["id"], params)
|
185
|
+
if options[:json]
|
186
|
+
puts as_json(json_response, options, "cypher")
|
187
|
+
return 0
|
188
|
+
elsif options[:yaml]
|
189
|
+
puts as_yaml(json_response, options, "cypher")
|
190
|
+
return 0
|
191
|
+
elsif options[:csv]
|
192
|
+
puts records_as_csv([json_response["crypt"]], options)
|
193
|
+
return 0
|
194
|
+
end
|
195
|
+
print_h1 "Cypher Decrypt"
|
196
|
+
print cyan
|
197
|
+
print_description_list({
|
198
|
+
"ID" => 'id',
|
199
|
+
"Key" => lambda {|it| it["itemKey"] },
|
200
|
+
"Value" => lambda {|it| it["itemValue"] }
|
201
|
+
}, json_response["cypher"])
|
202
|
+
print reset, "\n"
|
203
|
+
return 0
|
204
|
+
rescue RestClient::Exception => e
|
205
|
+
print_rest_exception(e, options)
|
206
|
+
return 1
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def add(args)
|
211
|
+
options = {}
|
212
|
+
params = {}
|
213
|
+
|
214
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
215
|
+
opts.banner = subcommand_usage()
|
216
|
+
opts.on('--key VALUE', String, "Key for this cypher") do |val|
|
217
|
+
params['itemKey'] = val
|
218
|
+
end
|
219
|
+
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
|
220
|
+
opts.footer = "Create a new cypher."
|
221
|
+
end
|
222
|
+
optparse.parse!(args)
|
223
|
+
if args.count > 1
|
224
|
+
print_error Morpheus::Terminal.angry_prompt
|
225
|
+
puts_error "wrong number of arguments, expected 0-1 and got #{args.count}\n#{optparse}"
|
226
|
+
return 1
|
227
|
+
end
|
228
|
+
connect(options)
|
229
|
+
begin
|
230
|
+
payload = nil
|
231
|
+
if options[:payload]
|
232
|
+
payload = options[:payload]
|
233
|
+
else
|
234
|
+
# merge -O options into normally parsed options
|
235
|
+
params.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options] && options[:options].keys.size > 0
|
236
|
+
|
237
|
+
# support [key] as first argument
|
238
|
+
if args[0]
|
239
|
+
params['itemKey'] = args[0]
|
240
|
+
end
|
241
|
+
# Key
|
242
|
+
if !params['itemKey']
|
243
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'itemKey', 'fieldLabel' => 'Key', 'type' => 'text', 'required' => true, 'description' => cypher_key_help}], options)
|
244
|
+
params['itemKey'] = v_prompt['itemKey']
|
245
|
+
end
|
246
|
+
|
247
|
+
# Value
|
248
|
+
value_is_required = false
|
249
|
+
cypher_mount_type = params['itemKey'].split("/").first
|
250
|
+
if cypher_mount_type == ["secret", "password"].include?(cypher_mount_type)
|
251
|
+
value_is_required = true
|
252
|
+
end
|
253
|
+
|
254
|
+
if !params['itemValue']
|
255
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'itemValue', 'fieldLabel' => 'Value', 'type' => 'text', 'required' => value_is_required, 'description' => "Value for this cypher"}], options)
|
256
|
+
params['itemValue'] = v_prompt['itemValue']
|
257
|
+
end
|
258
|
+
|
259
|
+
# Lease
|
260
|
+
if !params['leaseTimeout']
|
261
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'leaseTimeout', 'fieldLabel' => 'Lease', 'type' => 'text', 'required' => false, 'description' => cypher_lease_help}], options)
|
262
|
+
params['leaseTimeout'] = v_prompt['leaseTimeout']
|
263
|
+
end
|
264
|
+
if !params['leaseTimeout'].to_s.empty?
|
265
|
+
params['leaseTimeout'] = params['leaseTimeout'].to_i
|
266
|
+
end
|
267
|
+
|
268
|
+
# construct payload
|
269
|
+
payload = {
|
270
|
+
'cypher' => params
|
271
|
+
}
|
272
|
+
end
|
273
|
+
|
274
|
+
if options[:dry_run]
|
275
|
+
print_dry_run @cypher_interface.dry.create(payload)
|
276
|
+
return
|
277
|
+
end
|
278
|
+
json_response = @cypher_interface.create(payload)
|
279
|
+
if options[:json]
|
280
|
+
print JSON.pretty_generate(json_response)
|
281
|
+
print "\n"
|
282
|
+
elsif !options[:quiet]
|
283
|
+
print_green_success "Added cypher"
|
284
|
+
# list([])
|
285
|
+
cypher_item = json_response['cypher']
|
286
|
+
get([cypher_item['id']])
|
287
|
+
end
|
288
|
+
return 0
|
289
|
+
rescue RestClient::Exception => e
|
290
|
+
print_rest_exception(e, options)
|
291
|
+
exit 1
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# def update(args)
|
296
|
+
# end
|
297
|
+
|
298
|
+
# def decrypt(args)
|
299
|
+
# end
|
300
|
+
|
301
|
+
def remove(args)
|
302
|
+
options = {}
|
303
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
304
|
+
opts.banner = subcommand_usage("[id]")
|
305
|
+
build_common_options(opts, options, [:account, :auto_confirm, :json, :dry_run, :quiet, :remote])
|
306
|
+
opts.footer = "Delete a cypher." + "\n" +
|
307
|
+
"[id] is required. This is the id or key of a cypher."
|
308
|
+
end
|
309
|
+
optparse.parse!(args)
|
310
|
+
|
311
|
+
if args.count != 1
|
312
|
+
print_error Morpheus::Terminal.angry_prompt
|
313
|
+
puts_error "wrong number of arguments, expected 1 and got #{args.count}\n#{optparse}"
|
314
|
+
return 1
|
315
|
+
end
|
316
|
+
|
317
|
+
connect(options)
|
318
|
+
begin
|
319
|
+
cypher_item = find_cypher_by_name_or_id(args[0])
|
320
|
+
return 1 if cypher_item.nil?
|
321
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the cypher #{cypher_item['itemKey']}?")
|
322
|
+
return 9, "aborted command"
|
323
|
+
end
|
324
|
+
if options[:dry_run]
|
325
|
+
print_dry_run @cypher_interface.dry.destroy(cypher_item["id"])
|
326
|
+
return
|
327
|
+
end
|
328
|
+
json_response = @cypher_interface.destroy(cypher_item["id"])
|
329
|
+
if options[:json]
|
330
|
+
print JSON.pretty_generate(json_response)
|
331
|
+
print "\n"
|
332
|
+
elsif !options[:quiet]
|
333
|
+
print_green_success "Deleted cypher #{cypher_item['itemKey']}"
|
334
|
+
# list([])
|
335
|
+
end
|
336
|
+
return 0
|
337
|
+
rescue RestClient::Exception => e
|
338
|
+
print_rest_exception(e, options)
|
339
|
+
return 1
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
private
|
344
|
+
|
345
|
+
def find_cypher_by_name_or_id(val)
|
346
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
347
|
+
return find_cypher_by_id(val)
|
348
|
+
else
|
349
|
+
return find_cypher_by_name(val)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def find_cypher_by_id(id)
|
354
|
+
begin
|
355
|
+
|
356
|
+
json_response = @cypher_interface.get(id.to_i)
|
357
|
+
return json_response['cypher']
|
358
|
+
rescue RestClient::Exception => e
|
359
|
+
if e.response && e.response.code == 404
|
360
|
+
print_red_alert "Cypher not found by id #{id}"
|
361
|
+
return nil
|
362
|
+
else
|
363
|
+
raise e
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
def find_cypher_by_name(name)
|
369
|
+
# api supports name as alias for itemKey
|
370
|
+
json_response = @cypher_interface.list({name: name.to_s})
|
371
|
+
|
372
|
+
cypher_items = json_response['cyphers']
|
373
|
+
if cypher_items.empty?
|
374
|
+
print_red_alert "Cypher not found by name #{name}"
|
375
|
+
return nil
|
376
|
+
elsif cypher_items.size > 1
|
377
|
+
print_red_alert "#{cypher_items.size} cyphers found by name #{name}"
|
378
|
+
rows = cypher_items.collect do |cypher_item|
|
379
|
+
{id: cypher_item['id'], name: cypher_item['name']}
|
380
|
+
end
|
381
|
+
print red
|
382
|
+
print as_pretty_table(rows, [:id, :name])
|
383
|
+
print reset,"\n"
|
384
|
+
return nil
|
385
|
+
else
|
386
|
+
return cypher_items[0]
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
def cypher_key_help
|
391
|
+
"""
|
392
|
+
Keys can have different behaviors depending on the specified mountpoint.
|
393
|
+
Available Mountpoints:
|
394
|
+
password - Generates a secure password of specified character length in the key pattern (or 15) with symbols, numbers, upper case, and lower case letters (i.e. password/15/mypass generates a 15 character password).
|
395
|
+
tfvars - This is a module to store a tfvars file for terraform.
|
396
|
+
secret - This is the standard secret module that stores a key/value in encrypted form.
|
397
|
+
uuid - Returns a new UUID by key name when requested and stores the generated UUID by key name for a given lease timeout period.
|
398
|
+
key - Generates a Base 64 encoded AES Key of specified bit length in the key pattern (i.e. key/128/mykey generates a 128-bit key)"""
|
399
|
+
end
|
400
|
+
|
401
|
+
def cypher_lease_help
|
402
|
+
"""
|
403
|
+
Lease time in MS (defaults to 32 days)
|
404
|
+
Quick MS Time Reference:
|
405
|
+
Day: 86400000
|
406
|
+
Week: 604800000
|
407
|
+
Month (30 days): 2592000000
|
408
|
+
Year: 31536000000"""
|
409
|
+
end
|
410
|
+
|
411
|
+
end
|
412
|
+
|