hubcap 0.0.1

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.
@@ -0,0 +1,176 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+
3
+ namespace(:puppet) do
4
+
5
+ unless exists?(:puppet_repository)
6
+ set(:puppet_repository) { raise "Required variable: puppet_repository" }
7
+ end
8
+ set(:puppet_branch, 'master')
9
+ set(:puppet_path, '/var/www/provision/puppet')
10
+ set(:puppet_git_password) { Capistrano::CLI.password_prompt }
11
+ set(:puppet_manifest_path) { "#{puppet_path}/puppet/host.pp" }
12
+ set(:puppet_modules_path) { "#{puppet_path}/puppet/modules" }
13
+ set(:puppet_yaml_path) { "#{puppet_path}/puppet/host.yaml" }
14
+ set(:puppet_enc_path) { "#{puppet_path}/puppet/enc" }
15
+ set(:puppet_enc) { "#!/bin/sh\ncat '#{puppet_yaml_path}'" }
16
+ set(:puppet_parameters, '--no-report')
17
+
18
+
19
+ desc <<-DESC
20
+ Calls 'check' and 'update' and 'properties', which means it performs a
21
+ check for the necessary puppet dependencies, deploys the puppet scripts
22
+ via git, then pushes up a special yaml file describing the properties of
23
+ this particular server.
24
+ DESC
25
+ task(:freshen) do
26
+ check
27
+ update
28
+ properties
29
+ end
30
+
31
+
32
+ desc <<-DESC
33
+ Looks for Ruby 1.9 on the server. Installs it (and git-core) if not found.
34
+ Also looks for Puppet 3.0 gem on the server, and installs it if not found.
35
+ DESC
36
+ task(:check) do
37
+ unless exists?(:hubcap_agnostic)
38
+ raise "Hubcap has not configured this Capistrano instance."
39
+ end
40
+ unless hubcap_agnostic
41
+ raise "Puppet tasks are not available in Hubcap application mode"
42
+ end
43
+
44
+ apt_cmd = [
45
+ "env",
46
+ "DEBCONF_TERSE='yes'",
47
+ "DEBIAN_PRIORITY='critical'",
48
+ "DEBIAN_FRONTEND=noninteractive",
49
+ "apt-get --force-yes -qyu"
50
+ ].join(" ")
51
+
52
+ # Because Ubuntu is weird, the 1.9.1 package installs Ruby 1.9.3-p0.
53
+ sudo_bash([
54
+ 'if [[ `which ruby` && (`ruby -v` =~ "ruby 1.9") ]]; then',
55
+ 'echo "Ruby 1.9 verified";',
56
+ 'else',
57
+ "#{apt_cmd} update;",
58
+ "#{apt_cmd} install ruby1.9.1 git-core;",
59
+ 'fi'
60
+ ].join(' '))
61
+
62
+ # 3.0.0-rc5 is the last version that a) runs on Ruby 1.9 and b) works.
63
+ ppt_ver = '3.0.0.rc5'
64
+ sudo_bash([
65
+ "if [[ `gem q -i -n \"^puppet$\" -v #{ppt_ver}` =~ \"true\" ]]; then",
66
+ 'echo "Puppet verified";',
67
+ 'else',
68
+ "gem install puppet -v #{ppt_ver} --pre --no-rdoc --no-ri;",
69
+ 'fi'
70
+ ].join(' '))
71
+
72
+ # Workaround for: http://projects.puppetlabs.com/issues/9862
73
+ sudo_bash([
74
+ 'if [[ `egrep -i "^puppet" /etc/group` =~ "puppet" ]]; then',
75
+ 'echo "Puppet group exists";',
76
+ 'else',
77
+ 'groupadd puppet;',
78
+ 'fi'
79
+ ].join(' '))
80
+ end
81
+
82
+
83
+ desc <<-DESC
84
+ Basically, this pulls down your puppet scripts so you can run them.
85
+ It does this by cloning the Hubcap repository to each server (if necessary),
86
+ then fetching the latest code and resetting to the HEAD of the
87
+ specified branch.
88
+ DESC
89
+ task(:update) do
90
+ handle_data = lambda { |channel, stream, text|
91
+ host = channel[:host]
92
+ logger.info "[#{host} :: #{stream}] #{text}"
93
+ out = case text
94
+ when /\bpassword.*:/i, /passphrase/i # Git password or SSH passphrase.
95
+ "#{puppet_git_password}\n"
96
+ when %r{\(yes/no\)} # Should git connect?
97
+ "yes\n"
98
+ when /accept \(t\)emporarily/ # Should git accept certificate?
99
+ "t\n"
100
+ end
101
+ channel.send_data(out) if out
102
+ }
103
+ sudo("mkdir -p #{File.dirname(puppet_path)}")
104
+ sudo("chown #{user} #{File.dirname(puppet_path)}")
105
+ run(
106
+ "[ -d #{puppet_path} ] || git clone #{puppet_repository} #{puppet_path}",
107
+ :shell => nil,
108
+ &handle_data
109
+ )
110
+ run(
111
+ [
112
+ "cd #{puppet_path}",
113
+ "git fetch origin",
114
+ "git reset --hard origin/#{puppet_branch}"
115
+ ].join(' && '),
116
+ :shell => nil,
117
+ &handle_data
118
+ )
119
+ end
120
+
121
+
122
+ desc <<-DESC
123
+ Pushes a YAML file containing all the classes and parameters that Puppet
124
+ needs to know about in order to provision this server. Each file is
125
+ server-specific.
126
+ DESC
127
+ task(:properties) do
128
+ hubcap.servers.each { |s|
129
+ put(s.yaml, puppet_yaml_path, :hosts => s.address)
130
+ }
131
+ put(puppet_enc, puppet_enc_path)
132
+ run("chmod +x #{puppet_enc_path}")
133
+ end
134
+
135
+
136
+ desc <<-DESC
137
+ Tells you what would happen if you ran puppet:apply. This is a safe way
138
+ to test your changes.
139
+ DESC
140
+ task(:noop) do
141
+ run_puppet('--noop')
142
+ end
143
+
144
+
145
+ desc <<-DESC
146
+ Runs the puppet scripts on each server. Be careful!
147
+ DESC
148
+ task(:apply) do
149
+ run_puppet
150
+ end
151
+
152
+
153
+ def sudo_bash(cmd, options = {}, &blk)
154
+ sudo("/bin/bash -c \'#{cmd}\'", options, &blk)
155
+ end
156
+
157
+
158
+ def run_puppet(params = nil)
159
+ sudo([
160
+ "puppet apply",
161
+ "--node_terminus exec",
162
+ "--external_nodes '#{puppet_enc_path}'",
163
+ "--modulepath '#{puppet_modules_path}'",
164
+ puppet_parameters,
165
+ params,
166
+ "'#{puppet_manifest_path}'"
167
+ ].compact.join(' '))
168
+ end
169
+
170
+
171
+ before('puppet:noop', 'puppet:freshen')
172
+ before('puppet:apply', 'puppet:freshen')
173
+
174
+ end
175
+
176
+ end
@@ -0,0 +1,21 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+
3
+ namespace(:servers) do
4
+
5
+ desc <<-DESC
6
+ Lists all the servers that match the filter used when Hubcap was loaded.
7
+ DESC
8
+ task(:list) do
9
+ puts(hubcap.servers.collect(&:tree))
10
+ end
11
+
12
+ desc <<-DESC
13
+ Show the entire Hubcap configuration tree for the given filter.
14
+ DESC
15
+ task(:tree) do
16
+ puts(hubcap.tree)
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,46 @@
1
+ class Hubcap::Server < Hubcap::Group
2
+
3
+ attr_reader(:address)
4
+
5
+
6
+ def initialize(parent, name, options = {}, &blk)
7
+ @address = options[:address] || name
8
+ super(parent, name, &blk)
9
+ end
10
+
11
+
12
+ def application(*args)
13
+ raise(Hubcap::ServerSubgroupDisallowed, 'application')
14
+ end
15
+
16
+
17
+ def server(*args)
18
+ raise(Hubcap::ServerSubgroupDisallowed, 'server')
19
+ end
20
+
21
+
22
+ def group(*args)
23
+ raise(Hubcap::ServerSubgroupDisallowed, 'group')
24
+ end
25
+
26
+
27
+ def application_parent
28
+ p = self
29
+ while p && p != hub
30
+ return p if p.kind_of?(Hubcap::Application)
31
+ p = p.instance_variable_get(:@parent)
32
+ end
33
+ end
34
+
35
+
36
+ def yaml
37
+ {
38
+ 'classes' => @puppet_roles.collect(&:to_s),
39
+ 'parameters' => @params
40
+ }.to_yaml
41
+ end
42
+
43
+
44
+ class Hubcap::ServerSubgroupDisallowed < StandardError; end
45
+
46
+ end
@@ -0,0 +1,5 @@
1
+ module Hubcap
2
+
3
+ VERSION = '0.0.1'
4
+
5
+ end
data/lib/hubcap.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'yaml'
2
+
3
+ module Hubcap
4
+
5
+ def self.hub(filter_string = '', &blk)
6
+ Hubcap::Hub.new(filter_string).tap { |scope| scope.instance_eval(&blk) }
7
+ end
8
+
9
+
10
+ def self.load(filter_string, *paths)
11
+ Hubcap::Hub.new(filter_string).tap { |scope|
12
+ while paths.any?
13
+ path = paths.shift
14
+ if File.directory?(path)
15
+ paths += Dir.glob(File.join(path, '*.rb'))
16
+ else
17
+ scope.absorb(path)
18
+ end
19
+ end
20
+ }
21
+ end
22
+
23
+ end
24
+
25
+
26
+ require 'hubcap/group'
27
+ require 'hubcap/application'
28
+ require 'hubcap/server'
29
+ require 'hubcap/hub'
@@ -0,0 +1,68 @@
1
+ application('example', :recipes => 'deploy') {
2
+
3
+ # These cap settings will apply to all the servers within this application
4
+ # group or any subgroups. (The same applies for roles, attributes, etc.)
5
+ cap_set(:repository, 'git@github.com:example/example.git')
6
+ cap_set(:branch, 'master')
7
+
8
+ # Load some ssh keys into param() from a separate (more secure?) file.
9
+ # These will be handed to your puppet scripts.
10
+ #absorb('nodes/private/admin_ssh_keys')
11
+
12
+ # Just a normal Ruby hash var we can reuse throughout the config.
13
+ common_env = {
14
+ 'PGUSER' => 'example',
15
+ 'RABBIT_URI' => 'amqp://example:password@localhost:54322'
16
+ }
17
+
18
+ # A local dev simulation.
19
+ group('vagrant') {
20
+ param('env' => common_env.merge('PGPASSWORD' => 'password'))
21
+ server('127.0.0.1:2222') {
22
+ role(:app, :db, :queue)
23
+ }
24
+ }
25
+
26
+ # Your staging environment.
27
+ group('staging') {
28
+ param('env' => common_env.merge(
29
+ 'PGPASSWORD' => 'pa55w3rd',
30
+ 'PGHOST' => '10.10.10.20',
31
+ 'RABBIT_URI' => 'amqp://example:pa55w3rd@10.10.10.20'
32
+ ))
33
+ server('app', :address => '10.10.10.10') {
34
+ role(:app)
35
+ }
36
+ server('db', :address => '10.10.10.15') {
37
+ role(:db)
38
+ }
39
+ server('queue', :address => '10.10.10.20') {
40
+ role(:queue)
41
+ }
42
+ }
43
+
44
+ # Your production environment.
45
+ group('production') {
46
+ prod_env = common_env.merge(
47
+ 'PGPASSWORD' => '1391f3a24daef0c78f75cbef9d62eb848c2d454c71a5fd2a',
48
+ 'PGHOST' => '20.20.20.20',
49
+ 'RABBIT_URI' => 'amqp://example:e18edfa58fa6c5d3a9a@20.20.20.30'
50
+ )
51
+ param('env' => prod_env)
52
+ group('app') {
53
+ role(:app)
54
+ param('env' => prod_env.merge('FORCE_SSL' => '1'))
55
+ server('app-1.example.com')
56
+ server('app-2.example.com')
57
+ server('app-3.example.com')
58
+ }
59
+ group('db') {
60
+ role(:db)
61
+ server('db-1.example.com')
62
+ server('db-2.example.com')
63
+ }
64
+ server('queue-1.example.com') {
65
+ role(:queue)
66
+ }
67
+ }
68
+ }
@@ -0,0 +1 @@
1
+ param(:foo => 'foo')
@@ -0,0 +1,47 @@
1
+ # An application called 'readme' that uses Cap's default deployment recipe.
2
+ application('readme', :recipes => 'deploy') {
3
+ # Set a capistrano variable.
4
+ cap_set('repository', 'git@github.com:joseph/readme.git')
5
+
6
+ # Declare that all servers will have the 'baseline' puppet class.
7
+ role(:puppet => 'baseline')
8
+
9
+ group('staging') {
10
+ # Puppet will have a $::exception_subject_prefix variable on these servers.
11
+ param('exception_subject_prefix' => '[STAGING] ')
12
+ # For simple staging, just one server that does everything.
13
+ server('readme.stage') {
14
+ role(:cap => [:web, :app, :db], :puppet => ['proxy', 'app', 'db'])
15
+ }
16
+ }
17
+
18
+ group('production') {
19
+ # Puppet will have these top-scope variables on all these servers.
20
+ param(
21
+ 'exception_subject_prefix' => '[PRODUCTION] ',
22
+ 'env' => {
23
+ "FORCE_SSL" => true,
24
+ "S3_KEY" => "AKIAKJRK23943202JK",
25
+ "S3_SECRET" => "KDJkaddsalkjfkawjri32jkjaklvjgakljkj"
26
+ }
27
+ )
28
+
29
+ group('proxy') {
30
+ # Servers will have the :web role and the 'proxy' puppet class.
31
+ role(:cap => :web, :puppet => 'proxy')
32
+ server('proxy-1', :address => '10.10.10.5')
33
+ }
34
+
35
+ group('app') {
36
+ # Servers will have the :app role and the 'app' puppet class.
37
+ role(:app)
38
+ server('app-1', :address => '10.10.10.10')
39
+ server('app-2', :address => '10.10.10.11')
40
+ }
41
+
42
+ group('db') {
43
+ role(:db)
44
+ server('db-1', :address => '10.10.10.50')
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,4 @@
1
+ group('simple') {
2
+ role('baseline')
3
+ server('localhost')
4
+ }
@@ -0,0 +1,2 @@
1
+ require 'hubcap'
2
+ require 'test/unit'
@@ -0,0 +1,20 @@
1
+ require 'test_helper'
2
+
3
+ class Hubcap::TestApplication < Test::Unit::TestCase
4
+
5
+ def test_recipe_paths
6
+ hub = Hubcap.hub { application('test', :recipes => 'foo') }
7
+ assert_equal(['foo'], hub.applications.first.recipe_paths)
8
+
9
+ hub = Hubcap.hub { application('test', :recipes => ['foo', 'bar']) }
10
+ assert_equal(['foo', 'bar'], hub.applications.first.recipe_paths)
11
+ end
12
+
13
+
14
+ def test_nested_application_disallowed
15
+ assert_raises(Hubcap::NestedApplicationDisallowed) {
16
+ Hubcap.hub { application('test') { application('child') } }
17
+ }
18
+ end
19
+
20
+ end
@@ -0,0 +1,176 @@
1
+ require 'test_helper'
2
+
3
+ class Hubcap::TestGroup < Test::Unit::TestCase
4
+
5
+ def test_name
6
+ hub = Hubcap.hub { group('test') }
7
+ assert_equal('test', hub.groups.first.name)
8
+ end
9
+
10
+
11
+ def test_hub
12
+ hub = Hubcap.hub { group('test') }
13
+ assert_equal(hub, hub.groups.first.hub)
14
+ end
15
+
16
+
17
+ def test_must_have_parent_unless_hub
18
+ assert_raises(Hubcap::GroupWithoutParent) { Hubcap::Group.new(nil, 'foo') }
19
+ end
20
+
21
+
22
+ def test_history
23
+ hub = Hubcap.hub { group('test') { group('child') } }
24
+ assert_equal(['test'], hub.children.first.history)
25
+ assert_equal(['test', 'child'], hub.children.first.children.first.history)
26
+ end
27
+
28
+
29
+ def test_absorb
30
+ hub = Hubcap.hub {
31
+ group('test') { absorb('test/data/parts/foo_param') }
32
+ }
33
+ assert_equal('foo', hub.groups.first.params[:foo])
34
+ end
35
+
36
+
37
+ def test_processable_and_collectable
38
+ x = {}
39
+ hub = Hubcap.hub('g1.a1') {
40
+ x[:g1] = group('g1') {
41
+ x[:a1] = application('a1') {
42
+ x[:s1] = server('s1')
43
+ }
44
+ x[:a2] = application('a2') {
45
+ x[:s2] = server('s2')
46
+ }
47
+ }
48
+ x[:g2] = group('g2') {
49
+ x[:a3] = application('a3') {
50
+ x[:s3] = server('s3')
51
+ }
52
+ }
53
+ }
54
+ assert_equal(true, x[:g1].processable?)
55
+ assert_equal(false, x[:g1].collectable?)
56
+ assert_equal(true, x[:a1].processable?)
57
+ assert_equal(true, x[:a1].collectable?)
58
+ assert_equal(true, x[:s1].processable?)
59
+ assert_equal(true, x[:s1].collectable?)
60
+ assert_equal(false, x[:g2].processable?)
61
+ assert_equal(false, x[:g2].collectable?)
62
+ assert_nil(x[:a3])
63
+ assert_nil(x[:s3])
64
+ end
65
+
66
+
67
+ def test_cap_set
68
+ # Single value, as key, val arguments
69
+ hub = Hubcap.hub { group('test') { cap_set(:foo, 'bar') } }
70
+ assert_equal('bar', hub.cap_sets[:foo])
71
+
72
+ # Single value, as one hash argument
73
+ hub = Hubcap.hub { group('test') { cap_set('baz' => 'yyy') } }
74
+ assert_equal('yyy', hub.cap_sets['baz'])
75
+
76
+ # Multiple values
77
+ hub = Hubcap.hub { group('test') { cap_set(:a => 1, :z => 0) } }
78
+ assert_equal(1, hub.cap_sets[:a])
79
+ assert_equal(0, hub.cap_sets[:z])
80
+
81
+ # Lazily-evaluated block
82
+ hub = Hubcap.hub { group('test') { cap_set(:blk) { 'garply' } } }
83
+ assert_equal('garply', hub.cap_sets[:blk].call)
84
+ end
85
+
86
+
87
+ def test_cap_attribute
88
+ # Single value as key, val arguments
89
+ hub = Hubcap.hub { server('test') { cap_attribute(:foo, 'bar') } }
90
+ assert_equal({ :foo => 'bar' }, hub.servers.first.cap_attributes)
91
+
92
+ # Single value as hash
93
+ hub = Hubcap.hub { server('test') { cap_attribute(:foo => 'bar') } }
94
+ assert_equal({ :foo => 'bar' }, hub.servers.first.cap_attributes)
95
+
96
+ # Multiple values
97
+ hub = Hubcap.hub { server('test') { cap_attribute(:a => 1, :z => 0) } }
98
+ assert_equal({ :a => 1, :z => 0 }, hub.servers.first.cap_attributes)
99
+
100
+ # Cap attributes are additive down the tree
101
+ hub = Hubcap.hub {
102
+ group('g') {
103
+ cap_attribute(:excellent => true)
104
+ server('s') { cap_attribute(:modest => false) }
105
+ }
106
+ }
107
+ assert_equal({ :excellent => true }, hub.groups.first.cap_attributes)
108
+ assert_equal(
109
+ { :excellent => true, :modest => false },
110
+ hub.servers.first.cap_attributes
111
+ )
112
+ end
113
+
114
+
115
+ def test_role
116
+ # Single role
117
+ hub = Hubcap.hub {
118
+ role(:baseline)
119
+ server('test')
120
+ }
121
+ assert_equal([:baseline], hub.servers.first.cap_roles)
122
+ assert_equal([:baseline], hub.servers.first.puppet_roles)
123
+
124
+ # Multiple roles in a single declaration
125
+ hub = Hubcap.hub {
126
+ role(:baseline, :app)
127
+ server('test')
128
+ }
129
+ assert_equal([:baseline, :app], hub.servers.first.cap_roles)
130
+ assert_equal([:baseline, :app], hub.servers.first.puppet_roles)
131
+
132
+ # Multiple declarations are additive
133
+ hub = Hubcap.hub {
134
+ role(:baseline)
135
+ server('test') { role(:db) }
136
+ }
137
+ assert_equal([:baseline, :db], hub.servers.first.cap_roles)
138
+ assert_equal([:baseline, :db], hub.servers.first.puppet_roles)
139
+
140
+ # Separate cap and puppet roles
141
+ hub = Hubcap.hub {
142
+ role(:cap => :app, :puppet => 'testapp')
143
+ server('test')
144
+ }
145
+ assert_equal([:app], hub.servers.first.cap_roles)
146
+ assert_equal(['testapp'], hub.servers.first.puppet_roles)
147
+
148
+ # Separate cap/puppet roles can be defined with an array
149
+ # Also shows that multiple role declarations are additive
150
+ hub = Hubcap.hub {
151
+ role(:cap => [:app, :db])
152
+ server('test') { role(:baseline) }
153
+ }
154
+ assert_equal([:app, :db], hub.cap_roles)
155
+ assert_equal([], hub.puppet_roles)
156
+ assert_equal([:app, :db, :baseline], hub.servers.first.cap_roles)
157
+ assert_equal([:baseline], hub.servers.first.puppet_roles)
158
+ end
159
+
160
+
161
+ def test_param
162
+ # Single key/val.
163
+ hub = Hubcap.hub {
164
+ server('test') { param('foo' => 1) }
165
+ }
166
+ assert_equal(1, hub.servers.first.params['foo'])
167
+
168
+ # Multiple key/vals.
169
+ hub = Hubcap.hub {
170
+ server('test') { param('foo' => 1, 'baz' => 2) }
171
+ }
172
+ assert_equal(1, hub.servers.first.params['foo'])
173
+ assert_equal(2, hub.servers.first.params['baz'])
174
+ end
175
+
176
+ end