aspera-cli 4.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3592 -0
  3. data/bin/ascli +7 -0
  4. data/bin/asession +89 -0
  5. data/docs/Makefile +59 -0
  6. data/docs/README.erb.md +3012 -0
  7. data/docs/README.md +13 -0
  8. data/docs/diagrams.txt +49 -0
  9. data/docs/secrets.make +38 -0
  10. data/docs/test_env.conf +117 -0
  11. data/docs/transfer_spec.html +99 -0
  12. data/examples/aoc.rb +17 -0
  13. data/examples/proxy.pac +60 -0
  14. data/examples/transfer.rb +115 -0
  15. data/lib/aspera/api_detector.rb +60 -0
  16. data/lib/aspera/ascmd.rb +151 -0
  17. data/lib/aspera/ats_api.rb +43 -0
  18. data/lib/aspera/cli/basic_auth_plugin.rb +38 -0
  19. data/lib/aspera/cli/extended_value.rb +88 -0
  20. data/lib/aspera/cli/formater.rb +238 -0
  21. data/lib/aspera/cli/listener/line_dump.rb +17 -0
  22. data/lib/aspera/cli/listener/logger.rb +20 -0
  23. data/lib/aspera/cli/listener/progress.rb +52 -0
  24. data/lib/aspera/cli/listener/progress_multi.rb +91 -0
  25. data/lib/aspera/cli/main.rb +304 -0
  26. data/lib/aspera/cli/manager.rb +440 -0
  27. data/lib/aspera/cli/plugin.rb +90 -0
  28. data/lib/aspera/cli/plugins/alee.rb +24 -0
  29. data/lib/aspera/cli/plugins/ats.rb +231 -0
  30. data/lib/aspera/cli/plugins/bss.rb +71 -0
  31. data/lib/aspera/cli/plugins/config.rb +806 -0
  32. data/lib/aspera/cli/plugins/console.rb +62 -0
  33. data/lib/aspera/cli/plugins/cos.rb +106 -0
  34. data/lib/aspera/cli/plugins/faspex.rb +377 -0
  35. data/lib/aspera/cli/plugins/faspex5.rb +93 -0
  36. data/lib/aspera/cli/plugins/node.rb +438 -0
  37. data/lib/aspera/cli/plugins/oncloud.rb +937 -0
  38. data/lib/aspera/cli/plugins/orchestrator.rb +169 -0
  39. data/lib/aspera/cli/plugins/preview.rb +464 -0
  40. data/lib/aspera/cli/plugins/server.rb +216 -0
  41. data/lib/aspera/cli/plugins/shares.rb +63 -0
  42. data/lib/aspera/cli/plugins/shares2.rb +114 -0
  43. data/lib/aspera/cli/plugins/sync.rb +65 -0
  44. data/lib/aspera/cli/plugins/xnode.rb +115 -0
  45. data/lib/aspera/cli/transfer_agent.rb +251 -0
  46. data/lib/aspera/cli/version.rb +5 -0
  47. data/lib/aspera/colors.rb +39 -0
  48. data/lib/aspera/command_line_builder.rb +137 -0
  49. data/lib/aspera/fasp/aoc.rb +24 -0
  50. data/lib/aspera/fasp/connect.rb +99 -0
  51. data/lib/aspera/fasp/error.rb +21 -0
  52. data/lib/aspera/fasp/error_info.rb +60 -0
  53. data/lib/aspera/fasp/http_gw.rb +81 -0
  54. data/lib/aspera/fasp/installation.rb +240 -0
  55. data/lib/aspera/fasp/listener.rb +11 -0
  56. data/lib/aspera/fasp/local.rb +377 -0
  57. data/lib/aspera/fasp/manager.rb +69 -0
  58. data/lib/aspera/fasp/node.rb +88 -0
  59. data/lib/aspera/fasp/parameters.rb +235 -0
  60. data/lib/aspera/fasp/resume_policy.rb +76 -0
  61. data/lib/aspera/fasp/uri.rb +51 -0
  62. data/lib/aspera/faspex_gw.rb +196 -0
  63. data/lib/aspera/hash_ext.rb +28 -0
  64. data/lib/aspera/log.rb +80 -0
  65. data/lib/aspera/nagios.rb +71 -0
  66. data/lib/aspera/node.rb +14 -0
  67. data/lib/aspera/oauth.rb +319 -0
  68. data/lib/aspera/on_cloud.rb +421 -0
  69. data/lib/aspera/open_application.rb +72 -0
  70. data/lib/aspera/persistency_action_once.rb +42 -0
  71. data/lib/aspera/persistency_folder.rb +91 -0
  72. data/lib/aspera/preview/file_types.rb +300 -0
  73. data/lib/aspera/preview/generator.rb +258 -0
  74. data/lib/aspera/preview/image_error.png +0 -0
  75. data/lib/aspera/preview/options.rb +35 -0
  76. data/lib/aspera/preview/utils.rb +131 -0
  77. data/lib/aspera/preview/video_error.png +0 -0
  78. data/lib/aspera/proxy_auto_config.erb.js +287 -0
  79. data/lib/aspera/proxy_auto_config.rb +34 -0
  80. data/lib/aspera/rest.rb +296 -0
  81. data/lib/aspera/rest_call_error.rb +13 -0
  82. data/lib/aspera/rest_error_analyzer.rb +98 -0
  83. data/lib/aspera/rest_errors_aspera.rb +58 -0
  84. data/lib/aspera/ssh.rb +53 -0
  85. data/lib/aspera/sync.rb +82 -0
  86. data/lib/aspera/temp_file_manager.rb +37 -0
  87. data/lib/aspera/uri_reader.rb +25 -0
  88. metadata +288 -0
@@ -0,0 +1,24 @@
1
+ require 'aspera/rest'
2
+ require 'aspera/on_cloud'
3
+
4
+ module Aspera
5
+ module Cli
6
+ module Plugins
7
+ class Alee < BasicAuthPlugin
8
+
9
+ ACTIONS=[ :entitlement ]
10
+
11
+ def execute_action
12
+ command=self.options.get_next_command(ACTIONS)
13
+ case command
14
+ when :entitlement
15
+ entitlement_id = self.options.get_option(:username,:mandatory),
16
+ customer_id = self.options.get_option(:password,:mandatory)
17
+ api_metering=OnCloud.metering_api(entitlement_id,customer_id)
18
+ return {:type=>:single_object, :data=>api_metering.read('entitlement')[:data]}
19
+ end
20
+ end
21
+ end # Aspera
22
+ end # Plugins
23
+ end # Cli
24
+ end # Aspera
@@ -0,0 +1,231 @@
1
+ require 'aspera/cli/plugins/node'
2
+ require 'aspera/ats_api'
3
+
4
+ module Aspera
5
+ module Cli
6
+ module Plugins
7
+ # list and download connect client versions
8
+ # https://52.44.83.163/docs/
9
+ # https://developer.ibm.com/aspera/docs/ats-api-reference/creating-ats-api-keys/
10
+ class Ats < Plugin
11
+ def initialize(env)
12
+ super(env)
13
+ self.options.add_opt_simple(:ibm_api_key,"IBM API key, see https://cloud.ibm.com/iam/apikeys")
14
+ self.options.add_opt_simple(:instance,"ATS instance in ibm cloud")
15
+ self.options.add_opt_simple(:ats_key,"ATS key identifier (ats_xxx)")
16
+ self.options.add_opt_simple(:ats_secret,"ATS key secret")
17
+ self.options.add_opt_simple(:params,"Parameters access key creation (@json:)")
18
+ self.options.add_opt_simple(:cloud,"Cloud provider")
19
+ self.options.add_opt_simple(:region,"Cloud region")
20
+ self.options.parse_options!
21
+ end
22
+
23
+ #
24
+ def server_by_cloud_region
25
+ # todo: provide list ?
26
+ cloud=self.options.get_option(:cloud,:mandatory).upcase
27
+ region=self.options.get_option(:region,:mandatory)
28
+ return @ats_api_pub.read("servers/#{cloud}/#{region}")[:data]
29
+ end
30
+
31
+ # require api key only if needed
32
+ def ats_api_pub_v1
33
+ return @ats_api_pub_v1_cache unless @ats_api_pub_v1_cache.nil?
34
+ @ats_api_pub_v1_cache=Rest.new({
35
+ :base_url => AtsApi.base_url+'/pub/v1',
36
+ :auth => {
37
+ :type => :basic,
38
+ :username => self.options.get_option(:ats_key,:mandatory),
39
+ :password => self.options.get_option(:ats_secret,:mandatory)}
40
+ })
41
+ end
42
+
43
+ def execute_action_access_key
44
+ commands=[:create,:list,:show,:modify,:delete,:node,:cluster,:entitlement]
45
+ command=self.options.get_next_command(commands)
46
+ # those dont require access key id
47
+ unless [:create,:list].include?(command)
48
+ access_key_id=self.options.get_option(:id,:mandatory)
49
+ end
50
+ case command
51
+ when :create
52
+ params=self.options.get_option(:params,:optional) || {}
53
+ server_data=nil
54
+ # if transfer_server_id not provided, get it from command line options
55
+ if !params.has_key?('transfer_server_id')
56
+ server_data=server_by_cloud_region
57
+ params['transfer_server_id']=server_data['id']
58
+ end
59
+ Log.log.debug("using params: #{params}".bg_red.gray)
60
+ if params.has_key?('storage')
61
+ case params['storage']['type']
62
+ # here we need somehow to map storage type to field to get for auth end point
63
+ when 'ibm-s3'
64
+ server_data2=nil
65
+ if server_data.nil?
66
+ server_data2=@ats_api_pub.all_servers.select{|s|s['id'].eql?(params['transfer_server_id'])}.first
67
+ raise "no such transfer server id: #{params['transfer_server_id']}" if server_data2.nil?
68
+ else
69
+ server_data2=@ats_api_pub.all_servers.select{|s|s['cloud'].eql?(server_data['cloud']) and s['region'].eql?(server_data['region']) and s.has_key?('s3_authentication_endpoint')}.first
70
+ raise "no such transfer server id: #{params['transfer_server_id']}" if server_data2.nil?
71
+ # specific one do not have s3 end point in id
72
+ params['transfer_server_id']=server_data2['id']
73
+ end
74
+ if !params['storage'].has_key?('authentication_endpoint')
75
+ params['storage']['endpoint'] = server_data2['s3_authentication_endpoint']
76
+ end
77
+ end
78
+ end
79
+ res=ats_api_pub_v1.create('access_keys',params)
80
+ return {:type=>:single_object, :data=>res[:data]}
81
+ # TODO : action : modify, with "PUT"
82
+ when :list
83
+ params=self.options.get_option(:params,:optional) || {'offset'=>0,'max_results'=>1000}
84
+ res=ats_api_pub_v1.read("access_keys",params)
85
+ return {:type=>:object_list, :data=>res[:data]['data'], :fields => ['name','id','created.at','modified.at']}
86
+ when :show
87
+ res=ats_api_pub_v1.read("access_keys/#{access_key_id}")
88
+ return {:type=>:single_object, :data=>res[:data]}
89
+ when :modify
90
+ params=self.options.get_option(:value,:mandatory)
91
+ params["id"]=access_key_id
92
+ res=ats_api_pub_v1.update("access_keys/#{access_key_id}",params)
93
+ return Main.result_status('modified')
94
+ when :entitlement
95
+ ak=ats_api_pub_v1.read("access_keys/#{access_key_id}")[:data]
96
+ api_bss=OnCloud.metering_api(ak['license']['entitlement_id'],ak['license']['customer_id'])
97
+ return {:type=>:single_object, :data=>api_bss.read('entitlement')[:data]}
98
+ when :delete
99
+ res=ats_api_pub_v1.delete("access_keys/#{access_key_id}")
100
+ return Main.result_status("deleted #{access_key_id}")
101
+ when :node
102
+ ak_data=ats_api_pub_v1.read("access_keys/#{access_key_id}")[:data]
103
+ server_data=@ats_api_pub.all_servers.select {|i| i['id'].start_with?(ak_data['transfer_server_id'])}.first
104
+ raise CliError,"no such server found" if server_data.nil?
105
+ api_node=Rest.new({
106
+ :base_url => server_data['transfer_setup_url'],
107
+ :auth => {
108
+ :type => :basic,
109
+ :username => access_key_id,
110
+ :password => self.config.get_secret(access_key_id)}})
111
+ command=self.options.get_next_command(Node::COMMON_ACTIONS)
112
+ return Node.new(@agents.merge(skip_basic_auth_options: true, node_api: api_node)).execute_action(command)
113
+ when :cluster
114
+ rest_params={
115
+ :base_url => ats_api_pub_v1.params[:base_url],
116
+ :auth => {
117
+ :type => :basic,
118
+ :username => access_key_id,
119
+ :password => self.config.get_secret(access_key_id)
120
+ }}
121
+ api_ak_auth=Rest.new(rest_params)
122
+ return {:type=>:single_object, :data=>api_ak_auth.read("servers")[:data]}
123
+ else raise "INTERNAL ERROR"
124
+ end
125
+ end
126
+
127
+ def execute_action_cluster_pub
128
+ command=self.options.get_next_command([ :clouds, :list, :show])
129
+ case command
130
+ when :clouds
131
+ return {:type=>:single_object, :data=>@ats_api_pub.cloud_names, :columns=>['id','name']}
132
+ when :list
133
+ return {:type=>:object_list, :data=>@ats_api_pub.all_servers, :fields=>['id','cloud','region']}
134
+ when :show
135
+ server_id=self.options.get_option(:id,:optional)
136
+ if server_id.nil?
137
+ server_data=server_by_cloud_region
138
+ else
139
+ server_data=@ats_api_pub.all_servers.select {|i| i['id'].eql?(server_id)}.first
140
+ raise "no such server id" if server_data.nil?
141
+ end
142
+ return {:type=>:single_object, :data=>server_data}
143
+ end
144
+ end
145
+
146
+ def ats_api_v2_auth_ibm(rest_add_headers={})
147
+ return Rest.new({
148
+ :base_url => AtsApi.base_url+'/v2',
149
+ :headers => rest_add_headers,
150
+ :auth => {
151
+ :type => :oauth2,
152
+ :base_url => 'https://iam.bluemix.net/identity',
153
+ #does not work: :base_url => 'https://iam.cloud.ibm.com/identity',
154
+ :grant => :ibm_apikey,
155
+ :api_key => self.options.get_option(:ibm_api_key,:mandatory)
156
+ }
157
+ })
158
+ end
159
+
160
+ def execute_action_api_key
161
+ command=self.options.get_next_command([:instances, :create, :list, :show, :delete])
162
+ if [:show,:delete].include?(command)
163
+ concerned_id=self.options.get_option(:id,:mandatory)
164
+ end
165
+ rest_add_header={}
166
+ if !command.eql?(:instances)
167
+ instance=self.options.get_option(:instance,:optional)
168
+ #Log.log.error("1>>#{instance}".red)
169
+ if instance.nil?
170
+ # Take the first Aspera on Cloud transfer service instance ID if not provided by user
171
+ instance=ats_api_v2_auth_ibm.read('instances')[:data]['data'].first
172
+ self.format.display_status("using first instance: #{instance}")
173
+ end
174
+ #Log.log.error("2>>#{instance}".red)
175
+ rest_add_header={'X-ATS-Service-Instance-Id'=>instance}
176
+ end
177
+ ats_ibm_api=ats_api_v2_auth_ibm(rest_add_header)
178
+ case command
179
+ when :instances
180
+ instances=ats_ibm_api.read('instances')[:data]
181
+ Log.log.warn("more instances remaining: #{instances['remaining']}") unless instances['remaining'].to_i.eql?(0)
182
+ return {:type=>:value_list, :data=>instances['data'], :name=>'instance'}
183
+ when :create
184
+ create_value=self.options.get_option(:value,:optional)||{}
185
+ created_key=ats_ibm_api.create('api_keys',create_value)[:data]
186
+ return {:type=>:single_object, :data=>created_key}
187
+ when :list # list known api keys in ATS (this require an api_key ...)
188
+ res=ats_ibm_api.read('api_keys',{'offset'=>0,'max_results'=>1000})
189
+ return {:type=>:value_list, :data=>res[:data]['data'], :name => 'ats_id'}
190
+ when :show # show one of api_key in ATS
191
+ res=ats_ibm_api.read("api_keys/#{concerned_id}")
192
+ return {:type=>:single_object, :data=>res[:data]}
193
+ when :delete #
194
+ res=ats_ibm_api.delete("api_keys/#{concerned_id}")
195
+ return Main.result_status("deleted #{concerned_id}")
196
+ else raise "INTERNAL ERROR"
197
+ end
198
+ end
199
+
200
+ ACTIONS=[ :cluster, :access_key ,:api_key, :aws_trust_policy]
201
+
202
+ # called for legacy and AoC
203
+ def execute_action_gen(ats_api_arg)
204
+ actions=ACTIONS
205
+ actions.delete(:api_key) unless ats_api_arg.nil?
206
+ command=self.options.get_next_command(actions)
207
+ @ats_api_pub_v1_cache=ats_api_arg
208
+ # keep as member variable as we may want to use the api in AoC name space
209
+ @ats_api_pub = AtsApi.new
210
+ case command
211
+ when :cluster # display general ATS cluster information, this uses public API, no auth
212
+ return execute_action_cluster_pub
213
+ when :access_key
214
+ return execute_action_access_key
215
+ when :api_key # manage credential to access ATS API
216
+ return execute_action_api_key
217
+ when :aws_trust_policy
218
+ res=ats_api_pub_v1.read('aws/trustpolicy',{:region=>self.options.get_option(:region,:mandatory)})[:data]
219
+ return {:type=>:single_object, :data=>res}
220
+ else raise "ERROR"
221
+ end
222
+ end
223
+
224
+ # called for legacy ATS only
225
+ def execute_action
226
+ execute_action_gen(nil)
227
+ end
228
+ end
229
+ end
230
+ end # Cli
231
+ end # Aspera
@@ -0,0 +1,71 @@
1
+ require 'aspera/rest'
2
+
3
+ module Aspera
4
+ module Cli
5
+ module Plugins
6
+ class Bss < BasicAuthPlugin
7
+
8
+ ACTIONS=[ :subscription ]
9
+
10
+ FIELDS={
11
+ 'bssSubscriptions' => %w{id name termVolumeGb termMonths trial startDate endDate plan renewalType chargeAgreementNumber customerName}
12
+ }
13
+
14
+ def initialize(env)
15
+ super(env)
16
+ if env.has_key?(:bss_api)
17
+ @api_bss=env[:bss_api]
18
+ end
19
+ end
20
+
21
+ def all_fields(name)
22
+ return FIELDS[name].join(' ')
23
+ end
24
+
25
+ def execute_action
26
+ if @api_bss.nil?
27
+ key = self.options.get_option(:password,:mandatory)
28
+ @api_bss=Rest.new(
29
+ base_url: 'https://dashboard.bss.asperasoft.com/platform',
30
+ headers: {cookie: "_dashboard_key=#{key}"})
31
+ end
32
+ command=self.options.get_next_command(ACTIONS)
33
+ case command
34
+ when :subscription
35
+ command=self.options.get_next_command([:find,:show, :instances])
36
+ object='bssSubscriptions'
37
+ case command
38
+ when :find
39
+ query = self.options.get_option(:query,:mandatory) # AOC_ORGANIZATION_QUERY AOC_USER_EMAIL
40
+ value = self.options.get_option(:value,:mandatory)
41
+ request={
42
+ 'variables'=>{'filter'=>{'key'=>query,'value'=>value}},
43
+ 'query'=>"query($filter: BssSubscriptionFilter!) {#{object}(filter: $filter) { #{all_fields('bssSubscriptions')} } }"
44
+ }
45
+ result=@api_bss.create('graphql',request)[:data]
46
+ # give fields to keep order
47
+ return {:type=>:object_list, :data=>result['data'][object],:fields=> FIELDS['bssSubscriptions']}
48
+ when :show
49
+ id = self.options.get_option(:id,:mandatory)
50
+ request={
51
+ 'variables'=>{'id'=>id},
52
+ 'query'=>"query($id: ID!) {#{object}(id: $id) { #{all_fields('bssSubscriptions')} roleAssignments(uniqueSubjectId: true) { id subjectId } instances { id state planId serviceId ssmSubscriptionId entitlement { id } aocOrganization { id subdomainName name status tier urlId trialExpiresAt users(organizationAdmin: true) { id name email atsAdmin subscriptionAdmin } } } } }"
53
+ }
54
+ result=@api_bss.create('graphql',request)[:data]['data'][object].first
55
+ result.delete('instances')
56
+ return {:type=>:single_object, :data=>result}
57
+ when :instances
58
+ id = self.options.get_option(:id,:mandatory)
59
+ request={
60
+ 'variables'=>{'id'=>id},
61
+ 'query'=>"query($id: ID!) {#{object}(id: $id) { aocOrganization { id subdomainName name status tier urlId trialExpiresAt } } } }"
62
+ }
63
+ result=@api_bss.create('graphql',request)[:data]['data'][object].first
64
+ return {:type=>:object_list, :data=>result['instances']}
65
+ end
66
+ end
67
+ end
68
+ end # Bss
69
+ end # Plugins
70
+ end # Cli
71
+ end # Aspera
@@ -0,0 +1,806 @@
1
+ require 'aspera/cli/basic_auth_plugin'
2
+ require 'aspera/cli/extended_value'
3
+ require 'aspera/fasp/installation'
4
+ require 'aspera/api_detector'
5
+ require 'aspera/open_application'
6
+ require 'aspera/on_cloud'
7
+ require 'aspera/proxy_auto_config'
8
+ require 'aspera/uri_reader'
9
+ require 'xmlsimple'
10
+ require 'base64'
11
+ require 'net/smtp'
12
+ require 'open3'
13
+
14
+ module Aspera
15
+ module Cli
16
+ module Plugins
17
+ # manage the CLI config file
18
+ class Config < Plugin
19
+ # folder in $HOME for application files (config, cache)
20
+ ASPERA_HOME_FOLDER_NAME='.aspera'
21
+ # default config file
22
+ DEFAULT_CONFIG_FILENAME = 'config.yaml'
23
+ # reserved preset names
24
+ CONF_PRESET_CONFIG='config'
25
+ CONF_PRESET_VERSION='version'
26
+ CONF_PRESET_DEFAULT='default'
27
+ CONF_PLUGIN_SYM = :config # Plugins::Config.name.split('::').last.downcase.to_sym
28
+ CONF_GLOBAL_SYM = :config
29
+ # old tool name
30
+ PROGRAM_NAME_V1 = 'aslmcli'
31
+ PROGRAM_NAME_V2 = 'mlia'
32
+ # default redirect for AoC web auth
33
+ DEFAULT_REDIRECT='http://localhost:12345'
34
+ # folder containing custom plugins in `main_folder`
35
+ ASPERA_PLUGINS_FOLDERNAME='plugins'
36
+ # folder containing plugins in the gem's main folder
37
+ GEM_PLUGINS_FOLDER='aspera/cli/plugins'
38
+ RUBY_FILE_EXT='.rb'
39
+ AOC_COMMAND_V1='files'
40
+ AOC_COMMAND_V2='aspera'
41
+ AOC_COMMAND_V3='oncloud'
42
+ CONNECT_WEB_URL = 'https://d3gcli72yxqn2z.cloudfront.net/connect'
43
+ CONNECT_VERSIONS = 'connectversions.js'
44
+ DEMO='demo'
45
+ def option_preset; nil; end
46
+
47
+ def option_preset=(value)
48
+ self.options.add_option_preset(preset_by_name(value))
49
+ end
50
+
51
+ private_constant :ASPERA_HOME_FOLDER_NAME,:DEFAULT_CONFIG_FILENAME,:CONF_PRESET_CONFIG,:CONF_PRESET_VERSION,:CONF_PRESET_DEFAULT,:PROGRAM_NAME_V1,:PROGRAM_NAME_V2,:DEFAULT_REDIRECT,:ASPERA_PLUGINS_FOLDERNAME,:GEM_PLUGINS_FOLDER,:RUBY_FILE_EXT,:AOC_COMMAND_V1,:AOC_COMMAND_V2,:AOC_COMMAND_V3,:DEMO
52
+ attr_accessor :option_ak_secret,:option_secrets
53
+
54
+ def initialize(env,tool_name,help_url,version)
55
+ super(env)
56
+ @option_ak_secret=nil
57
+ @option_secrets={}
58
+ @plugins={}
59
+ @plugin_lookup_folders=[]
60
+ @use_plugin_defaults=true
61
+ @config_presets=nil
62
+ @program_version=version
63
+ @tool_name=tool_name
64
+ @help_url=help_url
65
+ @main_folder=File.join(Dir.home,ASPERA_HOME_FOLDER_NAME,tool_name)
66
+ @conf_file_default=File.join(@main_folder,DEFAULT_CONFIG_FILENAME)
67
+ @option_config_file=@conf_file_default
68
+ @connect_versions=nil
69
+ # set folder where generated FASP files are
70
+ Fasp::Installation.instance.config_folder=@main_folder
71
+ add_plugin_lookup_folder(File.join(@main_folder,ASPERA_PLUGINS_FOLDERNAME))
72
+ add_plugin_lookup_folder(File.join(Main.gem_root,GEM_PLUGINS_FOLDER))
73
+ # do file parameter first
74
+ self.options.set_obj_attr(:config_file,self,:option_config_file)
75
+ self.options.add_opt_simple(:config_file,"read parameters from file in YAML format, current=#{@option_config_file}")
76
+ self.options.parse_options!
77
+ # read correct file
78
+ read_config_file
79
+ # add preset handler (needed for smtp)
80
+ ExtendedValue.instance.set_handler(EXTV_PRESET,:reader,lambda{|v|preset_by_name(v)})
81
+ ExtendedValue.instance.set_handler(EXTV_INCLUDE_PRESETS,:decoder,lambda{|v|expanded_with_preset_includes(v)})
82
+ self.options.set_obj_attr(:override,self,:option_override,:no)
83
+ self.options.set_obj_attr(:ascp_path,self,:option_ascp_path)
84
+ self.options.set_obj_attr(:use_product,self,:option_use_product)
85
+ self.options.set_obj_attr(:preset,self,:option_preset)
86
+ self.options.set_obj_attr(:secret,self,:option_ak_secret)
87
+ self.options.set_obj_attr(:secrets,self,:option_secrets)
88
+ self.options.add_opt_boolean(:override,"override existing value")
89
+ self.options.add_opt_switch(:no_default,"-N","do not load default configuration for plugin") { @use_plugin_defaults=false }
90
+ self.options.add_opt_boolean(:use_generic_client,'wizard: AoC: use global or org specific jwt client id')
91
+ self.options.add_opt_simple(:pkeypath,"path to private key for JWT (wizard)")
92
+ self.options.add_opt_simple(:ascp_path,"path to ascp")
93
+ self.options.add_opt_simple(:use_product,"use ascp from specified product")
94
+ self.options.add_opt_simple(:smtp,"smtp configuration (extended value: hash)")
95
+ self.options.add_opt_simple(:fpac,"proxy auto configuration URL")
96
+ self.options.add_opt_simple(:preset,"-PVALUE","load the named option preset from current config file")
97
+ self.options.add_opt_simple(:default,"set as default configuration for specified plugin")
98
+ self.options.add_opt_simple(:secret,"access key secret for node")
99
+ self.options.add_opt_simple(:secrets,"access key secret for node")
100
+ self.options.add_opt_boolean(:test_mode,"skip user validation in wizard mode")
101
+ self.options.set_option(:use_generic_client,true)
102
+ self.options.set_option(:test_mode,false)
103
+ self.options.parse_options!
104
+ raise CliBadArgument,"secrets shall be Hash" unless @option_secrets.is_a?(Hash)
105
+ end
106
+
107
+ def get_secret(id=nil,mandatory=true)
108
+ secret=self.options.get_option(:secret,:optional) || @option_secrets[id]
109
+ raise "please provide secret for #{id}" if secret.nil? and mandatory
110
+ return secret
111
+ end
112
+
113
+ def get_secrets
114
+ return @option_secrets
115
+ end
116
+
117
+ # retrieve structure from cloud (CDN) with all versions available
118
+ def connect_versions
119
+ if @connect_versions.nil?
120
+ api_connect_cdn=Rest.new({:base_url=>CONNECT_WEB_URL})
121
+ javascript=api_connect_cdn.call({:operation=>'GET',:subpath=>CONNECT_VERSIONS})
122
+ # get result on one line
123
+ connect_versions_javascript=javascript[:http].body.gsub(/\r?\n\s*/,'')
124
+ Log.log.debug("javascript=[\n#{connect_versions_javascript}\n]")
125
+ # get javascript object only
126
+ found=connect_versions_javascript.match(/^.*? = (.*);/)
127
+ raise CliError,'Problen when getting connect versions from internet' if found.nil?
128
+ alldata=JSON.parse(found[1])
129
+ @connect_versions=alldata['entries']
130
+ end
131
+ return @connect_versions
132
+ end
133
+
134
+ # loads default parameters of plugin if no -P parameter
135
+ # and if there is a section defined for the plugin in the "default" section
136
+ # try to find: conffile[conffile["default"][plugin_str]]
137
+ # @param plugin_name_sym : symbol for plugin name
138
+ def add_plugin_default_preset(plugin_name_sym)
139
+ default_config_name=get_plugin_default_config_name(plugin_name_sym)
140
+ Log.log.debug("add_plugin_default_preset:#{plugin_name_sym}:#{default_config_name}")
141
+ self.options.add_option_preset(preset_by_name(default_config_name),:unshift) unless default_config_name.nil?
142
+ return nil
143
+ end
144
+ private
145
+
146
+ def generate_new_key(private_key_path)
147
+ require 'openssl'
148
+ priv_key = OpenSSL::PKey::RSA.new(4096)
149
+ File.write(private_key_path,priv_key.to_s)
150
+ File.write(private_key_path+".pub",priv_key.public_key.to_s)
151
+ nil
152
+ end
153
+
154
+ def self.flatten_all_config(t)
155
+ r=[]
156
+ t.each do |k,v|
157
+ v.each do |kk,vv|
158
+ r.push({"config"=>k,"parameter"=>kk,"value"=>vv})
159
+ end
160
+ end
161
+ return r
162
+ end
163
+
164
+ # set parameter and value in global config
165
+ # creates one if none already created
166
+ # @return preset that contains global default
167
+ def set_global_default(key,value)
168
+ global_default_preset_name=get_plugin_default_config_name(CONF_GLOBAL_SYM)
169
+ if global_default_preset_name.nil?
170
+ global_default_preset_name='global_common_defaults'
171
+ @config_presets[global_default_preset_name]={}
172
+ end
173
+ @config_presets[global_default_preset_name][key.to_s]=value
174
+ return global_default_preset_name
175
+ end
176
+
177
+ public
178
+
179
+ # $HOME/.aspera/`program_name`
180
+ attr_reader :main_folder
181
+ attr_reader :gem_url
182
+ attr_reader :plugins
183
+ attr_accessor :option_override
184
+ attr_accessor :option_config_file
185
+
186
+ EXTV_INCLUDE_PRESETS='incps'
187
+ EXTV_PRESET='preset'
188
+
189
+ # @return the hash from name (also expands possible includes)
190
+ def preset_by_name(config_name, include_path=[])
191
+ raise CliError,"no such config preset: #{config_name}" unless @config_presets.has_key?(config_name)
192
+ raise CliError,"loop in include" if include_path.include?(config_name)
193
+ return expanded_with_preset_includes(@config_presets[config_name],include_path.clone.push(config_name))
194
+ end
195
+
196
+ # @param hash_val
197
+ def expanded_with_preset_includes(hash_val, include_path=[])
198
+ raise CliError,"#{EXTV_INCLUDE_PRESETS} requires a Hash" unless hash_val.is_a?(Hash)
199
+ if hash_val.has_key?(EXTV_INCLUDE_PRESETS)
200
+ memory=hash_val.clone
201
+ includes=memory[EXTV_INCLUDE_PRESETS]
202
+ memory.delete(EXTV_INCLUDE_PRESETS)
203
+ hash_val={}
204
+ raise "#{EXTV_INCLUDE_PRESETS} must be an Array" unless includes.is_a?(Array)
205
+ raise "#{EXTV_INCLUDE_PRESETS} must contain names" unless includes.map{|i|i.class}.uniq.eql?([String])
206
+ includes.each do |preset_name|
207
+ hash_val.merge!(preset_by_name(preset_name,include_path))
208
+ end
209
+ hash_val.merge!(memory)
210
+ end
211
+ return hash_val
212
+ end
213
+
214
+ def option_ascp_path=(new_value)
215
+ Fasp::Installation.instance.ascp_path=new_value
216
+ end
217
+
218
+ def option_ascp_path
219
+ Fasp::Installation.instance.ascp_path
220
+ end
221
+
222
+ def option_use_product=(value)
223
+ Fasp::Installation.instance.use_ascp_from_product(value)
224
+ end
225
+
226
+ def option_use_product
227
+ "write-only value"
228
+ end
229
+
230
+ def convert_preset_path(old_name,new_name,files_to_copy)
231
+ old_subpath=File.join('',ASPERA_HOME_FOLDER_NAME,old_name,'')
232
+ new_subpath=File.join('',ASPERA_HOME_FOLDER_NAME,new_name,'')
233
+ # convert possible keys located in config folder
234
+ @config_presets.values.select{|p|p.is_a?(Hash)}.each do |preset|
235
+ preset.values.select{|v|v.is_a?(String) and v.include?(old_subpath)}.each do |value|
236
+ old_val=value.clone
237
+ included_path=File.expand_path(old_val.gsub(/^@file:/,''))
238
+ files_to_copy.push(included_path) unless files_to_copy.include?(included_path) or !File.exist?(included_path)
239
+ value.gsub!(old_subpath,new_subpath)
240
+ Log.log.warn("Converted config value: #{old_val} -> #{value}")
241
+ end
242
+ end
243
+ end
244
+
245
+ def convert_preset_plugin_name(old_name,new_name)
246
+ if @config_presets[CONF_PRESET_DEFAULT].is_a?(Hash) and @config_presets[CONF_PRESET_DEFAULT].has_key?(old_name)
247
+ @config_presets[CONF_PRESET_DEFAULT][new_name]=@config_presets[CONF_PRESET_DEFAULT][old_name]
248
+ @config_presets[CONF_PRESET_DEFAULT].delete(old_name)
249
+ Log.log.warn("Converted plugin default: #{old_name} -> #{new_name}")
250
+ end
251
+ end
252
+
253
+ # read config file and validate format
254
+ # tries to convert from older version if possible and required
255
+ def read_config_file
256
+ begin
257
+ Log.log.debug("config file is: #{@option_config_file}".red)
258
+ conf_file_v1=File.join(Dir.home,ASPERA_HOME_FOLDER_NAME,PROGRAM_NAME_V1,DEFAULT_CONFIG_FILENAME)
259
+ conf_file_v2=File.join(Dir.home,ASPERA_HOME_FOLDER_NAME,PROGRAM_NAME_V2,DEFAULT_CONFIG_FILENAME)
260
+ # files search for configuration, by default the one given by user
261
+ search_files=[@option_config_file]
262
+ # if default file, then also look for older versions
263
+ search_files.push(conf_file_v2,conf_file_v1) if @option_config_file.eql?(@conf_file_default)
264
+ # find first existing file (or nil)
265
+ conf_file_to_load=search_files.select{|f| File.exist?(f)}.first
266
+ # require save if old version of file
267
+ save_required=!@option_config_file.eql?(conf_file_to_load)
268
+ # if no file found, create default config
269
+ if conf_file_to_load.nil?
270
+ Log.log.warn("No config file found. Creating empty configuration file: #{@option_config_file}")
271
+ @config_presets={CONF_PRESET_CONFIG=>{CONF_PRESET_VERSION=>@program_version},CONF_PRESET_DEFAULT=>{'server'=>'demoserver'},
272
+ 'demoserver'=>{'url'=>'ssh://'+DEMO+'.asperasoft.com:33001','username'=>AOC_COMMAND_V2,'ssAP'.downcase.reverse+'drow'.reverse=>DEMO+AOC_COMMAND_V2}}
273
+ else
274
+ Log.log.debug "loading #{@option_config_file}"
275
+ @config_presets=YAML.load_file(conf_file_to_load)
276
+ end
277
+ files_to_copy=[]
278
+ Log.log.debug "Available_presets: #{@config_presets}"
279
+ raise "Expecting YAML Hash" unless @config_presets.is_a?(Hash)
280
+ # check there is at least the config section
281
+ if !@config_presets.has_key?(CONF_PRESET_CONFIG)
282
+ raise "Cannot find key: #{CONF_PRESET_CONFIG}"
283
+ end
284
+ version=@config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]
285
+ if version.nil?
286
+ raise "No version found in config section."
287
+ end
288
+ # oldest compatible conf file format, update to latest version when an incompatible change is made
289
+ # check compatibility of version of conf file
290
+ config_tested_version='0.4.5'
291
+ if Gem::Version.new(version) < Gem::Version.new(config_tested_version)
292
+ raise "Unsupported config file version #{version}. Expecting min version #{config_tested_version}"
293
+ end
294
+ config_tested_version='0.6.15'
295
+ if Gem::Version.new(version) < Gem::Version.new(config_tested_version)
296
+ convert_preset_plugin_name(AOC_COMMAND_V1,AOC_COMMAND_V2)
297
+ version=@config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]=config_tested_version
298
+ save_required=true
299
+ end
300
+ config_tested_version='0.8.10'
301
+ if Gem::Version.new(version) <= Gem::Version.new(config_tested_version)
302
+ convert_preset_path(PROGRAM_NAME_V1,PROGRAM_NAME_V2,files_to_copy)
303
+ version=@config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]=config_tested_version
304
+ save_required=true
305
+ end
306
+ config_tested_version='1.0'
307
+ if Gem::Version.new(version) <= Gem::Version.new(config_tested_version)
308
+ convert_preset_plugin_name(AOC_COMMAND_V2,AOC_COMMAND_V3)
309
+ convert_preset_path(PROGRAM_NAME_V2,@tool_name,files_to_copy)
310
+ version=@config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]=config_tested_version
311
+ save_required=true
312
+ end
313
+ # Place new compatibility code here
314
+ if save_required
315
+ Log.log.warn("Saving automatic conversion.")
316
+ @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]=@program_version
317
+ save_presets_to_config_file
318
+ Log.log.warn("Copying referenced files")
319
+ files_to_copy.each do |file|
320
+ FileUtils.cp(file,@main_folder)
321
+ Log.log.warn("..#{file} -> #{@main_folder}")
322
+ end
323
+ end
324
+ rescue Psych::SyntaxError => e
325
+ Log.log.error("YAML error in config file")
326
+ raise e
327
+ rescue => e
328
+ Log.log.debug("-> #{e}")
329
+ new_name="#{@option_config_file}.pre#{@program_version}.manual_conversion_needed"
330
+ File.rename(@option_config_file,new_name)
331
+ Log.log.warn("Renamed config file to #{new_name}.")
332
+ Log.log.warn("Manual Conversion is required. Next time, a new empty file will be created.")
333
+ raise CliError,e.to_s
334
+ end
335
+ end
336
+
337
+ # find plugins in defined paths
338
+ def add_plugins_from_lookup_folders
339
+ @plugin_lookup_folders.each do |folder|
340
+ if File.directory?(folder)
341
+ #TODO: add gem root to load path ? and require short folder ?
342
+ #$LOAD_PATH.push(folder) if i[:add_path]
343
+ Dir.entries(folder).select{|file|file.end_with?(RUBY_FILE_EXT)}.each do |source|
344
+ add_plugin_info(File.join(folder,source))
345
+ end
346
+ end
347
+ end
348
+ end
349
+
350
+ def add_plugin_lookup_folder(folder)
351
+ @plugin_lookup_folders.push(folder)
352
+ end
353
+
354
+ def add_plugin_info(path)
355
+ raise "ERROR: plugin path must end with #{RUBY_FILE_EXT}" if !path.end_with?(RUBY_FILE_EXT)
356
+ plugin_symbol=File.basename(path,RUBY_FILE_EXT).to_sym
357
+ req=path.gsub(/#{RUBY_FILE_EXT}$/,'')
358
+ if @plugins.has_key?(plugin_symbol)
359
+ Log.log.warn("skipping plugin already registered: #{plugin_symbol}")
360
+ return
361
+ end
362
+ @plugins[plugin_symbol]={:source=>path,:require_stanza=>req}
363
+ end
364
+
365
+ def execute_connect_action
366
+ command=self.options.get_next_command([:list,:id])
367
+ case command
368
+ when :list
369
+ return {:type=>:object_list, :data=>connect_versions, :fields => ['id','title','version']}
370
+ when :id
371
+ connect_id=self.options.get_next_argument('id or title')
372
+ one_res=connect_versions.select{|i|i['id'].eql?(connect_id) || i['title'].eql?(connect_id)}.first
373
+ raise CliNoSuchId.new(:connect,connect_id) if one_res.nil?
374
+ command=self.options.get_next_command([:info,:links])
375
+ case command
376
+ when :info # shows files used
377
+ one_res.delete('links')
378
+ return {:type=>:single_object, :data=>one_res}
379
+ when :links # shows files used
380
+ command=self.options.get_next_command([:list,:id])
381
+ all_links=one_res['links']
382
+ case command
383
+ when :list # shows files used
384
+ return {:type=>:object_list, :data=>all_links}
385
+ when :id
386
+ link_title=self.options.get_next_argument('title')
387
+ one_link=all_links.select {|i| i['title'].eql?(link_title)}.first
388
+ command=self.options.get_next_command([:download,:open])
389
+ case command
390
+ when :download #
391
+ folder_dest=self.transfer.destination_folder('receive')
392
+ #folder_dest=self.options.get_next_argument('destination folder')
393
+ api_connect_cdn=Rest.new({:base_url=>CONNECT_WEB_URL})
394
+ fileurl = one_link['href']
395
+ filename=fileurl.gsub(%r{.*/},'')
396
+ api_connect_cdn.call({:operation=>'GET',:subpath=>fileurl,:save_to_file=>File.join(folder_dest,filename)})
397
+ return Main.result_status("downloaded: #{filename}")
398
+ when :open #
399
+ OpenApplication.instance.uri(one_link['href'])
400
+ return Main.result_status("opened: #{one_link['href']}")
401
+ end
402
+ end
403
+ end
404
+ end
405
+ end
406
+
407
+ def execute_action_ascp
408
+ command=self.options.get_next_command([:connect,:use,:show,:products,:info])
409
+ case command
410
+ when :connect
411
+ return execute_connect_action
412
+ when :use
413
+ default_ascp=self.options.get_next_argument('path to ascp')
414
+ raise "file name must be ascp" unless File.basename(default_ascp).eql?('ascp')
415
+ raise "no such file: #{default_ascp}" unless File.exist?(default_ascp)
416
+ raise "not executable: #{default_ascp}" unless File.executable?(default_ascp)
417
+ preset_name=set_global_default(:ascp_path,default_ascp)
418
+ save_presets_to_config_file
419
+ return {:type=>:status, :data=>"saved to default global preset #{preset_name}"}
420
+ when :show # shows files used
421
+ return {:type=>:status, :data=>Fasp::Installation.instance.path(:ascp)}
422
+ when :info # shows files used
423
+ data=Fasp::Installation::FILES.inject({}) do |m,v|
424
+ m[v.to_s]=Fasp::Installation.instance.path(v) rescue "Not Found"
425
+ m
426
+ end
427
+ # read PATHs from ascp directly, and pvcl modules as well
428
+ Open3.popen3(Fasp::Installation.instance.path(:ascp),'-DDL-') do |stdin, stdout, stderr, thread|
429
+ while line=stderr.gets do
430
+ line.chomp!
431
+ case line
432
+ when /^DBG Path ([^ ]+) (dir|file) +: (.*)$/;data[$1]=$3
433
+ when /^DBG Added module group:"([^"]+)" name:"([^"]+)", version:"([^"]+)" interface:"([^"]+)"$/;data[$2]=$4
434
+ end
435
+ end
436
+ end
437
+ data['keypass']=Fasp::Installation.instance.bypass_keys.shift
438
+ return {:type=>:single_object, :data=>data}
439
+ when :products
440
+ command=self.options.get_next_command([:list,:use])
441
+ case command
442
+ when :list
443
+ return {:type=>:object_list, :data=>Fasp::Installation.instance.installed_products, :fields=>['name','app_root']}
444
+ when :use
445
+ default_product=self.options.get_next_argument('product name')
446
+ Fasp::Installation.instance.use_ascp_from_product(default_product)
447
+ preset_name=set_global_default(:ascp_path,Fasp::Installation.instance.ascp_path)
448
+ save_presets_to_config_file
449
+ return {:type=>:status, :data=>"saved to default global preset #{preset_name}"}
450
+ end
451
+ end
452
+ end
453
+
454
+ 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]
455
+
456
+ # "config" plugin
457
+ def execute_action
458
+ action=self.options.get_next_command(ACTIONS)
459
+ case action
460
+ when :id
461
+ config_name=self.options.get_next_argument('config name')
462
+ action=self.options.get_next_command([:show,:delete,:set,:get,:unset,:initialize,:update,:ask])
463
+ # those operations require existing option
464
+ raise "no such preset: #{config_name}" if [:show,:delete,:get,:unset].include?(action) and !@config_presets.has_key?(config_name)
465
+ selected_preset=@config_presets[config_name]
466
+ case action
467
+ when :show
468
+ raise "no such config: #{config_name}" if selected_preset.nil?
469
+ return {:type=>:single_object,:data=>selected_preset}
470
+ when :delete
471
+ @config_presets.delete(config_name)
472
+ save_presets_to_config_file
473
+ return Main.result_status("deleted: #{config_name}")
474
+ when :get
475
+ param_name=self.options.get_next_argument('parameter name')
476
+ value=selected_preset[param_name]
477
+ raise "no such option in preset #{config_name} : #{param_name}" if value.nil?
478
+ case value
479
+ when Numeric,String; return {:type=>:text,:data=>ExtendedValue.instance.evaluate(value.to_s)}
480
+ end
481
+ return {:type=>:single_object,:data=>value}
482
+ when :unset
483
+ param_name=self.options.get_next_argument('parameter name')
484
+ selected_preset.delete(param_name)
485
+ save_presets_to_config_file
486
+ return Main.result_status("removed: #{config_name}: #{param_name}")
487
+ when :set
488
+ param_name=self.options.get_next_argument('parameter name')
489
+ param_value=self.options.get_next_argument('parameter value')
490
+ if !@config_presets.has_key?(config_name)
491
+ Log.log.debug("no such config name: #{config_name}, initializing")
492
+ selected_preset=@config_presets[config_name]={}
493
+ end
494
+ if selected_preset.has_key?(param_name)
495
+ Log.log.warn("overwriting value: #{selected_preset[param_name]}")
496
+ end
497
+ selected_preset[param_name]=param_value
498
+ save_presets_to_config_file
499
+ return Main.result_status("updated: #{config_name}: #{param_name} <- #{param_value}")
500
+ when :initialize
501
+ config_value=self.options.get_next_argument('extended value (Hash)')
502
+ if @config_presets.has_key?(config_name)
503
+ Log.log.warn("configuration already exists: #{config_name}, overwriting")
504
+ end
505
+ @config_presets[config_name]=config_value
506
+ save_presets_to_config_file
507
+ return Main.result_status("modified: #{@option_config_file}")
508
+ when :update
509
+ default_for_plugin=self.options.get_option(:default,:optional)
510
+ # get unprocessed options
511
+ theopts=self.options.get_options_table
512
+ Log.log.debug("opts=#{theopts}")
513
+ @config_presets[config_name]||={}
514
+ @config_presets[config_name].merge!(theopts)
515
+ if ! default_for_plugin.nil?
516
+ @config_presets[CONF_PRESET_DEFAULT]||=Hash.new
517
+ @config_presets[CONF_PRESET_DEFAULT][default_for_plugin]=config_name
518
+ end
519
+ save_presets_to_config_file
520
+ return Main.result_status("updated: #{config_name}")
521
+ when :ask
522
+ self.options.ask_missing_mandatory=:yes
523
+ @config_presets[config_name]||={}
524
+ self.options.get_next_argument('option names',:multiple).each do |optionname|
525
+ option_value=self.options.get_interactive(:option,optionname)
526
+ @config_presets[config_name][optionname]=option_value
527
+ end
528
+ save_presets_to_config_file
529
+ return Main.result_status("updated: #{config_name}")
530
+ end
531
+ when :documentation
532
+ OpenApplication.instance.uri(@help_url)
533
+ return Main.result_nothing
534
+ when :open
535
+ OpenApplication.instance.uri("#{@option_config_file}") #file://
536
+ return Main.result_nothing
537
+ when :genkey # generate new rsa key
538
+ private_key_path=self.options.get_next_argument('private key file path')
539
+ generate_new_key(private_key_path)
540
+ return Main.result_status('generated key: '+private_key_path)
541
+ when :echo # display the content of a value given on command line
542
+ result={:type=>:other_struct, :data=>self.options.get_next_argument("value")}
543
+ # special for csv
544
+ result[:type]=:object_list if result[:data].is_a?(Array) and result[:data].first.is_a?(Hash)
545
+ return result
546
+ when :flush_tokens
547
+ deleted_files=Oauth.flush_tokens
548
+ return {:type=>:value_list, :name=>'file',:data=>deleted_files}
549
+ when :plugins
550
+ return {:data => @plugins.keys.map { |i| { 'plugin' => i.to_s, 'path' => @plugins[i][:source] } } , :fields => ['plugin','path'], :type => :object_list }
551
+ when :list
552
+ return {:data => @config_presets.keys, :type => :value_list, :name => 'name'}
553
+ when :overview
554
+ return {:type=>:object_list,:data=>self.class.flatten_all_config(@config_presets)}
555
+ when :wizard
556
+ self.options.ask_missing_mandatory=true
557
+ #self.options.set_option(:interactive,:yes)
558
+ # register url option
559
+ BasicAuthPlugin.new(@agents.merge(skip_option_header: true))
560
+ instance_url=self.options.get_option(:url,:mandatory)
561
+ appli=ApiDetector.discover_product(instance_url)
562
+ case appli[:product]
563
+ when :aoc
564
+ self.format.display_status("Detected: Aspera on Cloud".bold)
565
+ organization,instance_domain=OnCloud.parse_url(instance_url)
566
+ aspera_preset_name='aoc_'+organization
567
+ self.format.display_status("Preparing preset: #{aspera_preset_name}")
568
+ # init defaults if necessary
569
+ @config_presets[CONF_PRESET_DEFAULT]||=Hash.new
570
+ if !option_override
571
+ raise CliError,"a default configuration already exists for plugin '#{AOC_COMMAND_V2}' (use --override=yes)" if @config_presets[CONF_PRESET_DEFAULT].has_key?(AOC_COMMAND_V2)
572
+ raise CliError,"preset already exists: #{aspera_preset_name} (use --override=yes)" if @config_presets.has_key?(aspera_preset_name)
573
+ end
574
+ # lets see if path to priv key is provided
575
+ private_key_path=self.options.get_option(:pkeypath,:optional)
576
+ # give a chance to provide
577
+ if private_key_path.nil?
578
+ self.format.display_status("Please provide path to your private RSA key, or empty to generate one:")
579
+ private_key_path=self.options.get_option(:pkeypath,:mandatory).to_s
580
+ end
581
+ # else generate path
582
+ if private_key_path.empty?
583
+ private_key_path=File.join(@main_folder,'aspera_on_cloud_key')
584
+ end
585
+ if File.exist?(private_key_path)
586
+ self.format.display_status("Using existing key:")
587
+ else
588
+ self.format.display_status("Generating key...")
589
+ generate_new_key(private_key_path)
590
+ self.format.display_status("Created:")
591
+ end
592
+ self.format.display_status("#{private_key_path}")
593
+ pub_key_pem=OpenSSL::PKey::RSA.new(File.read(private_key_path)).public_key.to_s
594
+ # define options
595
+ require 'aspera/cli/plugins/oncloud'
596
+ # make username mandatory for jwt, this triggers interactive input
597
+ self.options.get_option(:username,:mandatory)
598
+ files_plugin=Plugins::Oncloud.new(@agents.merge({skip_basic_auth_options: true, private_key_path: private_key_path}))
599
+ auto_set_pub_key=false
600
+ auto_set_jwt=false
601
+ use_browser_authentication=false
602
+
603
+ if self.options.get_option(:use_generic_client)
604
+ self.format.display_status("Using global client_id.")
605
+ self.format.display_status("Please Login to your Aspera on Cloud instance.".red)
606
+ self.format.display_status("Navigate to your \"Account Settings\"".red)
607
+ self.format.display_status("Check or update the value of \"Public Key\" to be:".red.blink)
608
+ self.format.display_status("#{pub_key_pem}")
609
+ if ! self.options.get_option(:test_mode)
610
+ self.format.display_status("Once updated or validated, press enter.")
611
+ OpenApplication.instance.uri(instance_url)
612
+ STDIN.gets
613
+ end
614
+ else
615
+ self.format.display_status("Using organization specific client_id.")
616
+ if self.options.get_option(:client_id,:optional).nil? or self.options.get_option(:client_secret,:optional).nil?
617
+ self.format.display_status("Please login to your Aspera on Cloud instance.".red)
618
+ self.format.display_status("Go to: Apps->Admin->Organization->Integrations")
619
+ self.format.display_status("Create or check if there is an existing integration named:")
620
+ self.format.display_status("- name: #{@tool_name}")
621
+ self.format.display_status("- redirect uri: #{DEFAULT_REDIRECT}")
622
+ self.format.display_status("- origin: localhost")
623
+ self.format.display_status("Once created or identified,")
624
+ self.format.display_status("Please enter:".red)
625
+ end
626
+ OpenApplication.instance.uri(instance_url+"/admin/org/integrations")
627
+ self.options.get_option(:client_id,:mandatory)
628
+ self.options.get_option(:client_secret,:mandatory)
629
+ use_browser_authentication=true
630
+ end
631
+ if use_browser_authentication
632
+ self.format.display_status("We will use web authentication to bootstrap.")
633
+ self.options.set_option(:auth,:web)
634
+ self.options.set_option(:redirect_uri,DEFAULT_REDIRECT)
635
+ auto_set_pub_key=true
636
+ auto_set_jwt=true
637
+ self.options.set_option(:scope,OnCloud::SCOPE_FILES_ADMIN)
638
+ end
639
+ files_plugin.update_aoc_api
640
+ myself=files_plugin.api_aoc.read('self')[:data]
641
+ if auto_set_pub_key
642
+ raise CliError,"public key is already set in profile (use --override=yes)" unless myself['public_key'].empty? or option_override
643
+ self.format.display_status("Updating profile with new key")
644
+ files_plugin.api_aoc.update("users/#{myself['id']}",{'public_key'=>pub_key_pem})
645
+ end
646
+ if auto_set_jwt
647
+ self.format.display_status("Enabling JWT for client")
648
+ files_plugin.api_aoc.update("clients/#{self.options.get_option(:client_id)}",{'jwt_grant_enabled'=>true,'explicit_authorization_required'=>false})
649
+ end
650
+ self.format.display_status("creating new config preset: #{aspera_preset_name}")
651
+ @config_presets[aspera_preset_name]={
652
+ :url.to_s =>self.options.get_option(:url),
653
+ :username.to_s =>myself['email'],
654
+ :auth.to_s =>:jwt.to_s,
655
+ :private_key.to_s =>'@file:'+private_key_path,
656
+ }
657
+ # set only if non nil
658
+ [:client_id,:client_secret].each do |s|
659
+ o=self.options.get_option(s)
660
+ @config_presets[s.to_s] = o unless o.nil?
661
+ end
662
+ self.format.display_status("Setting config preset as default for #{AOC_COMMAND_V2}")
663
+ @config_presets[CONF_PRESET_DEFAULT][AOC_COMMAND_V2]=aspera_preset_name
664
+ self.format.display_status("saving config file")
665
+ save_presets_to_config_file
666
+ return Main.result_status("Done.\nYou can test with:\n#{@tool_name} aspera user info show")
667
+ else
668
+ raise CliBadArgument,"Supports only: aoc. Detected: #{appli}"
669
+ end
670
+ when :export_to_cli
671
+ self.format.display_status("Exporting: Aspera on Cloud")
672
+ require 'aspera/cli/plugins/oncloud'
673
+ # need url / username
674
+ add_plugin_default_preset(AOC_COMMAND_V3.to_sym)
675
+ files_plugin=Plugins::Oncloud.new(@agents) # TODO: is this line needed ?
676
+ url=self.options.get_option(:url,:mandatory)
677
+ cli_conf_file=Fasp::Installation.instance.cli_conf_file
678
+ data=JSON.parse(File.read(cli_conf_file))
679
+ organization,instance_domain=OnCloud.parse_url(url)
680
+ key_basename='org_'+organization+'.pem'
681
+ key_file=File.join(File.dirname(File.dirname(cli_conf_file)),'etc',key_basename)
682
+ File.write(key_file,self.options.get_option(:private_key,:mandatory))
683
+ new_conf={
684
+ 'organization' => organization,
685
+ 'hostname' => [organization,instance_domain].join('.'),
686
+ 'privateKeyFilename' => key_basename,
687
+ 'username' => self.options.get_option(:username,:mandatory)
688
+ }
689
+ new_conf['clientId'],new_conf['clientSecret']=OnCloud.get_client_ids(self.options.get_option(:client_id,:optional),self.options.get_option(:client_secret,:optional))
690
+ entry=data['AoCAccounts'].select{|i|i['organization'].eql?(organization)}.first
691
+ if entry.nil?
692
+ data['AoCAccounts'].push(new_conf)
693
+ self.format.display_status("Creating new aoc entry: #{organization}")
694
+ else
695
+ self.format.display_status("Updating existing aoc entry: #{organization}")
696
+ entry.merge!(new_conf)
697
+ end
698
+ File.write(cli_conf_file,JSON.pretty_generate(data))
699
+ return Main.result_status("updated: #{cli_conf_file}")
700
+ when :detect
701
+ # need url / username
702
+ BasicAuthPlugin.new(@agents)
703
+ return Main.result_status("found: #{ApiDetector.discover_product(self.options.get_option(:url,:mandatory))}")
704
+ when :coffee
705
+ OpenApplication.instance.uri('https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg')
706
+ return Main.result_nothing
707
+ when :ascp
708
+ execute_action_ascp
709
+ when :gem_path
710
+ return Main.result_status(Main.gem_root)
711
+ when :folder
712
+ return Main.result_status(@main_folder)
713
+ when :file
714
+ return Main.result_status(@option_config_file)
715
+ when :email_test
716
+ dest_email=self.options.get_next_argument("destination email")
717
+ send_email({
718
+ to: dest_email,
719
+ subject: 'Amelia email test',
720
+ body: 'It worked !',
721
+ })
722
+ return Main.result_nothing
723
+ when :smtp_settings
724
+ return {:type=>:single_object,:data=>email_settings}
725
+ when :proxy_check
726
+ pac_url=self.options.get_option(:fpac,:mandatory)
727
+ server_url=self.options.get_next_argument("server url")
728
+ return Main.result_status(Aspera::ProxyAutoConfig.new(UriReader.read(pac_url)).get_proxy(server_url))
729
+ else raise "error"
730
+ end
731
+ end
732
+
733
+ def email_settings
734
+ smtp=self.options.get_option(:smtp,:mandatory)
735
+ # change string keys into symbols
736
+ smtp=smtp.keys.inject({}){|m,v|m[v.to_sym]=smtp[v];m}
737
+ # defaults
738
+ smtp[:tls]||=true
739
+ smtp[:port]||=smtp[:tls]?587:25
740
+ smtp[:from_email]||=smtp[:username] if smtp.has_key?(:username)
741
+ smtp[:from_name]||=smtp[:from_email].gsub(/@.*$/,'').gsub(/[^a-zA-Z]/,' ').capitalize if smtp.has_key?(:username)
742
+ smtp[:domain]||=smtp[:from_email].gsub(/^.*@/,'') if smtp.has_key?(:from_email)
743
+ # check minimum required
744
+ [:server,:port,:domain].each do |n|
745
+ raise "missing smtp parameter: #{n}" unless smtp.has_key?(n)
746
+ end
747
+ Log.log.debug("smtp=#{smtp}")
748
+ return smtp
749
+ end
750
+
751
+ def send_email(email={})
752
+ opts=email_settings
753
+ email[:from_name]||=opts[:from_name]
754
+ email[:from_email]||=opts[:from_email]
755
+ # check minimum required
756
+ [:from_name,:from_email,:to,:subject].each do |n|
757
+ raise "missing email parameter: #{n}" unless email.has_key?(n)
758
+ end
759
+ msg = <<END_OF_MESSAGE
760
+ From: #{email[:from_name]} <#{email[:from_email]}>
761
+ To: <#{email[:to]}>
762
+ Subject: #{email[:subject]}
763
+
764
+ #{email[:body]}
765
+ END_OF_MESSAGE
766
+ start_options=[opts[:domain]]
767
+ start_options.push(opts[:username],opts[:password],:login) if opts.has_key?(:username) and opts.has_key?(:password)
768
+
769
+ smtp = Net::SMTP.new(opts[:server], opts[:port])
770
+ smtp.enable_starttls if opts[:tls]
771
+ smtp.start(*start_options) do |smtp|
772
+ smtp.send_message(msg, email[:from_email], email[:to])
773
+ end
774
+ end
775
+
776
+ def save_presets_to_config_file
777
+ raise "no configuration loaded" if @config_presets.nil?
778
+ FileUtils::mkdir_p(@main_folder) unless Dir.exist?(@main_folder)
779
+ Log.log.debug "writing #{@option_config_file}"
780
+ File.write(@option_config_file,@config_presets.to_yaml)
781
+ end
782
+
783
+ # returns [String] name if config_presets has default
784
+ # returns nil if there is no config or bypass default params
785
+ def get_plugin_default_config_name(plugin_sym)
786
+ raise "internal error: config_presets shall be defined" if @config_presets.nil?
787
+ if !@use_plugin_defaults
788
+ Log.log.debug("skip default config")
789
+ return nil
790
+ end
791
+ if @config_presets.has_key?(CONF_PRESET_DEFAULT) and
792
+ @config_presets[CONF_PRESET_DEFAULT].has_key?(plugin_sym.to_s)
793
+ default_config_name=@config_presets[CONF_PRESET_DEFAULT][plugin_sym.to_s]
794
+ if !@config_presets.has_key?(default_config_name)
795
+ 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}).")
796
+ end
797
+ raise CliError,"Config name [#{default_config_name}] must be a hash, check config file." if !@config_presets[default_config_name].is_a?(Hash)
798
+ return default_config_name
799
+ end
800
+ return nil
801
+ end # get_plugin_default_config_name
802
+
803
+ end
804
+ end
805
+ end
806
+ end