knife-sharp 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +143 -0
- data/ext/sharp-config.yml +12 -0
- data/lib/chef/knife/sharp-align.rb +359 -0
- data/lib/chef/knife/sharp-backup.rb +106 -0
- data/lib/chef/knife/sharp-history.rb +57 -0
- data/lib/chef/knife/sharp-server.rb +54 -0
- data/lib/knife-sharp.rb +3 -0
- metadata +84 -0
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# Knife sharp plugin
|
2
|
+
|
3
|
+
knife-sharp adds several handy features to knife, adapted to our workflow @ Fotolia.
|
4
|
+
Features:
|
5
|
+
* align : sync data bags, roles and cookbook versions between a local git branches and a chef server
|
6
|
+
* backup : dump environments, roles and data bags to local json files
|
7
|
+
* server : switch between chef servers using multiple knife.rb config files
|
8
|
+
|
9
|
+
# Tell me more
|
10
|
+
|
11
|
+
When you want an environment to reflect a given branch you have to check by hand (or using our consistency plugin), and some mistakes can be made. This plugin aims to help to push the right version into an environment.
|
12
|
+
|
13
|
+
It also allows to adopt a review workflow for main chef components :
|
14
|
+
* Track data bags, roles (as JSON files) and cookbooks in your Chef git repository
|
15
|
+
* Push each modification of any to peer-review
|
16
|
+
* Once merged, upload every change with knife sharp align
|
17
|
+
|
18
|
+
# Show me !
|
19
|
+
|
20
|
+
## Align
|
21
|
+
|
22
|
+
<pre>
|
23
|
+
$ git branch
|
24
|
+
...
|
25
|
+
master
|
26
|
+
* syslog_double
|
27
|
+
...
|
28
|
+
$ knife environment show sandboxnico
|
29
|
+
chef_type: environment
|
30
|
+
cookbook_versions:
|
31
|
+
...
|
32
|
+
syslog: 0.0.16
|
33
|
+
...
|
34
|
+
$ knife sharp align syslog_double sandboxnico
|
35
|
+
|
36
|
+
Will change in environment sandboxnico :
|
37
|
+
* syslog gets version 0.0.17
|
38
|
+
Upload and set version into environment sandboxnico ? Y/N
|
39
|
+
Y
|
40
|
+
Successfull upload for syslog
|
41
|
+
Aligning 1 cookbooks
|
42
|
+
Aligning data bags
|
43
|
+
* infrastructure/mail data bag item is not up-to-date
|
44
|
+
Update infrastructure/mail data bag item on server ? Y/N/(A)ll/(Q)uit [N] n
|
45
|
+
* Skipping infrastructure/mail data bag item
|
46
|
+
Aligning roles
|
47
|
+
* Dev_Server role is not up-to-date (run list)
|
48
|
+
Update Dev_Server role on server ? Y/N/(A)ll/(Q)uit [N] n
|
49
|
+
* Skipping Dev_Server role
|
50
|
+
</pre>
|
51
|
+
|
52
|
+
Then we can check environment :
|
53
|
+
|
54
|
+
<pre>
|
55
|
+
$ knife environment show sandboxnico
|
56
|
+
chef_type: environment
|
57
|
+
cookbook_versions:
|
58
|
+
...
|
59
|
+
syslog: 0.0.17
|
60
|
+
...
|
61
|
+
$ knife sharp align syslog_double sandboxnico
|
62
|
+
Nothing to do : sandboxnico has same versions as syslog_double
|
63
|
+
</pre>
|
64
|
+
|
65
|
+
It will upload the cookbooks (to ensure they meet the one on the branch you're working on) and will set the version to the required number.
|
66
|
+
|
67
|
+
## Backup
|
68
|
+
|
69
|
+
Making a backup before a large change can be a lifesaver. Knife sharp can do it for you, easily
|
70
|
+
<pre>
|
71
|
+
$ knife sharp backup
|
72
|
+
Backing up roles
|
73
|
+
Backing up environments
|
74
|
+
Backing up databags
|
75
|
+
$
|
76
|
+
</pre>
|
77
|
+
|
78
|
+
All these items get stored in the place defined in your config file.
|
79
|
+
|
80
|
+
## Server
|
81
|
+
|
82
|
+
<pre>
|
83
|
+
$ knife sharp server
|
84
|
+
Available servers:
|
85
|
+
prod (/home/jamiez/.chef/knife-prod.rb)
|
86
|
+
>> dev (/home/jamiez/.chef/knife-dev.rb)
|
87
|
+
|
88
|
+
$ knife sharp server prod
|
89
|
+
The knife configuration has been updated to use prod.
|
90
|
+
|
91
|
+
$ knife sharp server
|
92
|
+
Available servers:
|
93
|
+
>> prod (/home/jamiez/.chef/knife-prod.rb)
|
94
|
+
dev (/home/jamiez/.chef/knife-dev.rb)
|
95
|
+
</pre>
|
96
|
+
|
97
|
+
# Configuration
|
98
|
+
|
99
|
+
Dependencies :
|
100
|
+
* grit
|
101
|
+
|
102
|
+
The plugin will search in 2 places for its config file :
|
103
|
+
* "/etc/sharp-config.yml"
|
104
|
+
* "~/.chef/sharp-config.yml"
|
105
|
+
|
106
|
+
An example config file is provided in ext/.
|
107
|
+
|
108
|
+
A working knife setup is also required (cookbook/role/data bag paths depending on the desired features)
|
109
|
+
|
110
|
+
## Cookbooks path & git
|
111
|
+
If your cookbook_path is not the root of your git directory then the grit gem will produce an error. This can be circumvented by adding the following directive in your config file :
|
112
|
+
|
113
|
+
<pre>
|
114
|
+
global:
|
115
|
+
git_cookbook_path: "/home/nico/sysadmin/chef/"
|
116
|
+
</pre>
|
117
|
+
|
118
|
+
As we version more than the cookbooks in the repo.
|
119
|
+
|
120
|
+
## Logging
|
121
|
+
It's good to have things logged. The plugin can do it for you. Add this to your config file
|
122
|
+
<pre>
|
123
|
+
logging:
|
124
|
+
enabled: true
|
125
|
+
destination: "~/.chef/sharp.log"
|
126
|
+
</pre>
|
127
|
+
|
128
|
+
It will log uploads, bumps and databags to the standard logger format.
|
129
|
+
|
130
|
+
# Credits
|
131
|
+
|
132
|
+
The damn good knife spork plugin from the etsy folks : https://github.com/jonlives/knife-spork
|
133
|
+
|
134
|
+
Idea for knife sharp server comes from https://github.com/greenandsecure/knife-block
|
135
|
+
|
136
|
+
License
|
137
|
+
=======
|
138
|
+
3 clauses BSD
|
139
|
+
|
140
|
+
Authors
|
141
|
+
======
|
142
|
+
Nicolas Szalay | https://github.com/rottenbytes
|
143
|
+
Jonathan Amiez | https://github.com/josqu4red
|
@@ -0,0 +1,12 @@
|
|
1
|
+
logging:
|
2
|
+
enabled: true
|
3
|
+
destination: "~/.chef/sharp.log"
|
4
|
+
global:
|
5
|
+
git_cookbook_path: "/home/nico/sysadmin/chef/"
|
6
|
+
backupdir: "/home/nico/.chef/sharp/backups/"
|
7
|
+
notification:
|
8
|
+
hubot:
|
9
|
+
enabled: true
|
10
|
+
url: "http://your.hubot.lan:18080/hubot/chef/bump/"
|
11
|
+
channel: "foo"
|
12
|
+
username: "Nicolas"
|
@@ -0,0 +1,359 @@
|
|
1
|
+
require 'chef/knife'
|
2
|
+
require 'grit'
|
3
|
+
|
4
|
+
module KnifeSharp
|
5
|
+
class SharpAlign < Chef::Knife
|
6
|
+
|
7
|
+
banner "knife sharp align BRANCH ENVIRONMENT [--debug] [--quiet]"
|
8
|
+
|
9
|
+
option :debug,
|
10
|
+
:short => '-d',
|
11
|
+
:long => '--debug',
|
12
|
+
:description => "turn debug on",
|
13
|
+
:default => false
|
14
|
+
|
15
|
+
option :quiet,
|
16
|
+
:short => '-q',
|
17
|
+
:long => '--quiet',
|
18
|
+
:description => 'does not notifies',
|
19
|
+
:default => false
|
20
|
+
|
21
|
+
deps do
|
22
|
+
require 'chef/cookbook/metadata'
|
23
|
+
require 'chef/cookbook_loader'
|
24
|
+
require 'chef/cookbook_uploader'
|
25
|
+
end
|
26
|
+
|
27
|
+
def run
|
28
|
+
setup()
|
29
|
+
ui.msg "Aligning cookbooks"
|
30
|
+
align_cookbooks()
|
31
|
+
ui.msg "Aligning data bags"
|
32
|
+
align_databags()
|
33
|
+
ui.msg "Aligning roles"
|
34
|
+
align_roles()
|
35
|
+
end
|
36
|
+
|
37
|
+
def setup
|
38
|
+
# Checking args
|
39
|
+
if name_args.count != 2
|
40
|
+
ui.error "Usage : knife align BRANCH ENVIRONMENT [--debug]"
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sharp config
|
45
|
+
cfg_files = [ "/etc/sharp-config.yml", "~/.chef/sharp-config.yml" ]
|
46
|
+
loaded = false
|
47
|
+
cfg_files.each do |cfg_file|
|
48
|
+
begin
|
49
|
+
@cfg = YAML::load_file(File.expand_path(cfg_file))
|
50
|
+
loaded = true
|
51
|
+
rescue Exception => e
|
52
|
+
ui.error "Error on loading config : #{e.inspect}" if config[:debug]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
unless loaded == true
|
56
|
+
ui.error "config could not be loaded ! Tried the following files : #{cfg_files.join(", ")}"
|
57
|
+
exit 1
|
58
|
+
end
|
59
|
+
|
60
|
+
# Knife config
|
61
|
+
if Chef::Knife.chef_config_dir && File.exists?(File.join(Chef::Knife.chef_config_dir, "knife.rb"))
|
62
|
+
Chef::Config.from_file(File.join(Chef::Knife.chef_config_dir, "knife.rb"))
|
63
|
+
else
|
64
|
+
ui.error "Cannot find knife.rb config file"
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
|
68
|
+
# Env setup
|
69
|
+
@branch, @environment = name_args
|
70
|
+
@chef_path = @cfg["global"]["git_cookbook_path"]
|
71
|
+
|
72
|
+
chefcfg = Chef::Config
|
73
|
+
@cb_path = chefcfg.cookbook_path.is_a?(Array) ? chefcfg.cookbook_path.first : chefcfg.cookbook_path
|
74
|
+
@db_path = chefcfg.data_bag_path.is_a?(Array) ? chefcfg.data_bag_path.first : chefcfg.data_bag_path
|
75
|
+
@role_path = chefcfg.role_path.is_a?(Array) ? chefcfg.role_path.first : chefcfg.role_path
|
76
|
+
|
77
|
+
# Checking current branch
|
78
|
+
current_branch = Grit::Repo.new(@chef_path).head.name
|
79
|
+
if @branch != current_branch then
|
80
|
+
ui.error "Git repo is actually on branch #{current_branch} but you want to align using #{@branch}. Checkout to the desired one."
|
81
|
+
exit 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
### Cookbook methods ###
|
86
|
+
|
87
|
+
def align_cookbooks
|
88
|
+
updated_versions = Hash.new()
|
89
|
+
local_versions = get_cookbook_local_versions()
|
90
|
+
remote_versions = get_cookbook_versions_from_env(@environment)
|
91
|
+
|
92
|
+
(local_versions.keys - remote_versions.keys).each do |cb|
|
93
|
+
updated_versions[cb] = local_versions[cb]
|
94
|
+
ui.msg "* #{cb} is local only (version #{local_versions[cb]})"
|
95
|
+
end
|
96
|
+
|
97
|
+
(remote_versions.keys & local_versions.keys).each do |cb|
|
98
|
+
if remote_versions[cb] != local_versions[cb]
|
99
|
+
updated_versions[cb] = local_versions[cb]
|
100
|
+
ui.msg "* #{cb} is not up-to-date (local: #{local_versions[cb]}/remote: #{remote_versions[cb]})"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
bumped = Hash.new
|
105
|
+
if !updated_versions.empty?
|
106
|
+
all = false
|
107
|
+
env = Chef::Environment.load(@environment)
|
108
|
+
loader = Chef::CookbookLoader.new(@cb_path)
|
109
|
+
updated_versions.each_pair do |cb,version|
|
110
|
+
answer = ui.ask_question("Update #{cb} cookbook item on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
|
111
|
+
|
112
|
+
if answer == "A"
|
113
|
+
all = true
|
114
|
+
elsif answer == "Q"
|
115
|
+
ui.msg "> Aborting cookbook alignment."
|
116
|
+
break
|
117
|
+
end
|
118
|
+
|
119
|
+
if all or answer == "Y"
|
120
|
+
log_action("* Uploading cookbook #{cb} version #{version}")
|
121
|
+
cb_obj = loader[cb]
|
122
|
+
uploader = Chef::CookbookUploader.new(cb_obj, @cb_path)
|
123
|
+
uploader.upload_cookbooks
|
124
|
+
env.cookbook_versions[cb] = version
|
125
|
+
bumped[cb] = version
|
126
|
+
else
|
127
|
+
ui.msg "* Skipping #{cb} cookbook"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
if env.save
|
131
|
+
bumped.each do |cb, version|
|
132
|
+
log_action("* Bumping cookbook #{cb} to #{version} for environment #{@environment}")
|
133
|
+
hubot_notify(cb, version, @environment)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
else
|
137
|
+
ui.msg "> Environment #{@environment} is up-to-date."
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# get cookbook for a known environment
|
142
|
+
def get_cookbook_versions_from_env(env_name)
|
143
|
+
Chef::Environment.load(env_name).cookbook_versions.each_value {|v| v.gsub!("= ", "")}
|
144
|
+
end
|
145
|
+
|
146
|
+
# in your local dealer !
|
147
|
+
def get_cookbook_local_versions
|
148
|
+
cbs = Hash.new
|
149
|
+
Dir.glob("#{@cb_path}/*").each do |cookbook|
|
150
|
+
md = Chef::Cookbook::Metadata.new
|
151
|
+
md.from_file("#{cookbook}/metadata.rb")
|
152
|
+
cbs[File.basename(cookbook)] = md.version
|
153
|
+
end
|
154
|
+
return cbs
|
155
|
+
end
|
156
|
+
|
157
|
+
### Databag methods ###
|
158
|
+
|
159
|
+
def align_databags
|
160
|
+
unless File.exists?(@db_path)
|
161
|
+
ui.warn "Bad data bag path, skipping data bag sync."
|
162
|
+
return
|
163
|
+
end
|
164
|
+
|
165
|
+
updated_dbs = Hash.new
|
166
|
+
local_dbs = Dir.glob(File.join(@db_path, "**/*.json")).map {|f| [File.dirname(f).split("/").last, File.basename(f, ".json")]}
|
167
|
+
remote_dbs = Chef::DataBag.list.keys.map {|db| Chef::DataBag.load(db).keys.map{|dbi| [db, dbi]}}.flatten(1)
|
168
|
+
|
169
|
+
ui.warn "No local data bags found, is the role path correct ? (#{@role_path})" if local_dbs.empty?
|
170
|
+
|
171
|
+
# Create new data bags on server
|
172
|
+
(local_dbs - remote_dbs).each do |db|
|
173
|
+
ui.msg "+ #{db.join("/")} data bag item is local only. Creating"
|
174
|
+
begin
|
175
|
+
local_db = Chef::DataBagItem.new
|
176
|
+
local_db.data_bag(db.first)
|
177
|
+
local_db.raw_data = JSON::load(File.read(File.join(@db_path, "#{db.join("/")}.json")))
|
178
|
+
local_db.save
|
179
|
+
rescue Exception => e
|
180
|
+
ui.error "Unable to create #{db.join("/")} data bag item (#{e.message})"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Dump missing data bags locally
|
185
|
+
(remote_dbs - local_dbs).each do |db|
|
186
|
+
ui.msg "- #{db.join("/")} data bag item is remote only. Dumping to #{File.join(@db_path, "#{db.join("/")}.json")}"
|
187
|
+
begin
|
188
|
+
remote_db = Chef::DataBagItem.load(db.first, db.last).raw_data
|
189
|
+
Dir.mkdir(File.join(@db_path, db.first)) unless Dir.exists?(File.join(@db_path, db.first))
|
190
|
+
File.open(File.join(@db_path, "#{db.join("/")}.json"), "w") do |file|
|
191
|
+
file.puts JSON.pretty_generate(remote_db)
|
192
|
+
end
|
193
|
+
rescue Exception => e
|
194
|
+
ui.error "Unable to dump #{db.join("/")} data bag item (#{e.message})"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Compare roles common to local and remote
|
199
|
+
(remote_dbs & local_dbs).each do |db|
|
200
|
+
remote_db = Chef::DataBagItem.load(db.first, db.last).raw_data
|
201
|
+
local_db = JSON::load(File.read(File.join(@db_path, "#{db.join("/")}.json")))
|
202
|
+
|
203
|
+
if remote_db != local_db
|
204
|
+
updated_dbs[db] = local_db
|
205
|
+
ui.msg("* #{db.join("/")} data bag item is not up-to-date")
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
if !updated_dbs.empty?
|
210
|
+
all = false
|
211
|
+
updated_dbs.each do |name, obj|
|
212
|
+
answer = ui.ask_question("Update #{name.join("/")} data bag item on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
|
213
|
+
|
214
|
+
if answer == "A"
|
215
|
+
all = true
|
216
|
+
elsif answer == "Q"
|
217
|
+
ui.msg "> Aborting data bag alignment."
|
218
|
+
break
|
219
|
+
end
|
220
|
+
|
221
|
+
if all or answer == "Y"
|
222
|
+
ui.msg "* Updating #{name.join("/")} data bag item"
|
223
|
+
db = Chef::DataBagItem.new
|
224
|
+
db.data_bag(name.first)
|
225
|
+
db.raw_data = obj
|
226
|
+
db.save
|
227
|
+
else
|
228
|
+
ui.msg "* Skipping #{name.join("/")} data bag item"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
else
|
232
|
+
ui.msg "> Data bags are up-to-date."
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
### Role methods ###
|
237
|
+
|
238
|
+
def align_roles
|
239
|
+
# role sections to compare (methods)
|
240
|
+
to_check = {
|
241
|
+
"env_run_lists" => "run list",
|
242
|
+
"default_attributes" => "default attributes",
|
243
|
+
"override_attributes" => "override attributes"
|
244
|
+
}
|
245
|
+
|
246
|
+
unless File.exists?(@role_path)
|
247
|
+
ui.warn "Bad role path, skipping role sync."
|
248
|
+
return
|
249
|
+
end
|
250
|
+
|
251
|
+
updated_roles = Hash.new
|
252
|
+
local_roles = Dir.glob(File.join(@role_path, "*.json")).map {|file| File.basename(file, ".json")}
|
253
|
+
remote_roles = Chef::Role.list.keys
|
254
|
+
|
255
|
+
ui.warn "No local roles found, is the role path correct ? (#{@role_path})" if local_roles.empty?
|
256
|
+
|
257
|
+
# Create new roles on server
|
258
|
+
(local_roles - remote_roles).each do |role|
|
259
|
+
ui.msg "+ #{role} role is local only. Creating"
|
260
|
+
begin
|
261
|
+
local_role = Chef::Role.from_disk(role)
|
262
|
+
local_role.save
|
263
|
+
rescue Exception => e
|
264
|
+
ui.error "Unable to create #{role} role (#{e.message})"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Dump missing roles locally
|
269
|
+
(remote_roles - local_roles).each do |role|
|
270
|
+
ui.msg "- #{role} role is remote only. Dumping to #{File.join(@role_path, "#{role}.json")}"
|
271
|
+
begin
|
272
|
+
remote_role = Chef::Role.load(role)
|
273
|
+
File.open(File.join(@role_path, "#{role}.json"), "w") do |file|
|
274
|
+
file.puts JSON.pretty_generate(remote_role)
|
275
|
+
end
|
276
|
+
rescue Exception => e
|
277
|
+
ui.error "Unable to dump #{role} role (#{e.message})"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Compare roles common to local and remote
|
282
|
+
(remote_roles & local_roles).each do |role|
|
283
|
+
remote_role = Chef::Role.load(role)
|
284
|
+
local_role = Chef::Role.from_disk(role)
|
285
|
+
|
286
|
+
diffs = Array.new
|
287
|
+
to_check.each do |method, display|
|
288
|
+
if remote_role.send(method) != local_role.send(method)
|
289
|
+
updated_roles[role] = local_role
|
290
|
+
diffs << display
|
291
|
+
end
|
292
|
+
end
|
293
|
+
ui.msg("* #{role} role is not up-to-date (#{diffs.join(",")})") unless diffs.empty?
|
294
|
+
end
|
295
|
+
|
296
|
+
if !updated_roles.empty?
|
297
|
+
all = false
|
298
|
+
updated_roles.each do |name, obj|
|
299
|
+
answer = ui.ask_question("Update #{name} role on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
|
300
|
+
|
301
|
+
if answer == "A"
|
302
|
+
all = true
|
303
|
+
elsif answer == "Q"
|
304
|
+
ui.msg "> Aborting role alignment."
|
305
|
+
break
|
306
|
+
end
|
307
|
+
|
308
|
+
if all or answer == "Y"
|
309
|
+
ui.msg "* Updating #{name} role"
|
310
|
+
obj.save
|
311
|
+
else
|
312
|
+
ui.msg "* Skipping #{name} role"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
else
|
316
|
+
ui.msg "> Roles are up-to-date."
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
### Utility methods ###
|
321
|
+
|
322
|
+
def log_action(message)
|
323
|
+
ui.msg(message)
|
324
|
+
|
325
|
+
if @cfg["logging"]["enabled"]
|
326
|
+
begin
|
327
|
+
require "logger"
|
328
|
+
log_file = File.expand_path(@cfg["logging"]["destination"])
|
329
|
+
log = Logger.new(log_file)
|
330
|
+
log.info(message)
|
331
|
+
log.close
|
332
|
+
rescue Exception => e
|
333
|
+
ui.error "Oops ! #{e.inspect} ! message to log was #{message}"
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def hubot_notify(cookbook, to_version, environment)
|
339
|
+
unless @cfg["notification"]["hubot"]["enabled"] == true and config[:quiet] == false
|
340
|
+
ui.msg "Aborting due to quiet or config disabled" if config[:debug]
|
341
|
+
return
|
342
|
+
end
|
343
|
+
|
344
|
+
begin
|
345
|
+
require "net/http"
|
346
|
+
require "uri"
|
347
|
+
uri = URI.parse(@cfg["notification"]["hubot"]["url"] + @cfg["notification"]["hubot"]["channel"])
|
348
|
+
user = @cfg["notification"]["hubot"]["username"]
|
349
|
+
|
350
|
+
Net::HTTP.post_form(uri, { "cookbook" => cookbook,
|
351
|
+
"user" => user,
|
352
|
+
"to_version" => to_version,
|
353
|
+
"environment" => environment })
|
354
|
+
rescue
|
355
|
+
ui.error "Oops ! could not notify hubot !"
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'chef/knife'
|
2
|
+
|
3
|
+
module KnifeSharp
|
4
|
+
class SharpBackup < Chef::Knife
|
5
|
+
|
6
|
+
banner "knife sharp backup"
|
7
|
+
|
8
|
+
deps do
|
9
|
+
require 'chef/cookbook/metadata'
|
10
|
+
require 'chef/cookbook_loader'
|
11
|
+
require 'chef/cookbook_uploader'
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
setup()
|
16
|
+
backup()
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup
|
20
|
+
# Sharp config
|
21
|
+
cfg_files = [ "/etc/sharp-config.yml", "~/.chef/sharp-config.yml" ]
|
22
|
+
loaded = false
|
23
|
+
cfg_files.each do |cfg_file|
|
24
|
+
begin
|
25
|
+
@cfg = YAML::load_file(File.expand_path(cfg_file))
|
26
|
+
loaded = true
|
27
|
+
rescue Exception => e
|
28
|
+
ui.error "Error on loading config : #{e.inspect}" if config[:debug]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
unless loaded == true
|
32
|
+
ui.error "config could not be loaded ! Tried the following files : #{cfg_files.join(", ")}"
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
|
36
|
+
# Knife config
|
37
|
+
if (ENV['HOME'] && File.exist?(File.join(ENV['HOME'], '.chef', 'knife.rb')))
|
38
|
+
Chef::Config.from_file(File.join(ENV['HOME'], '.chef', 'knife.rb'))
|
39
|
+
else
|
40
|
+
ui.error "Cannot find knife.rb config file"
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def backup
|
47
|
+
timestamp = Time.now.to_i
|
48
|
+
|
49
|
+
unless @cfg["global"]["backupdir"]
|
50
|
+
ui.error "You need to add global/backupdir to your config file"
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
|
54
|
+
backup_path = @cfg["global"]["backupdir"] + "/backup-#{timestamp}"
|
55
|
+
|
56
|
+
if File.exist?(backup_path)
|
57
|
+
ui.error "Backup path (#{backup_path}) exists. Will not overwrite."
|
58
|
+
exit 1
|
59
|
+
end
|
60
|
+
|
61
|
+
ui.msg("Backing up roles")
|
62
|
+
FileUtils.mkdir_p(backup_path + "/roles")
|
63
|
+
Chef::Role.list.keys.each do |role|
|
64
|
+
begin
|
65
|
+
remote_role = Chef::Role.load(role)
|
66
|
+
File.open(backup_path + "/roles/#{role}.json", "w") do |file|
|
67
|
+
file.puts JSON.pretty_generate(remote_role)
|
68
|
+
end
|
69
|
+
rescue Exception => e
|
70
|
+
ui.error "Unable to dump #{role} role (#{e.message})"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
ui.msg("Backing up environments")
|
75
|
+
FileUtils.mkdir_p(backup_path + "/environments")
|
76
|
+
Chef::Environment.list.keys.each do |environment|
|
77
|
+
begin
|
78
|
+
env = Chef::Environment.load(environment)
|
79
|
+
File.open(backup_path + "/environments/#{environment}.json", "w") do |file|
|
80
|
+
file.puts JSON.pretty_generate(env)
|
81
|
+
end
|
82
|
+
rescue Exception => e
|
83
|
+
ui.error "Unable to dump #{environment} environment (#{e.message})"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
ui.msg("Backing up databags")
|
88
|
+
FileUtils.mkdir_p(backup_path + "/databags")
|
89
|
+
Chef::DataBag.list.keys.each do |bag|
|
90
|
+
Dir.mkdir(backup_path + "/databags/" + bag)
|
91
|
+
Chef::DataBag.load(bag).keys.each do |item|
|
92
|
+
begin
|
93
|
+
data = Chef::DataBagItem.load(bag,item)
|
94
|
+
File.open(backup_path + "/databags/#{bag}/#{item}.json", "w") do |file|
|
95
|
+
file.puts JSON.pretty_generate(data)
|
96
|
+
end
|
97
|
+
rescue Exception => e
|
98
|
+
ui.error "Unable to dump item #{item} from databag #{bag} (#{e.message})"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module KnifeSharp
|
2
|
+
class SharpHistory < Chef::Knife
|
3
|
+
banner "knife sharp history"
|
4
|
+
|
5
|
+
option :debug,
|
6
|
+
:long => '--debug',
|
7
|
+
:description => "turn debug on",
|
8
|
+
:default => false
|
9
|
+
|
10
|
+
deps do
|
11
|
+
require 'chef/data_bag'
|
12
|
+
require 'chef/data_bag_item'
|
13
|
+
require 'chef/json_compat'
|
14
|
+
require 'chef/knife/search'
|
15
|
+
require 'chef/environment'
|
16
|
+
require 'chef/cookbook/metadata'
|
17
|
+
require 'chef/knife/core/object_loader'
|
18
|
+
end
|
19
|
+
|
20
|
+
@@cfg_files = [ "/etc/sharp-config.yml", "~/.chef/sharp-config.yml" ]
|
21
|
+
|
22
|
+
def load_config
|
23
|
+
loaded = false
|
24
|
+
@@cfg_files.each do |cfg_file|
|
25
|
+
begin
|
26
|
+
@@cfg=YAML::load_file(File.expand_path(cfg_file))
|
27
|
+
loaded = true
|
28
|
+
rescue Exception => e
|
29
|
+
puts "Error on loading config : #{e.inspect}" if config[:debug]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
unless loaded == true
|
33
|
+
ui.error "config could not be loaded ! Tried the following files : #{@@cfg_files.join(", ")}"
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
puts @@cfg.inspect if config[:debug]
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
load_config()
|
41
|
+
show_logs()
|
42
|
+
end
|
43
|
+
|
44
|
+
def show_logs()
|
45
|
+
begin
|
46
|
+
fp = File.open(File.expand_path(@@cfg["logging"]["destination"]), "r")
|
47
|
+
fp.readlines.each do |line|
|
48
|
+
puts line
|
49
|
+
end
|
50
|
+
rescue Exception => e
|
51
|
+
ui.error "oops ! #{e.inspect}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module KnifeSharp
|
2
|
+
class SharpServer < Chef::Knife
|
3
|
+
|
4
|
+
banner "knife sharp server [SERVERNAME]"
|
5
|
+
|
6
|
+
def run
|
7
|
+
if File.exists?(knife_conf)
|
8
|
+
if !File.symlink?(knife_conf)
|
9
|
+
ui.error "#{knife_conf} is not a symlink."
|
10
|
+
ui.msg "Copy it to #{knife_conf("<server name>")} and try again."
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
unless name_args.size == 1
|
15
|
+
list_configs
|
16
|
+
exit 0
|
17
|
+
end
|
18
|
+
|
19
|
+
update_config(name_args.first)
|
20
|
+
end
|
21
|
+
|
22
|
+
def update_config(new_server)
|
23
|
+
if File.exists?(knife_conf(new_server))
|
24
|
+
File.unlink(knife_conf) if File.exists?(knife_conf)
|
25
|
+
File.symlink(knife_conf(new_server), knife_conf)
|
26
|
+
ui.msg "The knife configuration has been updated to use #{new_server}."
|
27
|
+
else
|
28
|
+
ui.error "Knife configuration for #{new_server} not found."
|
29
|
+
list_configs
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def list_configs
|
35
|
+
current_conf = File.readlink(knife_conf) if File.exists?(knife_conf)
|
36
|
+
avail_confs = Dir.glob(File.join(Chef::Knife::chef_config_dir, "knife-*.rb"))
|
37
|
+
if !avail_confs.empty?
|
38
|
+
ui.msg "Available servers:"
|
39
|
+
avail_confs.each do |file|
|
40
|
+
name = file.match(/#{Chef::Knife::chef_config_dir}\/knife-(?<srv>.*)\.rb/)
|
41
|
+
prefix = (file == current_conf) ? ">> " : " "
|
42
|
+
ui.msg "#{prefix}#{name[:srv]} (#{file})"
|
43
|
+
end
|
44
|
+
else
|
45
|
+
ui.msg "No knife server configuration file found."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def knife_conf(server = nil)
|
50
|
+
srv = server ? "-#{server}" : ""
|
51
|
+
File.join(Chef::Knife::chef_config_dir, "knife#{srv}.rb")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/knife-sharp.rb
ADDED
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: knife-sharp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nicolas Szalay
|
9
|
+
- Jonathan Amiez
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-10-12 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: chef
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: grit
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ~>
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 2.5.0
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 2.5.0
|
47
|
+
description: Sharpen your knife
|
48
|
+
email: nico@rottenbytes.info
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- README.md
|
54
|
+
- ext/sharp-config.yml
|
55
|
+
- lib/knife-sharp.rb
|
56
|
+
- lib/chef/knife/sharp-align.rb
|
57
|
+
- lib/chef/knife/sharp-backup.rb
|
58
|
+
- lib/chef/knife/sharp-history.rb
|
59
|
+
- lib/chef/knife/sharp-server.rb
|
60
|
+
homepage: http://www.rottenbytes.info
|
61
|
+
licenses: []
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.8.24
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Knife sharp plugin
|
84
|
+
test_files: []
|