souffle 0.0.2 → 0.0.3

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/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+
4
+ ### v0.0.1
5
+
6
+ * Application framework laid out
7
+ * Initial node state machine
8
+ * Initial system state machine
9
+ * Added AWS provider
10
+ * Initial command line interface
11
+ * Added daemonization
12
+ * Added singleton configuration
13
+ * Initial runlist parser
14
+ * Initial dependency traversal
15
+ * System rebalancing (pre-provisioning)
16
+ * Added REST API
17
+
18
+ ### v0.0.2
19
+
20
+ * AWS provider
21
+ * Added EBS support
22
+ * Added RAID support
23
+ * Added LVM Support
24
+ * Added support for chef-solo provisioning
25
+ * Switched from `Puma` to `Thin`
26
+ * Updated REST API
27
+ * Updated command line interface
28
+ * Initial Documentation
29
+ * Optimized AWS polling mechanism
30
+ * Optimized AWS creation ordering
31
+
32
+ ### v0.0.3
33
+
34
+ * Multiple documentation updates
35
+ * AWS provider
36
+ * Tags now use `hex(4)` instead of `hex(6)`
37
+ * Deprecated default tag prefix
38
+ * Added chef roles support to providers
39
+ * Fixed /version url link
40
+ * Added `tag`, `domain`, and `fqdn` node helpers
41
+ * Bugfix for repo_path
42
+ * Hostname is now set at provisioning
data/Gemfile CHANGED
@@ -12,7 +12,6 @@ gem "em-synchrony", "~> 0.2.0"
12
12
 
13
13
  gem "right_aws", "~> 3.0.4"
14
14
  gem "tilt", "~> 1.3.3"
15
- gem "redis", "~> 3.0.1"
16
15
 
17
16
  gem "mixlib-cli", ">= 1.2.2"
18
17
  gem "mixlib-config", ">= 1.1.0"
data/README.md CHANGED
@@ -1,6 +1,212 @@
1
1
  # souffle
2
2
 
3
- An orchestrator for setting up isolated chef-managed systems.
3
+ An orchestrator for describing and building entire systems with [Chef](https://github.com/opscode/chef).
4
+
5
+ Currently we only support `AWS`, however we intend to add support for `Vagrant` and `Rackspace` quite soon.
6
+
7
+ ## Setup
8
+
9
+ Example configuration file (/etc/souffle/souffle.rb):
10
+
11
+ ```ruby
12
+ rack_environment "production"
13
+
14
+ aws_access_key "160147B34F7DCE679A6B"
15
+ aws_access_secret "e01a4cb196b092ca8e93a5e66837bb194e86a9b1"
16
+ aws_region "us-west-2"
17
+ aws_image_id "ami-1d75574f"
18
+ aws_instance_type "c1.medium"
19
+ key_name "josh"
20
+ ```
21
+
22
+ ## CLI
23
+
24
+ The `souffle` command line client can either be run standalone (with a single json provision) or as a webserver.
25
+ Running the service as a daemon automatically starts the webserver.
26
+
27
+ Usage: souffle (options)
28
+ -c, --config CONFIG The configuration file to use
29
+ -d, --daemonize Run the application as a daemon (forces `-s`)
30
+ -E, --environment The environment profile to use
31
+ -g, --group GROUP Group to set privilege to
32
+ -j, --json JSON The json for a single provision (negates `-s`)
33
+ -l, --log_level LEVEL Set the log level (debug, info, warn, error, fatal)
34
+ -L, --logfile LOG_LOCATION Set the log file location, defaults to STDOUT
35
+ -f, --pid PID_FILE Set the PID file location, defaults to /tmp/souffle.pid
36
+ -p, --provider PROVIDER The provider to use (overrides config)
37
+ -H, --hostname HOSTNAME Hostname to listen on (default: 0.0.0.0)
38
+ -P, --port PORT Port to listen on (default: 8080)
39
+ -s, --server Start the application as a server
40
+ -u, --user USER User to set privilege to
41
+ -V, --vagrant_dir VAGRANT_DIR The path to the base vagrant vm directory
42
+ -v, --version Show souffle version
43
+ -h, --help Show this message
44
+
45
+ ## Defining a system
46
+
47
+ As an example system we'll generate two nodes that both are provisioned with `solo`, have 2 10GB `raid0` EBS drives attached and configured with `LVM`.
48
+
49
+ ```json
50
+ {
51
+ "provider": "aws",
52
+ "user": "josh",
53
+ "options": {
54
+ "domain": "mydomain.com",
55
+ "type": "solo",
56
+ "aws_ebs_size": 10,
57
+ "volume_count": 2
58
+ },
59
+ "nodes": [
60
+ {
61
+ "name": "example_repo",
62
+ "options": {
63
+ "attributes": {
64
+ "nginx": { "example_attribute": "blehk" }
65
+ },
66
+ "run_list": [ "role[nginx_server]" ]
67
+ }
68
+ },
69
+ {
70
+ "name": "example_srv",
71
+ "options": {
72
+ "attributes": {
73
+ "gem": { "source": "http://gem.mydomain.com" }
74
+ }
75
+ },
76
+ "run_list": [ "recipe[yum]", "recipe[gem]", "recipe[git]" ],
77
+ "dependencies": [ "role[nginx_server]" ]
78
+ }
79
+ ]
80
+ }
81
+ ```
82
+
83
+ ### Attributes
84
+
85
+ Attributes work in a specific-wins merge for the json configuration. If you define a `configuration option`, it's applied unless a `system` level option overrides that, which is in tern applied unless a `node` level option overrides that.
86
+
87
+ This should be a familiar concept to those of who are using [Chef](https://github.com/opscode/chef). Similar to `environments`, `roles`, and `nodes`.
88
+
89
+ #### Example
90
+
91
+ ```json
92
+ {
93
+ "options": {
94
+ "aws_ebs_size": 10,
95
+ "volume_count": 2
96
+ },
97
+ "nodes": [
98
+ {
99
+ "name": "is_overriden",
100
+ "options": {
101
+ "aws_ebs_size":20,
102
+ "volume_count": 4
103
+ }
104
+ },
105
+ {
106
+ "name": "not_overridden"
107
+ },
108
+ {
109
+ "name": "count_overridden",
110
+ "options": {
111
+ "volume_count": 6
112
+ }
113
+ },
114
+ {
115
+ "name": "ebs_overridden",
116
+ "options": {
117
+ "aws_ebs_size": 50
118
+ }
119
+ }
120
+ ]
121
+ }
122
+ ```
123
+
124
+ With the above system, we'll have four nodes and the default system-wide options:
125
+
126
+ <table>
127
+ <tr>
128
+ <th>Name</th><th>aws_ebs_size</th><th>volume_count</th>
129
+ </tr>
130
+ <tr>
131
+ <td>system (default)</td>
132
+ <td>10</td>
133
+ <td>2</td>
134
+ </tr>
135
+ <tr>
136
+ <td>is_overridden</td>
137
+ <td>20</td>
138
+ <td>4</td>
139
+ </tr>
140
+ <tr>
141
+ <td>not_overridden</td>
142
+ <td>10</td>
143
+ <td>2</td>
144
+ </tr>
145
+ <tr>
146
+ <td>count_overridden</td>
147
+ <td>10</td>
148
+ <td>6</td>
149
+ </tr>
150
+ <tr>
151
+ <td>ebs_overridden</td>
152
+ <td>50</td>
153
+ <td>2</td>
154
+ </tr>
155
+ </table>
156
+
157
+ #### Options
158
+
159
+ **Special Cases**
160
+ The `:attributes` key is representative of node-specific Chef attributes.
161
+
162
+
163
+ The options hash is used to represent provisioner-level options (AWS, Vagrant) with the exception of the attributes key.
164
+
165
+ ## REST Interface
166
+
167
+ You can start up the rest interface by starting `souffle` with the `-d` parameter. We do not currently have a web ui, however the webserver supports the following actions: `create`, `version`, `status`. The default path `/` returns the `version`.
168
+
169
+ <table>
170
+ <tr>
171
+ <th>Command</th><th>Url</th><th>Example</th>
172
+ </tr>
173
+ <tr>
174
+ <td>version</td>
175
+ <td>/, /version</td>
176
+ <td>curl -sL http://localhost:8080/</td>
177
+ </tr>
178
+ <tr>
179
+ <td>create</td>
180
+ <td>/create</td>
181
+ <td>curl -sL http://localhost:8080/create</td>
182
+ </tr>
183
+ <tr>
184
+ <td>status (all)</td>
185
+ <td>/status</td>
186
+ <td>curl -sL http://localhost:8080/status</td>
187
+ </tr>
188
+ <tr>
189
+ <td>status (specific)</td>
190
+ <td>/status/<code>system</code></td>
191
+ <td>curl -sL http://localhost:8080/status/<code>6cbb78b2925a</code></td>
192
+ </tr>
193
+ </table>
194
+
195
+ ### Creating a new system
196
+
197
+ There are two ways to create a new system, you can either create it with the `souffle` cli, or you can use the rest interface.
198
+
199
+ Both the cli and the rest interface use the standard `json` format for [defining systems](#defining-a-system).
200
+
201
+ # Running from the CLI
202
+ souffle -j /path/to/system.json
203
+
204
+ # Using cURL/HTTP
205
+ curl -H "Content-Type: application/json" -X PUT -T /path/to/system.json http://localhost:8080/create
206
+
207
+ ### Status
208
+
209
+ The `status` is returned in full `json` dump of the current system status.
4
210
 
5
211
  ## A note on tests
6
212
 
@@ -8,6 +214,8 @@ In order to avoid painfully silly charges and costs, all of the AWS tests
8
214
  that require you to pay (spinning up machines, etc), will only run if you
9
215
  have the environment variable `AWS_LIVE` set to `true`.
10
216
 
217
+ AWS_LIVE=true rake
218
+
11
219
  ## Contributing to souffle
12
220
 
13
221
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
data/Rakefile CHANGED
@@ -19,8 +19,8 @@ Jeweler::Tasks.new do |gem|
19
19
  gem.name = "souffle"
20
20
  gem.homepage = "http://github.com/seryl/souffle"
21
21
  gem.license = "MIT"
22
- gem.summary = %Q{An orchestrator to create entire chef environments}
23
- gem.description = %Q{An orchestrator to create entire chef environments}
22
+ gem.summary = %Q{An orchestrator for describing and building entire systems with Chef}
23
+ gem.description = %Q{An orchestrator for describing and building entire systems with Chef}
24
24
  gem.email = "joshtoft@gmail.com"
25
25
  gem.authors = ["Josh Toft"]
26
26
  gem.version = Souffle::VERSION
@@ -75,8 +75,11 @@ class Souffle::Config
75
75
 
76
76
  # Chef Settings
77
77
  chef_cookbook_path []
78
+ chef_role_path []
78
79
  chef_provisioner :solo
79
- chef_domain "souffle"
80
+
81
+ # Domain
82
+ domain "souffle"
80
83
 
81
84
  # Provider Settings
82
85
  provider "Vagrant"
data/lib/souffle/http.rb CHANGED
@@ -7,9 +7,11 @@ class Souffle::Http < Sinatra::Base
7
7
  before { content_type :json }
8
8
 
9
9
  # Returns the current version of souffle.
10
- get '/' do
11
- { :name => 'souffle',
12
- :version => Souffle::VERSION }.to_json
10
+ ['/', '/version'].each do |path|
11
+ get path do
12
+ { :name => 'souffle',
13
+ :version => Souffle::VERSION }.to_json
14
+ end
13
15
  end
14
16
 
15
17
  # Returns the current status of souffle.
@@ -36,8 +38,13 @@ class Souffle::Http < Sinatra::Base
36
38
  provider = Souffle::Provider::AWS.new
37
39
 
38
40
  system = Souffle::System.from_hash(data)
39
- provider.create_system(system)
41
+ system_tag = provider.create_system(system)
40
42
 
41
- { :success => true }.to_json
43
+ begin
44
+ { :success => true, :system => system_tag }.to_json
45
+ rescue Exception => e
46
+ Souffle::Log.error "#{e.message}:\n#{e.backtrace.join("\n")}"
47
+ { :success => false }.to_json
48
+ end
42
49
  end
43
50
  end
@@ -19,4 +19,11 @@ class Souffle::Node::RunList < Array
19
19
  super(item)
20
20
  end
21
21
 
22
+ # Returns the description of a run_list in hash format.
23
+ #
24
+ # @return [ Hash ] The description of a run_list in hash format.
25
+ def to_hash
26
+ map.each { |item| item.to_s }
27
+ end
28
+
22
29
  end
data/lib/souffle/node.rb CHANGED
@@ -103,6 +103,40 @@ class Souffle::Node
103
103
  #
104
104
  # @return [ String ] The logging prefix for the given node.
105
105
  def log_prefix
106
- "[#{try_opt(:tag)}: #{name}]"
106
+ "[#{tag}: #{name}]"
107
+ end
108
+
109
+ # The tag for the given node.
110
+ #
111
+ # @return [ String ] The tag for the given node.
112
+ def tag
113
+ try_opt(:tag)
114
+ end
115
+
116
+ # The top-level domain name for the given node.
117
+ #
118
+ # @return [ String ] The top-level domain name for the given node.
119
+ def domain
120
+ try_opt(:domain)
121
+ end
122
+
123
+ # The fully qualified domain name for the given node.
124
+ #
125
+ # @return [ String ] The fully qualified domain name for the given node.
126
+ def fqdn
127
+ [name, tag, domain].compact.join('.')
128
+ end
129
+
130
+ # Returns the description of a node in hash format.
131
+ #
132
+ # @return [ Hash ] The description of a node in hash format.
133
+ def to_hash
134
+ {
135
+ :name => @name,
136
+ :options => @options,
137
+ :provisioner => @provisioner,
138
+ :dependencies => @dependencies.to_hash,
139
+ :run_list => @run_list.to_hash
140
+ }
107
141
  end
108
142
  end
@@ -5,6 +5,12 @@ require 'souffle/polling_event'
5
5
 
6
6
  # Monkeypatch RightAws to support EBS delete on termination.
7
7
  class RightAws::Ec2
8
+
9
+ # Modifies an EBS device delete on termination flag.
10
+ #
11
+ # @param [ String ] instance_id The instance id to associate with the EBS.
12
+ # @param [ String ] device_name The name of the EBS device.
13
+ # @param [ Boolean ] delete_on_termination Whether or not to delete on term.
8
14
  def modify_block_device_delete_on_termination_attribute(instance_id,
9
15
  device_name, delete_on_termination)
10
16
  request_hash = {'InstanceId' => instance_id}
@@ -50,18 +56,25 @@ class Souffle::Provider::AWS < Souffle::Provider::Base
50
56
  # @param [ String ] tag_prefix The tag prefix to use.
51
57
  #
52
58
  # @return [ String ] The unique tag with prefix.
53
- def generate_tag(tag_prefix="souffle")
54
- "#{tag_prefix}-#{SecureRandom.hex(6)}"
59
+ def generate_tag(tag_prefix="sys")
60
+ if tag_prefix
61
+ "#{tag_prefix}-#{SecureRandom.hex(4)}"
62
+ else
63
+ SecureRandom.hex(4)
64
+ end
55
65
  end
56
66
 
57
67
  # Creates a system using aws as the provider.
58
68
  #
59
69
  # @param [ Souffle::System ] system The system to instantiate.
60
70
  # @param [ String ] tag_prefix The tag prefix to use for the system.
61
- def create_system(system, tag_prefix="souffle")
71
+ #
72
+ # @return [ String ] The tag for the created system.
73
+ def create_system(system, tag_prefix=nil)
62
74
  system.options[:tag] = generate_tag(tag_prefix)
63
75
  system.provisioner = Souffle::Provisioner::System.new(system, self)
64
76
  system.provisioner.initialized
77
+ system.options[:tag]
65
78
  end
66
79
 
67
80
  # Takes a list of nodes and returns the list of their aws instance_ids.
@@ -158,7 +171,7 @@ class Souffle::Provider::AWS < Souffle::Provider::Base
158
171
 
159
172
  # Wait for the machine to boot up.
160
173
  #
161
- # @parameter [ Souffle::Node ] The node to boot up.
174
+ # @param [ Souffle::Node ] node The node to boot up.
162
175
  def boot(node)
163
176
  wait_for_boot(node)
164
177
  end
@@ -465,6 +478,7 @@ class Souffle::Provider::AWS < Souffle::Provider::Base
465
478
  #
466
479
  # @todo Setup the chef/chef-solo tar gzip and ssh connections.
467
480
  def provision(node)
481
+ set_hostname(node)
468
482
  if node.try_opt(:chef_provisioner) == :solo
469
483
  provision_chef_solo(node, generate_chef_json(node))
470
484
  else
@@ -536,21 +550,35 @@ class Souffle::Provider::AWS < Souffle::Provider::Base
536
550
  end
537
551
  end
538
552
 
553
+ # Sets the hostname for the given node for the chef run.
554
+ #
555
+ # @param [ Souffle:Node ] node The node to update the hostname for.
556
+ def set_hostname(node)
557
+ local_lookup = "127.0.0.1 #{node.fqdn} #{node.name}\n"
558
+ fqdn = node.fqdn
559
+ ssh_block(node) do |ssh|
560
+ ssh.exec!("hostname '#{fqdn}'")
561
+ ssh.exec!("echo \"#{local_lookup}\" >> /etc/hosts")
562
+ ssh.exec!("echo \"HOSTNAME=#{fqdn}\" >> /etc/sysconfig/network")
563
+ end
564
+ end
565
+
539
566
  # Provisions a box using the chef_solo provisioner.
540
567
  #
541
- # @param [ String ] ipaddress The ip address of the node to provision.
568
+ # @param [ String ] node The node to provision.
542
569
  # @param [ String ] solo_json The chef solo json string to use.
543
570
  def provision_chef_solo(node, solo_json)
544
571
  rsync_file(node, @newest_cookbooks, "/tmp")
545
- solo_config = "node_name \"#{node.name}.souffle\"\n"
546
- solo_config << 'cookbook_path "/tmp/cookbooks"'
572
+ solo_config = "node_name \"#{node.fqdn}\"\n"
573
+ solo_config << "cookbook_path \"/tmp/cookbooks\"\n"
574
+ solo_config << 'role_path "/tmp/roles"'
547
575
  ssh_block(node) do |ssh|
548
576
  ssh.exec!("sleep 2; tar -zxf /tmp/cookbooks-latest.tar.gz -C /tmp")
549
577
  ssh.exec!("echo '#{solo_config}' >/tmp/solo.rb")
550
578
  ssh.exec!("echo '#{solo_json}' >/tmp/solo.json")
551
579
  ssh.exec!("chef-solo -c /tmp/solo.rb -j /tmp/solo.json")
552
- rm_files = "/tmp/cookbooks /tmp/cookbooks-latest.tar.gz"
553
- rm_files << " /tmp/solo.rb /tmp/solo.json > /tmp/chef_bootstrap"
580
+ rm_files = %w{ /tmp/cookbooks /tmp/cookbooks-latest.tar.gz
581
+ /tmp/roles /tmp/solo.rb /tmp/solo.json /tmp/chef_bootstrap }
554
582
  ssh.exec!("rm -rf #{rm_files}")
555
583
  end
556
584
  end
@@ -567,8 +595,8 @@ class Souffle::Provider::AWS < Souffle::Provider::Base
567
595
  # Rsync's a file to a remote node.
568
596
  #
569
597
  # @param [ Souffle::Node ] node The node to connect to.
570
- # @param [ Souffle::Node ] file The file to rsync.
571
- # @param [ Souffle::Node ] path The remote path to rsync.
598
+ # @param [ String ] file The file to rsync.
599
+ # @param [ String ] path The remote path to rsync.
572
600
  def rsync_file(node, file, path='.')
573
601
  n = @ec2.describe_instances(node.options[:aws_instance_id]).first
574
602
  super(n[:private_ip_address], file, path)
@@ -656,8 +684,45 @@ class Souffle::Provider::AWS < Souffle::Provider::Base
656
684
  #
657
685
  # @param [ String ] filesystem The filessytem you intend to use.
658
686
  #
659
- # @param [ String ] The filesystem formatter.
687
+ # @return [ String ] The filesystem formatter.
660
688
  def fs_formatter(filesystem)
661
689
  "mkfs.#{filesystem}"
662
690
  end
691
+
692
+ class << self
693
+ # Updates the souffle status with the latest AWS information.
694
+ #
695
+ # @param [ RightAws::Ec2 ] ec2 The ec2 object to use for the status update.
696
+ def update_status(ec2=nil)
697
+ ec2 = get_base_ec2_info if ec2.nil?
698
+ return if ec2.nil?
699
+
700
+ ec2.describe_instances(
701
+ :filters => { 'tag-key' => "souffle" }).each do |instance|
702
+ instance[:tags]["souffle"]
703
+ # TODO: ADD Status update.
704
+ end
705
+ end
706
+
707
+ # Returns the base (configured) ec2 object for status updates.
708
+ #
709
+ # @return [ RightAws::Ec2 ] The base RightAws Ec2 object.
710
+ def get_base_ec2_info
711
+ access_key = Souffle::Config[:aws_access_key]
712
+ access_secret = Souffle::Config[:aws_access_secret]
713
+ aws_region = Souffle::Config[:aws_region]
714
+
715
+ if Souffle::Config[:debug]
716
+ logger = Souffle::Log.logger
717
+ else
718
+ logger = Logger.new('/dev/null')
719
+ end
720
+
721
+ RightAws::Ec2.new(access_key, access_secret,
722
+ :region => aws_region, :logger => logger)
723
+ rescue
724
+ nil
725
+ end
726
+ end
727
+
663
728
  end
@@ -23,7 +23,7 @@ module Souffle::Provider
23
23
  end
24
24
 
25
25
  # Wait until ssh is available for the node and then connect.
26
- def boot(node, retries=50)
26
+ def boot(node)
27
27
  end
28
28
 
29
29
  # Creates a system for a given provider. Intended to be overridden.
@@ -33,7 +33,7 @@ module Souffle::Provider
33
33
  #
34
34
  # @param [ Souffle::System ] system The system to instantiate.
35
35
  # @param [ String ] tag The tag to use for the system.
36
- def create_system(system, tag="souffle")
36
+ def create_system(system, tag=nil)
37
37
  error_msg = "#{self.class.to_s}: you must override create_system"
38
38
  raise Souffle::Exceptions::Provider, error_msg
39
39
  end
@@ -63,7 +63,7 @@ module Souffle::Provider
63
63
  # @return [ String ] The chef-solo json for the particular node.
64
64
  def generate_chef_json(node)
65
65
  json_info = Hash.new
66
- json_info[:domain] = "souffle"
66
+ json_info[:domain] = node.try_opt(:domain) || "souffle"
67
67
  json_info.merge!(node.options[:attributes])
68
68
  json_info[:run_list] = node.run_list
69
69
  JSON.pretty_generate(json_info)
@@ -200,6 +200,18 @@ module Souffle::Provider
200
200
  end
201
201
  end
202
202
 
203
+ # The list of roles and their full paths.
204
+ #
205
+ # @return [ Array ] The list of roles and their full paths.
206
+ def role_paths
207
+ Array(Souffle::Config[:chef_role_path]).inject([]) do |_paths, path|
208
+ Dir.glob("#{File.expand_path(path)}/*").each do |role|
209
+ _paths << role if role[-3..-1].eql?(".rb")
210
+ end
211
+ _paths
212
+ end
213
+ end
214
+
203
215
  # Creates a new cookbook tarball for the deployment.
204
216
  #
205
217
  # @return [ String ] The path to the created tarball.
@@ -207,15 +219,19 @@ module Souffle::Provider
207
219
  tarball_name = "cookbooks-latest.tar.gz"
208
220
  temp_dir = File.join(Dir.tmpdir, "chef-cookbooks-latest")
209
221
  temp_cookbook_dir = File.join(temp_dir, "cookbooks")
222
+ temp_roles_dir = File.join(temp_dir, "roles")
210
223
  tarball_dir = "#{File.dirname(Souffle::Config[:config_file])}/tarballs"
211
224
  tarball_path = File.join(tarball_dir, tarball_name)
212
225
 
213
226
  FileUtils.mkdir_p(tarball_dir) unless File.exists?(tarball_dir)
214
227
  FileUtils.mkdir_p(temp_dir) unless File.exists?(temp_dir)
215
228
  FileUtils.mkdir(temp_cookbook_dir) unless File.exists?(temp_cookbook_dir)
229
+ FileUtils.mkdir(temp_roles_dir) unless File.exists?(temp_roles_dir)
216
230
  cookbook_paths.each { |pkg| FileUtils.cp_r(pkg, temp_cookbook_dir) }
231
+ role_paths.each { |role| FileUtils.cp(role, temp_roles_dir) }
217
232
 
218
- tar_command = "tar -C #{temp_dir} -czf #{tarball_path} ./cookbooks"
233
+ tar_command = "tar -C #{temp_dir} -czf #{tarball_path} "
234
+ tar_command << "./cookbooks ./roles"
219
235
  if EM.reactor_running?
220
236
  EM::DeferrableChildProcess.open(tar_command) do
221
237
  FileUtils.rm_rf temp_dir
@@ -226,6 +242,12 @@ module Souffle::Provider
226
242
  end
227
243
  tarball_path
228
244
  end
245
+
246
+ class << self
247
+ # Updates the souffle status with the latest provider information.
248
+ def update_status; end
249
+ end
250
+
229
251
  end
230
252
  end
231
253
 
data/lib/souffle/state.rb CHANGED
@@ -1,16 +1,11 @@
1
- require 'souffle/redis_client'
1
+ require 'mixlib/config'
2
2
 
3
+ # The singleton state object for the souffle server.
3
4
  class Souffle::State
4
- class << self
5
+ extend Mixlib::Config
5
6
 
6
- # The Souffle::State prefix for Redis.
7
- def prefix
8
- "souffle_state_"
9
- end
10
-
11
- # Returns the current system states.
12
- def status
13
- Souffle::Redis.get("#{Souffle::State.prefix}status")
14
- end
7
+ # Return the configuration itself upon inspection.
8
+ def self.inspect
9
+ configuration.inspect
15
10
  end
16
11
  end
@@ -114,6 +114,16 @@ class Souffle::System
114
114
  nil
115
115
  end
116
116
 
117
+ # Returns the description of a system in hash format.
118
+ #
119
+ # @return [ Hash ] The description of a system in hash format.
120
+ def to_hash
121
+ {
122
+ :nodes => @nodes.map { |n| n.to_hash },
123
+ :options => @options
124
+ }
125
+ end
126
+
117
127
  class << self
118
128
  # Creates a new system from a given hash.
119
129
  #
@@ -123,6 +133,8 @@ class Souffle::System
123
133
  system_hash[:options] ||= {}
124
134
 
125
135
  sys = Souffle::System.new
136
+ sys.options = system_hash[:options]
137
+
126
138
  system_hash[:nodes].each do |n|
127
139
  n[:options] ||= Hash.new
128
140
 
@@ -130,7 +142,7 @@ class Souffle::System
130
142
  node.name = n[:name]
131
143
  Array(n[:run_list]).each { |rl| node.run_list << rl }
132
144
  Array(n[:dependencies]).each { |dep| node.dependencies << dep }
133
- node.options = system_hash[:options].merge(n[:options])
145
+ node.options = n[:options]
134
146
  node.options[:attributes] ||= Hash.new
135
147
  sys.add(node)
136
148
  end
@@ -1,6 +1,6 @@
1
1
  # An orchestrator for setting up isolated chef-managed systems.
2
2
  module Souffle
3
3
  # The current souffle version.
4
- VERSION = "0.0.2"
4
+ VERSION = "0.0.3"
5
5
  end
6
6
 
data/lib/souffle.rb CHANGED
@@ -10,6 +10,7 @@ require 'souffle/ssh_monkey'
10
10
  require 'souffle/log'
11
11
  require 'souffle/exceptions'
12
12
  require 'souffle/config'
13
+ require 'souffle/state'
13
14
  require 'souffle/daemon'
14
15
  require 'souffle/node'
15
16
  require 'souffle/system'
@@ -11,4 +11,14 @@ describe "Souffle::Node::RunList" do
11
11
  rl = Souffle::Node::RunList.new
12
12
  lambda { rl << "fsjklfds" }.should raise_error
13
13
  end
14
+
15
+ it "should be able to present itself in a hash format" do
16
+ rl = Souffle::Node::RunList.new
17
+ rl << "role[dns_server]"
18
+ rl << "recipe[chef_server::rubygems_install]"
19
+
20
+ rl_hash = [ "role[dns_server]", "recipe[chef_server::rubygems_install]" ]
21
+
22
+ rl.to_hash.should eql(rl_hash)
23
+ end
14
24
  end
data/spec/node_spec.rb CHANGED
@@ -119,4 +119,24 @@ describe "Souffle::Node" do
119
119
  @node.try_opt(:example_passthru).should eql("blehk")
120
120
  Souffle::Config.configuration.delete(:example_passthru)
121
121
  end
122
+
123
+ it "should be able to create a hashed version of a node" do
124
+ node_hashed = {
125
+ :name => "testname",
126
+ :options => {
127
+ :attributes => {}
128
+ },
129
+ :provisioner => :solo,
130
+ :run_list => ["recipe[chef_server]", "recipe[blehk]"],
131
+ :dependencies => ["recipe[some_recipe]"]
132
+ }
133
+
134
+ @node.name = "testname"
135
+ @node.provisioner = :solo
136
+ @node.run_list << "recipe[chef_server]"
137
+ @node.run_list << "recipe[blehk]"
138
+ @node.dependencies << "recipe[some_recipe]"
139
+
140
+ @node.to_hash.should eql(node_hashed)
141
+ end
122
142
  end
data/spec/system_spec.rb CHANGED
@@ -223,8 +223,7 @@ describe "Souffle::System" do
223
223
  parent = new_sys.nodes.select { |n| n.name == "parent_node" }.first
224
224
  parent.children.first.name.should eql("child_node")
225
225
  parent.options.should eql({ :attributes => {}, :type => "chef-solo" })
226
- parent.children.first.options.should eql({
227
- :attributes => {}, :type => "chef" })
226
+ parent.children.first.options.should eql({ :attributes => {} })
228
227
 
229
228
  new_sys.roots.size.should eql(2)
230
229
  new_sys.dependent_nodes.size.should eql(1)
@@ -261,4 +260,32 @@ describe "Souffle::System" do
261
260
  parent.run_list.should eql(masternode.run_list)
262
261
  parent.options.should eql(masternode.options)
263
262
  end
263
+
264
+ it "should be able to generate a system hash from a working system" do
265
+ sys = {
266
+ :options => { :type => "chef" },
267
+ :nodes => [
268
+ { :name => "parent_node",
269
+ :options => { :type => "chef-solo", :attributes => {} },
270
+ :run_list => ["role[somerole]"],
271
+ :dependencies => [],
272
+ :provisioner => nil },
273
+ { :name => "lone_node",
274
+ :options => { :attributes => {} },
275
+ :run_list => ["role[bestone]"],
276
+ :dependencies => [],
277
+ :provisioner => nil },
278
+ { :name => "child_node",
279
+ :options => { :attributes => {} },
280
+ :run_list => ["recipe[base]"],
281
+ :dependencies => ["role[somerole]"],
282
+ :provisioner => nil }
283
+ ]
284
+ }
285
+
286
+ new_sys = Souffle::System.from_hash(sys)
287
+ new_hash = new_sys.to_hash
288
+ new_hash[:options].should eql(sys[:options])
289
+ new_hash[:nodes].each { |n| sys[:nodes].include?(n).should eql(true) }
290
+ end
264
291
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: souffle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-11 00:00:00.000000000 Z
12
+ date: 2012-09-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: yajl-ruby
@@ -139,22 +139,6 @@ dependencies:
139
139
  - - ~>
140
140
  - !ruby/object:Gem::Version
141
141
  version: 1.3.3
142
- - !ruby/object:Gem::Dependency
143
- name: redis
144
- requirement: !ruby/object:Gem::Requirement
145
- none: false
146
- requirements:
147
- - - ~>
148
- - !ruby/object:Gem::Version
149
- version: 3.0.1
150
- type: :runtime
151
- prerelease: false
152
- version_requirements: !ruby/object:Gem::Requirement
153
- none: false
154
- requirements:
155
- - - ~>
156
- - !ruby/object:Gem::Version
157
- version: 3.0.1
158
142
  - !ruby/object:Gem::Dependency
159
143
  name: mixlib-cli
160
144
  requirement: !ruby/object:Gem::Requirement
@@ -379,7 +363,7 @@ dependencies:
379
363
  - - ! '>='
380
364
  - !ruby/object:Gem::Version
381
365
  version: '0'
382
- description: An orchestrator to create entire chef environments
366
+ description: An orchestrator for describing and building entire systems with Chef
383
367
  email: joshtoft@gmail.com
384
368
  executables:
385
369
  - souffle
@@ -390,6 +374,7 @@ extra_rdoc_files:
390
374
  files:
391
375
  - .document
392
376
  - .rspec
377
+ - CHANGELOG.md
393
378
  - Gemfile
394
379
  - LICENSE.txt
395
380
  - README.md
@@ -417,8 +402,6 @@ files:
417
402
  - lib/souffle/provisioner.rb
418
403
  - lib/souffle/provisioner/node.rb
419
404
  - lib/souffle/provisioner/system.rb
420
- - lib/souffle/redis_client.rb
421
- - lib/souffle/redis_mixin.rb
422
405
  - lib/souffle/server.rb
423
406
  - lib/souffle/ssh_monkey.rb
424
407
  - lib/souffle/state.rb
@@ -465,6 +448,6 @@ rubyforge_project:
465
448
  rubygems_version: 1.8.24
466
449
  signing_key:
467
450
  specification_version: 3
468
- summary: An orchestrator to create entire chef environments
451
+ summary: An orchestrator for describing and building entire systems with Chef
469
452
  test_files: []
470
453
  has_rdoc:
@@ -1,8 +0,0 @@
1
- require 'souffle/redis_mixin'
2
-
3
- class Souffle::Redis
4
- extend Souffle::RedisMixin
5
-
6
- # Force initialization of the redis client (@redis).
7
- init
8
- end
@@ -1,40 +0,0 @@
1
- require 'eventmachine'
2
- require 'redis'
3
-
4
- # A singleton mixin adapter similar to mixlib/log.
5
- #
6
- # @example
7
- #
8
- # require 'souffle/redis_mixin'
9
- #
10
- # class MyRedis
11
- # extend Souffle::RedisMixin
12
- # end
13
- #
14
- # MyRedis.set('awesome', 'cool')
15
- # MyRedis.get('awesome')
16
- #
17
- module Souffle::RedisMixin
18
- attr_reader :redis
19
-
20
- # Initializes the redis client (uses synchrony if Eventmachine is running).
21
- def init(*opts)
22
- if EM.reactor_running?
23
- @redis ||= Redis.new({ :driver => :synchrony }.merge(*opts))
24
- else
25
- @redis ||= Redis.new(*opts)
26
- end
27
- end
28
-
29
- # The singleton redis object, initializes if it doesn't exist.
30
- def redis
31
- @redis || init
32
- end
33
-
34
- # Pass any other method calls to the underlying redis object created with
35
- # init. If this method is hit before the call to Souffle::RedisMixin.init
36
- # has been made, it will call Souffle::RedisMixin.init() with no arguments.
37
- def method_missing(method_symbol, *args, &blk)
38
- redis.send(method_symbol, *args, &blk)
39
- end
40
- end