aspera-cli 4.4.0 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1042 -787
  3. data/bin/ascli +1 -1
  4. data/bin/asession +3 -5
  5. data/docs/Makefile +4 -7
  6. data/docs/README.erb.md +988 -740
  7. data/examples/faspex4.rb +4 -6
  8. data/examples/transfer.rb +2 -2
  9. data/lib/aspera/aoc.rb +139 -118
  10. data/lib/aspera/cli/listener/progress_multi.rb +5 -5
  11. data/lib/aspera/cli/main.rb +64 -34
  12. data/lib/aspera/cli/manager.rb +19 -20
  13. data/lib/aspera/cli/plugin.rb +9 -1
  14. data/lib/aspera/cli/plugins/aoc.rb +156 -143
  15. data/lib/aspera/cli/plugins/ats.rb +11 -10
  16. data/lib/aspera/cli/plugins/bss.rb +2 -2
  17. data/lib/aspera/cli/plugins/config.rb +236 -112
  18. data/lib/aspera/cli/plugins/faspex.rb +29 -7
  19. data/lib/aspera/cli/plugins/faspex5.rb +21 -8
  20. data/lib/aspera/cli/plugins/node.rb +21 -9
  21. data/lib/aspera/cli/plugins/orchestrator.rb +5 -3
  22. data/lib/aspera/cli/plugins/preview.rb +2 -2
  23. data/lib/aspera/cli/plugins/server.rb +3 -3
  24. data/lib/aspera/cli/plugins/shares.rb +17 -0
  25. data/lib/aspera/cli/transfer_agent.rb +47 -85
  26. data/lib/aspera/cli/version.rb +1 -1
  27. data/lib/aspera/environment.rb +4 -4
  28. data/lib/aspera/fasp/{manager.rb → agent_base.rb} +7 -6
  29. data/lib/aspera/fasp/{connect.rb → agent_connect.rb} +46 -39
  30. data/lib/aspera/fasp/{local.rb → agent_direct.rb} +14 -17
  31. data/lib/aspera/fasp/{http_gw.rb → agent_httpgw.rb} +4 -4
  32. data/lib/aspera/fasp/{node.rb → agent_node.rb} +25 -8
  33. data/lib/aspera/fasp/agent_trsdk.rb +106 -0
  34. data/lib/aspera/fasp/default.rb +17 -0
  35. data/lib/aspera/fasp/installation.rb +64 -48
  36. data/lib/aspera/fasp/parameters.rb +7 -3
  37. data/lib/aspera/faspex_gw.rb +6 -6
  38. data/lib/aspera/keychain/encrypted_hash.rb +120 -0
  39. data/lib/aspera/keychain/macos_security.rb +94 -0
  40. data/lib/aspera/log.rb +45 -32
  41. data/lib/aspera/node.rb +3 -6
  42. data/lib/aspera/rest.rb +65 -49
  43. metadata +68 -27
  44. data/lib/aspera/api_detector.rb +0 -60
  45. data/lib/aspera/secrets.rb +0 -20
@@ -44,9 +44,7 @@ module Aspera
44
44
  commands=[:create,:list,:show,:modify,:delete,:node,:cluster,:entitlement]
45
45
  command=self.options.get_next_command(commands)
46
46
  # those dont require access key id
47
- unless [:create,:list].include?(command)
48
- access_key_id=self.options.get_option(:id,:mandatory)
49
- end
47
+ access_key_id=self.instance_identifier() unless [:create,:list].include?(command)
50
48
  case command
51
49
  when :create
52
50
  params=self.options.get_option(:params,:optional) || {}
@@ -102,21 +100,24 @@ module Aspera
102
100
  ak_data=ats_api_pub_v1.read("access_keys/#{access_key_id}")[:data]
103
101
  server_data=@ats_api_pub.all_servers.select {|i| i['id'].start_with?(ak_data['transfer_server_id'])}.first
104
102
  raise CliError,"no such server found" if server_data.nil?
103
+ base_url=server_data['transfer_setup_url']
105
104
  api_node=Rest.new({
106
- :base_url => server_data['transfer_setup_url'],
105
+ :base_url => base_url,
107
106
  :auth => {
108
107
  :type => :basic,
109
108
  :username => access_key_id,
110
- :password => @agents[:secret].get_secret(access_key_id)}})
109
+ :password => @agents[:config].get_secret(url: base_url,username: access_key_id)
110
+ }})
111
111
  command=self.options.get_next_command(Node::COMMON_ACTIONS)
112
112
  return Node.new(@agents.merge(skip_basic_auth_options: true, node_api: api_node)).execute_action(command)
113
113
  when :cluster
114
+ base_url=ats_api_pub_v1.params[:base_url]
114
115
  rest_params={
115
- :base_url => ats_api_pub_v1.params[:base_url],
116
+ :base_url => base_url,
116
117
  :auth => {
117
118
  :type => :basic,
118
119
  :username => access_key_id,
119
- :password => @agents[:secret].get_secret(access_key_id)
120
+ :password => @agents[:config].get_secret(url: base_url, username: access_key_id)
120
121
  }}
121
122
  api_ak_auth=Rest.new(rest_params)
122
123
  return {:type=>:single_object, :data=>api_ak_auth.read("servers")[:data]}
@@ -132,10 +133,10 @@ module Aspera
132
133
  when :list
133
134
  return {:type=>:object_list, :data=>@ats_api_pub.all_servers, :fields=>['id','cloud','region']}
134
135
  when :show
135
- server_id=self.options.get_option(:id,:optional)
136
- if server_id.nil?
136
+ if self.options.get_option(:cloud,:optional) or self.options.get_option(:region,:optional)
137
137
  server_data=server_by_cloud_region
138
138
  else
139
+ server_id=instance_identifier()
139
140
  server_data=@ats_api_pub.all_servers.select {|i| i['id'].eql?(server_id)}.first
140
141
  raise "no such server id" if server_data.nil?
141
142
  end
@@ -160,7 +161,7 @@ module Aspera
160
161
  def execute_action_api_key
161
162
  command=self.options.get_next_command([:instances, :create, :list, :show, :delete])
162
163
  if [:show,:delete].include?(command)
163
- concerned_id=self.options.get_option(:id,:mandatory)
164
+ concerned_id=self.instance_identifier()
164
165
  end
165
166
  rest_add_header={}
166
167
  if !command.eql?(:instances)
@@ -46,7 +46,7 @@ module Aspera
46
46
  # give fields to keep order
47
47
  return {:type=>:object_list, :data=>result['data'][object],:fields=> FIELDS['bssSubscriptions']}
48
48
  when :show
49
- id = self.options.get_option(:id,:mandatory)
49
+ id = self.instance_identifier()
50
50
  request={
51
51
  'variables'=>{'id'=>id},
52
52
  'query'=>"query($id: ID!) {#{object}(id: $id) { #{all_fields('bssSubscriptions')} roleAssignments(uniqueSubjectId: true) { id subjectId } instances { id state planId serviceId ssmSubscriptionId entitlement { id } aocOrganization { id subdomainName name status tier urlId trialExpiresAt users(organizationAdmin: true) { id name email atsAdmin subscriptionAdmin } } } } }"
@@ -55,7 +55,7 @@ module Aspera
55
55
  result.delete('instances')
56
56
  return {:type=>:single_object, :data=>result}
57
57
  when :instances
58
- id = self.options.get_option(:id,:mandatory)
58
+ id = self.instance_identifier()
59
59
  request={
60
60
  'variables'=>{'id'=>id},
61
61
  'query'=>"query($id: ID!) {#{object}(id: $id) { aocOrganization { id subdomainName name status tier urlId trialExpiresAt } } } }"
@@ -2,7 +2,6 @@ require 'aspera/cli/basic_auth_plugin'
2
2
  require 'aspera/cli/extended_value'
3
3
  require 'aspera/fasp/installation'
4
4
  require 'aspera/fasp/parameters'
5
- require 'aspera/api_detector'
6
5
  require 'aspera/open_application'
7
6
  require 'aspera/aoc'
8
7
  require 'aspera/proxy_auto_config'
@@ -10,6 +9,8 @@ require 'aspera/uri_reader'
10
9
  require 'aspera/rest'
11
10
  require 'aspera/persistency_action_once'
12
11
  require 'aspera/id_generator'
12
+ require 'aspera/keychain/encrypted_hash'
13
+ require 'aspera/keychain/macos_security'
13
14
  require 'xmlsimple'
14
15
  require 'base64'
15
16
  require 'net/smtp'
@@ -31,6 +32,7 @@ module Aspera
31
32
  CONF_PRESET_VERSION='version'
32
33
  CONF_PRESET_DEFAULT='default'
33
34
  CONF_PRESET_GLOBAL='global_common_defaults'
35
+ CONF_PRESET_SECRETS='default_secrets'
34
36
  CONF_PLUGIN_SYM = :config # Plugins::Config.name.split('::').last.downcase.to_sym
35
37
  CONF_GLOBAL_SYM = :config
36
38
  # old tool name
@@ -48,7 +50,7 @@ module Aspera
48
50
  SERVER_COMMAND='server'
49
51
  CONNECT_WEB_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
50
52
  CONNECT_VERSIONS = 'connectversions.js'
51
- TRANSFER_SDK_ARCHIVE_URL = 'https://ibm.biz/aspera_sdk'
53
+ TRANSFER_SDK_ARCHIVE_URL = 'https://ibm.biz/aspera_transfer_sdk'
52
54
  DEMO='demo'
53
55
  DEMO_SERVER_PRESET='demoserver'
54
56
  AOC_PATH_API_CLIENTS='admin/api-clients'
@@ -69,7 +71,7 @@ END_OF_TEMPLATE
69
71
  :CONF_PRESET_GLOBAL,:PROGRAM_NAME_V1,:PROGRAM_NAME_V2,:DEFAULT_REDIRECT,:ASPERA_PLUGINS_FOLDERNAME,
70
72
  :RUBY_FILE_EXT,:AOC_COMMAND_V1,:AOC_COMMAND_V2,:AOC_COMMAND_V3,:AOC_COMMAND_CURRENT,:DEMO,
71
73
  :TRANSFER_SDK_ARCHIVE_URL,:AOC_PATH_API_CLIENTS,:DEMO_SERVER_PRESET,:EMAIL_TEST_TEMPLATE,:EXTV_INCLUDE_PRESETS,
72
- :EXTV_PRESET,:DEFAULT_CHECK_NEW_VERSION_DAYS,:DEFAULT_PRIV_KEY_FILENAME,:SERVER_COMMAND
74
+ :EXTV_PRESET,:DEFAULT_CHECK_NEW_VERSION_DAYS,:DEFAULT_PRIV_KEY_FILENAME,:SERVER_COMMAND,:CONF_PRESET_SECRETS
73
75
  def option_preset; nil; end
74
76
 
75
77
  def option_preset=(value)
@@ -86,12 +88,12 @@ END_OF_TEMPLATE
86
88
 
87
89
  def initialize(env,tool_name,help_url,version,main_folder)
88
90
  super(env)
89
- raise 'missing secret manager' if @agents[:secret].nil?
90
91
  @plugins={}
91
92
  @plugin_lookup_folders=[]
92
93
  @use_plugin_defaults=true
93
94
  @config_presets=nil
94
95
  @connect_versions=nil
96
+ @vault=nil
95
97
  @program_version=version
96
98
  @tool_name=tool_name
97
99
  @help_url=help_url
@@ -111,24 +113,25 @@ END_OF_TEMPLATE
111
113
  # add preset handler (needed for smtp)
112
114
  ExtendedValue.instance.set_handler(EXTV_PRESET,:reader,lambda{|v|preset_by_name(v)})
113
115
  ExtendedValue.instance.set_handler(EXTV_INCLUDE_PRESETS,:decoder,lambda{|v|expanded_with_preset_includes(v)})
116
+ # load defaults before it can be overriden
117
+ self.add_plugin_default_preset(CONF_GLOBAL_SYM)
118
+ self.options.parse_options!
114
119
  self.options.set_obj_attr(:ascp_path,self,:option_ascp_path)
115
120
  self.options.set_obj_attr(:use_product,self,:option_use_product)
116
121
  self.options.set_obj_attr(:preset,self,:option_preset)
117
- self.options.set_obj_attr(:secret,@agents[:secret],:default_secret)
118
- self.options.set_obj_attr(:secrets,@agents[:secret],:all_secrets)
119
122
  self.options.add_opt_switch(:no_default,'-N','do not load default configuration for plugin') { @use_plugin_defaults=false }
120
123
  self.options.add_opt_boolean(:override,'Wizard: override existing value')
121
124
  self.options.add_opt_boolean(:use_generic_client,'Wizard: AoC: use global or org specific jwt client id')
122
125
  self.options.add_opt_boolean(:default,'Wizard: set as default configuration for specified plugin (also: update)')
123
126
  self.options.add_opt_boolean(:test_mode,'Wizard: skip private key check step')
127
+ self.options.add_opt_simple(:preset,'-PVALUE','load the named option preset from current config file')
124
128
  self.options.add_opt_simple(:pkeypath,'Wizard: path to private key for JWT')
125
129
  self.options.add_opt_simple(:ascp_path,'path to ascp')
126
130
  self.options.add_opt_simple(:use_product,'use ascp from specified product')
127
131
  self.options.add_opt_simple(:smtp,'smtp configuration (extended value: hash)')
128
132
  self.options.add_opt_simple(:fpac,'proxy auto configuration URL')
129
- self.options.add_opt_simple(:preset,'-PVALUE','load the named option preset from current config file')
130
133
  self.options.add_opt_simple(:secret,'default secret')
131
- self.options.add_opt_simple(:secrets,'secret repository (Hash)')
134
+ self.options.add_opt_simple(:secrets,'secret vault')
132
135
  self.options.add_opt_simple(:sdk_url,'URL to get SDK')
133
136
  self.options.add_opt_simple(:sdk_folder,'SDK folder path')
134
137
  self.options.add_opt_simple(:notif_to,'email recipient for notification of transfers')
@@ -142,7 +145,6 @@ END_OF_TEMPLATE
142
145
  self.options.set_option(:sdk_folder,File.join(@main_folder,'sdk'))
143
146
  self.options.set_option(:override,:no)
144
147
  self.options.parse_options!
145
- raise CliBadArgument,'secrets shall be Hash' unless @agents[:secret].all_secrets.is_a?(Hash)
146
148
  Fasp::Installation.instance.folder=self.options.get_option(:sdk_folder,:mandatory)
147
149
  end
148
150
 
@@ -191,8 +193,8 @@ END_OF_TEMPLATE
191
193
  # retrieve structure from cloud (CDN) with all versions available
192
194
  def connect_versions
193
195
  if @connect_versions.nil?
194
- api_connect_cdn=Rest.new({:base_url=>CONNECT_WEB_URL})
195
- javascript=api_connect_cdn.call({:operation=>'GET',:subpath=>CONNECT_VERSIONS})
196
+ api_connect_cdn=Rest.new({base_url: CONNECT_WEB_URL})
197
+ javascript=api_connect_cdn.call({operation: 'GET',subpath: CONNECT_VERSIONS})
196
198
  # get result on one line
197
199
  connect_versions_javascript=javascript[:http].body.gsub(/\r?\n\s*/,'')
198
200
  Log.log.debug("javascript=[\n#{connect_versions_javascript}\n]")
@@ -238,9 +240,9 @@ END_OF_TEMPLATE
238
240
 
239
241
  # instanciate a plugin
240
242
  # plugins must be Capitalized
241
- def self.plugin_new(plugin_name_sym,env)
243
+ def self.plugin_class(plugin_name_sym)
242
244
  # Module.nesting[2] is Aspera::Cli
243
- return Object::const_get("#{Module.nesting[2].to_s}::Plugins::#{plugin_name_sym.to_s.capitalize}").new(env)
245
+ return Object::const_get("#{Module.nesting[2].to_s}::Plugins::#{plugin_name_sym.to_s.capitalize}")
244
246
  end
245
247
 
246
248
  def self.flatten_all_config(t)
@@ -255,10 +257,15 @@ END_OF_TEMPLATE
255
257
 
256
258
  # set parameter and value in global config
257
259
  # creates one if none already created
258
- # @return preset that contains global default
260
+ # @return preset name that contains global default
259
261
  def set_global_default(key,value)
260
262
  # get default preset if it exists
261
- global_default_preset_name=get_plugin_default_config_name(CONF_GLOBAL_SYM) || CONF_PRESET_GLOBAL
263
+ global_default_preset_name=get_plugin_default_config_name(CONF_GLOBAL_SYM)
264
+ if global_default_preset_name.nil?
265
+ global_default_preset_name=CONF_PRESET_GLOBAL
266
+ @config_presets[CONF_PRESET_DEFAULT]||={}
267
+ @config_presets[CONF_PRESET_DEFAULT][CONF_GLOBAL_SYM.to_s]=global_default_preset_name
268
+ end
262
269
  @config_presets[global_default_preset_name]||={}
263
270
  @config_presets[global_default_preset_name][key.to_s]=value
264
271
  self.format.display_status("Updated: #{global_default_preset_name}: #{key} <- #{value}")
@@ -446,14 +453,42 @@ END_OF_TEMPLATE
446
453
  Log.log.warn("skipping plugin already registered: #{plugin_symbol}")
447
454
  return
448
455
  end
449
- @plugins[plugin_symbol]={:source=>path,:require_stanza=>req}
456
+ @plugins[plugin_symbol]={source: path,require_stanza: req}
457
+ end
458
+
459
+ def identify_plugin_for_url(url)
460
+ plugins.each do |plugin_name_sym,plugin_info|
461
+ next if plugin_name_sym.eql?(CONF_PLUGIN_SYM)
462
+ require plugin_info[:require_stanza]
463
+ c=self.class.plugin_class(plugin_name_sym)
464
+ if c.respond_to?(:detect)
465
+ current_url=url
466
+ begin
467
+ res=c.send(:detect,current_url)
468
+ return res.merge(product: plugin_name_sym, url: current_url) unless res.nil?
469
+ rescue
470
+ end
471
+ begin
472
+ # is there a redirect ?
473
+ result=Rest.new({:base_url=>url}).call({operation: 'GET',subpath: '',redirect_max: 1})
474
+ current_url=result[:http].uri.to_s
475
+ # check if redirect
476
+ if ! url.eql?(current_url)
477
+ res=c.send(:detect,current_url)
478
+ return res.merge(product: plugin_name_sym, url: current_url) unless res.nil?
479
+ end
480
+ rescue
481
+ end
482
+ end
483
+ end
484
+ raise "No known application found at #{url}"
450
485
  end
451
486
 
452
487
  def execute_connect_action
453
488
  command=self.options.get_next_command([:list,:id])
454
489
  case command
455
490
  when :list
456
- return {:type=>:object_list, :data=>connect_versions, :fields => ['id','title','version']}
491
+ return {type: :object_list, data: connect_versions, fields: ['id','title','version']}
457
492
  when :id
458
493
  connect_id=self.options.get_next_argument('id or title')
459
494
  one_res=connect_versions.select{|i|i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}.first
@@ -462,13 +497,13 @@ END_OF_TEMPLATE
462
497
  case command
463
498
  when :info # shows files used
464
499
  one_res.delete('links')
465
- return {:type=>:single_object, :data=>one_res}
500
+ return {type: :single_object, data: one_res}
466
501
  when :links # shows files used
467
502
  command=self.options.get_next_command([:list,:id])
468
503
  all_links=one_res['links']
469
504
  case command
470
505
  when :list # shows files used
471
- return {:type=>:object_list, :data=>all_links}
506
+ return {type: :object_list, data: all_links}
472
507
  when :id
473
508
  link_title=self.options.get_next_argument('title')
474
509
  one_link=all_links.select {|i| i['title'].eql?(link_title)}.first
@@ -477,10 +512,10 @@ END_OF_TEMPLATE
477
512
  when :download #
478
513
  folder_dest=self.transfer.destination_folder('receive')
479
514
  #folder_dest=self.options.get_next_argument('destination folder')
480
- api_connect_cdn=Rest.new({:base_url=>CONNECT_WEB_URL})
515
+ api_connect_cdn=Rest.new({base_url: CONNECT_WEB_URL})
481
516
  fileurl = one_link['href']
482
517
  filename=fileurl.gsub(%r{.*/},'')
483
- api_connect_cdn.call({:operation=>'GET',:subpath=>fileurl,:save_to_file=>File.join(folder_dest,filename)})
518
+ api_connect_cdn.call({operation: 'GET',subpath: fileurl,save_to_file: File.join(folder_dest,filename)})
484
519
  return Main.result_status("Downloaded: #{filename}")
485
520
  when :open #
486
521
  OpenApplication.instance.uri(one_link['href'])
@@ -503,7 +538,7 @@ END_OF_TEMPLATE
503
538
  preset_name=set_global_default(:ascp_path,ascp_path)
504
539
  return Main.result_status("Saved to default global preset #{preset_name}")
505
540
  when :show # shows files used
506
- return {:type=>:status, :data=>Fasp::Installation.instance.path(:ascp)}
541
+ return {type: :status, data: Fasp::Installation.instance.path(:ascp)}
507
542
  when :info # shows files used
508
543
  data=Fasp::Installation::FILES.inject({}) do |m,v|
509
544
  m[v.to_s]=Fasp::Installation.instance.path(v) rescue 'Not Found'
@@ -528,12 +563,12 @@ END_OF_TEMPLATE
528
563
  end
529
564
  end
530
565
  data['keypass']=Fasp::Installation.instance.bypass_pass
531
- return {:type=>:single_object, :data=>data}
566
+ return {type: :single_object, data: data}
532
567
  when :products
533
568
  command=self.options.get_next_command([:list,:use])
534
569
  case command
535
570
  when :list
536
- return {:type=>:object_list, :data=>Fasp::Installation.instance.installed_products, :fields=>['name','app_root']}
571
+ return {type: :object_list, data: Fasp::Installation.instance.installed_products, fields: ['name','app_root']}
537
572
  when :use
538
573
  default_product=self.options.get_next_argument('product name')
539
574
  Fasp::Installation.instance.use_ascp_from_product(default_product)
@@ -549,109 +584,122 @@ END_OF_TEMPLATE
549
584
  raise "unexpected case: #{command}"
550
585
  end
551
586
 
552
- ACTIONS=[:gem_path, :genkey,:plugins,:flush_tokens,:list,:overview,:open,:echo,:id,:documentation,:wizard,:export_to_cli,:detect,:coffee,:ascp,:email_test,:smtp_settings,:proxy_check,:folder,:file,:check_update,:initdemo]
587
+ # legacy actions available globally
588
+ PRESET_GBL_ACTIONS=[:list,:overview].freeze
589
+ # require existing preset
590
+ PRESET_EXST_ACTIONS=[:show,:delete,:get,:unset].freeze
591
+ # require id
592
+ PRESET_INSTANCE_ACTIONS=[PRESET_EXST_ACTIONS,:initialize,:update,:ask,:set].flatten.freeze
593
+ PRESET_ALL_ACTIONS=[PRESET_GBL_ACTIONS,PRESET_INSTANCE_ACTIONS].flatten.freeze
594
+
595
+ def execute_file_action(action,config_name)
596
+ action=self.options.get_next_command(PRESET_ALL_ACTIONS) if action.nil?
597
+ config_name=instance_identifier() if config_name.nil? and PRESET_INSTANCE_ACTIONS.include?(action)
598
+ # those operations require existing option
599
+ raise "no such preset: #{config_name}" if PRESET_EXST_ACTIONS.include?(action) and !@config_presets.has_key?(config_name)
600
+ selected_preset=@config_presets[config_name]
601
+ case action
602
+ when :list
603
+ return {type: :value_list, data: @config_presets.keys, name: 'name'}
604
+ when :overview
605
+ return {type: :object_list, data: self.class.flatten_all_config(@config_presets)}
606
+ when :show
607
+ raise "no such config: #{config_name}" if selected_preset.nil?
608
+ return {type: :single_object, data: selected_preset}
609
+ when :delete
610
+ @config_presets.delete(config_name)
611
+ save_presets_to_config_file
612
+ return Main.result_status("Deleted: #{config_name}")
613
+ when :get
614
+ param_name=self.options.get_next_argument('parameter name')
615
+ value=selected_preset[param_name]
616
+ raise "no such option in preset #{config_name} : #{param_name}" if value.nil?
617
+ case value
618
+ when Numeric,String; return {type: :text, data: ExtendedValue.instance.evaluate(value.to_s)}
619
+ end
620
+ return {type: :single_object, data: value}
621
+ when :unset
622
+ param_name=self.options.get_next_argument('parameter name')
623
+ selected_preset.delete(param_name)
624
+ save_presets_to_config_file
625
+ return Main.result_status("Removed: #{config_name}: #{param_name}")
626
+ when :set
627
+ param_name=self.options.get_next_argument('parameter name')
628
+ param_value=self.options.get_next_argument('parameter value')
629
+ if !@config_presets.has_key?(config_name)
630
+ Log.log.debug("no such config name: #{config_name}, initializing")
631
+ selected_preset=@config_presets[config_name]={}
632
+ end
633
+ if selected_preset.has_key?(param_name)
634
+ Log.log.warn("overwriting value: #{selected_preset[param_name]}")
635
+ end
636
+ selected_preset[param_name]=param_value
637
+ save_presets_to_config_file
638
+ return Main.result_status("Updated: #{config_name}: #{param_name} <- #{param_value}")
639
+ when :initialize
640
+ config_value=self.options.get_next_argument('extended value (Hash)')
641
+ if @config_presets.has_key?(config_name)
642
+ Log.log.warn("configuration already exists: #{config_name}, overwriting")
643
+ end
644
+ @config_presets[config_name]=config_value
645
+ save_presets_to_config_file
646
+ return Main.result_status("Modified: #{@option_config_file}")
647
+ when :update
648
+ # get unprocessed options
649
+ theopts=self.options.get_options_table
650
+ Log.log.debug("opts=#{theopts}")
651
+ @config_presets[config_name]||={}
652
+ @config_presets[config_name].merge!(theopts)
653
+ # fix bug in 4.4 (creating key "true" in "default" preset)
654
+ @config_presets[CONF_PRESET_DEFAULT].delete(true) if @config_presets[CONF_PRESET_DEFAULT].is_a?(Hash)
655
+ save_presets_to_config_file
656
+ return Main.result_status("Updated: #{config_name}")
657
+ when :ask
658
+ self.options.ask_missing_mandatory=:yes
659
+ @config_presets[config_name]||={}
660
+ self.options.get_next_argument('option names',:multiple).each do |optionname|
661
+ option_value=self.options.get_interactive(:option,optionname)
662
+ @config_presets[config_name][optionname]=option_value
663
+ end
664
+ save_presets_to_config_file
665
+ return Main.result_status("Updated: #{config_name}")
666
+ end
667
+ end
668
+
669
+ ACTIONS=[PRESET_GBL_ACTIONS,:id,:preset,:open,:documentation,:genkey,:gem_path,:plugins,:flush_tokens,:echo,:wizard,:export_to_cli,:detect,:coffee,:ascp,:email_test,:smtp_settings,:proxy_check,:folder,:file,:check_update,:initdemo,:vault].flatten.freeze
553
670
 
554
671
  # "config" plugin
555
672
  def execute_action
556
673
  action=self.options.get_next_command(ACTIONS)
557
674
  case action
558
- when :id
559
- config_name=self.options.get_next_argument('config name')
560
- action=self.options.get_next_command([:show,:delete,:set,:get,:unset,:initialize,:update,:ask])
561
- # those operations require existing option
562
- raise "no such preset: #{config_name}" if [:show,:delete,:get,:unset].include?(action) and !@config_presets.has_key?(config_name)
563
- selected_preset=@config_presets[config_name]
564
- case action
565
- when :show
566
- raise "no such config: #{config_name}" if selected_preset.nil?
567
- return {:type=>:single_object,:data=>selected_preset}
568
- when :delete
569
- @config_presets.delete(config_name)
570
- save_presets_to_config_file
571
- return Main.result_status("Deleted: #{config_name}")
572
- when :get
573
- param_name=self.options.get_next_argument('parameter name')
574
- value=selected_preset[param_name]
575
- raise "no such option in preset #{config_name} : #{param_name}" if value.nil?
576
- case value
577
- when Numeric,String; return {:type=>:text,:data=>ExtendedValue.instance.evaluate(value.to_s)}
578
- end
579
- return {:type=>:single_object,:data=>value}
580
- when :unset
581
- param_name=self.options.get_next_argument('parameter name')
582
- selected_preset.delete(param_name)
583
- save_presets_to_config_file
584
- return Main.result_status("Removed: #{config_name}: #{param_name}")
585
- when :set
586
- param_name=self.options.get_next_argument('parameter name')
587
- param_value=self.options.get_next_argument('parameter value')
588
- if !@config_presets.has_key?(config_name)
589
- Log.log.debug("no such config name: #{config_name}, initializing")
590
- selected_preset=@config_presets[config_name]={}
591
- end
592
- if selected_preset.has_key?(param_name)
593
- Log.log.warn("overwriting value: #{selected_preset[param_name]}")
594
- end
595
- selected_preset[param_name]=param_value
596
- save_presets_to_config_file
597
- return Main.result_status("Updated: #{config_name}: #{param_name} <- #{param_value}")
598
- when :initialize
599
- config_value=self.options.get_next_argument('extended value (Hash)')
600
- if @config_presets.has_key?(config_name)
601
- Log.log.warn("configuration already exists: #{config_name}, overwriting")
602
- end
603
- @config_presets[config_name]=config_value
604
- save_presets_to_config_file
605
- return Main.result_status("Modified: #{@option_config_file}")
606
- when :update
607
- default_for_plugin=self.options.get_option(:default,:mandatory)
608
- # get unprocessed options
609
- theopts=self.options.get_options_table
610
- Log.log.debug("opts=#{theopts}")
611
- @config_presets[config_name]||={}
612
- @config_presets[config_name].merge!(theopts)
613
- if ! default_for_plugin.nil?
614
- @config_presets[CONF_PRESET_DEFAULT]||=Hash.new
615
- @config_presets[CONF_PRESET_DEFAULT][default_for_plugin]=config_name
616
- end
617
- save_presets_to_config_file
618
- return Main.result_status("Updated: #{config_name}")
619
- when :ask
620
- self.options.ask_missing_mandatory=:yes
621
- @config_presets[config_name]||={}
622
- self.options.get_next_argument('option names',:multiple).each do |optionname|
623
- option_value=self.options.get_interactive(:option,optionname)
624
- @config_presets[config_name][optionname]=option_value
625
- end
626
- save_presets_to_config_file
627
- return Main.result_status("Updated: #{config_name}")
628
- end
675
+ when *PRESET_GBL_ACTIONS # older syntax
676
+ return execute_file_action(action,nil)
677
+ when :id # older syntax
678
+ return execute_file_action(nil,self.options.get_next_argument('config name'))
679
+ when :preset # newer syntax
680
+ return execute_file_action(nil,nil)
681
+ when :open
682
+ OpenApplication.instance.uri("#{@option_config_file}") #file://
683
+ return Main.result_nothing
629
684
  when :documentation
630
685
  section=options.get_next_argument('private key file path',:single,:optional)
631
686
  section='#'+section unless section.nil?
632
687
  OpenApplication.instance.uri("#{@help_url}#{section}")
633
688
  return Main.result_nothing
634
- when :open
635
- OpenApplication.instance.uri("#{@option_config_file}") #file://
636
- return Main.result_nothing
637
689
  when :genkey # generate new rsa key
638
690
  private_key_path=self.options.get_next_argument('private key file path')
639
691
  generate_rsa_private_key(private_key_path,DEFAULT_PRIVKEY_LENGTH)
640
692
  return Main.result_status('Generated key: '+private_key_path)
641
693
  when :echo # display the content of a value given on command line
642
- result={:type=>:other_struct, :data=>self.options.get_next_argument('value')}
694
+ result={type: :other_struct, data: self.options.get_next_argument('value')}
643
695
  # special for csv
644
696
  result[:type]=:object_list if result[:data].is_a?(Array) and result[:data].first.is_a?(Hash)
645
697
  return result
646
698
  when :flush_tokens
647
699
  deleted_files=Oauth.flush_tokens
648
- return {:type=>:value_list, :name=>'file',:data=>deleted_files}
700
+ return {type: :value_list, data: deleted_files, name: 'file'}
649
701
  when :plugins
650
- return {:data => @plugins.keys.map { |i| { 'plugin' => i.to_s, 'path' => @plugins[i][:source] } } , :fields => ['plugin','path'], :type => :object_list }
651
- when :list
652
- return {:data => @config_presets.keys, :type => :value_list, :name => 'name'}
653
- when :overview
654
- return {:type=>:object_list,:data=>self.class.flatten_all_config(@config_presets)}
702
+ return {type: :object_list, data: @plugins.keys.map { |i| { 'plugin' => i.to_s, 'path' => @plugins[i][:source] } } , fields: ['plugin','path']}
655
703
  when :wizard
656
704
  # interactive mode
657
705
  self.options.ask_missing_mandatory=true
@@ -661,7 +709,7 @@ END_OF_TEMPLATE
661
709
  instance_url=self.options.get_option(:url,:mandatory)
662
710
  # allow user to tell the preset name
663
711
  preset_name=self.options.get_option(:id,:optional)
664
- appli=ApiDetector.discover_product(instance_url)
712
+ appli=identify_plugin_for_url(instance_url)
665
713
  plugin_name="<replace per app>"
666
714
  test_args="<replace per app>"
667
715
  case appli[:product]
@@ -704,7 +752,7 @@ END_OF_TEMPLATE
704
752
  # make username mandatory for jwt, this triggers interactive input
705
753
  self.options.get_option(:username,:mandatory)
706
754
  # instanciate AoC plugin, so that command line options are known
707
- files_plugin=self.class.plugin_new(plugin_name,@agents.merge({skip_basic_auth_options: true, private_key_path: private_key_path}))
755
+ files_plugin=self.class.plugin_class(plugin_name).new(@agents.merge({skip_basic_auth_options: true, private_key_path: private_key_path}))
708
756
  aoc_api=files_plugin.get_api
709
757
  auto_set_pub_key=false
710
758
  auto_set_jwt=false
@@ -786,7 +834,7 @@ END_OF_TEMPLATE
786
834
  # need url / username
787
835
  add_plugin_default_preset(AOC_COMMAND_V3.to_sym)
788
836
  # instanciate AoC plugin
789
- files_plugin=self.class.plugin_new(AOC_COMMAND_CURRENT,@agents) # TODO: is this line needed ?
837
+ files_plugin=self.class.plugin_class(AOC_COMMAND_CURRENT).new(@agents) # TODO: is this line needed ?
790
838
  url=self.options.get_option(:url,:mandatory)
791
839
  cli_conf_file=Fasp::Installation.instance.cli_conf_file
792
840
  data=JSON.parse(File.read(cli_conf_file))
@@ -818,7 +866,7 @@ END_OF_TEMPLATE
818
866
  when :detect
819
867
  # need url / username
820
868
  BasicAuthPlugin.new(@agents)
821
- return Main.result_status("Found: #{ApiDetector.discover_product(self.options.get_option(:url,:mandatory))}")
869
+ return Main.result_status("Found: #{identify_plugin_for_url(self.options.get_option(:url,:mandatory))}")
822
870
  when :coffee
823
871
  OpenApplication.instance.uri('https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg')
824
872
  return Main.result_nothing
@@ -834,13 +882,13 @@ END_OF_TEMPLATE
834
882
  send_email_template({},EMAIL_TEST_TEMPLATE)
835
883
  return Main.result_nothing
836
884
  when :smtp_settings
837
- return {:type=>:single_object,:data=>email_settings}
885
+ return {type: :single_object, data: email_settings}
838
886
  when :proxy_check
839
887
  pac_url=self.options.get_option(:fpac,:mandatory)
840
888
  server_url=self.options.get_next_argument('server url')
841
889
  return Main.result_status(Aspera::ProxyAutoConfig.new(UriReader.read(pac_url)).get_proxy(server_url))
842
890
  when :check_update
843
- return {:type=>:single_object, :data=>check_gem_version}
891
+ return {type: :single_object, data: check_gem_version}
844
892
  when :initdemo
845
893
  if @config_presets.has_key?(DEMO_SERVER_PRESET)
846
894
  Log.log.warn("Demo server preset already present: #{DEMO_SERVER_PRESET}")
@@ -858,6 +906,46 @@ END_OF_TEMPLATE
858
906
  end
859
907
  save_presets_to_config_file
860
908
  return Main.result_status("Done")
909
+ when :vault
910
+ command=self.options.get_next_command([:init,:list,:get,:set,:delete])
911
+ case command
912
+ when :init
913
+ type=self.options.get_option(:value,:optional)
914
+ case type
915
+ when 'config',NilClass
916
+ raise "default secrets already exists" if @config_presets.has_key?(CONF_PRESET_SECRETS)
917
+ @config_presets[CONF_PRESET_SECRETS]={}
918
+ set_global_default(:secrets,"@preset:#{CONF_PRESET_SECRETS}")
919
+ else raise "no such vault type"
920
+ end
921
+ return Main.result_status("Done")
922
+ when :list
923
+ return {type: :object_list, data: vault.list}
924
+ when :set
925
+ # register url option
926
+ BasicAuthPlugin.new(@agents.merge(skip_option_header: true))
927
+ username=self.options.get_option(:username,:mandatory)
928
+ url=self.options.get_option(:url,:mandatory)
929
+ description=self.options.get_option(:value,:optional)
930
+ secret=self.options.get_next_argument('secret')
931
+ vault.set(username: username, url: url, description: description, secret: secret)
932
+ save_presets_to_config_file if vault.is_a?(Keychain::EncryptedHash)
933
+ return Main.result_status("Done")
934
+ when :get
935
+ # register url option
936
+ BasicAuthPlugin.new(@agents.merge(skip_option_header: true))
937
+ username=self.options.get_option(:username,:mandatory)
938
+ url=self.options.get_option(:url,:optional)
939
+ result=vault.get(username: username, url: url)
940
+ return {type: :single_object, data: result}
941
+ when :delete
942
+ # register url option
943
+ BasicAuthPlugin.new(@agents.merge(skip_option_header: true))
944
+ username=self.options.get_option(:username,:mandatory)
945
+ url=self.options.get_option(:url,:optional)
946
+ result=vault.delete(username: username, url: url)
947
+ return Main.result_status("Done")
948
+ end
861
949
  else raise 'INTERNAL ERROR: wrong case'
862
950
  end
863
951
  end
@@ -941,6 +1029,42 @@ END_OF_TEMPLATE
941
1029
  return nil
942
1030
  end # get_plugin_default_config_name
943
1031
 
1032
+ def vault
1033
+ if @vault.nil?
1034
+ vault_info=self.options.get_option(:secrets,:optional)
1035
+ case vault_info
1036
+ when Hash
1037
+ @vault=Keychain::EncryptedHash.new(vault_info)
1038
+ when /^system/
1039
+ name=vault_info.start_with?('system:') ? vault_info[7..-1] : nil
1040
+ case Environment.os
1041
+ when Environment::OS_X
1042
+ @vault=Keychain::MacosSecurity.new(name)
1043
+ when Environment::OS_WINDOWS,Environment::OS_LINUX,Environment::OS_AIX
1044
+ raise "not implemented"
1045
+ else raise "Error"
1046
+ end
1047
+ when NilClass
1048
+ # keep nil
1049
+ else
1050
+ raise CliBadArgument,'secrets shall be Hash'
1051
+ end
1052
+ end
1053
+ raise "No vault defined" if @vault.nil?
1054
+ @vault
1055
+ end
1056
+
1057
+ def get_secret(options)
1058
+ raise "options shall be Hash" unless options.is_a?(Hash)
1059
+ raise "options shall have username" unless options.has_key?(:username)
1060
+ secret=self.options.get_option(:secret,:optional)
1061
+ if secret.nil?
1062
+ secret=vault.get(options) rescue nil
1063
+ # mandatory by default
1064
+ raise "please provide secret for #{options[:username]}" if secret.nil? and ( options[:mandatory].nil? or options[:mandatory] )
1065
+ end
1066
+ return secret
1067
+ end
944
1068
  end
945
1069
  end
946
1070
  end