morpheus-cli 3.4.1.10 → 3.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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