aspera-cli 4.4.0 → 4.7.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2095 -1503
  3. data/bin/ascli +2 -1
  4. data/bin/asession +4 -5
  5. data/docs/test_env.conf +3 -0
  6. data/examples/aoc.rb +4 -3
  7. data/examples/faspex4.rb +25 -25
  8. data/examples/proxy.pac +1 -1
  9. data/examples/transfer.rb +17 -17
  10. data/lib/aspera/aoc.rb +238 -185
  11. data/lib/aspera/ascmd.rb +93 -83
  12. data/lib/aspera/ats_api.rb +11 -10
  13. data/lib/aspera/cli/basic_auth_plugin.rb +13 -14
  14. data/lib/aspera/cli/extended_value.rb +42 -33
  15. data/lib/aspera/cli/formater.rb +142 -108
  16. data/lib/aspera/cli/info.rb +17 -0
  17. data/lib/aspera/cli/listener/line_dump.rb +3 -2
  18. data/lib/aspera/cli/listener/logger.rb +2 -1
  19. data/lib/aspera/cli/listener/progress.rb +16 -18
  20. data/lib/aspera/cli/listener/progress_multi.rb +18 -21
  21. data/lib/aspera/cli/main.rb +173 -149
  22. data/lib/aspera/cli/manager.rb +163 -168
  23. data/lib/aspera/cli/plugin.rb +43 -31
  24. data/lib/aspera/cli/plugins/alee.rb +6 -6
  25. data/lib/aspera/cli/plugins/aoc.rb +405 -370
  26. data/lib/aspera/cli/plugins/ats.rb +86 -79
  27. data/lib/aspera/cli/plugins/bss.rb +14 -16
  28. data/lib/aspera/cli/plugins/config.rb +580 -362
  29. data/lib/aspera/cli/plugins/console.rb +23 -19
  30. data/lib/aspera/cli/plugins/cos.rb +18 -18
  31. data/lib/aspera/cli/plugins/faspex.rb +201 -158
  32. data/lib/aspera/cli/plugins/faspex5.rb +80 -57
  33. data/lib/aspera/cli/plugins/node.rb +183 -166
  34. data/lib/aspera/cli/plugins/orchestrator.rb +71 -67
  35. data/lib/aspera/cli/plugins/preview.rb +92 -96
  36. data/lib/aspera/cli/plugins/server.rb +79 -75
  37. data/lib/aspera/cli/plugins/shares.rb +35 -19
  38. data/lib/aspera/cli/plugins/sync.rb +20 -22
  39. data/lib/aspera/cli/transfer_agent.rb +76 -113
  40. data/lib/aspera/cli/version.rb +2 -1
  41. data/lib/aspera/colors.rb +35 -27
  42. data/lib/aspera/command_line_builder.rb +48 -34
  43. data/lib/aspera/cos_node.rb +29 -21
  44. data/lib/aspera/data_repository.rb +3 -2
  45. data/lib/aspera/environment.rb +50 -45
  46. data/lib/aspera/fasp/{manager.rb → agent_base.rb} +28 -25
  47. data/lib/aspera/fasp/{connect.rb → agent_connect.rb} +52 -43
  48. data/lib/aspera/fasp/{local.rb → agent_direct.rb} +58 -72
  49. data/lib/aspera/fasp/{http_gw.rb → agent_httpgw.rb} +37 -43
  50. data/lib/aspera/fasp/{node.rb → agent_node.rb} +35 -16
  51. data/lib/aspera/fasp/agent_trsdk.rb +104 -0
  52. data/lib/aspera/fasp/error.rb +2 -1
  53. data/lib/aspera/fasp/error_info.rb +68 -52
  54. data/lib/aspera/fasp/installation.rb +152 -124
  55. data/lib/aspera/fasp/listener.rb +1 -0
  56. data/lib/aspera/fasp/parameters.rb +87 -92
  57. data/lib/aspera/fasp/parameters.yaml +305 -249
  58. data/lib/aspera/fasp/resume_policy.rb +11 -14
  59. data/lib/aspera/fasp/transfer_spec.rb +26 -0
  60. data/lib/aspera/fasp/uri.rb +22 -21
  61. data/lib/aspera/faspex_gw.rb +55 -89
  62. data/lib/aspera/hash_ext.rb +4 -3
  63. data/lib/aspera/id_generator.rb +8 -7
  64. data/lib/aspera/keychain/encrypted_hash.rb +121 -0
  65. data/lib/aspera/keychain/macos_security.rb +90 -0
  66. data/lib/aspera/log.rb +55 -37
  67. data/lib/aspera/nagios.rb +13 -12
  68. data/lib/aspera/node.rb +30 -25
  69. data/lib/aspera/oauth.rb +175 -226
  70. data/lib/aspera/open_application.rb +4 -3
  71. data/lib/aspera/persistency_action_once.rb +6 -6
  72. data/lib/aspera/persistency_folder.rb +5 -9
  73. data/lib/aspera/preview/file_types.rb +6 -5
  74. data/lib/aspera/preview/generator.rb +25 -24
  75. data/lib/aspera/preview/options.rb +16 -14
  76. data/lib/aspera/preview/utils.rb +98 -98
  77. data/lib/aspera/{proxy_auto_config.erb.js → proxy_auto_config.js} +23 -31
  78. data/lib/aspera/proxy_auto_config.rb +111 -20
  79. data/lib/aspera/rest.rb +154 -135
  80. data/lib/aspera/rest_call_error.rb +2 -2
  81. data/lib/aspera/rest_error_analyzer.rb +23 -25
  82. data/lib/aspera/rest_errors_aspera.rb +15 -14
  83. data/lib/aspera/ssh.rb +12 -10
  84. data/lib/aspera/sync.rb +42 -41
  85. data/lib/aspera/temp_file_manager.rb +18 -14
  86. data/lib/aspera/timer_limiter.rb +2 -1
  87. data/lib/aspera/uri_reader.rb +7 -5
  88. data/lib/aspera/web_auth.rb +79 -76
  89. metadata +116 -29
  90. data/docs/Makefile +0 -66
  91. data/docs/README.erb.md +0 -3973
  92. data/docs/README.md +0 -13
  93. data/docs/diagrams.txt +0 -49
  94. data/docs/doc_tools.rb +0 -58
  95. data/lib/aspera/api_detector.rb +0 -60
  96. data/lib/aspera/cli/plugins/shares2.rb +0 -114
  97. data/lib/aspera/secrets.rb +0 -20
@@ -1,15 +1,19 @@
1
+ # frozen_string_literal: true
1
2
  require 'aspera/cli/basic_auth_plugin'
2
3
  require 'aspera/cli/extended_value'
4
+ require 'aspera/cli/version'
3
5
  require 'aspera/fasp/installation'
4
6
  require 'aspera/fasp/parameters'
5
- require 'aspera/api_detector'
6
- require 'aspera/open_application'
7
- require 'aspera/aoc'
7
+ require 'aspera/fasp/transfer_spec'
8
+ require 'aspera/fasp/error_info'
8
9
  require 'aspera/proxy_auto_config'
9
- require 'aspera/uri_reader'
10
- require 'aspera/rest'
10
+ require 'aspera/open_application'
11
11
  require 'aspera/persistency_action_once'
12
12
  require 'aspera/id_generator'
13
+ require 'aspera/keychain/encrypted_hash'
14
+ require 'aspera/keychain/macos_security'
15
+ require 'aspera/aoc'
16
+ require 'aspera/rest'
13
17
  require 'xmlsimple'
14
18
  require 'base64'
15
19
  require 'net/smtp'
@@ -31,6 +35,7 @@ module Aspera
31
35
  CONF_PRESET_VERSION='version'
32
36
  CONF_PRESET_DEFAULT='default'
33
37
  CONF_PRESET_GLOBAL='global_common_defaults'
38
+ CONF_PRESET_SECRETS='default_secrets' # pragma: allowlist secret
34
39
  CONF_PLUGIN_SYM = :config # Plugins::Config.name.split('::').last.downcase.to_sym
35
40
  CONF_GLOBAL_SYM = :config
36
41
  # old tool name
@@ -48,113 +53,127 @@ module Aspera
48
53
  SERVER_COMMAND='server'
49
54
  CONNECT_WEB_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
50
55
  CONNECT_VERSIONS = 'connectversions.js'
51
- TRANSFER_SDK_ARCHIVE_URL = 'https://ibm.biz/aspera_sdk'
56
+ TRANSFER_SDK_ARCHIVE_URL = 'https://ibm.biz/aspera_transfer_sdk'
52
57
  DEMO='demo'
53
58
  DEMO_SERVER_PRESET='demoserver'
54
59
  AOC_PATH_API_CLIENTS='admin/api-clients'
55
- EMAIL_TEST_TEMPLATE=<<END_OF_TEMPLATE
56
- From: <%=from_name%> <<%=from_email%>>
57
- To: <<%=to%>>
58
- Subject: Amelia email test
60
+ EMAIL_TEST_TEMPLATE=<<~END_OF_TEMPLATE
61
+ From: <%=from_name%> <<%=from_email%>>
62
+ To: <<%=to%>>
63
+ Subject: Amelia email test
59
64
 
60
- It worked !
61
- END_OF_TEMPLATE
65
+ It worked !
66
+ END_OF_TEMPLATE
62
67
  # special extended values
63
- EXTV_INCLUDE_PRESETS='incps'
64
- EXTV_PRESET='preset'
68
+ EXTV_INCLUDE_PRESETS=:incps
69
+ EXTV_PRESET=:preset
70
+ PRESET_DIG_SEPARATOR='.'
65
71
  DEFAULT_CHECK_NEW_VERSION_DAYS=7
66
- DEFAULT_PRIV_KEY_FILENAME='aspera_aoc_key'
72
+ DEFAULT_PRIV_KEY_FILENAME='aspera_aoc_key' # pragma: allowlist secret
67
73
  DEFAULT_PRIVKEY_LENGTH=4096
68
74
  private_constant :DEFAULT_CONFIG_FILENAME,:CONF_PRESET_CONFIG,:CONF_PRESET_VERSION,:CONF_PRESET_DEFAULT,
69
75
  :CONF_PRESET_GLOBAL,:PROGRAM_NAME_V1,:PROGRAM_NAME_V2,:DEFAULT_REDIRECT,:ASPERA_PLUGINS_FOLDERNAME,
70
76
  :RUBY_FILE_EXT,:AOC_COMMAND_V1,:AOC_COMMAND_V2,:AOC_COMMAND_V3,:AOC_COMMAND_CURRENT,:DEMO,
71
77
  :TRANSFER_SDK_ARCHIVE_URL,:AOC_PATH_API_CLIENTS,:DEMO_SERVER_PRESET,:EMAIL_TEST_TEMPLATE,:EXTV_INCLUDE_PRESETS,
72
- :EXTV_PRESET,:DEFAULT_CHECK_NEW_VERSION_DAYS,:DEFAULT_PRIV_KEY_FILENAME,:SERVER_COMMAND
73
- def option_preset; nil; end
74
-
75
- def option_preset=(value)
76
- case value
77
- when String
78
- self.options.add_option_preset(preset_by_name(value))
79
- when Hash
80
- self.options.add_option_preset(value)
81
- else
82
- raise "Preset definition must be a String for name, or Hash for value"
83
- end
84
- nil
85
- end
86
-
87
- def initialize(env,tool_name,help_url,version,main_folder)
78
+ :EXTV_PRESET,:DEFAULT_CHECK_NEW_VERSION_DAYS,:DEFAULT_PRIV_KEY_FILENAME,:SERVER_COMMAND,:CONF_PRESET_SECRETS,
79
+ :PRESET_DIG_SEPARATOR
80
+ def initialize(env,params)
81
+ raise 'env and params must be Hash' unless env.is_a?(Hash) && params.is_a?(Hash)
82
+ raise 'missing param' unless [:name,:help,:version,:gem].sort.eql?(params.keys.sort)
88
83
  super(env)
89
- raise 'missing secret manager' if @agents[:secret].nil?
84
+ @info=params
85
+ @main_folder=default_app_main_folder
90
86
  @plugins={}
91
87
  @plugin_lookup_folders=[]
92
88
  @use_plugin_defaults=true
93
89
  @config_presets=nil
94
90
  @connect_versions=nil
95
- @program_version=version
96
- @tool_name=tool_name
97
- @help_url=help_url
98
- @main_folder=main_folder
91
+ @vault=nil
99
92
  @conf_file_default=File.join(@main_folder,DEFAULT_CONFIG_FILENAME)
100
93
  @option_config_file=@conf_file_default
101
- Log.log.debug("#{tool_name} folder: #{@main_folder}")
94
+ @pac_exec=nil
95
+ Log.log.debug("#{@info[:name]} folder: #{@main_folder}")
102
96
  # set folder for FASP SDK
103
- add_plugin_lookup_folder(File.join(@main_folder,ASPERA_PLUGINS_FOLDERNAME))
104
97
  add_plugin_lookup_folder(self.class.gem_plugins_folder)
98
+ add_plugin_lookup_folder(File.join(@main_folder,ASPERA_PLUGINS_FOLDERNAME))
105
99
  # do file parameter first
106
- self.options.set_obj_attr(:config_file,self,:option_config_file)
107
- self.options.add_opt_simple(:config_file,"read parameters from file in YAML format, current=#{@option_config_file}")
108
- self.options.parse_options!
100
+ options.set_obj_attr(:config_file,self,:option_config_file)
101
+ options.add_opt_simple(:config_file,"read parameters from file in YAML format, current=#{@option_config_file}")
102
+ options.parse_options!
109
103
  # read correct file
110
104
  read_config_file
111
105
  # add preset handler (needed for smtp)
112
106
  ExtendedValue.instance.set_handler(EXTV_PRESET,:reader,lambda{|v|preset_by_name(v)})
113
107
  ExtendedValue.instance.set_handler(EXTV_INCLUDE_PRESETS,:decoder,lambda{|v|expanded_with_preset_includes(v)})
114
- self.options.set_obj_attr(:ascp_path,self,:option_ascp_path)
115
- self.options.set_obj_attr(:use_product,self,:option_use_product)
116
- self.options.set_obj_attr(:preset,self,:option_preset)
117
- self.options.set_obj_attr(:secret,@agents[:secret],:default_secret)
118
- self.options.set_obj_attr(:secrets,@agents[:secret],:all_secrets)
119
- self.options.add_opt_switch(:no_default,'-N','do not load default configuration for plugin') { @use_plugin_defaults=false }
120
- self.options.add_opt_boolean(:override,'Wizard: override existing value')
121
- self.options.add_opt_boolean(:use_generic_client,'Wizard: AoC: use global or org specific jwt client id')
122
- self.options.add_opt_boolean(:default,'Wizard: set as default configuration for specified plugin (also: update)')
123
- self.options.add_opt_boolean(:test_mode,'Wizard: skip private key check step')
124
- self.options.add_opt_simple(:pkeypath,'Wizard: path to private key for JWT')
125
- self.options.add_opt_simple(:ascp_path,'path to ascp')
126
- self.options.add_opt_simple(:use_product,'use ascp from specified product')
127
- self.options.add_opt_simple(:smtp,'smtp configuration (extended value: hash)')
128
- self.options.add_opt_simple(:fpac,'proxy auto configuration URL')
129
- self.options.add_opt_simple(:preset,'-PVALUE','load the named option preset from current config file')
130
- self.options.add_opt_simple(:secret,'default secret')
131
- self.options.add_opt_simple(:secrets,'secret repository (Hash)')
132
- self.options.add_opt_simple(:sdk_url,'URL to get SDK')
133
- self.options.add_opt_simple(:sdk_folder,'SDK folder path')
134
- self.options.add_opt_simple(:notif_to,'email recipient for notification of transfers')
135
- self.options.add_opt_simple(:notif_template,'email ERB template for notification of transfers')
136
- self.options.add_opt_simple(:version_check_days,Integer,'period in days to check new version (zero to disable)')
137
- self.options.set_option(:use_generic_client,true)
138
- self.options.set_option(:test_mode,false)
139
- self.options.set_option(:default,true)
140
- self.options.set_option(:version_check_days,DEFAULT_CHECK_NEW_VERSION_DAYS)
141
- self.options.set_option(:sdk_url,TRANSFER_SDK_ARCHIVE_URL)
142
- self.options.set_option(:sdk_folder,File.join(@main_folder,'sdk'))
143
- self.options.set_option(:override,:no)
144
- self.options.parse_options!
145
- raise CliBadArgument,'secrets shall be Hash' unless @agents[:secret].all_secrets.is_a?(Hash)
146
- Fasp::Installation.instance.folder=self.options.get_option(:sdk_folder,:mandatory)
108
+ # load defaults before it can be overriden
109
+ add_plugin_default_preset(CONF_GLOBAL_SYM)
110
+ options.parse_options!
111
+ options.set_obj_attr(:ascp_path,Fasp::Installation.instance,:ascp_path)
112
+ options.set_obj_attr(:sdk_folder,Fasp::Installation.instance,:sdk_folder)
113
+ options.set_obj_attr(:use_product,self,:option_use_product)
114
+ options.set_obj_attr(:preset,self,:option_preset)
115
+ options.set_obj_attr(:plugin_folder,self,:option_plugin_folder)
116
+ options.add_opt_switch(:no_default,'-N','do not load default configuration for plugin') { @use_plugin_defaults=false }
117
+ options.add_opt_boolean(:override,'Wizard: override existing value')
118
+ options.add_opt_boolean(:use_generic_client,'Wizard: AoC: use global or org specific jwt client id')
119
+ options.add_opt_boolean(:default,'Wizard: set as default configuration for specified plugin (also: update)')
120
+ options.add_opt_boolean(:test_mode,'Wizard: skip private key check step')
121
+ options.add_opt_simple(:preset,'-PVALUE','load the named option preset from current config file')
122
+ options.add_opt_simple(:pkeypath,'Wizard: path to private key for JWT')
123
+ options.add_opt_simple(:ascp_path,'path to ascp')
124
+ options.add_opt_simple(:use_product,'use ascp from specified product')
125
+ options.add_opt_simple(:smtp,'smtp configuration (extended value: hash)')
126
+ options.add_opt_simple(:fpac,'proxy auto configuration script')
127
+ options.add_opt_simple(:secret,'default secret')
128
+ options.add_opt_simple(:secrets,'secret vault')
129
+ options.add_opt_simple(:sdk_url,'URL to get SDK')
130
+ options.add_opt_simple(:sdk_folder,'SDK folder path')
131
+ options.add_opt_simple(:notif_to,'email recipient for notification of transfers')
132
+ options.add_opt_simple(:notif_template,'email ERB template for notification of transfers')
133
+ options.add_opt_simple(:version_check_days,Integer,'period in days to check new version (zero to disable)')
134
+ options.add_opt_simple(:plugin_folder,'folder where to find additional plugins')
135
+ options.set_option(:use_generic_client,true)
136
+ options.set_option(:test_mode,false)
137
+ options.set_option(:default,true)
138
+ options.set_option(:version_check_days,DEFAULT_CHECK_NEW_VERSION_DAYS)
139
+ options.set_option(:sdk_url,TRANSFER_SDK_ARCHIVE_URL)
140
+ options.set_option(:sdk_folder,File.join(@main_folder,'sdk'))
141
+ options.set_option(:override,:no)
142
+ options.parse_options!
143
+ pac_script=options.get_option(:fpac,:optional)
144
+ # create PAC executor
145
+ @pac_exec=Aspera::ProxyAutoConfig.new(pac_script).register_uri_generic unless pac_script.nil?
146
+ end
147
+
148
+ # env var name to override the app's main folder
149
+ # default main folder is $HOME/<vendor main app folder>/<program name>
150
+ def conf_dir_env_var
151
+ return "#{@info[:name]}_home".upcase
152
+ end
153
+
154
+ def default_app_main_folder
155
+ # find out application main folder
156
+ app_folder=ENV[conf_dir_env_var]
157
+ # if env var undefined or empty
158
+ if app_folder.nil? || app_folder.empty?
159
+ user_home_folder=Dir.home
160
+ raise CliError,"Home folder does not exist: #{user_home_folder}. Check your user environment or use #{conf_dir_env_var}." unless Dir.exist?(user_home_folder)
161
+ app_folder=File.join(user_home_folder,ASPERA_HOME_FOLDER_NAME,@info[:name])
162
+ end
163
+ return app_folder
147
164
  end
148
165
 
149
166
  def check_gem_version
150
- this_gem_name=File.basename(File.dirname(self.class.gem_root)).gsub(/-[0-9].*$/,'')
151
167
  latest_version=begin
152
- Rest.new(base_url: 'https://rubygems.org/api/v1').read("versions/#{this_gem_name}/latest.json")[:data]['version']
153
- rescue
168
+ Rest.new(base_url: 'https://rubygems.org/api/v1').read("versions/#{@info[:gem]}/latest.json")[:data]['version']
169
+ rescue StandardError
154
170
  Log.log.warn('Could not retrieve latest gem version on rubygems.')
155
171
  '0'
156
172
  end
157
- return {name: this_gem_name, current: Aspera::Cli::VERSION, latest: latest_version, need_update: Gem::Version.new(Aspera::Cli::VERSION) < Gem::Version.new(latest_version)}
173
+ if Gem::Version.new(Environment.ruby_version) < Gem::Version.new(RUBY_FUTURE_MINIMUM_VERSION)
174
+ Log.log.warn("Note that a future version will require Ruby version #{RUBY_FUTURE_MINIMUM_VERSION} at minimum, you are using #{Environment.ruby_version}")
175
+ end
176
+ return {name: @info[:gem], current: Aspera::Cli::VERSION, latest: latest_version, need_update: Gem::Version.new(Aspera::Cli::VERSION) < Gem::Version.new(latest_version)}
158
177
  end
159
178
 
160
179
  def periodic_check_newer_gem_version
@@ -162,37 +181,36 @@ END_OF_TEMPLATE
162
181
  delay_days=options.get_option(:version_check_days,:mandatory)
163
182
  Log.log.info("check days: #{delay_days}")
164
183
  # check only if not zero day
165
- if !delay_days.eql?(0)
166
- # get last date from persistency
167
- last_check_array=[]
168
- check_date_persist=PersistencyActionOnce.new(
169
- manager: persistency,
170
- data: last_check_array,
171
- id: 'version_last_check')
172
- # get persisted date or nil
173
- last_check_date = begin
174
- Date.strptime(last_check_array.first, '%Y/%m/%d')
175
- rescue
176
- nil
177
- end
178
- current_date=Date.today
179
- Log.log.debug("days elapsed: #{last_check_date.is_a?(Date) ? current_date - last_check_date : last_check_date.class.name}")
180
- if last_check_date.nil? or (current_date - last_check_date) > delay_days
181
- last_check_array[0]=current_date.strftime('%Y/%m/%d')
182
- check_date_persist.save
183
- check_data=check_gem_version
184
- if check_data[:need_update]
185
- Log.log.warn("A new version is available: #{check_data[:latest]}. You have #{check_data[:current]}. Upgrade with: gem update #{check_data[:name]}")
186
- end
187
- end
184
+ return if delay_days.eql?(0)
185
+ # get last date from persistency
186
+ last_check_array=[]
187
+ check_date_persist=PersistencyActionOnce.new(
188
+ manager: persistency,
189
+ data: last_check_array,
190
+ id: 'version_last_check')
191
+ # get persisted date or nil
192
+ current_date=Date.today
193
+ last_check_days = begin
194
+ current_date-Date.strptime(last_check_array.first, '%Y/%m/%d')
195
+ rescue StandardError
196
+ # negative value will force check
197
+ -1
188
198
  end
199
+ Log.log.debug("days elapsed: #{last_check_days}")
200
+ return if last_check_days < delay_days
201
+ # generate timestamp
202
+ last_check_array[0]=current_date.strftime('%Y/%m/%d')
203
+ check_date_persist.save
204
+ # compare this version and the one on internet
205
+ check_data=check_gem_version
206
+ Log.log.warn("A new version is available: #{check_data[:latest]}. You have #{check_data[:current]}. Upgrade with: gem update #{check_data[:name]}") if check_data[:need_update]
189
207
  end
190
208
 
191
209
  # retrieve structure from cloud (CDN) with all versions available
192
210
  def connect_versions
193
211
  if @connect_versions.nil?
194
- api_connect_cdn=Rest.new({:base_url=>CONNECT_WEB_URL})
195
- javascript=api_connect_cdn.call({:operation=>'GET',:subpath=>CONNECT_VERSIONS})
212
+ api_connect_cdn=Rest.new({base_url: CONNECT_WEB_URL})
213
+ javascript=api_connect_cdn.call({operation: 'GET',subpath: CONNECT_VERSIONS})
196
214
  # get result on one line
197
215
  connect_versions_javascript=javascript[:http].body.gsub(/\r?\n\s*/,'')
198
216
  Log.log.debug("javascript=[\n#{connect_versions_javascript}\n]")
@@ -212,9 +230,10 @@ END_OF_TEMPLATE
212
230
  def add_plugin_default_preset(plugin_name_sym)
213
231
  default_config_name=get_plugin_default_config_name(plugin_name_sym)
214
232
  Log.log.debug("add_plugin_default_preset:#{plugin_name_sym}:#{default_config_name}")
215
- self.options.add_option_preset(preset_by_name(default_config_name),:unshift) unless default_config_name.nil?
233
+ options.add_option_preset(preset_by_name(default_config_name),:unshift) unless default_config_name.nil?
216
234
  return nil
217
235
  end
236
+
218
237
  private
219
238
 
220
239
  def generate_rsa_private_key(private_key_path,length)
@@ -225,40 +244,53 @@ END_OF_TEMPLATE
225
244
  nil
226
245
  end
227
246
 
228
- # folder containing plugins in the gem's main folder
229
- def self.gem_plugins_folder
230
- File.dirname(File.expand_path(__FILE__))
231
- end
247
+ class << self
248
+ # folder containing plugins in the gem's main folder
249
+ def gem_plugins_folder
250
+ File.dirname(File.expand_path(__FILE__))
251
+ end
232
252
 
233
- # find the root folder of gem where this class is
234
- # go up as many times as englobing modules (not counting class, as it is a file)
235
- def self.gem_root
236
- File.expand_path(Module.nesting[1].to_s.gsub('::','/').gsub(%r([^/]+),'..'),File.dirname(__FILE__))
237
- end
253
+ # name of englobin module
254
+ # @return "Aspera::Cli::Plugins"
255
+ def module_full_name
256
+ return Module.nesting[2].to_s
257
+ end
238
258
 
239
- # instanciate a plugin
240
- # plugins must be Capitalized
241
- def self.plugin_new(plugin_name_sym,env)
242
- # Module.nesting[2] is Aspera::Cli
243
- return Object::const_get("#{Module.nesting[2].to_s}::Plugins::#{plugin_name_sym.to_s.capitalize}").new(env)
244
- end
259
+ # @return main folder where code is, i.e. .../lib
260
+ # go up as many times as englobing modules (not counting class, as it is a file)
261
+ def gem_src_root
262
+ File.expand_path(module_full_name.gsub('::','/').gsub(%r{[^/]+},'..'),gem_plugins_folder)
263
+ end
245
264
 
246
- def self.flatten_all_config(t)
247
- r=[]
248
- t.each do |k,v|
249
- v.each do |kk,vv|
250
- r.push({'config'=>k,'parameter'=>kk,'value'=>vv})
265
+ # instanciate a plugin
266
+ # plugins must be Capitalized
267
+ def plugin_class(plugin_name_sym)
268
+ # Module.nesting[2] is Aspera::Cli::Plugins
269
+ return Object.const_get("#{module_full_name}::#{plugin_name_sym.to_s.capitalize}")
270
+ end
271
+
272
+ def flatten_all_config(t)
273
+ r=[]
274
+ t.each do |k,v|
275
+ v.each do |kk,vv|
276
+ r.push({'config'=>k,'parameter'=>kk,'value'=>vv})
277
+ end
251
278
  end
279
+ return r
252
280
  end
253
- return r
254
281
  end
255
282
 
256
283
  # set parameter and value in global config
257
284
  # creates one if none already created
258
- # @return preset that contains global default
285
+ # @return preset name that contains global default
259
286
  def set_global_default(key,value)
260
287
  # get default preset if it exists
261
- global_default_preset_name=get_plugin_default_config_name(CONF_GLOBAL_SYM) || CONF_PRESET_GLOBAL
288
+ global_default_preset_name=get_plugin_default_config_name(CONF_GLOBAL_SYM)
289
+ if global_default_preset_name.nil?
290
+ global_default_preset_name=CONF_PRESET_GLOBAL
291
+ @config_presets[CONF_PRESET_DEFAULT]||={}
292
+ @config_presets[CONF_PRESET_DEFAULT][CONF_GLOBAL_SYM.to_s]=global_default_preset_name
293
+ end
262
294
  @config_presets[global_default_preset_name]||={}
263
295
  @config_presets[global_default_preset_name][key.to_s]=value
264
296
  self.format.display_status("Updated: #{global_default_preset_name}: #{key} <- #{value}")
@@ -270,27 +302,41 @@ END_OF_TEMPLATE
270
302
 
271
303
  # $HOME/.aspera/`program_name`
272
304
  attr_reader :main_folder
273
- attr_reader :gem_url
274
- attr_reader :plugins
305
+ attr_reader :gem_url, :plugins
275
306
  attr_accessor :option_config_file
276
307
 
277
308
  # @return the hash from name (also expands possible includes)
309
+ # @param config_name name of the preset in config file
310
+ # @param include_path used to detect and avoid include loops
278
311
  def preset_by_name(config_name, include_path=[])
279
- raise CliError,"no such config preset: #{config_name}" unless @config_presets.has_key?(config_name)
280
312
  raise CliError,'loop in include' if include_path.include?(config_name)
281
- return expanded_with_preset_includes(@config_presets[config_name],include_path.clone.push(config_name))
313
+ include_path=include_path.clone # avoid messing up if there are multiple branches
314
+ current=@config_presets
315
+ config_name.split(PRESET_DIG_SEPARATOR).each do |name|
316
+ raise CliError,"not a Hash: #{include_path} (#{current.class})" unless current.is_a?(Hash)
317
+ include_path.push(name)
318
+ current=current[name]
319
+ raise CliError,"no such config preset: #{include_path}" if nil?
320
+ end
321
+ case current
322
+ when Hash then return expanded_with_preset_includes(current,include_path)
323
+ when String then return ExtendedValue.instance.evaluate(current)
324
+ else return current
325
+ end
282
326
  end
283
327
 
328
+ # @return the hash value with 'incps' keys expanced to include other presets
284
329
  # @param hash_val
330
+ # @param include_path to avoid inclusion loop
285
331
  def expanded_with_preset_includes(hash_val, include_path=[])
286
- raise CliError,"#{EXTV_INCLUDE_PRESETS} requires a Hash" unless hash_val.is_a?(Hash)
332
+ raise CliError,"#{EXTV_INCLUDE_PRESETS} requires a Hash, have #{hash_val.class}" unless hash_val.is_a?(Hash)
287
333
  if hash_val.has_key?(EXTV_INCLUDE_PRESETS)
288
334
  memory=hash_val.clone
289
335
  includes=memory[EXTV_INCLUDE_PRESETS]
290
336
  memory.delete(EXTV_INCLUDE_PRESETS)
291
337
  hash_val={}
292
338
  raise "#{EXTV_INCLUDE_PRESETS} must be an Array" unless includes.is_a?(Array)
293
- raise "#{EXTV_INCLUDE_PRESETS} must contain names" unless includes.map{|i|i.class}.uniq.eql?([String])
339
+ raise "#{EXTV_INCLUDE_PRESETS} must contain names" unless includes.map(&:class).uniq.eql?([String])
294
340
  includes.each do |preset_name|
295
341
  hash_val.merge!(preset_by_name(preset_name,include_path))
296
342
  end
@@ -299,20 +345,37 @@ END_OF_TEMPLATE
299
345
  return hash_val
300
346
  end
301
347
 
302
- def option_ascp_path=(new_value)
303
- Fasp::Installation.instance.ascp_path=new_value
348
+ def option_use_product=(value)
349
+ Fasp::Installation.instance.use_ascp_from_product(value)
304
350
  end
305
351
 
306
- def option_ascp_path
307
- Fasp::Installation.instance.path(:ascp)
352
+ def option_use_product
353
+ 'write-only option, see value of ascp_path'
308
354
  end
309
355
 
310
- def option_use_product=(value)
311
- Fasp::Installation.instance.use_ascp_from_product(value)
356
+ def option_plugin_folder=(value)
357
+ case value
358
+ when String then add_plugin_lookup_folder(value)
359
+ when Array then value.each{|f|add_plugin_lookup_folder(f)}
360
+ else raise "folder shall be Array or String, not #{value.class}"
361
+ end
312
362
  end
313
363
 
314
- def option_use_product
315
- 'write-only value'
364
+ def option_plugin_folder
365
+ return @plugin_lookup_folders
366
+ end
367
+
368
+ def option_preset; 'write-only option'; end
369
+
370
+ def option_preset=(value)
371
+ case value
372
+ when String
373
+ options.add_option_preset(preset_by_name(value))
374
+ when Hash
375
+ options.add_option_preset(value)
376
+ else
377
+ raise 'Preset definition must be a String for name, or Hash for value'
378
+ end
316
379
  end
317
380
 
318
381
  def convert_preset_path(old_name,new_name,files_to_copy)
@@ -323,7 +386,7 @@ END_OF_TEMPLATE
323
386
  preset.values.select{|v|v.is_a?(String) and v.include?(old_subpath)}.each do |value|
324
387
  old_val=value.clone
325
388
  included_path=File.expand_path(old_val.gsub(/^@file:/,''))
326
- files_to_copy.push(included_path) unless files_to_copy.include?(included_path) or !File.exist?(included_path)
389
+ files_to_copy.push(included_path) unless files_to_copy.include?(included_path) || !File.exist?(included_path)
327
390
  value.gsub!(old_subpath,new_subpath)
328
391
  Log.log.warn("Converted config value: #{old_val} -> #{value}")
329
392
  end
@@ -331,11 +394,11 @@ END_OF_TEMPLATE
331
394
  end
332
395
 
333
396
  def convert_preset_plugin_name(old_name,new_name)
334
- if @config_presets[CONF_PRESET_DEFAULT].is_a?(Hash) and @config_presets[CONF_PRESET_DEFAULT].has_key?(old_name)
335
- @config_presets[CONF_PRESET_DEFAULT][new_name]=@config_presets[CONF_PRESET_DEFAULT][old_name]
336
- @config_presets[CONF_PRESET_DEFAULT].delete(old_name)
337
- Log.log.warn("Converted plugin default: #{old_name} -> #{new_name}")
338
- end
397
+ default_preset=@config_presets[CONF_PRESET_DEFAULT]
398
+ return unless default_preset.is_a?(Hash) && default_preset.has_key?(old_name)
399
+ default_preset[new_name]=default_preset[old_name]
400
+ default_preset.delete(old_name)
401
+ Log.log.warn("Converted plugin default: #{old_name} -> #{new_name}")
339
402
  end
340
403
 
341
404
  # read config file and validate format
@@ -352,11 +415,11 @@ END_OF_TEMPLATE
352
415
  # find first existing file (or nil)
353
416
  conf_file_to_load=search_files.select{|f| File.exist?(f)}.first
354
417
  # require save if old version of file
355
- save_required=!@option_config_file.eql?(conf_file_to_load)
418
+ save_required= !@option_config_file.eql?(conf_file_to_load)
356
419
  # if no file found, create default config
357
420
  if conf_file_to_load.nil?
358
421
  Log.log.warn("No config file found. Creating empty configuration file: #{@option_config_file}")
359
- @config_presets={CONF_PRESET_CONFIG=>{CONF_PRESET_VERSION=>@program_version}}
422
+ @config_presets={CONF_PRESET_CONFIG=>{CONF_PRESET_VERSION=>@info[:version]}}
360
423
  else
361
424
  Log.log.debug("loading #{@option_config_file}")
362
425
  @config_presets=YAML.load_file(conf_file_to_load)
@@ -393,14 +456,15 @@ END_OF_TEMPLATE
393
456
  config_tested_version='1.0'
394
457
  if Gem::Version.new(version) <= Gem::Version.new(config_tested_version)
395
458
  convert_preset_plugin_name(AOC_COMMAND_V2,AOC_COMMAND_V3)
396
- convert_preset_path(PROGRAM_NAME_V2,@tool_name,files_to_copy)
459
+ convert_preset_path(PROGRAM_NAME_V2,@info[:name],files_to_copy)
397
460
  version=@config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]=config_tested_version
398
461
  save_required=true
399
462
  end
463
+ Log.log.debug("conf version: #{version}")
400
464
  # Place new compatibility code here
401
465
  if save_required
402
466
  Log.log.warn('Saving automatic conversion.')
403
- @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]=@program_version
467
+ @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]=@info[:version]
404
468
  save_presets_to_config_file
405
469
  Log.log.warn('Copying referenced files')
406
470
  files_to_copy.each do |file|
@@ -411,9 +475,9 @@ END_OF_TEMPLATE
411
475
  rescue Psych::SyntaxError => e
412
476
  Log.log.error('YAML error in config file')
413
477
  raise e
414
- rescue => e
478
+ rescue StandardError => e
415
479
  Log.log.debug("-> #{e}")
416
- new_name="#{@option_config_file}.pre#{@program_version}.manual_conversion_needed"
480
+ new_name="#{@option_config_file}.pre#{@info[:version]}.manual_conversion_needed"
417
481
  File.rename(@option_config_file,new_name)
418
482
  Log.log.warn("Renamed config file to #{new_name}.")
419
483
  Log.log.warn('Manual Conversion is required. Next time, a new empty file will be created.')
@@ -424,18 +488,17 @@ END_OF_TEMPLATE
424
488
  # find plugins in defined paths
425
489
  def add_plugins_from_lookup_folders
426
490
  @plugin_lookup_folders.each do |folder|
427
- if File.directory?(folder)
428
- #TODO: add gem root to load path ? and require short folder ?
429
- #$LOAD_PATH.push(folder) if i[:add_path]
430
- Dir.entries(folder).select{|file|file.end_with?(RUBY_FILE_EXT)}.each do |source|
431
- add_plugin_info(File.join(folder,source))
432
- end
491
+ next unless File.directory?(folder)
492
+ #TODO: add gem root to load path ? and require short folder ?
493
+ #$LOAD_PATH.push(folder) if i[:add_path]
494
+ Dir.entries(folder).select{|file|file.end_with?(RUBY_FILE_EXT)}.each do |source|
495
+ add_plugin_info(File.join(folder,source))
433
496
  end
434
497
  end
435
498
  end
436
499
 
437
500
  def add_plugin_lookup_folder(folder)
438
- @plugin_lookup_folders.push(folder)
501
+ @plugin_lookup_folders.unshift(folder)
439
502
  end
440
503
 
441
504
  def add_plugin_info(path)
@@ -446,245 +509,319 @@ END_OF_TEMPLATE
446
509
  Log.log.warn("skipping plugin already registered: #{plugin_symbol}")
447
510
  return
448
511
  end
449
- @plugins[plugin_symbol]={:source=>path,:require_stanza=>req}
512
+ @plugins[plugin_symbol]={source: path,require_stanza: req}
513
+ end
514
+
515
+ def identify_plugin_for_url(url)
516
+ plugins.each do |plugin_name_sym,plugin_info|
517
+ # no detection for internal plugin
518
+ next if plugin_name_sym.eql?(CONF_PLUGIN_SYM)
519
+ # load plugin class
520
+ require plugin_info[:require_stanza]
521
+ c=self.class.plugin_class(plugin_name_sym)
522
+ next unless c.respond_to?(:detect)
523
+ current_url=url
524
+ detection_info=nil
525
+ # first try : direct
526
+ begin
527
+ detection_info=c.detect(current_url)
528
+ rescue StandardError => e
529
+ Log.log.debug("Cannot detect #{plugin_name_sym} : #{e.message}")
530
+ end
531
+ # second try : is there a redirect ?
532
+ if detection_info.nil?
533
+ begin
534
+ # TODO: check if redirect ?
535
+ new_url=Rest.new(base_url: url).call(operation: 'GET',subpath: '',redirect_max: 1)[:http].uri.to_s
536
+ unless url.eql?(new_url)
537
+ detection_info=c.detect(new_url)
538
+ current_url=new_url
539
+ end
540
+ rescue StandardError => e
541
+ Log.log.debug("Cannot detect #{plugin_name_sym} : #{e.message}")
542
+ end
543
+ end
544
+ return detection_info.merge(product: plugin_name_sym, url: current_url) unless detection_info.nil?
545
+ end # loop
546
+ raise "No known application found at #{url}"
450
547
  end
451
548
 
452
549
  def execute_connect_action
453
- command=self.options.get_next_command([:list,:id])
454
- case command
455
- when :list
456
- return {:type=>:object_list, :data=>connect_versions, :fields => ['id','title','version']}
457
- when :id
458
- connect_id=self.options.get_next_argument('id or title')
550
+ command=options.get_next_command([:list,:info,:version])
551
+ if [:info,:version].include?(command)
552
+ connect_id=options.get_next_argument('id or title')
459
553
  one_res=connect_versions.select{|i|i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}.first
460
554
  raise CliNoSuchId.new(:connect,connect_id) if one_res.nil?
461
- command=self.options.get_next_command([:info,:links])
555
+ end
556
+ case command
557
+ when :list
558
+ return {type: :object_list, data: connect_versions, fields: ['id','title','version']}
559
+ when :info # shows files used
560
+ one_res.delete('links')
561
+ return {type: :single_object, data: one_res}
562
+ when :version # shows files used
563
+ all_links=one_res['links']
564
+ command=options.get_next_command([:list,:download,:open])
565
+ if [:download,:open].include?(command)
566
+ link_title=options.get_next_argument('title or rel')
567
+ one_link=all_links.select {|i| i['title'].eql?(link_title) or i['rel'].eql?(link_title)}.first
568
+ raise 'no such value' if one_link.nil?
569
+ end
462
570
  case command
463
- when :info # shows files used
464
- one_res.delete('links')
465
- return {:type=>:single_object, :data=>one_res}
466
- when :links # shows files used
467
- command=self.options.get_next_command([:list,:id])
468
- all_links=one_res['links']
469
- case command
470
- when :list # shows files used
471
- return {:type=>:object_list, :data=>all_links}
472
- when :id
473
- link_title=self.options.get_next_argument('title')
474
- one_link=all_links.select {|i| i['title'].eql?(link_title)}.first
475
- command=self.options.get_next_command([:download,:open])
476
- case command
477
- when :download #
478
- folder_dest=self.transfer.destination_folder('receive')
479
- #folder_dest=self.options.get_next_argument('destination folder')
480
- api_connect_cdn=Rest.new({:base_url=>CONNECT_WEB_URL})
481
- fileurl = one_link['href']
482
- filename=fileurl.gsub(%r{.*/},'')
483
- api_connect_cdn.call({:operation=>'GET',:subpath=>fileurl,:save_to_file=>File.join(folder_dest,filename)})
484
- return Main.result_status("Downloaded: #{filename}")
485
- when :open #
486
- OpenApplication.instance.uri(one_link['href'])
487
- return Main.result_status("Opened: #{one_link['href']}")
488
- end
489
- end
571
+ when :list # shows files used
572
+ return {type: :object_list, data: all_links}
573
+ when :download
574
+ folder_dest=transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE)
575
+ #folder_dest=self.options.get_next_argument('destination folder')
576
+ api_connect_cdn=Rest.new({base_url: CONNECT_WEB_URL})
577
+ fileurl = one_link['href']
578
+ filename=fileurl.gsub(%r{.*/},'')
579
+ api_connect_cdn.call({operation: 'GET',subpath: fileurl,save_to_file: File.join(folder_dest,filename)})
580
+ return Main.result_status("Downloaded: #{filename}")
581
+ when :open
582
+ OpenApplication.instance.uri(one_link['href'])
583
+ return Main.result_status("Opened: #{one_link['href']}")
490
584
  end
491
585
  end
492
586
  end
493
587
 
494
588
  def execute_action_ascp
495
- command=self.options.get_next_command([:connect,:use,:show,:products,:info,:install,:spec])
589
+ command=options.get_next_command([:connect,:use,:show,:products,:info,:install,:spec,:errors])
496
590
  case command
497
591
  when :connect
498
592
  return execute_connect_action
499
593
  when :use
500
- ascp_path=self.options.get_next_argument('path to ascp')
594
+ ascp_path=options.get_next_argument('path to ascp')
501
595
  ascp_version=Fasp::Installation.instance.get_ascp_version(ascp_path)
502
596
  self.format.display_status("ascp version: #{ascp_version}")
503
597
  preset_name=set_global_default(:ascp_path,ascp_path)
504
598
  return Main.result_status("Saved to default global preset #{preset_name}")
505
599
  when :show # shows files used
506
- return {:type=>:status, :data=>Fasp::Installation.instance.path(:ascp)}
600
+ return {type: :status, data: Fasp::Installation.instance.path(:ascp)}
507
601
  when :info # shows files used
508
- data=Fasp::Installation::FILES.inject({}) do |m,v|
602
+ data=Fasp::Installation::FILES.each_with_object({}) do |v,m|
509
603
  m[v.to_s]=Fasp::Installation.instance.path(v) rescue 'Not Found'
510
- m
511
604
  end
512
605
  # read PATHs from ascp directly, and pvcl modules as well
513
- Open3.popen3(Fasp::Installation.instance.path(:ascp),'-DDL-') do |stdin, stdout, stderr, thread|
606
+ Open3.popen3(Fasp::Installation.instance.path(:ascp),'-DDL-') do |_stdin, _stdout, stderr, thread|
514
607
  last_line=''
515
- while line=stderr.gets do
608
+ while (line=stderr.gets)
516
609
  line.chomp!
517
610
  last_line=line
518
611
  case line
519
- when %r{^DBG Path ([^ ]+) (dir|file) +: (.*)$};data[$1]=$3
520
- when %r{^DBG Added module group:"([^"]+)" name:"([^"]+)", version:"([^"]+)" interface:"([^"]+)"$};data[$2]=$4
521
- when %r{^DBG License result \(/license/(\S+)\): (.+)$};data[$1]=$2
522
- when %r{^LOG (.+) version ([0-9.]+)$};data['product_name']=$1;data['product_version']=$2
523
- when %r{^LOG Initializing FASP version ([^,]+),};data['ascp_version']=$1
612
+ when %r{^DBG Path ([^ ]+) (dir|file) +: (.*)$} then data[Regexp.last_match(1)]=Regexp.last_match(3)
613
+ when %r{^DBG Added module group:"([^"]+)" name:"([^"]+)", version:"([^"]+)" interface:"([^"]+)"$} then data[Regexp.last_match(2)]=Regexp.last_match(4)
614
+ when %r{^DBG License result \(/license/(\S+)\): (.+)$} then data[Regexp.last_match(1)]=Regexp.last_match(2)
615
+ when %r{^LOG (.+) version ([0-9.]+)$} then data['product_name']=Regexp.last_match(1);data['product_version']=Regexp.last_match(2)
616
+ when %r{^LOG Initializing FASP version ([^,]+),} then data['ascp_version']=Regexp.last_match(1)
524
617
  end
525
618
  end
526
- if !thread.value.exitstatus.eql?(1) and !data.has_key?('root')
619
+ if !thread.value.exitstatus.eql?(1) && !data.has_key?('root')
527
620
  raise last_line
528
621
  end
529
622
  end
530
623
  data['keypass']=Fasp::Installation.instance.bypass_pass
531
- return {:type=>:single_object, :data=>data}
624
+ return {type: :single_object, data: data}
532
625
  when :products
533
- command=self.options.get_next_command([:list,:use])
626
+ command=options.get_next_command([:list,:use])
534
627
  case command
535
628
  when :list
536
- return {:type=>:object_list, :data=>Fasp::Installation.instance.installed_products, :fields=>['name','app_root']}
629
+ return {type: :object_list, data: Fasp::Installation.instance.installed_products, fields: ['name','app_root']}
537
630
  when :use
538
- default_product=self.options.get_next_argument('product name')
631
+ default_product=options.get_next_argument('product name')
539
632
  Fasp::Installation.instance.use_ascp_from_product(default_product)
540
633
  preset_name=set_global_default(:ascp_path,Fasp::Installation.instance.path(:ascp))
541
634
  return Main.result_status("Saved to default global preset #{preset_name}")
542
635
  end
543
636
  when :install
544
- v=Fasp::Installation.instance.install_sdk(self.options.get_option(:sdk_url,:mandatory))
637
+ v=Fasp::Installation.instance.install_sdk(options.get_option(:sdk_url,:mandatory))
545
638
  return Main.result_status("Installed version #{v}")
546
639
  when :spec
547
- return {type: :object_list, data: Fasp::Parameters.man_table, fields: ['name','type',Fasp::Parameters::SUPPORTED_AGENTS_SHORT.map{|i|i.to_s},'description'].flatten}
640
+ return {type: :object_list, data: Fasp::Parameters.man_table, fields: ['name','type',Fasp::Parameters::SUPPORTED_AGENTS_SHORT.map(&:to_s),'description'].flatten}
641
+ when :errors
642
+ error_data=[]
643
+ Fasp::ERROR_INFO.each_pair do |code,prop|
644
+ error_data.push(code: code, mnemonic: prop[:c], retry: prop[:r], info: prop[:a])
645
+ end
646
+ return {type: :object_list, data: error_data}
548
647
  end
549
648
  raise "unexpected case: #{command}"
550
649
  end
551
650
 
552
- ACTIONS=[:gem_path, :genkey,:plugins,:flush_tokens,:list,:overview,:open,:echo,:id,:documentation,:wizard,:export_to_cli,:detect,:coffee,:ascp,:email_test,:smtp_settings,:proxy_check,:folder,:file,:check_update,:initdemo]
651
+ # legacy actions available globally
652
+ PRESET_GBL_ACTIONS=[:list,:overview].freeze
653
+ # require existing preset
654
+ PRESET_EXST_ACTIONS=[:show,:delete,:get,:unset].freeze
655
+ # require id
656
+ PRESET_INSTANCE_ACTIONS=[PRESET_EXST_ACTIONS,:initialize,:update,:ask,:set].flatten.freeze
657
+ PRESET_ALL_ACTIONS=[PRESET_GBL_ACTIONS,PRESET_INSTANCE_ACTIONS].flatten.freeze
658
+
659
+ def execute_file_action(action,config_name)
660
+ action=options.get_next_command(PRESET_ALL_ACTIONS) if action.nil?
661
+ config_name=instance_identifier() if config_name.nil? && PRESET_INSTANCE_ACTIONS.include?(action)
662
+ # those operations require existing option
663
+ raise "no such preset: #{config_name}" if PRESET_EXST_ACTIONS.include?(action) && !@config_presets.has_key?(config_name)
664
+ selected_preset=@config_presets[config_name]
665
+ case action
666
+ when :list
667
+ return {type: :value_list, data: @config_presets.keys, name: 'name'}
668
+ when :overview
669
+ return {type: :object_list, data: self.class.flatten_all_config(@config_presets)}
670
+ when :show
671
+ raise "no such config: #{config_name}" if selected_preset.nil?
672
+ return {type: :single_object, data: selected_preset}
673
+ when :delete
674
+ @config_presets.delete(config_name)
675
+ save_presets_to_config_file
676
+ return Main.result_status("Deleted: #{config_name}")
677
+ when :get
678
+ param_name=options.get_next_argument('parameter name')
679
+ value=selected_preset[param_name]
680
+ raise "no such option in preset #{config_name} : #{param_name}" if value.nil?
681
+ case value
682
+ when Numeric,String then return {type: :text, data: ExtendedValue.instance.evaluate(value.to_s)}
683
+ end
684
+ return {type: :single_object, data: value}
685
+ when :unset
686
+ param_name=options.get_next_argument('parameter name')
687
+ selected_preset.delete(param_name)
688
+ save_presets_to_config_file
689
+ return Main.result_status("Removed: #{config_name}: #{param_name}")
690
+ when :set
691
+ param_name=options.get_next_argument('parameter name')
692
+ param_value=options.get_next_argument('parameter value')
693
+ if !@config_presets.has_key?(config_name)
694
+ Log.log.debug("no such config name: #{config_name}, initializing")
695
+ selected_preset=@config_presets[config_name]={}
696
+ end
697
+ if selected_preset.has_key?(param_name)
698
+ Log.log.warn("overwriting value: #{selected_preset[param_name]}")
699
+ end
700
+ selected_preset[param_name]=param_value
701
+ save_presets_to_config_file
702
+ return Main.result_status("Updated: #{config_name}: #{param_name} <- #{param_value}")
703
+ when :initialize
704
+ config_value=options.get_next_argument('extended value (Hash)')
705
+ if @config_presets.has_key?(config_name)
706
+ Log.log.warn("configuration already exists: #{config_name}, overwriting")
707
+ end
708
+ @config_presets[config_name]=config_value
709
+ save_presets_to_config_file
710
+ return Main.result_status("Modified: #{@option_config_file}")
711
+ when :update
712
+ # get unprocessed options
713
+ theopts=options.get_options_table
714
+ Log.log.debug("opts=#{theopts}")
715
+ @config_presets[config_name]||={}
716
+ @config_presets[config_name].merge!(theopts)
717
+ # fix bug in 4.4 (creating key "true" in "default" preset)
718
+ @config_presets[CONF_PRESET_DEFAULT].delete(true) if @config_presets[CONF_PRESET_DEFAULT].is_a?(Hash)
719
+ save_presets_to_config_file
720
+ return Main.result_status("Updated: #{config_name}")
721
+ when :ask
722
+ options.ask_missing_mandatory=:yes
723
+ @config_presets[config_name]||={}
724
+ options.get_next_argument('option names',:multiple).each do |optionname|
725
+ option_value=options.get_interactive(:option,optionname)
726
+ @config_presets[config_name][optionname]=option_value
727
+ end
728
+ save_presets_to_config_file
729
+ return Main.result_status("Updated: #{config_name}")
730
+ end
731
+ end
732
+
733
+ ACTIONS=[PRESET_GBL_ACTIONS,:id,:preset,:open,:documentation,:genkey,:gem,:plugin,:flush_tokens,:echo,:wizard,:export_to_cli,:detect,:coffee,:ascp,:email_test,
734
+ :smtp_settings,:proxy_check,:folder,:file,:check_update,:initdemo,:vault].flatten.freeze
553
735
 
554
736
  # "config" plugin
555
737
  def execute_action
556
- action=self.options.get_next_command(ACTIONS)
738
+ action=options.get_next_command(ACTIONS)
557
739
  case action
558
- when :id
559
- config_name=self.options.get_next_argument('config name')
560
- action=self.options.get_next_command([:show,:delete,:set,:get,:unset,:initialize,:update,:ask])
561
- # those operations require existing option
562
- raise "no such preset: #{config_name}" if [:show,:delete,:get,:unset].include?(action) and !@config_presets.has_key?(config_name)
563
- selected_preset=@config_presets[config_name]
564
- case action
565
- when :show
566
- raise "no such config: #{config_name}" if selected_preset.nil?
567
- return {:type=>:single_object,:data=>selected_preset}
568
- when :delete
569
- @config_presets.delete(config_name)
570
- save_presets_to_config_file
571
- return Main.result_status("Deleted: #{config_name}")
572
- when :get
573
- param_name=self.options.get_next_argument('parameter name')
574
- value=selected_preset[param_name]
575
- raise "no such option in preset #{config_name} : #{param_name}" if value.nil?
576
- case value
577
- when Numeric,String; return {:type=>:text,:data=>ExtendedValue.instance.evaluate(value.to_s)}
578
- end
579
- return {:type=>:single_object,:data=>value}
580
- when :unset
581
- param_name=self.options.get_next_argument('parameter name')
582
- selected_preset.delete(param_name)
583
- save_presets_to_config_file
584
- return Main.result_status("Removed: #{config_name}: #{param_name}")
585
- when :set
586
- param_name=self.options.get_next_argument('parameter name')
587
- param_value=self.options.get_next_argument('parameter value')
588
- if !@config_presets.has_key?(config_name)
589
- Log.log.debug("no such config name: #{config_name}, initializing")
590
- selected_preset=@config_presets[config_name]={}
591
- end
592
- if selected_preset.has_key?(param_name)
593
- Log.log.warn("overwriting value: #{selected_preset[param_name]}")
594
- end
595
- selected_preset[param_name]=param_value
596
- save_presets_to_config_file
597
- return Main.result_status("Updated: #{config_name}: #{param_name} <- #{param_value}")
598
- when :initialize
599
- config_value=self.options.get_next_argument('extended value (Hash)')
600
- if @config_presets.has_key?(config_name)
601
- Log.log.warn("configuration already exists: #{config_name}, overwriting")
602
- end
603
- @config_presets[config_name]=config_value
604
- save_presets_to_config_file
605
- return Main.result_status("Modified: #{@option_config_file}")
606
- when :update
607
- default_for_plugin=self.options.get_option(:default,:mandatory)
608
- # get unprocessed options
609
- theopts=self.options.get_options_table
610
- Log.log.debug("opts=#{theopts}")
611
- @config_presets[config_name]||={}
612
- @config_presets[config_name].merge!(theopts)
613
- if ! default_for_plugin.nil?
614
- @config_presets[CONF_PRESET_DEFAULT]||=Hash.new
615
- @config_presets[CONF_PRESET_DEFAULT][default_for_plugin]=config_name
616
- end
617
- save_presets_to_config_file
618
- return Main.result_status("Updated: #{config_name}")
619
- when :ask
620
- self.options.ask_missing_mandatory=:yes
621
- @config_presets[config_name]||={}
622
- self.options.get_next_argument('option names',:multiple).each do |optionname|
623
- option_value=self.options.get_interactive(:option,optionname)
624
- @config_presets[config_name][optionname]=option_value
625
- end
626
- save_presets_to_config_file
627
- return Main.result_status("Updated: #{config_name}")
628
- end
740
+ when *PRESET_GBL_ACTIONS # older syntax
741
+ return execute_file_action(action,nil)
742
+ when :id # older syntax
743
+ return execute_file_action(nil,options.get_next_argument('config name'))
744
+ when :preset # newer syntax
745
+ return execute_file_action(nil,nil)
746
+ when :open
747
+ OpenApplication.instance.uri(@option_config_file.to_s) #file://
748
+ return Main.result_nothing
629
749
  when :documentation
630
750
  section=options.get_next_argument('private key file path',:single,:optional)
631
751
  section='#'+section unless section.nil?
632
- OpenApplication.instance.uri("#{@help_url}#{section}")
633
- return Main.result_nothing
634
- when :open
635
- OpenApplication.instance.uri("#{@option_config_file}") #file://
752
+ OpenApplication.instance.uri("#{@info[:help]}#{section}")
636
753
  return Main.result_nothing
637
754
  when :genkey # generate new rsa key
638
- private_key_path=self.options.get_next_argument('private key file path')
755
+ private_key_path=options.get_next_argument('private key file path')
639
756
  generate_rsa_private_key(private_key_path,DEFAULT_PRIVKEY_LENGTH)
640
757
  return Main.result_status('Generated key: '+private_key_path)
641
758
  when :echo # display the content of a value given on command line
642
- result={:type=>:other_struct, :data=>self.options.get_next_argument('value')}
759
+ result={type: :other_struct, data: options.get_next_argument('value')}
643
760
  # special for csv
644
- result[:type]=:object_list if result[:data].is_a?(Array) and result[:data].first.is_a?(Hash)
761
+ result[:type]=:object_list if result[:data].is_a?(Array) && result[:data].first.is_a?(Hash)
645
762
  return result
646
763
  when :flush_tokens
647
764
  deleted_files=Oauth.flush_tokens
648
- return {:type=>:value_list, :name=>'file',:data=>deleted_files}
649
- when :plugins
650
- return {:data => @plugins.keys.map { |i| { 'plugin' => i.to_s, 'path' => @plugins[i][:source] } } , :fields => ['plugin','path'], :type => :object_list }
651
- when :list
652
- return {:data => @config_presets.keys, :type => :value_list, :name => 'name'}
653
- when :overview
654
- return {:type=>:object_list,:data=>self.class.flatten_all_config(@config_presets)}
765
+ return {type: :value_list, data: deleted_files, name: 'file'}
766
+ when :plugin
767
+ case options.get_next_command([:list,:create])
768
+ when :list
769
+ return {type: :object_list, data: @plugins.keys.map { |i| { 'plugin' => i.to_s, 'path' => @plugins[i][:source] } }, fields: ['plugin','path']}
770
+ when :create
771
+ plugin_name=options.get_next_argument('name',:single,:mandatory).downcase
772
+ plugin_folder=options.get_next_argument('folder',:single,:optional) || File.join(@main_folder,ASPERA_PLUGINS_FOLDERNAME)
773
+ plugin_file=File.join(plugin_folder,"#{plugin_name}.rb")
774
+ content=<<~END_OF_PLUGIN_CODE
775
+ require 'aspera/cli/plugin'
776
+ module Aspera
777
+ module Cli
778
+ module Plugins
779
+ class #{plugin_name.capitalize} < Plugin
780
+ ACTIONS=[]
781
+ def execute_action; return Main.result_status('You called plugin #{plugin_name}'); end
782
+ end # #{plugin_name.capitalize}
783
+ end # Plugins
784
+ end # Cli
785
+ end # Aspera
786
+ END_OF_PLUGIN_CODE
787
+ File.write(plugin_file,content)
788
+ return Main.result_status("Created #{plugin_file}")
789
+ end
655
790
  when :wizard
656
791
  # interactive mode
657
- self.options.ask_missing_mandatory=true
792
+ options.ask_missing_mandatory=true
658
793
  # register url option
659
794
  BasicAuthPlugin.new(@agents.merge(skip_option_header: true))
660
795
  # get from option, or ask
661
- instance_url=self.options.get_option(:url,:mandatory)
796
+ instance_url=options.get_option(:url,:mandatory)
662
797
  # allow user to tell the preset name
663
- preset_name=self.options.get_option(:id,:optional)
664
- appli=ApiDetector.discover_product(instance_url)
665
- plugin_name="<replace per app>"
666
- test_args="<replace per app>"
667
- case appli[:product]
798
+ preset_name=options.get_option(:id,:optional)
799
+ # allow user to specify type of application
800
+ application=options.get_option(:value,:optional)
801
+ application=application.nil? ? identify_plugin_for_url(instance_url)[:product] : application.to_sym
802
+ plugin_name='<replace per app>'
803
+ test_args='<replace per app>'
804
+ case application
668
805
  when :aoc
669
806
  self.format.display_status('Detected: Aspera on Cloud'.bold)
670
807
  plugin_name=AOC_COMMAND_CURRENT
671
- organization,instance_domain=AoC.parse_url(instance_url)
808
+ organization=AoC.parse_url(instance_url).first
672
809
  # if not defined by user, generate name
673
- preset_name=[appli[:product],organization].join('_') if preset_name.nil?
810
+ preset_name=[application,organization].join('_') if preset_name.nil?
674
811
  self.format.display_status("Preparing preset: #{preset_name}")
675
812
  # init defaults if necessary
676
813
  @config_presets[CONF_PRESET_DEFAULT]||={}
677
- option_override=self.options.get_option(:override,:mandatory)
678
- option_default=self.options.get_option(:default,:mandatory)
814
+ option_override=options.get_option(:override,:mandatory)
815
+ option_default=options.get_option(:default,:mandatory)
679
816
  Log.log.error("override=#{option_override} -> #{option_override.class}")
680
- 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)
681
- raise CliError,"Preset already exists: #{preset_name} (use --override=yes or --id=<name>)" if !option_override and @config_presets.has_key?(preset_name)
817
+ raise CliError,"A default configuration already exists for plugin '#{plugin_name}' (use --override=yes or --default=no)" if !option_override && option_default && @config_presets[CONF_PRESET_DEFAULT].has_key?(plugin_name)
818
+ raise CliError,"Preset already exists: #{preset_name} (use --override=yes or --id=<name>)" if !option_override && @config_presets.has_key?(preset_name)
682
819
  # lets see if path to priv key is provided
683
- private_key_path=self.options.get_option(:pkeypath,:optional)
820
+ private_key_path=options.get_option(:pkeypath,:optional)
684
821
  # give a chance to provide
685
822
  if private_key_path.nil?
686
823
  self.format.display_status('Please provide path to your private RSA key, or empty to generate one:')
687
- private_key_path=self.options.get_option(:pkeypath,:mandatory).to_s
824
+ private_key_path=options.get_option(:pkeypath,:mandatory).to_s
688
825
  end
689
826
  # else generate path
690
827
  if private_key_path.empty?
@@ -702,74 +839,73 @@ END_OF_TEMPLATE
702
839
  # declare command line options for AoC
703
840
  require 'aspera/cli/plugins/aoc'
704
841
  # make username mandatory for jwt, this triggers interactive input
705
- self.options.get_option(:username,:mandatory)
842
+ options.get_option(:username,:mandatory)
706
843
  # instanciate AoC plugin, so that command line options are known
707
- files_plugin=self.class.plugin_new(plugin_name,@agents.merge({skip_basic_auth_options: true, private_key_path: private_key_path}))
708
- aoc_api=files_plugin.get_api
844
+ aoc_api=self.class.plugin_class(plugin_name).new(@agents.merge({skip_basic_auth_options: true, private_key_path: private_key_path})).aoc_api
709
845
  auto_set_pub_key=false
710
846
  auto_set_jwt=false
711
847
  use_browser_authentication=false
712
- if self.options.get_option(:use_generic_client)
848
+ if options.get_option(:use_generic_client)
713
849
  self.format.display_status('Using global client_id.')
714
850
  self.format.display_status('Please Login to your Aspera on Cloud instance.'.red)
715
851
  self.format.display_status('Navigate to your "Account Settings"'.red)
716
852
  self.format.display_status('Check or update the value of "Public Key" to be:'.red.blink)
717
- self.format.display_status("#{pub_key_pem}")
718
- if ! self.options.get_option(:test_mode)
853
+ self.format.display_status(pub_key_pem.to_s)
854
+ if !options.get_option(:test_mode)
719
855
  self.format.display_status('Once updated or validated, press enter.')
720
856
  OpenApplication.instance.uri(instance_url)
721
- STDIN.gets
857
+ $stdin.gets
722
858
  end
723
859
  else
724
860
  self.format.display_status('Using organization specific client_id.')
725
- if self.options.get_option(:client_id,:optional).nil? or self.options.get_option(:client_secret,:optional).nil?
861
+ if options.get_option(:client_id,:optional).nil? || options.get_option(:client_secret,:optional).nil?
726
862
  self.format.display_status('Please login to your Aspera on Cloud instance.'.red)
727
863
  self.format.display_status('Go to: Apps->Admin->Organization->Integrations')
728
864
  self.format.display_status('Create or check if there is an existing integration named:')
729
- self.format.display_status("- name: #{@tool_name}")
865
+ self.format.display_status("- name: #{@info[:name]}")
730
866
  self.format.display_status("- redirect uri: #{DEFAULT_REDIRECT}")
731
867
  self.format.display_status('- origin: localhost')
732
868
  self.format.display_status('Once created or identified,')
733
869
  self.format.display_status('Please enter:'.red)
734
870
  end
735
871
  OpenApplication.instance.uri("#{instance_url}/#{AOC_PATH_API_CLIENTS}")
736
- self.options.get_option(:client_id,:mandatory)
737
- self.options.get_option(:client_secret,:mandatory)
872
+ options.get_option(:client_id,:mandatory)
873
+ options.get_option(:client_secret,:mandatory)
738
874
  use_browser_authentication=true
739
875
  end
740
876
  if use_browser_authentication
741
877
  self.format.display_status('We will use web authentication to bootstrap.')
742
878
  auto_set_pub_key=true
743
879
  auto_set_jwt=true
744
- @api_aoc.oauth.params[:auth]=:web
745
- @api_aoc.oauth.params[:redirect_uri]=DEFAULT_REDIRECT
746
- @api_aoc.oauth.params[:scope]=AoC::SCOPE_FILES_ADMIN
880
+ aoc_api.oauth.params[:crtype]=:web
881
+ aoc_api.oauth.params[:web][:redirect_uri]=DEFAULT_REDIRECT
882
+ aoc_api.oauth.params[:scope]=AoC::SCOPE_FILES_ADMIN
747
883
  end
748
884
  myself=aoc_api.read('self')[:data]
749
885
  if auto_set_pub_key
750
- raise CliError,'Public key is already set in profile (use --override=yes)' unless myself['public_key'].empty? or option_override
886
+ raise CliError,'Public key is already set in profile (use --override=yes)' unless myself['public_key'].empty? || option_override
751
887
  self.format.display_status('Updating profile with new key')
752
888
  aoc_api.update("users/#{myself['id']}",{'public_key'=>pub_key_pem})
753
889
  end
754
890
  if auto_set_jwt
755
891
  self.format.display_status('Enabling JWT for client')
756
- aoc_api.update("clients/#{self.options.get_option(:client_id)}",{'jwt_grant_enabled'=>true,'explicit_authorization_required'=>false})
892
+ aoc_api.update("clients/#{options.get_option(:client_id)}",{'jwt_grant_enabled'=>true,'explicit_authorization_required'=>false})
757
893
  end
758
894
  self.format.display_status("Creating new config preset: #{preset_name}")
759
895
  @config_presets[preset_name]={
760
- :url.to_s =>self.options.get_option(:url),
896
+ :url.to_s =>options.get_option(:url),
761
897
  :username.to_s =>myself['email'],
762
898
  :auth.to_s =>:jwt.to_s,
763
- :private_key.to_s =>'@file:'+private_key_path,
899
+ :private_key.to_s =>'@file:'+private_key_path
764
900
  }
765
901
  # set only if non nil
766
902
  [:client_id,:client_secret].each do |s|
767
- o=self.options.get_option(s)
903
+ o=options.get_option(s)
768
904
  @config_presets[preset_name][s.to_s] = o unless o.nil?
769
905
  end
770
- test_args="#{plugin_name} user info show"
906
+ test_args="#{plugin_name} user profile show"
771
907
  else
772
- raise CliBadArgument,"Supports only: aoc. Detected: #{appli}"
908
+ raise CliBadArgument,"Supports only: aoc. Detected: #{application}"
773
909
  end # product
774
910
  if option_default
775
911
  self.format.display_status("Setting config preset as default for #{plugin_name}")
@@ -779,29 +915,29 @@ END_OF_TEMPLATE
779
915
  end
780
916
  self.format.display_status('Saving config file.')
781
917
  save_presets_to_config_file
782
- return Main.result_status("Done.\nYou can test with:\n#{@tool_name} #{test_args}")
918
+ return Main.result_status("Done.\nYou can test with:\n#{@info[:name]} #{test_args}")
783
919
  when :export_to_cli
784
920
  self.format.display_status('Exporting: Aspera on Cloud')
785
921
  require 'aspera/cli/plugins/aoc'
786
922
  # need url / username
787
923
  add_plugin_default_preset(AOC_COMMAND_V3.to_sym)
788
924
  # instanciate AoC plugin
789
- files_plugin=self.class.plugin_new(AOC_COMMAND_CURRENT,@agents) # TODO: is this line needed ?
790
- url=self.options.get_option(:url,:mandatory)
925
+ self.class.plugin_class(AOC_COMMAND_CURRENT).new(@agents) # TODO: is this line needed ? get options ?
926
+ url=options.get_option(:url,:mandatory)
791
927
  cli_conf_file=Fasp::Installation.instance.cli_conf_file
792
928
  data=JSON.parse(File.read(cli_conf_file))
793
929
  organization,instance_domain=AoC.parse_url(url)
794
930
  key_basename='org_'+organization+'.pem'
795
931
  key_file=File.join(File.dirname(File.dirname(cli_conf_file)),'etc',key_basename)
796
- File.write(key_file,self.options.get_option(:private_key,:mandatory))
932
+ File.write(key_file,options.get_option(:private_key,:mandatory))
797
933
  new_conf={
798
934
  'organization' => organization,
799
935
  'hostname' => [organization,instance_domain].join('.'),
800
936
  'privateKeyFilename' => key_basename,
801
- 'username' => self.options.get_option(:username,:mandatory)
937
+ 'username' => options.get_option(:username,:mandatory)
802
938
  }
803
- new_conf['clientId']=self.options.get_option(:client_id,:optional)
804
- new_conf['clientSecret']=self.options.get_option(:client_secret,:optional)
939
+ new_conf['clientId']=options.get_option(:client_id,:optional)
940
+ new_conf['clientSecret']=options.get_option(:client_secret,:optional)
805
941
  if new_conf['clientId'].nil?
806
942
  new_conf['clientId'],new_conf['clientSecret']=AoC.get_client_info()
807
943
  end
@@ -818,14 +954,18 @@ END_OF_TEMPLATE
818
954
  when :detect
819
955
  # need url / username
820
956
  BasicAuthPlugin.new(@agents)
821
- return Main.result_status("Found: #{ApiDetector.discover_product(self.options.get_option(:url,:mandatory))}")
957
+ return {type: :single_object, data: identify_plugin_for_url(options.get_option(:url,:mandatory))}
822
958
  when :coffee
823
959
  OpenApplication.instance.uri('https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg')
824
960
  return Main.result_nothing
825
961
  when :ascp
826
962
  execute_action_ascp
827
- when :gem_path
828
- return Main.result_status(self.class.gem_root)
963
+ when :gem
964
+ case options.get_next_command([:path,:version,:name])
965
+ when :path then return Main.result_status(self.class.gem_src_root)
966
+ when :version then return Main.result_status(Aspera::Cli::VERSION)
967
+ when :name then return Main.result_status(@info[:gem])
968
+ end
829
969
  when :folder
830
970
  return Main.result_status(@main_folder)
831
971
  when :file
@@ -834,19 +974,21 @@ END_OF_TEMPLATE
834
974
  send_email_template({},EMAIL_TEST_TEMPLATE)
835
975
  return Main.result_nothing
836
976
  when :smtp_settings
837
- return {:type=>:single_object,:data=>email_settings}
977
+ return {type: :single_object, data: email_settings}
838
978
  when :proxy_check
839
- pac_url=self.options.get_option(:fpac,:mandatory)
840
- server_url=self.options.get_next_argument('server url')
841
- return Main.result_status(Aspera::ProxyAutoConfig.new(UriReader.read(pac_url)).get_proxy(server_url))
979
+ # ensure fpac was provided
980
+ options.get_option(:fpac,:mandatory)
981
+ server_url=options.get_next_argument('server url')
982
+ return Main.result_status(@pac_exec.find_proxy_for_url(server_url))
842
983
  when :check_update
843
- return {:type=>:single_object, :data=>check_gem_version}
984
+ return {type: :single_object, data: check_gem_version}
844
985
  when :initdemo
845
986
  if @config_presets.has_key?(DEMO_SERVER_PRESET)
846
987
  Log.log.warn("Demo server preset already present: #{DEMO_SERVER_PRESET}")
847
988
  else
848
989
  Log.log.info("Creating Demo server preset: #{DEMO_SERVER_PRESET}")
849
- @config_presets[DEMO_SERVER_PRESET]={'url'=>'ssh://'+DEMO+'.asperasoft.com:33001','username'=>AOC_COMMAND_V2,'ssAP'.downcase.reverse+'drow'.reverse=>DEMO+AOC_COMMAND_V2}
990
+ @config_presets[DEMO_SERVER_PRESET]=
991
+ {'url'=>'ssh://'+DEMO+'.asperasoft.com:33001','username'=>AOC_COMMAND_V2,'ssAP'.downcase.reverse+'drow'.reverse=>DEMO+AOC_COMMAND_V2}
850
992
  end
851
993
  @config_presets[CONF_PRESET_DEFAULT]||={}
852
994
  if @config_presets[CONF_PRESET_DEFAULT].has_key?(SERVER_COMMAND)
@@ -857,16 +999,56 @@ END_OF_TEMPLATE
857
999
  Log.log.info("Setting server default preset to : #{DEMO_SERVER_PRESET}")
858
1000
  end
859
1001
  save_presets_to_config_file
860
- return Main.result_status("Done")
1002
+ return Main.result_status('Done')
1003
+ when :vault
1004
+ command=options.get_next_command([:init,:list,:get,:set,:delete])
1005
+ case command
1006
+ when :init
1007
+ type=options.get_option(:value,:optional)
1008
+ case type
1009
+ when 'config',NilClass
1010
+ raise 'default secrets already exists' if @config_presets.has_key?(CONF_PRESET_SECRETS)
1011
+ @config_presets[CONF_PRESET_SECRETS]={}
1012
+ set_global_default(:secrets,"@preset:#{CONF_PRESET_SECRETS}")
1013
+ else raise 'no such vault type'
1014
+ end
1015
+ return Main.result_status('Done')
1016
+ when :list
1017
+ return {type: :object_list, data: vault.list}
1018
+ when :set
1019
+ # register url option
1020
+ BasicAuthPlugin.new(@agents.merge(skip_option_header: true))
1021
+ username=options.get_option(:username,:mandatory)
1022
+ url=options.get_option(:url,:mandatory)
1023
+ description=options.get_option(:value,:optional)
1024
+ secret=options.get_next_argument('secret')
1025
+ vault.set(username: username, url: url, description: description, secret: secret)
1026
+ save_presets_to_config_file if vault.is_a?(Keychain::EncryptedHash)
1027
+ return Main.result_status('Done')
1028
+ when :get
1029
+ # register url option
1030
+ BasicAuthPlugin.new(@agents.merge(skip_option_header: true))
1031
+ username=options.get_option(:username,:mandatory)
1032
+ url=options.get_option(:url,:optional)
1033
+ result=vault.get(username: username, url: url)
1034
+ return {type: :single_object, data: result}
1035
+ when :delete
1036
+ # register url option
1037
+ BasicAuthPlugin.new(@agents.merge(skip_option_header: true))
1038
+ username=options.get_option(:username,:mandatory)
1039
+ url=options.get_option(:url,:optional)
1040
+ vault.delete(username: username, url: url)
1041
+ return Main.result_status('Done')
1042
+ end
861
1043
  else raise 'INTERNAL ERROR: wrong case'
862
1044
  end
863
1045
  end
864
1046
 
865
1047
  # @return email server setting with defaults if not defined
866
1048
  def email_settings
867
- smtp=self.options.get_option(:smtp,:mandatory)
1049
+ smtp=options.get_option(:smtp,:mandatory)
868
1050
  # change string keys into symbol keys
869
- smtp=smtp.keys.inject({}){|m,v|m[v.to_sym]=smtp[v];m}
1051
+ smtp=smtp.keys.each_with_object({}){|v,m|m[v.to_sym]=smtp[v];}
870
1052
  # defaults
871
1053
  smtp[:tls]||=true
872
1054
  smtp[:port]||=smtp[:tls]?587:25
@@ -896,7 +1078,7 @@ END_OF_TEMPLATE
896
1078
  raise "Missing email parameter: #{n}" unless vars.has_key?(n)
897
1079
  end
898
1080
  start_options=[mail_conf[:domain]]
899
- start_options.push(mail_conf[:username],mail_conf[:password],:login) if mail_conf.has_key?(:username) and mail_conf.has_key?(:password)
1081
+ start_options.push(mail_conf[:username],mail_conf[:password],:login) if mail_conf.has_key?(:username) && mail_conf.has_key?(:password)
900
1082
  # create a binding with only variables defined in vars
901
1083
  template_binding=empty_binding
902
1084
  # add variables to binding
@@ -909,8 +1091,8 @@ END_OF_TEMPLATE
909
1091
  Log.dump(:msg_with_headers,msg_with_headers)
910
1092
  smtp = Net::SMTP.new(mail_conf[:server], mail_conf[:port])
911
1093
  smtp.enable_starttls if mail_conf[:tls]
912
- smtp.start(*start_options) do |smtp|
913
- smtp.send_message(msg_with_headers, vars[:from_email], vars[:to])
1094
+ smtp.start(*start_options) do |smtp_session|
1095
+ smtp_session.send_message(msg_with_headers, vars[:from_email], vars[:to])
914
1096
  end
915
1097
  end
916
1098
 
@@ -924,16 +1106,16 @@ END_OF_TEMPLATE
924
1106
  # returns [String] name if config_presets has default
925
1107
  # returns nil if there is no config or bypass default params
926
1108
  def get_plugin_default_config_name(plugin_sym)
927
- raise "internal error: config_presets shall be defined" if @config_presets.nil?
1109
+ raise 'internal error: config_presets shall be defined' if @config_presets.nil?
928
1110
  if !@use_plugin_defaults
929
1111
  Log.log.debug('skip default config')
930
1112
  return nil
931
1113
  end
932
- if @config_presets.has_key?(CONF_PRESET_DEFAULT) and
1114
+ if @config_presets.has_key?(CONF_PRESET_DEFAULT) &&
933
1115
  @config_presets[CONF_PRESET_DEFAULT].has_key?(plugin_sym.to_s)
934
1116
  default_config_name=@config_presets[CONF_PRESET_DEFAULT][plugin_sym.to_s]
935
1117
  if !@config_presets.has_key?(default_config_name)
936
- Log.log.error("Default config name [#{default_config_name}] specified for plugin [#{plugin_sym.to_s}], but it does not exist in config file.\nPlease fix the issue: either create preset with one parameter (#{@tool_name} config id #{default_config_name} init @json:'{}') or remove default (#{@tool_name} config id default remove #{plugin_sym.to_s}).")
1118
+ Log.log.error("Default config name [#{default_config_name}] specified for plugin [#{plugin_sym}], but it does not exist in config file.\nPlease fix the issue: either create preset with one parameter (#{@info[:name]} config id #{default_config_name} init @json:'{}') or remove default (#{@info[:name]} config id default remove #{plugin_sym}).")
937
1119
  end
938
1120
  raise CliError,"Config name [#{default_config_name}] must be a hash, check config file." if !@config_presets[default_config_name].is_a?(Hash)
939
1121
  return default_config_name
@@ -941,6 +1123,42 @@ END_OF_TEMPLATE
941
1123
  return nil
942
1124
  end # get_plugin_default_config_name
943
1125
 
1126
+ def vault
1127
+ if @vault.nil?
1128
+ vault_info=options.get_option(:secrets,:optional)
1129
+ case vault_info
1130
+ when Hash
1131
+ @vault=Keychain::EncryptedHash.new(vault_info)
1132
+ when /^system/
1133
+ name=vault_info.start_with?('system:') ? vault_info[7..-1] : nil
1134
+ case Environment.os
1135
+ when Environment::OS_X
1136
+ @vault=Keychain::MacosSecurity.new(name)
1137
+ when Environment::OS_WINDOWS,Environment::OS_LINUX,Environment::OS_AIX
1138
+ raise 'not implemented'
1139
+ else raise 'Error'
1140
+ end
1141
+ when NilClass
1142
+ # keep nil
1143
+ else
1144
+ raise CliBadArgument,'secrets shall be Hash'
1145
+ end
1146
+ end
1147
+ raise 'No vault defined' if @vault.nil?
1148
+ @vault
1149
+ end
1150
+
1151
+ def get_secret(options)
1152
+ raise 'options shall be Hash' unless options.is_a?(Hash)
1153
+ raise 'options shall have username' unless options.has_key?(:username)
1154
+ secret=self.options.get_option(:secret,:optional)
1155
+ if secret.nil?
1156
+ secret=vault.get(options) rescue nil
1157
+ # mandatory by default
1158
+ raise "please provide secret for #{options[:username]}" if secret.nil? && (options[:mandatory].nil? || options[:mandatory])
1159
+ end
1160
+ return secret
1161
+ end
944
1162
  end
945
1163
  end
946
1164
  end