souffle 0.0.2 → 0.0.3

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