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 +42 -0
- data/Gemfile +0 -1
- data/README.md +209 -1
- data/Rakefile +2 -2
- data/lib/souffle/config.rb +4 -1
- data/lib/souffle/http.rb +12 -5
- data/lib/souffle/node/runlist.rb +7 -0
- data/lib/souffle/node.rb +35 -1
- data/lib/souffle/provider/aws.rb +77 -12
- data/lib/souffle/provider.rb +26 -4
- data/lib/souffle/state.rb +6 -11
- data/lib/souffle/system.rb +13 -1
- data/lib/souffle/version.rb +1 -1
- data/lib/souffle.rb +1 -0
- data/spec/node/runlist_spec.rb +10 -0
- data/spec/node_spec.rb +20 -0
- data/spec/system_spec.rb +29 -2
- metadata +5 -22
- data/lib/souffle/redis_client.rb +0 -8
- data/lib/souffle/redis_mixin.rb +0 -40
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
data/README.md
CHANGED
@@ -1,6 +1,212 @@
|
|
1
1
|
# souffle
|
2
2
|
|
3
|
-
An orchestrator for
|
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
|
23
|
-
gem.description = %Q{An orchestrator
|
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
|
data/lib/souffle/config.rb
CHANGED
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
|
-
|
11
|
-
|
12
|
-
:
|
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
|
-
|
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
|
data/lib/souffle/node/runlist.rb
CHANGED
@@ -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
|
-
"[#{
|
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
|
data/lib/souffle/provider/aws.rb
CHANGED
@@ -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="
|
54
|
-
|
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
|
-
|
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
|
-
# @
|
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 ]
|
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.
|
546
|
-
solo_config <<
|
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 =
|
553
|
-
|
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 [
|
571
|
-
# @param [
|
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
|
-
# @
|
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
|
data/lib/souffle/provider.rb
CHANGED
@@ -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
|
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=
|
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}
|
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 '
|
1
|
+
require 'mixlib/config'
|
2
2
|
|
3
|
+
# The singleton state object for the souffle server.
|
3
4
|
class Souffle::State
|
4
|
-
|
5
|
+
extend Mixlib::Config
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
data/lib/souffle/system.rb
CHANGED
@@ -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 =
|
145
|
+
node.options = n[:options]
|
134
146
|
node.options[:attributes] ||= Hash.new
|
135
147
|
sys.add(node)
|
136
148
|
end
|
data/lib/souffle/version.rb
CHANGED
data/lib/souffle.rb
CHANGED
data/spec/node/runlist_spec.rb
CHANGED
@@ -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.
|
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-
|
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
|
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
|
451
|
+
summary: An orchestrator for describing and building entire systems with Chef
|
469
452
|
test_files: []
|
470
453
|
has_rdoc:
|
data/lib/souffle/redis_client.rb
DELETED
data/lib/souffle/redis_mixin.rb
DELETED
@@ -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
|