yyuu-capistrano-chef-solo 0.0.9 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +167 -47
- data/capistrano-chef-solo.gemspec +4 -1
- data/lib/capistrano-chef-solo.rb +325 -166
- data/lib/capistrano-chef-solo/version.rb +1 -1
- data/test/centos6-64/.gitignore +5 -0
- data/test/centos6-64/Capfile +2 -0
- data/test/centos6-64/Gemfile +2 -0
- data/test/centos6-64/Vagrantfile +99 -0
- data/test/centos6-64/run.sh +7 -0
- data/test/config/cookbooks-ext/one/metadata.rb +5 -0
- data/test/config/cookbooks-ext/one/recipes/default.rb +7 -0
- data/test/config/cookbooks-ext/three/metadata.rb +5 -0
- data/test/config/cookbooks-ext/three/recipes/default.rb +7 -0
- data/test/config/cookbooks-ext/two/metadata.rb +5 -0
- data/test/config/cookbooks-ext/two/recipes/default.rb +7 -0
- data/test/config/cookbooks/bar/metadata.rb +5 -0
- data/test/config/cookbooks/bar/recipes/default.rb +7 -0
- data/test/config/cookbooks/baz/metadata.rb +5 -0
- data/test/config/cookbooks/baz/recipes/default.rb +7 -0
- data/test/config/cookbooks/foo/metadata.rb +5 -0
- data/test/config/cookbooks/foo/recipes/default.rb +7 -0
- data/test/config/deploy.rb +309 -0
- data/test/precise64/.gitignore +5 -0
- data/test/precise64/Capfile +2 -0
- data/test/precise64/Gemfile +2 -0
- data/test/precise64/Vagrantfile +99 -0
- data/test/precise64/run.sh +7 -0
- metadata +99 -5
data/README.md
CHANGED
@@ -20,70 +20,190 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
This recipe will try to bootstrap your servers with `chef-solo`. Following processes will be invoked.
|
22
22
|
|
23
|
-
1. Install ruby
|
24
|
-
2. Install `chef` with [bundler](http://gembundler.com)
|
25
|
-
3. Checkout your cookbooks
|
26
|
-
4.
|
23
|
+
1. Install ruby with using [capistrano-rbenv](https://github.com/yyuu/capistrano-rbenv)
|
24
|
+
2. Install `chef` with using [bundler](http://gembundler.com)
|
25
|
+
3. Checkout your cookbooks
|
26
|
+
4. Generate `attributes` and `run_list` for your hosts
|
27
|
+
5. Invoke `chef-solo`
|
27
28
|
|
28
29
|
To setup your servers with `chef-solo`, add following in you `config/deploy.rb`.
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
```ruby
|
32
|
+
# in "config/deploy.rb"
|
33
|
+
require "capistrano-chef-solo"
|
34
|
+
set(:chef_solo_version, "11.4.0")
|
35
|
+
```
|
34
36
|
|
35
|
-
And then, now you can start using `chef-solo` via
|
37
|
+
And then, now you can start using `chef-solo` via Capistrano.
|
38
|
+
This task will deploy cookbooks from `./config/cookbooks`, and then invoke `chef-solo`.
|
36
39
|
|
37
40
|
$ cap chef-solo
|
38
41
|
|
39
|
-
|
42
|
+
Plus, there is special method `chef_solo.run_list`. You can use this to apply recipes during deployment.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
after "deploy:finalize_update" do
|
46
|
+
chef_solo.run_list "recipe[foo]", "recipe[bar]", "recipe[baz]"
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
### Bootstrap mode
|
51
|
+
|
52
|
+
After the first time of boot up of servers, you might need to apply some recipes for your initial setup.
|
53
|
+
Let us say you want to have two users on servers.
|
54
|
+
|
55
|
+
* admin - the default user of system, created by system installer. use this for bootstrap. can invoke all commands via sudo.
|
56
|
+
* deploy - the user to use for application deployments. will be created during bootstrap. can invoke all commands via sudo.
|
57
|
+
|
58
|
+
There is _bootstrap_ mode for this kind of situations.
|
59
|
+
To setup these users, set them in your Capfile.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
set(:user, "deploy")
|
63
|
+
set(:chef_solo_bootstrap_user, "admin")
|
64
|
+
```
|
65
|
+
|
66
|
+
Then, apply recipes with _bootstrap_ mode.
|
67
|
+
|
68
|
+
% cap -S chef_solo_bootstrap=true chef-solo
|
69
|
+
|
70
|
+
After the bootstrap, you can deploy application normaly with `deploy` user.
|
71
|
+
|
72
|
+
% cap deploy:setup
|
40
73
|
|
41
|
-
* `:chef_solo_version` - the version of chef.
|
42
|
-
* `:chef_solo_user` - special user to invoke `chef-solo`. use `user` by default.
|
43
|
-
* `:chef_solo_ssh_options` - special ssh options for `chef_solo_user`. use `ssh_options` by default.
|
44
|
-
* `:chef_solo_ruby_version` - ruby version to launch `chef-solo`.
|
45
|
-
* `:chef_solo_cookbooks_repository` - the URL of your cookbook repository. use `repository` by default.
|
46
|
-
* `:chef_solo_cookbooks_revision` - the `branch` in the repository.
|
47
|
-
* `:chef_solo_cookbooks_subdir` - the path to the `cookbooks` directory in the repository. use `config/cookbooks` by default.
|
48
|
-
* `:chef_solo_cookbooks` - an alternative way to specify cookbooks repository. you can set multiple repositories from here.
|
49
|
-
* `:chef_solo_attributes` - the `attributes` of chef-solo. must be a `Hash<String,String>`. will be converted into JSON.
|
50
|
-
* `:chef_solo_run_list` - the `run_list` of chef-solo. must be an `Array<String>`. will be merged into `:chef_solo_attributes`.
|
51
|
-
* `:chef_solo_host_attributes` - per-host `attributes` of chef-solo. must be a `Hash<String,Hash<String,String>>`.
|
52
|
-
* `:chef_solo_host_run_list` - per-host `run_list` of chef-solo. must be a `Hash<String,Array<String>>`.
|
53
74
|
|
54
75
|
## Examples
|
55
76
|
|
56
|
-
###
|
77
|
+
### Using cookbooks
|
78
|
+
|
79
|
+
#### Using cookbooks from local path
|
80
|
+
|
81
|
+
By default, `capistrano-chef-solo` searches cookbooks from local path of `config/cookbooks`.
|
82
|
+
You can specify the cookbooks directory with using `chef_solo_cookbooks_subdir`.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
set(:chef_solo_cookbooks_scm, :none)
|
86
|
+
set(:chef_solo_cookbooks_subdir, "config/cookbooks")
|
87
|
+
```
|
88
|
+
|
89
|
+
#### Using cookbooks from remote repository
|
90
|
+
|
91
|
+
You can use cookbooks in remote repository.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
set(:chef_solo_cookbooks_scm, :git)
|
95
|
+
set(:chef_solo_cookbooks_repository, "git://example.com/example.git")
|
96
|
+
set(:chef_solo_cookbooks_revision, "master")
|
97
|
+
set(:chef_solo_cookbooks_subdir, "/")
|
98
|
+
```
|
99
|
+
|
100
|
+
#### Using mixed configuration
|
57
101
|
|
58
|
-
|
102
|
+
You can use multiple cookbooks repositories at once.
|
59
103
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
104
|
+
```ruby
|
105
|
+
set(:chef_solo_cookbooks) {{
|
106
|
+
# use cookbooks in ./config/cookbooks.
|
107
|
+
"local" => {
|
108
|
+
:scm => :none,
|
109
|
+
:cookbooks => "config/cookbooks",
|
110
|
+
},
|
111
|
+
# use cookbooks in git repository.
|
112
|
+
"repository" => {
|
113
|
+
:scm => :git,
|
114
|
+
:repository => "git://example.com/example.git",
|
115
|
+
:revision => "master",
|
116
|
+
:cookbooks => "/",
|
117
|
+
}
|
118
|
+
}}
|
119
|
+
```
|
65
120
|
|
66
|
-
### Setting individual `attributes` and `run_list` per host
|
67
121
|
|
68
|
-
|
122
|
+
### Attributes configuration
|
123
|
+
|
124
|
+
By default, the Chef attributes will be generated by following order.
|
125
|
+
|
126
|
+
1. Use _non-lazy_ variables of Capistrano.
|
127
|
+
2. Use attributes defined in `:chef_solo_attributes`.
|
128
|
+
3. Use attributes defined in `:chef_solo_role_attributes` for target role.
|
129
|
+
4. Use attributes defined in `:chef_solo_host_attributes` for target host.
|
130
|
+
|
131
|
+
#### Setting common attributes
|
132
|
+
|
133
|
+
To apply same attributes to all hosts.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
set(:chef_solo_attributes) {{
|
137
|
+
"locales" => { "language" => "ja" },
|
138
|
+
"tzdata" => { "timezone" => "Asia/Tokyo" },
|
139
|
+
}}
|
140
|
+
set(:chef_solo_run_list, ["recipe[locales]", "recipe[tzdata]"])
|
141
|
+
```
|
142
|
+
|
143
|
+
#### Setting individual attributes per roles
|
144
|
+
|
145
|
+
In some cases, you may want to apply individual `attributes` per roles.
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
set(:chef_solo_role_attributes) {
|
149
|
+
:app => {
|
150
|
+
"foo" => "foo",
|
151
|
+
},
|
152
|
+
:web => {
|
153
|
+
"bar" => "bar",
|
154
|
+
},
|
155
|
+
}
|
156
|
+
set(:chef_solo_role_run_list) {
|
157
|
+
:app => ["recipe[build-essential]"],
|
158
|
+
}
|
159
|
+
```
|
160
|
+
|
161
|
+
#### Setting individual attributes per host
|
162
|
+
|
163
|
+
In some cases, you may want to apply individual `attributes` per hosts.
|
69
164
|
(Something like `server_id` of mysqld or VRRP priority of keepalived)
|
70
165
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
166
|
+
```ruby
|
167
|
+
set(:chef_solo_host_attributes) {
|
168
|
+
"foo1.example.com" => {
|
169
|
+
"keepalived" => {
|
170
|
+
"virtual_router_id" => 1,
|
171
|
+
"priority" => 100, #=> MASTER
|
172
|
+
"virtual_ipaddress" => "192.168.0.1/24",
|
173
|
+
},
|
174
|
+
},
|
175
|
+
"foo2.example.com" => {
|
176
|
+
"keepalived" => {
|
177
|
+
"virtual_router_id" => 1,
|
178
|
+
"priority" => 50, #=> BACKUP
|
179
|
+
"virtual_ipaddress" => "192.168.0.1/24",
|
180
|
+
},
|
181
|
+
},
|
182
|
+
}
|
183
|
+
```
|
184
|
+
|
185
|
+
#### Testing attributes
|
186
|
+
|
187
|
+
You can check generated attributes with using `chef-solo:attributes` task.
|
188
|
+
|
189
|
+
% cap HOST=foo.example.com chef-solo:attributes
|
190
|
+
|
191
|
+
|
192
|
+
## Reference
|
193
|
+
|
194
|
+
Following options are available to manage your `chef-solo`.
|
195
|
+
|
196
|
+
* `:chef_solo_version` - the version of chef.
|
197
|
+
* `:chef_solo_cookbooks` - the definition of cookbooks. by default, copy cookbooks from `./config/cookbooks`.
|
198
|
+
* `:chef_solo_attributes` - the `attributes` of chef-solo. must be a `Hash<String,String>`. will be converted into JSON.
|
199
|
+
* `:chef_solo_run_list` - the `run_list` of chef-solo. must be an `Array<String>`. will be merged into `:chef_solo_attributes`.
|
200
|
+
* `:chef_solo_role_attributes` - the per-roles `attributes` of chef-solo. must be a `Hash<Symbol,Hash<String,String>>`.
|
201
|
+
* `:chef_solo_role_run_list` - the per-roles `run_list` of chef-solo. must be a `Hash<Symbol,Array<String>>`.
|
202
|
+
* `:chef_solo_host_attributes` - the per-hosts `attributes` of chef-solo. must be a `Hash<String,Hash<String,String>>`.
|
203
|
+
* `:chef_solo_host_run_list` - the per-hosts `run_list` of chef-solo. must be a `Hash<String,Array<String>>`.
|
204
|
+
* `:chef_solo_capistrano_attributes` - the Capistrano variables to use as Chef attributes.
|
205
|
+
* `:chef_solo_capistrano_attributes_exclude` - the black list for `:chef_solo_capistrano_attributes`
|
206
|
+
* `:chef_solo_capistrano_attributes_include` - the white list for `:chef_solo_capistrano_attributes`
|
87
207
|
|
88
208
|
|
89
209
|
## Contributing
|
@@ -17,5 +17,8 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.version = Capistrano::ChefSolo::VERSION
|
18
18
|
|
19
19
|
gem.add_dependency("capistrano")
|
20
|
-
gem.add_dependency("capistrano-rbenv", "~> 0.0
|
20
|
+
gem.add_dependency("capistrano-rbenv", "~> 1.0.0")
|
21
|
+
gem.add_development_dependency("net-scp", "~> 1.0.4")
|
22
|
+
gem.add_development_dependency("net-ssh", "~> 2.2.2")
|
23
|
+
gem.add_development_dependency("vagrant", "~> 1.0.6")
|
21
24
|
end
|
data/lib/capistrano-chef-solo.rb
CHANGED
@@ -10,245 +10,404 @@ module Capistrano
|
|
10
10
|
def self.extended(configuration)
|
11
11
|
configuration.load {
|
12
12
|
namespace(:"chef-solo") {
|
13
|
-
|
14
|
-
|
13
|
+
desc("Setup chef-solo. (an alias of chef_solo:setup)")
|
14
|
+
task(:setup, :except => { :no_release => true }) {
|
15
|
+
find_and_execute_task("chef_solo:setup")
|
15
16
|
}
|
16
|
-
|
17
|
-
|
17
|
+
|
18
|
+
desc("Run chef-solo. (an alias of chef_solo)")
|
19
|
+
task(:default, :except => { :no_release => true }) {
|
20
|
+
find_and_execute_task("chef_solo:default")
|
21
|
+
}
|
22
|
+
|
23
|
+
desc("Show chef-solo version. (an alias of chef_solo:version)")
|
24
|
+
task(:version, :except => { :no_release => true }) {
|
25
|
+
find_and_execute_task("chef_solo:version")
|
26
|
+
}
|
27
|
+
|
28
|
+
desc("Show chef-solo attributes. (an alias of chef_solo:attributes)")
|
29
|
+
task(:attributes, :except => { :no_release => true }) {
|
30
|
+
find_and_execute_task("chef_solo:attributes")
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
namespace(:chef_solo) {
|
35
|
+
_cset(:chef_solo_version, "11.4.0")
|
36
|
+
_cset(:chef_solo_path) { capture("echo $HOME/chef").strip }
|
18
37
|
_cset(:chef_solo_path_children, %w(bundle cache config cookbooks))
|
38
|
+
_cset(:chef_solo_config_file) { File.join(chef_solo_path, "config", "solo.rb") }
|
39
|
+
_cset(:chef_solo_attributes_file) { File.join(chef_solo_path, "config", "solo.json") }
|
19
40
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
41
|
+
_cset(:chef_solo_bootstrap_user) {
|
42
|
+
if variables.key?(:chef_solo_user)
|
43
|
+
logger.info(":chef_solo_user has been deprecated. use :chef_solo_bootstrap_user instead.")
|
44
|
+
fetch(:chef_solo_user, user)
|
45
|
+
else
|
46
|
+
user
|
47
|
+
end
|
48
|
+
}
|
49
|
+
_cset(:chef_solo_bootstrap_password) { password }
|
50
|
+
_cset(:chef_solo_bootstrap_ssh_options) {
|
51
|
+
if variables.key?(:chef_solo_ssh_options)
|
52
|
+
logger.info(":chef_solo_ssh_options has been deprecated. use :chef_solo_bootstrap_ssh_options instead.")
|
53
|
+
fetch(:chef_solo_ssh_options, ssh_options)
|
54
|
+
else
|
55
|
+
ssh_options
|
56
|
+
end
|
57
|
+
}
|
58
|
+
_cset(:chef_solo_use_password) {
|
59
|
+
auth_methods = ssh_options.fetch(:auth_methods, []).map { |m| m.to_sym }
|
60
|
+
auth_methods.include?(:password) or auth_methods.empty?
|
61
|
+
}
|
62
|
+
def _bootstrap_settings(&block)
|
63
|
+
if fetch(:_chef_solo_bootstrapped, false)
|
64
|
+
yield
|
65
|
+
else
|
66
|
+
# preserve original :user and :ssh_options
|
67
|
+
set(:_chef_solo_bootstrap_user, fetch(:user))
|
68
|
+
set(:_chef_solo_bootstrap_password, fetch(:password)) if chef_solo_use_password
|
69
|
+
set(:_chef_solo_bootstrap_ssh_options, fetch(:ssh_options))
|
70
|
+
servers = find_servers
|
71
|
+
begin
|
72
|
+
# we have to establish connections before teardown.
|
73
|
+
# https://github.com/capistrano/capistrano/pull/416
|
74
|
+
establish_connections_to(servers)
|
75
|
+
logger.info("entering chef-solo bootstrap mode. reconnect to servers as `#{chef_solo_bootstrap_user}'.")
|
76
|
+
# drop connection which is connected as standard :user.
|
77
|
+
teardown_connections_to(servers)
|
78
|
+
set(:user, chef_solo_bootstrap_user)
|
79
|
+
set(:password, chef_solo_bootstrap_password) if chef_solo_use_password
|
80
|
+
set(:ssh_options, chef_solo_bootstrap_ssh_options)
|
81
|
+
set(:_chef_solo_bootstrapped, true)
|
82
|
+
yield
|
83
|
+
ensure
|
84
|
+
set(:user, _chef_solo_bootstrap_user)
|
85
|
+
set(:password, _chef_solo_bootstrap_password) if chef_solo_use_password
|
86
|
+
set(:ssh_options, _chef_solo_bootstrap_ssh_options)
|
87
|
+
set(:_chef_solo_bootstrapped, false)
|
88
|
+
# we have to establish connections before teardown.
|
89
|
+
# https://github.com/capistrano/capistrano/pull/416
|
90
|
+
establish_connections_to(servers)
|
91
|
+
logger.info("leaving chef-solo bootstrap mode. reconnect to servers as `#{user}'.")
|
92
|
+
# drop connection which is connected as bootstrap :user.
|
93
|
+
teardown_connections_to(servers)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
_cset(:chef_solo_bootstrap, false)
|
98
|
+
def connect_with_settings(&block)
|
99
|
+
if chef_solo_bootstrap
|
100
|
+
_bootstrap_settings do
|
101
|
+
yield
|
102
|
+
end
|
103
|
+
else
|
30
104
|
yield
|
31
|
-
ensure
|
32
|
-
# restore original :user and :ssh_options
|
33
|
-
set(:user, _chef_solo_user)
|
34
|
-
set(:ssh_options, _chef_solo_ssh_options)
|
35
|
-
set(:rbenv_ruby_version, _chef_solo_rbenv_ruby_version)
|
36
105
|
end
|
37
106
|
end
|
38
107
|
|
39
108
|
desc("Setup chef-solo.")
|
40
|
-
task(:setup) {
|
41
|
-
connect_with_settings
|
42
|
-
transaction
|
43
|
-
|
44
|
-
|
45
|
-
|
109
|
+
task(:setup, :except => { :no_release => true }) {
|
110
|
+
connect_with_settings do
|
111
|
+
transaction do
|
112
|
+
install
|
113
|
+
end
|
114
|
+
end
|
46
115
|
}
|
47
116
|
|
48
117
|
desc("Run chef-solo.")
|
49
|
-
task(:default) {
|
50
|
-
connect_with_settings
|
51
|
-
|
52
|
-
|
118
|
+
task(:default, :except => { :no_release => true }) {
|
119
|
+
connect_with_settings do
|
120
|
+
setup
|
121
|
+
transaction do
|
53
122
|
update
|
54
|
-
|
55
|
-
|
123
|
+
invoke
|
124
|
+
end
|
125
|
+
end
|
56
126
|
}
|
57
127
|
|
58
|
-
|
59
|
-
|
60
|
-
connect_with_settings
|
61
|
-
|
62
|
-
|
128
|
+
# Acts like `default`, but will apply specified recipes only.
|
129
|
+
def run_list(*recipes)
|
130
|
+
connect_with_settings do
|
131
|
+
setup
|
132
|
+
transaction do
|
133
|
+
update(:run_list => recipes)
|
134
|
+
invoke
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
desc("Show chef-solo version.")
|
140
|
+
task(:version, :except => { :no_release => true }) {
|
141
|
+
connect_with_settings do
|
142
|
+
run("cd #{chef_solo_path.dump} && #{bundle_cmd} exec chef-solo --version")
|
143
|
+
end
|
144
|
+
}
|
145
|
+
|
146
|
+
desc("Show chef-solo attributes.")
|
147
|
+
task(:attributes, :except => { :no_release => true }) {
|
148
|
+
hosts = ENV.fetch("HOST", "").split(/\s*,\s*/)
|
149
|
+
roles = ENV.fetch("ROLE", "").split(/\s*,\s*/).map { |role| role.to_sym }
|
150
|
+
roles += hosts.map { |host| role_names_for_host(ServerDefinition.new(host)) }
|
151
|
+
attributes = _generate_attributes(:hosts => hosts, :roles => roles)
|
152
|
+
STDOUT.puts(_json_attributes(attributes))
|
63
153
|
}
|
64
154
|
|
65
|
-
task(:
|
155
|
+
task(:install, :except => { :no_release => true }) {
|
66
156
|
install_ruby
|
67
157
|
install_chef
|
68
158
|
}
|
69
159
|
|
70
|
-
task(:install_ruby) {
|
71
|
-
set(:
|
72
|
-
find_and_execute_task(
|
160
|
+
task(:install_ruby, :except => { :no_release => true }) {
|
161
|
+
set(:rbenv_install_bundler, true)
|
162
|
+
find_and_execute_task("rbenv:setup")
|
73
163
|
}
|
74
164
|
|
75
165
|
_cset(:chef_solo_gemfile) {
|
76
|
-
(<<-EOS).gsub(/^\s*/,
|
166
|
+
(<<-EOS).gsub(/^\s*/, "")
|
77
167
|
source "https://rubygems.org"
|
78
168
|
gem "chef", #{chef_solo_version.to_s.dump}
|
79
169
|
EOS
|
80
170
|
}
|
81
|
-
task(:install_chef) {
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
171
|
+
task(:install_chef, :except => { :no_release => true }) {
|
172
|
+
begin
|
173
|
+
version = capture("cd #{chef_solo_path.dump} && #{bundle_cmd} exec chef-solo --version")
|
174
|
+
installed = Regexp.new(Regexp.escape(chef_solo_version)) =~ version
|
175
|
+
rescue
|
176
|
+
installed = false
|
177
|
+
end
|
178
|
+
unless installed
|
179
|
+
dirs = chef_solo_path_children.map { |dir| File.join(chef_solo_path, dir) }
|
180
|
+
run("mkdir -p #{dirs.map { |x| x.dump }.join(" ")}")
|
181
|
+
top.put(chef_solo_gemfile, File.join(chef_solo_path, "Gemfile"))
|
182
|
+
args = fetch(:chef_solo_bundle_options, [])
|
183
|
+
args << "--path=#{File.join(chef_solo_path, "bundle").dump}"
|
184
|
+
args << "--quiet"
|
185
|
+
run("cd #{chef_solo_path.dump} && #{bundle_cmd} install #{args.join(" ")}")
|
186
|
+
end
|
86
187
|
}
|
87
188
|
|
88
|
-
|
89
|
-
update_cookbooks
|
90
|
-
update_config
|
91
|
-
update_attributes
|
92
|
-
|
93
|
-
}
|
189
|
+
def update(options={})
|
190
|
+
update_cookbooks(options)
|
191
|
+
update_config(options)
|
192
|
+
update_attributes(options)
|
193
|
+
end
|
94
194
|
|
95
|
-
|
96
|
-
tmpdir =
|
97
|
-
remote_tmpdir = capture("mktemp -d /tmp/
|
98
|
-
destination = File.join(tmpdir,
|
99
|
-
remote_destination = File.join(chef_solo_path,
|
100
|
-
filename = File.join(tmpdir,
|
101
|
-
remote_filename = File.join(remote_tmpdir,
|
195
|
+
def update_cookbooks(options={})
|
196
|
+
tmpdir = run_locally("mktemp -d /tmp/chef-solo.XXXXXXXXXX").strip
|
197
|
+
remote_tmpdir = capture("mktemp -d /tmp/chef-solo.XXXXXXXXXX").strip
|
198
|
+
destination = File.join(tmpdir, "cookbooks")
|
199
|
+
remote_destination = File.join(chef_solo_path, "cookbooks")
|
200
|
+
filename = File.join(tmpdir, "cookbooks.tar.gz")
|
201
|
+
remote_filename = File.join(remote_tmpdir, "cookbooks.tar.gz")
|
102
202
|
begin
|
103
203
|
bundle_cookbooks(filename, destination)
|
104
|
-
run("mkdir -p #{remote_tmpdir}")
|
204
|
+
run("mkdir -p #{remote_tmpdir.dump}")
|
105
205
|
distribute_cookbooks(filename, remote_filename, remote_destination)
|
106
206
|
ensure
|
107
|
-
run("rm -rf #{remote_tmpdir}") rescue nil
|
108
|
-
run_locally("rm -rf #{tmpdir}") rescue nil
|
207
|
+
run("rm -rf #{remote_tmpdir.dump}") rescue nil
|
208
|
+
run_locally("rm -rf #{tmpdir.dump}") rescue nil
|
109
209
|
end
|
110
|
-
|
111
|
-
|
112
|
-
# s/cookbook/&s/g for backward compatibility with releases older than 0.0.2.
|
113
|
-
# they will be removed in future releases.
|
114
|
-
_cset(:chef_solo_cookbook_repository) {
|
115
|
-
logger.info("WARNING: `chef_solo_cookbook_repository' has been deprecated. use `chef_solo_cookbooks_repository' instead.")
|
116
|
-
abort("chef_solo_cookbook_repository not set")
|
117
|
-
}
|
118
|
-
_cset(:chef_solo_cookbook_revision) {
|
119
|
-
logger.info("WARNING: `chef_solo_cookbook_revision' has been deprecated. use `chef_solo_cookbooks_revision' instead.")
|
120
|
-
"HEAD"
|
121
|
-
}
|
122
|
-
_cset(:chef_solo_cookbook_subdir) {
|
123
|
-
logger.info("WARNING: `chef_solo_cookbook_subdir' has been deprecated. use `chef_solo_cookbooks_subdir' instead.")
|
124
|
-
"/"
|
125
|
-
}
|
126
|
-
_cset(:chef_solo_cookbooks_exclude, %w(.hg .git .svn))
|
210
|
+
end
|
127
211
|
|
128
|
-
#
|
129
|
-
#
|
212
|
+
#
|
213
|
+
# The definition of cookbooks.
|
214
|
+
# By default, load cookbooks from local path of "config/cookbooks".
|
215
|
+
#
|
216
|
+
_cset(:chef_solo_cookbooks_name) { application }
|
217
|
+
_cset(:chef_solo_cookbooks_scm, :none)
|
130
218
|
_cset(:chef_solo_cookbooks) {
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
options[:cookbooks] = fetch(:chef_solo_cookbooks_subdir, nil)
|
138
|
-
options[:cookbooks] = fetch(:chef_solo_cookbook_subdir, nil) unless options[:cookbooks] # for backward compatibility
|
139
|
-
{ name => options }
|
219
|
+
cookbooks = {}
|
220
|
+
cookbooks[chef_solo_cookbooks_name] = {}
|
221
|
+
cookbooks[chef_solo_cookbooks_name][:cookbooks] = fetch(:chef_solo_cookbooks_subdir, "config/cookbooks")
|
222
|
+
cookbooks[chef_solo_cookbooks_name][:repository] = fetch(:chef_solo_cookbooks_repository) if exists?(:chef_solo_cookbooks_repository)
|
223
|
+
cookbooks[chef_solo_cookbooks_name][:revision] = fetch(:chef_solo_cookbooks_revision) if exists?(:chef_solo_cookbooks_revision)
|
224
|
+
cookbooks
|
140
225
|
}
|
141
226
|
|
142
|
-
_cset(:
|
227
|
+
_cset(:chef_solo_cookbooks_exclude, %w(.hg .git .svn))
|
228
|
+
def _normalize_cookbooks(cookbooks)
|
229
|
+
xs = cookbooks.map { |name, options|
|
230
|
+
options[:scm] ||= chef_solo_cookbooks_scm
|
231
|
+
options[:cookbooks_exclude] ||= chef_solo_cookbooks_exclude
|
232
|
+
[name, options]
|
233
|
+
}
|
234
|
+
Hash[xs]
|
235
|
+
end
|
236
|
+
|
237
|
+
_cset(:chef_solo_repository_cache) { File.expand_path("./tmp/cookbooks-cache") }
|
143
238
|
def bundle_cookbooks(filename, destination)
|
144
239
|
dirs = [ File.dirname(filename), destination ].uniq
|
145
|
-
run_locally("mkdir -p #{dirs.join(
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
:revision => proc { configuration[:source].head },
|
152
|
-
:real_revision => proc {
|
153
|
-
configuration[:source].local.query_revision(configuration[:revision]) { |cmd| with_env("LC_ALL", "C") { run_locally(cmd) } }
|
154
|
-
},
|
155
|
-
}.merge(options)
|
156
|
-
variables.merge(options).each do |key, val|
|
157
|
-
configuration.set(key, val)
|
158
|
-
end
|
159
|
-
repository_cache = File.join(chef_solo_repository_cache, name)
|
160
|
-
if File.exist?(repository_cache)
|
161
|
-
run_locally(configuration[:source].sync(configuration[:real_revision], repository_cache))
|
240
|
+
run_locally("mkdir -p #{dirs.map { |x| x.dump }.join(" ")}")
|
241
|
+
cookbooks = _normalize_cookbooks(chef_solo_cookbooks)
|
242
|
+
cookbooks.each do |name, options|
|
243
|
+
case options[:scm].to_sym
|
244
|
+
when :none
|
245
|
+
fetch_cookbooks_none(name, destination, options)
|
162
246
|
else
|
163
|
-
|
247
|
+
fetch_cookbooks_repository(name, destination, options)
|
164
248
|
end
|
249
|
+
end
|
250
|
+
run_locally("cd #{File.dirname(destination).dump} && tar chzf #{filename.dump} #{File.basename(destination).dump}")
|
251
|
+
end
|
165
252
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
253
|
+
def _fetch_cookbook(source, destination, options)
|
254
|
+
exclusions = options.fetch(:cookbooks_exclude, []).map { |e| "--exclude=#{e.dump}" }.join(" ")
|
255
|
+
run_locally("rsync -lrpt #{exclusions} #{source}/ #{destination}")
|
256
|
+
end
|
257
|
+
|
258
|
+
def _fetch_cookbooks(source, destination, options)
|
259
|
+
cookbooks = [ options.fetch(:cookbooks, "/") ].flatten.compact
|
260
|
+
cookbooks.each do |cookbook|
|
261
|
+
_fetch_cookbook(File.join(source, cookbook), destination, options)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def fetch_cookbooks_none(name, destination, options={})
|
266
|
+
_fetch_cookbooks(options.fetch(:repository, "."), destination, options)
|
267
|
+
end
|
268
|
+
|
269
|
+
def fetch_cookbooks_repository(name, destination, options={})
|
270
|
+
configuration = Capistrano::Configuration.new
|
271
|
+
# refreshing just :source, :revision and :real_revision is enough?
|
272
|
+
options = {
|
273
|
+
:source => lambda { Capistrano::Deploy::SCM.new(configuration[:scm], configuration) },
|
274
|
+
:revision => lambda { configuration[:source].head },
|
275
|
+
:real_revision => lambda {
|
276
|
+
configuration[:source].local.query_revision(configuration[:revision]) { |cmd| with_env("LC_ALL", "C") { run_locally(cmd) } }
|
277
|
+
},
|
278
|
+
}.merge(options)
|
279
|
+
variables.merge(options).each do |key, val|
|
280
|
+
configuration.set(key, val)
|
281
|
+
end
|
282
|
+
repository_cache = File.join(chef_solo_repository_cache, name)
|
283
|
+
if File.exist?(repository_cache)
|
284
|
+
run_locally(configuration[:source].sync(configuration[:real_revision], repository_cache))
|
285
|
+
else
|
286
|
+
run_locally(configuration[:source].checkout(configuration[:real_revision], repository_cache))
|
173
287
|
end
|
174
|
-
|
288
|
+
_fetch_cookbooks(repository_cache, destination, options)
|
175
289
|
end
|
176
290
|
|
177
291
|
def distribute_cookbooks(filename, remote_filename, remote_destination)
|
178
292
|
upload(filename, remote_filename)
|
179
|
-
run("rm -rf #{remote_destination}")
|
180
|
-
run("cd #{File.dirname(remote_destination)} && tar xzf #{remote_filename}")
|
293
|
+
run("rm -rf #{remote_destination.dump}")
|
294
|
+
run("cd #{File.dirname(remote_destination).dump} && tar xzf #{remote_filename.dump}")
|
181
295
|
end
|
182
296
|
|
183
297
|
_cset(:chef_solo_config) {
|
184
|
-
(<<-EOS).gsub(/^\s*/,
|
185
|
-
file_cache_path #{File.join(chef_solo_path,
|
186
|
-
cookbook_path #{File.join(chef_solo_path,
|
298
|
+
(<<-EOS).gsub(/^\s*/, "")
|
299
|
+
file_cache_path #{File.join(chef_solo_path, "cache").dump}
|
300
|
+
cookbook_path #{File.join(chef_solo_path, "cookbooks").dump}
|
187
301
|
EOS
|
188
302
|
}
|
189
|
-
|
190
|
-
put(chef_solo_config,
|
191
|
-
|
303
|
+
def update_config(options={})
|
304
|
+
top.put(chef_solo_config, chef_solo_config_file)
|
305
|
+
end
|
192
306
|
|
193
307
|
# merge nested hashes
|
194
|
-
def
|
195
|
-
f = lambda { |key, val1, val2|
|
196
|
-
|
308
|
+
def _merge_attributes!(a, b)
|
309
|
+
f = lambda { |key, val1, val2|
|
310
|
+
case val1
|
311
|
+
when Array
|
312
|
+
val1 + val2
|
313
|
+
when Hash
|
314
|
+
val1.merge(val2, &f)
|
315
|
+
else
|
316
|
+
val2
|
317
|
+
end
|
318
|
+
}
|
319
|
+
a.merge!(b, &f)
|
197
320
|
end
|
198
321
|
|
199
|
-
def
|
200
|
-
|
201
|
-
JSON.pretty_generate(x)
|
202
|
-
else
|
203
|
-
JSON.generate(x)
|
204
|
-
end
|
322
|
+
def _json_attributes(x)
|
323
|
+
JSON.send(fetch(:chef_solo_pretty_json, true) ? :pretty_generate : :generate, x)
|
205
324
|
end
|
206
325
|
|
207
326
|
_cset(:chef_solo_capistrano_attributes) {
|
208
|
-
#
|
209
|
-
|
327
|
+
#
|
328
|
+
# The rule of generating chef attributes from Capistrano variables
|
329
|
+
#
|
330
|
+
# 1. Reject variables if it is in exclude list.
|
331
|
+
# 2. Reject variables if it is lazy and not in include list.
|
332
|
+
# (lazy variables might have any side-effects)
|
333
|
+
#
|
334
|
+
attributes = variables.reject { |key, value|
|
335
|
+
excluded = chef_solo_capistrano_attributes_exclude.include?(key)
|
336
|
+
included = chef_solo_capistrano_attributes_include.include?(key)
|
337
|
+
excluded or (not included and value.respond_to?(:call))
|
338
|
+
}
|
339
|
+
Hash[attributes.map { |key, value| [key, fetch(key, nil)] }]
|
210
340
|
}
|
341
|
+
_cset(:chef_solo_capistrano_attributes_include, [
|
342
|
+
:application, :deploy_to, :rails_env, :latest_release,
|
343
|
+
:releases_path, :shared_path, :current_path, :release_path,
|
344
|
+
])
|
345
|
+
_cset(:chef_solo_capistrano_attributes_exclude, [:logger, :password])
|
211
346
|
_cset(:chef_solo_attributes, {})
|
212
347
|
_cset(:chef_solo_host_attributes, {})
|
348
|
+
_cset(:chef_solo_role_attributes, {})
|
213
349
|
_cset(:chef_solo_run_list, [])
|
214
350
|
_cset(:chef_solo_host_run_list, {})
|
351
|
+
_cset(:chef_solo_role_run_list, {})
|
215
352
|
|
216
|
-
def
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
353
|
+
def _generate_attributes(options={})
|
354
|
+
hosts = [ options.delete(:hosts) ].flatten.compact.uniq
|
355
|
+
roles = [ options.delete(:roles) ].flatten.compact.uniq
|
356
|
+
run_list = [ options.delete(:run_list) ].flatten.compact.uniq
|
357
|
+
#
|
358
|
+
# By default, the Chef attributes will be generated by following order.
|
359
|
+
#
|
360
|
+
# 1. Use _non-lazy_ variables of Capistrano.
|
361
|
+
# 2. Use attributes defined in `:chef_solo_attributes`.
|
362
|
+
# 3. Use attributes defined in `:chef_solo_role_attributes` for target role.
|
363
|
+
# 4. Use attributes defined in `:chef_solo_host_attributes` for target host.
|
364
|
+
#
|
365
|
+
attributes = chef_solo_capistrano_attributes.dup
|
366
|
+
_merge_attributes!(attributes, chef_solo_attributes)
|
367
|
+
roles.each do |role|
|
368
|
+
_merge_attributes!(attributes, chef_solo_role_attributes.fetch(role, {}))
|
369
|
+
end
|
370
|
+
hosts.each do |host|
|
371
|
+
_merge_attributes!(attributes, chef_solo_host_attributes.fetch(host, {}))
|
372
|
+
end
|
373
|
+
#
|
374
|
+
# The Chef `run_list` will be generated by following rules.
|
375
|
+
#
|
376
|
+
# * If `:run_list` was given as argument, just use it.
|
377
|
+
# * Otherwise, generate it from `:chef_solo_role_run_list`, `:chef_solo_role_run_list`
|
378
|
+
# and `:chef_solo_host_run_list`.
|
379
|
+
#
|
380
|
+
if run_list.empty?
|
381
|
+
_merge_attributes!(attributes, {"run_list" => chef_solo_run_list})
|
382
|
+
roles.each do |role|
|
383
|
+
_merge_attributes!(attributes, {"run_list" => chef_solo_role_run_list.fetch(role, [])})
|
384
|
+
end
|
385
|
+
hosts.each do |host|
|
386
|
+
_merge_attributes!(attributes, {"run_list" => chef_solo_host_run_list.fetch(host, [])})
|
387
|
+
end
|
388
|
+
else
|
389
|
+
attributes["run_list"] = [] # ignore run_list not from argument
|
390
|
+
_merge_attributes!(attributes, {"run_list" => run_list})
|
222
391
|
end
|
223
392
|
attributes
|
224
393
|
end
|
225
394
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
to = File.join(chef_solo_path, "config", "solo.json")
|
233
|
-
if chef_solo_host_attributes.empty? and chef_solo_host_run_list.empty?
|
234
|
-
put(json(generate_attributes), to)
|
235
|
-
else
|
236
|
-
execute_on_servers { |servers|
|
237
|
-
servers.each { |server|
|
238
|
-
put(json(generate_attributes(:host => server.host), to, :hosts => server.host))
|
239
|
-
}
|
240
|
-
}
|
395
|
+
def update_attributes(options={})
|
396
|
+
run_list = options.delete(:run_list)
|
397
|
+
servers = find_servers_for_task(current_task)
|
398
|
+
servers.each do |server|
|
399
|
+
attributes = _generate_attributes(:hosts => server.host, :roles => role_names_for_host(server), :run_list => run_list)
|
400
|
+
top.put(_json_attributes(attributes), chef_solo_attributes_file, options.merge(:hosts => server.host))
|
241
401
|
end
|
242
|
-
|
402
|
+
end
|
243
403
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
}
|
404
|
+
def invoke(options={})
|
405
|
+
bin = fetch(:chef_solo_executable, "chef-solo")
|
406
|
+
args = fetch(:chef_solo_options, [])
|
407
|
+
args << "-c #{chef_solo_config_file.dump}"
|
408
|
+
args << "-j #{chef_solo_attributes_file.dump}"
|
409
|
+
run("cd #{chef_solo_path.dump} && #{sudo} #{bundle_cmd} exec #{bin.dump} #{args.join(" ")}", options)
|
410
|
+
end
|
252
411
|
}
|
253
412
|
}
|
254
413
|
end
|