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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b765ca3e62a5f6d52c70023568a7ff479eb90fb4
4
- data.tar.gz: c742add987d14bc54d14933bf560d236e89f3be0
3
+ metadata.gz: 6a595e50ec520bdedf5e94b927b4dd8080a1acef
4
+ data.tar.gz: 1c7c1f218276ed668d62c2d31f482e12a2f3ca40
5
5
  SHA512:
6
- metadata.gz: a59aea6fc1ea7fc7ce56b0ce33adbd53095d895945f2a036a2057b2b9f9b5460047d92b94a5f20866b20756d39511ab38e032dca792f85e5b786d73995028792
7
- data.tar.gz: dd3ef0d2f879a4d9e1dd4b1bd3cb2747c390070f9d7544db077d044aa76dda67e11883ff08b45e79042fba471a6ca2bf351e625f7ee67457b4d9272f74f0b959
6
+ metadata.gz: 727921d665f949d90cb092b3c653c698a7f1c82d2d48e1dd8f73adbb9905fd60c3b0348a6bfdc3c1e01b77c35f7843507768c3f18102d3e5e9ae8d16b748b9a6
7
+ data.tar.gz: b2ece9b39a0be51041fd89f1b526c2d8042a252c3e4961be6cb9c621420c1f5bbccfab531df0885b3fa8f29473c07c32de8f30d46b2a6f7332bdccff2b2f9d73
@@ -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 [-v] [-n] [-t host.example] recipe_file
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
  """
@@ -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:.+/
@@ -3,8 +3,7 @@ module Producer
3
3
  class CLI
4
4
  ArgumentError = Class.new(::ArgumentError)
5
5
 
6
- OPTIONS_USAGE = '[-v] [-n] [-t host.example]'.freeze
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 USAGE
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
- @env = Env.new(input: stdin, output: stdout, error_output: stderr)
33
+ @stderr = stderr
34
+ @env = build_env
35
35
  end
36
36
 
37
37
  def parse_arguments!
38
- @arguments = arguments.each_with_index.inject([]) do |m, (e, i)|
39
- case e
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 recipe
61
- @recipe ||= Recipe::FileEvaluator.evaluate(@arguments.first, env)
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
@@ -18,7 +18,9 @@ module Producer
18
18
  end
19
19
 
20
20
  def [](key)
21
- @registry[key]
21
+ @registry.fetch key
22
+ rescue KeyError
23
+ fail RegistryKeyError, key.inspect
22
24
  end
23
25
  alias get []
24
26
 
@@ -4,5 +4,6 @@ module Producer
4
4
  RuntimeError = Class.new(RuntimeError)
5
5
  ConditionNotMetError = Class.new(Error)
6
6
  RemoteCommandExecutionError = Class.new(RuntimeError)
7
+ RegistryKeyError = Class.new(RuntimeError)
7
8
  end
8
9
  end
@@ -1,5 +1,5 @@
1
1
  module Producer
2
2
  module Core
3
- VERSION = '0.3.7'.freeze
3
+ VERSION = '0.3.8'.freeze
4
4
  end
5
5
  end
@@ -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
- @env.log "Task: `#{task}' applying..."
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
- @env.log " action: #{e}"
24
+ log " action: #{e}", indent_level
25
25
  e.apply unless @env.dry_run?
26
26
  end
27
27
  end
28
28
  else
29
- @env.log "Task: `#{task}' skipped"
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
@@ -1,5 +1,6 @@
1
1
  require 'etc'
2
2
  require 'forwardable'
3
+ require 'optparse'
3
4
  require 'pathname'
4
5
 
5
6
  require 'net/ssh'
@@ -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
- let(:stderr) { StringIO.new }
16
- subject(:run!) { described_class.run! arguments, stderr: stderr }
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 given arguments are invalid' do
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! }.to raise_error(SystemExit) do |e|
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
- expect(stderr.string).to match /\AUsage: .+/
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! }.to raise_error(SystemExit) do |e|
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
- expect(stderr.string).to eq "RemoteCommandExecutionError: false\n"
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 '#env' do
50
- let(:stdin) { StringIO.new }
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
- context 'with options' do
76
- let(:options) { %w[-v -t some_host.example] }
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
- before { cli.parse_arguments! }
88
+ context 'with verbose option' do
89
+ let(:options) { %w[-v] }
79
90
 
80
- it 'removes options from arguments' do
81
- expect(cli.arguments).to eq [recipe_file]
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
- context 'verbose' do
85
- let(:options) { %w[-v] }
97
+ context 'with dry run option' do
98
+ let(:options) { %w[-n] }
86
99
 
87
- it 'enables env verbose mode' do
88
- expect(cli.env).to be_verbose
89
- end
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
- context 'dry run' do
93
- let(:options) { %w[-n] }
106
+ context 'with target option' do
107
+ let(:options) { %w[-t some_host.example] }
94
108
 
95
- it 'enables env dry run mode' do
96
- expect(cli.env).to be_dry_run
97
- end
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
- context 'target' do
101
- let(:options) { %w[-t some_host.example] }
115
+ context 'with combined options' do
116
+ let(:options) { %w[-vn]}
102
117
 
103
- it 'assigns the given target to the env' do
104
- expect(cli.env.target).to eq 'some_host.example'
105
- end
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 'without arguments' do
124
+ context 'when no arguments remains after parsing' do
110
125
  let(:arguments) { [] }
111
126
 
112
- it 'raises the argument error exception' do
113
- expect { cli.parse_arguments! }.to raise_error described_class::ArgumentError
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 recipe tasks with a worker' do
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).with cli.recipe.tasks
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 '#recipe' do
132
- it 'returns the evaluated recipe' do
133
- expect(cli.recipe.tasks).to match [
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.7
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-25 00:00:00.000000000 Z
11
+ date: 2014-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-ssh