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.
@@ -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 [--debug]"
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 :debug,
10
- :short => '-d',
11
- :long => '--debug',
12
- :description => "turn debug on",
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 "On server #{@chef_server}" if @chef_server
24
- ui.msg "Aligning cookbooks"
25
- align_cookbooks()
26
- ui.msg "Aligning data bags"
27
- align_databags()
28
- ui.msg "Aligning roles"
29
- align_roles()
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
- ui.error "Usage : knife align BRANCH ENVIRONMENT [--debug]"
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[:debug]
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
- # Checking current branch
88
- current_branch = Grit::Repo.new(@chef_path).head.name
89
- if @branch != current_branch then
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 align_cookbooks
98
- updated_versions = Hash.new()
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] != local_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 = nil
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 "> Skipping next cookbooks alignment."
170
+ ui.msg "* Skipping next cookbooks alignment."
137
171
  break
138
172
  end
139
173
 
140
174
  if all or answer == "Y"
141
- # Force "= a.b.c" in cookbook version, as chef11 will not accept "a.b.c"
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
- unless bumped.empty?
150
- ui.msg "* Uploading cookbook(s) #{bumped.map{|cb| cb.name.to_s}.join(",")}"
151
- uploader = Chef::CookbookUploader.new(bumped, @cb_path)
152
- uploader.upload_cookbooks
153
- if env.save
154
- bumped.each do |cb|
155
- ui.msg "* Bumping #{cb.name.to_s} to #{cb.version} for environment #{@environment}"
156
- log_action("bumping #{cb.name.to_s} to #{cb.version} for environment #{@environment}")
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 align_databags
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
- ui.warn "No local data bags found, is the role path correct ? (#{@role_path})" if local_dbs.empty?
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 = Chef::DataBagItem.new
184
- local_db.data_bag(db.first)
185
- local_db.raw_data = JSON::load(File.read(File.join(@db_path, "#{db.join("/")}.json")))
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 create #{db.join("/")} data bag item (#{e.message})"
253
+ ui.error "Unable to load #{db.join("/")} data bag item (#{e.message})"
189
254
  end
190
255
  end
191
256
 
192
- # Dump missing data bags locally
193
- (remote_dbs - local_dbs).each do |db|
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
- Dir.mkdir(File.join(@db_path, db.first)) unless File.exists?(File.join(@db_path, db.first))
198
- File.open(File.join(@db_path, "#{db.join("/")}.json"), "w") do |file|
199
- file.puts JSON.pretty_generate(remote_db)
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 dump #{db.join("/")} data bag item (#{e.message})"
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 "> Aborting data bag alignment."
279
+ ui.msg "* Aborting data bag alignment."
226
280
  break
227
281
  end
228
282
 
229
283
  if all or answer == "Y"
230
- ui.msg "* Updating #{name.join("/")} data bag item"
231
- log_action("updating #{name.join("/")} data bag item")
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
- else
237
- ui.msg "* Skipping #{name.join("/")} data bag item"
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 align_roles
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
- ui.warn "No local roles found, is the role path correct ? (#{@role_path})" if local_roles.empty?
265
-
266
- # Create new roles on server
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 "- #{role} role is remote only. Dumping to #{File.join(@role_path, "#{role}.json")}"
280
- begin
281
- remote_role = Chef::Role.load(role)
282
- File.open(File.join(@role_path, "#{role}.json"), "w") do |file|
283
- file.puts JSON.pretty_generate(remote_role)
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 dump #{role} role (#{e.message})"
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 "> Aborting role alignment."
387
+ ui.msg "* Aborting role alignment."
314
388
  break
315
389
  end
316
390
 
317
391
  if all or answer == "Y"
318
- ui.msg "* Updating #{name} role"
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 "> Roles are up-to-date."
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
@@ -1,3 +1,3 @@
1
1
  module KnifeSharp
2
- VERSION = "0.1.8"
2
+ VERSION = "0.2.0"
3
3
  end
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.1.8
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-03-20 00:00:00.000000000 Z
13
+ date: 2013-04-23 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: chef