aspera-cli 4.2.1 → 4.5.0

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