morpheus-cli 5.0.0 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +12 -0
  4. data/lib/morpheus/api/billing_interface.rb +1 -0
  5. data/lib/morpheus/api/deploy_interface.rb +1 -1
  6. data/lib/morpheus/api/deployments_interface.rb +20 -1
  7. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  8. data/lib/morpheus/api/instances_interface.rb +7 -0
  9. data/lib/morpheus/api/search_interface.rb +13 -0
  10. data/lib/morpheus/api/servers_interface.rb +7 -0
  11. data/lib/morpheus/api/usage_interface.rb +18 -0
  12. data/lib/morpheus/cli.rb +4 -1
  13. data/lib/morpheus/cli/cli_command.rb +26 -9
  14. data/lib/morpheus/cli/commands/standard/curl_command.rb +3 -5
  15. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  16. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  17. data/lib/morpheus/cli/deploy.rb +199 -90
  18. data/lib/morpheus/cli/deployments.rb +341 -28
  19. data/lib/morpheus/cli/deploys.rb +206 -41
  20. data/lib/morpheus/cli/error_handler.rb +7 -0
  21. data/lib/morpheus/cli/forgot_password.rb +133 -0
  22. data/lib/morpheus/cli/health_command.rb +2 -2
  23. data/lib/morpheus/cli/hosts.rb +169 -32
  24. data/lib/morpheus/cli/instances.rb +83 -32
  25. data/lib/morpheus/cli/invoices_command.rb +33 -16
  26. data/lib/morpheus/cli/logs_command.rb +9 -6
  27. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -2
  28. data/lib/morpheus/cli/mixins/print_helper.rb +0 -21
  29. data/lib/morpheus/cli/mixins/provisioning_helper.rb +24 -4
  30. data/lib/morpheus/cli/option_types.rb +266 -17
  31. data/lib/morpheus/cli/remote.rb +35 -10
  32. data/lib/morpheus/cli/reports_command.rb +99 -30
  33. data/lib/morpheus/cli/search_command.rb +182 -0
  34. data/lib/morpheus/cli/setup.rb +1 -1
  35. data/lib/morpheus/cli/shell.rb +33 -11
  36. data/lib/morpheus/cli/tasks.rb +20 -21
  37. data/lib/morpheus/cli/usage_command.rb +64 -11
  38. data/lib/morpheus/cli/version.rb +1 -1
  39. data/lib/morpheus/cli/virtual_images.rb +280 -199
  40. data/lib/morpheus/cli/whoami.rb +6 -6
  41. data/lib/morpheus/cli/workflows.rb +33 -40
  42. data/lib/morpheus/formatters.rb +22 -0
  43. data/lib/morpheus/terminal.rb +6 -2
  44. metadata +7 -2
@@ -6,6 +6,7 @@ class Morpheus::Cli::Deploy
6
6
  include Morpheus::Cli::DeploymentsHelper
7
7
 
8
8
  set_command_name :deploy
9
+ set_command_description "Deploy to an instance from a morpheus.yml file."
9
10
 
10
11
  def connect(opts)
11
12
  @api_client = establish_remote_appliance_connection(opts)
@@ -18,30 +19,35 @@ class Morpheus::Cli::Deploy
18
19
  options={}
19
20
  optparse = Morpheus::Cli::OptionParser.new do|opts|
20
21
  opts.banner = "Usage: morpheus deploy [environment]"
21
- build_common_options(opts, options, [:auto_confirm, :remote, :dry_run])
22
+ build_common_options(opts, options, [:auto_confirm, :quiet, :remote, :dry_run])
22
23
  opts.footer = <<-EOT
23
24
  Deploy to an instance using the morpheus.yml file, located in the working directory.
24
25
  [environment] is optional. Merge settings under environments.{environment}. Default is no environment.
25
26
 
26
- First this parses the morpheus.yml file and merges the specified environment settings.
27
- The specified instance must exist and the specified version must not exist.
28
- If the settings are valid, the new deployment version will be created and
29
- all the specified files are uploaded to the new deployment version.
30
- Finally, it deploys the new version to the instance.
27
+ First the morpheus.yml YAML file is parsed, merging the specified environment's nested settings.
28
+ The specified instance must exist and the specified deployment version must not exist.
29
+ If the settings are valid, the new deployment version will be created.
30
+ If is a file type deployment, all the discovered files are uploaded to the new deployment version.
31
+ Finally, it deploys the new version to the instance using any specified config options.
31
32
 
32
33
  The morpheus.yml should be located in the working directory.
33
- This file contains the information necessary to perform a deployment via the cli.
34
+ This YAML file contains the settings that specify how to execute the deployment.
34
35
 
35
36
  File Settings
36
37
  ==================
37
38
 
38
- * name - (required) The instance name we are deploying to and, by default, name of the deployment being created.
39
+ * name - (required) The instance name being deployed to, also the default name of the deployment.
39
40
  * version - (required) The version identifier of the deployment being created (userVersion)
40
41
  * deployment - The name of the deployment being created, name is used by default
41
- * script - The initial script to run before looking for files to upload.
42
- * files - List of file patterns to use for uploading files and their target destination.
42
+ * type - The type of deployment, file, 'git' or 'fetch', default is 'file'.
43
+ * script - The initial script to run, happens before finding the files to be uploaded.
44
+ * files - (required) List of file patterns to use for uploading files and their target destination.
43
45
  Each item should contain path and pattern, path may be relative to the working directory, default pattern is: '**/*'
44
- * options - Map of deployment options depending on deployment type
46
+ only applies to type 'file'
47
+ * url - (required) The url to fetch files from, only applies to types 'git' and 'fetch'.
48
+ * ref - The git reference, default is master (main), only applies to type git.
49
+ * config - Map of deployment config options depending on deployment type
50
+ * options - alias for config
45
51
  * post_script - A post operation script to be run on the local machine
46
52
  * stage_only - If set to true the deploy will only be staged and not actually run
47
53
  * environments - Map of objects that contain nested properties for each environment name
@@ -51,7 +57,7 @@ It is possible to nest these properties in an "environments" map to override bas
51
57
  Example
52
58
  ==================
53
59
 
54
- name: neatsite
60
+ name: mysite
55
61
  version: 5.0
56
62
  script: "rake build"
57
63
  files:
@@ -60,6 +66,16 @@ environments:
60
66
  production:
61
67
  files:
62
68
  - path: production-build
69
+
70
+
71
+ Git Example
72
+ ==================
73
+
74
+ name: morpheus-apidoc
75
+ version: 5.0.0
76
+ type: git
77
+ url: "https://github.com/gomorpheus/morpheus-apidoc"
78
+
63
79
  EOT
64
80
  end
65
81
  optparse.parse!(args)
@@ -106,45 +122,90 @@ EOT
106
122
  instance = instance_results['instances'][0]
107
123
  instance_id = instance['id']
108
124
 
125
+ # auto detect type, default to file
126
+ deploy_type = deploy_args['type'] || deploy_args['deployType']
127
+ if deploy_type.nil?
128
+ if deploy_args['gitUrl']
129
+ deploy_type = 'git'
130
+ elsif deploy_args['fetchUrl'] || deploy_args['url']
131
+ deploy_type = 'fetch'
132
+ end
133
+ end
134
+ if deploy_type.nil?
135
+ deploy_type = "file"
136
+ end
137
+ deploy_url = deploy_args['url'] || deploy_args['fetchUrl'] || deploy_args['gitUrl']
138
+ if deploy_url.nil? && (deploy_type == "git" || deploy_type == "fetch")
139
+ raise_command_error "Deploy type '#{deploy_type}' requires a url to be specified"
140
+ end
141
+ #deploy_type = "file" if deploy_type.to_s.downcase == "files"
142
+
143
+ deploy_config = deploy_args['options'] || deploy_args['config']
144
+
109
145
  # ok do it
110
146
  # fetch/create deployment, create deployment version, upload files, and deploy it to instance.
111
147
 
112
- print_h1 "Morpheus Deployment"
113
-
114
- columns = {
115
- "Instance" => :name,
116
- "Deployment" => :deployment,
117
- "Version" => :version,
118
- "Script" => :script,
119
- "Post Script" => :post_script,
120
- "Files" => :files,
121
- "Environment" => :environment,
122
- }
123
- pretty_file_config = deploy_args['files'].collect {|it|
124
- [(it['path'] ? "path: #{it['path']}" : nil), (it['pattern'] ? "pattern: #{it['pattern']}" : nil)].compact.join(", ")
125
- }.join(", ")
126
- deploy_settings = {
127
- :name => instance_name,
128
- :deployment => deployment_name,
129
- :version => version_number,
130
- :script => deploy_args['script'],
131
- :post_script => deploy_args['post_script'],
132
- :files => pretty_file_config,
133
- # :files => deploy_args['files'],
134
- # :files => deploy_files.size,
135
- # :file_config => (deploy_files.size == 1 ? deploy_files[0][:destination] : deploy_args['files'])
136
- :environment => environment
137
- }
138
- columns.delete("Script") if deploy_settings[:script].nil?
139
- columns.delete("Post Script") if deploy_settings[:post_script].nil?
140
- columns.delete("Environment") if deploy_settings[:environment].nil?
141
- print_description_list(columns, deploy_settings)
142
- print reset, "\n"
148
+ unless options[:quiet]
149
+
150
+ print_h1 "Morpheus Deployment", options
151
+
152
+ columns = {
153
+ "Instance" => :name,
154
+ "Deployment" => :deployment,
155
+ "Version" => :version,
156
+ "Deploy Type" => :type,
157
+ "Script" => :script,
158
+ "Post Script" => :post_script,
159
+ "Files" => :files,
160
+ "Git Url" => :git_url,
161
+ "Git Ref" => :git_ref,
162
+ "Fetch Url" => :fetch_url,
163
+ "Environment" => :environment,
164
+ }
165
+ pretty_file_config = deploy_args['files'] ? deploy_args['files'].collect {|it|
166
+ [(it['path'] ? "path: #{it['path']}" : nil), (it['pattern'] ? "pattern: #{it['pattern']}" : nil)].compact.join(", ")
167
+ }.join(", ") : "(none)"
168
+ deploy_settings = {
169
+ :name => instance_name,
170
+ :deployment => deployment_name,
171
+ :version => version_number,
172
+ :script => deploy_args['script'],
173
+ :post_script => deploy_args['post_script'],
174
+ :files => pretty_file_config,
175
+ :type => format_deploy_type(deploy_type),
176
+ :git_url => deploy_args['gitUrl'] || (deploy_type == "git" ? deploy_args['url'] : nil),
177
+ :git_ref => deploy_args['gitRef'] || (deploy_type == "git" ? deploy_args['ref'] : nil),
178
+ :fetch_url => deploy_args['fetchUrl'] || (deploy_type == "fetch" ? deploy_args['url'] : nil),
179
+ # :files => deploy_args['files'],
180
+ # :files => deploy_files.size,
181
+ # :file_config => (deploy_files.size == 1 ? deploy_files[0][:destination] : deploy_args['files'])
182
+ :environment => environment
183
+ }
184
+ columns.delete("Script") if deploy_settings[:script].nil?
185
+ columns.delete("Post Script") if deploy_settings[:post_script].nil?
186
+ columns.delete("Environment") if deploy_settings[:environment].nil?
187
+ columns.delete("Files") if deploy_type != "file" && deploy_type != "files"
188
+ columns.delete("Git Url") if deploy_settings[:git_url].nil?
189
+ columns.delete("Git Ref") if deploy_settings[:git_ref].nil?
190
+ columns.delete("Fetch Url") if deploy_settings[:fetch_url].nil?
191
+ print_description_list(columns, deploy_settings)
192
+ print reset, "\n"
193
+
194
+ if deploy_config
195
+ print_h2 "Config Options", options
196
+ print cyan
197
+ puts as_json(deploy_config)
198
+ print "\n\n", reset
199
+ end
200
+
201
+ end # unless options[:quiet]
143
202
 
144
203
  if !deploy_args['script'].nil?
145
204
  # do this for dry run too since this is usually what creates the files to be uploaded
146
- print cyan, "Executing Pre Deploy Script...", reset, "\n"
147
- puts "running command: #{deploy_args['script']}"
205
+ unless options[:quiet]
206
+ print cyan, "Executing Pre Deploy Script...", reset, "\n"
207
+ puts "running command: #{deploy_args['script']}"
208
+ end
148
209
  if !system(deploy_args['script'])
149
210
  raise_command_error "Error executing pre script..."
150
211
  end
@@ -152,33 +213,71 @@ EOT
152
213
 
153
214
  # Find Files to Upload
154
215
  deploy_files = []
155
- if deploy_args['files'].nil? || deploy_args['files'].empty? || !deploy_args['files'].is_a?(Array)
156
- raise_command_error "Files not specified. Please specify files array, each item may specify a path or pattern of file(s) to upload"
157
- else
158
- #print "\n",cyan, "Finding Files...", reset, "\n"
159
- current_working_dir = Dir.pwd
160
- deploy_args['files'].each do |fmap|
161
- Dir.chdir(fmap['path'] || current_working_dir)
162
- files = Dir.glob(fmap['pattern'] || '**/*')
163
- files.each do |file|
164
- if File.file?(file)
165
- destination = file.split("/")[0..-2].join("/")
166
- # deploy_files << {filepath: File.expand_path(file), destination: destination}
167
- deploy_files << {filepath: File.expand_path(file), destination: file}
216
+ if deploy_type == "file" || deploy_type == "files"
217
+ if deploy_args['files'].nil? || deploy_args['files'].empty? || !deploy_args['files'].is_a?(Array)
218
+ raise_command_error "Files not specified. Please specify the files to include, each item may specify a path or pattern of file(s) to upload"
219
+ else
220
+ #print "\n",cyan, "Finding Files...", reset, "\n"
221
+ current_working_dir = Dir.pwd
222
+ deploy_args['files'].each do |fmap|
223
+ Dir.chdir(fmap['path'] || current_working_dir)
224
+ files = Dir.glob(fmap['pattern'] || '**/*')
225
+ files.each do |file|
226
+ if File.file?(file)
227
+ destination = file.split("/")[0..-2].join("/")
228
+ # deploy_files << {filepath: File.expand_path(file), destination: destination}
229
+ deploy_files << {filepath: File.expand_path(file), destination: file}
230
+ end
168
231
  end
169
232
  end
233
+ #print cyan, "Found #{deploy_files.size} Files to Upload!", reset, "\n"
234
+ Dir.chdir(current_working_dir)
170
235
  end
171
- #print cyan, "Found #{deploy_files.size} Files to Upload!", reset, "\n"
172
- Dir.chdir(current_working_dir)
173
- end
174
236
 
175
- if deploy_files.empty?
176
- raise_command_error "0 files found for: #{deploy_args['files'].inspect}"
177
- else
178
- print cyan, "Found #{deploy_files.size} Files to Upload!", reset, "\n"
237
+ if deploy_files.empty?
238
+ raise_command_error "0 files found for: #{deploy_args['files'].inspect}"
239
+ else
240
+ unless options[:quiet]
241
+ print cyan, "Found #{deploy_files.size} Files to Upload!", reset, "\n"
242
+ end
243
+ end
244
+ elsif deploy_type == "git"
245
+ # make it work with simpler config, url instead of gitUrl
246
+ if deploy_args['gitUrl'].nil? && deploy_args['url']
247
+ deploy_args['gitUrl'] = deploy_args['url'] # .delete('url') maybe?
248
+ end
249
+ if deploy_args['gitRef'].nil? && deploy_args['ref']
250
+ deploy_args['gitRef'] = deploy_args['ref'] # .delete('ref') maybe?
251
+ end
252
+ if deploy_args['gitRef'].nil?
253
+ raise_command_error "fetchUrl not specified. Please specify the git url to fetch the deploy files from."
254
+ end
255
+ if deploy_args['gitRef'].nil?
256
+ #raise_command_error "gitRef not specified. Please specify the git reference to use. eg. main"
257
+ # deploy_args['gitRef'] = "main"
258
+ end
259
+ elsif deploy_type == "git"
260
+ # make it work with simpler config, url instead of fetchUrl
261
+ if deploy_args['fetchUrl'].nil? && deploy_args['url']
262
+ deploy_args['fetchUrl'] = deploy_args['url'] # .delete('url') maybe?
263
+ end
264
+ if deploy_args['fetchUrl'].nil?
265
+ raise_command_error "fetchUrl not specified. Please specify the url to fetch the deploy files from."
266
+ end
267
+
179
268
  end
180
269
 
181
- unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to create deployment version #{version_number} (#{deploy_files.size} #{deploy_files.size == 1 ? 'file' : 'files'}) and deploy it to instance #{instance['name']}?")
270
+ confirm_warning = ""
271
+ confirm_message = "Are you sure you want to perform this action?"
272
+ if deploy_type == "file" || deploy_type == "files"
273
+ confirm_warning = "This will create deployment #{deployment_name} version #{version_number} and deploy it to instance #{instance['name']}."
274
+ elsif deploy_type == "git"
275
+ confirm_warning = "This will create deployment #{deployment_name} version #{version_number} and deploy it to instance #{instance['name']}."
276
+ elsif deploy_type == "fetch"
277
+ confirm_warning = "This will create deployment #{deployment_name} version #{version_number} and deploy it to instance #{instance['name']}."
278
+ end
279
+ puts confirm_warning if !options[:quiet]
280
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm(confirm_message)
182
281
  return 9, "aborted command"
183
282
  end
184
283
 
@@ -242,7 +341,7 @@ EOT
242
341
  payload = {
243
342
  'version' => {
244
343
  'userVersion' => version_number,
245
- 'deployType' => (deploy_args['type'] || deploy_args['deployType'] || 'file')
344
+ 'deployType' => deploy_type
246
345
  }
247
346
  }
248
347
  payload['version']['fetchUrl'] = deploy_args['fetchUrl'] if deploy_args['fetchUrl']
@@ -261,31 +360,31 @@ EOT
261
360
 
262
361
 
263
362
  # Upload Files
264
- if deploy_files && !deploy_files.empty?
265
- print "\n",cyan, "Uploading #{deploy_files.size} Files...", reset, "\n"
266
- current_working_dir = Dir.pwd
267
- deploy_files.each do |f|
268
- destination = f[:destination]
269
- if options[:dry_run]
270
- print_dry_run @deployments_interface.upload_file(deployment['id'], deployment_version['id'], f[:filepath], f[:destination])
271
- else
272
- print cyan," - Uploading #{f[:destination]} ...", reset if !options[:quiet]
273
- upload_result = @deployments_interface.upload_file(deployment['id'], deployment_version['id'], f[:filepath], f[:destination])
274
- #print green + "SUCCESS" + reset + "\n" if !options[:quiet]
275
- print reset, "\n" if !options[:quiet]
363
+ if deploy_type == "file" || deploy_type == "files"
364
+ if deploy_files && !deploy_files.empty?
365
+ print "\n",cyan, "Uploading #{deploy_files.size} Files...", reset, "\n" if !options[:quiet]
366
+ current_working_dir = Dir.pwd
367
+ deploy_files.each do |f|
368
+ destination = f[:destination]
369
+ if options[:dry_run]
370
+ print_dry_run @deployments_interface.upload_file(deployment['id'], deployment_version['id'], f[:filepath], f[:destination])
371
+ else
372
+ print cyan," - Uploading #{f[:destination]} ...", reset if !options[:quiet]
373
+ upload_result = @deployments_interface.upload_file(deployment['id'], deployment_version['id'], f[:filepath], f[:destination])
374
+ #print green + "SUCCESS" + reset + "\n" if !options[:quiet]
375
+ print reset, "\n" if !options[:quiet]
376
+ end
276
377
  end
378
+ print cyan, "Upload Complete!", reset, "\n" if !options[:quiet]
379
+ Dir.chdir(current_working_dir)
380
+ else
381
+ print "\n",cyan, "0 files to upload", reset, "\n" if !options[:quiet]
277
382
  end
278
- print cyan, "Upload Complete!", reset, "\n"
279
- Dir.chdir(current_working_dir)
280
- else
281
- print "\n",cyan, "0 files to upload", reset, "\n"
282
383
  end
283
384
 
284
- # TODO: support deploying other deployTypes too, git and fetch
285
-
286
385
  if !deploy_args['post_script'].nil?
287
- print cyan, "Executing Post Script...", reset, "\n"
288
- puts "running command: #{deploy_args['post_script']}"
386
+ print cyan, "Executing Post Script...", reset, "\n" if !options[:quiet]
387
+ puts "running command: #{deploy_args['post_script']}" if !options[:quiet]
289
388
  if !system(deploy_args['post_script'])
290
389
  raise_command_error "Error executing post script..."
291
390
  end
@@ -314,10 +413,14 @@ EOT
314
413
  end
315
414
  # stageOnly means do not actually deploy yet, can invoke @deploy_interface.deploy(deployment['id']) later
316
415
  # there is no cli command for that yet though..
317
- stage_only = deploy_args['stage_deploy'] || deploy_args['stage_only'] || deploy_args['stageOnly']
416
+ stage_only = deploy_args['stage'] || deploy_args['stage_deploy'] || deploy_args['stage_only'] || deploy_args['stageOnly']
318
417
  if stage_only
319
418
  payload['appDeploy']['stageOnly'] = true
320
419
  end
420
+ # config/options to apply to deployment
421
+ if deploy_config
422
+ payload['appDeploy']['config'] = deploy_config
423
+ end
321
424
  app_deploy_id = nil
322
425
  if options[:dry_run]
323
426
  print_dry_run @deploy_interface.dry.create(instance_id, payload)
@@ -325,11 +428,17 @@ EOT
325
428
  app_deploy_id = ':appDeployId'
326
429
  else
327
430
  # Create a new appDeploy record, without stageOnly, this actually does the deployment
328
- print cyan, "Deploying #{deployment_name} version #{version_number} to instance #{instance_name} ...", reset, "\n"
431
+ #print cyan, "Deploying #{deployment_name} version #{version_number} to instance #{instance_name} ...", reset, "\n"
329
432
  deploy_result = @deploy_interface.create(instance_id, payload)
330
433
  app_deploy = deploy_result['appDeploy']
331
434
  app_deploy_id = app_deploy['id']
332
- print_green_success "Deploy Successful!"
435
+ if !options[:quiet]
436
+ if app_deploy['status'] == 'staged'
437
+ print_green_success "Staged Deploy #{deployment_name} version #{version_number} to instance #{instance_name}"
438
+ else
439
+ print_green_success "Deploying #{deployment_name} version #{version_number} to instance #{instance_name}"
440
+ end
441
+ end
333
442
  end
334
443
  return 0, nil
335
444
  end
@@ -4,8 +4,11 @@ class Morpheus::Cli::Deployments
4
4
  include Morpheus::Cli::CliCommand
5
5
  include Morpheus::Cli::DeploymentsHelper
6
6
 
7
+ set_command_description "View and manage deployments, including versions and files."
8
+
7
9
  register_subcommands :list, :get, :add, :update, :remove
8
10
  register_subcommands :list_versions, :get_version, :add_version, :update_version, :remove_version
11
+ register_subcommands :list_files, :upload, :remove_file
9
12
  alias_subcommand :versions, :'list-versions'
10
13
 
11
14
  def initialize()
@@ -81,7 +84,7 @@ EOT
81
84
  if id.to_s =~ /\A\d{1,}\Z/
82
85
  id
83
86
  else
84
- deployment = find_deployment_by_name(id)
87
+ deployment = find_deployment_by_name_or_id(id)
85
88
  if deployment
86
89
  deployment['id']
87
90
  else
@@ -275,6 +278,9 @@ EOT
275
278
  connect(options)
276
279
  deployment = find_deployment_by_name_or_id(args[0])
277
280
  return 1 if deployment.nil?
281
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the deployment #{deployment['name']}?")
282
+ return 9, "aborted command"
283
+ end
278
284
  @deployments_interface.setopts(options)
279
285
  if options[:dry_run]
280
286
  print_dry_run @deployments_interface.dry.destroy(deployment['id'], params)
@@ -292,6 +298,9 @@ EOT
292
298
  params = {}
293
299
  optparse = Morpheus::Cli::OptionParser.new do |opts|
294
300
  opts.banner = subcommand_usage("[deployment] [version] [options]")
301
+ opts.on(nil, '--no-files', "Do not show files") do
302
+ options[:no_files] = true
303
+ end
295
304
  build_option_type_options(opts, options, add_deployment_version_option_types)
296
305
  build_option_type_options(opts, options, add_deployment_version_advanced_option_types)
297
306
  build_standard_add_options(opts, options)
@@ -311,7 +320,7 @@ EOT
311
320
  if id.to_s =~ /\A\d{1,}\Z/
312
321
  id = id.to_i
313
322
  else
314
- deployment_version = find_deployment_version_by_name(deployment['id'], id)
323
+ deployment_version = find_deployment_version_by_name_or_id(deployment['id'], id)
315
324
  if deployment_version
316
325
  id = deployment_version['id']
317
326
  else
@@ -326,6 +335,13 @@ EOT
326
335
  end
327
336
  json_response = @deployments_interface.get_version(deployment['id'], id, params)
328
337
  deployment_version = json_response['version']
338
+ deploy_type = deployment_version['deployType'] || deployment_version['type']
339
+ deployment_files_response = nil
340
+ deployment_files = nil
341
+ if options[:no_files] != true
342
+ deployment_files_response = @deployments_interface.list_files(deployment['id'], deployment_version['id'], params)
343
+ deployment_files = deployment_files_response.is_a?(Array) ? deployment_files_response : deployment_files_response['files']
344
+ end
329
345
  render_response(json_response, options, 'version') do
330
346
  # print_h1 "Deployment Version Details", [deployment['name']], options
331
347
  print_h1 "Deployment Version Details", [], options
@@ -334,26 +350,17 @@ EOT
334
350
  columns = {
335
351
  "ID" => 'id',
336
352
  "Deployment" => lambda {|it| deployment['name'] },
337
- "Version" => 'userVersion',
353
+ "Version" => lambda {|it| format_deployment_version_number(it) },
338
354
  "Deploy Type" => lambda {|it| it['deployType'] },
339
- "URL" => lambda {|it|
340
- if it['deployType'] == 'fetch'
341
- "#{it['fetchUrl']}"
342
- elsif it['deployType'] == 'git'
343
- "#{it['gitUrl']}"
344
- end
345
- },
346
- "Ref" => lambda {|it|
347
- if it['deployType'] == 'git'
348
- "#{it['gitRef']}"
349
- end
350
- },
355
+ "URL" => lambda {|it| it['fetchUrl'] || it['gitUrl'] || it['url'] },
356
+ "Ref" => lambda {|it| it['gitRef'] || it['ref'] },
351
357
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
352
358
  "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
353
359
  }
354
360
  if deployment_version['deployType'] == 'git'
355
- columns['Git URL'] = columns['URL']
361
+ options[:no_files] = true
356
362
  elsif deployment_version['deployType'] == 'fetch'
363
+ options[:no_files] = true
357
364
  columns['Fetch URL'] = columns['URL']
358
365
  columns.delete('Ref')
359
366
  else
@@ -362,6 +369,18 @@ EOT
362
369
  end
363
370
  print_description_list(columns, deployment_version)
364
371
  print reset,"\n"
372
+
373
+ if options[:no_files] != true
374
+ print_h2 "Deployment Files", options
375
+ if !deployment_files || deployment_files.empty?
376
+ print cyan,"No files found.",reset,"\n"
377
+ else
378
+ print as_pretty_table(deployment_files, deployment_file_column_definitions.upcase_keys!, options)
379
+ print_results_pagination({size:deployment_files.size,total:deployment_files.size.to_i})
380
+ end
381
+ print reset,"\n"
382
+ end
383
+
365
384
  end
366
385
  return 0, nil
367
386
  end
@@ -371,7 +390,11 @@ EOT
371
390
  params = {}
372
391
  optparse = Morpheus::Cli::OptionParser.new do |opts|
373
392
  opts.banner = subcommand_usage("[deployment] [version] [options]")
393
+ opts.on('-t', '--type CODE', String, "Deploy Type, file, git or fetch, default is file.") do |val|
394
+ options[:options]['deployType'] = val
395
+ end
374
396
  build_option_type_options(opts, options, add_deployment_version_option_types)
397
+ opts.add_hidden_option('--deployType')
375
398
  build_option_type_options(opts, options, add_deployment_version_advanced_option_types)
376
399
  build_standard_add_options(opts, options)
377
400
  opts.footer = <<-EOT
@@ -485,28 +508,304 @@ EOT
485
508
  verify_args!(args:args, optparse:optparse, count:2)
486
509
  connect(options)
487
510
  deployment = find_deployment_by_name_or_id(args[0])
488
- return 1 if deployment.nil?
511
+ return 1, "deployment not found" if deployment.nil?
489
512
  id = args[1]
513
+ deployment_version = find_deployment_version_by_name_or_id(deployment['id'], id)
514
+ return 1, "version not found" if deployment_version.nil?
515
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the deployment version #{format_deployment_version_number(deployment_version)}?")
516
+ return 9, "aborted command"
517
+ end
518
+ @deployments_interface.setopts(options)
519
+ if options[:dry_run]
520
+ print_dry_run @deployments_interface.dry.destroy_version(deployment['id'], deployment_version['id'], params)
521
+ return
522
+ end
523
+ json_response = @deployments_interface.destroy_version(deployment['id'], deployment_version['id'], params)
524
+ render_response(json_response, options) do
525
+ print_green_success "Removed deployment #{deployment['name']} version #{format_deployment_version_number(deployment_version)}"
526
+ end
527
+ return 0, nil
528
+ end
490
529
 
491
- if id.to_s =~ /\A\d{1,}\Z/
492
- id = id.to_i
530
+ def list_files(args)
531
+ options = {}
532
+ params = {}
533
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
534
+ opts.banner = subcommand_usage("[deployment] [version] [path] [options]")
535
+ build_standard_list_options(opts, options)
536
+ opts.footer = <<-EOT
537
+ List files in a deployment version.
538
+ [deployment] is required. This is the name or id of a deployment.
539
+ [version] is required. This is the deployment version identifier
540
+ [path] is optional. This is a the directory to search for files under.
541
+ EOT
542
+ end
543
+ optparse.parse!(args)
544
+ verify_args!(args:args, optparse:optparse, min:2, max: 3)
545
+ connect(options)
546
+ params.merge!(parse_list_options(options))
547
+ deployment = find_deployment_by_name_or_id(args[0])
548
+ return 1, "deployment not found for '#{args[0]}'" if deployment.nil?
549
+ deployment_version = find_deployment_version_by_name_or_id(deployment['id'], args[1])
550
+ return 1, "deployment version not found for '#{args[1]}'" if deployment_version.nil?
551
+ if args[2]
552
+ params['filePath'] = args[2]
553
+ end
554
+ @deployments_interface.setopts(options)
555
+ if options[:dry_run]
556
+ print_dry_run @deployments_interface.dry.list_files(deployment['id'], deployment_version['id'], params)
557
+ return
558
+ end
559
+ json_response = @deployments_interface.list_files(deployment['id'], deployment_version['id'], params)
560
+ # odd, api used to just return an array
561
+ deployment_files = json_response.is_a?(Array) ? json_response : json_response['files']
562
+ render_response(json_response, options) do
563
+ print_h1 "Deployment Files", ["#{deployment['name']} #{format_deployment_version_number(deployment_version)}"]
564
+ if !deployment_files || deployment_files.empty?
565
+ print cyan,"No files found.",reset,"\n"
566
+ else
567
+ print as_pretty_table(deployment_files, deployment_file_column_definitions.upcase_keys!, options)
568
+ #print_results_pagination(json_response)
569
+ print_results_pagination({size:deployment_files.size,total:deployment_files.size.to_i})
570
+ end
571
+ print reset,"\n"
572
+ end
573
+ return 0, nil
574
+ end
575
+
576
+ def upload(args)
577
+ options = {}
578
+ params = {}
579
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
580
+ opts.banner = subcommand_usage("[deployment] [version] [files]")
581
+ opts.on('--files LIST', String, "Files to upload") do |val|
582
+ val_list = val.to_s.split(",").collect {|it| it.to_s.strip }.select { |it| it != "" }
583
+ options[:files] ||= []
584
+ options[:files] += val_list
585
+ end
586
+ opts.on('--workdir DIRECTORY', String, "Working directory to switch to before uploading files, determines the paths of the uploaded files. The current working directory of your terminal is used by default.") do |val|
587
+ options[:workdir] = File.expand_path(val)
588
+ if !File.directory?(options[:workdir])
589
+ raise_command_error "invalid directory: #{val}"
590
+ end
591
+ end
592
+ opts.on('--destination FILEPATH', String, "Destination filepath for file being uploaded, should include full filename and extension. Only applies when uploading a single file.") do |val|
593
+ options[:destination] = val
594
+ end
595
+ build_standard_update_options(opts, options, [:auto_confirm])
596
+ opts.footer = <<-EOT
597
+ Upload one or more files or directories to a deployment version.
598
+ [deployment] is required. This is the name or id of a deployment.
599
+ [version] is required. This is the deployment version identifier
600
+ [files] is required. This is a list of files or directories to be uploaded. Glob pattern format supported eg. build/*.html
601
+ EOT
602
+ end
603
+ optparse.parse!(args)
604
+ # verify_args!(args:args, optparse:optparse, min:0, max:2)
605
+ connect(options)
606
+
607
+ # fetch deployment
608
+ deployment = nil
609
+ if args[0]
610
+ deployment = find_deployment_by_name_or_id(args[0])
611
+ return 1 if deployment.nil?
493
612
  else
494
- deployment_version = find_deployment_version_by_name(deployment['id'], id)
495
- if deployment_version
496
- id = deployment_version['id']
613
+ all_deployments = @deployments_interface.list(max:10000)['deployments']
614
+ deployment_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'deployment', 'fieldLabel' => 'Deployment', 'type' => 'select', 'required' => true, 'description' => 'Deployment identifier (name or ID)', 'optionSource' => lambda { |api_client, api_params|
615
+ all_deployments.collect {|it| {'name' => it['name'], 'value' => it['id']} }
616
+ }}], options[:options])['deployment']
617
+ deployment = all_deployments.find {|it| deployment_id == it['id'] || deployment_id == it['name'] }
618
+ raise_command_error "Deployment not found for '#{deployment_id}'" if deployment.nil?
619
+ end
620
+
621
+ # fetch deployment version
622
+ deployment_version = nil
623
+ if args[1]
624
+ deployment_version = find_deployment_version_by_name_or_id(deployment['id'], args[1])
625
+ return 1 if deployment_version.nil?
626
+ else
627
+ all_deployment_versions = @deployments_interface.list_versions(deployment['id'], {max:10000})['versions']
628
+ deployment_version_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'version', 'fieldLabel' => 'Version', 'type' => 'select', 'required' => true, 'description' => 'Deployment Version identifier (version or ID) to upload files to', 'optionSource' => lambda { |api_client, api_params|
629
+ all_deployment_versions.collect {|it| {'name' => it['version'] || it['userVersion'], 'value' => it['id']} }
630
+ }}], options[:options])['version']
631
+ deployment_version = all_deployment_versions.find {|it| deployment_version_id == it['id'] || deployment_version_id == it['userVersion'] || deployment_version_id == it['version'] }
632
+ raise_command_error "Deployment Version not found for '#{deployment_version_id}'" if deployment_version.nil?
633
+ end
634
+
635
+
636
+ # Determine which files to find
637
+ file_patterns = []
638
+ # [files] is args 3 - N
639
+ if args.size > 2
640
+ file_patterns += args[2..-1]
641
+ end
642
+ if options[:files]
643
+ file_patterns += options[:files]
644
+ end
645
+ if file_patterns.empty?
646
+ #raise_command_error "Files not specified. Please specify files array, each item may specify a path or pattern of file(s) to upload", args, optparse
647
+ file_patterns = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'files', 'fieldLabel' => 'Files', 'type' => 'text', 'required' => true, 'description' => 'Files or directories to upload'}], options[:options])['files'].to_s.split(",").collect {|it| it.to_s.strip }.select { |it| it != "" }
648
+ end
649
+
650
+ # Find Files to Upload
651
+ deploy_files = []
652
+
653
+ #print "\n",cyan, "Finding Files...", reset, "\n" unless options[:quiet]
654
+ original_working_dir = Dir.pwd
655
+ base_working_dir = options[:workdir] || original_working_dir
656
+ begin
657
+ file_patterns.each do |file_pattern|
658
+ # start in the working directory
659
+ # to preserve relative paths in upload file destinations
660
+ # allow passing just build instead build/**/*
661
+ Dir.chdir(base_working_dir)
662
+ fmap = nil
663
+ full_file_pattern = File.expand_path(file_pattern)
664
+ if File.exists?(full_file_pattern)
665
+ if File.directory?(full_file_pattern)
666
+ fmap = {'path' => full_file_pattern, 'pattern' => '**/*'}
667
+ else
668
+ fmap = {'path' => File.dirname(full_file_pattern), 'pattern' => File.basename(full_file_pattern)}
669
+ end
670
+ else
671
+ fmap = {'path' => nil, 'pattern' => file_pattern}
672
+ end
673
+ if fmap['path']
674
+ Dir.chdir(File.expand_path(fmap['path']))
675
+ end
676
+ files = Dir.glob(fmap['pattern'] || '**/*')
677
+ if files.empty?
678
+ raise_command_error "Found 0 files for file pattern '#{file_pattern}'"
679
+ end
680
+ files.each do |file|
681
+ if File.file?(file)
682
+ destination = file.split("/")[0..-2].join("/")
683
+ # deploy_files << {filepath: File.expand_path(file), destination: destination}
684
+ # absolute path was given, so no path is given to the destination file
685
+ # maybe apply options[:destination] as prefix here
686
+ # actually just do destination.sub!(base_working_dir, '')
687
+ if file[0].chr == "/"
688
+ deploy_files << {filepath: File.expand_path(file), destination: File.basename(file)}
689
+ else
690
+ deploy_files << {filepath: File.expand_path(file), destination: file}
691
+ end
692
+ end
693
+ end
694
+ end
695
+ #print cyan, "Found #{deploy_files.size} Files to Upload!", reset, "\n"
696
+ rescue => ex
697
+ # does not happen, just in case
698
+ #print_error "An error occured while searching for files to upload: #{ex}"
699
+ raise ex
700
+ ensure
701
+ Dir.chdir(original_working_dir)
702
+ end
703
+
704
+ # make sure we have something to upload.
705
+ if deploy_files.empty?
706
+ raise_command_error "0 files found for: #{file_patterns.join(', ')}"
707
+ else
708
+ unless options[:quiet]
709
+ print cyan, "Found #{deploy_files.size} Files to Upload!", reset, "\n"
710
+ end
711
+ end
712
+
713
+ # support uploading a local file to a custom destination
714
+ # this only works for a single file right now, should be better
715
+ # could try to add destination + filename
716
+ # for now expect filename to be included in destination
717
+ if options[:destination]
718
+ if deploy_files.size == 1
719
+ deploy_files[0][:destination] = options[:destination]
497
720
  else
498
- # raise_command_error "deployment not found for '#{id}'"
499
- return 1, "deployment version not found for '#{id}'"
721
+ raise_command_error "--destination can only specified for a single file upload, not #{deploy_files} files.", args, optparse
500
722
  end
501
723
  end
724
+
725
+ confirm_message = "Are you sure you want to upload #{deploy_files.size} files to deployment #{deployment['name']} #{format_deployment_version_number(deployment_version)}?"
726
+ if deploy_files.size == 1
727
+ confirm_message = "Are you sure you want to upload file #{deploy_files[0][:destination]} to deployment #{deployment['name']} #{format_deployment_version_number(deployment_version)}?"
728
+ end
729
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm(confirm_message)
730
+ return 9, "aborted command"
731
+ end
732
+
733
+ @deployments_interface.setopts(options)
734
+
735
+ # Upload Files
736
+ if deploy_files && !deploy_files.empty?
737
+ print "\n",cyan, "Uploading #{deploy_files.size} Files...", reset, "\n" if !options[:quiet]
738
+ deploy_files.each do |f|
739
+ destination = f[:destination]
740
+ if options[:dry_run]
741
+ print_dry_run @deployments_interface.upload_file(deployment['id'], deployment_version['id'], f[:filepath], f[:destination])
742
+ else
743
+ print cyan," - Uploading #{f[:destination]} ...", reset if !options[:quiet]
744
+ upload_result = @deployments_interface.upload_file(deployment['id'], deployment_version['id'], f[:filepath], f[:destination])
745
+ #print green + "SUCCESS" + reset + "\n" if !options[:quiet]
746
+ print reset, "\n" if !options[:quiet]
747
+ end
748
+ end
749
+ if options[:dry_run]
750
+ return 0, nil
751
+ end
752
+ #print cyan, "Upload Complete!", reset, "\n" if !options[:quiet]
753
+ if options[:quiet]
754
+ return 0, nil
755
+ else
756
+ print_green_success "Upload Complete!"
757
+ return get_version([deployment["id"], deployment_version['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
758
+ end
759
+ else
760
+ raise_command_error "No files to upload!"
761
+ end
762
+ end
763
+
764
+ def remove_file(args)
765
+ options = {}
766
+ params = {}
767
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
768
+ opts.banner = subcommand_usage("[deployment] [version] [file] [options]")
769
+ opts.on( '-R', '--recursive', "Delete a directory and all of its files. This must be passed if specifying a directory." ) do
770
+ # do_recursive = true
771
+ params['force'] = true
772
+ end
773
+ opts.on( '-f', '--force', "Force delete, this will do a recursive delete of directories." ) do
774
+ params['force'] = true
775
+ end
776
+ build_standard_remove_options(opts, options)
777
+ opts.footer = <<-EOT
778
+ Delete a deployment file.
779
+ [deployment] is required. This is the name or id of a deployment.
780
+ [version] is required. This is the version identifier of a deployment version.
781
+ [file] is required. This is the name of the file to be deleted.
782
+ EOT
783
+ end
784
+ optparse.parse!(args)
785
+ verify_args!(args:args, optparse:optparse, min:2, max:3)
786
+ connect(options)
787
+ deployment = find_deployment_by_name_or_id(args[0])
788
+ return 1, "deployment not found" if deployment.nil?
789
+ id = args[1]
790
+ deployment_version = find_deployment_version_by_name_or_id(deployment['id'], id)
791
+ return 1, "version not found" if deployment_version.nil?
792
+ # could look it up here, or allow a directory instead of a single file
793
+ filename = args[2]
794
+ if filename.nil?
795
+ #raise_command_error "Files not specified. Please specify files array, each item may specify a path or pattern of file(s) to upload", args, optparse
796
+ filename = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'file', 'fieldLabel' => 'Files', 'type' => 'text', 'required' => true, 'description' => 'Files or directories to upload'}], options[:options])['file'].to_s #.split(",").collect {|it| it.to_s.strip }.select { |it| it != "" }
797
+ end
798
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the file #{filename}?")
799
+ return 9, "aborted command"
800
+ end
502
801
  @deployments_interface.setopts(options)
503
802
  if options[:dry_run]
504
- print_dry_run @deployments_interface.dry.destroy(deployment['id'], params)
803
+ print_dry_run @deployments_interface.dry.destroy_file(deployment['id'], deployment_version['id'], filename, params)
505
804
  return
506
805
  end
507
- json_response = @deployments_interface.destroy(deployment['id'], params)
806
+ json_response = @deployments_interface.destroy_file(deployment['id'], deployment_version['id'], filename, params)
508
807
  render_response(json_response, options) do
509
- print_green_success "Removed deployment version #{deployment_version['userVersion']}"
808
+ print_green_success "Removed deployment file #{filename}"
510
809
  end
511
810
  return 0, nil
512
811
  end
@@ -557,7 +856,7 @@ EOT
557
856
  def deployment_version_column_definitions
558
857
  {
559
858
  "ID" => 'id',
560
- "Version" => 'userVersion',
859
+ "Version" => lambda {|it| format_deployment_version_number(it) },
561
860
  "Deploy Type" => lambda {|it| it['deployType'] },
562
861
  "URL" => lambda {|it|
563
862
  if it['deployType'] == 'fetch'
@@ -607,4 +906,18 @@ EOT
607
906
  }
608
907
  end
609
908
 
909
+ # Deployment Files
910
+
911
+ def deployment_file_column_definitions
912
+ {
913
+ #"ID" => 'id',
914
+ "Name" => 'name',
915
+ "Type" => lambda {|it| (it['directory'] || it['isDirectory']) ? "directory" : (it["contentType"] || "file") },
916
+ "Size" => lambda {|it| (it['directory'] || it['isDirectory']) ? "" : format_bytes_short(it['contentLength']) },
917
+ #"Content Type" => lambda {|it| it['contentType'] },
918
+ # "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
919
+ # "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
920
+ }
921
+ end
922
+
610
923
  end