souffle 0.0.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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +30 -0
- data/LICENSE.txt +20 -0
- data/README.md +18 -0
- data/Rakefile +56 -0
- data/bin/souffle-server +7 -0
- data/bin/souffle-worker +7 -0
- data/features/souffle.feature +9 -0
- data/features/step_definitions/souffle_steps.rb +0 -0
- data/features/support/env.rb +13 -0
- data/lib/souffle/application/souffle-server.rb +46 -0
- data/lib/souffle/application/souffle-worker.rb +46 -0
- data/lib/souffle/application.rb +133 -0
- data/lib/souffle/config.rb +75 -0
- data/lib/souffle/exceptions.rb +21 -0
- data/lib/souffle/log.rb +22 -0
- data/lib/souffle/node/runlist.rb +24 -0
- data/lib/souffle/node/runlist_item.rb +46 -0
- data/lib/souffle/node/runlist_parser.rb +73 -0
- data/lib/souffle/node.rb +70 -0
- data/lib/souffle/provider/aws.rb +16 -0
- data/lib/souffle/provider/vagrant.rb +15 -0
- data/lib/souffle/provider.rb +27 -0
- data/lib/souffle/providers.rb +2 -0
- data/lib/souffle/server.rb +14 -0
- data/lib/souffle/system.rb +51 -0
- data/lib/souffle/worker.rb +14 -0
- data/lib/souffle.rb +17 -0
- data/spec/config/example.json +6 -0
- data/spec/config/example.rb +4 -0
- data/spec/config_spec.rb +30 -0
- data/spec/node_spec.rb +87 -0
- data/spec/runlist_parser_spec.rb +83 -0
- data/spec/runlist_spec.rb +14 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/system_spec.rb +35 -0
- metadata +375 -0
data/lib/souffle/node.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Souffle
|
2
|
+
class Node; end
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'souffle/node/runlist_item'
|
6
|
+
require 'souffle/node/runlist'
|
7
|
+
|
8
|
+
module Souffle
|
9
|
+
# A node object that's part of a given system.
|
10
|
+
class Node
|
11
|
+
attr_accessor :dependencies, :run_list, :parent
|
12
|
+
attr_reader :children
|
13
|
+
|
14
|
+
state_machine :state, :initial => :uninitialized do
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates a new souffle node with bare dependencies and run_list.
|
18
|
+
def initialize
|
19
|
+
@dependencies = Souffle::Node::RunList.new
|
20
|
+
@run_list = Souffle::Node::RunList.new
|
21
|
+
@parent = nil
|
22
|
+
@children = []
|
23
|
+
super() # NOTE: This is here to initialize state_machine.
|
24
|
+
end
|
25
|
+
|
26
|
+
# Check whether or not a given node depends on another node.
|
27
|
+
#
|
28
|
+
# @param [ Souffle::Node ] node Check to see whether this node depends
|
29
|
+
#
|
30
|
+
# @return [ true,false ] Whether or not this node depends on the given.
|
31
|
+
def depends_on?(node)
|
32
|
+
depends = false
|
33
|
+
self.dependencies.each do |d|
|
34
|
+
if node.run_list.include? d
|
35
|
+
depends = true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
depends
|
39
|
+
end
|
40
|
+
|
41
|
+
# Adds a child node to the current node.
|
42
|
+
#
|
43
|
+
# @param [ Souffle::Node ] node The node to add as a child.
|
44
|
+
#
|
45
|
+
# @raise [ InvaidChild ] Children must have dependencies and a run_list.
|
46
|
+
def add_child(node)
|
47
|
+
unless node.respond_to?(:dependencies) && node.respond_to?(:run_list)
|
48
|
+
raise Souffle::Exceptions::InvalidChild,
|
49
|
+
"Child must act as a Souffle::Node"
|
50
|
+
end
|
51
|
+
node.parent = self
|
52
|
+
@children.push(node)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Iterator method for children.
|
56
|
+
#
|
57
|
+
# @yield [ Souffle::Node,nil ] The child node.
|
58
|
+
def each_child
|
59
|
+
@children.each { |child| yield child }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Equality comparator for nodes.
|
63
|
+
#
|
64
|
+
# @param [ Souffle::Node ] other The node to compare against.
|
65
|
+
def eql?(other)
|
66
|
+
@dependencies == other.dependencies && @run_list == other.run_list
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'souffle/provider'
|
2
|
+
|
3
|
+
# The AWS souffle provider.
|
4
|
+
class Souffle::Provider::AWS < Souffle::Provider
|
5
|
+
|
6
|
+
# Setup the internal AWS configuration and object.
|
7
|
+
def setup
|
8
|
+
end
|
9
|
+
|
10
|
+
# The name of the given provider.
|
11
|
+
def name; "AWS"; end
|
12
|
+
|
13
|
+
# Creates a raid array with the given requirements.
|
14
|
+
def create_raid
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'souffle/provider'
|
2
|
+
|
3
|
+
# The Vagrant souffle provider.
|
4
|
+
class Souffle::Provider::Vagrant < Souffle::Provider
|
5
|
+
|
6
|
+
# Setup the internal Vagrant configuration and object.
|
7
|
+
def setup
|
8
|
+
end
|
9
|
+
|
10
|
+
# The name of the given provider.
|
11
|
+
def name; "Vagrant"; end
|
12
|
+
|
13
|
+
# Noop.
|
14
|
+
def create_raid; end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# The souffle cloud provider class.
|
2
|
+
class Souffle::Provider
|
3
|
+
|
4
|
+
# The setup method for the provider. Intended to be overridden.
|
5
|
+
#
|
6
|
+
# @raise [Souffle::Exceptions::Provider] This definition must be overridden.
|
7
|
+
def setup
|
8
|
+
error_msg = "#{self.to_s}: you must override setup"
|
9
|
+
raise Souffle::Exceptions::Provider, error_msg
|
10
|
+
end
|
11
|
+
|
12
|
+
# The name of the given provider. Intended to be overridden.
|
13
|
+
#
|
14
|
+
# @raise [Souffle::Exceptions::Provider] This definition must be overridden.
|
15
|
+
def name
|
16
|
+
error_msg = "#{self.to_s}: you must override name"
|
17
|
+
raise Souffle::Exceptions::Provider, error_msg
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates a raid array for a given provider. Intended to be overridden.
|
21
|
+
#
|
22
|
+
# @raise [Souffle::Exceptions::Provider] This definition must be overridden.
|
23
|
+
def create_raid
|
24
|
+
error_msg = "#{self.to_s}: you must override create_raid"
|
25
|
+
raise Souffle::Exceptions::Provider, error_msg
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Souffle
|
2
|
+
# A system description with nodes and the statemachine to manage them.
|
3
|
+
class System
|
4
|
+
attr_reader :nodes, :root, :provider
|
5
|
+
|
6
|
+
state_machine :state, :initial => :uninitialized do
|
7
|
+
before_transition :uninitialized => any - :uninitialized,
|
8
|
+
:do => :initialize_provider
|
9
|
+
|
10
|
+
around_transition do |system, transition, block|
|
11
|
+
start = Time.now
|
12
|
+
block.call
|
13
|
+
system.time_used += Time.now - start
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates a new souffle system, defaulting to using Vagrant as a provider.
|
18
|
+
#
|
19
|
+
# @param [ String ] provider The provider to use for the given system.
|
20
|
+
def initialize(provider="Vagrant")
|
21
|
+
initialize_provider(provider)
|
22
|
+
super() # NOTE: This is here to initialize state_machine.
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize_provider(provider)
|
26
|
+
@provider = Souffle::Provider.const_get(provider.to_sym).new
|
27
|
+
rescue
|
28
|
+
raise Souffle::Exceptions::InvalidProvider,
|
29
|
+
"The provider Souffle::Provider::#{provider} does not exist."
|
30
|
+
end
|
31
|
+
|
32
|
+
# Proxy to the provider setup routine.
|
33
|
+
def setup_provider
|
34
|
+
@provider.setup
|
35
|
+
end
|
36
|
+
|
37
|
+
# Adds the root node to the system.
|
38
|
+
#
|
39
|
+
# @param [ Souffle::Node ] node The node to become to root node.
|
40
|
+
def root=(node)
|
41
|
+
@root = node
|
42
|
+
end
|
43
|
+
|
44
|
+
# Adds a node to the system tree.
|
45
|
+
#
|
46
|
+
# @param [ Souffle::Node ] node The node to add into the tree.
|
47
|
+
def add(node)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/souffle.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
require 'yajl'
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'state_machine'
|
5
|
+
require 'right_aws'
|
6
|
+
|
7
|
+
# An orchestrator for setting up isolated chef-managed systems.
|
8
|
+
module Souffle
|
9
|
+
VERSION = "0.0.1"
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'souffle/log'
|
13
|
+
require 'souffle/exceptions'
|
14
|
+
require 'souffle/config'
|
15
|
+
require 'souffle/providers'
|
16
|
+
require 'souffle/node'
|
17
|
+
require 'souffle/system'
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Souffle::Config" do
|
4
|
+
it "should be able to read from a ruby config file" do
|
5
|
+
config = File.join(File.dirname(__FILE__), 'config', 'example.rb')
|
6
|
+
Souffle::Config.from_file(config)
|
7
|
+
|
8
|
+
Souffle::Config[:random_something].should == 1234
|
9
|
+
Souffle::Config[:aws_access_key].should == "test_key"
|
10
|
+
Souffle::Config[:aws_access_secret].should == "test_secret"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should be able to read from a json config stream" do
|
14
|
+
config = File.join(File.dirname(__FILE__), 'config', 'example.json')
|
15
|
+
Souffle::Config.from_stream_json(IO.read(config))
|
16
|
+
|
17
|
+
Souffle::Config[:random_something].should == 1234
|
18
|
+
Souffle::Config[:aws_access_key].should == "test_key"
|
19
|
+
Souffle::Config[:aws_access_secret].should == "test_secret"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be able to read from a json config file" do
|
23
|
+
config = File.join(File.dirname(__FILE__), 'config', 'example.json')
|
24
|
+
Souffle::Config.from_file(config, "json")
|
25
|
+
|
26
|
+
Souffle::Config[:random_something].should == 1234
|
27
|
+
Souffle::Config[:aws_access_key].should == "test_key"
|
28
|
+
Souffle::Config[:aws_access_secret].should == "test_secret"
|
29
|
+
end
|
30
|
+
end
|
data/spec/node_spec.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Souffle::Node" do
|
4
|
+
before(:each) do
|
5
|
+
@node = Souffle::Node.new
|
6
|
+
end
|
7
|
+
|
8
|
+
after(:each) do
|
9
|
+
@node = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to setup dependencies" do
|
13
|
+
@node.dependencies << "recipe[chef_server]"
|
14
|
+
item = @node.dependencies.first
|
15
|
+
item.type.should eql("recipe")
|
16
|
+
item.name.should eql("chef_server")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be able to setup the run_list" do
|
20
|
+
@node.run_list << "role[example_role]"
|
21
|
+
item = @node.run_list.first
|
22
|
+
item.type.should eql("role")
|
23
|
+
item.name.should eql("example_role")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should fail on improper run_list type" do
|
27
|
+
lambda { @node.run_list<<("b0rken[role]") }.should raise_error
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should fail on improper run_list name" do
|
31
|
+
lambda { @node.run_list << "role[GT***]}" }.should raise_error
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should be able to describe a single node" do
|
35
|
+
@node.run_list << "recipe[chef_server::rubygems_install]"
|
36
|
+
@node.run_list << "role[dns_server]"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should be able to test whether or not a node depends on another" do
|
40
|
+
@node.run_list << "role[dns_server]"
|
41
|
+
node2 = Souffle::Node.new
|
42
|
+
node2.dependencies << "role[dns_server]"
|
43
|
+
node2.depends_on?(@node).should eql(true)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should not depend on another node when there are no dependencies" do
|
47
|
+
@node.run_list << "role[dns_server]"
|
48
|
+
node2 = Souffle::Node.new
|
49
|
+
node2.depends_on?(@node).should eql(false)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should be able to add child nodes" do
|
53
|
+
child = Souffle::Node.new
|
54
|
+
lambda { @node.add_child(child) }.should_not raise_error
|
55
|
+
@node.children.should eql([child])
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should raise and error on adding an invalid child" do
|
59
|
+
child = []
|
60
|
+
lambda { node.add_child(child) }.should raise_error
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should be able to iterate across children" do
|
64
|
+
child1 = Souffle::Node.new
|
65
|
+
child2 = Souffle::Node.new
|
66
|
+
@node.add_child(child1)
|
67
|
+
@node.add_child(child2)
|
68
|
+
|
69
|
+
children = [child1, child2]
|
70
|
+
@node.each_child { |c| children.delete(c) }
|
71
|
+
children.should eql([])
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should be able to test node equality" do
|
75
|
+
@node.dependencies << "role[awesome]"
|
76
|
+
@node.run_list << "recipe[the_best]"
|
77
|
+
|
78
|
+
node2 = Souffle::Node.new
|
79
|
+
node2.dependencies << "role[awesome]"
|
80
|
+
node2.run_list << "recipe[the_best]"
|
81
|
+
@node.should eql(node2)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should have an initial state of `:uninitialized`" do
|
85
|
+
@node.state_name.should eql(:uninitialized)
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Souffle::Node::RunListParser" do
|
4
|
+
it "should be able to check whether a hash name is a valid word" do
|
5
|
+
ex = Hash.new
|
6
|
+
ex["name"] = "AwesomeWord"
|
7
|
+
d = lambda { Souffle::Node::RunListParser.gaurentee_name_is_word(ex) }
|
8
|
+
d.should_not raise_error
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should raise an error when the hash name is spaced" do
|
12
|
+
ex = Hash.new
|
13
|
+
ex["name"] = "AwesomeWord SecondWord"
|
14
|
+
d = lambda { Souffle::Node::RunListParser.gaurentee_name_is_word(ex) }
|
15
|
+
d.should raise_error
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should raise an error when the hash name contains invalid characters" do
|
19
|
+
ex = Hash.new
|
20
|
+
ex["name"] = "AwesomeWord**"
|
21
|
+
d = lambda { Souffle::Node::RunListParser.gaurentee_name_is_word(ex) }
|
22
|
+
d.should raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should raise an error when the hash is empty" do
|
26
|
+
ex = Hash.new
|
27
|
+
d = lambda { Souffle::Node::RunListParser.gaurentee_valid_keys(ex) }
|
28
|
+
d.should raise_error
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should raise an error when the hash is nil" do
|
32
|
+
ex = nil
|
33
|
+
d = lambda { Souffle::Node::RunListParser.gaurentee_valid_keys(ex) }
|
34
|
+
d.should raise_error
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should raise an error when the name is nil but the type is valid" do
|
38
|
+
ex = Hash.new
|
39
|
+
ex["name"] = nil
|
40
|
+
ex["type"] = "role"
|
41
|
+
d = lambda { Souffle::Node::RunListParser.gaurentee_valid_keys(ex) }
|
42
|
+
d.should raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should raise an error when the name is empty but the type is valid" do
|
46
|
+
ex = Hash.new
|
47
|
+
ex["name"] = ""
|
48
|
+
ex["type"] = "recipe"
|
49
|
+
d = lambda { Souffle::Node::RunListParser.gaurentee_valid_keys(ex) }
|
50
|
+
d.should raise_error
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should raise an error when the name is valid but the type is nil" do
|
54
|
+
ex = Hash.new
|
55
|
+
ex["name"] = "best_name"
|
56
|
+
ex["type"] = nil
|
57
|
+
d = lambda { Souffle::Node::RunListParser.gaurentee_valid_keys(ex) }
|
58
|
+
d.should raise_error
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should raise an error when the name is valud but the type is empty" do
|
62
|
+
ex = Hash.new
|
63
|
+
ex["name"] = "anotherone"
|
64
|
+
ex["type"] = ""
|
65
|
+
d = lambda { Souffle::Node::RunListParser.gaurentee_valid_keys(ex) }
|
66
|
+
d.should raise_error
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should raise an error when parsing an invalid type" do
|
70
|
+
d = lambda { Souffle::Node::RunListParser.parse("rofsdfsdle[somerole]") }
|
71
|
+
d.should raise_error
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should be able to parse a role" do
|
75
|
+
r = Souffle::Node::RunListParser.parse("role[painfully_long_role]")
|
76
|
+
r.should eql( {"type" => "role", "name" => "painfully_long_role"} )
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should be able to parse a recipe" do
|
80
|
+
r = Souffle::Node::RunListParser.parse("recipe[a_pretty_serious_recipe]")
|
81
|
+
r.should eql( {"type" => "recipe", "name" => "a_pretty_serious_recipe"} )
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Souffle::Node::RunList" do
|
4
|
+
it "should be able to add a runlist item onto the runlist" do
|
5
|
+
rl = Souffle::Node::RunList.new
|
6
|
+
rl << "role[dns_server]"
|
7
|
+
rl << "recipe[chef_server::rubygems_install]"
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should raise an error when an invalid runlist item is added" do
|
11
|
+
rl = Souffle::Node::RunList.new
|
12
|
+
lambda { rl << "fsjklfds" }.should raise_error
|
13
|
+
end
|
14
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
if RUBY_VERSION =~ /^1\.9/
|
5
|
+
require 'simplecov'
|
6
|
+
|
7
|
+
module SimpleCov::Configuration
|
8
|
+
def clean_filters
|
9
|
+
@filters = []
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
SimpleCov.configure do
|
14
|
+
clean_filters
|
15
|
+
load_adapter 'test_frameworks'
|
16
|
+
end
|
17
|
+
|
18
|
+
ENV["COVERAGE"] && SimpleCov.start do
|
19
|
+
add_filter "/.rvm/"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'rspec'
|
24
|
+
require 'souffle'
|
25
|
+
|
26
|
+
# Requires supporting files with custom matchers and macros, etc,
|
27
|
+
# in ./support/ and its subdirectories.
|
28
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
29
|
+
|
30
|
+
RSpec.configure do |config|
|
31
|
+
|
32
|
+
end
|
data/spec/system_spec.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Souffle::System" do
|
4
|
+
before(:each) do
|
5
|
+
@system = Souffle::System.new
|
6
|
+
end
|
7
|
+
|
8
|
+
after(:each) do
|
9
|
+
@system = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to setup a Vagrant provider" do
|
13
|
+
@system.provider.name.should eql("Vagrant")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be able to setup an AWS provider" do
|
17
|
+
@system = Souffle::System.new("AWS")
|
18
|
+
@system.provider.name.should eql("AWS")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should raise an InvalidProvider error when the provider doesn't exist" do
|
22
|
+
d = lambda { @system = Souffle::System.new("UnholyProviderOfBadness") }
|
23
|
+
d.should raise_error
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should be able to add a root node" do
|
27
|
+
node = Souffle::Node.new
|
28
|
+
@system.root = node
|
29
|
+
@system.root.should eql(node)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should have an initial state of `:uninitialized`" do
|
33
|
+
@system.state_name.should eql(:uninitialized)
|
34
|
+
end
|
35
|
+
end
|