souffle 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|