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.
Files changed (49) hide show
  1. data/Gemfile +9 -3
  2. data/README.md +6 -0
  3. data/bin/{souffle-server → souffle} +0 -0
  4. data/lib/souffle.rb +8 -8
  5. data/lib/souffle/application.rb +15 -10
  6. data/lib/souffle/application/souffle-server.rb +90 -5
  7. data/lib/souffle/config.rb +88 -59
  8. data/lib/souffle/daemon.rb +156 -0
  9. data/lib/souffle/exceptions.rb +29 -17
  10. data/lib/souffle/http.rb +43 -0
  11. data/lib/souffle/log.rb +11 -14
  12. data/lib/souffle/node.rb +91 -53
  13. data/lib/souffle/node/runlist.rb +16 -18
  14. data/lib/souffle/node/runlist_item.rb +43 -36
  15. data/lib/souffle/node/runlist_parser.rb +60 -62
  16. data/lib/souffle/polling_event.rb +110 -0
  17. data/lib/souffle/provider.rb +231 -23
  18. data/lib/souffle/provider/aws.rb +654 -7
  19. data/lib/souffle/provider/vagrant.rb +42 -5
  20. data/lib/souffle/provisioner.rb +55 -0
  21. data/lib/souffle/provisioner/node.rb +157 -0
  22. data/lib/souffle/provisioner/system.rb +195 -0
  23. data/lib/souffle/redis_client.rb +8 -0
  24. data/lib/souffle/redis_mixin.rb +40 -0
  25. data/lib/souffle/server.rb +42 -8
  26. data/lib/souffle/ssh_monkey.rb +8 -0
  27. data/lib/souffle/state.rb +16 -0
  28. data/lib/souffle/system.rb +139 -37
  29. data/lib/souffle/template.rb +30 -0
  30. data/lib/souffle/templates/Vagrantfile.erb +41 -0
  31. data/lib/souffle/version.rb +6 -0
  32. data/spec/config_spec.rb +20 -0
  33. data/spec/log_spec.rb +24 -0
  34. data/spec/{runlist_parser_spec.rb → node/runlist_parser_spec.rb} +1 -1
  35. data/spec/{runlist_spec.rb → node/runlist_spec.rb} +1 -1
  36. data/spec/node_spec.rb +43 -8
  37. data/spec/provider_spec.rb +56 -0
  38. data/spec/providers/aws_provider_spec.rb +114 -0
  39. data/spec/providers/vagrant_provider_spec.rb +22 -0
  40. data/spec/provisioner_spec.rb +47 -0
  41. data/spec/spec_helper.rb +8 -0
  42. data/spec/system_spec.rb +242 -13
  43. data/spec/template_spec.rb +20 -0
  44. data/spec/templates/example_template.erb +1 -0
  45. metadata +125 -30
  46. data/bin/souffle-worker +0 -7
  47. data/lib/souffle/application/souffle-worker.rb +0 -46
  48. data/lib/souffle/providers.rb +0 -2
  49. data/lib/souffle/worker.rb +0 -14
@@ -1,21 +1,33 @@
1
- module Souffle
2
- # Souffle specific exceptions; intended for debugging purposes.
3
- class Exceptions
4
-
5
- # Application level error.
6
- class Application < RuntimeError; end
7
- # Provider level error.
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
- # Runlist Name cannot be nil or empty and must be a word [A-Za-z0-9_:].
11
- class InvalidRunlistName < RuntimeError; end
12
- # Runlist Type cannot be nil or empty and must be one of (role|recipe).
13
- class InvalidRunlistType < RuntimeError; end
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
- # Node children must respond to dependencies and run_list.
16
- class InvalidChild < RuntimeError; end
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
- # The provider must exist in souffle/providers.
19
- class InvalidProvider < RuntimeError; end
20
- end
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
@@ -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
- module Souffle
5
- # Souffle's internal logging facility.
6
- # Standardized to provide a consistent log format.
7
- class Log
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
- # Force initialization of the primary log device (@logger)
11
- init
9
+ # Force initialization of the primary log device (@logger).
10
+ init
12
11
 
13
- # Monkeypatch Formatter to allow local show_time updates.
14
- class Formatter
15
- # Allow enabling and disabling of time with a singleton.
16
- def self.show_time=(*args)
17
- Mixlib::Log::Formatter.show_time = *args
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
- module Souffle
2
- class Node; end
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
- 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
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
- state_machine :state, :initial => :uninitialized do
15
- end
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
- # 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.
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
- # 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
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
- # 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
60
+ unless @children.include? node
61
+ node.parents << self
52
62
  @children.push(node)
53
63
  end
64
+ end
54
65
 
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
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
- # 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
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
@@ -1,24 +1,22 @@
1
1
  require 'souffle/node/runlist_item'
2
2
 
3
- module Souffle
4
- # A specialized Array that handles runlist items appropriately.
5
- class Node::RunList < Array
3
+ # A specialized Array that handles runlist items appropriately.
4
+ class Souffle::Node::RunList < Array
6
5
 
7
- # Pushes another runlist item onto the runlist array.
8
- #
9
- # @param [ String ] item The runlist item as a string.
10
- def <<(item)
11
- item = Souffle::Node::RunListItem.new(item)
12
- super(item)
13
- end
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
- module Souffle
4
- # A single runlist item, most be parsed and either a recipe or role.
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
- # Creates a new runlist item from a string.
8
- #
9
- # @param [ String ] item The runlist string to turn into an object.
10
- # @raise [ InvalidRunlistName, InvalidRunlistType ] Raises exceptions when
11
- # the runlist item or type isn't a proper chef role or recipe.
12
- def initialize(item=nil)
13
- @original_item = item
14
- @item = Souffle::Node::RunListParser.parse(item)
15
- end
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
- # Returns the name of the runlist item.
18
- #
19
- # @return [ String ] The name of the runlist item.
20
- def name
21
- @item["name"]
22
- end
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
- # Returns the type of the runlist item.
25
- #
26
- # @return [ String ] The type of the runlist item.
27
- def type
28
- @item["type"]
29
- end
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
- # Returns the RunListItem as it's original string.
32
- def to_s
33
- @original_item
34
- end
30
+ # Returns the RunListItem as it's original string.
31
+ def to_s
32
+ @original_item
33
+ end
35
34
 
36
- # Overriding the default equality comparator to use string representation.
37
- #
38
- # @param [ Souffle::Node::RunListItem ] runlist_item
39
- #
40
- # @return [ true,false ] Whether or not the objects are equal.
41
- def ==(runlist_item)
42
- self.to_s == runlist_item.to_s
43
- end
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
- module Souffle
2
- # The runlist parser singleton.
3
- class Node::RunListParser
1
+ # The runlist parser singleton.
2
+ class Souffle::Node::RunListParser
4
3
 
5
- # The runlist match parser
6
- PARSER = %r{
7
- (?<type> (recipe|role)) {0} # The runlist item type.
8
- (?<name> (.*)) {0} # The runlist item name.
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
- \g<type>\[\g<name>\]
11
- }x
9
+ \g<type>\[\g<name>\]
10
+ }x
12
11
 
13
- class << self
14
- # Checks to see whether the runlist item is a valid recipe or role.
15
- #
16
- # @param [ String ] item The runlist item.
17
- #
18
- # @return [ Hash ] The runlist item as a hash.
19
- def parse(item)
20
- runlist_hash = hashify_match(PARSER.match(item))
21
- gaurentee_valid_keys(runlist_hash)
22
- gaurentee_name_is_word(runlist_hash)
23
- runlist_hash
24
- end
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
- # Takes the matches and converts them into a hashed version.
27
- #
28
- # @param [ MatchData,nil ] match The MatchData to hashify.
29
- #
30
- # @return [ Hash,nil ] The hashified version of the runlist item.
31
- def hashify_match(match)
32
- return nil if match.nil?
33
- Hash[*match.names.zip(match.captures).flatten]
34
- end
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
- # Tests whether the runlist_hash name and type are valid.
37
- #
38
- # @param [ Hash ] runlist_hash The runlist hash to test.
39
- #
40
- # @raise [ InvalidRunlistName, InvalidRunlistType ] Raises exceptions
41
- # when the runlist match failed, the type wasn't a recipe or role,
42
- # or when the name itself isn't a valid word.
43
- def gaurentee_valid_keys(runlist_hash)
44
- if runlist_hash.nil?
45
- raise Souffle::Exceptions::InvalidRunlistType,
46
- "Type must be one of (role|recipe)"
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
- # Checks whether the runlist_hash is a valid word.
59
- #
60
- # @param [ Hash ] runlist_hash The runlist hash to test.
61
- #
62
- # @raise [ InvalidRunlistName ] Runlist Name is invalid.
63
- def gaurentee_name_is_word(runlist_hash)
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