producer-core 0.3.0 → 0.3.1

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: faa828c272b7b1283b43c4484b3058c3150c1406
4
- data.tar.gz: a2cd0da5eab61ceaadfb8970f2db93ed9fa00c10
3
+ metadata.gz: 1a3adf6101a1558efb3ddeaf66acf177c1d60cc1
4
+ data.tar.gz: 0013660606738921e69b6cda3198ca828bcf4b8b
5
5
  SHA512:
6
- metadata.gz: 9f1f5de22346bd0ba148b9c27a60f3d4bb14320fc14fdbdd4422300b526f6bcf904ebc99909a1d31994a27ec06176eca91a17b78be82b8e8a644c171b5032fef
7
- data.tar.gz: e3eb1cdb96edbc33280530a0036704860fdb160dbd71c879e97deecff92a53596d6dc2cc2c25b049d157933545c21517bdfce26e8b544e4b54002edc7001090d
6
+ metadata.gz: ca676ef9b26f0fd6ff8b4a0b88e1cc7caec24da357b8f40280692d19991b8e64b7e7b4c6697a6d5e5debe9bbb3ef4b84ce2cc03ac30c9145a1aceb06a9df04fb
7
+ data.tar.gz: b8606bd4d3cd7c3507e8ea7e87ad4199a8a5538b95ca2438deff520d40244382b350bfc51b3d1a5fda11ea2ca90f55d3b206cac37f45de059ee2a8632d18b2f1
@@ -1,40 +1,47 @@
1
- @sshd
2
1
  Feature: negated test prefix (no_)
3
2
 
4
3
  Scenario: prefixed test fails when non-prefixed test is successful
5
4
  Given a recipe with:
6
5
  """
6
+ test_macro :even? do |n|
7
+ n % 2 == 0
8
+ end
9
+
7
10
  task :successful_test do
8
- condition { env? :shell }
11
+ condition { even? 4 }
9
12
 
10
13
  echo 'successful_test'
11
14
  end
12
15
 
13
16
  task :negated_test do
14
- condition { no_env? :shell }
17
+ condition { no_even? 4 }
15
18
 
16
19
  echo 'negated_test'
17
20
  end
18
21
  """
19
- When I successfully execute the recipe on remote target
22
+ When I successfully execute the recipe
20
23
  Then the output must contain "successful_test"
21
24
  And the output must not contain "negated_test"
22
25
 
23
- Scenario: prefixed test fails when non-prefixed test is failing
26
+ Scenario: prefixed test succeed when non-prefixed test is failing
24
27
  Given a recipe with:
25
28
  """
29
+ test_macro :even? do |n|
30
+ n % 2 == 0
31
+ end
32
+
26
33
  task :failing_test do
27
- condition { env? :inexistent_var }
34
+ condition { even? 5 }
28
35
 
29
36
  echo 'failing_test'
30
37
  end
31
38
 
32
39
  task :negated_test do
33
- condition { no_env? :inexistent_var }
40
+ condition { no_even? 5 }
34
41
 
35
42
  echo 'negated_test'
36
43
  end
37
44
  """
38
- When I successfully execute the recipe on remote target
45
+ When I successfully execute the recipe
39
46
  Then the output must not contain "failing_test"
40
47
  And the output must contain "negated_test"
@@ -1,4 +1,4 @@
1
- Feature: `ask' recipe keyword
1
+ Feature: `ask' task keyword
2
2
 
3
3
  @exec
4
4
  Scenario: prompts user with a list of choices on standard output
@@ -58,7 +58,7 @@ module Producer
58
58
  end
59
59
 
60
60
  def recipe
61
- @recipe ||= Recipe.evaluate_from_file(@arguments.first, env)
61
+ @recipe ||= Recipe::FileEvaluator.evaluate(@arguments.first, env)
62
62
  end
63
63
  end
64
64
  end
@@ -2,16 +2,45 @@ module Producer
2
2
  module Core
3
3
  class Condition
4
4
  class << self
5
+ def define_test(keyword, test)
6
+ {
7
+ keyword => false,
8
+ "no_#{keyword}" => true
9
+ }.each do |kw, negated|
10
+ define_method(kw) do |*args|
11
+ if test.respond_to? :call
12
+ args = [test, *args]
13
+ klass = Tests::ConditionTest
14
+ else
15
+ klass = test
16
+ end
17
+ t = klass.new(@env, *args, negated: negated)
18
+ @tests << t
19
+ end
20
+ end
21
+ end
22
+
5
23
  def evaluate(env, *args, &block)
6
- dsl = DSL.new(env, &block)
7
- return_value = dsl.evaluate *args
8
- Condition.new(dsl.tests, return_value)
24
+ new.tap do |o|
25
+ o.instance_eval { @env = env }
26
+ return_value = o.instance_exec *args, &block
27
+ o.instance_eval { @return_value = return_value }
28
+ end
9
29
  end
10
30
  end
11
31
 
32
+ define_test :`, Tests::ShellCommandStatus
33
+ define_test :sh, Tests::ShellCommandStatus
34
+ define_test :file_contains, Tests::FileContains
35
+ define_test :file_eq, Tests::FileEq
36
+ define_test :env?, Tests::HasEnv
37
+ define_test :executable?, Tests::HasExecutable
38
+ define_test :dir?, Tests::HasDir
39
+ define_test :file?, Tests::HasFile
40
+
12
41
  attr_reader :tests, :return_value
13
42
 
14
- def initialize(tests, return_value = nil)
43
+ def initialize(tests = [], return_value = nil)
15
44
  @tests = tests
16
45
  @return_value = return_value
17
46
  end
@@ -0,0 +1,14 @@
1
+ module Producer
2
+ module Core
3
+ class Recipe
4
+ class FileEvaluator
5
+ class << self
6
+ def evaluate(file_path, env)
7
+ content = File.read(file_path)
8
+ Recipe.new(env).tap { |o| o.instance_eval content, file_path }
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -2,16 +2,46 @@ module Producer
2
2
  module Core
3
3
  class Recipe
4
4
  class << self
5
- def evaluate_from_file(filepath, env)
6
- dsl = DSL.new(env, File.read(filepath)).evaluate
7
- Recipe.new(dsl.tasks)
5
+ def define_macro(name, block)
6
+ define_method(name) { |*args| task name, *args, &block }
8
7
  end
9
8
  end
10
9
 
11
- attr_accessor :tasks
10
+ attr_reader :env, :tasks
12
11
 
13
- def initialize(tasks = [])
14
- @tasks = tasks
12
+ def initialize(env)
13
+ @env = env
14
+ @tasks = []
15
+ end
16
+
17
+ def source(filepath)
18
+ instance_eval File.read("./#{filepath}.rb"), "#{filepath}.rb"
19
+ end
20
+
21
+ def target(hostname)
22
+ env.target ||= hostname
23
+ end
24
+
25
+ def task(name, *args, &block)
26
+ @tasks << Task.evaluate(env, name, *args, &block)
27
+ end
28
+
29
+ def macro(name, &block)
30
+ define_singleton_method(name) do |*args|
31
+ task("#{name}", *args, &block)
32
+ end
33
+ end
34
+
35
+ def test_macro(name, &block)
36
+ Condition.define_test(name, block)
37
+ end
38
+
39
+ def set(key, value)
40
+ env[key] = value
41
+ end
42
+
43
+ def get(key)
44
+ env[key]
15
45
  end
16
46
  end
17
47
  end
@@ -2,28 +2,54 @@ module Producer
2
2
  module Core
3
3
  class Task
4
4
  class << self
5
- def evaluate(name, env, *args, &block)
6
- dsl = DSL.new(env, &block)
7
- dsl.evaluate(*args)
8
- Task.new(name, dsl.actions, dsl.condition)
5
+ def define_action(keyword, klass)
6
+ define_method(keyword) do |*args|
7
+ @actions << klass.new(@env, *args)
8
+ end
9
+ end
10
+
11
+ def evaluate(env, name, *args, &block)
12
+ new(env, name).tap { |o| o.instance_exec *args, &block }
9
13
  end
10
14
  end
11
15
 
16
+ define_action :echo, Actions::Echo
17
+ define_action :sh, Actions::ShellCommand
18
+
19
+ define_action :mkdir, Actions::Mkdir
20
+ define_action :file_append, Actions::FileAppend
21
+ define_action :file_replace_content, Actions::FileReplaceContent
22
+ define_action :file_write, Actions::FileWriter
23
+
12
24
  attr_reader :name, :actions, :condition
13
25
 
14
- def initialize(name, actions = [], condition = true)
26
+ def initialize(env, name, actions = [], condition = true)
27
+ @env = env
15
28
  @name = name
16
29
  @actions = actions
17
30
  @condition = condition
18
31
  end
19
32
 
20
33
  def to_s
21
- name.to_s
34
+ @name.to_s
22
35
  end
23
36
 
24
37
  def condition_met?
25
38
  !!@condition
26
39
  end
40
+
41
+ def condition(&block)
42
+ @condition = Condition.evaluate(@env, &block) if block
43
+ @condition
44
+ end
45
+
46
+ def ask(question, choices, prompter: Prompter.new(@env.input, @env.output))
47
+ prompter.prompt(question, choices)
48
+ end
49
+
50
+ def get(key)
51
+ @env[key]
52
+ end
27
53
  end
28
54
  end
29
55
  end
@@ -1,5 +1,5 @@
1
1
  module Producer
2
2
  module Core
3
- VERSION = '0.3.0'.freeze
3
+ VERSION = '0.3.1'.freeze
4
4
  end
5
5
  end
@@ -4,27 +4,25 @@ module Producer
4
4
  DRY_RUN_WARNING =
5
5
  'running in dry run mode, actions will NOT be applied'.freeze
6
6
 
7
- attr_accessor :env
8
-
9
7
  def initialize(env)
10
8
  @env = env
11
9
  end
12
10
 
13
11
  def process(tasks)
14
- env.log DRY_RUN_WARNING, :warn if env.dry_run?
12
+ @env.log DRY_RUN_WARNING, :warn if @env.dry_run?
15
13
 
16
14
  tasks.each { |t| process_task t }
17
15
  end
18
16
 
19
17
  def process_task(task)
20
18
  if task.condition_met?
21
- env.log "Task: `#{task}' applying..."
19
+ @env.log "Task: `#{task}' applying..."
22
20
  task.actions.each do |e|
23
- env.log " action: #{e}"
24
- e.apply unless env.dry_run?
21
+ @env.log " action: #{e}"
22
+ e.apply unless @env.dry_run?
25
23
  end
26
24
  else
27
- env.log "Task: `#{task}' skipped"
25
+ @env.log "Task: `#{task}' skipped"
28
26
  end
29
27
  end
30
28
  end
data/lib/producer/core.rb CHANGED
@@ -14,7 +14,7 @@ require 'producer/core/actions/file_append'
14
14
  require 'producer/core/actions/file_replace_content'
15
15
  require 'producer/core/actions/file_writer'
16
16
 
17
- # condition tests (need to be defined before the condition DSL)
17
+ # condition tests
18
18
  require 'producer/core/test'
19
19
  require 'producer/core/tests/condition_test'
20
20
  require 'producer/core/tests/file_contains'
@@ -27,17 +27,15 @@ require 'producer/core/tests/shell_command_status'
27
27
 
28
28
  require 'producer/core/cli'
29
29
  require 'producer/core/condition'
30
- require 'producer/core/condition/dsl'
31
30
  require 'producer/core/env'
32
31
  require 'producer/core/errors'
33
32
  require 'producer/core/logger_formatter'
34
33
  require 'producer/core/prompter'
35
34
  require 'producer/core/recipe'
36
- require 'producer/core/recipe/dsl'
35
+ require 'producer/core/recipe/file_evaluator'
37
36
  require 'producer/core/remote'
38
37
  require 'producer/core/remote/environment'
39
38
  require 'producer/core/remote/fs'
40
39
  require 'producer/core/task'
41
- require 'producer/core/task/dsl'
42
40
  require 'producer/core/version'
43
41
  require 'producer/core/worker'
@@ -2,45 +2,128 @@ require 'spec_helper'
2
2
 
3
3
  module Producer::Core
4
4
  describe Condition do
5
- let(:test_ok) { double 'test', pass?: true }
6
- let(:test_ko) { double 'test', pass?: false }
7
- let(:tests) { [test_ok, test_ko] }
8
- subject(:condition) { Condition.new(tests) }
5
+ subject(:condition) { described_class.new }
6
+
7
+ %w[
8
+ `
9
+ sh
10
+ file_contains
11
+ file_eq
12
+ dir?
13
+ env?
14
+ executable?
15
+ file?
16
+ ].each do |test|
17
+ it "has `#{test}' test defined" do
18
+ expect(condition).to respond_to test.to_sym
19
+ end
20
+ end
21
+
22
+ describe '.define_test' do
23
+ let(:some_test) { Test }
24
+
25
+ before { described_class.define_test(:some_test, some_test) }
26
+
27
+ it 'defines a new test keyword' do
28
+ expect(condition).to respond_to :some_test
29
+ end
30
+
31
+ it 'defines the negated test' do
32
+ expect(condition).to respond_to :no_some_test
33
+ end
34
+
35
+ context 'when a test keyword is called' do
36
+ it 'registers the test' do
37
+ expect { condition.some_test }.to change { condition.tests.count }.by 1
38
+ end
39
+
40
+ it 'registers the test with assigned env' do
41
+ env = double 'env'
42
+ condition.instance_eval { @env = env }
43
+ condition.some_test
44
+ expect(condition.tests.last.env).to be env
45
+ end
46
+
47
+ it 'registers the test with given arguments' do
48
+ condition.some_test :foo, :bar
49
+ expect(condition.tests.last.arguments).to eq %i[foo bar]
50
+ end
51
+
52
+ context 'when given test is callable' do
53
+ let(:some_test) { proc { } }
54
+
55
+ before { condition.some_test }
56
+
57
+ it 'registers a condition test' do
58
+ expect(condition.tests.last).to be_a Tests::ConditionTest
59
+ end
60
+
61
+ it 'registers the test with given block' do
62
+ expect(condition.tests.last.condition_block).to be some_test
63
+ end
64
+
65
+ it 'registers the test with given arguments' do
66
+ condition.some_test :foo, :bar
67
+ expect(condition.tests.last.condition_args).to eq %i[foo bar]
68
+ end
69
+ end
70
+ end
71
+
72
+ context 'when a negated test keyword is called' do
73
+ it 'registers a negated test' do
74
+ condition.no_some_test
75
+ expect(condition.tests.last).to be_negated
76
+ end
77
+ end
78
+ end
9
79
 
10
80
  describe '.evaluate' do
11
- let(:env) { double 'env' }
12
- let(:block) { proc { some_test; :some_return_value } }
13
- let(:some_test_class) { Class.new(Test) }
14
- subject(:condition) { described_class.evaluate(env, &block) }
81
+ let(:env) { double 'env' }
82
+ let(:code) { proc { some_test; :some_return_value } }
83
+ let(:some_test) { Class.new(Test) }
84
+ let(:arguments) { [] }
85
+ subject(:condition) { described_class.evaluate(env, *arguments, &code) }
15
86
 
16
- before { Condition::DSL.define_test(:some_test, some_test_class) }
87
+ before { described_class.define_test(:some_test, some_test) }
17
88
 
18
89
  it 'returns an evaluated condition' do
90
+ expect(condition).to be_a Condition
91
+ end
92
+
93
+ it 'evaluates the condition tests' do
19
94
  expect(condition.tests.first).to be_a Test
95
+ end
96
+
97
+ it 'evaluates the condition return value' do
20
98
  expect(condition.return_value).to eq :some_return_value
21
99
  end
100
+
101
+ context 'when arguments are given' do
102
+ let(:code) { proc { |a, b| throw a } }
103
+ let(:arguments) { %i[foo bar] }
104
+
105
+ it 'passes arguments as block parameters' do
106
+ expect { condition }
107
+ .to throw_symbol :foo
108
+ end
109
+ end
22
110
  end
23
111
 
24
112
  describe '#initialize' do
25
- it 'assigns the tests' do
26
- expect(condition.tests).to eq tests
113
+ it 'assigns no tests' do
114
+ expect(condition.tests).to be_empty
27
115
  end
28
116
 
29
- it 'assigns nil as a default return value' do
117
+ it 'assigns the return value as nil' do
30
118
  expect(condition.return_value).to be nil
31
119
  end
32
-
33
- context 'when a return value is given as argument' do
34
- let(:return_value) { :some_return_value }
35
- subject(:condition) { described_class.new(tests, return_value) }
36
-
37
- it 'assigns the return value' do
38
- expect(condition.return_value).to eq return_value
39
- end
40
- end
41
120
  end
42
121
 
43
122
  describe '#met?' do
123
+ let(:test_ok) { instance_spy Test, pass?: true }
124
+ let(:test_ko) { instance_spy Test, pass?: false }
125
+ subject(:condition) { described_class.new(tests) }
126
+
44
127
  context 'when all tests are successful' do
45
128
  let(:tests) { [test_ok, test_ok] }
46
129
 
@@ -58,6 +141,7 @@ module Producer::Core
58
141
  end
59
142
 
60
143
  context 'when there are no test' do
144
+ let(:tests) { [] }
61
145
  subject(:condition) { described_class.new([], return_value) }
62
146
 
63
147
  context 'and return value is truthy' do
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ module Producer::Core
4
+ class Recipe
5
+ describe FileEvaluator do
6
+ include FixturesHelpers
7
+
8
+ describe '.evaluate' do
9
+ let(:env) { Env.new }
10
+ let(:file_path) { fixture_path_for 'recipes/some_recipe.rb' }
11
+ subject(:recipe) { described_class.evaluate(file_path, env) }
12
+
13
+ it 'returns an evaluated recipe' do
14
+ expect(recipe.tasks).to match [
15
+ an_object_having_attributes(name: :some_task),
16
+ an_object_having_attributes(name: :another_task)
17
+ ]
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -4,33 +4,92 @@ module Producer::Core
4
4
  describe Recipe do
5
5
  include FixturesHelpers
6
6
 
7
- subject(:recipe) { Recipe.new }
7
+ let(:env) { Env.new }
8
+ subject(:recipe) { described_class.new(env) }
8
9
 
9
- describe '.evaluate_from_file' do
10
- let(:env) { double 'env' }
11
- let(:filepath) { fixture_path_for 'recipes/some_recipe.rb' }
12
- subject(:recipe) { Recipe.evaluate_from_file(filepath, env) }
10
+ describe '#initialize' do
11
+ it 'assigns no task' do
12
+ expect(recipe.tasks).to be_empty
13
+ end
14
+ end
15
+
16
+ describe '#source' do
17
+ let(:filepath) { fixture_path_for 'recipes/throw' }
13
18
 
14
- it 'returns an evaluated recipe' do
15
- expect(recipe.tasks.map(&:name)).to eq [:some_task, :another_task]
19
+ it 'sources the recipe given as argument' do
20
+ expect { recipe.source filepath }.to throw_symbol :recipe_code
16
21
  end
17
22
  end
18
23
 
19
- describe '#initialize' do
20
- context 'without arguments' do
21
- it 'assigns no task' do
22
- expect(recipe.tasks).to be_empty
24
+ describe '#target' do
25
+ let(:host) { 'some_host.example' }
26
+
27
+ context 'when env has no assigned target' do
28
+ it 'registers the target host in the env' do
29
+ recipe.target host
30
+ expect(env.target).to eq host
23
31
  end
24
32
  end
25
33
 
26
- context 'when tasks are given as argument' do
27
- let(:tasks) { [double('task')] }
28
- subject(:recipe) { Recipe.new(tasks) }
34
+ context 'when env has an assigned target' do
35
+ before { env.target = 'already_assigned_host.example' }
29
36
 
30
- it 'assigns the tasks' do
31
- expect(recipe.tasks).to eq tasks
37
+ it 'does not change env target' do
38
+ expect { recipe.target host }.not_to change { env.target }
32
39
  end
33
40
  end
34
41
  end
42
+
43
+ describe '#task' do
44
+ it 'registers a new evaluated task' do
45
+ expect { recipe.task(:some_task) { :some_task_code } }
46
+ .to change { recipe.tasks.count }.by 1
47
+ end
48
+ end
49
+
50
+ describe '#macro' do
51
+ it 'defines the new recipe keyword' do
52
+ recipe.macro :hello
53
+ expect(recipe).to respond_to(:hello)
54
+ end
55
+
56
+ context 'when a defined macro is called' do
57
+ before { recipe.macro(:hello) { :some_macro_code } }
58
+
59
+ it 'registers the new task' do
60
+ expect { recipe.hello }.to change { recipe.tasks.count }.by 1
61
+ end
62
+ end
63
+
64
+ context 'when a defined macro is called with arguments' do
65
+ before { recipe.macro(:hello) { |a, b| echo a, b } }
66
+
67
+ it 'evaluates task code with given arguments' do
68
+ recipe.hello :foo, :bar
69
+ expect(recipe.tasks.first.actions.first.arguments).to eq %i[foo bar]
70
+ end
71
+ end
72
+ end
73
+
74
+ describe '#test_macro' do
75
+ it 'defines the new test' do
76
+ recipe.test_macro(:some_test) { }
77
+ expect(Condition.new).to respond_to :some_test
78
+ end
79
+ end
80
+
81
+ describe '#set' do
82
+ it 'registers a key/value pair in env registry' do
83
+ recipe.set :some_key, :some_value
84
+ expect(env[:some_key]).to eq :some_value
85
+ end
86
+ end
87
+
88
+ describe '#get' do
89
+ it 'fetches a value from the registry at given index' do
90
+ recipe.set :some_key, :some_value
91
+ expect(recipe.get :some_key).to eq :some_value
92
+ end
93
+ end
35
94
  end
36
95
  end