aspera-cli 4.4.0 → 4.5.0

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