producer-core 0.3.7 → 0.3.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/features/cli_usage.feature +6 -1
- data/features/registry.feature +12 -0
- data/features/task_nested_tasks.feature +5 -0
- data/lib/producer/core/cli.rb +38 -24
- data/lib/producer/core/env.rb +3 -1
- data/lib/producer/core/errors.rb +1 -0
- data/lib/producer/core/version.rb +1 -1
- data/lib/producer/core/worker.rb +13 -5
- data/lib/producer/core.rb +1 -0
- data/spec/producer/core/cli_spec.rb +71 -57
- data/spec/producer/core/env_spec.rb +4 -0
- 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: 6a595e50ec520bdedf5e94b927b4dd8080a1acef
|
4
|
+
data.tar.gz: 1c7c1f218276ed668d62c2d31f482e12a2f3ca40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 727921d665f949d90cb092b3c653c698a7f1c82d2d48e1dd8f73adbb9905fd60c3b0348a6bfdc3c1e01b77c35f7843507768c3f18102d3e5e9ae8d16b748b9a6
|
7
|
+
data.tar.gz: b2ece9b39a0be51041fd89f1b526c2d8042a252c3e4961be6cb9c621420c1f5bbccfab531df0885b3fa8f29473c07c32de8f30d46b2a6f7332bdccff2b2f9d73
|
data/features/cli_usage.feature
CHANGED
@@ -6,5 +6,10 @@ Feature: CLI usage
|
|
6
6
|
Then the exit status must be 64
|
7
7
|
And the output must contain exactly:
|
8
8
|
"""
|
9
|
-
Usage: producer [
|
9
|
+
Usage: producer [options] [recipes]
|
10
|
+
|
11
|
+
options:
|
12
|
+
-v, --verbose enable verbose mode
|
13
|
+
-n, --dry-run enable dry run mode
|
14
|
+
-t, --target HOST target host
|
10
15
|
"""
|
data/features/registry.feature
CHANGED
@@ -34,3 +34,15 @@ Feature: key/value registry
|
|
34
34
|
"""
|
35
35
|
When I successfully execute the recipe
|
36
36
|
Then the output must contain "some_value"
|
37
|
+
|
38
|
+
Scenario: `get' keyword should trigger an error when given invalid key
|
39
|
+
Given a recipe with:
|
40
|
+
"""
|
41
|
+
task :some_task do
|
42
|
+
echo get :no_key
|
43
|
+
echo 'after_fail'
|
44
|
+
end
|
45
|
+
"""
|
46
|
+
When I execute the recipe
|
47
|
+
Then the output must not contain "after_fail"
|
48
|
+
And the output must match /\A\w+Error:\s+:no_key/
|
@@ -13,3 +13,8 @@ Feature: nested tasks
|
|
13
13
|
Scenario: applies nested tasks
|
14
14
|
When I successfully execute the recipe
|
15
15
|
Then the output must match /\AOK/
|
16
|
+
|
17
|
+
Scenario: indents logging from nested tasks
|
18
|
+
When I successfully execute the recipe with option -v
|
19
|
+
Then the output must match /^ Task:.+/
|
20
|
+
And the output must match /^ action:.+/
|
data/lib/producer/core/cli.rb
CHANGED
@@ -3,8 +3,7 @@ module Producer
|
|
3
3
|
class CLI
|
4
4
|
ArgumentError = Class.new(::ArgumentError)
|
5
5
|
|
6
|
-
|
7
|
-
USAGE = "Usage: #{File.basename $0} #{OPTIONS_USAGE} recipe_file".freeze
|
6
|
+
USAGE = "Usage: #{File.basename $0} [options] [recipes]".freeze
|
8
7
|
|
9
8
|
EX_USAGE = 64
|
10
9
|
EX_SOFTWARE = 70
|
@@ -15,8 +14,8 @@ module Producer
|
|
15
14
|
begin
|
16
15
|
cli.parse_arguments!
|
17
16
|
cli.run
|
18
|
-
rescue ArgumentError
|
19
|
-
stderr.puts
|
17
|
+
rescue ArgumentError => e
|
18
|
+
stderr.puts e.message
|
20
19
|
exit EX_USAGE
|
21
20
|
rescue RuntimeError => e
|
22
21
|
stderr.puts "#{e.class.name.split('::').last}: #{e.message}"
|
@@ -25,40 +24,55 @@ module Producer
|
|
25
24
|
end
|
26
25
|
end
|
27
26
|
|
28
|
-
attr_reader :arguments, :env
|
27
|
+
attr_reader :arguments, :stdin, :stdout, :stderr, :env
|
29
28
|
|
30
29
|
def initialize(args, stdin: $stdin, stdout: $stdout, stderr: $stderr)
|
31
30
|
@arguments = args
|
32
31
|
@stdin = stdin
|
33
32
|
@stdout = stdout
|
34
|
-
@
|
33
|
+
@stderr = stderr
|
34
|
+
@env = build_env
|
35
35
|
end
|
36
36
|
|
37
37
|
def parse_arguments!
|
38
|
-
@arguments
|
39
|
-
|
40
|
-
when '-v'
|
41
|
-
env.verbose = true
|
42
|
-
when '-n'
|
43
|
-
env.dry_run = true
|
44
|
-
when '-t'
|
45
|
-
env.target = arguments.delete_at i + 1
|
46
|
-
else
|
47
|
-
m << e
|
48
|
-
end
|
49
|
-
m
|
50
|
-
end
|
51
|
-
|
52
|
-
fail ArgumentError unless @arguments.any?
|
38
|
+
option_parser.parse!(@arguments)
|
39
|
+
fail ArgumentError, option_parser if @arguments.empty?
|
53
40
|
end
|
54
41
|
|
55
42
|
def run(worker: Worker.new(@env))
|
56
|
-
worker.process recipe.tasks
|
43
|
+
evaluate_recipes.each { |recipe| worker.process recipe.tasks }
|
57
44
|
@env.cleanup
|
58
45
|
end
|
59
46
|
|
60
|
-
def
|
61
|
-
@
|
47
|
+
def evaluate_recipes
|
48
|
+
@arguments.map { |e| Recipe::FileEvaluator.evaluate(e, @env) }
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def build_env
|
55
|
+
Env.new(input: @stdin, output: @stdout, error_output: @stderr)
|
56
|
+
end
|
57
|
+
|
58
|
+
def option_parser
|
59
|
+
OptionParser.new do |opts|
|
60
|
+
opts.banner = USAGE
|
61
|
+
opts.separator ''
|
62
|
+
opts.separator 'options:'
|
63
|
+
|
64
|
+
opts.on '-v', '--verbose', 'enable verbose mode' do |e|
|
65
|
+
env.verbose = true
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on '-n', '--dry-run', 'enable dry run mode' do |e|
|
69
|
+
env.dry_run = true
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on '-t', '--target HOST', 'target host' do |e|
|
73
|
+
env.target = e
|
74
|
+
end
|
75
|
+
end
|
62
76
|
end
|
63
77
|
end
|
64
78
|
end
|
data/lib/producer/core/env.rb
CHANGED
data/lib/producer/core/errors.rb
CHANGED
data/lib/producer/core/worker.rb
CHANGED
@@ -14,21 +14,29 @@ module Producer
|
|
14
14
|
tasks.each { |t| process_task t }
|
15
15
|
end
|
16
16
|
|
17
|
-
def process_task(task)
|
17
|
+
def process_task(task, indent_level = 0)
|
18
18
|
if task.condition_met?
|
19
|
-
|
19
|
+
log "Task: `#{task}' applying...", indent_level
|
20
20
|
task.actions.each do |e|
|
21
21
|
case e
|
22
|
-
when Task then process_task e
|
22
|
+
when Task then process_task e, indent_level + 2
|
23
23
|
else
|
24
|
-
|
24
|
+
log " action: #{e}", indent_level
|
25
25
|
e.apply unless @env.dry_run?
|
26
26
|
end
|
27
27
|
end
|
28
28
|
else
|
29
|
-
|
29
|
+
log "Task: `#{task}' skipped", indent_level
|
30
30
|
end
|
31
31
|
end
|
32
|
+
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def log(message, indent_level)
|
37
|
+
message = [' ' * indent_level, message].join
|
38
|
+
@env.log message
|
39
|
+
end
|
32
40
|
end
|
33
41
|
end
|
34
42
|
end
|
data/lib/producer/core.rb
CHANGED
@@ -5,28 +5,42 @@ module Producer::Core
|
|
5
5
|
include ExitHelpers
|
6
6
|
include FixturesHelpers
|
7
7
|
|
8
|
-
let(:recipe_file) { fixture_path_for 'recipes/some_recipe.rb' }
|
9
8
|
let(:options) { [] }
|
9
|
+
let(:recipe_file) { fixture_path_for 'recipes/some_recipe.rb' }
|
10
10
|
let(:arguments) { [*options, recipe_file] }
|
11
11
|
|
12
12
|
subject(:cli) { described_class.new(arguments) }
|
13
13
|
|
14
14
|
describe '.run!' do
|
15
|
-
|
16
|
-
|
15
|
+
subject(:run!) { described_class.run! arguments }
|
16
|
+
|
17
|
+
it 'builds a new CLI instance with given arguments' do
|
18
|
+
expect(described_class)
|
19
|
+
.to receive(:new).with(arguments, anything).and_call_original
|
20
|
+
run!
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'parses new CLI instance arguments' do
|
24
|
+
expect_any_instance_of(described_class).to receive :parse_arguments!
|
25
|
+
run!
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'runs the new CLI instance' do
|
29
|
+
expect_any_instance_of(described_class).to receive :run
|
30
|
+
run!
|
31
|
+
end
|
17
32
|
|
18
|
-
context 'when
|
33
|
+
context 'when no recipe is given' do
|
19
34
|
let(:arguments) { [] }
|
20
35
|
|
21
36
|
it 'exits with a return status of 64' do
|
22
|
-
expect { run!
|
23
|
-
expect(e.status).to eq 64
|
24
|
-
end
|
37
|
+
expect { described_class.run! arguments, stderr: StringIO.new }
|
38
|
+
.to raise_error(SystemExit) { |e| expect(e.status).to eq 64 }
|
25
39
|
end
|
26
40
|
|
27
41
|
it 'prints the usage on the error stream' do
|
28
|
-
trap_exit { run! }
|
29
|
-
|
42
|
+
expect { trap_exit { run! } }
|
43
|
+
.to output(/\AUsage: .+/).to_stderr
|
30
44
|
end
|
31
45
|
end
|
32
46
|
|
@@ -34,92 +48,95 @@ module Producer::Core
|
|
34
48
|
let(:recipe_file) { fixture_path_for 'recipes/raise.rb' }
|
35
49
|
|
36
50
|
it 'exits with a return status of 70' do
|
37
|
-
expect { run!
|
38
|
-
expect(e.status).to eq 70
|
39
|
-
end
|
51
|
+
expect { described_class.run! arguments, stderr: StringIO.new }
|
52
|
+
.to raise_error(SystemExit) { |e| expect(e.status).to eq 70 }
|
40
53
|
end
|
41
54
|
|
42
55
|
it 'prints exception name and message and the error stream' do
|
43
|
-
trap_exit { run! }
|
44
|
-
|
56
|
+
expect { trap_exit { run! } }
|
57
|
+
.to output("RemoteCommandExecutionError: false\n").to_stderr
|
45
58
|
end
|
46
59
|
end
|
47
60
|
end
|
48
61
|
|
49
|
-
describe '#
|
50
|
-
|
51
|
-
let(:stdout) { StringIO.new }
|
52
|
-
let(:stderr) { StringIO.new }
|
53
|
-
|
54
|
-
subject(:cli) { described_class.new(arguments,
|
55
|
-
stdin: stdin, stdout: stdout, stderr: stderr) }
|
56
|
-
|
57
|
-
it 'returns an env' do
|
62
|
+
describe '#initialize' do
|
63
|
+
it 'assigns an env' do
|
58
64
|
expect(cli.env).to be_an Env
|
59
65
|
end
|
60
66
|
|
61
67
|
it 'assigns CLI stdin as the env input' do
|
62
|
-
expect(cli.env.input).to be stdin
|
68
|
+
expect(cli.env.input).to be cli.stdin
|
63
69
|
end
|
64
70
|
|
65
71
|
it 'assigns CLI stdout as the env output' do
|
66
|
-
expect(cli.env.output).to be stdout
|
72
|
+
expect(cli.env.output).to be cli.stdout
|
67
73
|
end
|
68
74
|
|
69
75
|
it 'assigns CLI stderr as the env error output' do
|
70
|
-
expect(cli.env.error_output).to be stderr
|
76
|
+
expect(cli.env.error_output).to be cli.stderr
|
71
77
|
end
|
72
78
|
end
|
73
79
|
|
74
80
|
describe '#parse_arguments!' do
|
75
|
-
|
76
|
-
|
81
|
+
let(:options) { %w[-v -t some_host.example] }
|
82
|
+
|
83
|
+
it 'removes options from arguments' do
|
84
|
+
cli.parse_arguments!
|
85
|
+
expect(cli.arguments).to eq [recipe_file]
|
86
|
+
end
|
77
87
|
|
78
|
-
|
88
|
+
context 'with verbose option' do
|
89
|
+
let(:options) { %w[-v] }
|
79
90
|
|
80
|
-
it '
|
81
|
-
|
91
|
+
it 'enables env verbose mode' do
|
92
|
+
cli.parse_arguments!
|
93
|
+
expect(cli.env).to be_verbose
|
82
94
|
end
|
95
|
+
end
|
83
96
|
|
84
|
-
|
85
|
-
|
97
|
+
context 'with dry run option' do
|
98
|
+
let(:options) { %w[-n] }
|
86
99
|
|
87
|
-
|
88
|
-
|
89
|
-
|
100
|
+
it 'enables env dry run mode' do
|
101
|
+
cli.parse_arguments!
|
102
|
+
expect(cli.env).to be_dry_run
|
90
103
|
end
|
104
|
+
end
|
91
105
|
|
92
|
-
|
93
|
-
|
106
|
+
context 'with target option' do
|
107
|
+
let(:options) { %w[-t some_host.example] }
|
94
108
|
|
95
|
-
|
96
|
-
|
97
|
-
|
109
|
+
it 'assigns the given target to the env' do
|
110
|
+
cli.parse_arguments!
|
111
|
+
expect(cli.env.target).to eq 'some_host.example'
|
98
112
|
end
|
113
|
+
end
|
99
114
|
|
100
|
-
|
101
|
-
|
115
|
+
context 'with combined options' do
|
116
|
+
let(:options) { %w[-vn]}
|
102
117
|
|
103
|
-
|
104
|
-
|
105
|
-
|
118
|
+
it 'handles combined options' do
|
119
|
+
cli.parse_arguments!
|
120
|
+
expect(cli.env).to be_verbose.and be_dry_run
|
106
121
|
end
|
107
122
|
end
|
108
123
|
|
109
|
-
context '
|
124
|
+
context 'when no arguments remains after parsing' do
|
110
125
|
let(:arguments) { [] }
|
111
126
|
|
112
|
-
it 'raises
|
113
|
-
expect { cli.parse_arguments! }
|
127
|
+
it 'raises an error' do
|
128
|
+
expect { cli.parse_arguments! }
|
129
|
+
.to raise_error described_class::ArgumentError
|
114
130
|
end
|
115
131
|
end
|
116
132
|
end
|
117
133
|
|
118
134
|
describe '#run' do
|
119
|
-
it 'processes
|
135
|
+
it 'processes recipes tasks with a worker' do
|
120
136
|
worker = instance_spy Worker
|
121
137
|
cli.run worker: worker
|
122
|
-
expect(worker).to have_received(:process)
|
138
|
+
expect(worker).to have_received(:process)
|
139
|
+
.with all be_an_instance_of Task
|
123
140
|
end
|
124
141
|
|
125
142
|
it 'cleans up the env' do
|
@@ -128,12 +145,9 @@ module Producer::Core
|
|
128
145
|
end
|
129
146
|
end
|
130
147
|
|
131
|
-
describe '#
|
132
|
-
it 'returns the evaluated
|
133
|
-
expect(cli.
|
134
|
-
an_object_having_attributes(name: :some_task),
|
135
|
-
an_object_having_attributes(name: :another_task)
|
136
|
-
]
|
148
|
+
describe '#evaluate_recipes' do
|
149
|
+
it 'returns the evaluated recipes' do
|
150
|
+
expect(cli.evaluate_recipes).to all be_an_instance_of Recipe
|
137
151
|
end
|
138
152
|
end
|
139
153
|
end
|
@@ -107,6 +107,10 @@ module Producer::Core
|
|
107
107
|
it 'returns the value indexed by given key from the registry' do
|
108
108
|
expect(env[:some_key]).to eq :some_value
|
109
109
|
end
|
110
|
+
|
111
|
+
it 'raises an error when given invalid key' do
|
112
|
+
expect { env[:no_key] }.to raise_error RegistryKeyError
|
113
|
+
end
|
110
114
|
end
|
111
115
|
|
112
116
|
describe '#[]=' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: producer-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thibault Jouan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-ssh
|