knife-flip 0.1.6 → 0.1.7

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.
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
  ````
@@ -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.6'
17
- s.date = '2013-03-22'
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
@@ -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
- puts "Looking for an fqdn of #{@node_name} or name of #{@node_name}"
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
- knife_search = Chef::Knife::Search.new
34
- node = result.first.first
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
- begin
41
- e = Chef::Environment.load(@environment)
42
- rescue Net::HTTPServerException => e
43
- if e.response.code.to_s == "404"
44
- ui.error "The environment #{@environment} does not exist on the server, aborting."
45
- Chef::Log.debug(e)
46
- exit 1
47
- else
48
- raise
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 "Setting environment to #{@environment}"
53
- node.chef_environment(@environment)
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
@@ -1,3 +1,3 @@
1
1
  module KnifeFlip
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.6"
3
3
  end
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.6
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-03-22 00:00:00.000000000 Z
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