aspera-cli 4.0.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +3592 -0
- data/bin/ascli +7 -0
- data/bin/asession +89 -0
- data/docs/Makefile +59 -0
- data/docs/README.erb.md +3012 -0
- data/docs/README.md +13 -0
- data/docs/diagrams.txt +49 -0
- data/docs/secrets.make +38 -0
- data/docs/test_env.conf +117 -0
- data/docs/transfer_spec.html +99 -0
- data/examples/aoc.rb +17 -0
- data/examples/proxy.pac +60 -0
- data/examples/transfer.rb +115 -0
- data/lib/aspera/api_detector.rb +60 -0
- data/lib/aspera/ascmd.rb +151 -0
- data/lib/aspera/ats_api.rb +43 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +38 -0
- data/lib/aspera/cli/extended_value.rb +88 -0
- data/lib/aspera/cli/formater.rb +238 -0
- data/lib/aspera/cli/listener/line_dump.rb +17 -0
- data/lib/aspera/cli/listener/logger.rb +20 -0
- data/lib/aspera/cli/listener/progress.rb +52 -0
- data/lib/aspera/cli/listener/progress_multi.rb +91 -0
- data/lib/aspera/cli/main.rb +304 -0
- data/lib/aspera/cli/manager.rb +440 -0
- data/lib/aspera/cli/plugin.rb +90 -0
- data/lib/aspera/cli/plugins/alee.rb +24 -0
- data/lib/aspera/cli/plugins/ats.rb +231 -0
- data/lib/aspera/cli/plugins/bss.rb +71 -0
- data/lib/aspera/cli/plugins/config.rb +806 -0
- data/lib/aspera/cli/plugins/console.rb +62 -0
- data/lib/aspera/cli/plugins/cos.rb +106 -0
- data/lib/aspera/cli/plugins/faspex.rb +377 -0
- data/lib/aspera/cli/plugins/faspex5.rb +93 -0
- data/lib/aspera/cli/plugins/node.rb +438 -0
- data/lib/aspera/cli/plugins/oncloud.rb +937 -0
- data/lib/aspera/cli/plugins/orchestrator.rb +169 -0
- data/lib/aspera/cli/plugins/preview.rb +464 -0
- data/lib/aspera/cli/plugins/server.rb +216 -0
- data/lib/aspera/cli/plugins/shares.rb +63 -0
- data/lib/aspera/cli/plugins/shares2.rb +114 -0
- data/lib/aspera/cli/plugins/sync.rb +65 -0
- data/lib/aspera/cli/plugins/xnode.rb +115 -0
- data/lib/aspera/cli/transfer_agent.rb +251 -0
- data/lib/aspera/cli/version.rb +5 -0
- data/lib/aspera/colors.rb +39 -0
- data/lib/aspera/command_line_builder.rb +137 -0
- data/lib/aspera/fasp/aoc.rb +24 -0
- data/lib/aspera/fasp/connect.rb +99 -0
- data/lib/aspera/fasp/error.rb +21 -0
- data/lib/aspera/fasp/error_info.rb +60 -0
- data/lib/aspera/fasp/http_gw.rb +81 -0
- data/lib/aspera/fasp/installation.rb +240 -0
- data/lib/aspera/fasp/listener.rb +11 -0
- data/lib/aspera/fasp/local.rb +377 -0
- data/lib/aspera/fasp/manager.rb +69 -0
- data/lib/aspera/fasp/node.rb +88 -0
- data/lib/aspera/fasp/parameters.rb +235 -0
- data/lib/aspera/fasp/resume_policy.rb +76 -0
- data/lib/aspera/fasp/uri.rb +51 -0
- data/lib/aspera/faspex_gw.rb +196 -0
- data/lib/aspera/hash_ext.rb +28 -0
- data/lib/aspera/log.rb +80 -0
- data/lib/aspera/nagios.rb +71 -0
- data/lib/aspera/node.rb +14 -0
- data/lib/aspera/oauth.rb +319 -0
- data/lib/aspera/on_cloud.rb +421 -0
- data/lib/aspera/open_application.rb +72 -0
- data/lib/aspera/persistency_action_once.rb +42 -0
- data/lib/aspera/persistency_folder.rb +91 -0
- data/lib/aspera/preview/file_types.rb +300 -0
- data/lib/aspera/preview/generator.rb +258 -0
- data/lib/aspera/preview/image_error.png +0 -0
- data/lib/aspera/preview/options.rb +35 -0
- data/lib/aspera/preview/utils.rb +131 -0
- data/lib/aspera/preview/video_error.png +0 -0
- data/lib/aspera/proxy_auto_config.erb.js +287 -0
- data/lib/aspera/proxy_auto_config.rb +34 -0
- data/lib/aspera/rest.rb +296 -0
- data/lib/aspera/rest_call_error.rb +13 -0
- data/lib/aspera/rest_error_analyzer.rb +98 -0
- data/lib/aspera/rest_errors_aspera.rb +58 -0
- data/lib/aspera/ssh.rb +53 -0
- data/lib/aspera/sync.rb +82 -0
- data/lib/aspera/temp_file_manager.rb +37 -0
- data/lib/aspera/uri_reader.rb +25 -0
- 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
|