souffle 0.0.1 → 0.0.2
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/Gemfile +9 -3
- data/README.md +6 -0
- data/bin/{souffle-server → souffle} +0 -0
- data/lib/souffle.rb +8 -8
- data/lib/souffle/application.rb +15 -10
- data/lib/souffle/application/souffle-server.rb +90 -5
- data/lib/souffle/config.rb +88 -59
- data/lib/souffle/daemon.rb +156 -0
- data/lib/souffle/exceptions.rb +29 -17
- data/lib/souffle/http.rb +43 -0
- data/lib/souffle/log.rb +11 -14
- data/lib/souffle/node.rb +91 -53
- data/lib/souffle/node/runlist.rb +16 -18
- data/lib/souffle/node/runlist_item.rb +43 -36
- data/lib/souffle/node/runlist_parser.rb +60 -62
- data/lib/souffle/polling_event.rb +110 -0
- data/lib/souffle/provider.rb +231 -23
- data/lib/souffle/provider/aws.rb +654 -7
- data/lib/souffle/provider/vagrant.rb +42 -5
- data/lib/souffle/provisioner.rb +55 -0
- data/lib/souffle/provisioner/node.rb +157 -0
- data/lib/souffle/provisioner/system.rb +195 -0
- data/lib/souffle/redis_client.rb +8 -0
- data/lib/souffle/redis_mixin.rb +40 -0
- data/lib/souffle/server.rb +42 -8
- data/lib/souffle/ssh_monkey.rb +8 -0
- data/lib/souffle/state.rb +16 -0
- data/lib/souffle/system.rb +139 -37
- data/lib/souffle/template.rb +30 -0
- data/lib/souffle/templates/Vagrantfile.erb +41 -0
- data/lib/souffle/version.rb +6 -0
- data/spec/config_spec.rb +20 -0
- data/spec/log_spec.rb +24 -0
- data/spec/{runlist_parser_spec.rb → node/runlist_parser_spec.rb} +1 -1
- data/spec/{runlist_spec.rb → node/runlist_spec.rb} +1 -1
- data/spec/node_spec.rb +43 -8
- data/spec/provider_spec.rb +56 -0
- data/spec/providers/aws_provider_spec.rb +114 -0
- data/spec/providers/vagrant_provider_spec.rb +22 -0
- data/spec/provisioner_spec.rb +47 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/system_spec.rb +242 -13
- data/spec/template_spec.rb +20 -0
- data/spec/templates/example_template.erb +1 -0
- metadata +125 -30
- data/bin/souffle-worker +0 -7
- data/lib/souffle/application/souffle-worker.rb +0 -46
- data/lib/souffle/providers.rb +0 -2
- data/lib/souffle/worker.rb +0 -14
data/lib/souffle/exceptions.rb
CHANGED
@@ -1,21 +1,33 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
class Provider < RuntimeError; end
|
1
|
+
# Souffle specific exceptions; intended for debugging purposes.
|
2
|
+
class Souffle::Exceptions
|
3
|
+
|
4
|
+
# Application level error.
|
5
|
+
class Application < RuntimeError; end
|
6
|
+
# Provider level error.
|
7
|
+
class Provider < RuntimeError; end
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
# Runlist Name cannot be nil or empty and must be a word [A-Za-z0-9_:].
|
10
|
+
class InvalidRunlistName < RuntimeError; end
|
11
|
+
# Runlist Type cannot be nil or empty and must be one of (role|recipe).
|
12
|
+
class InvalidRunlistType < RuntimeError; end
|
14
13
|
|
15
|
-
|
16
|
-
|
14
|
+
# Node children must respond to dependencies and run_list.
|
15
|
+
class InvalidChild < RuntimeError; end
|
16
|
+
# Node parents must respond to dependencies and run_list.
|
17
|
+
class InvalidParent < RuntimeError; end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
# The provider must exist in souffle/providers.
|
20
|
+
class InvalidProvider < RuntimeError; end
|
21
|
+
|
22
|
+
# The system hash must have a nodes key with a list of nodes.
|
23
|
+
class InvalidSystemHash < RuntimeError; end
|
24
|
+
|
25
|
+
# The souffle ssh directory must have writable permissions.
|
26
|
+
class PermissionErrorSshKeys < RuntimeError; end
|
27
|
+
|
28
|
+
# The AWS Instance searched for does not exist.
|
29
|
+
class AwsInstanceDoesNotExist < RuntimeError; end
|
30
|
+
|
31
|
+
# The AWS Keys are invalid.
|
32
|
+
class InvalidAwsKeys < RuntimeError; end
|
21
33
|
end
|
data/lib/souffle/http.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
require 'souffle/state'
|
4
|
+
|
5
|
+
# The souffle service REST interface.
|
6
|
+
class Souffle::Http < Sinatra::Base
|
7
|
+
before { content_type :json }
|
8
|
+
|
9
|
+
# Returns the current version of souffle.
|
10
|
+
get '/' do
|
11
|
+
{ :name => 'souffle',
|
12
|
+
:version => Souffle::VERSION }.to_json
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the current status of souffle.
|
16
|
+
get '/status' do
|
17
|
+
{ :status => Souffle::State.status }.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the id for the created environment or false on failure.
|
21
|
+
put '/create' do
|
22
|
+
begin
|
23
|
+
data = JSON.parse(request.body.read, :symbolize_keys => true)
|
24
|
+
rescue
|
25
|
+
status 415
|
26
|
+
return { :success => false,
|
27
|
+
:message => "Invalid json in request." }.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
user = data[:user]
|
31
|
+
msg = "Http request to create a new system"
|
32
|
+
msg << " for user: #{user}" if user
|
33
|
+
Souffle::Log.debug msg
|
34
|
+
Souffle::Log.debug data.to_s
|
35
|
+
|
36
|
+
provider = Souffle::Provider::AWS.new
|
37
|
+
|
38
|
+
system = Souffle::System.from_hash(data)
|
39
|
+
provider.create_system(system)
|
40
|
+
|
41
|
+
{ :success => true }.to_json
|
42
|
+
end
|
43
|
+
end
|
data/lib/souffle/log.rb
CHANGED
@@ -1,22 +1,19 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'mixlib/log'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
extend Mixlib::Log
|
4
|
+
# Souffle's internal logging facility.
|
5
|
+
# Standardized to provide a consistent log format.
|
6
|
+
class Souffle::Log
|
7
|
+
extend Mixlib::Log
|
9
8
|
|
10
|
-
|
11
|
-
|
9
|
+
# Force initialization of the primary log device (@logger).
|
10
|
+
init
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
12
|
+
# Monkeypatch Formatter to allow local show_time updates.
|
13
|
+
class Formatter
|
14
|
+
# Allow enabling and disabling of time with a singleton.
|
15
|
+
def self.show_time=(*args)
|
16
|
+
Mixlib::Log::Formatter.show_time = *args
|
19
17
|
end
|
20
|
-
|
21
18
|
end
|
22
19
|
end
|
data/lib/souffle/node.rb
CHANGED
@@ -1,70 +1,108 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
end
|
1
|
+
# A node object that's part of a given system.
|
2
|
+
class Souffle::Node; end
|
4
3
|
|
5
4
|
require 'souffle/node/runlist_item'
|
6
5
|
require 'souffle/node/runlist'
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
attr_reader :children
|
7
|
+
# A node object that's part of a given system.
|
8
|
+
class Souffle::Node
|
9
|
+
attr_accessor :system, :dependencies, :run_list,
|
10
|
+
:parents, :children, :name, :options, :provisioner
|
13
11
|
|
14
|
-
|
15
|
-
|
12
|
+
# Creates a new souffle node with bare dependencies and run_list.
|
13
|
+
#
|
14
|
+
# @param [ Fixnum ] parent_multiplier The multiplier for parent nodes.
|
15
|
+
def initialize(parent_multiplier=5)
|
16
|
+
@dependencies = Souffle::Node::RunList.new
|
17
|
+
@run_list = Souffle::Node::RunList.new
|
18
|
+
@parents = []
|
19
|
+
@children = []
|
20
|
+
@options = {
|
21
|
+
:attributes => Hash.new
|
22
|
+
}
|
23
|
+
@parent_multiplier = parent_multiplier
|
24
|
+
end
|
16
25
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
26
|
+
# Check whether or not a given node depends on another node.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
#
|
30
|
+
# n1 = Souffle::Node.new
|
31
|
+
# n2 = Souffle::Node.new
|
32
|
+
#
|
33
|
+
# n1.run_list << "role[dns_server]"
|
34
|
+
# n2.dependencies << "role[dns_server]"
|
35
|
+
# n2.depends_on?(n1)
|
36
|
+
#
|
37
|
+
# > [ true, [role[dns_server]] ]
|
38
|
+
#
|
39
|
+
# @param [ Souffle::Node ] node Check to see whether this node depends
|
40
|
+
#
|
41
|
+
# @return [ Array ] The tuple of [depends_on, dependency_list].
|
42
|
+
def depends_on?(node)
|
43
|
+
dependency_list = []
|
44
|
+
@dependencies.each do |d|
|
45
|
+
dependency_list << d if node.run_list.include? d
|
24
46
|
end
|
47
|
+
[dependency_list.any?, dependency_list]
|
48
|
+
end
|
25
49
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
depends = true
|
36
|
-
end
|
37
|
-
end
|
38
|
-
depends
|
50
|
+
# Adds a child node to the current node.
|
51
|
+
#
|
52
|
+
# @param [ Souffle::Node ] node The node to add as a child.
|
53
|
+
#
|
54
|
+
# @raise [ InvaidChild ] Children must have dependencies and a run_list.
|
55
|
+
def add_child(node)
|
56
|
+
unless node.respond_to?(:dependencies) && node.respond_to?(:run_list)
|
57
|
+
raise Souffle::Exceptions::InvalidChild,
|
58
|
+
"Child must act as a Souffle::Node"
|
39
59
|
end
|
40
|
-
|
41
|
-
|
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
|
60
|
+
unless @children.include? node
|
61
|
+
node.parents << self
|
52
62
|
@children.push(node)
|
53
63
|
end
|
64
|
+
end
|
54
65
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
66
|
+
# Iterator method for children.
|
67
|
+
#
|
68
|
+
# @yield [ Souffle::Node,NilClass ] The child node.
|
69
|
+
def each_child
|
70
|
+
@children.each { |child| yield child }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Equality comparator for nodes.
|
74
|
+
#
|
75
|
+
# @param [ Souffle::Node ] other The node to compare against.
|
76
|
+
def eql?(other)
|
77
|
+
@dependencies == other.dependencies && @run_list == other.run_list
|
78
|
+
end
|
61
79
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
80
|
+
# The dependency weight of a given node.
|
81
|
+
#
|
82
|
+
# @return [ Fixnum ] The relative weight of a node used for balancing.
|
83
|
+
def weight
|
84
|
+
@parents.inject(1) { |res, p| res + p.weight * @parent_multiplier }
|
85
|
+
end
|
86
|
+
|
87
|
+
# Tries to fetch an option parameter otherwise it grabs it from config.
|
88
|
+
#
|
89
|
+
# @param [ Symbol ] opt The option to try and fetch.
|
90
|
+
#
|
91
|
+
# @return [ String ] The option return value.
|
92
|
+
def try_opt(opt)
|
93
|
+
if system
|
94
|
+
options.fetch(opt, system.try_opt(opt))
|
95
|
+
else
|
96
|
+
options.fetch(opt, Souffle::Config[opt])
|
67
97
|
end
|
98
|
+
rescue
|
99
|
+
nil
|
100
|
+
end
|
68
101
|
|
102
|
+
# The logging prefix for the given node.
|
103
|
+
#
|
104
|
+
# @return [ String ] The logging prefix for the given node.
|
105
|
+
def log_prefix
|
106
|
+
"[#{try_opt(:tag)}: #{name}]"
|
69
107
|
end
|
70
108
|
end
|
data/lib/souffle/node/runlist.rb
CHANGED
@@ -1,24 +1,22 @@
|
|
1
1
|
require 'souffle/node/runlist_item'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
class Node::RunList < Array
|
3
|
+
# A specialized Array that handles runlist items appropriately.
|
4
|
+
class Souffle::Node::RunList < Array
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# Pushes another item onto the runlist array.
|
16
|
-
#
|
17
|
-
# @param [ String ] item The runlist item as a string.
|
18
|
-
def push(item)
|
19
|
-
item = Souffle::Node::RunListItem.new(item)
|
20
|
-
super(item)
|
21
|
-
end
|
6
|
+
# Pushes another runlist item onto the runlist array.
|
7
|
+
#
|
8
|
+
# @param [ String ] item The runlist item as a string.
|
9
|
+
def <<(item)
|
10
|
+
item = Souffle::Node::RunListItem.new(item)
|
11
|
+
super(item)
|
12
|
+
end
|
22
13
|
|
14
|
+
# Pushes another item onto the runlist array.
|
15
|
+
#
|
16
|
+
# @param [ String ] item The runlist item as a string.
|
17
|
+
def push(item)
|
18
|
+
item = Souffle::Node::RunListItem.new(item)
|
19
|
+
super(item)
|
23
20
|
end
|
21
|
+
|
24
22
|
end
|
@@ -1,46 +1,53 @@
|
|
1
1
|
require 'souffle/node/runlist_parser'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
class Node::RunListItem
|
3
|
+
# A single runlist item, most be parsed and either a recipe or role.
|
4
|
+
class Souffle::Node::RunListItem
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
6
|
+
# Creates a new runlist item from a string.
|
7
|
+
#
|
8
|
+
# @param [ String ] item The runlist string to turn into an object.
|
9
|
+
# @raise [ InvalidRunlistName, InvalidRunlistType ] Raises exceptions when
|
10
|
+
# the runlist item or type isn't a proper chef role or recipe.
|
11
|
+
def initialize(item=nil)
|
12
|
+
@original_item = item
|
13
|
+
@item = Souffle::Node::RunListParser.parse(item)
|
14
|
+
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
# Returns the name of the runlist item.
|
17
|
+
#
|
18
|
+
# @return [ String ] The name of the runlist item.
|
19
|
+
def name
|
20
|
+
@item["name"]
|
21
|
+
end
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
# Returns the type of the runlist item.
|
24
|
+
#
|
25
|
+
# @return [ String ] The type of the runlist item.
|
26
|
+
def type
|
27
|
+
@item["type"]
|
28
|
+
end
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
# Returns the RunListItem as it's original string.
|
31
|
+
def to_s
|
32
|
+
@original_item
|
33
|
+
end
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
35
|
+
# Overriding the default equality comparator to use string representation.
|
36
|
+
#
|
37
|
+
# @param [ Souffle::Node::RunListItem ] runlist_item
|
38
|
+
#
|
39
|
+
# @return [ Boolean ] Whether or not the objects are equal.
|
40
|
+
def ==(runlist_item)
|
41
|
+
self.to_s == runlist_item.to_s
|
42
|
+
end
|
44
43
|
|
44
|
+
# Overriding the default equality comparator to use string representation.
|
45
|
+
#
|
46
|
+
# @param [ Souffle::Node::RunListItem ] runlist_item
|
47
|
+
#
|
48
|
+
# @return [ Boolean ] Whether or not the objects are equal.
|
49
|
+
def eql?(runlist_item)
|
50
|
+
self.to_s == runlist_item.to_s
|
45
51
|
end
|
52
|
+
|
46
53
|
end
|
@@ -1,73 +1,71 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class Node::RunListParser
|
1
|
+
# The runlist parser singleton.
|
2
|
+
class Souffle::Node::RunListParser
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
# The runlist match parser
|
5
|
+
PARSER = %r{
|
6
|
+
(?<type> (recipe|role)) {0} # The runlist item type.
|
7
|
+
(?<name> (.*)) {0} # The runlist item name.
|
9
8
|
|
10
|
-
|
11
|
-
|
9
|
+
\g<type>\[\g<name>\]
|
10
|
+
}x
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
12
|
+
class << self
|
13
|
+
# Checks to see whether the runlist item is a valid recipe or role.
|
14
|
+
#
|
15
|
+
# @param [ String ] item The runlist item.
|
16
|
+
#
|
17
|
+
# @return [ Hash ] The runlist item as a hash.
|
18
|
+
def parse(item)
|
19
|
+
runlist_hash = hashify_match(PARSER.match(item))
|
20
|
+
gaurentee_valid_keys(runlist_hash)
|
21
|
+
gaurentee_name_is_word(runlist_hash)
|
22
|
+
runlist_hash
|
23
|
+
end
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
25
|
+
# Takes the matches and converts them into a hashed version.
|
26
|
+
#
|
27
|
+
# @param [ MatchData,NilClass ] match The MatchData to hashify.
|
28
|
+
#
|
29
|
+
# @return [ Hash,NilClass ] The hashified version of the runlist item.
|
30
|
+
def hashify_match(match)
|
31
|
+
return nil if match.nil?
|
32
|
+
Hash[*match.names.zip(match.captures).flatten]
|
33
|
+
end
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
if runlist_hash["name"].nil? or runlist_hash["name"].empty?
|
49
|
-
raise Souffle::Exceptions::InvalidRunlistName,
|
50
|
-
"Name cannot be nil or empty."
|
51
|
-
end
|
52
|
-
if runlist_hash["type"].nil? or runlist_hash["type"].empty?
|
53
|
-
raise Souffle::Exceptions::InvalidRunlistType,
|
54
|
-
"Type cannot be nil or empty and must be one of (role|recipe)"
|
55
|
-
end
|
35
|
+
# Tests whether the runlist_hash name and type are valid.
|
36
|
+
#
|
37
|
+
# @param [ Hash ] runlist_hash The runlist hash to test.
|
38
|
+
#
|
39
|
+
# @raise [ InvalidRunlistName, InvalidRunlistType ] Raises exceptions
|
40
|
+
# when the runlist match failed, the type wasn't a recipe or role,
|
41
|
+
# or when the name itself isn't a valid word.
|
42
|
+
def gaurentee_valid_keys(runlist_hash)
|
43
|
+
if runlist_hash.nil?
|
44
|
+
raise Souffle::Exceptions::InvalidRunlistType,
|
45
|
+
"Type must be one of (role|recipe)"
|
56
46
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
m = /[A-Za-z0-9_:]+/
|
65
|
-
unless m.match(runlist_hash["name"])[0] == runlist_hash["name"]
|
66
|
-
raise Souffle::Exceptions::InvalidRunlistName,
|
67
|
-
"Name must be [A-Za-z0-9_:]."
|
68
|
-
end
|
47
|
+
if runlist_hash["name"].nil? or runlist_hash["name"].empty?
|
48
|
+
raise Souffle::Exceptions::InvalidRunlistName,
|
49
|
+
"Name cannot be nil or empty."
|
50
|
+
end
|
51
|
+
if runlist_hash["type"].nil? or runlist_hash["type"].empty?
|
52
|
+
raise Souffle::Exceptions::InvalidRunlistType,
|
53
|
+
"Type cannot be nil or empty and must be one of (role|recipe)"
|
69
54
|
end
|
70
55
|
end
|
71
56
|
|
57
|
+
# Checks whether the runlist_hash is a valid word.
|
58
|
+
#
|
59
|
+
# @param [ Hash ] runlist_hash The runlist hash to test.
|
60
|
+
#
|
61
|
+
# @raise [ InvalidRunlistName ] Runlist Name is invalid.
|
62
|
+
def gaurentee_name_is_word(runlist_hash)
|
63
|
+
m = /[A-Za-z0-9_\-:]+/
|
64
|
+
unless m.match(runlist_hash["name"])[0] == runlist_hash["name"]
|
65
|
+
raise Souffle::Exceptions::InvalidRunlistName,
|
66
|
+
"Name must be [A-Za-z0-9_-:]."
|
67
|
+
end
|
68
|
+
end
|
72
69
|
end
|
70
|
+
|
73
71
|
end
|