knife-spork 0.1.11 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rvmrc +1 -0
- data/CHANGELOG.md +37 -23
- data/Gemfile +3 -0
- data/LICENSE +35 -0
- data/README.md +202 -219
- data/Rakefile +2 -122
- data/knife-spork.gemspec +20 -67
- data/lib/chef/knife/spork-bump.rb +31 -205
- data/lib/chef/knife/spork-check.rb +77 -125
- data/lib/chef/knife/spork-info.rb +30 -0
- data/lib/chef/knife/spork-promote.rb +105 -417
- data/lib/chef/knife/spork-upload.rb +71 -298
- data/lib/knife-spork.rb +1 -1
- data/lib/knife-spork/plugins.rb +22 -0
- data/lib/knife-spork/plugins/campfire.rb +47 -0
- data/lib/knife-spork/plugins/eventinator.rb +71 -0
- data/lib/knife-spork/plugins/foodcritic.rb +41 -0
- data/lib/knife-spork/plugins/git.rb +123 -0
- data/lib/knife-spork/plugins/graphite.rb +25 -0
- data/lib/knife-spork/plugins/hip_chat.rb +50 -0
- data/lib/knife-spork/plugins/irccat.rb +48 -0
- data/lib/knife-spork/plugins/plugin.rb +74 -0
- data/lib/knife-spork/runner.rb +166 -0
- data/plugins/Campfire.md +43 -0
- data/plugins/Eventinator.md +30 -0
- data/plugins/Foodcritic.md +53 -0
- data/plugins/Git.md +46 -0
- data/plugins/Graphite.md +30 -0
- data/plugins/HipChat.md +50 -0
- data/plugins/Irccat.md +44 -0
- data/plugins/README.md +70 -0
- data/plugins/Template.md +34 -0
- metadata +37 -27
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'knife-spork/plugins/plugin'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module KnifeSpork
|
6
|
+
module Plugins
|
7
|
+
class Eventinator < Plugin
|
8
|
+
name :eventinator
|
9
|
+
|
10
|
+
def perform; end
|
11
|
+
|
12
|
+
def after_upload
|
13
|
+
cookbooks.each do |cookbook|
|
14
|
+
event_data = {
|
15
|
+
:tag => 'knife',
|
16
|
+
:username => current_user,
|
17
|
+
:status => "#{current_user} has uploaded and frozen #{cookbook.name}@#{cookbook.version}",
|
18
|
+
:metadata => {
|
19
|
+
:cookbook_name => cookbook.name,
|
20
|
+
:cookbook_version => cookbook.version
|
21
|
+
}.to_json
|
22
|
+
}
|
23
|
+
eventinate(event_data)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def after_promote_remote
|
28
|
+
environments.each do |environment|
|
29
|
+
cookbooks.each do |cookbook|
|
30
|
+
event_data = {
|
31
|
+
:tag => 'knife',
|
32
|
+
:username => current_user,
|
33
|
+
:status => "#{current_user} has promoted #{cookbook.name}(#{cookbook.version}) to #{environment.name}",
|
34
|
+
:metadata => {
|
35
|
+
:cookbook_name => cookbook.name,
|
36
|
+
:cookbook_version => cookbook.version
|
37
|
+
}.to_json
|
38
|
+
}
|
39
|
+
eventinate(event_data)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def eventinate(event_data)
|
45
|
+
begin
|
46
|
+
uri = URI.parse(config.url)
|
47
|
+
rescue Exception => e
|
48
|
+
ui.error 'Could not parse URI for Eventinator.'
|
49
|
+
ui.error e.to_s
|
50
|
+
return
|
51
|
+
end
|
52
|
+
|
53
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
54
|
+
http.read_timeout = config.read_timeout || 5
|
55
|
+
|
56
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
57
|
+
request.set_form_data(event_data)
|
58
|
+
|
59
|
+
begin
|
60
|
+
response = http.request(request)
|
61
|
+
ui.error "Eventinator at #{config.url} did not receive a good response from the server" if response.code != '200'
|
62
|
+
rescue Timeout::Error
|
63
|
+
ui.error "Eventinator timed out connecting to #{config.url}. Is that URL accessible?"
|
64
|
+
rescue Exception => e
|
65
|
+
ui.error 'Eventinator error.'
|
66
|
+
ui.error e.to_s
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'knife-spork/plugins/plugin'
|
2
|
+
|
3
|
+
module KnifeSpork
|
4
|
+
module Plugins
|
5
|
+
class Foodcritic < Plugin
|
6
|
+
name :foodcritic
|
7
|
+
hooks :after_check, :before_upload
|
8
|
+
|
9
|
+
def perform
|
10
|
+
safe_require 'foodcritic'
|
11
|
+
|
12
|
+
tags = config.tags || []
|
13
|
+
fail_tags = config.fail_tags || ['any']
|
14
|
+
include_rules = config.include_rules || []
|
15
|
+
|
16
|
+
cookbooks.each do |cookbook|
|
17
|
+
ui.info "Running foodcritic against #{cookbook.name}@#{cookbook.version}..."
|
18
|
+
|
19
|
+
cookbook_path = cookbook.root_dir
|
20
|
+
|
21
|
+
ui.info cookbook_path
|
22
|
+
|
23
|
+
options = {:tags => tags, :fail_tags => fail_tags, :include_rules => include_rules}
|
24
|
+
review = ::FoodCritic::Linter.new.check([cookbook_path], options)
|
25
|
+
|
26
|
+
if review.failed?
|
27
|
+
ui.error "Foodcritic failed!"
|
28
|
+
review.to_s.split("\n").each{ |r| ui.error r.to_s }
|
29
|
+
exit(1) if config.epic_fail
|
30
|
+
else
|
31
|
+
ui.info "Passed!"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def epic_fail?
|
37
|
+
config.epic_fail.nil? ? 'true' : config.epic_fail
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'knife-spork/plugins/plugin'
|
2
|
+
|
3
|
+
module KnifeSpork
|
4
|
+
module Plugins
|
5
|
+
class Git < Plugin
|
6
|
+
name :git
|
7
|
+
|
8
|
+
def perform; end
|
9
|
+
|
10
|
+
def before_bump
|
11
|
+
git_pull
|
12
|
+
git_pull_submodules
|
13
|
+
end
|
14
|
+
|
15
|
+
def before_upload
|
16
|
+
git_pull
|
17
|
+
git_pull_submodules
|
18
|
+
end
|
19
|
+
|
20
|
+
def before_promote
|
21
|
+
git_pull
|
22
|
+
git_pull_submodules
|
23
|
+
end
|
24
|
+
|
25
|
+
def after_bump
|
26
|
+
cookbooks.each do |cookbook|
|
27
|
+
git_add("#{cookbook.root_dir}/metadata.rb")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def after_promote_local
|
32
|
+
environments.each do |environment|
|
33
|
+
git_add("./environments/#{environment}.json")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def git
|
39
|
+
safe_require 'git'
|
40
|
+
log = Logger.new(STDOUT)
|
41
|
+
log.level = Logger::WARN
|
42
|
+
@git ||= begin
|
43
|
+
::Git.open('.', :log => log)
|
44
|
+
rescue
|
45
|
+
ui.error 'You are not currently in a git repository. Ensure you are in the proper working directory or remove the git plugin from your KnifeSpork configuration!'
|
46
|
+
exit(0)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# In this case, a git pull will:
|
51
|
+
# - Stash local changes
|
52
|
+
# - Pull from the remote
|
53
|
+
# - Pop the stash
|
54
|
+
def git_pull
|
55
|
+
ui.msg "Pulling latest changes from remote Git repo."
|
56
|
+
begin
|
57
|
+
git.fetch(remote)
|
58
|
+
git.merge("#{remote}/#{branch}")
|
59
|
+
rescue ::Git::GitExecuteError => e
|
60
|
+
ui.error "Could not pull from remote #{remote}/#{branch}. Does it exist?"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def git_pull_submodules
|
65
|
+
ui.msg "Pulling latest changes from git submodules (if any)"
|
66
|
+
output = IO.popen ("git submodule foreach git pull 2>&1")
|
67
|
+
Process.wait
|
68
|
+
exit_code = $?
|
69
|
+
if !exit_code.exitstatus == 0
|
70
|
+
ui.error "#{output.read()}\n"
|
71
|
+
exit 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def git_add(filepath)
|
76
|
+
begin
|
77
|
+
ui.msg "Git add'ing #{filepath}"
|
78
|
+
git.add("#{filepath}")
|
79
|
+
rescue ::Git::GitExecuteError => e
|
80
|
+
ui.error "Git: Something went wrong with git add #{filepath}. Please try running git add manually."
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Commit changes, if any
|
85
|
+
def git_commit
|
86
|
+
begin
|
87
|
+
git.add('.')
|
88
|
+
`git ls-files --deleted`.chomp.split("\n").each{ |f| git.remove(f) }
|
89
|
+
git.commit_all "[KnifeSpork] Bumping cookbooks:\n#{cookbooks.collect{|c| " #{c.name}@#{c.version}"}.join("\n")}"
|
90
|
+
rescue ::Git::GitExecuteError; end
|
91
|
+
end
|
92
|
+
|
93
|
+
def git_push(tags = false)
|
94
|
+
begin
|
95
|
+
git.push remote, branch, tags
|
96
|
+
rescue ::Git::GitExecuteError => e
|
97
|
+
ui.error "Could not push to remote #{remote}/#{branch}. Does it exist?"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def git_tag(tag)
|
102
|
+
begin
|
103
|
+
git.add_tag(tag)
|
104
|
+
rescue ::Git::GitExecuteError => e
|
105
|
+
ui.error "Could not tag #{tag_name}. Does it already exist?"
|
106
|
+
ui.error 'You may need to delete the tag before running promote again.'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def remote
|
111
|
+
config.remote || 'origin'
|
112
|
+
end
|
113
|
+
|
114
|
+
def branch
|
115
|
+
config.branch || 'master'
|
116
|
+
end
|
117
|
+
|
118
|
+
def tag_name
|
119
|
+
cookbooks.collect{|c| "#{c.name}@#{c.version}"}.join('-')
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'knife-spork/plugins/plugin'
|
2
|
+
|
3
|
+
module KnifeSpork
|
4
|
+
module Plugins
|
5
|
+
class Graphite < Plugin
|
6
|
+
name :graphite
|
7
|
+
hooks :after_promote_remote
|
8
|
+
|
9
|
+
def perform
|
10
|
+
environments.each do |environment|
|
11
|
+
begin
|
12
|
+
message = "deploys.chef.#{environment} 1 #{Time.now.to_i}\n"
|
13
|
+
socket = TCPSocket.open(config.server, config.port)
|
14
|
+
socket.write(message)
|
15
|
+
rescue Exception => e
|
16
|
+
ui.error 'Graphite was unable to process the request.'
|
17
|
+
ui.error e.to_s
|
18
|
+
ensure
|
19
|
+
socket.close unless socket.nil?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'knife-spork/plugins/plugin'
|
2
|
+
|
3
|
+
module KnifeSpork
|
4
|
+
module Plugins
|
5
|
+
class HipChat < Plugin
|
6
|
+
name :hip_chat
|
7
|
+
|
8
|
+
def perform; end
|
9
|
+
|
10
|
+
def after_upload
|
11
|
+
hipchat "#{current_user} uploaded the following cookbooks:\n#{cookbooks.collect{ |c| " #{c.name}@#{c.version}" }.join("\n")}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def after_promote_remote
|
15
|
+
hipchat "#{current_user} promoted the following cookbooks:\n#{cookbooks.collect{ |c| " #{c.name}@#{c.version}" }.join("\n")} to #{environments.collect{ |e| "#{e.name}" }.join(", ")}"
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def hipchat(message)
|
20
|
+
safe_require 'hipchat'
|
21
|
+
|
22
|
+
rooms.each do |room_name|
|
23
|
+
begin
|
24
|
+
client = ::HipChat::Client.new(config.api_token)
|
25
|
+
client[room_name].send(nickname, message, notify:notify, color:color)
|
26
|
+
rescue Exception => e
|
27
|
+
ui.error 'Something went wrong sending to HipChat.'
|
28
|
+
ui.error e.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def rooms
|
34
|
+
[ config.room || config.rooms ].flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
def nickname
|
38
|
+
config.nickname || 'KnifeSpork'
|
39
|
+
end
|
40
|
+
|
41
|
+
def notify
|
42
|
+
config.notify.nil? ? true : config.notify
|
43
|
+
end
|
44
|
+
|
45
|
+
def color
|
46
|
+
config.color || 'yellow'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'knife-spork/plugins/plugin'
|
2
|
+
|
3
|
+
module KnifeSpork
|
4
|
+
module Plugins
|
5
|
+
class Irccat < Plugin
|
6
|
+
name :irccat
|
7
|
+
|
8
|
+
def perform; end
|
9
|
+
|
10
|
+
def after_upload
|
11
|
+
irccat("#BOLD#PURPLECHEF:#NORMAL #{current_user} uploaded #TEAL#{cookbooks.collect{ |c| "#{c.name}@#{c.version}" }.join(", ")}#NORMAL")
|
12
|
+
end
|
13
|
+
|
14
|
+
def after_promote_remote
|
15
|
+
environments.each do |environment|
|
16
|
+
diff = environment_diffs[environment.name]
|
17
|
+
env_gist = gist(environment, diff) if config.gist
|
18
|
+
irccat("#BOLD#PURPLECHEF:#NORMAL #{current_user} promoted #TEAL#{cookbooks.collect{ |c| "#{c.name}@#{c.version}" }.join(", ")}#NORMAL to #{environment.name} #{env_gist}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def irccat(message)
|
24
|
+
channels.each do |channel|
|
25
|
+
begin
|
26
|
+
# Write the message using a TCP Socket
|
27
|
+
socket = TCPSocket.open(config.server, config.port)
|
28
|
+
socket.write("#{channel} #{message}")
|
29
|
+
rescue Exception => e
|
30
|
+
ui.error 'Failed to post message with irccat.'
|
31
|
+
ui.error e.to_s
|
32
|
+
ensure
|
33
|
+
socket.close unless socket.nil?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def gist(environment, diff)
|
39
|
+
msg = "Environment #{environment} uploaded at #{Time.now.getutc} by #{current_user}\n\nConstraints updated on server in this version:\n\n#{diff.collect { |k, v| "#{k}: #{v}\n" }.join}"
|
40
|
+
%x[ echo "#{msg}" | #{config.gist}]
|
41
|
+
end
|
42
|
+
|
43
|
+
def channels
|
44
|
+
[ config.channel || config.channels ].flatten
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module KnifeSpork
|
2
|
+
module Plugins
|
3
|
+
class Plugin
|
4
|
+
# This is the name of the plugin. It must correspond to the name in the yaml configuration
|
5
|
+
# file in order to load this plugin. If an attribute is passed in, the name is set to that
|
6
|
+
# given value. Otherwise, the name is returned.
|
7
|
+
def self.name(name = nil)
|
8
|
+
if name.nil?
|
9
|
+
class_variable_get(:@@name)
|
10
|
+
else
|
11
|
+
class_variable_set(:@@name, name)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# This is a convenience method for defining multiple hooks in a single call.
|
16
|
+
def self.hooks(*the_hooks)
|
17
|
+
[the_hooks].flatten.each{ |the_hook| hook(the_hook) }
|
18
|
+
end
|
19
|
+
|
20
|
+
# When defining a hook, we define a method on the instance that corresponds to that
|
21
|
+
# hook. That will be fired when the hook is fired.
|
22
|
+
def self.hook(the_hook)
|
23
|
+
self.send(:define_method, the_hook.to_sym) do
|
24
|
+
perform
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(options = {})
|
29
|
+
@options = {
|
30
|
+
:payload => {}
|
31
|
+
}.merge(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def enabled?
|
35
|
+
!config.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def config
|
40
|
+
@options[:config].plugins.send(self.class.name.to_sym)
|
41
|
+
end
|
42
|
+
|
43
|
+
def cookbooks
|
44
|
+
@options[:cookbooks]
|
45
|
+
end
|
46
|
+
|
47
|
+
def environments
|
48
|
+
@options[:environments]
|
49
|
+
end
|
50
|
+
|
51
|
+
def environment_diffs
|
52
|
+
@options[:environment_diffs]
|
53
|
+
end
|
54
|
+
|
55
|
+
def ui
|
56
|
+
@options[:ui]
|
57
|
+
end
|
58
|
+
|
59
|
+
def current_user
|
60
|
+
(begin `git config user.name`.chomp; rescue nil; end || ENV['USERNAME'] || ENV['USER']).strip
|
61
|
+
end
|
62
|
+
|
63
|
+
# Wrapper method around require that attempts to include the associated file. If it does not exist
|
64
|
+
# or cannot be loaded, an nice error is produced instead of blowing up.
|
65
|
+
def safe_require(file)
|
66
|
+
begin
|
67
|
+
require file
|
68
|
+
rescue LoadError
|
69
|
+
raise "You are using a plugin for knife-spork that requires #{file}, but you have not installed it. Please either run \"gem install #{file}\", add #{file} to your Gemfile or remove the plugin from your configuration."
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'app_conf'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require 'chef/cookbook_loader'
|
5
|
+
require 'chef/knife/core/object_loader'
|
6
|
+
require 'knife-spork/plugins'
|
7
|
+
|
8
|
+
module KnifeSpork
|
9
|
+
module Runner
|
10
|
+
module ClassMethods; end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def spork_config
|
14
|
+
return @spork_config unless @spork_config.nil?
|
15
|
+
|
16
|
+
@spork_config = AppConf.new
|
17
|
+
load_paths = [ File.expand_path('config/spork-config.yml'), '/etc/spork-config.yml', File.expand_path('~/.chef/spork-config.yml') ]
|
18
|
+
load_paths.each do |load_path|
|
19
|
+
if File.exists?(load_path)
|
20
|
+
@spork_config.load(load_path)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@spork_config
|
25
|
+
end
|
26
|
+
|
27
|
+
def run_plugins(hook)
|
28
|
+
cookbooks = [ @cookbooks || @cookbook ].flatten.compact.collect{|cookbook| cookbook.is_a?(::Chef::CookbookVersion) ? cookbook : load_cookbook(cookbook)}.sort{|a,b| a.name.to_s <=> b.name.to_s}
|
29
|
+
environments = [ @environments || @environment ].flatten.compact.collect{|environment| environment.is_a?(::Chef::Environment) ? environment : load_environment(environment)}.sort{|a,b| a.name.to_s <=> b.name.to_s}
|
30
|
+
environment_diffs = @environment_diffs
|
31
|
+
|
32
|
+
KnifeSpork::Plugins.run(
|
33
|
+
:config => spork_config,
|
34
|
+
:hook => hook.to_sym,
|
35
|
+
:cookbooks => cookbooks,
|
36
|
+
:environments => environments,
|
37
|
+
:environment_diffs => environment_diffs,
|
38
|
+
:ui => ui
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def load_environments_and_cookbook
|
43
|
+
ensure_environment_provided!
|
44
|
+
|
45
|
+
if @name_args.size == 2
|
46
|
+
[ [@name_args[0]].flatten, @name_args[1] ]
|
47
|
+
elsif @name_args.size == 1
|
48
|
+
[ [default_environments].flatten, @name_args[0] ]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def ensure_environment_provided!
|
53
|
+
if default_environments.empty? && @name_args.size < 2
|
54
|
+
ui.error('You must specify a cookbook name and an environment')
|
55
|
+
exit(1)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def default_environments
|
60
|
+
[ spork_config.default_environment || spork_config.default_environments ].flatten.compact
|
61
|
+
end
|
62
|
+
|
63
|
+
def pretty_print_json(json)
|
64
|
+
JSON.pretty_generate(json)
|
65
|
+
end
|
66
|
+
|
67
|
+
def valid_version?(version)
|
68
|
+
version_keys = version.split('.')
|
69
|
+
return false unless version_keys.size == 3 && version_keys.any?{ |k| begin Float(k); rescue false; else true; end }
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
def validate_version!(version)
|
74
|
+
if version && !valid_version?(version)
|
75
|
+
ui.error("#{version} is not a valid version!")
|
76
|
+
exit(1)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def loader
|
81
|
+
@loader ||= Chef::Knife::Core::ObjectLoader.new(::Chef::Environment, ui)
|
82
|
+
end
|
83
|
+
|
84
|
+
# It's not feasible to try and "guess" which cookbook path to use, so we will
|
85
|
+
# always just use the first one in the path.
|
86
|
+
def cookbook_path
|
87
|
+
ensure_cookbook_path!
|
88
|
+
[config[:cookbook_path] ||= ::Chef::Config.cookbook_path].flatten[0]
|
89
|
+
end
|
90
|
+
|
91
|
+
def all_cookbooks
|
92
|
+
::Chef::CookbookLoader.new(::Chef::Config.cookbook_path)
|
93
|
+
end
|
94
|
+
|
95
|
+
def load_cookbook(cookbook_name)
|
96
|
+
return cookbook_name if cookbook_name.is_a?(::Chef::CookbookVersion)
|
97
|
+
loader = ::Chef::CookbookLoader.new(Chef::Config.cookbook_path)
|
98
|
+
loader[cookbook_name]
|
99
|
+
end
|
100
|
+
|
101
|
+
def load_cookbooks(cookbook_names)
|
102
|
+
cookbook_names = [cookbook_names].flatten
|
103
|
+
cookbook_names.collect{ |cookbook_name| load_cookbook(cookbook_name) }
|
104
|
+
end
|
105
|
+
|
106
|
+
def load_environment(environment_name)
|
107
|
+
loader.load_from('environments', "#{environment_name}.json")
|
108
|
+
end
|
109
|
+
|
110
|
+
def load_remote_environment(environment_name)
|
111
|
+
begin
|
112
|
+
Chef::Environment.load(environment_name)
|
113
|
+
rescue Net::HTTPServerException => e
|
114
|
+
ui.error "Could not load #{environment_name} from Chef Server. You must upload the environment manually the first time."
|
115
|
+
exit(1)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def environment_diff (local_environment, remote_environment)
|
120
|
+
local_environment_versions = local_environment.to_hash['cookbook_versions']
|
121
|
+
remote_environment_versions = remote_environment.to_hash['cookbook_versions']
|
122
|
+
remote_environment_versions.diff(local_environment_versions)
|
123
|
+
end
|
124
|
+
|
125
|
+
def constraints_diff (environment_diff)
|
126
|
+
Hash[Hash[environment_diff.map{|k,v| [k, v.split(" changed to ").map{|x|x.gsub("= ","")}]}].map{|k,v|[k,calc_diff(v)]}]
|
127
|
+
end
|
128
|
+
|
129
|
+
def calc_diff(version)
|
130
|
+
components = version.map{|v|v.split(".")}
|
131
|
+
if components[1][0].to_i != components[0][0].to_i
|
132
|
+
return (components[1][0].to_i - components[0][0].to_i)*100
|
133
|
+
elsif components[1][1].to_i != components[0][1].to_i
|
134
|
+
return (components[1][1].to_i - components[0][1].to_i)*10
|
135
|
+
else
|
136
|
+
return (components[1][2].to_i - components[0][2].to_i)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def ensure_cookbook_path!
|
141
|
+
if !config.has_key?(:cookbook_path)
|
142
|
+
ui.fatal "No default cookbook_path; Specify with -o or fix your knife.rb."
|
143
|
+
show_usage
|
144
|
+
exit(1)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.included(receiver)
|
150
|
+
receiver.extend(ClassMethods)
|
151
|
+
receiver.send(:include, InstanceMethods)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
class Hash
|
158
|
+
def diff(other)
|
159
|
+
self.keys.inject({}) do |memo, key|
|
160
|
+
unless self[key] == other[key]
|
161
|
+
memo[key] = "#{self[key]} changed to #{other[key]}"
|
162
|
+
end
|
163
|
+
memo
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|