knife-sharp 0.4.5 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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