aspera-cli 4.2.1 → 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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1580 -946
  3. data/bin/ascli +1 -1
  4. data/bin/asession +3 -5
  5. data/docs/Makefile +8 -11
  6. data/docs/README.erb.md +1521 -829
  7. data/docs/doc_tools.rb +58 -0
  8. data/docs/test_env.conf +3 -1
  9. data/examples/faspex4.rb +28 -19
  10. data/examples/transfer.rb +2 -2
  11. data/lib/aspera/aoc.rb +157 -134
  12. data/lib/aspera/cli/listener/progress_multi.rb +5 -5
  13. data/lib/aspera/cli/main.rb +106 -48
  14. data/lib/aspera/cli/manager.rb +19 -20
  15. data/lib/aspera/cli/plugin.rb +22 -7
  16. data/lib/aspera/cli/plugins/aoc.rb +260 -208
  17. data/lib/aspera/cli/plugins/ats.rb +11 -10
  18. data/lib/aspera/cli/plugins/bss.rb +2 -2
  19. data/lib/aspera/cli/plugins/config.rb +360 -189
  20. data/lib/aspera/cli/plugins/faspex.rb +119 -56
  21. data/lib/aspera/cli/plugins/faspex5.rb +32 -17
  22. data/lib/aspera/cli/plugins/node.rb +72 -31
  23. data/lib/aspera/cli/plugins/orchestrator.rb +5 -3
  24. data/lib/aspera/cli/plugins/preview.rb +94 -68
  25. data/lib/aspera/cli/plugins/server.rb +16 -5
  26. data/lib/aspera/cli/plugins/shares.rb +17 -0
  27. data/lib/aspera/cli/transfer_agent.rb +64 -82
  28. data/lib/aspera/cli/version.rb +1 -1
  29. data/lib/aspera/command_line_builder.rb +48 -31
  30. data/lib/aspera/cos_node.rb +4 -3
  31. data/lib/aspera/environment.rb +4 -4
  32. data/lib/aspera/fasp/{manager.rb → agent_base.rb} +7 -6
  33. data/lib/aspera/fasp/{connect.rb → agent_connect.rb} +46 -39
  34. data/lib/aspera/fasp/{local.rb → agent_direct.rb} +42 -38
  35. data/lib/aspera/fasp/{http_gw.rb → agent_httpgw.rb} +50 -29
  36. data/lib/aspera/fasp/{node.rb → agent_node.rb} +43 -4
  37. data/lib/aspera/fasp/agent_trsdk.rb +106 -0
  38. data/lib/aspera/fasp/default.rb +17 -0
  39. data/lib/aspera/fasp/installation.rb +64 -48
  40. data/lib/aspera/fasp/parameters.rb +78 -91
  41. data/lib/aspera/fasp/parameters.yaml +531 -0
  42. data/lib/aspera/fasp/uri.rb +1 -1
  43. data/lib/aspera/faspex_gw.rb +12 -11
  44. data/lib/aspera/id_generator.rb +22 -0
  45. data/lib/aspera/keychain/encrypted_hash.rb +120 -0
  46. data/lib/aspera/keychain/macos_security.rb +94 -0
  47. data/lib/aspera/log.rb +45 -32
  48. data/lib/aspera/node.rb +9 -4
  49. data/lib/aspera/oauth.rb +116 -100
  50. data/lib/aspera/persistency_action_once.rb +11 -7
  51. data/lib/aspera/persistency_folder.rb +6 -26
  52. data/lib/aspera/rest.rb +66 -50
  53. data/lib/aspera/sync.rb +40 -35
  54. data/lib/aspera/timer_limiter.rb +22 -0
  55. metadata +86 -29
  56. data/docs/transfer_spec.html +0 -99
  57. data/lib/aspera/api_detector.rb +0 -60
  58. data/lib/aspera/fasp/aoc.rb +0 -24
  59. data/lib/aspera/secrets.rb +0 -20
@@ -1,18 +1,22 @@
1
1
  require 'aspera/cli/basic_auth_plugin'
2
2
  require 'aspera/cli/extended_value'
3
3
  require 'aspera/fasp/installation'
4
- require 'aspera/api_detector'
4
+ require 'aspera/fasp/parameters'
5
5
  require 'aspera/open_application'
6
6
  require 'aspera/aoc'
7
7
  require 'aspera/proxy_auto_config'
8
8
  require 'aspera/uri_reader'
9
9
  require 'aspera/rest'
10
10
  require 'aspera/persistency_action_once'
11
+ require 'aspera/id_generator'
12
+ require 'aspera/keychain/encrypted_hash'
13
+ require 'aspera/keychain/macos_security'
11
14
  require 'xmlsimple'
12
15
  require 'base64'
13
16
  require 'net/smtp'
14
17
  require 'open3'
15
18
  require 'date'
19
+ require 'erb'
16
20
 
17
21
  module Aspera
18
22
  module Cli
@@ -28,6 +32,7 @@ module Aspera
28
32
  CONF_PRESET_VERSION='version'
29
33
  CONF_PRESET_DEFAULT='default'
30
34
  CONF_PRESET_GLOBAL='global_common_defaults'
35
+ CONF_PRESET_SECRETS='default_secrets'
31
36
  CONF_PLUGIN_SYM = :config # Plugins::Config.name.split('::').last.downcase.to_sym
32
37
  CONF_GLOBAL_SYM = :config
33
38
  # old tool name
@@ -42,28 +47,53 @@ module Aspera
42
47
  AOC_COMMAND_V2='aspera'
43
48
  AOC_COMMAND_V3='aoc'
44
49
  AOC_COMMAND_CURRENT=AOC_COMMAND_V3
50
+ SERVER_COMMAND='server'
45
51
  CONNECT_WEB_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
46
52
  CONNECT_VERSIONS = 'connectversions.js'
47
- TRANSFER_SDK_ARCHIVE_URL = 'https://ibm.biz/aspera_sdk'
53
+ TRANSFER_SDK_ARCHIVE_URL = 'https://ibm.biz/aspera_transfer_sdk'
48
54
  DEMO='demo'
49
55
  DEMO_SERVER_PRESET='demoserver'
50
56
  AOC_PATH_API_CLIENTS='admin/api-clients'
57
+ EMAIL_TEST_TEMPLATE=<<END_OF_TEMPLATE
58
+ From: <%=from_name%> <<%=from_email%>>
59
+ To: <<%=to%>>
60
+ Subject: Amelia email test
61
+
62
+ It worked !
63
+ END_OF_TEMPLATE
64
+ # special extended values
65
+ EXTV_INCLUDE_PRESETS='incps'
66
+ EXTV_PRESET='preset'
67
+ DEFAULT_CHECK_NEW_VERSION_DAYS=7
68
+ DEFAULT_PRIV_KEY_FILENAME='aspera_aoc_key'
69
+ DEFAULT_PRIVKEY_LENGTH=4096
70
+ private_constant :DEFAULT_CONFIG_FILENAME,:CONF_PRESET_CONFIG,:CONF_PRESET_VERSION,:CONF_PRESET_DEFAULT,
71
+ :CONF_PRESET_GLOBAL,:PROGRAM_NAME_V1,:PROGRAM_NAME_V2,:DEFAULT_REDIRECT,:ASPERA_PLUGINS_FOLDERNAME,
72
+ :RUBY_FILE_EXT,:AOC_COMMAND_V1,:AOC_COMMAND_V2,:AOC_COMMAND_V3,:AOC_COMMAND_CURRENT,:DEMO,
73
+ :TRANSFER_SDK_ARCHIVE_URL,:AOC_PATH_API_CLIENTS,:DEMO_SERVER_PRESET,:EMAIL_TEST_TEMPLATE,:EXTV_INCLUDE_PRESETS,
74
+ :EXTV_PRESET,:DEFAULT_CHECK_NEW_VERSION_DAYS,:DEFAULT_PRIV_KEY_FILENAME,:SERVER_COMMAND,:CONF_PRESET_SECRETS
51
75
  def option_preset; nil; end
52
76
 
53
77
  def option_preset=(value)
54
- self.options.add_option_preset(preset_by_name(value))
78
+ case value
79
+ when String
80
+ self.options.add_option_preset(preset_by_name(value))
81
+ when Hash
82
+ self.options.add_option_preset(value)
83
+ else
84
+ raise "Preset definition must be a String for name, or Hash for value"
85
+ end
86
+ nil
55
87
  end
56
88
 
57
- private_constant :DEFAULT_CONFIG_FILENAME,:CONF_PRESET_CONFIG,:CONF_PRESET_VERSION,:CONF_PRESET_DEFAULT,:CONF_PRESET_GLOBAL,:PROGRAM_NAME_V1,:PROGRAM_NAME_V2,:DEFAULT_REDIRECT,:ASPERA_PLUGINS_FOLDERNAME,:RUBY_FILE_EXT,:AOC_COMMAND_V1,:AOC_COMMAND_V2,:AOC_COMMAND_V3,:AOC_COMMAND_CURRENT,:DEMO,:TRANSFER_SDK_ARCHIVE_URL,:AOC_PATH_API_CLIENTS,:DEMO_SERVER_PRESET
58
-
59
89
  def initialize(env,tool_name,help_url,version,main_folder)
60
90
  super(env)
61
- raise 'missing secret manager' if @agents[:secret].nil?
62
91
  @plugins={}
63
92
  @plugin_lookup_folders=[]
64
93
  @use_plugin_defaults=true
65
94
  @config_presets=nil
66
95
  @connect_versions=nil
96
+ @vault=nil
67
97
  @program_version=version
68
98
  @tool_name=tool_name
69
99
  @help_url=help_url
@@ -83,35 +113,38 @@ module Aspera
83
113
  # add preset handler (needed for smtp)
84
114
  ExtendedValue.instance.set_handler(EXTV_PRESET,:reader,lambda{|v|preset_by_name(v)})
85
115
  ExtendedValue.instance.set_handler(EXTV_INCLUDE_PRESETS,:decoder,lambda{|v|expanded_with_preset_includes(v)})
86
- self.options.set_obj_attr(:override,self,:option_override,:no)
116
+ # load defaults before it can be overriden
117
+ self.add_plugin_default_preset(CONF_GLOBAL_SYM)
118
+ self.options.parse_options!
87
119
  self.options.set_obj_attr(:ascp_path,self,:option_ascp_path)
88
120
  self.options.set_obj_attr(:use_product,self,:option_use_product)
89
121
  self.options.set_obj_attr(:preset,self,:option_preset)
90
- self.options.set_obj_attr(:secret,@agents[:secret],:default_secret)
91
- self.options.set_obj_attr(:secrets,@agents[:secret],:all_secrets)
92
- self.options.add_opt_boolean(:override,'override existing value')
93
122
  self.options.add_opt_switch(:no_default,'-N','do not load default configuration for plugin') { @use_plugin_defaults=false }
94
- self.options.add_opt_boolean(:use_generic_client,'wizard: AoC: use global or org specific jwt client id')
95
- self.options.add_opt_simple(:pkeypath,'path to private key for JWT (wizard)')
123
+ self.options.add_opt_boolean(:override,'Wizard: override existing value')
124
+ self.options.add_opt_boolean(:use_generic_client,'Wizard: AoC: use global or org specific jwt client id')
125
+ self.options.add_opt_boolean(:default,'Wizard: set as default configuration for specified plugin (also: update)')
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')
128
+ self.options.add_opt_simple(:pkeypath,'Wizard: path to private key for JWT')
96
129
  self.options.add_opt_simple(:ascp_path,'path to ascp')
97
130
  self.options.add_opt_simple(:use_product,'use ascp from specified product')
98
131
  self.options.add_opt_simple(:smtp,'smtp configuration (extended value: hash)')
99
132
  self.options.add_opt_simple(:fpac,'proxy auto configuration URL')
100
- self.options.add_opt_simple(:preset,'-PVALUE','load the named option preset from current config file')
101
- self.options.add_opt_simple(:default,'set as default configuration for specified plugin')
102
133
  self.options.add_opt_simple(:secret,'default secret')
103
- self.options.add_opt_simple(:secrets,'secret repository (Hash)')
134
+ self.options.add_opt_simple(:secrets,'secret vault')
104
135
  self.options.add_opt_simple(:sdk_url,'URL to get SDK')
105
- self.options.add_opt_simple(:sdk_folder,'SDK folder location')
106
- self.options.add_opt_boolean(:test_mode,'skip user validation in wizard mode')
107
- self.options.add_opt_simple(:version_check_days,Integer,'period to check neew version in days (zero to disable)')
136
+ self.options.add_opt_simple(:sdk_folder,'SDK folder path')
137
+ self.options.add_opt_simple(:notif_to,'email recipient for notification of transfers')
138
+ self.options.add_opt_simple(:notif_template,'email ERB template for notification of transfers')
139
+ self.options.add_opt_simple(:version_check_days,Integer,'period in days to check new version (zero to disable)')
108
140
  self.options.set_option(:use_generic_client,true)
109
141
  self.options.set_option(:test_mode,false)
110
- self.options.set_option(:version_check_days,7)
142
+ self.options.set_option(:default,true)
143
+ self.options.set_option(:version_check_days,DEFAULT_CHECK_NEW_VERSION_DAYS)
111
144
  self.options.set_option(:sdk_url,TRANSFER_SDK_ARCHIVE_URL)
112
145
  self.options.set_option(:sdk_folder,File.join(@main_folder,'sdk'))
146
+ self.options.set_option(:override,:no)
113
147
  self.options.parse_options!
114
- raise CliBadArgument,'secrets shall be Hash' unless @agents[:secret].all_secrets.is_a?(Hash)
115
148
  Fasp::Installation.instance.folder=self.options.get_option(:sdk_folder,:mandatory)
116
149
  end
117
150
 
@@ -137,7 +170,7 @@ module Aspera
137
170
  check_date_persist=PersistencyActionOnce.new(
138
171
  manager: persistency,
139
172
  data: last_check_array,
140
- ids: ['version_last_check'])
173
+ id: 'version_last_check')
141
174
  # get persisted date or nil
142
175
  last_check_date = begin
143
176
  Date.strptime(last_check_array.first, '%Y/%m/%d')
@@ -160,8 +193,8 @@ module Aspera
160
193
  # retrieve structure from cloud (CDN) with all versions available
161
194
  def connect_versions
162
195
  if @connect_versions.nil?
163
- api_connect_cdn=Rest.new({:base_url=>CONNECT_WEB_URL})
164
- 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})
165
198
  # get result on one line
166
199
  connect_versions_javascript=javascript[:http].body.gsub(/\r?\n\s*/,'')
167
200
  Log.log.debug("javascript=[\n#{connect_versions_javascript}\n]")
@@ -186,9 +219,9 @@ module Aspera
186
219
  end
187
220
  private
188
221
 
189
- def generate_new_key(private_key_path)
222
+ def generate_rsa_private_key(private_key_path,length)
190
223
  require 'openssl'
191
- priv_key = OpenSSL::PKey::RSA.new(4096)
224
+ priv_key = OpenSSL::PKey::RSA.new(length)
192
225
  File.write(private_key_path,priv_key.to_s)
193
226
  File.write(private_key_path+'.pub',priv_key.public_key.to_s)
194
227
  nil
@@ -207,9 +240,9 @@ module Aspera
207
240
 
208
241
  # instanciate a plugin
209
242
  # plugins must be Capitalized
210
- def self.plugin_new(plugin_name_sym,env)
243
+ def self.plugin_class(plugin_name_sym)
211
244
  # Module.nesting[2] is Aspera::Cli
212
- 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}")
213
246
  end
214
247
 
215
248
  def self.flatten_all_config(t)
@@ -224,10 +257,15 @@ module Aspera
224
257
 
225
258
  # set parameter and value in global config
226
259
  # creates one if none already created
227
- # @return preset that contains global default
260
+ # @return preset name that contains global default
228
261
  def set_global_default(key,value)
229
262
  # get default preset if it exists
230
- 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
231
269
  @config_presets[global_default_preset_name]||={}
232
270
  @config_presets[global_default_preset_name][key.to_s]=value
233
271
  self.format.display_status("Updated: #{global_default_preset_name}: #{key} <- #{value}")
@@ -241,12 +279,8 @@ module Aspera
241
279
  attr_reader :main_folder
242
280
  attr_reader :gem_url
243
281
  attr_reader :plugins
244
- attr_accessor :option_override
245
282
  attr_accessor :option_config_file
246
283
 
247
- EXTV_INCLUDE_PRESETS='incps'
248
- EXTV_PRESET='preset'
249
-
250
284
  # @return the hash from name (also expands possible includes)
251
285
  def preset_by_name(config_name, include_path=[])
252
286
  raise CliError,"no such config preset: #{config_name}" unless @config_presets.has_key?(config_name)
@@ -331,11 +365,11 @@ module Aspera
331
365
  Log.log.warn("No config file found. Creating empty configuration file: #{@option_config_file}")
332
366
  @config_presets={CONF_PRESET_CONFIG=>{CONF_PRESET_VERSION=>@program_version}}
333
367
  else
334
- Log.log.debug "loading #{@option_config_file}"
368
+ Log.log.debug("loading #{@option_config_file}")
335
369
  @config_presets=YAML.load_file(conf_file_to_load)
336
370
  end
337
371
  files_to_copy=[]
338
- Log.log.debug "Available_presets: #{@config_presets}"
372
+ Log.log.debug("Available_presets: #{@config_presets}")
339
373
  raise 'Expecting YAML Hash' unless @config_presets.is_a?(Hash)
340
374
  # check there is at least the config section
341
375
  if !@config_presets.has_key?(CONF_PRESET_CONFIG)
@@ -419,14 +453,42 @@ module Aspera
419
453
  Log.log.warn("skipping plugin already registered: #{plugin_symbol}")
420
454
  return
421
455
  end
422
- @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}"
423
485
  end
424
486
 
425
487
  def execute_connect_action
426
488
  command=self.options.get_next_command([:list,:id])
427
489
  case command
428
490
  when :list
429
- return {:type=>:object_list, :data=>connect_versions, :fields => ['id','title','version']}
491
+ return {type: :object_list, data: connect_versions, fields: ['id','title','version']}
430
492
  when :id
431
493
  connect_id=self.options.get_next_argument('id or title')
432
494
  one_res=connect_versions.select{|i|i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}.first
@@ -435,13 +497,13 @@ module Aspera
435
497
  case command
436
498
  when :info # shows files used
437
499
  one_res.delete('links')
438
- return {:type=>:single_object, :data=>one_res}
500
+ return {type: :single_object, data: one_res}
439
501
  when :links # shows files used
440
502
  command=self.options.get_next_command([:list,:id])
441
503
  all_links=one_res['links']
442
504
  case command
443
505
  when :list # shows files used
444
- return {:type=>:object_list, :data=>all_links}
506
+ return {type: :object_list, data: all_links}
445
507
  when :id
446
508
  link_title=self.options.get_next_argument('title')
447
509
  one_link=all_links.select {|i| i['title'].eql?(link_title)}.first
@@ -450,10 +512,10 @@ module Aspera
450
512
  when :download #
451
513
  folder_dest=self.transfer.destination_folder('receive')
452
514
  #folder_dest=self.options.get_next_argument('destination folder')
453
- api_connect_cdn=Rest.new({:base_url=>CONNECT_WEB_URL})
515
+ api_connect_cdn=Rest.new({base_url: CONNECT_WEB_URL})
454
516
  fileurl = one_link['href']
455
517
  filename=fileurl.gsub(%r{.*/},'')
456
- 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)})
457
519
  return Main.result_status("Downloaded: #{filename}")
458
520
  when :open #
459
521
  OpenApplication.instance.uri(one_link['href'])
@@ -465,7 +527,7 @@ module Aspera
465
527
  end
466
528
 
467
529
  def execute_action_ascp
468
- command=self.options.get_next_command([:connect,:use,:show,:products,:info,:install])
530
+ command=self.options.get_next_command([:connect,:use,:show,:products,:info,:install,:spec])
469
531
  case command
470
532
  when :connect
471
533
  return execute_connect_action
@@ -476,7 +538,7 @@ module Aspera
476
538
  preset_name=set_global_default(:ascp_path,ascp_path)
477
539
  return Main.result_status("Saved to default global preset #{preset_name}")
478
540
  when :show # shows files used
479
- return {:type=>:status, :data=>Fasp::Installation.instance.path(:ascp)}
541
+ return {type: :status, data: Fasp::Installation.instance.path(:ascp)}
480
542
  when :info # shows files used
481
543
  data=Fasp::Installation::FILES.inject({}) do |m,v|
482
544
  m[v.to_s]=Fasp::Installation.instance.path(v) rescue 'Not Found'
@@ -501,12 +563,12 @@ module Aspera
501
563
  end
502
564
  end
503
565
  data['keypass']=Fasp::Installation.instance.bypass_pass
504
- return {:type=>:single_object, :data=>data}
566
+ return {type: :single_object, data: data}
505
567
  when :products
506
568
  command=self.options.get_next_command([:list,:use])
507
569
  case command
508
570
  when :list
509
- 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']}
510
572
  when :use
511
573
  default_product=self.options.get_next_argument('product name')
512
574
  Fasp::Installation.instance.use_ascp_from_product(default_product)
@@ -516,131 +578,155 @@ module Aspera
516
578
  when :install
517
579
  v=Fasp::Installation.instance.install_sdk(self.options.get_option(:sdk_url,:mandatory))
518
580
  return Main.result_status("Installed version #{v}")
581
+ when :spec
582
+ return {type: :object_list, data: Fasp::Parameters.man_table, fields: ['name','type',Fasp::Parameters::SUPPORTED_AGENTS_SHORT.map{|i|i.to_s},'description'].flatten}
519
583
  end
520
584
  raise "unexpected case: #{command}"
521
585
  end
522
586
 
523
- 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
524
670
 
525
671
  # "config" plugin
526
672
  def execute_action
527
673
  action=self.options.get_next_command(ACTIONS)
528
674
  case action
529
- when :id
530
- config_name=self.options.get_next_argument('config name')
531
- action=self.options.get_next_command([:show,:delete,:set,:get,:unset,:initialize,:update,:ask])
532
- # those operations require existing option
533
- raise "no such preset: #{config_name}" if [:show,:delete,:get,:unset].include?(action) and !@config_presets.has_key?(config_name)
534
- selected_preset=@config_presets[config_name]
535
- case action
536
- when :show
537
- raise "no such config: #{config_name}" if selected_preset.nil?
538
- return {:type=>:single_object,:data=>selected_preset}
539
- when :delete
540
- @config_presets.delete(config_name)
541
- save_presets_to_config_file
542
- return Main.result_status("Deleted: #{config_name}")
543
- when :get
544
- param_name=self.options.get_next_argument('parameter name')
545
- value=selected_preset[param_name]
546
- raise "no such option in preset #{config_name} : #{param_name}" if value.nil?
547
- case value
548
- when Numeric,String; return {:type=>:text,:data=>ExtendedValue.instance.evaluate(value.to_s)}
549
- end
550
- return {:type=>:single_object,:data=>value}
551
- when :unset
552
- param_name=self.options.get_next_argument('parameter name')
553
- selected_preset.delete(param_name)
554
- save_presets_to_config_file
555
- return Main.result_status("Removed: #{config_name}: #{param_name}")
556
- when :set
557
- param_name=self.options.get_next_argument('parameter name')
558
- param_value=self.options.get_next_argument('parameter value')
559
- if !@config_presets.has_key?(config_name)
560
- Log.log.debug("no such config name: #{config_name}, initializing")
561
- selected_preset=@config_presets[config_name]={}
562
- end
563
- if selected_preset.has_key?(param_name)
564
- Log.log.warn("overwriting value: #{selected_preset[param_name]}")
565
- end
566
- selected_preset[param_name]=param_value
567
- save_presets_to_config_file
568
- return Main.result_status("Updated: #{config_name}: #{param_name} <- #{param_value}")
569
- when :initialize
570
- config_value=self.options.get_next_argument('extended value (Hash)')
571
- if @config_presets.has_key?(config_name)
572
- Log.log.warn("configuration already exists: #{config_name}, overwriting")
573
- end
574
- @config_presets[config_name]=config_value
575
- save_presets_to_config_file
576
- return Main.result_status("Modified: #{@option_config_file}")
577
- when :update
578
- default_for_plugin=self.options.get_option(:default,:optional)
579
- # get unprocessed options
580
- theopts=self.options.get_options_table
581
- Log.log.debug("opts=#{theopts}")
582
- @config_presets[config_name]||={}
583
- @config_presets[config_name].merge!(theopts)
584
- if ! default_for_plugin.nil?
585
- @config_presets[CONF_PRESET_DEFAULT]||=Hash.new
586
- @config_presets[CONF_PRESET_DEFAULT][default_for_plugin]=config_name
587
- end
588
- save_presets_to_config_file
589
- return Main.result_status("Updated: #{config_name}")
590
- when :ask
591
- self.options.ask_missing_mandatory=:yes
592
- @config_presets[config_name]||={}
593
- self.options.get_next_argument('option names',:multiple).each do |optionname|
594
- option_value=self.options.get_interactive(:option,optionname)
595
- @config_presets[config_name][optionname]=option_value
596
- end
597
- save_presets_to_config_file
598
- return Main.result_status("Updated: #{config_name}")
599
- 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
600
684
  when :documentation
601
685
  section=options.get_next_argument('private key file path',:single,:optional)
602
686
  section='#'+section unless section.nil?
603
687
  OpenApplication.instance.uri("#{@help_url}#{section}")
604
688
  return Main.result_nothing
605
- when :open
606
- OpenApplication.instance.uri("#{@option_config_file}") #file://
607
- return Main.result_nothing
608
689
  when :genkey # generate new rsa key
609
690
  private_key_path=self.options.get_next_argument('private key file path')
610
- generate_new_key(private_key_path)
691
+ generate_rsa_private_key(private_key_path,DEFAULT_PRIVKEY_LENGTH)
611
692
  return Main.result_status('Generated key: '+private_key_path)
612
693
  when :echo # display the content of a value given on command line
613
- result={:type=>:other_struct, :data=>self.options.get_next_argument('value')}
694
+ result={type: :other_struct, data: self.options.get_next_argument('value')}
614
695
  # special for csv
615
696
  result[:type]=:object_list if result[:data].is_a?(Array) and result[:data].first.is_a?(Hash)
616
697
  return result
617
698
  when :flush_tokens
618
699
  deleted_files=Oauth.flush_tokens
619
- return {:type=>:value_list, :name=>'file',:data=>deleted_files}
700
+ return {type: :value_list, data: deleted_files, name: 'file'}
620
701
  when :plugins
621
- return {:data => @plugins.keys.map { |i| { 'plugin' => i.to_s, 'path' => @plugins[i][:source] } } , :fields => ['plugin','path'], :type => :object_list }
622
- when :list
623
- return {:data => @config_presets.keys, :type => :value_list, :name => 'name'}
624
- when :overview
625
- 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']}
626
703
  when :wizard
704
+ # interactive mode
627
705
  self.options.ask_missing_mandatory=true
628
706
  # register url option
629
707
  BasicAuthPlugin.new(@agents.merge(skip_option_header: true))
708
+ # get from option, or ask
630
709
  instance_url=self.options.get_option(:url,:mandatory)
631
- appli=ApiDetector.discover_product(instance_url)
710
+ # allow user to tell the preset name
711
+ preset_name=self.options.get_option(:id,:optional)
712
+ appli=identify_plugin_for_url(instance_url)
713
+ plugin_name="<replace per app>"
714
+ test_args="<replace per app>"
632
715
  case appli[:product]
633
716
  when :aoc
634
717
  self.format.display_status('Detected: Aspera on Cloud'.bold)
718
+ plugin_name=AOC_COMMAND_CURRENT
635
719
  organization,instance_domain=AoC.parse_url(instance_url)
636
- aspera_preset_name='aoc_'+organization
637
- self.format.display_status("Preparing preset: #{aspera_preset_name}")
720
+ # if not defined by user, generate name
721
+ preset_name=[appli[:product],organization].join('_') if preset_name.nil?
722
+ self.format.display_status("Preparing preset: #{preset_name}")
638
723
  # init defaults if necessary
639
- @config_presets[CONF_PRESET_DEFAULT]||=Hash.new
640
- if !option_override
641
- raise CliError,"a default configuration already exists for plugin '#{AOC_COMMAND_CURRENT}' (use --override=yes)" if @config_presets[CONF_PRESET_DEFAULT].has_key?(AOC_COMMAND_CURRENT)
642
- raise CliError,"preset already exists: #{aspera_preset_name} (use --override=yes)" if @config_presets.has_key?(aspera_preset_name)
643
- end
724
+ @config_presets[CONF_PRESET_DEFAULT]||={}
725
+ option_override=self.options.get_option(:override,:mandatory)
726
+ option_default=self.options.get_option(:default,:mandatory)
727
+ Log.log.error("override=#{option_override} -> #{option_override.class}")
728
+ raise CliError,"A default configuration already exists for plugin '#{plugin_name}' (use --override=yes or --default=no)" if !option_override and option_default and @config_presets[CONF_PRESET_DEFAULT].has_key?(plugin_name)
729
+ raise CliError,"Preset already exists: #{preset_name} (use --override=yes or --id=<name>)" if !option_override and @config_presets.has_key?(preset_name)
644
730
  # lets see if path to priv key is provided
645
731
  private_key_path=self.options.get_option(:pkeypath,:optional)
646
732
  # give a chance to provide
@@ -650,23 +736,23 @@ module Aspera
650
736
  end
651
737
  # else generate path
652
738
  if private_key_path.empty?
653
- private_key_path=File.join(@main_folder,'aspera_aoc_key')
739
+ private_key_path=File.join(@main_folder,DEFAULT_PRIV_KEY_FILENAME)
654
740
  end
655
741
  if File.exist?(private_key_path)
656
742
  self.format.display_status('Using existing key:')
657
743
  else
658
- self.format.display_status('Generating key...')
659
- generate_new_key(private_key_path)
744
+ self.format.display_status("Generating #{DEFAULT_PRIVKEY_LENGTH} bit RSA key...")
745
+ generate_rsa_private_key(private_key_path,DEFAULT_PRIVKEY_LENGTH)
660
746
  self.format.display_status('Created:')
661
747
  end
662
- self.format.display_status("#{private_key_path}")
748
+ self.format.display_status(private_key_path)
663
749
  pub_key_pem=OpenSSL::PKey::RSA.new(File.read(private_key_path)).public_key.to_s
664
750
  # declare command line options for AoC
665
751
  require 'aspera/cli/plugins/aoc'
666
752
  # make username mandatory for jwt, this triggers interactive input
667
753
  self.options.get_option(:username,:mandatory)
668
754
  # instanciate AoC plugin, so that command line options are known
669
- files_plugin=self.class.plugin_new(AOC_COMMAND_CURRENT,@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}))
670
756
  aoc_api=files_plugin.get_api
671
757
  auto_set_pub_key=false
672
758
  auto_set_jwt=false
@@ -709,7 +795,7 @@ module Aspera
709
795
  end
710
796
  myself=aoc_api.read('self')[:data]
711
797
  if auto_set_pub_key
712
- raise CliError,'public key is already set in profile (use --override=yes)' unless myself['public_key'].empty? or option_override
798
+ raise CliError,'Public key is already set in profile (use --override=yes)' unless myself['public_key'].empty? or option_override
713
799
  self.format.display_status('Updating profile with new key')
714
800
  aoc_api.update("users/#{myself['id']}",{'public_key'=>pub_key_pem})
715
801
  end
@@ -717,8 +803,8 @@ module Aspera
717
803
  self.format.display_status('Enabling JWT for client')
718
804
  aoc_api.update("clients/#{self.options.get_option(:client_id)}",{'jwt_grant_enabled'=>true,'explicit_authorization_required'=>false})
719
805
  end
720
- self.format.display_status("creating new config preset: #{aspera_preset_name}")
721
- @config_presets[aspera_preset_name]={
806
+ self.format.display_status("Creating new config preset: #{preset_name}")
807
+ @config_presets[preset_name]={
722
808
  :url.to_s =>self.options.get_option(:url),
723
809
  :username.to_s =>myself['email'],
724
810
  :auth.to_s =>:jwt.to_s,
@@ -727,23 +813,28 @@ module Aspera
727
813
  # set only if non nil
728
814
  [:client_id,:client_secret].each do |s|
729
815
  o=self.options.get_option(s)
730
- @config_presets[s.to_s] = o unless o.nil?
816
+ @config_presets[preset_name][s.to_s] = o unless o.nil?
731
817
  end
732
- self.format.display_status("Setting config preset as default for #{AOC_COMMAND_CURRENT}")
733
- @config_presets[CONF_PRESET_DEFAULT][AOC_COMMAND_CURRENT]=aspera_preset_name
734
- self.format.display_status('saving config file')
735
- save_presets_to_config_file
736
- return Main.result_status("Done.\nYou can test with:\n#{@tool_name} #{AOC_COMMAND_CURRENT} user info show")
818
+ test_args="#{plugin_name} user info show"
737
819
  else
738
820
  raise CliBadArgument,"Supports only: aoc. Detected: #{appli}"
821
+ end # product
822
+ if option_default
823
+ self.format.display_status("Setting config preset as default for #{plugin_name}")
824
+ @config_presets[CONF_PRESET_DEFAULT][plugin_name]=preset_name
825
+ else
826
+ test_args="-P#{preset_name} #{test_args}"
739
827
  end
828
+ self.format.display_status('Saving config file.')
829
+ save_presets_to_config_file
830
+ return Main.result_status("Done.\nYou can test with:\n#{@tool_name} #{test_args}")
740
831
  when :export_to_cli
741
832
  self.format.display_status('Exporting: Aspera on Cloud')
742
833
  require 'aspera/cli/plugins/aoc'
743
834
  # need url / username
744
835
  add_plugin_default_preset(AOC_COMMAND_V3.to_sym)
745
836
  # instanciate AoC plugin
746
- 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 ?
747
838
  url=self.options.get_option(:url,:mandatory)
748
839
  cli_conf_file=Fasp::Installation.instance.cli_conf_file
749
840
  data=JSON.parse(File.read(cli_conf_file))
@@ -775,7 +866,7 @@ module Aspera
775
866
  when :detect
776
867
  # need url / username
777
868
  BasicAuthPlugin.new(@agents)
778
- 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))}")
779
870
  when :coffee
780
871
  OpenApplication.instance.uri('https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg')
781
872
  return Main.result_nothing
@@ -788,21 +879,16 @@ module Aspera
788
879
  when :file
789
880
  return Main.result_status(@option_config_file)
790
881
  when :email_test
791
- dest_email=self.options.get_next_argument('destination email')
792
- send_email({
793
- to: dest_email,
794
- subject: 'Amelia email test',
795
- body: 'It worked !',
796
- })
882
+ send_email_template({},EMAIL_TEST_TEMPLATE)
797
883
  return Main.result_nothing
798
884
  when :smtp_settings
799
- return {:type=>:single_object,:data=>email_settings}
885
+ return {type: :single_object, data: email_settings}
800
886
  when :proxy_check
801
887
  pac_url=self.options.get_option(:fpac,:mandatory)
802
888
  server_url=self.options.get_next_argument('server url')
803
889
  return Main.result_status(Aspera::ProxyAutoConfig.new(UriReader.read(pac_url)).get_proxy(server_url))
804
890
  when :check_update
805
- return {:type=>:single_object, :data=>check_gem_version}
891
+ return {type: :single_object, data: check_gem_version}
806
892
  when :initdemo
807
893
  if @config_presets.has_key?(DEMO_SERVER_PRESET)
808
894
  Log.log.warn("Demo server preset already present: #{DEMO_SERVER_PRESET}")
@@ -811,22 +897,63 @@ module Aspera
811
897
  @config_presets[DEMO_SERVER_PRESET]={'url'=>'ssh://'+DEMO+'.asperasoft.com:33001','username'=>AOC_COMMAND_V2,'ssAP'.downcase.reverse+'drow'.reverse=>DEMO+AOC_COMMAND_V2}
812
898
  end
813
899
  @config_presets[CONF_PRESET_DEFAULT]||={}
814
- if @config_presets[CONF_PRESET_DEFAULT].has_key?('server')
815
- Log.log.warn("server default preset already set to: #{@config_presets[CONF_PRESET_DEFAULT]['server']}")
816
- Log.log.warn("use #{DEMO_SERVER_PRESET} for demo: -P#{DEMO_SERVER_PRESET}") unless DEMO_SERVER_PRESET.eql?(@config_presets[CONF_PRESET_DEFAULT]['server'])
900
+ if @config_presets[CONF_PRESET_DEFAULT].has_key?(SERVER_COMMAND)
901
+ Log.log.warn("Server default preset already set to: #{@config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND]}")
902
+ Log.log.warn("Use #{DEMO_SERVER_PRESET} for demo: -P#{DEMO_SERVER_PRESET}") unless DEMO_SERVER_PRESET.eql?(@config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND])
817
903
  else
818
- @config_presets[CONF_PRESET_DEFAULT]['server']=DEMO_SERVER_PRESET
819
- Log.log.info("setting server default preset to : #{DEMO_SERVER_PRESET}")
904
+ @config_presets[CONF_PRESET_DEFAULT][SERVER_COMMAND]=DEMO_SERVER_PRESET
905
+ Log.log.info("Setting server default preset to : #{DEMO_SERVER_PRESET}")
820
906
  end
821
907
  save_presets_to_config_file
822
908
  return Main.result_status("Done")
823
- else raise 'error'
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
949
+ else raise 'INTERNAL ERROR: wrong case'
824
950
  end
825
951
  end
826
952
 
953
+ # @return email server setting with defaults if not defined
827
954
  def email_settings
828
955
  smtp=self.options.get_option(:smtp,:mandatory)
829
- # change string keys into symbols
956
+ # change string keys into symbol keys
830
957
  smtp=smtp.keys.inject({}){|m,v|m[v.to_sym]=smtp[v];m}
831
958
  # defaults
832
959
  smtp[:tls]||=true
@@ -836,41 +963,49 @@ module Aspera
836
963
  smtp[:domain]||=smtp[:from_email].gsub(/^.*@/,'') if smtp.has_key?(:from_email)
837
964
  # check minimum required
838
965
  [:server,:port,:domain].each do |n|
839
- raise "missing smtp parameter: #{n}" unless smtp.has_key?(n)
966
+ raise "Missing smtp parameter: #{n}" unless smtp.has_key?(n)
840
967
  end
841
968
  Log.log.debug("smtp=#{smtp}")
842
969
  return smtp
843
970
  end
844
971
 
845
- def send_email(email={})
846
- opts=email_settings
847
- email[:from_name]||=opts[:from_name]
848
- email[:from_email]||=opts[:from_email]
849
- # check minimum required
850
- [:from_name,:from_email,:to,:subject].each do |n|
851
- raise "missing email parameter: #{n}" unless email.has_key?(n)
972
+ # create a clean binding (ruby variable environment)
973
+ def empty_binding
974
+ Kernel.binding
975
+ end
976
+
977
+ def send_email_template(vars,email_template_default=nil)
978
+ vars[:to]||=options.get_option(:notif_to,:mandatory)
979
+ notif_template=options.get_option(:notif_template,email_template_default.nil? ? :mandatory : :optional) || email_template_default
980
+ mail_conf=email_settings
981
+ vars[:from_name]||=mail_conf[:from_name]
982
+ vars[:from_email]||=mail_conf[:from_email]
983
+ [:from_name,:from_email].each do |n|
984
+ raise "Missing email parameter: #{n}" unless vars.has_key?(n)
985
+ end
986
+ start_options=[mail_conf[:domain]]
987
+ start_options.push(mail_conf[:username],mail_conf[:password],:login) if mail_conf.has_key?(:username) and mail_conf.has_key?(:password)
988
+ # create a binding with only variables defined in vars
989
+ template_binding=empty_binding
990
+ # add variables to binding
991
+ vars.each do |k,v|
992
+ raise "key (#{k.class}) must be Symbol" unless k.is_a?(Symbol)
993
+ template_binding.local_variable_set(k,v)
852
994
  end
853
- msg = <<END_OF_MESSAGE
854
- From: #{email[:from_name]} <#{email[:from_email]}>
855
- To: <#{email[:to]}>
856
- Subject: #{email[:subject]}
857
-
858
- #{email[:body]}
859
- END_OF_MESSAGE
860
- start_options=[opts[:domain]]
861
- start_options.push(opts[:username],opts[:password],:login) if opts.has_key?(:username) and opts.has_key?(:password)
862
-
863
- smtp = Net::SMTP.new(opts[:server], opts[:port])
864
- smtp.enable_starttls if opts[:tls]
995
+ # execute template
996
+ msg_with_headers=ERB.new(notif_template).result(template_binding)
997
+ Log.dump(:msg_with_headers,msg_with_headers)
998
+ smtp = Net::SMTP.new(mail_conf[:server], mail_conf[:port])
999
+ smtp.enable_starttls if mail_conf[:tls]
865
1000
  smtp.start(*start_options) do |smtp|
866
- smtp.send_message(msg, email[:from_email], email[:to])
1001
+ smtp.send_message(msg_with_headers, vars[:from_email], vars[:to])
867
1002
  end
868
1003
  end
869
1004
 
870
1005
  def save_presets_to_config_file
871
1006
  raise 'no configuration loaded' if @config_presets.nil?
872
1007
  FileUtils.mkdir_p(@main_folder) unless Dir.exist?(@main_folder)
873
- Log.log.debug "writing #{@option_config_file}"
1008
+ Log.log.debug("Writing #{@option_config_file}")
874
1009
  File.write(@option_config_file,@config_presets.to_yaml)
875
1010
  end
876
1011
 
@@ -894,6 +1029,42 @@ END_OF_MESSAGE
894
1029
  return nil
895
1030
  end # get_plugin_default_config_name
896
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
897
1068
  end
898
1069
  end
899
1070
  end