hubcap 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +35 -9
- data/hubcap.gemspec +2 -1
- data/lib/hubcap/group.rb +72 -21
- data/lib/hubcap/hub.rb +5 -6
- data/lib/hubcap/recipes/puppet.rb +2 -2
- data/lib/hubcap/server.rb +2 -2
- data/lib/hubcap/version.rb +1 -1
- data/test/data/readme.rb +3 -3
- data/test/unit/test_group.rb +16 -1
- metadata +27 -41
data/README.md
CHANGED
@@ -15,7 +15,32 @@ manifests, plus a special host-specific file, and applies it to the server.
|
|
15
15
|
(This is sometimes called "masterless Puppet". It has a lot of benefits that
|
16
16
|
derive from decentralization and pushing changes on-demand.)
|
17
17
|
|
18
|
-
Here's
|
18
|
+
Here's a really simple infrastructure configuration file:
|
19
|
+
|
20
|
+
group('us') {
|
21
|
+
server('app.example.com') {
|
22
|
+
role(:app)
|
23
|
+
cap_attribute(:primary => true)
|
24
|
+
}
|
25
|
+
server('db.example.com') {
|
26
|
+
role(:db)
|
27
|
+
cap_attribute(:no_release => true)
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
group('au') {
|
32
|
+
server('example.com.au') {
|
33
|
+
role(:app, :db)
|
34
|
+
cap_attribute(:primary => true)
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
Using this config, you could tell Capistrano to deploy to all servers, servers
|
39
|
+
in one group, or just a single server.
|
40
|
+
|
41
|
+
Here's a more advanced example - an application that can be deployed to a set
|
42
|
+
of *staging* servers or a larger set of *production* servers. It has special
|
43
|
+
parameters that Puppet will use.
|
19
44
|
|
20
45
|
# An application called 'readme' that uses Cap's default deployment recipe.
|
21
46
|
application('readme', :recipes => 'deploy') {
|
@@ -39,9 +64,9 @@ Here's what your config file might look like:
|
|
39
64
|
param(
|
40
65
|
'exception_subject_prefix' => '[PRODUCTION] ',
|
41
66
|
'env' => {
|
42
|
-
|
43
|
-
|
44
|
-
|
67
|
+
'FORCE_SSL' => true,
|
68
|
+
'S3_KEY' => 'AKIAKJRK23943202JK',
|
69
|
+
'S3_SECRET' => 'KDJkaddsalkjfkawjri32jkjaklvjgakljkj'
|
45
70
|
}
|
46
71
|
)
|
47
72
|
|
@@ -66,7 +91,8 @@ Here's what your config file might look like:
|
|
66
91
|
}
|
67
92
|
|
68
93
|
|
69
|
-
Save this as `
|
94
|
+
Save this as `example.rb` in a `hub` subdirectory of the location of your
|
95
|
+
`Capfile`.
|
70
96
|
|
71
97
|
Run:
|
72
98
|
|
@@ -83,7 +109,7 @@ your filter:
|
|
83
109
|
$ `hubcap example.production.db servers:tree`
|
84
110
|
|
85
111
|
|
86
|
-
|
112
|
+
### Working with Puppet
|
87
113
|
|
88
114
|
You should have your Puppet modules in a git repository. The location of this
|
89
115
|
repository should be specified in your Capfile with
|
@@ -101,7 +127,7 @@ Once that's done, you can deploy your app in the usual way:
|
|
101
127
|
|
102
128
|
|
103
129
|
|
104
|
-
|
130
|
+
### The Hubcap DSL
|
105
131
|
|
106
132
|
The Hubcap DSL is very simple. This is the basic set of statements:
|
107
133
|
|
@@ -136,7 +162,7 @@ list of classes and parameters for a specific host. More info here:
|
|
136
162
|
http://docs.puppetlabs.com/guides/external_nodes.html
|
137
163
|
|
138
164
|
|
139
|
-
|
165
|
+
### Hubcap as a library
|
140
166
|
|
141
167
|
If you'd rather run `cap` than `hubcap`, you can load your hub configuration
|
142
168
|
directly in your `Capfile`. Add this to the end of the file:
|
@@ -159,7 +185,7 @@ with something like this in your `Capfile`.
|
|
159
185
|
require('hubcap')
|
160
186
|
Hubcap.load(target, 'hub').configure_capistrano(self)
|
161
187
|
else
|
162
|
-
warn(
|
188
|
+
warn('NB: No servers specified. Target a Hubcap group with TO.')
|
163
189
|
end
|
164
190
|
|
165
191
|
In this set-up, you'd run `cap` like this:
|
data/hubcap.gemspec
CHANGED
@@ -4,7 +4,8 @@ require File.expand_path('../lib/hubcap/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ['Joseph Pearson']
|
6
6
|
gem.email = ['joseph@booki.sh']
|
7
|
-
gem.description = '
|
7
|
+
gem.description = 'Create a hub for your server configuration. '+
|
8
|
+
'Use it with Capistrano, Puppet and others.'
|
8
9
|
gem.summary = 'Hubcap Capistrano/Puppet extension'
|
9
10
|
gem.homepage = ''
|
10
11
|
|
data/lib/hubcap/group.rb
CHANGED
@@ -1,14 +1,6 @@
|
|
1
1
|
class Hubcap::Group
|
2
2
|
|
3
|
-
attr_reader(
|
4
|
-
:name,
|
5
|
-
:cap_attributes,
|
6
|
-
:cap_roles,
|
7
|
-
:puppet_roles,
|
8
|
-
:params,
|
9
|
-
:parent,
|
10
|
-
:children
|
11
|
-
)
|
3
|
+
attr_reader(:name, :parent, :children)
|
12
4
|
|
13
5
|
# Supply the parent group, the name of this new group and a block of code to
|
14
6
|
# evaluate in the context of this new group.
|
@@ -18,14 +10,14 @@ class Hubcap::Group
|
|
18
10
|
#
|
19
11
|
def initialize(parent, name, &blk)
|
20
12
|
@name = name.to_s
|
21
|
-
|
22
|
-
|
23
|
-
@cap_roles = parent.cap_roles.clone
|
24
|
-
@puppet_roles = parent.puppet_roles.clone
|
25
|
-
@params = parent.params.clone
|
26
|
-
elsif !kind_of?(Hubcap::Hub)
|
13
|
+
@parent = parent
|
14
|
+
unless @parent || kind_of?(Hubcap::Hub)
|
27
15
|
raise(Hubcap::GroupWithoutParent, self.inspect)
|
28
16
|
end
|
17
|
+
@cap_attributes = {}
|
18
|
+
@cap_roles = []
|
19
|
+
@puppet_roles = []
|
20
|
+
@params = {}
|
29
21
|
@children = []
|
30
22
|
instance_eval(&blk) if blk && processable?
|
31
23
|
end
|
@@ -58,14 +50,13 @@ class Hubcap::Group
|
|
58
50
|
|
59
51
|
|
60
52
|
# Indicates whether we should process this group. We process all groups that
|
61
|
-
# match the
|
53
|
+
# match any of the filters or are below the furthest point in the filter.
|
62
54
|
#
|
63
55
|
# "Match the filter" means that this group's history and the filter are
|
64
56
|
# identical to the end of the shortest of the two arrays.
|
65
57
|
#
|
66
58
|
def processable?
|
67
|
-
|
68
|
-
history.slice(0,s) == hub.filter.slice(0,s)
|
59
|
+
hub.filters.any? { |fr| matching_filter?(fr) }
|
69
60
|
end
|
70
61
|
|
71
62
|
|
@@ -77,7 +68,7 @@ class Hubcap::Group
|
|
77
68
|
# filter, but identical at each point in the filter.
|
78
69
|
#
|
79
70
|
def collectable?
|
80
|
-
|
71
|
+
hub.filters.any? { |fr| history.size >= fr.size && matching_filter?(fr) }
|
81
72
|
end
|
82
73
|
|
83
74
|
|
@@ -167,8 +158,45 @@ class Hubcap::Group
|
|
167
158
|
# params(:foo => 'bar')
|
168
159
|
# ...then Puppet will have a top-level variable called $foo, containing 'bar'.
|
169
160
|
#
|
161
|
+
# Note that hash keys that are not strings or symbols will raise an error,
|
162
|
+
# and symbol keys will be converted to strings. ie, :foo becomes 'foo' in the
|
163
|
+
# above example.
|
164
|
+
#
|
170
165
|
def param(hash)
|
171
|
-
|
166
|
+
hash.each_key { |k|
|
167
|
+
unless k.kind_of?(String) || k.kind_of?(Symbol)
|
168
|
+
raise(Hubcap::InvalidParamKeyType, k.inspect)
|
169
|
+
end
|
170
|
+
}
|
171
|
+
|
172
|
+
recurse = lambda { |dest, src|
|
173
|
+
src.each_pair { |k, v|
|
174
|
+
v = recurse.call({}, v) if v.is_a?(Hash)
|
175
|
+
dest.update(k.to_s => v)
|
176
|
+
}
|
177
|
+
dest
|
178
|
+
}
|
179
|
+
recurse.call(@params, hash)
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
def cap_attributes
|
184
|
+
@parent ? @parent.cap_attributes.merge(@cap_attributes) : @cap_attributes
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def cap_roles
|
189
|
+
@parent ? @parent.cap_roles + @cap_roles : @cap_roles
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
def puppet_roles
|
194
|
+
@parent ? @parent.puppet_roles + @puppet_roles : @puppet_roles
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
def params
|
199
|
+
@parent ? @parent.params.merge(@params) : @params
|
172
200
|
end
|
173
201
|
|
174
202
|
|
@@ -199,9 +227,15 @@ class Hubcap::Group
|
|
199
227
|
def tree(indent = " ")
|
200
228
|
outs = [self.class.name.split('::').last.upcase, "Name: #{@name}"]
|
201
229
|
outs << "Atts: #{@cap_attributes.inspect}" if @cap_attributes.any?
|
230
|
+
if @cap_roles == @puppet_roles
|
231
|
+
outs << "Role: #{@cap_roles.inspect}" if @cap_roles.any?
|
232
|
+
else
|
233
|
+
cr = @cap_roles.any? ? 'Cap - '+@cap_roles.inspect : nil
|
234
|
+
pr = @puppet_roles.any? ? 'Puppet - '+@puppet_roles.inspect : nil
|
235
|
+
outs << "Role: #{[cr,pr].compact.join(' ')}" if cr || pr
|
236
|
+
end
|
202
237
|
outs << "Pram: #{@params.inspect}" if @params.any?
|
203
238
|
extend_tree(outs) if respond_to?(:extend_tree)
|
204
|
-
outs << ""
|
205
239
|
if @children.any?
|
206
240
|
@children.each { |child| outs << child.tree(indent+" ") }
|
207
241
|
end
|
@@ -218,6 +252,23 @@ class Hubcap::Group
|
|
218
252
|
end
|
219
253
|
|
220
254
|
|
255
|
+
def matching_filter?(fr)
|
256
|
+
s = [history.size, fr.size].min
|
257
|
+
history.slice(0, s) == fr.slice(0, s)
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
def resolv(*names)
|
262
|
+
require 'resolv'
|
263
|
+
if names.size == 1
|
264
|
+
Resolv.getaddress(names.first)
|
265
|
+
else
|
266
|
+
names.collect { |name| Resolv.getaddress(name) }
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
|
221
271
|
class Hubcap::GroupWithoutParent < StandardError; end
|
272
|
+
class Hubcap::InvalidParamKeyType < StandardError; end
|
222
273
|
|
223
274
|
end
|
data/lib/hubcap/hub.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
class Hubcap::Hub < Hubcap::Group
|
2
4
|
|
3
|
-
attr_reader(:
|
5
|
+
attr_reader(:filters, :applications, :servers, :groups, :cap_sets)
|
4
6
|
|
5
7
|
|
6
8
|
def initialize(filter_string)
|
7
|
-
@
|
9
|
+
@filters = filter_string.split(',').collect { |fltr| fltr.split('.') }
|
10
|
+
@filters = [[]] if @filters.empty?
|
8
11
|
@cap_sets = {}
|
9
12
|
@cap_set_clashes = []
|
10
|
-
@cap_attributes = {}
|
11
|
-
@cap_roles = []
|
12
|
-
@puppet_roles = []
|
13
|
-
@params = {}
|
14
13
|
@applications = []
|
15
14
|
@servers = []
|
16
15
|
@groups = []
|
@@ -93,7 +93,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
93
93
|
out = case text
|
94
94
|
when /\bpassword.*:/i, /passphrase/i # Git password or SSH passphrase.
|
95
95
|
"#{puppet_git_password}\n"
|
96
|
-
when %r{\(yes
|
96
|
+
when %r{\(yes\/no\)} # Should git connect?
|
97
97
|
"yes\n"
|
98
98
|
when /accept \(t\)emporarily/ # Should git accept certificate?
|
99
99
|
"t\n"
|
@@ -101,7 +101,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
101
101
|
channel.send_data(out) if out
|
102
102
|
}
|
103
103
|
sudo("mkdir -p #{File.dirname(puppet_path)}")
|
104
|
-
sudo("chown #{user} #{File.dirname(puppet_path)}")
|
104
|
+
sudo("chown -R #{user} #{File.dirname(puppet_path)}")
|
105
105
|
run(
|
106
106
|
"[ -d #{puppet_path} ] || git clone #{puppet_repository} #{puppet_path}",
|
107
107
|
:shell => nil,
|
data/lib/hubcap/server.rb
CHANGED
data/lib/hubcap/version.rb
CHANGED
data/test/data/readme.rb
CHANGED
@@ -20,9 +20,9 @@ application('readme', :recipes => 'deploy') {
|
|
20
20
|
param(
|
21
21
|
'exception_subject_prefix' => '[PRODUCTION] ',
|
22
22
|
'env' => {
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
'FORCE_SSL' => true,
|
24
|
+
'S3_KEY' => 'AKIAKJRK23943202JK',
|
25
|
+
'S3_SECRET' => 'KDJkaddsalkjfkawjri32jkjaklvjgakljkj'
|
26
26
|
}
|
27
27
|
)
|
28
28
|
|
data/test/unit/test_group.rb
CHANGED
@@ -30,7 +30,7 @@ class Hubcap::TestGroup < Test::Unit::TestCase
|
|
30
30
|
hub = Hubcap.hub {
|
31
31
|
group('test') { absorb('test/data/parts/foo_param') }
|
32
32
|
}
|
33
|
-
assert_equal('foo', hub.groups.first.params[
|
33
|
+
assert_equal('foo', hub.groups.first.params['foo'])
|
34
34
|
end
|
35
35
|
|
36
36
|
|
@@ -171,6 +171,21 @@ class Hubcap::TestGroup < Test::Unit::TestCase
|
|
171
171
|
}
|
172
172
|
assert_equal(1, hub.servers.first.params['foo'])
|
173
173
|
assert_equal(2, hub.servers.first.params['baz'])
|
174
|
+
|
175
|
+
# Recursive stringification of hash keys.
|
176
|
+
hub = Hubcap.hub {
|
177
|
+
server('test') {
|
178
|
+
param(:foo => { :bar => { :garply => 'grault' } })
|
179
|
+
}
|
180
|
+
}
|
181
|
+
assert_equal('foo', hub.servers.first.params.keys.first)
|
182
|
+
assert_equal('bar', hub.servers.first.params['foo'].keys.first)
|
183
|
+
assert_equal('garply', hub.servers.first.params['foo']['bar'].keys.first)
|
184
|
+
|
185
|
+
# Top-level keys other than strings or symbols are rejected.
|
186
|
+
assert_raises(Hubcap::InvalidParamKeyType) {
|
187
|
+
hub = Hubcap.hub { server('test') { param(1 => 'x') } }
|
188
|
+
}
|
174
189
|
end
|
175
190
|
|
176
191
|
end
|
metadata
CHANGED
@@ -1,33 +1,25 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: hubcap
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 0
|
8
|
-
- 1
|
9
|
-
version: 0.0.1
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
7
|
+
authors:
|
12
8
|
- Joseph Pearson
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
date: 2012-09-20 00:00:00 +10:00
|
18
|
-
default_executable:
|
12
|
+
date: 2012-09-26 00:00:00.000000000 Z
|
19
13
|
dependencies: []
|
20
|
-
|
21
|
-
|
22
|
-
email:
|
14
|
+
description: Create a hub for your server configuration. Use it with Capistrano, Puppet
|
15
|
+
and others.
|
16
|
+
email:
|
23
17
|
- joseph@booki.sh
|
24
|
-
executables:
|
18
|
+
executables:
|
25
19
|
- hubcap
|
26
20
|
extensions: []
|
27
|
-
|
28
21
|
extra_rdoc_files: []
|
29
|
-
|
30
|
-
files:
|
22
|
+
files:
|
31
23
|
- Capfile.example
|
32
24
|
- README.md
|
33
25
|
- Rakefile
|
@@ -51,37 +43,31 @@ files:
|
|
51
43
|
- test/unit/test_hub.rb
|
52
44
|
- test/unit/test_hubcap.rb
|
53
45
|
- test/unit/test_server.rb
|
54
|
-
|
55
|
-
homepage: ""
|
46
|
+
homepage: ''
|
56
47
|
licenses: []
|
57
|
-
|
58
48
|
post_install_message:
|
59
49
|
rdoc_options: []
|
60
|
-
|
61
|
-
require_paths:
|
50
|
+
require_paths:
|
62
51
|
- lib
|
63
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
requirements:
|
72
|
-
- -
|
73
|
-
- !ruby/object:Gem::Version
|
74
|
-
|
75
|
-
- 0
|
76
|
-
version: "0"
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
77
64
|
requirements: []
|
78
|
-
|
79
65
|
rubyforge_project:
|
80
|
-
rubygems_version: 1.
|
66
|
+
rubygems_version: 1.8.11
|
81
67
|
signing_key:
|
82
68
|
specification_version: 3
|
83
69
|
summary: Hubcap Capistrano/Puppet extension
|
84
|
-
test_files:
|
70
|
+
test_files:
|
85
71
|
- test/data/example.rb
|
86
72
|
- test/data/parts/foo_param.rb
|
87
73
|
- test/data/readme.rb
|