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 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 what your config file might look like:
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
- "FORCE_SSL" => true,
43
- "S3_KEY" => "AKIAKJRK23943202JK",
44
- "S3_SECRET" => "KDJkaddsalkjfkawjri32jkjaklvjgakljkj"
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 `hub/example.rb`.
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
- ## Working with Puppet
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
- ## The Hubcap DSL
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
- ## Hubcap as a library
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("NB: No servers specified. Target a Hubcap group with TO.")
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 = 'Unite Capistrano and Puppet config in one Ruby file.'
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
- if @parent = parent
22
- @cap_attributes = parent.cap_attributes.clone
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 filter or are below the furthest point in the filter.
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
- s = [history.length, hub.filter.length].min
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
- (history.length >= hub.filter.length) && processable?
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
- @params.update(hash)
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(:filter, :applications, :servers, :groups, :cap_sets)
5
+ attr_reader(:filters, :applications, :servers, :groups, :cap_sets)
4
6
 
5
7
 
6
8
  def initialize(filter_string)
7
- @filter = filter_string.split('.')
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/no\)} # Should git connect?
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
@@ -35,8 +35,8 @@ class Hubcap::Server < Hubcap::Group
35
35
 
36
36
  def yaml
37
37
  {
38
- 'classes' => @puppet_roles.collect(&:to_s),
39
- 'parameters' => @params
38
+ 'classes' => puppet_roles.collect(&:to_s),
39
+ 'parameters' => params
40
40
  }.to_yaml
41
41
  end
42
42
 
@@ -1,5 +1,5 @@
1
1
  module Hubcap
2
2
 
3
- VERSION = '0.0.1'
3
+ VERSION = '0.0.2'
4
4
 
5
5
  end
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
- "FORCE_SSL" => true,
24
- "S3_KEY" => "AKIAKJRK23943202JK",
25
- "S3_SECRET" => "KDJkaddsalkjfkawjri32jkjaklvjgakljkj"
23
+ 'FORCE_SSL' => true,
24
+ 'S3_KEY' => 'AKIAKJRK23943202JK',
25
+ 'S3_SECRET' => 'KDJkaddsalkjfkawjri32jkjaklvjgakljkj'
26
26
  }
27
27
  )
28
28
 
@@ -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[:foo])
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
- prerelease: false
5
- segments:
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
- description: Unite Capistrano and Puppet config in one Ruby file.
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
- has_rdoc: true
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
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- segments:
68
- - 0
69
- version: "0"
70
- required_rubygems_version: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- segments:
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.3.6
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