nex_client 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/nex_client/cli.rb +64 -36
- data/lib/nex_client/commands/addons.rb +93 -0
- data/lib/nex_client/commands/apps.rb +36 -2
- data/lib/nex_client/commands/domains.rb +3 -2
- data/lib/nex_client/commands/ssl_certificates.rb +10 -8
- data/lib/nex_client/domain.rb +1 -0
- data/lib/nex_client/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bea397684b919daf609e59cba8da6003c876186
|
4
|
+
data.tar.gz: 4eb230395b5f7a8b0fe41f5f4d9f9f45adc8d8bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16aa93de97a9261ae2fa9e6ba2ebe15fc0f3194c14685258d1d98e3cc1616a665ce80ed31fe7199fc94d01c1a27d15fa68ae523b6229a41c935d2f0b03d87bca
|
7
|
+
data.tar.gz: 2f7cfccadd6e26606426eed05f8c55ee32c3703d6358660b97013044afe7b871cec293fe6ecc5f988ac7b440288b33bbdf7e34f4260158802ec96054315b9688
|
data/lib/nex_client/cli.rb
CHANGED
@@ -37,6 +37,7 @@ module NexClient
|
|
37
37
|
c.example 'create mysql addon for myapp', 'nex-cli addons:create mysql myapp'
|
38
38
|
c.example 'create redis addon for myapp', 'nex-cli addons:create redis myapp'
|
39
39
|
c.option '--size SIZE', Integer, 'specify container size (default: 2, min: 1, max: 20). Container will have N shares of CPU and N*128M of memory.'
|
40
|
+
c.option '--http-log-drain DRAIN_URL', String, 'specify the URL of a remote log drain'
|
40
41
|
c.action do |args, options|
|
41
42
|
NexClient::Commands::Addons.create(args,options)
|
42
43
|
end
|
@@ -64,6 +65,16 @@ module NexClient
|
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
68
|
+
command :'addons:info' do |c|
|
69
|
+
c.syntax = 'nex-cli addons:info ADDON_NAME [options]'
|
70
|
+
c.summary = 'Show information about an addon'
|
71
|
+
c.description = 'Show all details about an addon'
|
72
|
+
c.example 'show details about myaddon', 'nex-cli addons:info myaddon'
|
73
|
+
c.action do |args, options|
|
74
|
+
NexClient::Commands::Addons.info(args,options)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
67
78
|
command :'addons:logs' do |c|
|
68
79
|
c.syntax = 'nex-cli addons:logs ADDON_NAME [options]'
|
69
80
|
c.summary = 'Gather addon logs'
|
@@ -98,6 +109,18 @@ module NexClient
|
|
98
109
|
end
|
99
110
|
end
|
100
111
|
|
112
|
+
command :'addons:update' do |c|
|
113
|
+
c.syntax = 'nex-cli addons:update ADDON_NAME [options]'
|
114
|
+
c.summary = 'Update addon settings'
|
115
|
+
c.description = 'Update addon settings'
|
116
|
+
c.example 'change container size to 4', 'nex-cli addons:update myaddon --size 4'
|
117
|
+
c.option '--size SIZE', Integer, 'change container size (default: 2, min: 1, max: 20). Container will have N shares of CPU and N*128M of memory. [restart required]'
|
118
|
+
c.option '--http-log-drain DRAIN_URL', String, 'specify the URL of a remote log drain. [restart required]'
|
119
|
+
c.action do |args, options|
|
120
|
+
NexClient::Commands::Addons.update(args,options)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
101
124
|
command :apps do |c|
|
102
125
|
c.syntax = 'nex-cli apps [options]'
|
103
126
|
c.summary = 'Manage apps'
|
@@ -130,6 +153,7 @@ module NexClient
|
|
130
153
|
c.option '--no-ssl', 'disable SSL support (enabled by default)'
|
131
154
|
c.option '--size SIZE', Integer, 'specify container size (default: 2, min: 1, max: 20). Container will have N shares of CPU and N*128M of memory.'
|
132
155
|
c.option '--storage', 'enable persistent storage (/!\ only one node allowed)'
|
156
|
+
c.option '--http-log-drain DRAIN_URL', String, 'specify the URL of a remote log drain'
|
133
157
|
c.option '--owner ORGANIZATION_HANDLE', 'specify an organisation as owner (organization admin only)'
|
134
158
|
c.option '--desc DESCRIPTION', String, 'description for this application (140 characters max)'
|
135
159
|
c.option '--tags TAG_LIST', Array, 'comma separated list of tags describing this app'
|
@@ -242,7 +266,8 @@ module NexClient
|
|
242
266
|
c.summary = 'Update apps settings'
|
243
267
|
c.description = 'Update application settings'
|
244
268
|
c.example 'change container size to 4', 'nex-cli apps:update myapp --size 4'
|
245
|
-
c.option '--size SIZE', Integer, 'change container size (default: 2, min: 1, max: 20). Container will have N shares of CPU and N*128M of memory.'
|
269
|
+
c.option '--size SIZE', Integer, 'change container size (default: 2, min: 1, max: 20). Container will have N shares of CPU and N*128M of memory. [restart required]'
|
270
|
+
c.option '--http-log-drain DRAIN_URL', String, 'specify the URL of a remote log drain. [restart required]'
|
246
271
|
c.option '--desc DESCRIPTION', String, 'update the application description'
|
247
272
|
c.option '--tags TAG_LIST', String, 'comma separated list of tags to use to describe the app'
|
248
273
|
c.action do |args, options|
|
@@ -268,6 +293,44 @@ module NexClient
|
|
268
293
|
end
|
269
294
|
end
|
270
295
|
|
296
|
+
command :certs do |c|
|
297
|
+
c.syntax = 'nex-cli certs [APP_OR_ORG_NAME] [options]'
|
298
|
+
c.summary = 'Manage certs'
|
299
|
+
c.description = 'List ssl certificates'
|
300
|
+
c.example 'list all certs', 'nex-cli certs'
|
301
|
+
c.example 'list all certs under app myapp', 'nex-cli certs myapp'
|
302
|
+
c.example 'list all certs under organization myorg', 'nex-cli certs myorg'
|
303
|
+
c.example 'list all certs matching example.com', 'nex-cli certs --domain example.com'
|
304
|
+
c.option '--domain', 'list all certs matching the provided cname'
|
305
|
+
c.action do |args, options|
|
306
|
+
NexClient::Commands::SslCertificates.list(args,options)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
command :'certs:create' do |c|
|
311
|
+
c.syntax = 'nex-cli certs:create CNAME APP_OR_ORG_NAME'
|
312
|
+
c.summary = 'Create ssl certificates'
|
313
|
+
c.description = 'Create certs for your apps'
|
314
|
+
c.example 'create some.example.com certificate for myapp', 'nex-cli certs:create some.example.com myapp'
|
315
|
+
c.example 'create *.example.com certificate for myorg', 'nex-cli certs:create *.example.com myorg'
|
316
|
+
c.option '--cert CERT_PATH', String, 'path to pem certificate'
|
317
|
+
c.option '--bundle BUNDLE_PATH', String, 'path to certificate bundle'
|
318
|
+
c.option '--privkey KEY_PATH', String, 'path to certificate private key'
|
319
|
+
c.action do |args, options|
|
320
|
+
NexClient::Commands::SslCertificates.create(args,options)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
command :'certs:delete' do |c|
|
325
|
+
c.syntax = 'nex-cli certs:delete CNAME'
|
326
|
+
c.summary = 'Delete certs'
|
327
|
+
c.description = 'Permanently delete a ssl certificate'
|
328
|
+
c.example 'delete certificate for some.example.com', 'nex-cli certs:delete some.example.com'
|
329
|
+
c.action do |args, options|
|
330
|
+
NexClient::Commands::SslCertificates.destroy(args,options)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
271
334
|
command :cubes do |c|
|
272
335
|
c.syntax = 'nex-cli cubes [options]'
|
273
336
|
c.summary = 'Manage cubes [platform admin]'
|
@@ -418,41 +481,6 @@ module NexClient
|
|
418
481
|
end
|
419
482
|
end
|
420
483
|
|
421
|
-
command :certs do |c|
|
422
|
-
c.syntax = 'nex-cli certs [APP_NAME] [options]'
|
423
|
-
c.summary = 'Manage certs'
|
424
|
-
c.description = 'List ssl certificates'
|
425
|
-
c.example 'list all certs', 'nex-cli certs'
|
426
|
-
c.example 'list all certs matching example.com', 'nex-cli certs --domain example.com'
|
427
|
-
c.option '--domain', 'list all certs matching the provided cname'
|
428
|
-
c.action do |args, options|
|
429
|
-
NexClient::Commands::SslCertificates.list(args,options)
|
430
|
-
end
|
431
|
-
end
|
432
|
-
|
433
|
-
command :'certs:create' do |c|
|
434
|
-
c.syntax = 'nex-cli certs:create CNAME APP_NAME'
|
435
|
-
c.summary = 'Create ssl certificates'
|
436
|
-
c.description = 'Create certs for your apps'
|
437
|
-
c.example 'create some.example.com certificate for myapp', 'nex-cli certs:create some.example.com myapp'
|
438
|
-
c.option '--cert CERT_PATH', String, 'path to pem certificate'
|
439
|
-
c.option '--bundle BUNDLE_PATH', String, 'path to certificate bundle'
|
440
|
-
c.option '--privkey KEY_PATH', String, 'path to certificate private key'
|
441
|
-
c.action do |args, options|
|
442
|
-
NexClient::Commands::SslCertificates.create(args,options)
|
443
|
-
end
|
444
|
-
end
|
445
|
-
|
446
|
-
command :'certs:delete' do |c|
|
447
|
-
c.syntax = 'nex-cli certs:delete CNAME'
|
448
|
-
c.summary = 'Delete certs'
|
449
|
-
c.description = 'Permanently delete a ssl certificate'
|
450
|
-
c.example 'delete certificate for some.example.com', 'nex-cli certs:delete some.example.com'
|
451
|
-
c.action do |args, options|
|
452
|
-
NexClient::Commands::SslCertificates.destroy(args,options)
|
453
|
-
end
|
454
|
-
end
|
455
|
-
|
456
484
|
command :users do |c|
|
457
485
|
c.syntax = 'nex-cli users [options]'
|
458
486
|
c.summary = 'List users'
|
@@ -8,6 +8,12 @@ module NexClient
|
|
8
8
|
ADDONS_TITLE = "Addons".colorize(:red)
|
9
9
|
ADDONS_HEADERS = ['name','status','service','size','nodes','app'].map(&:upcase)
|
10
10
|
|
11
|
+
VARS_TITLE = "Environment Variables".colorize(:blue)
|
12
|
+
VARS_HEADERS = ['key','value'].map(&:upcase)
|
13
|
+
|
14
|
+
OPTS_TITLE = "Options".colorize(:blue)
|
15
|
+
OPTS_HEADERS = ['key','value'].map(&:upcase)
|
16
|
+
|
11
17
|
def self.list(args,opts)
|
12
18
|
filters = {}
|
13
19
|
filters[:status] = opts.status || ['active','restarting']
|
@@ -31,6 +37,32 @@ module NexClient
|
|
31
37
|
end
|
32
38
|
end
|
33
39
|
|
40
|
+
def self.info(args,opts)
|
41
|
+
name = args.first
|
42
|
+
e = NexClient::Addon.includes(:app).find(name: name).first
|
43
|
+
|
44
|
+
# Display error
|
45
|
+
unless e
|
46
|
+
error("Error! Could not find addon: #{name}")
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
|
50
|
+
# Display app id
|
51
|
+
self.display_id(e)
|
52
|
+
|
53
|
+
# Display addon details
|
54
|
+
self.display_addons(e)
|
55
|
+
|
56
|
+
# Display all vars
|
57
|
+
self.display_vars(e.all_vars)
|
58
|
+
|
59
|
+
# Display options
|
60
|
+
self.display_options(e.opts)
|
61
|
+
|
62
|
+
# Display App
|
63
|
+
Apps.display_apps(e.app)
|
64
|
+
end
|
65
|
+
|
34
66
|
def self.logs(args,opts)
|
35
67
|
name = args.first
|
36
68
|
e = NexClient::Addon.find(name: name).first
|
@@ -76,6 +108,12 @@ module NexClient
|
|
76
108
|
attrs[:service] = svc_name
|
77
109
|
attrs[:container_size] = opts.size if opts.size.present?
|
78
110
|
|
111
|
+
# Option: HTTP Log Drain
|
112
|
+
if opts.http_log_drain.present?
|
113
|
+
attrs[:opts] ||= {}
|
114
|
+
attrs[:opts][:http_log_drain] = opts.http_log_drain
|
115
|
+
end
|
116
|
+
|
79
117
|
addon = NexClient::Addon.new(attrs)
|
80
118
|
addon.relationships.attributes = { app: { data: { type: 'apps', id: app.id } } }
|
81
119
|
addon.save
|
@@ -90,6 +128,33 @@ module NexClient
|
|
90
128
|
self.display_addons(NexClient::Addon.includes(:app).find(addon.id).first)
|
91
129
|
end
|
92
130
|
|
131
|
+
def self.update(args,opts)
|
132
|
+
name = args.first
|
133
|
+
e = NexClient::Addon.find(name: name).first
|
134
|
+
|
135
|
+
# Display error
|
136
|
+
unless e
|
137
|
+
error("Error! Could not find addon: #{name}")
|
138
|
+
return false
|
139
|
+
end
|
140
|
+
|
141
|
+
# Attributes
|
142
|
+
attrs = {}
|
143
|
+
attrs[:container_size] = opts.size if opts.size.present?
|
144
|
+
|
145
|
+
# Option: HTTP Log Drain
|
146
|
+
if opts.http_log_drain.present?
|
147
|
+
attrs[:opts] ||= (e.opts || {}).dup
|
148
|
+
attrs[:opts]['http_log_drain'] = opts.http_log_drain
|
149
|
+
end
|
150
|
+
|
151
|
+
# Update
|
152
|
+
e.update_attributes(attrs)
|
153
|
+
|
154
|
+
# Display app
|
155
|
+
self.display_addons(NexClient::Addon.includes(:app).find(e.id).first)
|
156
|
+
end
|
157
|
+
|
93
158
|
def self.destroy(args,opts)
|
94
159
|
name = args.first
|
95
160
|
e = NexClient::Addon.find(name: name).first
|
@@ -150,6 +215,34 @@ module NexClient
|
|
150
215
|
puts "\n"
|
151
216
|
end
|
152
217
|
|
218
|
+
def self.display_id(e)
|
219
|
+
table = Terminal::Table.new do |t|
|
220
|
+
t.add_row(['ADDON ID',e.id])
|
221
|
+
end
|
222
|
+
puts table
|
223
|
+
puts "\n"
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.display_vars(list)
|
227
|
+
table = Terminal::Table.new title: VARS_TITLE, headings: VARS_HEADERS do |t|
|
228
|
+
[list].flatten.compact.each do |e|
|
229
|
+
e.sort_by { |k, v| k }.each { |k,v| t.add_row([k,v]) }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
puts table
|
233
|
+
puts "\n"
|
234
|
+
end
|
235
|
+
|
236
|
+
def self.display_options(list)
|
237
|
+
table = Terminal::Table.new title: OPTS_TITLE, headings: OPTS_HEADERS do |t|
|
238
|
+
[list].flatten.compact.each do |e|
|
239
|
+
e.each { |k,v| t.add_row([k,v]) }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
puts table
|
243
|
+
puts "\n"
|
244
|
+
end
|
245
|
+
|
153
246
|
def self.format_record(record)
|
154
247
|
app = self.format_app(record)
|
155
248
|
node_count = self.format_node_count(record)
|
@@ -13,6 +13,9 @@ module NexClient
|
|
13
13
|
VARS_TITLE = "Environment Variables".colorize(:blue)
|
14
14
|
VARS_HEADERS = ['key','value'].map(&:upcase)
|
15
15
|
|
16
|
+
OPTS_TITLE = "Options".colorize(:blue)
|
17
|
+
OPTS_HEADERS = ['key','value'].map(&:upcase)
|
18
|
+
|
16
19
|
SCM_TITLE = "Source Control Management".colorize(:yellow)
|
17
20
|
SCM_HEADERS = ['provider','repo','branch','last commit'].map(&:upcase)
|
18
21
|
|
@@ -42,7 +45,7 @@ module NexClient
|
|
42
45
|
|
43
46
|
def self.info(args,opts)
|
44
47
|
name = args.first
|
45
|
-
e = NexClient::App.includes(:addons,:owner).find(name: name).first
|
48
|
+
e = NexClient::App.includes(:addons,:owner,:domains,:ssl_certificates).find(name: name).first
|
46
49
|
|
47
50
|
# Display error
|
48
51
|
unless e
|
@@ -59,9 +62,18 @@ module NexClient
|
|
59
62
|
# Display all vars
|
60
63
|
self.display_vars(e.all_vars)
|
61
64
|
|
62
|
-
# Display
|
65
|
+
# Display options
|
66
|
+
self.display_options(e.opts)
|
67
|
+
|
68
|
+
# Display Source Control Management
|
63
69
|
self.display_scm(e.scm)
|
64
70
|
|
71
|
+
# Display custom domains
|
72
|
+
Domains.display_domains(e.domains)
|
73
|
+
|
74
|
+
# Display SSL certificates
|
75
|
+
SslCertificates.display_certs(e.ssl_certificates)
|
76
|
+
|
65
77
|
# Display all addons
|
66
78
|
Addons.display_addons(e.addons.select { |a| a.status == 'active'})
|
67
79
|
end
|
@@ -107,6 +119,12 @@ module NexClient
|
|
107
119
|
attrs[:description] = opts.desc if opts.desc.present?
|
108
120
|
attrs[:tag_list] = opts.tags if opts.tags.present?
|
109
121
|
|
122
|
+
# Option: HTTP Log Drain
|
123
|
+
if opts.http_log_drain.present?
|
124
|
+
attrs[:opts] ||= {}
|
125
|
+
attrs[:opts]['http_log_drain'] = opts.http_log_drain
|
126
|
+
end
|
127
|
+
|
110
128
|
# Env variables via command line
|
111
129
|
if opts.env.present?
|
112
130
|
attrs[:vars] ||= {}
|
@@ -166,6 +184,12 @@ module NexClient
|
|
166
184
|
attrs[:description] = opts.desc if opts.desc.present?
|
167
185
|
attrs[:tag_list] = opts.tags if opts.tags.present?
|
168
186
|
|
187
|
+
# Option: HTTP Log Drain
|
188
|
+
if opts.http_log_drain.present?
|
189
|
+
attrs[:opts] ||= (e.opts || {}).dup
|
190
|
+
attrs[:opts]['http_log_drain'] = opts.http_log_drain
|
191
|
+
end
|
192
|
+
|
169
193
|
# Update
|
170
194
|
e.update_attributes(attrs)
|
171
195
|
|
@@ -370,6 +394,16 @@ module NexClient
|
|
370
394
|
|
371
395
|
def self.display_vars(list)
|
372
396
|
table = Terminal::Table.new title: VARS_TITLE, headings: VARS_HEADERS do |t|
|
397
|
+
[list].flatten.compact.each do |e|
|
398
|
+
e.sort_by { |k, v| k }.each { |k,v| t.add_row([k,v]) }
|
399
|
+
end
|
400
|
+
end
|
401
|
+
puts table
|
402
|
+
puts "\n"
|
403
|
+
end
|
404
|
+
|
405
|
+
def self.display_options(list)
|
406
|
+
table = Terminal::Table.new title: OPTS_TITLE, headings: OPTS_HEADERS do |t|
|
373
407
|
[list].flatten.compact.each do |e|
|
374
408
|
e.each { |k,v| t.add_row([k,v]) }
|
375
409
|
end
|
@@ -5,7 +5,7 @@ module NexClient
|
|
5
5
|
extend Helpers
|
6
6
|
|
7
7
|
DOMAINS_TITLE = "Domains".colorize(:magenta)
|
8
|
-
DOMAINS_HEADERS = ['id','cname','origin'].map(&:upcase)
|
8
|
+
DOMAINS_HEADERS = ['id','cname','ssl?','origin'].map(&:upcase)
|
9
9
|
|
10
10
|
def self.list(args,opts)
|
11
11
|
filters = {}
|
@@ -27,7 +27,7 @@ module NexClient
|
|
27
27
|
domain_name,app_name = args
|
28
28
|
app = NexClient::App.find(name: app_name).first
|
29
29
|
app ||= NexClient::CubeInstance.find(name: app_name).first
|
30
|
-
|
30
|
+
|
31
31
|
# Display error
|
32
32
|
unless app
|
33
33
|
error("Error! Could not find app: #{app_name}")
|
@@ -84,6 +84,7 @@ module NexClient
|
|
84
84
|
[
|
85
85
|
record.id,
|
86
86
|
record.cname,
|
87
|
+
record.ssl_available,
|
87
88
|
origin
|
88
89
|
]
|
89
90
|
end
|
@@ -24,13 +24,14 @@ module NexClient
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.create(args,opts)
|
27
|
-
cert_name,
|
28
|
-
|
29
|
-
|
27
|
+
cert_name,origin_name = args
|
28
|
+
origin = NexClient::App.find(name: origin_name).first
|
29
|
+
origin ||= NexClient::CubeInstance.find(name: origin_name).first
|
30
|
+
origin ||= NexClient::Organization.find(handle: origin_name).first
|
30
31
|
|
31
32
|
# Display error
|
32
|
-
unless
|
33
|
-
error("Error! Could not find
|
33
|
+
unless origin
|
34
|
+
error("Error! Could not find origin: #{origin_name}")
|
34
35
|
return false
|
35
36
|
end
|
36
37
|
|
@@ -45,7 +46,7 @@ module NexClient
|
|
45
46
|
cert_bundle: bundle,
|
46
47
|
private_key: privkey
|
47
48
|
)
|
48
|
-
cert.relationships.attributes = { origin: { data: { type:
|
49
|
+
cert.relationships.attributes = { origin: { data: { type: origin.type, id: origin.id } } }
|
49
50
|
cert.save
|
50
51
|
|
51
52
|
# Display errors if any
|
@@ -99,8 +100,9 @@ module NexClient
|
|
99
100
|
end
|
100
101
|
|
101
102
|
def self.format_origin(record)
|
102
|
-
return
|
103
|
-
|
103
|
+
return '-' unless o = record.origin
|
104
|
+
name = o.respond_to?(:handle) ? o.handle : o.name
|
105
|
+
"#{o.type[0..2]}:#{name}"
|
104
106
|
end
|
105
107
|
end
|
106
108
|
end
|
data/lib/nex_client/domain.rb
CHANGED
data/lib/nex_client/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nex_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arnaud Lachaume
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json_api_client
|