phoseum-cli 0.0.15 → 0.0.20
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/bin/phoseum-cli +136 -23
- data/lib/phoseum/phoseum-cli-lib.rb +107 -78
- data/lib/phoseum/phoseum-common-lib.rb +45 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bfc9c3e5f01d6ea982796a580be0195b76156127ab1b39c3c1bfdb0d1ada4f21
|
4
|
+
data.tar.gz: eb9e11b414ee5be39678593668f2fa8f02477e61b1cdbd85a0d69fbdf8728d6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c433b128e7140e01364e04309bec935cf66efcfebe785ffbe7f4cefbf0dcdf007cb2f8f7f6398eb7fcae87c751fb7f4213110422b03d621120c991369277d8a6
|
7
|
+
data.tar.gz: 1435d44fa6d8599e5ba9492fbce3bb5e30dba57e24c225f7ed6cbfd87ae56f7f3cca5820e9c94a7d259ca652e8875ec5ea503ebb1839969561cf99c5c5f28b7c
|
data/bin/phoseum-cli
CHANGED
@@ -277,6 +277,15 @@ def user_mgmt(action,user,pass='',role='')
|
|
277
277
|
end
|
278
278
|
|
279
279
|
def user_login(puser='',ppass='')
|
280
|
+
|
281
|
+
server_value = cross_versions()
|
282
|
+
if server_value['version'] != VERSION_SIGN
|
283
|
+
puts "Server and Client version won't match, please make them match to continue.".red
|
284
|
+
puts "Remote version: #{server_value['version']}"
|
285
|
+
puts "Local version: #{VERSION_SIGN}"
|
286
|
+
exit 1
|
287
|
+
end
|
288
|
+
|
280
289
|
post_body={}
|
281
290
|
user= !puser ? '' : puser
|
282
291
|
pass= !ppass ? '' : ppass
|
@@ -332,7 +341,6 @@ def user_login(puser='',ppass='')
|
|
332
341
|
return false
|
333
342
|
end
|
334
343
|
|
335
|
-
|
336
344
|
def delete(what='',path='')
|
337
345
|
if !validate_token($config['TOKEN'])
|
338
346
|
if token = user_login()
|
@@ -383,6 +391,7 @@ case options
|
|
383
391
|
puts "Creating User".green if !$QUIET
|
384
392
|
if options[:create_user]
|
385
393
|
value=check_string_sanity(options[:create_user])
|
394
|
+
username_sanity(value)
|
386
395
|
what='add_user'
|
387
396
|
if !options[:role]
|
388
397
|
puts "You need to select one of the valid Roles".red
|
@@ -393,6 +402,7 @@ case options
|
|
393
402
|
print "\t"
|
394
403
|
confirm=STDIN.getpass('Confirm: ')
|
395
404
|
if new_password == confirm
|
405
|
+
password_sanity(new_password)
|
396
406
|
user_mgmt(what,value,new_password,options[:role])
|
397
407
|
end
|
398
408
|
end
|
@@ -435,15 +445,24 @@ case options
|
|
435
445
|
if options[:user]
|
436
446
|
what='user'
|
437
447
|
value=options[:user]
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
448
|
+
secretinfo = api_caller({"action" => "check-secret"},true)
|
449
|
+
if secretinfo['error']
|
450
|
+
err_msg = JSON.parse(secretinfo)
|
451
|
+
puts err_msg['error'].red
|
452
|
+
puts "You cannot delete users if you don't have the Server Secret."
|
453
|
+
exit 1
|
454
|
+
end
|
455
|
+
if secretinfo['success']
|
456
|
+
if confirm_action("Are you sure you want to delete the #{what} #{value}")
|
457
|
+
puts "Deleting #{what} #{value}".green if !$QUIET
|
458
|
+
if delete_user(value)
|
459
|
+
puts "#{value} successfully deleted.".green
|
460
|
+
else
|
461
|
+
puts "#{value} failed to be deleted.".red
|
462
|
+
end
|
442
463
|
else
|
443
|
-
puts "#{
|
464
|
+
puts "Deleting #{what} cancelled.".green
|
444
465
|
end
|
445
|
-
else
|
446
|
-
puts "Deleting #{what} cancelled.".green
|
447
466
|
end
|
448
467
|
end
|
449
468
|
if !what
|
@@ -452,17 +471,117 @@ case options
|
|
452
471
|
end
|
453
472
|
when -> (o) { o[:options] }
|
454
473
|
if options[:user]
|
455
|
-
|
456
|
-
|
457
|
-
if
|
458
|
-
|
459
|
-
confirm=STDIN.gets.chomp
|
460
|
-
else
|
461
|
-
puts "User Check Failed".red
|
474
|
+
user_work=check_string_sanity(options[:user])
|
475
|
+
tokeninfo = api_caller({"action" => "token-payload", "token" => "#{$config['TOKEN']}"})
|
476
|
+
if tokeninfo['error']
|
477
|
+
puts "Your token seems invalid, please login again (-l or -c)".red
|
462
478
|
exit 1
|
463
479
|
end
|
480
|
+
auth_user = ''
|
481
|
+
role_user = ''
|
482
|
+
if tokeninfo['success']
|
483
|
+
payload = tokeninfo['payload']
|
484
|
+
tkdata = JSON.parse(payload)
|
485
|
+
auth_user = tkdata[0]['data']['user'].clone
|
486
|
+
role_user = tkdata[0]['data']['role'].clone
|
487
|
+
puts "Current user is #{auth_user}".green
|
488
|
+
end
|
489
|
+
|
490
|
+
secretinfo = api_caller({"action" => "check-secret"},true)
|
491
|
+
if secretinfo['error']
|
492
|
+
err_msg = JSON.parse(secretinfo)
|
493
|
+
puts err_msg['error'].red
|
494
|
+
puts "You will only be able to change your own password."
|
495
|
+
if user_work != auth_user
|
496
|
+
puts "Requesting other user than your own at command line is wrong, aborting. (#{user_work} is not #{auth_user})".red
|
497
|
+
exit 1
|
498
|
+
end
|
499
|
+
end
|
500
|
+
if secretinfo['success']
|
501
|
+
puts secretinfo['success'].green
|
502
|
+
if role_user == "Super"
|
503
|
+
puts "You will be able to change any user on the system".green
|
504
|
+
else
|
505
|
+
if user_work != auth_user
|
506
|
+
puts "Having a regular user will now allow to change other users's options".yellow
|
507
|
+
puts "Requesting other user than your own at command line is wrong, aborting. (#{user_work} is not #{auth_user})".red
|
508
|
+
exit 1
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
if user_work == auth_user
|
514
|
+
print "Please type new password for user #{user_work} .\n\t"
|
515
|
+
new_password=STDIN.getpass('Password: ')
|
516
|
+
print "\t"
|
517
|
+
check_password=STDIN.getpass('Confirm: ')
|
518
|
+
if new_password != check_password
|
519
|
+
puts "Passwords won't match, try again.".red
|
520
|
+
exit 1
|
521
|
+
end
|
522
|
+
if change_user_password(auth_user,check_password)
|
523
|
+
puts "Password changed successfully".green
|
524
|
+
exit 0
|
525
|
+
else
|
526
|
+
puts "Could not change user password.".red
|
527
|
+
exit 1
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
if user_work != auth_user && secretinfo['success']
|
532
|
+
print "\n Change [P]assword, [U]sername, [R]ole from user #{user_work} :[P/U/R]: "
|
533
|
+
opt_action=STDIN.gets.chomp
|
534
|
+
if opt_action == "P"
|
535
|
+
print "Please type new password for user #{user_work} .\n\t"
|
536
|
+
new_password=STDIN.getpass('Password: ')
|
537
|
+
puts "testing #{new_password}"
|
538
|
+
print "\t"
|
539
|
+
check_password=STDIN.getpass('Confirm: ')
|
540
|
+
if new_password != check_password
|
541
|
+
puts "Passwords won't match, try again.".red
|
542
|
+
exit 1
|
543
|
+
end
|
544
|
+
password_sanity(new_password)
|
545
|
+
if change_user_password(options[:user],check_password,true)
|
546
|
+
puts "Password changed successfully".green
|
547
|
+
exit 0
|
548
|
+
else
|
549
|
+
puts "Could not change user password.".red
|
550
|
+
exit 1
|
551
|
+
end
|
552
|
+
end
|
553
|
+
if opt_action == "U"
|
554
|
+
print "\tPlease type new username for user #{user_work} : "
|
555
|
+
new_user_proto=STDIN.gets.chomp
|
556
|
+
new_user=check_string_sanity(new_user_proto)
|
557
|
+
username_sanity(new_user)
|
558
|
+
if change_username(user_work,new_user)
|
559
|
+
puts "Username changed successfully".green
|
560
|
+
exit 0
|
561
|
+
else
|
562
|
+
puts "Could not change username.".red
|
563
|
+
exit 1
|
564
|
+
end
|
565
|
+
end
|
566
|
+
if opt_action == "R"
|
567
|
+
print " Please select between roles [S]uper,[U]ser for user #{user_work} :[S/U]: "
|
568
|
+
new_role=STDIN.gets.chomp
|
569
|
+
if new_role == "S" || new_role == "U"
|
570
|
+
role_nominal = new_role == "S" ? "Super" : "User"
|
571
|
+
if change_role(user_work,role_nominal)
|
572
|
+
puts "Username changed successfully".green
|
573
|
+
exit 0
|
574
|
+
else
|
575
|
+
puts "Could not change username.".red
|
576
|
+
exit 1
|
577
|
+
end
|
578
|
+
else
|
579
|
+
puts "Unknown option: #{new_role} .".red
|
580
|
+
exit 1
|
581
|
+
end
|
582
|
+
end
|
583
|
+
end
|
464
584
|
else
|
465
|
-
# if !what
|
466
585
|
puts "Got no user to work with. Exiting.".red
|
467
586
|
exit 1
|
468
587
|
end
|
@@ -498,13 +617,6 @@ case options
|
|
498
617
|
upload(album_clean,usable_name,usable_desc)
|
499
618
|
end
|
500
619
|
when -> (l) { l[:login] }
|
501
|
-
server_value = cross_versions()
|
502
|
-
if server_value['version'] != VERSION_SIGN
|
503
|
-
puts "Server and Client version won't match, please make them match to continue.".red
|
504
|
-
puts "Remote version: #{server_value['version']}"
|
505
|
-
puts "Local version: #{VERSION_SIGN}"
|
506
|
-
exit 1
|
507
|
-
end
|
508
620
|
puts "Start login process".green if !$QUIET
|
509
621
|
if user_login
|
510
622
|
puts "Login successful".green
|
@@ -524,6 +636,7 @@ case options
|
|
524
636
|
test
|
525
637
|
else
|
526
638
|
ARGV[0] = '--help'
|
639
|
+
local_version
|
527
640
|
option_parser(ARGV)
|
528
641
|
exit 1
|
529
642
|
end
|
@@ -1,4 +1,12 @@
|
|
1
1
|
|
2
|
+
# Provide some help and options!
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
# >> options = option_parser(ARGV)
|
6
|
+
# => puts options[:option]
|
7
|
+
#
|
8
|
+
# Arguments:
|
9
|
+
# ARGV: (Array or command line parameters)
|
2
10
|
def option_parser(opts)
|
3
11
|
options = {}
|
4
12
|
OptionParser.new do |opts|
|
@@ -60,7 +68,7 @@ def option_parser(opts)
|
|
60
68
|
options[:role] = r
|
61
69
|
end
|
62
70
|
|
63
|
-
opts.on("-o", "--options", "Update options from
|
71
|
+
opts.on("-o", "--options", "Update options from User. Use with: [user]") do |o|
|
64
72
|
options[:options] = o
|
65
73
|
end
|
66
74
|
|
@@ -88,6 +96,15 @@ def option_parser(opts)
|
|
88
96
|
return options
|
89
97
|
end
|
90
98
|
|
99
|
+
# Give yes/no prompt!
|
100
|
+
#
|
101
|
+
# Example:
|
102
|
+
# >> confirm_action("message")
|
103
|
+
# => You must write 'YES' to confirm, otherwise NO is assumed
|
104
|
+
# => message :[YES/NO]:
|
105
|
+
#
|
106
|
+
# Arguments:
|
107
|
+
# msg: (String)
|
91
108
|
def confirm_action(msg)
|
92
109
|
puts "You must write 'YES' to confirm, otherwise NO is assumed".yellow
|
93
110
|
print "#{msg} :[YES/NO]: "
|
@@ -138,16 +155,6 @@ def client_checks
|
|
138
155
|
puts "I could not find a valid SERVERURL configuration. Contains: #{$config['SERVERURL']}".red
|
139
156
|
exit 1
|
140
157
|
end
|
141
|
-
if !$config['DEFAULT_SECRET']
|
142
|
-
puts "I could not find the DEFAULT_SECRET from Phoseum config, this will limit our actions.".red
|
143
|
-
exit 1
|
144
|
-
elsif $config['DEFAULT_SECRET'] == 'copy-secret-from-server'
|
145
|
-
puts "DEFAULT_SECRET from Phoseum config. Still on self generated value, copy a valid one from the server.".red
|
146
|
-
exit 1
|
147
|
-
elsif $config['DEFAULT_SECRET'] == ''
|
148
|
-
puts "I could not find the DEFAULT_SECRET from Phoseum config, Variable is empty.".red
|
149
|
-
exit 1
|
150
|
-
end
|
151
158
|
if !$config['SERVERURL']
|
152
159
|
puts "I could not find the SERVERURL from Phoseum config, this client is then useless.".red
|
153
160
|
exit 1
|
@@ -228,45 +235,31 @@ def search_image(sign,album='')
|
|
228
235
|
end
|
229
236
|
end
|
230
237
|
|
231
|
-
def
|
238
|
+
def api_caller(json_body,auth=false,cli=false)
|
232
239
|
headers = {}
|
233
|
-
if
|
234
|
-
|
235
|
-
|
236
|
-
|
240
|
+
if !auth
|
241
|
+
if $config['TOKEN']
|
242
|
+
headers={ "bearer" => "#{$config['TOKEN']}" }
|
243
|
+
else
|
244
|
+
return false
|
245
|
+
end
|
237
246
|
end
|
238
247
|
base = URI.parse("#{$config['SERVERURL']}")
|
239
248
|
request = Net::HTTP::Post.new(base,headers)
|
240
|
-
request.body = JSON.generate(
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
http.request(request)
|
248
|
-
end
|
249
|
-
begin
|
250
|
-
list = JSON.parse(response.body)
|
251
|
-
if list['error']
|
252
|
-
return false
|
249
|
+
request.body = JSON.generate(json_body)
|
250
|
+
|
251
|
+
if auth
|
252
|
+
if cli
|
253
|
+
request.basic_auth("cli", "loginNOauth")
|
254
|
+
else
|
255
|
+
request.basic_auth("auth", $config['DEFAULT_SECRET'])
|
253
256
|
end
|
254
|
-
|
255
|
-
|
257
|
+
else
|
258
|
+
if cli
|
259
|
+
request.basic_auth("cli", "loginNOauth")
|
256
260
|
end
|
257
|
-
rescue
|
258
|
-
puts "\nThe server sent out an Error:".red
|
259
|
-
puts clean_html(response.body)
|
260
|
-
exit 1
|
261
261
|
end
|
262
|
-
end
|
263
262
|
|
264
|
-
def cross_versions
|
265
|
-
headers = {}
|
266
|
-
base = URI.parse("#{$config['SERVERURL']}")
|
267
|
-
request = Net::HTTP::Post.new(base,headers)
|
268
|
-
request.basic_auth("cli", "loginNOauth")
|
269
|
-
request.body = JSON.generate({"action" => "version-check" })
|
270
263
|
response = Net::HTTP.start(base.hostname, $config['PORT'],
|
271
264
|
:timeout => $config['CALL_TIMEOUT'],
|
272
265
|
:use_ssl => base.scheme == "https",
|
@@ -278,56 +271,92 @@ def cross_versions
|
|
278
271
|
begin
|
279
272
|
list = JSON.parse(response.body)
|
280
273
|
return list
|
281
|
-
# if list['error']
|
282
|
-
# return false
|
283
|
-
# end
|
284
|
-
# if list['success']
|
285
|
-
# return true
|
286
|
-
# end
|
287
274
|
rescue
|
288
|
-
|
289
|
-
|
290
|
-
|
275
|
+
if !auth
|
276
|
+
puts "\nThe server sent out an Error:".red
|
277
|
+
puts clean_html(response.body)
|
278
|
+
exit 1
|
279
|
+
else
|
280
|
+
return '{"error": "Secret is invalid"}'
|
281
|
+
end
|
291
282
|
end
|
292
283
|
end
|
293
284
|
|
285
|
+
def validate_token(token)
|
286
|
+
result = api_caller({"action" => "check-token" })
|
287
|
+
if result['error']
|
288
|
+
return false
|
289
|
+
end
|
290
|
+
if result['success']
|
291
|
+
return true
|
292
|
+
end
|
293
|
+
end
|
294
294
|
|
295
|
-
def
|
296
|
-
|
297
|
-
if
|
298
|
-
headers={ "bearer" => "#{$config['TOKEN']}" }
|
299
|
-
else
|
295
|
+
def change_user_password(user,pass,auth=false)
|
296
|
+
result = api_caller({"action" => "change-password", "username" => user, "password" => pass},auth)
|
297
|
+
if result['error']
|
300
298
|
return false
|
301
299
|
end
|
302
|
-
|
303
|
-
|
304
|
-
request.body = JSON.generate({"action" => "delete-user", "username" => username })
|
305
|
-
response = Net::HTTP.start(base.hostname, $config['PORT'],
|
306
|
-
:timeout => $config['CALL_TIMEOUT'],
|
307
|
-
:use_ssl => base.scheme == "https",
|
308
|
-
:verify_mode => OpenSSL::SSL::VERIFY_PEER,
|
309
|
-
:ca_file => $config['CA_TRUST']
|
310
|
-
) do |http|
|
311
|
-
http.request(request)
|
300
|
+
if result['success']
|
301
|
+
return true
|
312
302
|
end
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
303
|
+
end
|
304
|
+
|
305
|
+
def change_username(user,new_user)
|
306
|
+
result = api_caller({"action" => "change-username", "username" => user, "new-username" => new_user},true)
|
307
|
+
if result['error']
|
308
|
+
return false
|
309
|
+
end
|
310
|
+
if result['success']
|
311
|
+
return true
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def change_role(user,new_role)
|
316
|
+
result = api_caller({"action" => "change-role", "username" => user, "new-role" => new_role},true)
|
317
|
+
if result['error']
|
318
|
+
return false
|
319
|
+
end
|
320
|
+
if result['success']
|
321
|
+
return true
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def password_sanity(password)
|
326
|
+
if password.length < MIN_PASS
|
327
|
+
puts "New password is shorter than #{MIN_PASS} chars, please use a bigger password.".red
|
328
|
+
exit 1
|
329
|
+
end
|
330
|
+
if !password.test_password
|
331
|
+
puts "Password must be at least #{MIN_PASS} and contain at least one capital, one symbol, one number, one regular characters.".red
|
324
332
|
exit 1
|
325
333
|
end
|
326
334
|
end
|
327
335
|
|
336
|
+
def username_sanity(username)
|
337
|
+
if new_user.length < MIN_USER
|
338
|
+
puts "New Username is shorter than #{MIN_USER} chars, please use a bigger username.".red
|
339
|
+
exit 1
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def cross_versions
|
344
|
+
return api_caller({"action" => "version-check" },true,true)
|
345
|
+
end
|
346
|
+
|
347
|
+
def delete_user(username)
|
348
|
+
result = api_caller({"action" => "delete-user", "username" => username},true)
|
349
|
+
if result['error']
|
350
|
+
return false
|
351
|
+
end
|
352
|
+
if result['success']
|
353
|
+
return true
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
328
357
|
def local_version
|
329
358
|
puts "This CLI library is running version: #{VERSION_SIGN}"
|
330
359
|
return
|
331
360
|
end
|
332
361
|
|
333
|
-
VERSION_SIGN="0.0.
|
362
|
+
VERSION_SIGN="0.0.20"
|
@@ -1,17 +1,51 @@
|
|
1
|
+
MIN_USER = 3
|
2
|
+
MIN_PASS = 7
|
3
|
+
|
1
4
|
class String
|
2
5
|
def remove_non_ascii(replacement='')
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
n=self.split("")
|
7
|
+
self.slice!(0..self.size)
|
8
|
+
n.each { |b|
|
9
|
+
if b.ord < 48 || b.ord > 57 && b.ord < 65 || b.ord > 90 && b.ord < 97 || b.ord > 122 then
|
10
|
+
self.concat(replacement)
|
11
|
+
else
|
12
|
+
self.concat(b)
|
13
|
+
end
|
14
|
+
}
|
15
|
+
self.to_s
|
16
|
+
end
|
14
17
|
|
18
|
+
def test_password()
|
19
|
+
symbol = false
|
20
|
+
number = false
|
21
|
+
capital= false
|
22
|
+
regular= false
|
23
|
+
all_valid= true
|
24
|
+
puts "Testing #{self}"
|
25
|
+
n=self.split("")
|
26
|
+
self.slice!(0..self.size)
|
27
|
+
n.each { |b|
|
28
|
+
# test symbols
|
29
|
+
if b.ord > 32 && b.ord < 48 || b.ord > 58 && b.ord < 65 || b.ord > 91 && b.ord < 97 || b.ord > 123 && b.ord < 126 then
|
30
|
+
symbol = true
|
31
|
+
# test capital letters
|
32
|
+
elsif b.ord > 65 && b.ord < 91 then
|
33
|
+
capital = true
|
34
|
+
# test numbers
|
35
|
+
elsif b.ord > 47 && b.ord < 58 then
|
36
|
+
number = true
|
37
|
+
# test regular alphabet
|
38
|
+
elsif b.ord > 96 && b.ord < 123 then
|
39
|
+
regular = true
|
40
|
+
else
|
41
|
+
# com character out of the acceptable ranges
|
42
|
+
all_valid = false
|
43
|
+
end
|
44
|
+
}
|
45
|
+
if symbol && capital && number && regular && all_valid
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
end
|
15
49
|
end
|
16
50
|
|
17
51
|
def check_string_sanity(album)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phoseum-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.20
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julio C Hegedus
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-10-
|
11
|
+
date: 2020-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: yaml
|