morpheus-cli 3.6.8 → 3.6.9

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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/lib/morpheus/api/account_groups_interface.rb +2 -2
  3. data/lib/morpheus/api/accounts_interface.rb +4 -7
  4. data/lib/morpheus/api/api_client.rb +207 -70
  5. data/lib/morpheus/api/app_templates_interface.rb +7 -28
  6. data/lib/morpheus/api/apps_interface.rb +14 -21
  7. data/lib/morpheus/api/archive_buckets_interface.rb +2 -2
  8. data/lib/morpheus/api/archive_files_interface.rb +6 -6
  9. data/lib/morpheus/api/auth_interface.rb +14 -1
  10. data/lib/morpheus/api/blueprints_interface.rb +9 -16
  11. data/lib/morpheus/api/cloud_datastores_interface.rb +1 -1
  12. data/lib/morpheus/api/cloud_policies_interface.rb +1 -1
  13. data/lib/morpheus/api/clouds_interface.rb +18 -21
  14. data/lib/morpheus/api/cypher_interface.rb +19 -28
  15. data/lib/morpheus/api/file_copy_request_interface.rb +1 -1
  16. data/lib/morpheus/api/group_policies_interface.rb +1 -1
  17. data/lib/morpheus/api/groups_interface.rb +4 -4
  18. data/lib/morpheus/api/image_builder_boot_scripts_interface.rb +1 -1
  19. data/lib/morpheus/api/image_builder_image_builds_interface.rb +2 -2
  20. data/lib/morpheus/api/image_builder_preseed_scripts_interface.rb +1 -1
  21. data/lib/morpheus/api/instances_interface.rb +17 -23
  22. data/lib/morpheus/api/logs_interface.rb +7 -10
  23. data/lib/morpheus/api/network_domains_interface.rb +1 -1
  24. data/lib/morpheus/api/network_groups_interface.rb +1 -1
  25. data/lib/morpheus/api/network_pool_servers_interface.rb +1 -1
  26. data/lib/morpheus/api/network_pools_interface.rb +1 -1
  27. data/lib/morpheus/api/network_proxies_interface.rb +1 -1
  28. data/lib/morpheus/api/network_services_interface.rb +1 -1
  29. data/lib/morpheus/api/networks_interface.rb +1 -1
  30. data/lib/morpheus/api/old_cypher_interface.rb +55 -0
  31. data/lib/morpheus/api/packages_interface.rb +1 -1
  32. data/lib/morpheus/api/policies_interface.rb +1 -1
  33. data/lib/morpheus/api/setup_interface.rb +1 -1
  34. data/lib/morpheus/api/storage_providers_interface.rb +1 -1
  35. data/lib/morpheus/api/whoami_interface.rb +1 -1
  36. data/lib/morpheus/benchmarking.rb +277 -0
  37. data/lib/morpheus/cli.rb +6 -22
  38. data/lib/morpheus/cli/access_token_command.rb +172 -0
  39. data/lib/morpheus/cli/accounts.rb +5 -0
  40. data/lib/morpheus/cli/apps.rb +93 -37
  41. data/lib/morpheus/cli/archives_command.rb +0 -2
  42. data/lib/morpheus/cli/auth_command.rb +112 -0
  43. data/lib/morpheus/cli/blueprints_command.rb +50 -13
  44. data/lib/morpheus/cli/change_password_command.rb +148 -0
  45. data/lib/morpheus/cli/cli_command.rb +173 -49
  46. data/lib/morpheus/cli/clouds.rb +15 -5
  47. data/lib/morpheus/cli/command_error.rb +7 -1
  48. data/lib/morpheus/cli/{alias_command.rb → commands/standard/alias_command.rb} +79 -51
  49. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +399 -0
  50. data/lib/morpheus/cli/commands/standard/coloring_command.rb +60 -0
  51. data/lib/morpheus/cli/{curl_command.rb → commands/standard/curl_command.rb} +0 -7
  52. data/lib/morpheus/cli/commands/standard/debug_command.rb +61 -0
  53. data/lib/morpheus/cli/{echo_command.rb → commands/standard/echo_command.rb} +1 -1
  54. data/lib/morpheus/cli/{edit_profile_command.rb → commands/standard/edit_profile_command.rb} +0 -0
  55. data/lib/morpheus/cli/{edit_rc_command.rb → commands/standard/edit_rc_command.rb} +0 -0
  56. data/lib/morpheus/cli/commands/standard/get_prompt_command.rb +39 -0
  57. data/lib/morpheus/cli/commands/standard/history_command.rb +76 -0
  58. data/lib/morpheus/cli/{log_level_command.rb → commands/standard/log_level_command.rb} +1 -1
  59. data/lib/morpheus/cli/{man_command.rb → commands/standard/man_command.rb} +2 -2
  60. data/lib/morpheus/cli/commands/standard/rm_command.rb +14 -0
  61. data/lib/morpheus/cli/commands/standard/set_prompt_command.rb +54 -0
  62. data/lib/morpheus/cli/{sleep_command.rb → commands/standard/sleep_command.rb} +0 -0
  63. data/lib/morpheus/cli/{source_command.rb → commands/standard/source_command.rb} +0 -0
  64. data/lib/morpheus/cli/{ssl_verification_command.rb → commands/standard/ssl_verification_command.rb} +1 -1
  65. data/lib/morpheus/cli/commands/standard/tee_command.rb +14 -0
  66. data/lib/morpheus/cli/{version_command.rb → commands/standard/version_command.rb} +0 -0
  67. data/lib/morpheus/cli/credentials.rb +276 -87
  68. data/lib/morpheus/cli/cypher_command.rb +333 -214
  69. data/lib/morpheus/cli/error_handler.rb +12 -2
  70. data/lib/morpheus/cli/groups.rb +44 -20
  71. data/lib/morpheus/cli/hosts.rb +39 -16
  72. data/lib/morpheus/cli/instances.rb +114 -62
  73. data/lib/morpheus/cli/login.rb +74 -21
  74. data/lib/morpheus/cli/logout.rb +3 -4
  75. data/lib/morpheus/cli/mixins/accounts_helper.rb +50 -18
  76. data/lib/morpheus/cli/mixins/print_helper.rb +207 -42
  77. data/lib/morpheus/cli/old_cypher_command.rb +414 -0
  78. data/lib/morpheus/cli/option_parser.rb +6 -1
  79. data/lib/morpheus/cli/processes_command.rb +3 -0
  80. data/lib/morpheus/cli/remote.rb +11 -17
  81. data/lib/morpheus/cli/roles.rb +17 -17
  82. data/lib/morpheus/cli/security_groups.rb +47 -17
  83. data/lib/morpheus/cli/shell.rb +139 -79
  84. data/lib/morpheus/cli/tenants_command.rb +353 -0
  85. data/lib/morpheus/cli/users.rb +26 -18
  86. data/lib/morpheus/cli/version.rb +1 -1
  87. data/lib/morpheus/cli/whoami.rb +14 -10
  88. data/lib/morpheus/formatters.rb +4 -4
  89. data/lib/morpheus/logging.rb +16 -8
  90. data/lib/morpheus/terminal.rb +63 -34
  91. metadata +28 -15
  92. data/lib/morpheus/cli/coloring_command.rb +0 -45
  93. data/lib/morpheus/cli/set_prompt_command.rb +0 -51
@@ -6,9 +6,15 @@ require 'morpheus/cli/cli_command'
6
6
  class Morpheus::Cli::CypherCommand
7
7
  include Morpheus::Cli::CliCommand
8
8
 
9
- set_command_name :cypher
9
+ set_command_name :'cypher'
10
10
 
11
- register_subcommands :list, :get, :add, :remove, :decrypt
11
+ register_subcommands :list, :get, :put, :remove
12
+ # some appropriate aliases
13
+ #register_subcommands :read => :get,
14
+ #register_subcommands :write => :put
15
+ #register_subcommands :add => :put
16
+ #register_subcommands :delete => :remove
17
+ # register_subcommands :destroy => :destroy
12
18
 
13
19
  def initialize()
14
20
  # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
@@ -16,6 +22,7 @@ class Morpheus::Cli::CypherCommand
16
22
 
17
23
  def connect(opts)
18
24
  @api_client = establish_remote_appliance_connection(opts)
25
+ # @cypher_interface = @api_client.cypher
19
26
  @cypher_interface = @api_client.cypher
20
27
  end
21
28
 
@@ -27,51 +34,74 @@ class Morpheus::Cli::CypherCommand
27
34
  options = {}
28
35
  params = {}
29
36
  optparse = Morpheus::Cli::OptionParser.new do |opts|
30
- opts.banner = subcommand_usage()
37
+ opts.banner = subcommand_usage("[key]")
38
+ # opts.on('--details', '--details', "Show more details." ) do
39
+ # options[:details] = true
40
+ # end
31
41
  build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :json, :dry_run, :remote])
32
- opts.footer = "List cypher items."
42
+ opts.footer = "List cypher keys." + "\n" +
43
+ "[key] is optional. This is the cypher key or path to search for."
33
44
  end
34
45
  optparse.parse!(args)
35
46
  connect(options)
47
+ if args.count > 1
48
+ print_error Morpheus::Terminal.angry_prompt
49
+ puts_error "wrong number of arguments, expected 0-1 and got #{args.count}\n#{optparse}"
50
+ return 1
51
+ end
52
+ item_key = args[0]
36
53
  begin
37
54
  params.merge!(parse_list_options(options))
55
+ @cypher_interface.setopts(options)
38
56
  if options[:dry_run]
39
- print_dry_run @cypher_interface.dry.list(params)
57
+ print_dry_run @cypher_interface.dry.list(item_key, params)
40
58
  return 0
41
59
  end
42
- json_response = @cypher_interface.list(params)
43
- cypher_items = json_response["cyphers"]
60
+ json_response = @cypher_interface.list(item_key, params)
44
61
  if options[:json]
45
- puts as_json(json_response, options, "cyphers")
62
+ puts as_json(json_response, options)
46
63
  return 0
47
64
  elsif options[:yaml]
48
- puts as_yaml(json_response, options, "cyphers")
65
+ puts as_yaml(json_response, options)
49
66
  return 0
50
67
  elsif options[:csv]
51
- puts records_as_csv(cypher_items, options)
68
+ puts records_as_csv([json_response], options)
52
69
  return 0
53
70
  end
54
- title = "Morpheus Cypher List"
71
+ cypher_data = json_response["data"]
72
+ title = "Morpheus Cypher Key List"
55
73
  subtitles = []
56
74
  subtitles += parse_list_subtitles(options)
57
- print_h1 title, subtitles
58
- if cypher_items.empty?
59
- print cyan,"No cypher items found.",reset,"\n"
75
+ if item_key
76
+ subtitles << "Key: #{item_key}"
77
+ end
78
+ print_h1 title, subtitles, options
79
+
80
+ cypher_keys = json_response["data"] ? json_response["data"]["keys"] : []
81
+ if cypher_keys.nil? || cypher_keys.empty?
82
+ if item_key
83
+ print cyan,"No cypher items found for '#{item_key}'.",reset,"\n"
84
+ end
60
85
  else
86
+
61
87
  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"]) }
88
+ "KEY" => lambda {|it| it["itemKey"] },
89
+ # "LEASE REMAINING" => lambda {|it|
90
+ # format_lease_remaining(it["expireDate"])
91
+ # },
92
+ "TTL" => lambda {|it|
93
+ format_expiration_ttl(it["expireDate"])
94
+ },
95
+ "EXPIRATION" => lambda {|it|
96
+ format_expiration_date(it["expireDate"])
97
+ },
98
+ "DATE CREATED" => lambda {|it| format_local_dt(it["dateCreated"]) },
99
+ "LAST ACCESS" => lambda {|it| format_local_dt(it["lastAccessed"]) }
67
100
  }
68
- if options[:include_fields]
69
- cypher_columns = options[:include_fields]
70
- end
71
101
  print cyan
72
- print as_pretty_table(cypher_items, cypher_columns, options)
102
+ print as_pretty_table(json_response["cypherItems"], cypher_columns, options)
73
103
  print reset
74
- print_results_pagination(json_response)
104
+ print_results_pagination({size:cypher_keys.size,total:cypher_keys.size.to_i})
75
105
  end
76
106
  print reset,"\n"
77
107
  return 0
@@ -83,15 +113,33 @@ class Morpheus::Cli::CypherCommand
83
113
 
84
114
  def get(args)
85
115
  options = {}
116
+ params = {}
117
+ value_only = false
86
118
  do_decrypt = false
119
+ item_ttl = nil
87
120
  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
121
+ opts.banner = subcommand_usage("[key]")
122
+ # opts.on(nil, '--decrypt', 'Display the decrypted value') do
123
+ # do_decrypt = true
124
+ # end
125
+ # opts.on(nil, '--metadata', 'Display metadata about the key, such as versions.') do
126
+ # display_versions = true
127
+ # end
128
+ opts.on('-v', '--value', 'Print only the decrypted value.') do
129
+ value_only = true
130
+ end
131
+ opts.on( '-t', '--ttl SECONDS', "Time to live, the lease duration before this key expires. Use if creating new key." ) do |val|
132
+ item_ttl = val
133
+ if val.to_s.empty? || val.to_s == '0'
134
+ item_ttl = 0
135
+ else
136
+ item_ttl = val
137
+ end
91
138
  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."
139
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :quiet, :remote])
140
+ opts.footer = "Read a cypher item and display the decrypted value." + "\n" +
141
+ "[key] is required. This is the cypher key to read." + "\n" +
142
+ "Use --ttl to specify a ttl if expecting cypher engine to automatically create the key."
95
143
  end
96
144
  optparse.parse!(args)
97
145
  if args.count != 1
@@ -101,105 +149,98 @@ class Morpheus::Cli::CypherCommand
101
149
  end
102
150
  connect(options)
103
151
  begin
152
+ item_key = args[0]
153
+ if item_ttl
154
+ params["ttl"] = item_ttl
155
+ end
156
+ @cypher_interface.setopts(options)
104
157
  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
158
+ print_dry_run @cypher_interface.dry.get(item_key, params)
159
+ return 0
111
160
  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"])
161
+ json_response = @cypher_interface.get(item_key, params)
162
+
163
+ if options[:quiet]
164
+ return 0
118
165
  end
119
- # json_response = @cypher_interface.get(cypher_item['id'])
120
- cypher_item = json_response['cypher']
166
+
121
167
  if options[:json]
122
- puts as_json(json_response, options, "cypher")
168
+ puts as_json(json_response, options)
123
169
  return 0
124
170
  elsif options[:yaml]
125
- puts as_yaml(json_response, options, "cypher")
171
+ puts as_yaml(json_response, options)
126
172
  return 0
127
173
  elsif options[:csv]
128
- puts records_as_csv([cypher_item], options)
174
+ puts records_as_csv([json_response], options)
175
+ return 0
176
+ end
177
+
178
+ cypher_item = json_response['cypher']
179
+ decrypted_value = json_response["data"]
180
+
181
+ if value_only
182
+ print cyan
183
+ if decrypted_value.is_a?(Hash)
184
+ puts as_json(decrypted_value)
185
+ else
186
+ puts decrypted_value.to_s
187
+ end
188
+ print reset
129
189
  return 0
130
190
  end
131
- print_h1 "Cypher Details"
191
+
192
+ print_h1 "Cypher Key", [], options
132
193
  print cyan
194
+ # This response does contain cypher too though.
195
+
196
+ if cypher_item.empty?
197
+ puts_error "Cypher data not found in response"
198
+ return 1
199
+ end
133
200
  description_cols = {
134
- "ID" => 'id',
135
- # what here
201
+ #"ID" => 'id',
136
202
  "Key" => lambda {|it| it["itemKey"] },
137
- #"Value" => lambda {|it| it["value"] || "************" },
138
- "Lease Remaining" => lambda {|it| format_local_dt(it["expireDate"]) },
203
+ "TTL" => lambda {|it|
204
+ format_expiration_ttl(it["expireDate"])
205
+ },
206
+ "Expiration" => lambda {|it|
207
+ format_expiration_date(it["expireDate"])
208
+ },
139
209
  "Date Created" => lambda {|it| format_local_dt(it["dateCreated"]) },
140
- "Last Accessed" => lambda {|it| format_local_dt(it["lastAccessed"]) }
210
+ "Last Access" => lambda {|it| format_local_dt(it["lastAccessed"]) }
141
211
  }
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"] : ""
212
+ if cypher_item["expireDate"].nil?
213
+ description_cols.delete("Expires")
147
214
  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
215
+ print_description_list(description_cols, cypher_item)
156
216
 
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
217
+ print_h2 "Value", options
218
+ # print_h2 "Decrypted Value"
180
219
 
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
220
+ if decrypted_value
221
+ print cyan
222
+ if decrypted_value.is_a?(String)
223
+ # attempt to parse and render as_json
224
+ if decrypted_value.to_s[0] == '{' && decrypted_value.to_s[-1] == '}'
225
+ begin
226
+ json_value = JSON.parse(decrypted_value)
227
+ puts as_json(json_value)
228
+ rescue => ex
229
+ Morpheus::Logging::DarkPrinter.puts "Failed to parse cypher value '#{decrypted_value}' as JSON. Error: #{ex}" if Morpheus::Logging.debug?
230
+ puts decrypted_value
231
+ end
232
+ else
233
+ puts decrypted_value
234
+ end
235
+ else
236
+ puts as_json(decrypted_value)
237
+ end
238
+ else
239
+ puts "No data found."
194
240
  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"])
241
+
202
242
  print reset, "\n"
243
+
203
244
  return 0
204
245
  rescue RestClient::Exception => e
205
246
  print_rest_exception(e, options)
@@ -207,83 +248,150 @@ class Morpheus::Cli::CypherCommand
207
248
  end
208
249
  end
209
250
 
210
- def add(args)
251
+ def put(args)
211
252
  options = {}
212
253
  params = {}
213
-
254
+ item_key = nil
255
+ item_value = nil
256
+ item_ttl = nil
257
+ no_overwrite = nil
214
258
  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
259
+ opts.banner = subcommand_usage("[key] [value]")
260
+ # opts.on( '--key VALUE', String, "Key" ) do |val|
261
+ # item_key = val
262
+ # end
263
+ opts.on( '-v', '--value VALUE', "Secret value" ) do |val|
264
+ item_value = val
218
265
  end
219
- build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
220
- opts.footer = "Create a new cypher."
266
+ opts.on( '-t', '--ttl SECONDS', "Time to live, the lease duration before this key expires." ) do |val|
267
+ item_ttl = val
268
+ if val.to_s.empty? || val.to_s == '0'
269
+ item_ttl = 0
270
+ else
271
+ item_ttl = val
272
+ end
273
+ end
274
+ # opts.on( '--no-overwrite', '--no-overwrite', "Do not overwrite existing keys. Existing keys are overwritten by default." ) do
275
+ # params['overwrite'] = false
276
+ # end
277
+ build_common_options(opts, options, [:auto_confirm, :options, :payload, :json, :dry_run, :quiet, :remote])
278
+ opts.footer = "Create or update a cypher key." + "\n" +
279
+ "[key] is required. This is the key of the cypher being created or updated." + "\n" +
280
+ "[value] is required. This is the new value or value pairs being stored. Supports format foo=bar, 1-N arguments." + "\n" +
281
+ "The --payload option can be used instead of passing [value] argument."
221
282
  end
222
283
  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
284
+ # if args.count < 1
285
+ # print_error Morpheus::Terminal.angry_prompt
286
+ # puts_error "wrong number of arguments, expected 1-N and got #{args.count}\n#{optparse}"
287
+ # return 1
288
+ # end
228
289
  connect(options)
229
290
  begin
291
+ if args[0]
292
+ item_key = args[0]
293
+ end
294
+ options[:options] ||= {}
295
+ options[:options]['key'] = item_key if item_key
296
+ # Key prompt
297
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'key', 'fieldLabel' => 'Key', 'type' => 'text', 'required' => true, 'description' => cypher_key_help}], options[:options])
298
+ item_key = v_prompt['key']
299
+
230
300
  payload = nil
231
301
  if options[:payload]
232
302
  payload = options[:payload]
303
+ payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) || ['key','value'].include?(k)}) if options[:options] && options[:options].keys.size > 0
233
304
  else
234
305
  # 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
306
+ params.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) || ['key','value'].include?(k)}) if options[:options] && options[:options].keys.size > 0
236
307
 
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
308
+ # Value prompt
248
309
  value_is_required = false
249
- cypher_mount_type = params['itemKey'].split("/").first
250
- if cypher_mount_type == ["secret", "password"].include?(cypher_mount_type)
310
+ cypher_mount_type = item_key.split("/").first
311
+ if ["secret"].include?(cypher_mount_type)
251
312
  value_is_required = true
252
313
  end
253
314
 
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
315
+ # todo: read value from STDIN shall we?
258
316
 
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
317
+ # cool, we got value as arguments like foo=bar
318
+ if args.count > 1
319
+ # parse one and only arg as the value like password/mine mypassword123
320
+ if args.count == 2 && args[1].split("=").size() == 1
321
+ item_value = args[1]
322
+ elsif args.count > 1
323
+ # parse args as key value pairs like secret/config foo=bar thing=myvalue
324
+ value_arguments = args[1..-1]
325
+ value_arguments_map = {}
326
+ value_arguments.each do |value_argument|
327
+ value_pair = value_argument.split("=")
328
+ value_arguments_map[value_pair[0]] = value_pair[1] ? value_pair[1..-1].join("=") : nil
329
+ end
330
+ item_value = value_arguments_map
331
+ end
332
+ else
333
+ # Prompt for a single text value to be sent as {"value":"my secret"}
334
+ if value_is_required
335
+ options[:options]['value'] = item_value if item_value
336
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'fieldLabel' => 'Value', 'type' => 'text', 'required' => value_is_required, 'description' => "Secret value for this cypher"}], options[:options])
337
+ item_value = v_prompt['value']
338
+ end
266
339
  end
267
340
 
268
341
  # construct payload
269
- payload = {
270
- 'cypher' => params
271
- }
342
+ # payload = {
343
+ # 'cypher' => params
344
+ # }
345
+
346
+ # if value is valid json, then the payload IS the value
347
+ if item_value.is_a?(String) && item_value.to_s[0] == '{' && item_value.to_s[-1] == '}'
348
+ begin
349
+ json_object = JSON.parse(item_value)
350
+ item_value = json_object
351
+ rescue => ex
352
+ Morpheus::Logging::DarkPrinter.puts "Failed to parse cypher value '#{item_value}' as JSON. Error: #{ex}" if Morpheus::Logging.debug?
353
+ raise_command_error "Failed to parse cypher value as JSON: #{item_value}"
354
+ # return 1
355
+ end
356
+ else
357
+ # it is just a string
358
+ if item_value.is_a?(String)
359
+ payload = {"value" => item_value}
360
+ elsif item_value.nil?
361
+ payload = {}
362
+ else item_value
363
+ # great, a Hash I hope
364
+ payload = item_value
365
+ end
366
+ end
272
367
  end
273
368
 
369
+ # prompt for Lease
370
+ options[:options]['ttl'] = item_ttl if item_ttl
371
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'ttl', 'fieldLabel' => 'Lease (TTL in seconds)', 'type' => 'text', 'required' => false, 'description' => cypher_ttl_help}], options[:options])
372
+ item_ttl = v_prompt['ttl']
373
+
374
+
375
+ if item_ttl
376
+ # I would like this better as params...
377
+ payload["ttl"] = item_ttl
378
+ end
379
+ @cypher_interface.setopts(options)
274
380
  if options[:dry_run]
275
- print_dry_run @cypher_interface.dry.create(payload)
381
+ print_dry_run @cypher_interface.dry.create(item_key, payload)
276
382
  return
277
383
  end
278
- json_response = @cypher_interface.create(payload)
384
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to overwrite the cypher key #{item_key}?", {default:true})
385
+ return 9, "aborted command"
386
+ end
387
+ json_response = @cypher_interface.create(item_key, payload)
279
388
  if options[:json]
280
- print JSON.pretty_generate(json_response)
281
- print "\n"
389
+ puts as_json(json_response, options)
282
390
  elsif !options[:quiet]
283
- print_green_success "Added cypher"
284
- # list([])
391
+ print_green_success "Wrote cypher #{item_key}"
392
+ # should print without doing get, because that can use a token.
285
393
  cypher_item = json_response['cypher']
286
- get([cypher_item['id']])
394
+ get([item_key])
287
395
  end
288
396
  return 0
289
397
  rescue RestClient::Exception => e
@@ -292,19 +400,14 @@ class Morpheus::Cli::CypherCommand
292
400
  end
293
401
  end
294
402
 
295
- # def update(args)
296
- # end
297
-
298
- # def decrypt(args)
299
- # end
300
-
301
403
  def remove(args)
302
404
  options = {}
405
+ params = {}
303
406
  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])
407
+ opts.banner = subcommand_usage("[key]")
408
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
306
409
  opts.footer = "Delete a cypher." + "\n" +
307
- "[id] is required. This is the id or key of a cypher."
410
+ "[key] is required. This is the key of a cypher."
308
411
  end
309
412
  optparse.parse!(args)
310
413
 
@@ -316,21 +419,22 @@ class Morpheus::Cli::CypherCommand
316
419
 
317
420
  connect(options)
318
421
  begin
319
- cypher_item = find_cypher_by_name_or_id(args[0])
422
+ item_key = args[0]
423
+ cypher_item = find_cypher_by_key(item_key)
320
424
  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']}?")
425
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the cypher #{item_key}?")
322
426
  return 9, "aborted command"
323
427
  end
428
+ @cypher_interface.setopts(options)
324
429
  if options[:dry_run]
325
- print_dry_run @cypher_interface.dry.destroy(cypher_item["id"])
430
+ print_dry_run @cypher_interface.dry.destroy(item_key, params)
326
431
  return
327
432
  end
328
- json_response = @cypher_interface.destroy(cypher_item["id"])
433
+ json_response = @cypher_interface.destroy(item_key, params)
329
434
  if options[:json]
330
- print JSON.pretty_generate(json_response)
331
- print "\n"
435
+ puts as_json(json_response, options)
332
436
  elsif !options[:quiet]
333
- print_green_success "Deleted cypher #{cypher_item['itemKey']}"
437
+ print_green_success "Deleted cypher #{item_key}"
334
438
  # list([])
335
439
  end
336
440
  return 0
@@ -342,22 +446,13 @@ class Morpheus::Cli::CypherCommand
342
446
 
343
447
  private
344
448
 
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)
449
+ def find_cypher_by_key(key, params={})
354
450
  begin
355
-
356
- json_response = @cypher_interface.get(id.to_i)
357
- return json_response['cypher']
451
+ json_response = @cypher_interface.get(key, params)
452
+ return json_response
358
453
  rescue RestClient::Exception => e
359
454
  if e.response && e.response.code == 404
360
- print_red_alert "Cypher not found by id #{id}"
455
+ print_red_alert "Cypher not found by key #{key}"
361
456
  return nil
362
457
  else
363
458
  raise e
@@ -365,28 +460,6 @@ class Morpheus::Cli::CypherCommand
365
460
  end
366
461
  end
367
462
 
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
463
  def cypher_key_help
391
464
  """
392
465
  Keys can have different behaviors depending on the specified mountpoint.
@@ -398,15 +471,61 @@ uuid - Returns a new UUID by key name when requested and stores the generated UU
398
471
  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
472
  end
400
473
 
401
- def cypher_lease_help
474
+ def cypher_ttl_help
402
475
  """
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"""
476
+ Lease time in seconds (defaults to 32 days)
477
+ Quick Second Time Reference:
478
+ Hour: 3600
479
+ Day: 86400
480
+ Week: 604800
481
+ Month (30 days): 2592000
482
+ Year: 31536000
483
+ This can also be passed in abbreviated format with the unit as the suffix. eg. 32d, 90s, 5y
484
+ """
485
+ end
486
+
487
+ def format_lease_remaining(expire_date, warning_threshold=3600, return_color=cyan)
488
+ out = ""
489
+ if expire_date
490
+ out << format_expiration_date(expire_date, warning_threshold, return_color)
491
+ out << " ("
492
+ out << format_expiration_ttl(expire_date, warning_threshold, return_color)
493
+ out << ")"
494
+ else
495
+ # out << return_color
496
+ out << "Never expires"
497
+ end
498
+ return out
499
+ end
500
+
501
+ def format_expiration_date(expire_date, warning_threshold=3600, return_color=cyan)
502
+ expire_date = parse_time(expire_date)
503
+ if !expire_date
504
+ # return ""
505
+ return cyan + "Never expires" + return_color
506
+ end
507
+ if expire_date <= Time.now
508
+ return red + format_local_dt(expire_date) + return_color
509
+ else
510
+ return cyan + format_local_dt(expire_date) + return_color
511
+ end
409
512
  end
410
513
 
514
+ def format_expiration_ttl(expire_date, warning_threshold=3600, return_color=cyan)
515
+ expire_date = parse_time(expire_date)
516
+ if !expire_date
517
+ return ""
518
+ #return cyan + "Never expires" + return_color
519
+ end
520
+ seconds = expire_date - Time.now
521
+ if seconds <= 0
522
+ # return red + "Expired" + return_color
523
+ return red + "Expired " + format_duration_seconds(seconds.abs).to_s + " ago" + return_color
524
+ elsif seconds <= warning_threshold
525
+ return yellow + format_duration_seconds(seconds.abs).to_s + return_color
526
+ else
527
+ return cyan + format_duration_seconds(seconds.abs).to_s + return_color
528
+ end
529
+ end
411
530
  end
412
531