knife-sharp 0.4.5 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,56 +1,32 @@
1
1
  require 'chef/knife'
2
+ require 'knife-sharp/common'
2
3
 
3
4
  module KnifeSharp
4
5
  class SharpBackup < Chef::Knife
6
+ include KnifeSharp::Common
5
7
 
6
8
  banner "knife sharp backup"
7
9
 
8
10
  deps do
9
- require 'chef/cookbook/metadata'
10
- require 'chef/cookbook_loader'
11
+ require 'chef/environment'
12
+ require 'chef/role'
13
+ require 'chef/data_bag'
14
+ require 'chef/data_bag_item'
11
15
  end
12
16
 
13
17
  def run
14
- setup()
15
18
  backup()
16
19
  end
17
20
 
18
- def setup
19
- # Sharp config
20
- cfg_files = [ "/etc/sharp-config.yml", "~/.chef/sharp-config.yml" ]
21
- loaded = false
22
- cfg_files.each do |cfg_file|
23
- begin
24
- @cfg = YAML::load_file(File.expand_path(cfg_file))
25
- loaded = true
26
- rescue Exception => e
27
- ui.error "Error on loading config : #{e.inspect}" if config[:debug]
28
- end
29
- end
30
- unless loaded == true
31
- ui.error "config could not be loaded ! Tried the following files : #{cfg_files.join(", ")}"
32
- exit 1
33
- end
34
-
35
- # Knife config
36
- if (ENV['HOME'] && File.exist?(File.join(ENV['HOME'], '.chef', 'knife.rb')))
37
- Chef::Config.from_file(File.join(ENV['HOME'], '.chef', 'knife.rb'))
38
- else
39
- ui.error "Cannot find knife.rb config file"
40
- exit 1
41
- end
42
-
43
- end
44
-
45
21
  def backup
46
22
  timestamp = Time.now.to_i
47
23
 
48
- unless @cfg["global"]["backupdir"]
24
+ unless sharp_config["global"]["backupdir"]
49
25
  ui.error "You need to add global/backupdir to your config file"
50
26
  exit 1
51
27
  end
52
28
 
53
- backup_path = @cfg["global"]["backupdir"] + "/backup-#{timestamp}"
29
+ backup_path = sharp_config["global"]["backupdir"] + "/backup-#{timestamp}"
54
30
 
55
31
  if File.exist?(backup_path)
56
32
  ui.error "Backup path (#{backup_path}) exists. Will not overwrite."
@@ -0,0 +1,149 @@
1
+ require 'chef/knife'
2
+ require 'knife-sharp/common'
3
+
4
+ module KnifeSharp
5
+ class SharpCookbookAlign < Chef::Knife
6
+ include KnifeSharp::Common
7
+
8
+ banner "knife sharp cookbook align BRANCH ENVIRONMENT [OPTS]"
9
+
10
+ deps do
11
+ require 'chef/cookbook/metadata'
12
+ require 'chef/cookbook_loader'
13
+ require 'chef/cookbook_uploader'
14
+ require 'chef/environment'
15
+ end
16
+
17
+ def run
18
+ ui.error "Not implemented yet. Use ```knife sharp align -C```"
19
+ end
20
+
21
+ ### Cookbook methods ###
22
+
23
+ def check_cookbooks(env)
24
+ to_update = Array.new
25
+
26
+ unless File.exists?(cookbook_path)
27
+ ui.warn "Bad cookbook path, skipping cookbook sync."
28
+ return to_update
29
+ end
30
+
31
+ ui.msg(ui.color("== Cookbooks ==", :bold))
32
+
33
+ updated_versions = Hash.new
34
+ local_versions = local_cookbook_versions
35
+ remote_versions = remote_cookbook_versions(env)
36
+
37
+ if local_versions.empty?
38
+ ui.warn "No local cookbooks found, is the cookbook path correct ? (#{cookbook_path})"
39
+ return to_update
40
+ end
41
+
42
+ # get local-only cookbooks
43
+ (local_versions.keys - remote_versions.keys).each do |cb|
44
+ updated_versions[cb] = local_versions[cb]
45
+ ui.msg "* #{cb} is local only (version #{local_versions[cb]})"
46
+ end
47
+
48
+ # get cookbooks not up-to-date
49
+ (remote_versions.keys & local_versions.keys).each do |cb|
50
+ if Chef::VersionConstraint.new("> #{remote_versions[cb]}").include?(local_versions[cb])
51
+ updated_versions[cb] = local_versions[cb]
52
+ ui.msg "* #{cb} is not up-to-date (local: #{local_versions[cb]}/remote: #{remote_versions[cb]})"
53
+ elsif Chef::VersionConstraint.new("> #{local_versions[cb]}").include?(remote_versions[cb]) and config[:force_align]
54
+ updated_versions[cb] = local_versions[cb]
55
+ ui.msg "* #{cb} is to be downgraded (local: #{local_versions[cb]}/remote: #{remote_versions[cb]})"
56
+ end
57
+ end
58
+
59
+ if sharp_config[chef_server] and sharp_config[chef_server].has_key?("ignore_cookbooks")
60
+ (updated_versions.keys & sharp_config[chef_server]["ignore_cookbooks"]).each do |cb|
61
+ updated_versions.delete(cb)
62
+ ui.msg "* Skipping #{cb} cookbook (ignore list)"
63
+ end
64
+ end
65
+
66
+ if !updated_versions.empty?
67
+ all = false
68
+ updated_versions.each_pair do |cb,version|
69
+ answer = ui.ask_question("> Update #{cb} cookbook to #{version} on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
70
+
71
+ if answer == "A"
72
+ all = true
73
+ elsif answer == "Q"
74
+ ui.msg "* Skipping next cookbooks alignment."
75
+ break
76
+ end
77
+
78
+ if all or answer == "Y"
79
+ to_update << cb
80
+ else
81
+ ui.msg "* Skipping #{cb} cookbook"
82
+ end
83
+ end
84
+ else
85
+ ui.msg "* Environment #{env} is up-to-date."
86
+ end
87
+
88
+ to_update
89
+ end
90
+
91
+
92
+ def bump_cookbooks(env_name, cookbook_list)
93
+ unless cookbook_list.empty?
94
+ env = Chef::Environment.load(env_name)
95
+ cbs = Array.new
96
+ backup_data = Hash.new
97
+ backup_data["environment"] = env.name
98
+ backup_data["cookbook_versions"] = Hash.new
99
+ cookbook_list.each do |cb_name|
100
+ cb = cookbook_loader[cb_name]
101
+ if sharp_config["rollback"] && sharp_config["rollback"]["enabled"] == true
102
+ backup_data["cookbook_versions"][cb_name] = env.cookbook_versions[cb_name]
103
+ end
104
+ # Force "= a.b.c" in cookbook version, as chef11 will not accept "a.b.c"
105
+ env.cookbook_versions[cb_name] = "= #{cb.version}"
106
+ cbs << cb
107
+ end
108
+
109
+ ui.msg "* Uploading cookbook(s) #{cookbook_list.join(", ")}"
110
+ cookbook_uploader(cbs).upload_cookbooks
111
+
112
+ if env.save
113
+ cbs.each do |cb|
114
+ ui.msg "* Bumping #{cb.name} to #{cb.version} for environment #{env.name}"
115
+ log_action("bumping #{cb.name} to #{cb.version} for environment #{env.name}")
116
+ end
117
+ end
118
+
119
+ if sharp_config["rollback"] && sharp_config["rollback"]["enabled"] == true
120
+ identifier = Time.now.to_i
121
+ Dir.mkdir(sharp_config["rollback"]["destination"]) unless File.exists?(sharp_config["rollback"]["destination"])
122
+ fp = open(File.join(sharp_config["rollback"]["destination"], "#{identifier}.json"), "w")
123
+ fp.write(JSON.pretty_generate(backup_data))
124
+ fp.close()
125
+ end
126
+ end
127
+ end
128
+
129
+ def cookbook_loader
130
+ @cookbook_loader ||= Chef::CookbookLoader.new(Chef::Config.cookbook_path)
131
+ end
132
+
133
+ def cookbook_uploader(cookbooks)
134
+ if Gem::Version.new(Chef::VERSION).release >= Gem::Version.new('12.0.0')
135
+ uploader = Chef::CookbookUploader.new(cookbooks)
136
+ else
137
+ uploader = Chef::CookbookUploader.new(cookbooks, Chef::Config.cookbook_path)
138
+ end
139
+ end
140
+
141
+ def local_cookbook_versions
142
+ Hash[Dir.glob("#{cookbook_path}/*").select {|cb| File.directory?(cb)}.map {|cb| [File.basename(cb), cookbook_loader[File.basename(cb)].version] }]
143
+ end
144
+
145
+ def remote_cookbook_versions(env)
146
+ Chef::Environment.load(env).cookbook_versions.each_value {|v| v.gsub!("= ", "")}
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,141 @@
1
+ require 'chef/knife'
2
+ require 'knife-sharp/common'
3
+
4
+ module KnifeSharp
5
+ class SharpDataBagAlign < Chef::Knife
6
+ include KnifeSharp::Common
7
+
8
+ banner "knife sharp data bag align BRANCH [OPTS]"
9
+
10
+ deps do
11
+ require 'chef/data_bag'
12
+ require 'chef/data_bag_item'
13
+ end
14
+
15
+ def run
16
+ ui.error "Not implemented yet. Use ```knife sharp align -D```"
17
+ end
18
+
19
+ ### Databag methods ###
20
+
21
+ def check_databags
22
+ to_update = Hash.new
23
+
24
+ unless File.exists?(data_bag_path)
25
+ ui.warn "Bad data bag path, skipping data bag sync."
26
+ return to_update
27
+ end
28
+
29
+ ui.msg(ui.color("== Data bags ==", :bold))
30
+
31
+ updated_dbs = Hash.new
32
+ local_dbs = Dir.glob(File.join(data_bag_path, "**/*.json")).map {|f| [File.dirname(f).split("/").last, File.basename(f, ".json")]}
33
+ remote_dbs = Chef::DataBag.list.keys.map {|db| Chef::DataBag.load(db).keys.map{|dbi| [db, dbi]}}.flatten(1)
34
+
35
+ if local_dbs.empty?
36
+ ui.warn "No local data bags found, is the data bag path correct ? (#{data_bag_path})"
37
+ return to_update
38
+ end
39
+
40
+ # Dump missing data bags locally
41
+ (remote_dbs - local_dbs).each do |db|
42
+ ui.msg "* #{db.join("/")} data bag item is remote only"
43
+ if config[:dump_remote_only]
44
+ ui.msg "* Dumping to #{File.join(data_bag_path, "#{db.join("/")}.json")}"
45
+ begin
46
+ remote_db = Chef::DataBagItem.load(db.first, db.last).raw_data
47
+ Dir.mkdir(File.join(data_bag_path, db.first)) unless File.exists?(File.join(data_bag_path, db.first))
48
+ File.open(File.join(data_bag_path, "#{db.join("/")}.json"), "w") do |file|
49
+ file.puts JSON.pretty_generate(remote_db)
50
+ end
51
+ rescue Exception => e
52
+ ui.error "Unable to dump #{db.join("/")} data bag item (#{e.message})"
53
+ end
54
+ end
55
+ end
56
+
57
+ # Create new data bags on server
58
+ (local_dbs - remote_dbs).each do |db|
59
+ begin
60
+ local_db = JSON::load(File.read(File.join(data_bag_path, "#{db.join("/")}.json")))
61
+ updated_dbs[db] = local_db
62
+ ui.msg "* #{db.join("/")} data bag item is local only"
63
+ rescue Exception => e
64
+ ui.error "Unable to load #{db.join("/")} data bag item (#{e.message})"
65
+ end
66
+ end
67
+
68
+ # Compare roles common to local and remote
69
+ (remote_dbs & local_dbs).each do |db|
70
+ begin
71
+ remote_db = Chef::DataBagItem.load(db.first, db.last).raw_data
72
+ local_db = JSON::load(File.read(File.join(data_bag_path, "#{db.join("/")}.json")))
73
+ if remote_db != local_db
74
+ updated_dbs[db] = local_db
75
+ ui.msg("* #{db.join("/")} data bag item is not up-to-date")
76
+ end
77
+ rescue Exception => e
78
+ ui.error "Unable to load #{db.join("/")} data bag item (#{e.message})"
79
+ end
80
+ end
81
+
82
+ if sharp_config[chef_server] and sharp_config[chef_server].has_key?("ignore_databags")
83
+ (updated_dbs.keys.map{|k| k.join("/")} & sharp_config[chef_server]["ignore_databags"]).each do |db|
84
+ updated_dbs.delete(db.split("/"))
85
+ ui.msg "* Skipping #{db} data bag (ignore list)"
86
+ end
87
+ end
88
+
89
+ if !updated_dbs.empty?
90
+ all = false
91
+ updated_dbs.each do |name, obj|
92
+ answer = nil
93
+ answer = ui.ask_question("> Update #{name.join("/")} data bag item on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
94
+
95
+ if answer == "A"
96
+ all = true
97
+ elsif answer == "Q"
98
+ ui.msg "* Aborting data bag alignment."
99
+ break
100
+ end
101
+
102
+ if all or answer == "Y"
103
+ to_update[name] = obj
104
+ else
105
+ ui.msg "* Skipping #{name.join("/")} data bag item"
106
+ end
107
+ end
108
+ else
109
+ ui.msg "* Data bags are up-to-date."
110
+ end
111
+
112
+ to_update
113
+ end
114
+
115
+ def update_databags(databag_list)
116
+ parent_databags = Chef::DataBag.list.keys
117
+ databag_list.each do |name, obj|
118
+ begin
119
+ # create the parent if needed
120
+ unless parent_databags.include?(name.first)
121
+ db = Chef::DataBag.new
122
+ db.name(name.first)
123
+ db.create
124
+ # add it to the list to avoid trying to recreate it
125
+ parent_databags.push(name.first)
126
+ ui.msg("* Creating data bag #{name.first}")
127
+ log_action("creating data bag #{name.first}")
128
+ end
129
+ db = Chef::DataBagItem.new
130
+ db.data_bag(name.first)
131
+ db.raw_data = obj
132
+ db.save
133
+ ui.msg "* Updating #{name.join("/")} data bag item"
134
+ log_action("updating #{name.join("/")} data bag item")
135
+ rescue Exception => e
136
+ ui.error "Unable to update #{name.join("/")} data bag item"
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -1,49 +1,23 @@
1
+ require 'knife-sharp/common'
2
+
1
3
  module KnifeSharp
2
4
  class SharpHistory < Chef::Knife
5
+ include KnifeSharp::Common
6
+
3
7
  banner "knife sharp history"
4
-
8
+
5
9
  option :debug,
6
10
  :long => '--debug',
7
11
  :description => "turn debug on",
8
12
  :default => false
9
13
 
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
14
  def run
40
- load_config()
41
15
  show_logs()
42
16
  end
43
17
 
44
18
  def show_logs()
45
19
  begin
46
- fp = File.open(File.expand_path(@@cfg["logging"]["destination"]), "r")
20
+ fp = File.open(File.expand_path(sharp_config["logging"]["destination"]), "r")
47
21
  fp.readlines.each do |line|
48
22
  puts line
49
23
  end
@@ -51,7 +25,5 @@ module KnifeSharp
51
25
  ui.error "oops ! #{e.inspect}"
52
26
  end
53
27
  end
54
-
55
-
56
28
  end
57
29
  end
@@ -0,0 +1,134 @@
1
+ require 'chef/knife'
2
+ require 'knife-sharp/common'
3
+
4
+ module KnifeSharp
5
+ class SharpRoleAlign < Chef::Knife
6
+ include KnifeSharp::Common
7
+
8
+ banner "knife sharp role align BRANCH [OPTS]"
9
+
10
+ deps do
11
+ require 'chef/role'
12
+ end
13
+
14
+ def run
15
+ ui.error "Not implemented yet. Use ```knife sharp align -R```"
16
+ end
17
+
18
+ ### Role methods ###
19
+
20
+ def check_roles
21
+ to_update = Hash.new
22
+
23
+ unless File.exists?(role_path)
24
+ ui.warn "Bad role path, skipping role sync."
25
+ return to_update
26
+ end
27
+
28
+ ui.msg(ui.color("== Roles ==", :bold))
29
+
30
+ updated_roles = Hash.new
31
+ local_roles = Dir.glob(File.join(role_path, "*.json")).map {|file| File.basename(file, ".json")}
32
+ remote_roles = Chef::Role.list.keys
33
+
34
+ if local_roles.empty?
35
+ ui.warn "No local roles found, is the role path correct ? (#{role_path})"
36
+ return to_update
37
+ end
38
+
39
+ # Dump missing roles locally
40
+ (remote_roles - local_roles).each do |role|
41
+ ui.msg "* #{role} role is remote only"
42
+ if config[:dump_remote_only]
43
+ ui.msg "* Dumping to #{File.join(role_path, "#{role}.json")}"
44
+ begin
45
+ remote_role = Chef::Role.load(role)
46
+ File.open(File.join(role_path, "#{role}.json"), "w") do |file|
47
+ file.puts JSON.pretty_generate(remote_role)
48
+ end
49
+ rescue Exception => e
50
+ ui.error "Unable to dump #{role} role (#{e.message})"
51
+ end
52
+ end
53
+ end
54
+
55
+ # Create new roles on server
56
+ (local_roles - remote_roles).each do |role|
57
+ begin
58
+ local_role = Chef::Role.from_disk(role)
59
+ updated_roles[role] = local_role
60
+ ui.msg "* #{role} role is local only"
61
+ rescue Exception => e
62
+ ui.error "Unable to load #{role} role (#{e.message})"
63
+ end
64
+ end
65
+
66
+ # Compare roles common to local and remote
67
+ (remote_roles & local_roles).each do |role|
68
+ remote_role = Chef::Role.load(role)
69
+ local_role = Chef::Role.from_disk(role)
70
+
71
+ diffs = Array.new
72
+ relevant_role_keys.each do |method, display|
73
+ if remote_role.send(method) != local_role.send(method)
74
+ updated_roles[role] = local_role
75
+ diffs << display
76
+ end
77
+ end
78
+ ui.msg("* #{role} role is not up-to-date (#{diffs.join(",")})") unless diffs.empty?
79
+ end
80
+
81
+ if sharp_config[chef_server] and sharp_config[chef_server].has_key?("ignore_roles")
82
+ (updated_roles.keys & sharp_config[chef_server]["ignore_roles"]).each do |r|
83
+ updated_roles.delete(r)
84
+ ui.msg "* Skipping #{r} role (ignore list)"
85
+ end
86
+ end
87
+
88
+ if !updated_roles.empty?
89
+ all = false
90
+ updated_roles.each do |name, obj|
91
+ answer = ui.ask_question("> Update #{name} role on server ? Y/N/(A)ll/(Q)uit ", :default => "N").upcase unless all
92
+
93
+ if answer == "A"
94
+ all = true
95
+ elsif answer == "Q"
96
+ ui.msg "* Aborting role alignment."
97
+ break
98
+ end
99
+
100
+ if all or answer == "Y"
101
+ to_update[name] = obj
102
+ else
103
+ ui.msg "* Skipping #{name} role"
104
+ end
105
+ end
106
+ else
107
+ ui.msg "* Roles are up-to-date."
108
+ end
109
+
110
+ to_update
111
+ end
112
+
113
+ def update_roles(role_list)
114
+ role_list.each do |name, obj|
115
+ begin
116
+ obj.save
117
+ ui.msg "* Updating #{name} role"
118
+ log_action("updating #{name} role")
119
+ rescue Exception => e
120
+ ui.error "Unable to update #{name} role"
121
+ end
122
+ end
123
+ end
124
+
125
+ def relevant_role_keys
126
+ # role sections to compare (methods)
127
+ {
128
+ "env_run_lists" => "run list",
129
+ "default_attributes" => "default attributes",
130
+ "override_attributes" => "override attributes"
131
+ }
132
+ end
133
+ end
134
+ end