producer-core 0.3.7 → 0.3.8

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 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