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