producer-core 0.3.0 → 0.3.1

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