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