Skip to content

Commit edf1d0a

Browse files
colinmollenhourSpComb
authored andcommitted
Added prompt to commands that wait for input from STDIN (kontena#2045)
* Fix broken update and import. * Use stdin.tty? to determine if prompting is possible * Vault update didnt have --grid option * Move Kontena.stdinput to common.stdin_input * Borrow and adapt specs from kontena#2002 * Add mode to stdin_input * Added comment to common#stdin_input * Fixed specs to match
1 parent 4275b7c commit edf1d0a

File tree

7 files changed

+173
-31
lines changed

7 files changed

+173
-31
lines changed

cli/lib/kontena/cli/common.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ def pastel
2222
@pastel ||= Pastel.new(enabled: $stdout.tty?)
2323
end
2424

25+
# Read from STDIN. If stdin is a console, use prompt to ask.
26+
# @param [String] message
27+
# @param [Symbol] mode (prompt method: :ask, :multiline, etc)
28+
def stdin_input(message = nil, mode = :ask)
29+
if $stdin.tty?
30+
Array(prompt.send(mode, message)).join.chomp
31+
elsif !$stdin.eof?
32+
$stdin.read.chomp
33+
else
34+
exit_with_error 'Missing input'
35+
end
36+
end
37+
2538
def running_silent?
2639
self.respond_to?(:silent?) && self.silent?
2740
end

cli/lib/kontena/cli/master/config/import_command.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class ImportCommand < Kontena::Command
88

99
banner "Updates configuration from a file into Master"
1010

11-
parameter '[PATH]', "Input from file in PATH, default: STDIN", required: false
11+
parameter '[PATH]', "Input from file in PATH (default: STDIN)", required: false
1212

1313
option ['--preset'], '[NAME]', 'Load preset', hidden: true
1414

@@ -30,7 +30,7 @@ def input_as_hash
3030
path = File.join(Kontena.root, 'lib/kontena/presets', "#{self.preset}.yml")
3131
File.read(path)
3232
else
33-
STDIN.read
33+
stdin_input("Enter master configuration as #{format.upcase}", :multiline)
3434
end
3535
end
3636

cli/lib/kontena/cli/vault/import_command.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class ImportCommand < Kontena::Command
1010
option '--skip-null', :flag, "Do not remove keys with null values"
1111
option '--empty-is-null', :flag, "Treat empty values as null"
1212

13-
parameter '[PATH]', "Input from file in PATH, default: STDIN"
13+
parameter '[PATH]', "Input from file in PATH (default: STDIN)"
1414

1515
requires_current_master
1616

@@ -23,7 +23,7 @@ def parsed_input
2323
end
2424

2525
def input
26-
path ? File.read(path) : STDIN.read
26+
path ? File.read(path) : stdin_input("Enter secrets YAML", :multiline)
2727
end
2828

2929
def execute

cli/lib/kontena/cli/vault/update_command.rb

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
module Kontena::Cli::Vault
22
class UpdateCommand < Kontena::Command
33
include Kontena::Cli::Common
4+
include Kontena::Cli::GridOptions
45

56
parameter 'NAME', 'Secret name'
6-
parameter '[VALUE]', 'Secret value'
7+
parameter '[VALUE]', 'Secret value (default: STDIN)'
78

89
option ['-u', '--upsert'], :flag, 'Create secret unless already exists', default: false
910
option '--silent', :flag, "Reduce output verbosity"
1011

11-
def execute
12-
require_api_url
13-
require_current_grid
12+
requires_current_master
13+
14+
def default_value
15+
stdin_input("Enter value for secret '#{name}'", :mask)
16+
end
1417

15-
token = require_token
16-
value ||= STDIN.read.chomp
17-
data = {
18-
name: name,
19-
value: value,
20-
upsert: upsert?
21-
}
18+
def execute
2219
vspinner "Updating #{name.colorize(:cyan)} value in the vault " do
23-
client(token).put("secrets/#{current_grid}/#{name}", data)
20+
client.put("secrets/#{current_grid}/#{name}", {name: name, value: value, upsert: upsert? })
2421
end
2522
end
2623
end

cli/lib/kontena/cli/vault/write_command.rb

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,19 @@ class WriteCommand < Kontena::Command
44
include Kontena::Cli::GridOptions
55

66
parameter 'NAME', 'Secret name'
7-
parameter '[VALUE]', 'Secret value'
7+
parameter '[VALUE]', 'Secret value (default: STDIN)'
88

99
option '--silent', :flag, "Reduce output verbosity"
1010

11-
def execute
12-
require_api_url
13-
require_current_grid
11+
requires_current_master
1412

15-
token = require_token
16-
secret = value
17-
if secret.to_s == ''
18-
secret = STDIN.read
19-
end
20-
exit_with_error('No value provided') if secret.to_s == ''
21-
data = {
22-
name: name,
23-
value: secret
24-
}
13+
def default_value
14+
stdin_input("Enter value for secret '#{name}'", :mask)
15+
end
16+
17+
def execute
2518
vspinner "Writing #{name.colorize(:cyan)} to the vault " do
26-
client(token).post("grids/#{current_grid}/secrets", data)
19+
client.post("grids/#{current_grid}/secrets", { name: name, value: value })
2720
end
2821
end
2922
end
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
require_relative "../../../spec_helper"
2+
require 'kontena/cli/vault/update_command'
3+
4+
describe Kontena::Cli::Vault::UpdateCommand do
5+
6+
include RequirementsHelper
7+
include ClientHelpers
8+
9+
let(:subject) { described_class.new(File.basename($0)) }
10+
11+
describe '#execute' do
12+
13+
context 'without value parameter' do
14+
15+
let(:stdin) { double(:stdin) }
16+
17+
before(:each) do
18+
@old_stdin = $stdin
19+
$stdin = stdin
20+
end
21+
22+
after(:each) { $stdin = @old_stdin }
23+
24+
context 'without tty' do
25+
before(:each) { allow(stdin).to receive(:tty?).and_return(false) }
26+
after(:each) { $stdin = @old_stdin }
27+
28+
context 'nothing in stdin' do
29+
before(:each) do
30+
allow(stdin).to receive(:eof?).and_return(true)
31+
end
32+
33+
it 'returns error if value not provided' do
34+
expect{subject.run(['mysql_password'])}.to exit_with_error.and output(/Missing/).to_stderr
35+
end
36+
end
37+
38+
context 'value in stdin' do
39+
it 'sends update request' do
40+
expect(stdin).to receive(:eof?).and_return(false)
41+
expect(stdin).to receive(:read).and_return('secret')
42+
expect(client).to receive(:put).with('secrets/test-grid/mysql_password', { name: 'mysql_password', value: 'secret', upsert: false})
43+
expect{subject.run(['mysql_password'])}.not_to exit_with_error
44+
end
45+
end
46+
end
47+
48+
context 'with tty' do
49+
before(:each) do
50+
allow(stdin).to receive(:tty?).and_return(true)
51+
end
52+
53+
context 'when value not given' do
54+
let(:prompt) { double(:prompt) }
55+
it 'prompts for value' do
56+
expect(subject).to receive(:prompt).and_return(prompt)
57+
expect(prompt).to receive(:mask).once.and_return('very-secret')
58+
expect(client).to receive(:put).with('secrets/test-grid/mysql_password', { name: 'mysql_password', value: 'very-secret', upsert: false})
59+
expect{subject.run(['mysql_password'])}.not_to exit_with_error
60+
end
61+
end
62+
end
63+
end
64+
65+
context 'with value parameter' do
66+
it 'sends update request' do
67+
expect(client).to receive(:put).with('secrets/test-grid/mysql_password', { name: 'mysql_password', value: 'secret', upsert: false})
68+
expect{subject.run(['mysql_password', 'secret'])}.not_to exit_with_error
69+
end
70+
71+
context 'when giving --upsert flag' do
72+
it 'sets upsert true' do
73+
expect(client).to receive(:put).with('secrets/test-grid/mysql_password', { name: 'mysql_password', value: 'secret', upsert: true})
74+
subject.run(['-u', 'mysql_password', 'secret'])
75+
end
76+
end
77+
end
78+
end
79+
end
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
require_relative "../../../spec_helper"
2+
require 'kontena/cli/vault/write_command'
3+
4+
describe Kontena::Cli::Vault::WriteCommand do
5+
6+
include RequirementsHelper
7+
include ClientHelpers
8+
9+
let(:subject) { described_class.new(File.basename($0)) }
10+
11+
describe '#execute' do
12+
context 'without value parameter' do
13+
14+
let(:stdin) { double(:stdin) }
15+
16+
before(:each) do
17+
@old_stdin = $stdin
18+
$stdin = stdin
19+
end
20+
21+
after(:each) { $stdin = @old_stdin }
22+
23+
context 'no tty' do
24+
context 'stdin empty' do
25+
it 'returns an error' do
26+
expect(stdin).to receive(:tty?).and_return(false)
27+
expect(stdin).to receive(:eof?).and_return(true)
28+
expect{subject.run(['mysql_password'])}.to exit_with_error.and output(/Missing/).to_stderr
29+
end
30+
end
31+
32+
context 'stdin has a value' do
33+
it 'sends create request' do
34+
expect(stdin).to receive(:tty?).and_return(false)
35+
expect(stdin).to receive(:eof?).and_return(false)
36+
expect(stdin).to receive(:read).and_return('secret')
37+
expect(client).to receive(:post).with('grids/test-grid/secrets', { name: 'mysql_password', value: 'secret'})
38+
expect{subject.run(['mysql_password'])}.not_to exit_with_error
39+
end
40+
end
41+
end
42+
43+
context 'with tty' do
44+
let(:prompt) { double(:prompt) }
45+
it 'prompts value from STDIN' do
46+
expect(stdin).to receive(:tty?).and_return(true)
47+
expect(subject).to receive(:prompt).and_return(prompt)
48+
expect(prompt).to receive(:mask).and_return('secret')
49+
expect(client).to receive(:post).with('grids/test-grid/secrets', { name: 'mysql_password', value: 'secret'})
50+
expect{subject.run(['mysql_password'])}.not_to exit_with_error
51+
end
52+
end
53+
end
54+
55+
it 'sends create request' do
56+
expect(client).to receive(:post).with('grids/test-grid/secrets', { name: 'mysql_password', value: 'secret'})
57+
subject.run(['mysql_password', 'secret'])
58+
end
59+
end
60+
end

0 commit comments

Comments
 (0)