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 +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
|