hubcap 0.0.1 → 0.0.2
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/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
|