knife-flip 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -2
- data/knife-flip.gemspec +3 -2
- data/lib/chef/knife/nodeflip.rb +142 -27
- data/lib/knife-flip.rb +1 -1
- metadata +18 -2
data/README.md
CHANGED
@@ -16,10 +16,12 @@ gem install knife-flip
|
|
16
16
|
## What it does
|
17
17
|
|
18
18
|
````
|
19
|
-
knife node flip mynode.foo.com myenv
|
19
|
+
knife node flip mynode.foo.com myenv [--preview]
|
20
20
|
````
|
21
21
|
|
22
|
-
will move the node mynode.foo.com into the environment myenv
|
22
|
+
will move the node mynode.foo.com into the environment myenv. Passing in the --preview option
|
23
|
+
it will dry-run the flip and show you what cookbooks and versions would be applied if it were actually
|
24
|
+
flipped.
|
23
25
|
|
24
26
|
|
25
27
|
````
|
data/knife-flip.gemspec
CHANGED
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
|
|
13
13
|
## If your rubyforge_project name is different, then edit it and comment out
|
14
14
|
## the sub! line in the Rakefile
|
15
15
|
s.name = 'knife-flip'
|
16
|
-
s.version = '0.1.
|
17
|
-
s.date = '2013-
|
16
|
+
s.version = '0.1.7'
|
17
|
+
s.date = '2013-06-06'
|
18
18
|
s.rubyforge_project = 'knife-flip'
|
19
19
|
|
20
20
|
## Make sure your summary is short. The description may be as long
|
@@ -41,6 +41,7 @@ Gem::Specification.new do |s|
|
|
41
41
|
## List your runtime dependencies here. Runtime dependencies are those
|
42
42
|
## that are needed for an end user to actually USE your code.
|
43
43
|
s.add_dependency('chef', [">= 0.10.4"])
|
44
|
+
s.add_dependency('colorize', [">= 0.5.8"])
|
44
45
|
|
45
46
|
## Leave this section as-is. It will be automatically generated from the
|
46
47
|
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
data/lib/chef/knife/nodeflip.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
|
4
4
|
require 'chef/knife'
|
5
|
+
require 'colorize'
|
5
6
|
|
6
7
|
module KnifeFlip
|
7
8
|
class NodeFlip < Chef::Knife
|
@@ -12,7 +13,13 @@ module KnifeFlip
|
|
12
13
|
require 'chef/knife/core/object_loader'
|
13
14
|
end
|
14
15
|
|
15
|
-
banner "knife node flip NODE ENVIRONMENT"
|
16
|
+
banner "knife node flip NODE ENVIRONMENT (options)"
|
17
|
+
|
18
|
+
option :preview,
|
19
|
+
:long => '--preview',
|
20
|
+
:boolean => true,
|
21
|
+
:on => :tail,
|
22
|
+
:description => 'Preview the target environment to see affected cookbooks'
|
16
23
|
|
17
24
|
def run
|
18
25
|
unless @node_name = name_args[0]
|
@@ -25,42 +32,150 @@ module KnifeFlip
|
|
25
32
|
exit 1
|
26
33
|
end
|
27
34
|
|
28
|
-
|
35
|
+
if config[:preview] then
|
36
|
+
show_environmental_differences
|
37
|
+
else
|
38
|
+
puts "Looking for an fqdn of #{@node_name} or name of #{@node_name}"
|
39
|
+
|
40
|
+
searcher = Chef::Search::Query.new
|
41
|
+
result = searcher.search(:node, "fqdn:#{@node_name} OR name:#{@node_name}")
|
42
|
+
|
43
|
+
knife_search = Chef::Knife::Search.new
|
44
|
+
node = result.first.first
|
45
|
+
if node.nil?
|
46
|
+
puts "Could not find a node with the fqdn of #{@node_name} or name of #{@node_name}"
|
47
|
+
exit 1
|
48
|
+
end
|
49
|
+
|
50
|
+
begin
|
51
|
+
e = Chef::Environment.load(@environment)
|
52
|
+
rescue Net::HTTPServerException => e
|
53
|
+
if e.response.code.to_s == "404"
|
54
|
+
ui.error "The environment #{@environment} does not exist on the server, aborting."
|
55
|
+
Chef::Log.debug(e)
|
56
|
+
exit 1
|
57
|
+
else
|
58
|
+
raise
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
puts "Setting environment to #{@environment}"
|
63
|
+
node.chef_environment(@environment)
|
64
|
+
node.save
|
65
|
+
|
66
|
+
knife_search = Chef::Knife::Search.new
|
67
|
+
# ensure that 'start' and 'rows' are set since we don't seem to properly inherit the +config+ hash and therefore don't get sane defaults
|
68
|
+
config[:start] = 0
|
69
|
+
config[:rows] = 1000
|
70
|
+
knife_search.config = config # without this, the +config+ hash is empty
|
71
|
+
knife_search.name_args = ['node', "fqdn:#{@node_name} OR name:#{@node_name}"]
|
72
|
+
knife_search.run
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Compare the two environments
|
79
|
+
def show_environmental_differences
|
80
|
+
# Re-iterate to the user that this won't flip you
|
81
|
+
remind_user_about_preview
|
82
|
+
|
83
|
+
# Load up the node information
|
84
|
+
load_node_object
|
85
|
+
|
86
|
+
# Get the source environment
|
87
|
+
get_source_environment
|
88
|
+
|
89
|
+
# Pull down the cookbooks for this node
|
90
|
+
cookbooks = get_cookbooks_for_node
|
91
|
+
|
92
|
+
# Get cookbooks for the environments we are checking
|
93
|
+
source_cookbooks = cookbooks_for_environment(@source_environment)
|
94
|
+
target_cookbooks = cookbooks_for_environment(@environment)
|
95
|
+
|
96
|
+
# Transform the uploaded cookbooks into a name => latest version hash
|
97
|
+
source_hash = get_cookbook_version_hash(source_cookbooks)
|
29
98
|
|
99
|
+
# Transform the production cookbooks into a name => latest version hash
|
100
|
+
target_hash = get_cookbook_version_hash(target_cookbooks)
|
101
|
+
|
102
|
+
# Intersect the production cookbook collection and ours
|
103
|
+
common_cookbooks = target_cookbooks.keys & cookbooks
|
104
|
+
changed_cookbooks = common_cookbooks.keep_if { |cookbook_key|
|
105
|
+
target_hash[cookbook_key] != source_hash[cookbook_key]
|
106
|
+
}
|
107
|
+
|
108
|
+
# Lets show what is different
|
109
|
+
show_cookbook_differences(changed_cookbooks, source_hash, target_hash)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Load up the node in question into a instance variable
|
113
|
+
def load_node_object
|
30
114
|
searcher = Chef::Search::Query.new
|
31
115
|
result = searcher.search(:node, "fqdn:#{@node_name} OR name:#{@node_name}")
|
32
116
|
|
33
|
-
|
34
|
-
node
|
35
|
-
if node.nil?
|
117
|
+
@node = result.first.first
|
118
|
+
if @node.nil?
|
36
119
|
puts "Could not find a node with the fqdn of #{@node_name} or name of #{@node_name}"
|
37
120
|
exit 1
|
38
|
-
end
|
121
|
+
end
|
122
|
+
end
|
39
123
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
124
|
+
# Extract the source environment from the node object
|
125
|
+
def get_source_environment
|
126
|
+
@source_environment = @node.chef_environment
|
127
|
+
end
|
128
|
+
|
129
|
+
# For the node being passed in, find and return an array of cookboock names
|
130
|
+
def get_cookbooks_for_node
|
131
|
+
cookbooks = @node.recipes.map {|recipe| recipe.match('^[^:]+')[0] }.uniq
|
132
|
+
|
133
|
+
return cookbooks
|
134
|
+
end
|
135
|
+
|
136
|
+
# For an environment, get all the cookbooks associated with it (in API object array form)
|
137
|
+
def cookbooks_for_environment(environment=nil, num_versions=1)
|
138
|
+
api_endpoint = environment ? "/environments/#{environment}/cookbooks?#{num_versions}" : "/cookbooks?#{num_versions}"
|
139
|
+
cookbooks = rest.get_rest(api_endpoint)
|
140
|
+
|
141
|
+
return cookbooks
|
142
|
+
end
|
143
|
+
|
144
|
+
# Given a cookbook array returned from the API, create a Hash of its name and the latest version
|
145
|
+
def get_cookbook_version_hash(cookbooks)
|
146
|
+
Hash[cookbooks.collect { |k, v| [k, v['versions'].first['version']] }]
|
147
|
+
end
|
148
|
+
|
149
|
+
# Takes in an array of modified cookbook names, the hash of uploaded cookbooks, and the hash of cookbooks
|
150
|
+
# from the environment being checked
|
151
|
+
def show_cookbook_differences(changed_cookbook_names, source_hash, target_hash)
|
152
|
+
changed_cookbook_count = changed_cookbook_names.size
|
153
|
+
(1..110).each { print "=".colorize(:cyan) }
|
154
|
+
puts ""
|
155
|
+
if changed_cookbook_count != 0 then
|
156
|
+
puts "#{changed_cookbook_count} difference(s) between environments"
|
157
|
+
changed_cookbook_names.each do |cookbook_name|
|
158
|
+
puts "#{cookbook_name}".colorize(:yellow) + ": " + "#{@source_environment}".colorize(:magenta) +
|
159
|
+
" version: " + "#{source_hash[cookbook_name]} ".colorize(:red) +
|
160
|
+
"will be changed to " + "#{target_hash[cookbook_name]}".colorize(:green) + " in " +
|
161
|
+
"#{@environment}".colorize(:magenta)
|
49
162
|
end
|
163
|
+
elsif (@source_environment == @environment) and (changed_cookbook_names.size == 0) then
|
164
|
+
puts "The environment the node is on and the one you are flipping to are identical, and thus "
|
165
|
+
puts "there are " + "NO".colorize(:red) +" cookbook differences"
|
166
|
+
else
|
167
|
+
puts "No differences".colorize(:red) +
|
168
|
+
" between the cookbook versions in the " + "#{@source_environment}".colorize(:magenta) + " and the " +
|
169
|
+
"#{@environment}".colorize(:magenta) + " environment for this node"
|
50
170
|
end
|
51
|
-
|
52
|
-
puts "
|
53
|
-
|
54
|
-
node.save
|
55
|
-
|
56
|
-
knife_search = Chef::Knife::Search.new
|
57
|
-
# ensure that 'start' and 'rows' are set since we don't seem to properly inherit the +config+ hash and therefore don't get sane defaults
|
58
|
-
config[:start] = 0
|
59
|
-
config[:rows] = 1000
|
60
|
-
knife_search.config = config # without this, the +config+ hash is empty
|
61
|
-
knife_search.name_args = ['node', "fqdn:#{@node_name} OR name:#{@node_name}"]
|
62
|
-
knife_search.run
|
171
|
+
(1..110).each { print "=".colorize(:cyan) }
|
172
|
+
puts ""
|
173
|
+
end
|
63
174
|
|
175
|
+
# Print out warning for users so they know that preview is a dry run
|
176
|
+
def remind_user_about_preview
|
177
|
+
puts "NOTE NOTE NOTE".colorize(:red) + " Running with --preview " + "WILL NOT".colorize(:red) +
|
178
|
+
" flip your node\n"
|
64
179
|
end
|
65
180
|
end
|
66
181
|
end
|
data/lib/knife-flip.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-flip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-06-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: chef
|
@@ -27,6 +27,22 @@ dependencies:
|
|
27
27
|
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: 0.10.4
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: colorize
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.5.8
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.5.8
|
30
46
|
description: A knife plugin to move a node, or all nodes in a role, to a specific
|
31
47
|
environment
|
32
48
|
email: jonlives@gmail.com
|