aspera-cli 4.0.0.pre1

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 (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