producer-core 0.1.0
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.
- data/.gitignore +3 -0
- data/Gemfile +5 -0
- data/Guardfile +11 -0
- data/LICENSE +30 -0
- data/Rakefile +13 -0
- data/bin/producer +5 -0
- data/features/actions/echo.feature +12 -0
- data/features/actions/sh.feature +39 -0
- data/features/cli/usage.feature +9 -0
- data/features/recipes/env.feature +9 -0
- data/features/recipes/evaluation.feature +10 -0
- data/features/recipes/source.feature +16 -0
- data/features/recipes/target.feature +12 -0
- data/features/steps/recipe_steps.rb +7 -0
- data/features/support/env.rb +0 -0
- data/features/support/env_aruba.rb +16 -0
- data/features/support/env_cucumber-doc_string.rb +23 -0
- data/features/support/ssh.rb +106 -0
- data/features/tasks/condition.feature +14 -0
- data/features/tasks/evaluation.feature +12 -0
- data/features/tests/has_env.feature +32 -0
- data/lib/producer/core/action.rb +12 -0
- data/lib/producer/core/actions/echo.rb +11 -0
- data/lib/producer/core/actions/shell_command.rb +11 -0
- data/lib/producer/core/cli.rb +43 -0
- data/lib/producer/core/condition/dsl.rb +35 -0
- data/lib/producer/core/condition.rb +26 -0
- data/lib/producer/core/env.rb +21 -0
- data/lib/producer/core/errors.rb +7 -0
- data/lib/producer/core/interpreter.rb +13 -0
- data/lib/producer/core/recipe/dsl.rb +48 -0
- data/lib/producer/core/recipe.rb +15 -0
- data/lib/producer/core/remote/environment.rb +27 -0
- data/lib/producer/core/remote.rb +40 -0
- data/lib/producer/core/task/dsl.rb +42 -0
- data/lib/producer/core/task.rb +21 -0
- data/lib/producer/core/test.rb +12 -0
- data/lib/producer/core/tests/has_env.rb +11 -0
- data/lib/producer/core/version.rb +5 -0
- data/lib/producer/core.rb +22 -0
- data/producer-core.gemspec +28 -0
- data/spec/fixtures/recipes/empty.rb +1 -0
- data/spec/fixtures/recipes/throw.rb +1 -0
- data/spec/producer/core/action_spec.rb +21 -0
- data/spec/producer/core/actions/echo_spec.rb +21 -0
- data/spec/producer/core/actions/shell_command_spec.rb +27 -0
- data/spec/producer/core/cli_spec.rb +102 -0
- data/spec/producer/core/condition/dsl_spec.rb +102 -0
- data/spec/producer/core/condition_spec.rb +96 -0
- data/spec/producer/core/env_spec.rb +54 -0
- data/spec/producer/core/interpreter_spec.rb +41 -0
- data/spec/producer/core/recipe/dsl_spec.rb +131 -0
- data/spec/producer/core/recipe_spec.rb +43 -0
- data/spec/producer/core/remote/environment_spec.rb +34 -0
- data/spec/producer/core/remote_spec.rb +87 -0
- data/spec/producer/core/task/dsl_spec.rb +131 -0
- data/spec/producer/core/task_spec.rb +91 -0
- data/spec/producer/core/test_spec.rb +30 -0
- data/spec/producer/core/tests/has_env_spec.rb +41 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/exit_helpers.rb +6 -0
- data/spec/support/fixtures_helpers.rb +7 -0
- data/spec/support/net_ssh_story_helpers.rb +17 -0
- data/spec/support/tests_helpers.rb +9 -0
- metadata +230 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
guard :cucumber, cli: '--format pretty --quiet' do
|
2
|
+
watch(%r{\Afeatures/.+\.feature\z})
|
3
|
+
watch(%r{\Afeatures/support/.+\.rb\z}) { 'features' }
|
4
|
+
watch(%r{\Afeatures/step_definitions/.+_steps\.rb\z}) { 'features' }
|
5
|
+
end
|
6
|
+
|
7
|
+
guard :rspec do
|
8
|
+
watch(%r{\Aspec/.+_spec\.rb\z})
|
9
|
+
watch(%r{\Alib/(.+)\.rb\z}) { |m| "spec/#{m[1]}_spec.rb" }
|
10
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
11
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Copyright 2013 Thibault Jouan. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
4
|
+
modification, are permitted provided that the following conditions are
|
5
|
+
met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright
|
11
|
+
notice, this list of conditions and the following disclaimer in
|
12
|
+
the documentation and/or other materials provided with the
|
13
|
+
distribution.
|
14
|
+
|
15
|
+
* Neither the name of the software nor the names of its contributors
|
16
|
+
may be used to endorse or promote products derived from this
|
17
|
+
software without specific prior written permission.
|
18
|
+
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS "AS IS" AND
|
21
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
23
|
+
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS
|
24
|
+
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
25
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
26
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
27
|
+
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
28
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
29
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
30
|
+
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'cucumber/rake/task'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
desc 'Run all scenarios'
|
5
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
6
|
+
t.cucumber_opts = '--tags ~@sshd'
|
7
|
+
end
|
8
|
+
|
9
|
+
desc 'Run all specs'
|
10
|
+
RSpec::Core::RakeTask.new(:spec)
|
11
|
+
|
12
|
+
|
13
|
+
task default: [:features, :spec]
|
data/bin/producer
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
@sshd
|
2
|
+
Feature: sh task action
|
3
|
+
|
4
|
+
Scenario: executes command
|
5
|
+
Given a recipe with:
|
6
|
+
"""
|
7
|
+
target 'some_host.test'
|
8
|
+
|
9
|
+
task :some_task do
|
10
|
+
sh '\true'
|
11
|
+
end
|
12
|
+
"""
|
13
|
+
When I execute the recipe
|
14
|
+
Then the exit status must be 0
|
15
|
+
|
16
|
+
Scenario: forwards standard ouput
|
17
|
+
Given a recipe with:
|
18
|
+
"""
|
19
|
+
target 'some_host.test'
|
20
|
+
|
21
|
+
task :some_task do
|
22
|
+
sh '\echo from remote'
|
23
|
+
end
|
24
|
+
"""
|
25
|
+
When I execute the recipe
|
26
|
+
Then the output must contain "from remote"
|
27
|
+
|
28
|
+
Scenario: aborts on failed command execution
|
29
|
+
Given a recipe with:
|
30
|
+
"""
|
31
|
+
target 'some_host.test'
|
32
|
+
|
33
|
+
task :some_task do
|
34
|
+
sh '\false'
|
35
|
+
sh '\echo after_fail'
|
36
|
+
end
|
37
|
+
"""
|
38
|
+
When I execute the recipe
|
39
|
+
Then the output must not contain "after_fail"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Feature: `source' recipe keyword
|
2
|
+
|
3
|
+
Background:
|
4
|
+
Given a recipe with:
|
5
|
+
"""
|
6
|
+
source 'sourced_recipe'
|
7
|
+
"""
|
8
|
+
|
9
|
+
Scenario: requires a recipe file
|
10
|
+
Given a file named "sourced_recipe.rb" with:
|
11
|
+
"""
|
12
|
+
puts 'sourced recipe'
|
13
|
+
"""
|
14
|
+
When I execute the recipe
|
15
|
+
Then the exit status must be 0
|
16
|
+
And the output must contain "sourced recipe"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Feature: `target' recipe keyword
|
2
|
+
|
3
|
+
Scenario: registers the target host on which tasks should be applied
|
4
|
+
Given a recipe with:
|
5
|
+
"""
|
6
|
+
target 'some_host.example'
|
7
|
+
|
8
|
+
puts env.target
|
9
|
+
"""
|
10
|
+
When I execute the recipe
|
11
|
+
Then the exit status must be 0
|
12
|
+
And the output must contain exactly "some_host.example\n"
|
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Cucumber
|
2
|
+
class Runtime
|
3
|
+
alias :old_step_match :step_match
|
4
|
+
|
5
|
+
def step_match(step_name, name_to_report = nil)
|
6
|
+
if step_name.include? ' must '
|
7
|
+
name_to_report = step_name.dup
|
8
|
+
step_name.gsub! ' must ', ' should '
|
9
|
+
end
|
10
|
+
|
11
|
+
old_step_match(step_name, name_to_report)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'aruba/cucumber'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'cucumber/formatter/pretty'
|
2
|
+
|
3
|
+
module Cucumber
|
4
|
+
module Ast
|
5
|
+
class DocString
|
6
|
+
alias :old_initialize :initialize
|
7
|
+
|
8
|
+
def initialize(string, content_type)
|
9
|
+
old_initialize(string + "\n", content_type)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Formatter
|
15
|
+
class Pretty
|
16
|
+
alias :old_doc_string :doc_string
|
17
|
+
|
18
|
+
def doc_string(string)
|
19
|
+
old_doc_string(string.chomp)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# FIXME: should be extracted as a separate gem `cucumber-sshd?'.
|
2
|
+
class SSHServer
|
3
|
+
include Aruba::Api
|
4
|
+
|
5
|
+
DEFAULT_LISTEN_PORT = 2222
|
6
|
+
KEY_PATH = 'etc/ssh_host_rsa_key'
|
7
|
+
KEY_PUB_PATH = KEY_PATH.dup << '.pub'
|
8
|
+
SSHD_CONFIG_PATH = 'etc/sshd_config'
|
9
|
+
SSH_CONFIG_PATH = '.ssh/config'
|
10
|
+
|
11
|
+
attr_accessor :listen_port
|
12
|
+
|
13
|
+
def initialize(base_path)
|
14
|
+
@base_path = base_path
|
15
|
+
@listen_port = ENV['PRODUCER_TEST_SSHD_PORT'] ?
|
16
|
+
ENV['PRODUCER_TEST_SSHD_PORT'] :
|
17
|
+
DEFAULT_LISTEN_PORT
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
in_current_dir do
|
22
|
+
@pid = fork do
|
23
|
+
$stderr.reopen '/dev/null'
|
24
|
+
exec "/usr/sbin/sshd -f etc/sshd_config -Deq"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def stop
|
30
|
+
Process.kill('TERM', @pid)
|
31
|
+
Process.wait(@pid)
|
32
|
+
end
|
33
|
+
|
34
|
+
def make_env
|
35
|
+
%w[etc .ssh].map { |e| create_dir e }
|
36
|
+
|
37
|
+
write_file KEY_PATH, <<-eoh
|
38
|
+
-----BEGIN RSA PRIVATE KEY-----
|
39
|
+
MIIEpAIBAAKCAQEA7EVDKeM7NYCGGVJw0wWLGCoptMFSR7DobhbEx2bAQbWDLFBF
|
40
|
+
7S9bXpW/ddebFA4GBkHVriNPwu/IGjIgO3tivVcy6iguNKdYRABlSfpeAs+OdCzK
|
41
|
+
hyEWhmKnFMZs2MhPiJg/KUep2gFZLoEcs9vkk37+cLcuUVkXVOSPXlYCuHjgkwaN
|
42
|
+
9ij/KvCcLP4tw83H3Pyh1Wn/Y4k1+3nWN2S3RgRHF9RjEx7nm2FmXcZq1wygPe5G
|
43
|
+
2bQNvZKykL+wlKIHwKiM4AWpVClczOiljNv9KROhGWanIZNNBIDTnikxFvY0FyoP
|
44
|
+
1qvRrE8BlqxNeyG/vYNmXsv8xpO0MOF65z3d2QIDAQABAoIBAHL8dmZxX0R3i0vR
|
45
|
+
knUwsnQNOQTuPPQFBelmDViaCiEwjGlJm+6F6KrMqERarO+Cr63l5m98YfoWJkWR
|
46
|
+
dZxdPT22rWHGMk6PzuYxZxoszgoCJ2skzWAcW1EFvBjhROHwAr0Qk1Ssut4NX/DB
|
47
|
+
B04FS2X5HS2QCOuwNymqnpejtmk+A2hv9bGVzj0X614gX3h5+0dImGrYE0Lu+Abf
|
48
|
+
5fvWhN5nxgK5CVlU7WM09WxyHj9lBXI+W2dgTl6w3QJfBBQTkarLmDpwIeErq9xc
|
49
|
+
al2qHj60nYC+RdFopuLfJWKiObdKRFpuPFYKbTA9nJz9T61zAF+LaDZ1mvdTuQmz
|
50
|
+
jJEJ0nECgYEA+o2uJcDOCD2LBp2LqeQcGk1PrFLpSQ5921B10SxDWhygs2+UlBCW
|
51
|
+
7t/qurqjEUZ91l3TAlUM7ViB/EkXk/xWJRJCzlmWRXfht9LPQTWlnDox/w8uSV2g
|
52
|
+
3VwPx1xpju2PHO7Vsk6dsQsyoro14qNhYa9m1lBHA1TtJ/RLWauvnmUCgYEA8WgZ
|
53
|
+
MthPXi/clDg2DlROGOEWd7WgaErEz578HWnegcMrHb8RV/XW21CO2UegHykzUlxz
|
54
|
+
vJxAqhQeKJbP7T8uzuCZnkZqBqPh5PJT1XqxZibTeQvqYLzbIiKqmDrZWuRJvbLL
|
55
|
+
kPxwYEG8R8nl9Dk1tLHuTQWWa5Q49he1cDss4GUCgYEA7WMBRZnIW3xb1Xe9VMjg
|
56
|
+
a3cmbqHbj7FgQ0OXbQigA6euBnRIdITHTCnxDtw4Fe0Q2uLoQoRsjA/YkDx8T2S8
|
57
|
+
BcGodDPjMYxk2rKsVR9L+poUtpEejLpd6H0KIhwHkzi26HXNGHRt6ckvP4hn94RO
|
58
|
+
hqwWJiXHMnvrenh2T85fxRUCgYEA7m06NhWejhAHc/zwpsZtO/VUE3e3rknqiIUl
|
59
|
+
zIc71D3G3+JOZunQA1xVOhSb+SrgHYBibu6Ej3a/MqeBRXkZ6gm6r7AsF9LU0SLl
|
60
|
+
2fsMKzA9vVgfbNwaMmS6yQ+WjUbb7hghJlmtQ+So6N5n2AaJHKaADmJuZmJGwAg6
|
61
|
+
k1ZexGECgYAFc+GjjFOPxC+Qg6z8261PnDffBK0o+0/EIq3cGA7Cp5pEo/hhMjDl
|
62
|
+
W7CLjAGkok9W+rr9XwwXWCBuJmPh2jYeNaQljrHt9hIAOAxoYdbweb9oSo5SkcHv
|
63
|
+
iDjcFK8S1e5vnlZAh9xH1WMCEsaz1WNqWm7CZOayN2LFn6Ed9seYYg==
|
64
|
+
-----END RSA PRIVATE KEY-----
|
65
|
+
eoh
|
66
|
+
|
67
|
+
write_file KEY_PUB_PATH, <<-eoh
|
68
|
+
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDsRUMp4zs1gIYZUnDTBYsYKim0wVJHsOhuFsTHZsBBtYMsUEXtL1telb9115sUDgYGQdWuI0/C78gaMiA7e2K9VzLqKC40p1hEAGVJ+l4Cz450LMqHIRaGYqcUxmzYyE+ImD8pR6naAVkugRyz2+STfv5wty5RWRdU5I9eVgK4eOCTBo32KP8q8Jws/i3Dzcfc/KHVaf9jiTX7edY3ZLdGBEcX1GMTHuebYWZdxmrXDKA97kbZtA29krKQv7CUogfAqIzgBalUKVzM6KWM2/0pE6EZZqchk00EgNOeKTEW9jQXKg/Wq9GsTwGWrE17Ib+9g2Zey/zGk7Qw4XrnPd3Z
|
69
|
+
eoh
|
70
|
+
|
71
|
+
write_file SSHD_CONFIG_PATH, <<-eoh
|
72
|
+
Port #{listen_port}
|
73
|
+
ListenAddress ::1
|
74
|
+
|
75
|
+
Protocol 2
|
76
|
+
HostKey #{File.expand_path @base_path}/#{KEY_PATH}
|
77
|
+
PidFile /dev/null
|
78
|
+
UsePrivilegeSeparation no
|
79
|
+
ForceCommand HOME=#{File.expand_path @base_path} sh -c "cd ~; $SSH_ORIGINAL_COMMAND"
|
80
|
+
eoh
|
81
|
+
|
82
|
+
write_file SSH_CONFIG_PATH, <<-eoh
|
83
|
+
Host some_host.test
|
84
|
+
HostName localhost
|
85
|
+
Port #{listen_port}
|
86
|
+
eoh
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
Before('@sshd') do
|
92
|
+
@_sshd = SSHServer.new(current_dir)
|
93
|
+
@_sshd.make_env
|
94
|
+
@_sshd.start
|
95
|
+
|
96
|
+
ENV['HOME'] = File.expand_path current_dir
|
97
|
+
|
98
|
+
# FIXME: we might need to wait until sshd accepts connections, polling isn't
|
99
|
+
# really acceptable, another workaround might be to execute sshd with
|
100
|
+
# LD_PRELOAD and a hook, so we could block here until the hook release the
|
101
|
+
# blocking call.
|
102
|
+
end
|
103
|
+
|
104
|
+
After('@sshd') do
|
105
|
+
@_sshd.stop
|
106
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Feature: `condition' task keyword
|
2
|
+
|
3
|
+
Scenario: prevents task actions application when condition is not met
|
4
|
+
Given a recipe with:
|
5
|
+
"""
|
6
|
+
task :hello do
|
7
|
+
condition { false }
|
8
|
+
|
9
|
+
echo 'evaluated'
|
10
|
+
end
|
11
|
+
"""
|
12
|
+
When I execute the recipe
|
13
|
+
Then the exit status must be 0
|
14
|
+
And the output must not contain "evaluated"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Feature: task evaluation
|
2
|
+
|
3
|
+
Scenario: evaluates ruby code in task blocks
|
4
|
+
Given a recipe with:
|
5
|
+
"""
|
6
|
+
task :hello do
|
7
|
+
puts 'hello from recipe'
|
8
|
+
end
|
9
|
+
"""
|
10
|
+
When I execute the recipe
|
11
|
+
Then the exit status must be 0
|
12
|
+
And the output must contain exactly "hello from recipe\n"
|
@@ -0,0 +1,32 @@
|
|
1
|
+
@sshd
|
2
|
+
Feature: `has_env' condition keyword
|
3
|
+
|
4
|
+
Scenario: succeeds when remote environment variable is defined
|
5
|
+
Given a recipe with:
|
6
|
+
"""
|
7
|
+
target 'some_host.test'
|
8
|
+
|
9
|
+
task :testing_env_var_definition do
|
10
|
+
condition { has_env :shell }
|
11
|
+
|
12
|
+
echo 'evaluated'
|
13
|
+
end
|
14
|
+
"""
|
15
|
+
When I execute the recipe
|
16
|
+
Then the exit status must be 0
|
17
|
+
And the output must contain "evaluated"
|
18
|
+
|
19
|
+
Scenario: fails when remote environment variable is not defined
|
20
|
+
Given a recipe with:
|
21
|
+
"""
|
22
|
+
target 'some_host.test'
|
23
|
+
|
24
|
+
task :testing_env_var_definition do
|
25
|
+
condition { has_env :inexistent_var }
|
26
|
+
|
27
|
+
echo 'evaluated'
|
28
|
+
end
|
29
|
+
"""
|
30
|
+
When I execute the recipe
|
31
|
+
Then the exit status must be 0
|
32
|
+
And the output must not contain "evaluated"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Producer
|
2
|
+
module Core
|
3
|
+
class CLI
|
4
|
+
attr_reader :arguments
|
5
|
+
|
6
|
+
USAGE = "Usage: #{File.basename $0} recipe_file"
|
7
|
+
|
8
|
+
def initialize(arguments, stdout = $stdout)
|
9
|
+
@stdout = stdout
|
10
|
+
@arguments = arguments
|
11
|
+
end
|
12
|
+
|
13
|
+
def run!
|
14
|
+
check_arguments!
|
15
|
+
interpreter.process recipe.tasks
|
16
|
+
end
|
17
|
+
|
18
|
+
def check_arguments!
|
19
|
+
print_usage_and_exit(64) unless @arguments.length == 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def env
|
23
|
+
@env ||= Env.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def recipe
|
27
|
+
@recipe ||= Recipe.evaluate_from_file(@arguments.first, env)
|
28
|
+
end
|
29
|
+
|
30
|
+
def interpreter
|
31
|
+
@interpreter ||= Interpreter.new
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def print_usage_and_exit(status)
|
37
|
+
@stdout.puts USAGE
|
38
|
+
|
39
|
+
exit status
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Producer
|
2
|
+
module Core
|
3
|
+
class Condition
|
4
|
+
class DSL
|
5
|
+
class << self
|
6
|
+
def evaluate(env, &block)
|
7
|
+
dsl = new(env, &block)
|
8
|
+
return_value = dsl.evaluate
|
9
|
+
Condition.new(dsl.tests, return_value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def define_test(keyword, klass)
|
13
|
+
define_method(keyword) do |*args|
|
14
|
+
@tests << klass.new(@env, *args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
define_test :has_env, Tests::HasEnv
|
20
|
+
|
21
|
+
attr_accessor :tests
|
22
|
+
|
23
|
+
def initialize(env, &block)
|
24
|
+
@env = env
|
25
|
+
@block = block
|
26
|
+
@tests = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def evaluate
|
30
|
+
instance_eval &@block
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Producer
|
2
|
+
module Core
|
3
|
+
class Condition
|
4
|
+
class << self
|
5
|
+
def evaluate(env, &block)
|
6
|
+
DSL.evaluate(env, &block)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(tests, return_value = nil)
|
11
|
+
@tests = tests
|
12
|
+
@return_value = return_value
|
13
|
+
end
|
14
|
+
|
15
|
+
def met?
|
16
|
+
return !!@return_value if @tests.empty?
|
17
|
+
@tests.each { |t| return false unless t.success? }
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def !
|
22
|
+
!met?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Producer
|
2
|
+
module Core
|
3
|
+
class Env
|
4
|
+
attr_writer :output
|
5
|
+
attr_accessor :target
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@output = $stdout
|
9
|
+
@target = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def output(str)
|
13
|
+
@output.puts str
|
14
|
+
end
|
15
|
+
|
16
|
+
def remote
|
17
|
+
@remote ||= Remote.new(target)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Producer
|
2
|
+
module Core
|
3
|
+
class Recipe
|
4
|
+
class DSL
|
5
|
+
attr_reader :tasks
|
6
|
+
|
7
|
+
def self.evaluate(code, env)
|
8
|
+
dsl = new(code).evaluate(env)
|
9
|
+
Recipe.new(dsl.tasks)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(code = nil, &block)
|
13
|
+
@code = code
|
14
|
+
@block = block
|
15
|
+
@tasks = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def evaluate(env)
|
19
|
+
@env = env
|
20
|
+
if @code
|
21
|
+
instance_eval @code
|
22
|
+
else
|
23
|
+
instance_eval &@block
|
24
|
+
end
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def env
|
31
|
+
@env
|
32
|
+
end
|
33
|
+
|
34
|
+
def source(filepath)
|
35
|
+
instance_eval File.read("./#{filepath}.rb"), "#{filepath}.rb"
|
36
|
+
end
|
37
|
+
|
38
|
+
def target(hostname)
|
39
|
+
env.target = hostname
|
40
|
+
end
|
41
|
+
|
42
|
+
def task(name, &block)
|
43
|
+
@tasks << Task.evaluate(name, env, &block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|