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.
@@ -27,7 +27,7 @@ Examples:
27
27
  set-prompt "%cyanmorpheus> "
28
28
  set-prompt "[%magenta%remote%reset] %cyan%username morpheus> "
29
29
 
30
- The default prompt is: "%cyanmorpheus> "
30
+ The default prompt is: "%cyanmorpheus> %reset"
31
31
 
32
32
  EOT
33
33
  end
@@ -80,8 +80,8 @@ class Morpheus::Cli::Shell
80
80
  end
81
81
  # cleanup empty brackets caused by var value
82
82
  @calculated_prompt = @calculated_prompt.gsub("[]", "").gsub("<>", "").gsub("{}", "")
83
- @calculated_prompt = @calculated_prompt.strip
84
- @calculated_prompt = "#{@calculated_prompt}#{reset} "
83
+ #@calculated_prompt = @calculated_prompt.strip
84
+ # @calculated_prompt = "#{@calculated_prompt}#{reset} "
85
85
  @calculated_prompt
86
86
  end
87
87
 
@@ -211,6 +211,7 @@ class Morpheus::Cli::Shell
211
211
  input = input.strip
212
212
 
213
213
  result = execute(input)
214
+ print reset
214
215
  end
215
216
  end
216
217
 
@@ -12,7 +12,17 @@ class Morpheus::Cli::StorageProvidersCommand
12
12
  set_command_name :'storage-providers'
13
13
 
14
14
  register_subcommands :list, :get, :add, :update, :remove
15
-
15
+ # file commands
16
+ register_subcommands :'list-files' => :list_files
17
+ register_subcommands :'ls' => :ls
18
+ #register_subcommands :'file' => :get_file
19
+ # register_subcommands :'history' => :file_history
20
+ register_subcommands :'upload' => :upload_file
21
+ register_subcommands :'download' => :download_file
22
+ register_subcommands :'read' => :read_file
23
+ register_subcommands :'remove-file' => :remove_file
24
+ register_subcommands :'rm' => :remove_file
25
+
16
26
  # set_default_subcommand :list
17
27
 
18
28
  def initialize()
@@ -39,6 +49,11 @@ class Morpheus::Cli::StorageProvidersCommand
39
49
  opts.footer = "List storage providers."
40
50
  end
41
51
  optparse.parse!(args)
52
+ if args.count != 0
53
+ print_error Morpheus::Terminal.angry_prompt
54
+ puts_error "wrong number of arguments, expected 0 and got #{args.count}\n#{optparse}"
55
+ return 1
56
+ end
42
57
  connect(options)
43
58
  begin
44
59
  params.merge!(parse_list_options(options))
@@ -158,6 +173,7 @@ class Morpheus::Cli::StorageProvidersCommand
158
173
  def add(args)
159
174
  options = {}
160
175
  ip_range_list = nil
176
+ create_bucket = nil
161
177
  optparse = Morpheus::Cli::OptionParser.new do |opts|
162
178
  opts.banner = subcommand_usage()
163
179
  opts.on('--name VALUE', String, "Name for this storage provider") do |val|
@@ -181,6 +197,9 @@ class Morpheus::Cli::StorageProvidersCommand
181
197
  opts.on('--copy-to-store [on|off]', String, "Archive Snapshots") do |val|
182
198
  options['copyToStore'] = val.to_s == 'on' || val.to_s == 'true'
183
199
  end
200
+ #opts.on('--create-bucket [on|off]', String, "Create Bucket") do |val|
201
+ # create_bucket = val.to_s == 'on' || val.to_s == 'true' || val.nil?
202
+ #end
184
203
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
185
204
  opts.footer = "Create a new storage provider." + "\n" +
186
205
  "[name] is required and can be passed as --name instead."
@@ -244,14 +263,17 @@ class Morpheus::Cli::StorageProvidersCommand
244
263
  {'fieldContext' => 'config', 'fieldName' => 'accessKey', 'fieldLabel' => 'Access Key', 'type' => 'text', 'required' => true, 'description' => ''},
245
264
  {'fieldContext' => 'config', 'fieldName' => 'secretKey', 'fieldLabel' => 'Secret Key', 'type' => 'password', 'required' => true, 'description' => ''},
246
265
  {'fieldName' => 'bucketName', 'fieldLabel' => 'Bucket Name', 'type' => 'text', 'required' => true, 'description' => ''},
247
- {'fieldContext' => 'config', 'fieldName' => 'endpoint', 'fieldLabel' => 'Endpoint URL', 'type' => 'text', 'required' => false, 'description' => 'Optional endpoint URL if pointing to an object store other than amazon that mimics the Amazon S3 APIs.'},
266
+ {'fieldName' => 'createBucket', 'fieldLabel' => 'Create Bucket', 'type' => 'checkbox', 'required' => false, 'defaultValue' => false, 'description' => 'Create the bucket if it does not exist.'},
267
+ {'fieldContext' => 'config', 'fieldName' => 'region', 'fieldLabel' => 'Region', 'type' => 'text', 'required' => false, 'description' => 'Optional Amazon region if creating a new bucket.'},
268
+ {'fieldContext' => 'config', 'fieldName' => 'endpoint', 'fieldLabel' => 'Endpoint URL', 'type' => 'text', 'required' => false, 'description' => 'Optional endpoint URL if pointing to an object store other than amazon that mimics the Amazon S3 APIs.'}
248
269
  ]
249
270
  elsif storage_provider_type_code == 'azure'
250
271
  # print_h2 "Azure Options"
251
272
  provider_type_option_types = [
252
273
  {'fieldContext' => 'config', 'fieldName' => 'storageAccount', 'fieldLabel' => 'Storage Account', 'type' => 'text', 'required' => true, 'description' => ''},
253
274
  {'fieldContext' => 'config', 'fieldName' => 'storageKey', 'fieldLabel' => 'Storage Key', 'type' => 'password', 'required' => true, 'description' => ''},
254
- {'fieldName' => 'bucketName', 'fieldLabel' => 'Bucket Name', 'type' => 'text', 'required' => true, 'description' => ''}
275
+ {'fieldName' => 'bucketName', 'fieldLabel' => 'Bucket Name', 'type' => 'text', 'required' => true, 'description' => ''},
276
+ {'fieldName' => 'createBucket', 'fieldLabel' => 'Create Bucket', 'type' => 'checkbox', 'required' => false, 'defaultValue' => false, 'description' => 'Create the bucket if it does not exist.'},
255
277
  ]
256
278
  elsif storage_provider_type_code == 'cifs'
257
279
  # print_h2 "CIFS Options"
@@ -281,6 +303,7 @@ class Morpheus::Cli::StorageProvidersCommand
281
303
  {'fieldContext' => 'config', 'fieldName' => 'apiKey', 'fieldLabel' => 'API Key', 'type' => 'password', 'required' => true, 'description' => ''},
282
304
  {'fieldContext' => 'config', 'fieldName' => 'region', 'fieldLabel' => 'Region', 'type' => 'text', 'required' => true, 'description' => ''},
283
305
  {'fieldName' => 'bucketName', 'fieldLabel' => 'Bucket Name', 'type' => 'text', 'required' => true, 'description' => ''},
306
+ {'fieldName' => 'createBucket', 'fieldLabel' => 'Create Bucket', 'type' => 'checkbox', 'required' => false, 'defaultValue' => false, 'description' => 'Create the bucket if it does not exist.'},
284
307
  {'fieldContext' => 'config', 'fieldName' => 'identityUrl', 'fieldLabel' => 'Identity URL', 'type' => 'text', 'required' => true, 'description' => ''},
285
308
  ]
286
309
  elsif storage_provider_type_code == 'rackspace'
@@ -289,6 +312,7 @@ class Morpheus::Cli::StorageProvidersCommand
289
312
  {'fieldContext' => 'config', 'fieldName' => 'accessKey', 'fieldLabel' => 'Access Key', 'type' => 'text', 'required' => true, 'description' => ''},
290
313
  {'fieldContext' => 'config', 'fieldName' => 'secretKey', 'fieldLabel' => 'Secret Key', 'type' => 'password', 'required' => true, 'description' => ''},
291
314
  {'fieldName' => 'bucketName', 'fieldLabel' => 'Bucket Name', 'type' => 'text', 'required' => true, 'description' => ''},
315
+ {'fieldName' => 'createBucket', 'fieldLabel' => 'Create Bucket', 'type' => 'checkbox', 'required' => false, 'defaultValue' => false, 'description' => 'Create the bucket if it does not exist.'},
292
316
  {'fieldContext' => 'config', 'fieldName' => 'endpoint', 'fieldLabel' => 'Endpoint URL', 'type' => 'text', 'required' => true, 'description' => 'Optional endpoint URL if pointing to an object store other than amazon that mimics the Amazon S3 APIs.'},
293
317
  ]
294
318
  else
@@ -330,7 +354,12 @@ class Morpheus::Cli::StorageProvidersCommand
330
354
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'defaultVirtualImageTarget', 'fieldLabel' => 'Default Virtual Image Store', 'type' => 'checkbox', 'required' => false, 'description' => '', 'defaultValue' => 'off'}], options)
331
355
  payload['storageProvider']['defaultVirtualImageTarget'] = (v_prompt['defaultVirtualImageTarget'].to_s == 'on') unless v_prompt['defaultVirtualImageTarget'].nil?
332
356
  end
333
-
357
+ #if create_bucket
358
+ # payload['createBucket'] = true
359
+ #end
360
+ if payload['storageProvider']['createBucket'] == 'on'
361
+ payload['storageProvider']['createBucket'] = true
362
+ end
334
363
  end
335
364
 
336
365
 
@@ -440,6 +469,9 @@ class Morpheus::Cli::StorageProvidersCommand
440
469
  payload['storageProvider']['defaultVirtualImageTarget'] = options['defaultVirtualImageTarget']
441
470
  end
442
471
 
472
+ if payload['storageProvider']['createBucket'] == 'on'
473
+ payload['storageProvider']['createBucket'] = true
474
+ end
443
475
  end
444
476
 
445
477
  if options[:dry_run]
@@ -465,7 +497,7 @@ class Morpheus::Cli::StorageProvidersCommand
465
497
  options = {}
466
498
  optparse = Morpheus::Cli::OptionParser.new do |opts|
467
499
  opts.banner = subcommand_usage("[storage-provider]")
468
- build_common_options(opts, options, [:account, :auto_confirm, :json, :dry_run, :remote])
500
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
469
501
  opts.footer = "Delete a storage provider." + "\n" +
470
502
  "[storage-provider] is required. This is the name or id of a storage provider."
471
503
  end
@@ -504,6 +536,686 @@ class Morpheus::Cli::StorageProvidersCommand
504
536
  end
505
537
  end
506
538
 
539
+ def list_files(args)
540
+ options = {}
541
+ params = {}
542
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
543
+ opts.banner = subcommand_usage("[provider:/path]")
544
+ opts.on('-a', '--all', "Show all files, including subdirectories under the /path.") do
545
+ params[:fullTree] = true
546
+ end
547
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run])
548
+ opts.footer = "List files in a storage provider. \nInclude [/path] to show files under a directory."
549
+ end
550
+ optparse.parse!(args)
551
+ if args.count < 1 || args.count > 2
552
+ print_error Morpheus::Terminal.angry_prompt
553
+ puts_error "#{command_name} list-files expects 1-2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
554
+ return 1
555
+ end
556
+ storage_provider_id, search_file_path = parse_storage_provider_id_and_file_path(args[0], args[1])
557
+ connect(options)
558
+ begin
559
+ storage_provider = find_storage_provider_by_name_or_id(storage_provider_id)
560
+ return 1 if storage_provider.nil?
561
+ params.merge!(parse_list_options(options))
562
+ [:fullTree].each do |k|
563
+ params[k] = options[k] unless options[k].nil?
564
+ end
565
+ if options[:dry_run]
566
+ print_dry_run @storage_providers_interface.dry.list_files(storage_provider['id'], search_file_path, params)
567
+ return
568
+ end
569
+ json_response = @storage_providers_interface.list_files(storage_provider['id'], search_file_path, params)
570
+ storage_files = json_response['storageFiles']
571
+ # storage_provider = json_response['storageProvider']
572
+ if options[:json]
573
+ print JSON.pretty_generate(json_response)
574
+ return
575
+ end
576
+ if options[:json]
577
+ puts as_json(json_response, options, "storageFiles")
578
+ return 0
579
+ elsif options[:yaml]
580
+ puts as_yaml(json_response, options, "storageFiles")
581
+ return 0
582
+ elsif options[:csv]
583
+ puts records_as_csv(json_response['storageFiles'], options)
584
+ return 0
585
+ end
586
+ print_h1 "Storage Files", ["#{storage_provider['name']}:#{search_file_path}"]
587
+ print cyan
588
+ description_cols = {
589
+ "ID" => 'id',
590
+ "Name" => 'name',
591
+ # "Bucket Name" => 'bucketName',
592
+ #"Path" => lambda {|it| search_file_path }
593
+ }
594
+ #print_description_list(description_cols, storage_provider)
595
+ #print "\n"
596
+ #print_h2 "Path: #{search_file_path}"
597
+ # print "Directory: #{search_file_path}"
598
+ if storage_files && storage_files.size > 0
599
+ print_storage_files_table(storage_files, {fullTree: params[:fullTree]})
600
+ #print_results_pagination(json_response, {:label => "file", :n_label => "files"})
601
+ print reset, "\n"
602
+ return 0
603
+ else
604
+ # puts "No files found for path #{search_file_path}"
605
+ if search_file_path.empty? || search_file_path == "/"
606
+ puts "This storage provider has no files."
607
+ print reset,"\n"
608
+ return 0
609
+ else
610
+ puts "No files found for path #{search_file_path}"
611
+ print reset,"\n"
612
+ return 1
613
+ end
614
+ end
615
+ rescue RestClient::Exception => e
616
+ print_rest_exception(e, options)
617
+ return 1
618
+ end
619
+ end
620
+
621
+ def ls(args)
622
+ options = {}
623
+ params = {}
624
+ do_one_file_per_line = false
625
+ do_long_format = false
626
+ do_human_bytes = false
627
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
628
+ opts.banner = subcommand_usage("[provider/path]")
629
+ opts.on('-a', '--all', "Show all files, including subdirectories under the /path.") do
630
+ params[:fullTree] = true
631
+ do_one_file_per_line = true
632
+ end
633
+ opts.on('-l', '--long', "Lists files in the long format, which contains lots of useful information, e.g. the exact size of the file, the file type, and when it was last modified.") do
634
+ do_long_format = true
635
+ do_one_file_per_line
636
+ end
637
+ opts.on('-H', '--human', "Humanized file sizes. The default is just the number of bytes.") do
638
+ do_human_bytes = true
639
+ end
640
+ opts.on('-1', '--oneline', "One file per line. The default delimiter is a single space.") do
641
+ do_one_file_per_line = true
642
+ end
643
+ build_common_options(opts, options, [:list, :json, :fields, :dry_run])
644
+ opts.footer = "Print filenames for a given location.\nPass storage location in the format provider/path."
645
+ end
646
+ optparse.parse!(args)
647
+ if args.count < 1 || args.count > 2
648
+ print_error Morpheus::Terminal.angry_prompt
649
+ puts_error "#{command_name} ls expects 1-2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
650
+ return 1
651
+ end
652
+ storage_provider_id, search_file_path = parse_storage_provider_id_and_file_path(args[0], args[1])
653
+ connect(options)
654
+ begin
655
+ storage_provider = find_storage_provider_by_name_or_id(storage_provider_id)
656
+ return 1 if storage_provider.nil?
657
+ params.merge!(parse_list_options(options))
658
+ [:fullTree].each do |k|
659
+ params[k] = options[k] unless options[k].nil?
660
+ end
661
+ if options[:dry_run]
662
+ print_dry_run @storage_providers_interface.dry.list_files(storage_provider['id'], search_file_path, params)
663
+ return 0
664
+ end
665
+ json_response = @storage_providers_interface.list_files(storage_provider['id'], search_file_path, params)
666
+ if options[:json]
667
+ puts as_json(json_response, options, "storageFiles")
668
+ # no files is an error condition for this command
669
+ if !json_response['storageFiles'] || json_response['storageFiles'].size == 0
670
+ return 1
671
+ end
672
+ return 0
673
+ end
674
+ #storage_provider = json_response['storageProvider'] # yep, this is returned too
675
+ storage_files = json_response['storageFiles']
676
+ # print_h2 "Directory: #{search_file_path}"
677
+ # print "Directory: #{search_file_path}"
678
+ if storage_files && storage_files.size > 0
679
+ if do_long_format
680
+ # ls long format
681
+ # owner groups filesize type filename
682
+ now = Time.now
683
+ storage_files.each do |storage_file|
684
+ # -rw-r--r-- 1 jdickson staff 1361 Oct 23 08:00 voltron_2.10.log
685
+ file_color = cyan # reset
686
+ if storage_file['isDirectory']
687
+ file_color = blue
688
+ end
689
+ file_info = []
690
+ # Number of links
691
+ # file_info << file["linkCount"].to_i + 1
692
+ # Owner
693
+ owner_str = ""
694
+ if storage_file['owner']
695
+ owner_str = storage_file['owner']['name']
696
+ elsif storage_provider['owner']
697
+ owner_str = storage_provider['owner']['name']
698
+ else
699
+ owner_str = "noone"
700
+ end
701
+ #file_info << truncate_string(owner_str, 15).ljust(15, " ")
702
+ # Group (Tenants)
703
+ groups_str = ""
704
+ if storage_file['visibility'] == 'public'
705
+ # this is confusing because of Public URL (isPublic) setting
706
+ groups_str = "public"
707
+ else
708
+ if storage_file['accounts'].instance_of?(Array) && storage_file['accounts'].size > 0
709
+ # groups_str = storage_file['accounts'].collect {|it| it['name'] }.join(',')
710
+ groups_str = (storage_file['accounts'].size == 1) ? "#{storage_file['accounts'][0]['name']}" : "#{storage_file['accounts'].size} tenants"
711
+ elsif storage_provider['accounts'].instance_of?(Array) && storage_provider['accounts'].size > 0
712
+ # groups_str = storage_provider['accounts'].collect {|it| it['name'] }.join(',')
713
+ groups_str = (storage_provider['accounts'].size == 1) ? "#{storage_provider['accounts'][0]['name']}" : "#{storage_provider['accounts'].size} tenants"
714
+ else
715
+ groups_str = owner_str
716
+ end
717
+ end
718
+ #file_info << truncate_string(groups_str, 15).ljust(15, " ")
719
+ # File Type
720
+ content_type = storage_file['contentType'].to_s
721
+ if storage_file['isDirectory']
722
+ content_type = "directory"
723
+ else
724
+ content_type = storage_file['contentType'].to_s
725
+ end
726
+ file_info << content_type.ljust(25, " ")
727
+ filesize_str = ""
728
+ if do_human_bytes
729
+ # filesize_str = format_bytes(storage_file['contentLength'])
730
+ filesize_str = format_bytes_short(storage_file['contentLength'])
731
+ else
732
+ filesize_str = storage_file['contentLength'].to_i.to_s
733
+ end
734
+ # file_info << filesize_str.ljust(12, " ")
735
+ file_info << filesize_str.ljust(7, " ")
736
+ mtime = ""
737
+ last_updated = parse_time(storage_file['dateModified'])
738
+ if last_updated
739
+ if last_updated.year == now.year
740
+ mtime = format_local_dt(last_updated, {format: "%b %e %H:%M"})
741
+ else
742
+ mtime = format_local_dt(last_updated, {format: "%b %e %Y"})
743
+ end
744
+ end
745
+ file_info << mtime.ljust(12, " ")
746
+ fn = format_filename(storage_file['name'], {fullTree: params[:fullTree]})
747
+ file_info << file_color + fn.to_s + cyan
748
+ print cyan, file_info.join(" "), reset, "\n"
749
+ end
750
+ else
751
+ file_names = storage_files.collect do |storage_file|
752
+ file_color = cyan # reset
753
+ if storage_file['isDirectory']
754
+ file_color = blue
755
+ end
756
+ fn = format_filename(storage_file['name'], {fullTree: params[:fullTree]})
757
+ file_color + fn.to_s + reset
758
+ end
759
+ if do_one_file_per_line
760
+ print file_names.join("\n")
761
+ else
762
+ print file_names.join("\t")
763
+ end
764
+ print "\n"
765
+ end
766
+ else
767
+ print_error yellow, "No files found for path: #{search_file_path}", reset, "\n"
768
+ return 1
769
+ end
770
+
771
+ return 0
772
+ rescue RestClient::Exception => e
773
+ print_rest_exception(e, options)
774
+ return 1
775
+ end
776
+ end
777
+
778
+ # def get_file(args)
779
+ # todo...
780
+ # end
781
+
782
+ def upload_file(args)
783
+ options = {}
784
+ query_params = {}
785
+ do_recursive = false
786
+ ignore_regexp = nil
787
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
788
+ opts.banner = subcommand_usage("[local-file] [provider:/path]")
789
+ # opts.on('--filename FILEPATH', String, "Remote file path for the file or folder being uploaded, this is an alternative to [remote-file-path]." ) do |val|
790
+ # options['type'] = val
791
+ # end
792
+ opts.on( '-R', '--recursive', "Upload a directory and all of its files. This must be passed if [local-file] is a directory." ) do
793
+ do_recursive = true
794
+ end
795
+ opts.on('--ignore-files PATTERN', String, "Pattern of files to be ignored when uploading a directory." ) do |val|
796
+ ignore_regexp = /#{Regexp.escape(val)}/
797
+ end
798
+ opts.footer = "Upload a local file or folder to a storage provider. " +
799
+ "\nThe first argument [local-file] should be the path of a local file or directory." +
800
+ "\nThe second argument [provider:/path] should contain the name or id of the provider." +
801
+ "\nThe [:/path] component is optional and can be used to specify the destination of the uploaded file or folder." +
802
+ "\nThe default destination is the same name as the [local-file], under the root directory '/'. " +
803
+ "\nThis will overwrite any existing remote files that match the destination /path."
804
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run])
805
+ end
806
+ optparse.parse!(args)
807
+
808
+ if args.count != 2
809
+ print_error Morpheus::Terminal.angry_prompt
810
+ puts_error "#{command_name} upload expects 2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
811
+ return 1
812
+ end
813
+ # validate local file path
814
+ local_file_path = File.expand_path(args[0].squeeze('/'))
815
+ if local_file_path == "" || local_file_path == "/" || local_file_path == "."
816
+ print_error Morpheus::Terminal.angry_prompt
817
+ puts_error "#{command_name} missing argument: [local-file]\n#{optparse}"
818
+ return 1
819
+ end
820
+ if !File.exists?(local_file_path)
821
+ print_error Morpheus::Terminal.angry_prompt
822
+ puts_error "#{command_name} bad argument: [local-file]\nFile '#{local_file_path}' was not found.\n#{optparse}"
823
+ return 1
824
+ end
825
+
826
+ # validate provider:/path
827
+ storage_provider_id, remote_file_path = parse_storage_provider_id_and_file_path(args[1], args[2])
828
+
829
+ # if local_file_path.include?('../') # || options[:yes]
830
+ # raise_command_error "Sorry, you may not use relative paths in your local filepath."
831
+ # end
832
+
833
+ # validate provider name (or id)
834
+ if !storage_provider_id
835
+ print_error Morpheus::Terminal.angry_prompt
836
+ puts_error "#{command_name} missing argument: [provider]\n#{optparse}"
837
+ return 1
838
+ end
839
+
840
+ # strip leading slash of remote name
841
+ # if remote_file_path[0].chr == "/"
842
+ # remote_file_path = remote_file_path[1..-1]
843
+ # end
844
+
845
+ if remote_file_path.include?('./') # || options[:yes]
846
+ raise_command_error "Sorry, you may not use relative paths in your remote filepath."
847
+ end
848
+
849
+ # if !options[:yes]
850
+ scary_local_paths = ["/", "/root", "C:\\"]
851
+ if scary_local_paths.include?(local_file_path)
852
+ unless Morpheus::Cli::OptionTypes.confirm("Are you sure you want to upload all the files in local directory '#{local_file_path}' !?")
853
+ return 9, "aborted command"
854
+ end
855
+ end
856
+ # end
857
+
858
+ connect(options)
859
+ begin
860
+ storage_provider = find_storage_provider_by_name_or_id(storage_provider_id)
861
+ return 1 if storage_provider.nil?
862
+
863
+ # how many files we dealing with?
864
+ files_to_upload = []
865
+ if File.directory?(local_file_path)
866
+ # upload directory
867
+ if !do_recursive
868
+ print_error Morpheus::Terminal.angry_prompt
869
+ puts_error "bad argument: '#{local_file_path}' is a directory. Use -R or --recursive to upload a directory.\n#{optparse}"
870
+ return 1
871
+ end
872
+ found_files = Dir.glob("#{local_file_path}/**/*")
873
+ # note: api call for directories is not needed
874
+ found_files = found_files.select {|file| File.file?(file) }
875
+ if ignore_regexp
876
+ found_files = found_files.reject {|it| it =~ ignore_regexp}
877
+ end
878
+ files_to_upload = found_files
879
+
880
+ if files_to_upload.size == 0
881
+ print_error Morpheus::Terminal.angry_prompt
882
+ puts_error "bad argument: Local directory '#{local_file_path}' contains 0 files."
883
+ return 1
884
+ end
885
+
886
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to upload directory #{local_file_path} (#{files_to_upload.size} files) to #{storage_provider['name']}:#{remote_file_path}?")
887
+ return 9, "aborted command"
888
+ end
889
+
890
+ if !options[:yes]
891
+ if files_to_upload.size > 100
892
+ unless Morpheus::Cli::OptionTypes.confirm("Are you REALLY sure you want to upload #{files_to_upload.size} files ?")
893
+ return 9, "aborted command"
894
+ end
895
+ end
896
+ end
897
+
898
+ # local_dirname = File.dirname(local_file_path)
899
+ # local_basename = File.basename(local_file_path)
900
+ upload_file_list = []
901
+ files_to_upload.each do |file|
902
+ destination = file.sub(local_file_path, (remote_file_path || "")).squeeze('/')
903
+ upload_file_list << {file: file, destination: destination}
904
+ end
905
+
906
+ if options[:dry_run]
907
+ # print_h1 "DRY RUN"
908
+ print "\n",cyan, bold, "Uploading #{upload_file_list.size} Files...", reset, "\n"
909
+ upload_file_list.each do |obj|
910
+ file, destination = obj[:file], obj[:destination]
911
+ #print cyan,bold, " - Uploading #{file} to #{storage_provider_id}:#{destination} DRY RUN", reset, "\n"
912
+ print_dry_run @storage_providers_interface.dry.upload_file(storage_provider['id'], file, destination)
913
+ print "\n"
914
+ end
915
+ return 0
916
+ end
917
+
918
+ print "\n",cyan, bold, "Uploading #{upload_file_list.size} Files...", reset, "\n"
919
+ bad_upload_responses = []
920
+ upload_file_list.each do |obj|
921
+ file, destination = obj[:file], obj[:destination]
922
+ print cyan,bold, " - Uploading #{file} to #{storage_provider_id}:#{destination}", reset
923
+ upload_response = @storage_providers_interface.upload_file(storage_provider['id'], file, destination)
924
+ if upload_response['success']
925
+ print bold," #{green}SUCCESS#{reset}"
926
+ else
927
+ print bold," #{red}ERROR#{reset}"
928
+ if upload_response['msg']
929
+ bad_upload_responses << upload_response
930
+ print " #{upload_response['msg']}#{reset}"
931
+ end
932
+ end
933
+ print "\n"
934
+ end
935
+ if bad_upload_responses.size > 0
936
+ print cyan, bold, "Completed Upload of #{upload_file_list.size} Files. #{red}#{bad_upload_responses.size} Errors!", reset, "\n"
937
+ else
938
+ print cyan, bold, "Completed Upload of #{upload_file_list.size} Files!", reset, "\n"
939
+ end
940
+
941
+ else
942
+
943
+ # upload file
944
+ if !File.exists?(local_file_path) && !File.file?(local_file_path)
945
+ print_error Morpheus::Terminal.angry_prompt
946
+ puts_error "#{command_name} bad argument: [local-file]\nFile '#{local_file_path}' was not found.\n#{optparse}"
947
+ return 1
948
+ end
949
+
950
+ # local_dirname = File.dirname(local_file_path)
951
+ # local_basename = File.basename(local_file_path)
952
+
953
+ file = local_file_path
954
+ destination = File.basename(file)
955
+ if remote_file_path[-1].chr == "/"
956
+ # work like `cp`, and place into the directory
957
+ destination = remote_file_path + File.basename(file)
958
+ elsif remote_file_path
959
+ # renaming file
960
+ destination = remote_file_path
961
+ end
962
+ destination = destination.squeeze('/')
963
+
964
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to upload #{local_file_path} to #{storage_provider['name']}:#{destination}?")
965
+ return 9, "aborted command"
966
+ end
967
+
968
+ if options[:dry_run]
969
+ #print cyan,bold, " - Uploading #{file} to #{storage_provider_id}:#{destination} DRY RUN", reset, "\n"
970
+ # print_h1 "DRY RUN"
971
+ print_dry_run @storage_providers_interface.dry.upload_file(storage_provider['id'], file, destination)
972
+ print "\n"
973
+ return 0
974
+ end
975
+
976
+ print cyan,bold, " - Uploading #{file} to #{storage_provider_id}:#{destination}", reset
977
+ upload_response = @storage_providers_interface.upload_file(storage_provider['id'], file, destination)
978
+ if upload_response['success']
979
+ print bold," #{green}Success#{reset}"
980
+ else
981
+ print bold," #{red}Error#{reset}"
982
+ if upload_response['msg']
983
+ print " #{upload_response['msg']}#{reset}"
984
+ end
985
+ end
986
+ print "\n"
987
+
988
+ end
989
+ #print cyan, bold, "Upload Complete!", reset, "\n"
990
+
991
+ return 0
992
+ rescue RestClient::Exception => e
993
+ print_rest_exception(e, options)
994
+ return 1
995
+ end
996
+ end
997
+
998
+ def download_file(args)
999
+ full_command_string = "#{command_name} download #{args.join(' ')}".strip
1000
+ options = {}
1001
+ outfile = nil
1002
+ do_overwrite = false
1003
+ do_mkdir = false
1004
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1005
+ opts.banner = subcommand_usage("[provider:/path] [local-file]")
1006
+ opts.on( '-f', '--force', "Overwrite existing [local-file] if it exists." ) do
1007
+ do_overwrite = true
1008
+ # do_mkdir = true
1009
+ end
1010
+ opts.on( '-p', '--mkdir', "Create missing directories for [local-file] if they do not exist." ) do
1011
+ do_mkdir = true
1012
+ end
1013
+ build_common_options(opts, options, [:dry_run, :quiet])
1014
+ opts.footer = "Download a file or directory.\n" +
1015
+ "[provider:/path] is required. This is the name or id of the provider and /path the file or folder to be downloaded.\n" +
1016
+ "[local-file] is required. This is the full local filepath for the downloaded file.\n" +
1017
+ "Directories will be downloaded as a .zip file, so you'll want to specify a [local-file] with a .zip extension."
1018
+ end
1019
+ optparse.parse!(args)
1020
+ if args.count < 2 || args.count > 3
1021
+ print_error Morpheus::Terminal.angry_prompt
1022
+ puts_error "#{command_name} download expects 2-3 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1023
+ return 1
1024
+ end
1025
+ storage_provider_id = nil
1026
+ local = nil
1027
+ outfile = nil
1028
+ if args.count == 3
1029
+ storage_provider_id, file_path = parse_storage_provider_id_and_file_path(args[0], args[1])
1030
+ outfile = args[2]
1031
+ else
1032
+ storage_provider_id, file_path = parse_storage_provider_id_and_file_path(args[0])
1033
+ outfile = args[1]
1034
+ end
1035
+ connect(options)
1036
+ begin
1037
+ storage_provider = find_storage_provider_by_name_or_id(storage_provider_id)
1038
+ return 1 if storage_provider.nil?
1039
+
1040
+ file_path = file_path.squeeze('/')
1041
+ outfile = File.expand_path(outfile)
1042
+ if Dir.exists?(outfile)
1043
+ outfile = File.join(outfile, File.basename(file_path))
1044
+ end
1045
+ if Dir.exists?(outfile)
1046
+ print_red_alert "[local-file] is invalid. It is the name of an existing directory: #{outfile}"
1047
+ return 1
1048
+ end
1049
+ destination_dir = File.dirname(outfile)
1050
+ if !Dir.exists?(destination_dir)
1051
+ if do_mkdir
1052
+ print cyan,"Creating local directory #{destination_dir}",reset,"\n"
1053
+ FileUtils.mkdir_p(destination_dir)
1054
+ else
1055
+ print_red_alert "[local-file] is invalid. Directory not found: #{destination_dir}"
1056
+ return 1
1057
+ end
1058
+ end
1059
+ if File.exists?(outfile)
1060
+ if do_overwrite
1061
+ # uhh need to be careful wih the passed filepath here..
1062
+ # don't delete, just overwrite.
1063
+ # File.delete(outfile)
1064
+ else
1065
+ print_error Morpheus::Terminal.angry_prompt
1066
+ puts_error "[local-file] is invalid. File already exists: #{outfile}", "Use -f to overwrite the existing file."
1067
+ # puts_error optparse
1068
+ return 1
1069
+ end
1070
+ end
1071
+ begin
1072
+ if options[:dry_run]
1073
+ print_dry_run @storage_providers_interface.dry.download_file_chunked(storage_provider['id'], file_path, outfile), full_command_string
1074
+ return 0
1075
+ end
1076
+ if !options[:quiet]
1077
+ print cyan + "Downloading archive file #{storage_provider['name']}:#{file_path} to #{outfile} ... "
1078
+ end
1079
+
1080
+ http_response = @storage_providers_interface.download_file_chunked(storage_provider['id'], file_path, outfile)
1081
+
1082
+ # FileUtils.chmod(0600, outfile)
1083
+ success = http_response.code.to_i == 200
1084
+ if success
1085
+ if !options[:quiet]
1086
+ print green + "SUCCESS" + reset + "\n"
1087
+ end
1088
+ return 0
1089
+ else
1090
+ if !options[:quiet]
1091
+ print red + "ERROR" + reset + " HTTP #{http_response.code}" + "\n"
1092
+ end
1093
+ # F it, just remove a bad result
1094
+ if File.exists?(outfile) && File.file?(outfile)
1095
+ Morpheus::Logging::DarkPrinter.puts "Deleting bad file download: #{outfile}" if Morpheus::Logging.debug?
1096
+ File.delete(outfile)
1097
+ end
1098
+ if options[:debug]
1099
+ puts_error http_response.inspect
1100
+ end
1101
+ return 1
1102
+ end
1103
+ rescue RestClient::Exception => e
1104
+ # this is not reached
1105
+ if e.response && e.response.code == 404
1106
+ print_red_alert "Storage file not found by path #{file_path}"
1107
+ return nil
1108
+ else
1109
+ raise e
1110
+ end
1111
+ end
1112
+ rescue RestClient::Exception => e
1113
+ print_rest_exception(e, options)
1114
+ return 1
1115
+ end
1116
+
1117
+ end
1118
+
1119
+ def read_file(args)
1120
+ full_command_string = "#{command_name} read #{args.join(' ')}".strip
1121
+ options = {}
1122
+ outfile = nil
1123
+ do_overwrite = false
1124
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1125
+ opts.banner = subcommand_usage("[provider:/path]")
1126
+ build_common_options(opts, options, [:dry_run])
1127
+ opts.footer = "Print the contents of a storage file.\n" +
1128
+ "[provider:/path] is required. This is the name or id of the provider and /path the file or folder to be downloaded.\n" +
1129
+ "This confirmation can be skipped with the -y option."
1130
+ end
1131
+ optparse.parse!(args)
1132
+ if args.count < 1 || args.count > 2
1133
+ print_error Morpheus::Terminal.angry_prompt
1134
+ puts_error "#{command_name} read expects 1-2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1135
+ return 1
1136
+ end
1137
+ connect(options)
1138
+ begin
1139
+ storage_provider_id, file_path = parse_storage_provider_id_and_file_path(args[0], args[1])
1140
+ storage_provider = find_storage_provider_by_name_or_id(storage_provider_id)
1141
+ return 1 if storage_provider.nil?
1142
+
1143
+ file_path = file_path.squeeze('/')
1144
+
1145
+ if options[:dry_run]
1146
+ print_dry_run @storage_providers_interface.dry.download_file(storage_provider['id'], file_path), full_command_string
1147
+ return 1
1148
+ end
1149
+ file_response = @storage_providers_interface.download_file(storage_provider['id'], file_path)
1150
+ puts file_response.body.to_s
1151
+ return 0
1152
+ rescue RestClient::Exception => e
1153
+ print_rest_exception(e, options)
1154
+ return 1
1155
+ end
1156
+
1157
+ end
1158
+
1159
+ def remove_file(args)
1160
+ options = {}
1161
+ query_params = {}
1162
+ do_recursive = false
1163
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1164
+ opts.banner = subcommand_usage("[provider:/path]")
1165
+ opts.on( '-R', '--recursive', "Delete a directory and all of its files. This must be passed if specifying a directory." ) do
1166
+ do_recursive = true
1167
+ end
1168
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run])
1169
+ opts.footer = "Delete a storage file or directory."
1170
+ end
1171
+ optparse.parse!(args)
1172
+ # consider only allowing args.count == 1 here in the format [provider:/path]
1173
+ if args.count < 1 || args.count > 2
1174
+ print_error Morpheus::Terminal.angry_prompt
1175
+ puts_error "#{command_name} remove-file expects 1-2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1176
+ return 1
1177
+ end
1178
+ storage_provider_id, file_path = parse_storage_provider_id_and_file_path(args[0], args[1])
1179
+ connect(options)
1180
+ begin
1181
+
1182
+ storage_file = find_storage_file_by_bucket_and_path(storage_provider_id, file_path)
1183
+ return 1 if storage_file.nil?
1184
+ if storage_file['isDirectory']
1185
+ if !do_recursive
1186
+ print_error Morpheus::Terminal.angry_prompt
1187
+ puts_error "bad argument: '#{file_path}' is a directory. Use -R or --recursive to delete a directory.\n#{optparse}"
1188
+ return 1
1189
+ end
1190
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the directory: #{args[0]}?")
1191
+ return 9, "aborted command"
1192
+ end
1193
+ else
1194
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the file: #{args[0]}?")
1195
+ return 9, "aborted command"
1196
+ end
1197
+ end
1198
+
1199
+ if options[:dry_run]
1200
+ print_dry_run @storage_files_interface.dry.destroy(storage_file['id'], query_params)
1201
+ return 0
1202
+ end
1203
+ json_response = @storage_files_interface.destroy(storage_file['id'], query_params)
1204
+ if options[:json]
1205
+ print JSON.pretty_generate(json_response)
1206
+ print "\n"
1207
+ else
1208
+ print_green_success "Removed file #{args[0]}"
1209
+ end
1210
+ return 0
1211
+
1212
+ rescue RestClient::Exception => e
1213
+ print_rest_exception(e, options)
1214
+ return 1
1215
+ end
1216
+
1217
+ end
1218
+
507
1219
  private
508
1220
 
509
1221
 
@@ -573,4 +1285,75 @@ class Morpheus::Cli::StorageProvidersCommand
573
1285
  storage_provider['providerType'].to_s.capitalize
574
1286
  end
575
1287
 
1288
+ # parse_storage_provider_id_and_file_path() provides flexible argument formats for provider and path
1289
+ # it looks for [provider:/path] or [provider] [path]
1290
+ # @param delim [String] Default is a comma and any surrounding white space.
1291
+ # @return [Array] 2 elements, provider name (or id) and the file path.
1292
+ # The default file path is "/".
1293
+ # Examples:
1294
+ # parse_storage_provider_id_and_file_path("test") == ["test", "/"]
1295
+ # parse_storage_provider_id_and_file_path("test:/global.cfg") == ["test", "/global.cfg"]
1296
+ # parse_storage_provider_id_and_file_path("test:/node1/node.cfg") == ["test", "/node1/node.cfg"]
1297
+ # parse_storage_provider_id_and_file_path("test/node1/node.cfg") == ["test", "/node1/node.cfg"]
1298
+ # parse_storage_provider_id_and_file_path("test", "node1/node.cfg") == ["test", "/node1/node.cfg"]
1299
+ #
1300
+ def parse_storage_provider_id_and_file_path(*args)
1301
+ if args.size < 1 || args.size > 2
1302
+ return nil, nil
1303
+ end
1304
+ if !args[0]
1305
+ return nil, nil
1306
+ end
1307
+ full_path = args[0].to_s
1308
+ if args[1]
1309
+ if full_path.include?(":")
1310
+ full_path = "#{full_path}/#{args[1]}"
1311
+ else
1312
+ full_path = "#{full_path}:#{args[1]}"
1313
+ end
1314
+ end
1315
+ # ok fine, allow just id/filePath, without a colon.
1316
+ if !full_path.include?(":") && full_path.include?("/")
1317
+ path_elements = full_path.split("/")
1318
+ full_path = path_elements[0] + ":" + path_elements[1..-1].join("/")
1319
+ end
1320
+ uri_elements = full_path.split(":")
1321
+ storage_provider_id = uri_elements[0]
1322
+ file_path = uri_elements[1..-1].join("/") # [1]
1323
+ file_path = "/#{file_path}".squeeze("/")
1324
+ return storage_provider_id, file_path
1325
+ end
1326
+
1327
+ def format_filename(filename, options={})
1328
+ if options[:fullTree]
1329
+ filename.to_s
1330
+ else
1331
+ filename.to_s.split('/').last()
1332
+ end
1333
+ end
1334
+
1335
+ def print_storage_files_table(storage_files, options={})
1336
+ table_color = options[:color] || cyan
1337
+ rows = storage_files.collect do |storage_file|
1338
+ {
1339
+ id: storage_file['id'],
1340
+ name: format_filename(storage_file['name'], options),
1341
+ type: storage_file['isDirectory'] ? 'directory' : (storage_file['contentType']),
1342
+ size: storage_file['isDirectory'] ? '' : format_bytes(storage_file['contentLength']),
1343
+ lastUpdated: format_local_dt(storage_file['dateModified'])
1344
+ }
1345
+ end
1346
+ columns = [
1347
+ # :id,
1348
+ {:name => {:display_name => "File".upcase} },
1349
+ :type,
1350
+ :size,
1351
+ # {:dateCreated => {:display_name => "Date Created"} },
1352
+ {:lastUpdated => {:display_name => "Last Modified".upcase} }
1353
+ ]
1354
+ print table_color
1355
+ print as_pretty_table(rows, columns, options)
1356
+ print reset
1357
+ end
1358
+
576
1359
  end