knife-sharp 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/chef/knife/sharp-align.rb +201 -116
- data/lib/knife-sharp.rb +1 -1
- metadata +2 -2
@@ -4,12 +4,20 @@ require 'grit'
|
|
4
4
|
module KnifeSharp
|
5
5
|
class SharpAlign < Chef::Knife
|
6
6
|
|
7
|
-
banner "knife sharp align BRANCH ENVIRONMENT [
|
7
|
+
banner "knife sharp align BRANCH ENVIRONMENT [OPTS]"
|
8
|
+
|
9
|
+
[:cookbooks, :databags, :roles].each do |opt|
|
10
|
+
option opt,
|
11
|
+
:short => "-#{opt.to_s[0].upcase}",
|
12
|
+
:long => "--#{opt}-only",
|
13
|
+
:description => "sync #{opt} only",
|
14
|
+
:default => false
|
15
|
+
end
|
8
16
|
|
9
|
-
option :
|
10
|
-
:short =>
|
11
|
-
:long
|
12
|
-
:description => "
|
17
|
+
option :dump_remote_only,
|
18
|
+
:short => "-B",
|
19
|
+
:long => "--dump-remote-only",
|
20
|
+
:description => "dump items not present locally (roles/databags)",
|
13
21
|
:default => false
|
14
22
|
|
15
23
|
deps do
|
@@ -20,22 +28,37 @@ module KnifeSharp
|
|
20
28
|
|
21
29
|
def run
|
22
30
|
setup()
|
23
|
-
ui.msg
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
31
|
+
ui.msg(ui.color("On server #{@chef_server}", :bold)) if @chef_server
|
32
|
+
check_cookbooks if @do_cookbooks
|
33
|
+
check_databags if @do_databags
|
34
|
+
check_roles if @do_roles
|
35
|
+
|
36
|
+
# All questions asked, can we proceed ?
|
37
|
+
if @cookbooks.empty? and @databags.empty? and @roles.empty?
|
38
|
+
ui.msg "Nothing else to do"
|
39
|
+
exit 0
|
40
|
+
end
|
41
|
+
|
42
|
+
ui.confirm(ui.color("> Proceed ", :bold))
|
43
|
+
bump_cookbooks if @do_cookbooks
|
44
|
+
update_databags if @do_databags
|
45
|
+
update_roles if @do_roles
|
30
46
|
end
|
31
47
|
|
32
48
|
def setup
|
33
49
|
# Checking args
|
34
50
|
if name_args.count != 2
|
35
|
-
|
51
|
+
show_usage
|
36
52
|
exit 1
|
37
53
|
end
|
38
54
|
|
55
|
+
# check cli flags
|
56
|
+
if config[:cookbooks] or config[:databags] or config[:roles]
|
57
|
+
@do_cookbooks, @do_databags, @do_roles = config[:cookbooks], config[:databags], config[:roles]
|
58
|
+
else
|
59
|
+
@do_cookbooks, @do_databags, @do_roles = true, true, true
|
60
|
+
end
|
61
|
+
|
39
62
|
# Sharp config
|
40
63
|
cfg_files = [ "/etc/sharp-config.yml", "~/.chef/sharp-config.yml" ]
|
41
64
|
loaded = false
|
@@ -44,7 +67,7 @@ module KnifeSharp
|
|
44
67
|
@cfg = YAML::load_file(File.expand_path(cfg_file))
|
45
68
|
loaded = true
|
46
69
|
rescue Exception => e
|
47
|
-
ui.error "Error on loading config : #{e.inspect}" if config[:
|
70
|
+
ui.error "Error on loading config : #{e.inspect}" if config[:verbosity] > 0
|
48
71
|
end
|
49
72
|
end
|
50
73
|
unless loaded == true
|
@@ -52,6 +75,17 @@ module KnifeSharp
|
|
52
75
|
exit 1
|
53
76
|
end
|
54
77
|
|
78
|
+
# Env setup
|
79
|
+
@branch, @environment = name_args
|
80
|
+
@chef_path = @cfg["global"]["git_cookbook_path"]
|
81
|
+
|
82
|
+
# Checking current branch
|
83
|
+
current_branch = Grit::Repo.new(@chef_path).head.name
|
84
|
+
if @branch != current_branch then
|
85
|
+
ui.error "Git repo is actually on branch #{current_branch} but you want to align using #{@branch}. Checkout to the desired one."
|
86
|
+
exit 1
|
87
|
+
end
|
88
|
+
|
55
89
|
# Knife config
|
56
90
|
if Chef::Knife.chef_config_dir && File.exists?(File.join(Chef::Knife.chef_config_dir, "knife.rb"))
|
57
91
|
Chef::Config.from_file(File.join(Chef::Knife.chef_config_dir, "knife.rb"))
|
@@ -72,10 +106,6 @@ module KnifeSharp
|
|
72
106
|
end
|
73
107
|
end
|
74
108
|
|
75
|
-
# Env setup
|
76
|
-
@branch, @environment = name_args
|
77
|
-
@chef_path = @cfg["global"]["git_cookbook_path"]
|
78
|
-
|
79
109
|
chefcfg = Chef::Config
|
80
110
|
@cb_path = chefcfg.cookbook_path.is_a?(Array) ? chefcfg.cookbook_path.first : chefcfg.cookbook_path
|
81
111
|
@db_path = chefcfg.data_bag_path.is_a?(Array) ? chefcfg.data_bag_path.first : chefcfg.data_bag_path
|
@@ -84,167 +114,203 @@ module KnifeSharp
|
|
84
114
|
@chef_server = SharpServer.new.current_server
|
85
115
|
@loader = Chef::CookbookLoader.new(@cb_path)
|
86
116
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
ui.error "Git repo is actually on branch #{current_branch} but you want to align using #{@branch}. Checkout to the desired one."
|
91
|
-
exit 1
|
92
|
-
end
|
117
|
+
@cookbooks = Array.new
|
118
|
+
@databags = Hash.new
|
119
|
+
@roles = Hash.new
|
93
120
|
end
|
94
121
|
|
95
122
|
### Cookbook methods ###
|
96
123
|
|
97
|
-
def
|
98
|
-
|
124
|
+
def check_cookbooks
|
125
|
+
unless File.exists?(@cb_path)
|
126
|
+
ui.warn "Bad cookbook path, skipping cookbook sync."
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
ui.msg(ui.color("== Cookbooks ==", :bold))
|
131
|
+
|
132
|
+
updated_versions = Hash.new
|
99
133
|
local_versions = Hash[Dir.glob("#{@cb_path}/*").map {|cb| [File.basename(cb), @loader[File.basename(cb)].version] }]
|
100
134
|
remote_versions = Chef::Environment.load(@environment).cookbook_versions.each_value {|v| v.gsub!("= ", "")}
|
101
135
|
|
136
|
+
if local_versions.empty?
|
137
|
+
ui.warn "No local cookbooks found, is the cookbook path correct ? (#{@cb_path})"
|
138
|
+
return
|
139
|
+
end
|
140
|
+
|
141
|
+
# get local-only cookbooks
|
102
142
|
(local_versions.keys - remote_versions.keys).each do |cb|
|
103
143
|
updated_versions[cb] = local_versions[cb]
|
104
144
|
ui.msg "* #{cb} is local only (version #{local_versions[cb]})"
|
105
145
|
end
|
106
146
|
|
147
|
+
# get cookbooks not up-to-date
|
107
148
|
(remote_versions.keys & local_versions.keys).each do |cb|
|
108
|
-
if remote_versions[cb]
|
149
|
+
if Chef::VersionConstraint.new("> #{remote_versions[cb]}").include?(local_versions[cb])
|
109
150
|
updated_versions[cb] = local_versions[cb]
|
110
151
|
ui.msg "* #{cb} is not up-to-date (local: #{local_versions[cb]}/remote: #{remote_versions[cb]})"
|
111
152
|
end
|
112
153
|
end
|
113
154
|
|
155
|
+
if @cfg[@chef_server] and @cfg[@chef_server].has_key?("ignore_cookbooks")
|
156
|
+
(updated_versions.keys & @cfg[@chef_server]["ignore_cookbooks"]).each do |cb|
|
157
|
+
updated_versions.delete(cb)
|
158
|
+
ui.msg "* Skipping #{cb} cookbook (ignore list)"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
114
162
|
if !updated_versions.empty?
|
115
163
|
all = false
|
116
|
-
bumped = Array.new
|
117
|
-
env = Chef::Environment.load(@environment)
|
118
164
|
updated_versions.each_pair do |cb,version|
|
119
|
-
answer =
|
120
|
-
|
121
|
-
if @cfg[@chef_server]
|
122
|
-
if @cfg[@chef_server]["ignore_cookbooks"]
|
123
|
-
if @cfg[@chef_server]["ignore_cookbooks"].include?(cb)
|
124
|
-
answer = "N"
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
if answer.nil?
|
130
|
-
answer = ui.ask_question("Update #{cb} cookbook item on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
|
131
|
-
end
|
165
|
+
answer = ui.ask_question("> Update #{cb} cookbook to #{version} on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
|
132
166
|
|
133
167
|
if answer == "A"
|
134
168
|
all = true
|
135
169
|
elsif answer == "Q"
|
136
|
-
ui.msg "
|
170
|
+
ui.msg "* Skipping next cookbooks alignment."
|
137
171
|
break
|
138
172
|
end
|
139
173
|
|
140
174
|
if all or answer == "Y"
|
141
|
-
|
142
|
-
env.cookbook_versions[cb] = "= #{version}"
|
143
|
-
bumped << @loader[cb]
|
175
|
+
@cookbooks << cb
|
144
176
|
else
|
145
177
|
ui.msg "* Skipping #{cb} cookbook"
|
146
178
|
end
|
147
179
|
end
|
180
|
+
else
|
181
|
+
ui.msg "* Environment #{@environment} is up-to-date."
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
def bump_cookbooks
|
187
|
+
unless @cookbooks.empty?
|
188
|
+
env = Chef::Environment.load(@environment)
|
189
|
+
cbs = Array.new
|
190
|
+
@cookbooks.each do |cb_name|
|
191
|
+
cb = @loader[cb_name]
|
192
|
+
# Force "= a.b.c" in cookbook version, as chef11 will not accept "a.b.c"
|
193
|
+
env.cookbook_versions[cb_name] = "= #{cb.version}"
|
194
|
+
cbs << cb
|
195
|
+
end
|
148
196
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
end
|
197
|
+
ui.msg "* Uploading cookbook(s) #{@cookbooks.join(", ")}"
|
198
|
+
uploader = Chef::CookbookUploader.new(cbs, @cb_path)
|
199
|
+
uploader.upload_cookbooks
|
200
|
+
|
201
|
+
if env.save
|
202
|
+
cbs.each do |cb|
|
203
|
+
ui.msg "* Bumping #{cb.name} to #{cb.version} for environment #{@environment}"
|
204
|
+
log_action("bumping #{cb.name} to #{cb.version} for environment #{@environment}")
|
158
205
|
end
|
159
206
|
end
|
160
|
-
else
|
161
|
-
ui.msg "> Environment #{@environment} is up-to-date."
|
162
207
|
end
|
163
208
|
end
|
164
209
|
|
165
210
|
### Databag methods ###
|
166
211
|
|
167
|
-
def
|
212
|
+
def check_databags
|
168
213
|
unless File.exists?(@db_path)
|
169
214
|
ui.warn "Bad data bag path, skipping data bag sync."
|
170
215
|
return
|
171
216
|
end
|
172
217
|
|
218
|
+
ui.msg(ui.color("== Data bags ==", :bold))
|
219
|
+
|
173
220
|
updated_dbs = Hash.new
|
174
221
|
local_dbs = Dir.glob(File.join(@db_path, "**/*.json")).map {|f| [File.dirname(f).split("/").last, File.basename(f, ".json")]}
|
175
222
|
remote_dbs = Chef::DataBag.list.keys.map {|db| Chef::DataBag.load(db).keys.map{|dbi| [db, dbi]}}.flatten(1)
|
176
223
|
|
177
|
-
|
224
|
+
if local_dbs.empty?
|
225
|
+
ui.warn "No local data bags found, is the data bag path correct ? (#{@db_path})"
|
226
|
+
return
|
227
|
+
end
|
228
|
+
|
229
|
+
# Dump missing data bags locally
|
230
|
+
(remote_dbs - local_dbs).each do |db|
|
231
|
+
ui.msg "* #{db.join("/")} data bag item is remote only"
|
232
|
+
if config[:dump_remote_only]
|
233
|
+
ui.msg "* Dumping to #{File.join(@db_path, "#{db.join("/")}.json")}"
|
234
|
+
begin
|
235
|
+
remote_db = Chef::DataBagItem.load(db.first, db.last).raw_data
|
236
|
+
Dir.mkdir(File.join(@db_path, db.first)) unless File.exists?(File.join(@db_path, db.first))
|
237
|
+
File.open(File.join(@db_path, "#{db.join("/")}.json"), "w") do |file|
|
238
|
+
file.puts JSON.pretty_generate(remote_db)
|
239
|
+
end
|
240
|
+
rescue Exception => e
|
241
|
+
ui.error "Unable to dump #{db.join("/")} data bag item (#{e.message})"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
178
245
|
|
179
246
|
# Create new data bags on server
|
180
247
|
(local_dbs - remote_dbs).each do |db|
|
181
|
-
ui.msg "+ #{db.join("/")} data bag item is local only. Creating"
|
182
248
|
begin
|
183
|
-
local_db =
|
184
|
-
local_db
|
185
|
-
|
186
|
-
local_db.save
|
249
|
+
local_db = JSON::load(File.read(File.join(@db_path, "#{db.join("/")}.json")))
|
250
|
+
updated_dbs[db] = local_db
|
251
|
+
ui.msg "* #{db.join("/")} data bag item is local only"
|
187
252
|
rescue Exception => e
|
188
|
-
ui.error "Unable to
|
253
|
+
ui.error "Unable to load #{db.join("/")} data bag item (#{e.message})"
|
189
254
|
end
|
190
255
|
end
|
191
256
|
|
192
|
-
#
|
193
|
-
(remote_dbs
|
194
|
-
ui.msg "- #{db.join("/")} data bag item is remote only. Dumping to #{File.join(@db_path, "#{db.join("/")}.json")}"
|
257
|
+
# Compare roles common to local and remote
|
258
|
+
(remote_dbs & local_dbs).each do |db|
|
195
259
|
begin
|
196
260
|
remote_db = Chef::DataBagItem.load(db.first, db.last).raw_data
|
197
|
-
|
198
|
-
|
199
|
-
|
261
|
+
local_db = JSON::load(File.read(File.join(@db_path, "#{db.join("/")}.json")))
|
262
|
+
if remote_db != local_db
|
263
|
+
updated_dbs[db] = local_db
|
264
|
+
ui.msg("* #{db.join("/")} data bag item is not up-to-date")
|
200
265
|
end
|
201
266
|
rescue Exception => e
|
202
|
-
ui.error "Unable to
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
# Compare roles common to local and remote
|
207
|
-
(remote_dbs & local_dbs).each do |db|
|
208
|
-
remote_db = Chef::DataBagItem.load(db.first, db.last).raw_data
|
209
|
-
local_db = JSON::load(File.read(File.join(@db_path, "#{db.join("/")}.json")))
|
210
|
-
|
211
|
-
if remote_db != local_db
|
212
|
-
updated_dbs[db] = local_db
|
213
|
-
ui.msg("* #{db.join("/")} data bag item is not up-to-date")
|
267
|
+
ui.error "Unable to load #{db.join("/")} data bag item (#{e.message})"
|
214
268
|
end
|
215
269
|
end
|
216
270
|
|
217
271
|
if !updated_dbs.empty?
|
218
272
|
all = false
|
219
273
|
updated_dbs.each do |name, obj|
|
220
|
-
answer = ui.ask_question("Update #{name.join("/")} data bag item on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
|
274
|
+
answer = ui.ask_question("> Update #{name.join("/")} data bag item on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
|
221
275
|
|
222
276
|
if answer == "A"
|
223
277
|
all = true
|
224
278
|
elsif answer == "Q"
|
225
|
-
ui.msg "
|
279
|
+
ui.msg "* Aborting data bag alignment."
|
226
280
|
break
|
227
281
|
end
|
228
282
|
|
229
283
|
if all or answer == "Y"
|
230
|
-
|
231
|
-
|
284
|
+
@databags[name] = obj
|
285
|
+
else
|
286
|
+
ui.msg "* Skipping #{name.join("/")} data bag item"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
else
|
290
|
+
ui.msg "* Data bags are up-to-date."
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def update_databags
|
295
|
+
unless @databags.empty?
|
296
|
+
@databags.each do |name, obj|
|
297
|
+
begin
|
232
298
|
db = Chef::DataBagItem.new
|
233
299
|
db.data_bag(name.first)
|
234
300
|
db.raw_data = obj
|
235
301
|
db.save
|
236
|
-
|
237
|
-
|
302
|
+
ui.msg "* Updating #{name.join("/")} data bag item"
|
303
|
+
log_action("updating #{name.join("/")} data bag item")
|
304
|
+
rescue Exception => e
|
305
|
+
ui.error "Unable to update #{name.join("/")} data bag item"
|
238
306
|
end
|
239
307
|
end
|
240
|
-
else
|
241
|
-
ui.msg "> Data bags are up-to-date."
|
242
308
|
end
|
243
309
|
end
|
244
310
|
|
245
311
|
### Role methods ###
|
246
312
|
|
247
|
-
def
|
313
|
+
def check_roles
|
248
314
|
# role sections to compare (methods)
|
249
315
|
to_check = {
|
250
316
|
"env_run_lists" => "run list",
|
@@ -257,33 +323,41 @@ module KnifeSharp
|
|
257
323
|
return
|
258
324
|
end
|
259
325
|
|
326
|
+
ui.msg(ui.color("== Roles ==", :bold))
|
327
|
+
|
260
328
|
updated_roles = Hash.new
|
261
329
|
local_roles = Dir.glob(File.join(@role_path, "*.json")).map {|file| File.basename(file, ".json")}
|
262
330
|
remote_roles = Chef::Role.list.keys
|
263
331
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
(local_roles - remote_roles).each do |role|
|
268
|
-
ui.msg "+ #{role} role is local only. Creating"
|
269
|
-
begin
|
270
|
-
local_role = Chef::Role.from_disk(role)
|
271
|
-
local_role.save
|
272
|
-
rescue Exception => e
|
273
|
-
ui.error "Unable to create #{role} role (#{e.message})"
|
274
|
-
end
|
332
|
+
if local_roles.empty?
|
333
|
+
ui.warn "No local roles found, is the role path correct ? (#{@role_path})"
|
334
|
+
return
|
275
335
|
end
|
276
336
|
|
277
337
|
# Dump missing roles locally
|
278
338
|
(remote_roles - local_roles).each do |role|
|
279
|
-
ui.msg "
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
339
|
+
ui.msg "* #{role} role is remote only"
|
340
|
+
if config[:dump_remote_only]
|
341
|
+
ui.msg "* Dumping to #{File.join(@role_path, "#{role}.json")}"
|
342
|
+
begin
|
343
|
+
remote_role = Chef::Role.load(role)
|
344
|
+
File.open(File.join(@role_path, "#{role}.json"), "w") do |file|
|
345
|
+
file.puts JSON.pretty_generate(remote_role)
|
346
|
+
end
|
347
|
+
rescue Exception => e
|
348
|
+
ui.error "Unable to dump #{role} role (#{e.message})"
|
284
349
|
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# Create new roles on server
|
354
|
+
(local_roles - remote_roles).each do |role|
|
355
|
+
begin
|
356
|
+
local_role = Chef::Role.from_disk(role)
|
357
|
+
updated_roles[role] = local_role
|
358
|
+
ui.msg "* #{role} role is local only"
|
285
359
|
rescue Exception => e
|
286
|
-
ui.error "Unable to
|
360
|
+
ui.error "Unable to load #{role} role (#{e.message})"
|
287
361
|
end
|
288
362
|
end
|
289
363
|
|
@@ -305,25 +379,37 @@ module KnifeSharp
|
|
305
379
|
if !updated_roles.empty?
|
306
380
|
all = false
|
307
381
|
updated_roles.each do |name, obj|
|
308
|
-
answer = ui.ask_question("Update #{name} role on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
|
382
|
+
answer = ui.ask_question("> Update #{name} role on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
|
309
383
|
|
310
384
|
if answer == "A"
|
311
385
|
all = true
|
312
386
|
elsif answer == "Q"
|
313
|
-
ui.msg "
|
387
|
+
ui.msg "* Aborting role alignment."
|
314
388
|
break
|
315
389
|
end
|
316
390
|
|
317
391
|
if all or answer == "Y"
|
318
|
-
|
319
|
-
log_action("updating #{name} role")
|
320
|
-
obj.save
|
392
|
+
@roles[name] = obj
|
321
393
|
else
|
322
394
|
ui.msg "* Skipping #{name} role"
|
323
395
|
end
|
324
396
|
end
|
325
397
|
else
|
326
|
-
ui.msg "
|
398
|
+
ui.msg "* Roles are up-to-date."
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def update_roles
|
403
|
+
unless @roles.empty?
|
404
|
+
@roles.each do |name, obj|
|
405
|
+
begin
|
406
|
+
obj.save
|
407
|
+
ui.msg "* Updating #{name} role"
|
408
|
+
log_action("updating #{name} role")
|
409
|
+
rescue Exception => e
|
410
|
+
ui.error "Unable to update #{name} role"
|
411
|
+
end
|
412
|
+
end
|
327
413
|
end
|
328
414
|
end
|
329
415
|
|
@@ -359,6 +445,5 @@ module KnifeSharp
|
|
359
445
|
ui.error "Unable to notify via hubot."
|
360
446
|
end
|
361
447
|
end
|
362
|
-
|
363
448
|
end
|
364
449
|
end
|
data/lib/knife-sharp.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-sharp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-
|
13
|
+
date: 2013-04-23 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: chef
|