knife-update 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .DS_Store
2
+ pkg/*
3
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rake'
7
+ gem 'rspec'
8
+ end
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # knife update
2
+
3
+ A small collection of knife plugins to ease the process of safely updating
4
+ chef in a setup with hundreds of nodes.
5
+
6
+ ## Installation
7
+
8
+ gem install knife-update
9
+
10
+ ## Usage
11
+
12
+ ### knife update status
13
+
14
+ Returns the current state of installed chef-client versions.
15
+
16
+ ```bash
17
+ $ knife update status
18
+ Search nodes '*:*'
19
+
20
+ Currently installed chef-client versions:
21
+ Chef 0.9.12: 5 nodes
22
+ Chef 0.9.16: 10 nodes
23
+ Chef 0.9.18: 400 nodes
24
+ Chef 10.14.4: 500 nodes
25
+ Chef 10.24.0: 1000 nodes
26
+ ```
27
+
28
+ ### knife update run_list
29
+
30
+ Displays lists of run_list items both used and not yet used in the target
31
+ chef-client version.
32
+
33
+ ```bash
34
+ $ knife update run_list
35
+ Search nodes '*:*'
36
+
37
+ Compatbile Chef 10.24.0 run_list items:
38
+ role[base]
39
+ role[web-server]
40
+
41
+ Incompatible / not tested run_list items:
42
+ role[loadbalancer]
43
+ role[database-server]
44
+ ```
45
+
46
+ ### knife update run
47
+
48
+ Updates all nodes which contain only run_list items already applied on other
49
+ nodes running the target chef-client version (therefore, assuming these are
50
+ compatible).
51
+
52
+ ```bash
53
+ $ knife update run
54
+ Search nodes '*:*'
55
+
56
+ Update 2 nodes to chef 10.24.0
57
+ node01.example.com Successfully installed chef-10.24.0
58
+ node01.example.com 1 gem installed
59
+ node33.example.com Successfully installed chef-10.24.0
60
+ node33.example.com 1 gem installed
61
+ ```
62
+
63
+ ### Options
64
+
65
+ Lookup possible options for each command with the `-h` option. In addition to
66
+ common knife ssh options, the two update specific options are:
67
+
68
+ * `-q, --search-query QUERY`
69
+
70
+ Filter nodes by a different search query, defaults to `*:*`.
71
+
72
+ * `-t, --target-version VERSION`
73
+
74
+ The desired chef-client version, defaults to `Chef::VERSION` of knife.
75
+
76
+ ## Authors
77
+
78
+ SoundCloud Inc., [Tobias Schmidt](mailto:ts@soundcloud.com)
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require 'bundler'
2
+ require 'rdoc/task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ begin
7
+ require 'rspec/core/rake_task'
8
+
9
+ desc 'Default: run specs'
10
+ task :default => :spec
11
+
12
+ desc "Run all specs in spec directory"
13
+ RSpec::Core::RakeTask.new(:spec) do |t|
14
+ t.pattern = 'spec/unit/**/*_spec.rb'
15
+ end
16
+
17
+ rescue LoadError
18
+ STDERR.puts "\n*** RSpec not available. (sudo) gem install rspec to run unit tests. ***\n\n"
19
+ end
20
+
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'knife-update/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'knife-update'
7
+ s.version = Knife::Update::VERSION
8
+ s.authors = ['Tobias Schmidt']
9
+ s.email = ['ts@soundcloud.com']
10
+ s.homepage = 'https://github.com/soundcloud/knife-update'
11
+ s.summary = 'knife update plugin'
12
+ s.description = 'A small collection of knife plugins to ease the process of safely updating chef in a setup with hundreds of nodes.'
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ['lib']
18
+
19
+ s.add_dependency 'chef', '>= 0.10.24'
20
+ s.add_development_dependency 'rspec'
21
+ end
@@ -0,0 +1,86 @@
1
+ #
2
+ # Author:: Tobias Schmidt (<ts@soundcloud.com>)
3
+ # Copyright:: Copyright (c) 2013 SoundCloud, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife'
20
+
21
+ class Chef
22
+ class Knife
23
+ module Update
24
+ def self.included(includer)
25
+ includer.class_eval do
26
+
27
+ deps do
28
+ require 'net/ssh'
29
+ require 'net/ssh/multi'
30
+ require 'chef/knife/ssh'
31
+ require 'chef/search/query'
32
+
33
+ Chef::Knife::Ssh.load_deps
34
+ end
35
+
36
+ option :query,
37
+ :short => "-q QUERY",
38
+ :long => "--search-query QUERY",
39
+ :description => "Filter nodes by search query",
40
+ :default => '*:*'
41
+
42
+ option :target_version,
43
+ :short => "-t VERSION",
44
+ :long => "--target-version VERSION",
45
+ :description => "The target chef-client version",
46
+ :default => Chef::VERSION
47
+ end
48
+ end
49
+
50
+ # Get a list of nodes and their used chef version
51
+ def node_list
52
+ list = {}
53
+ search = Chef::Search::Query.new
54
+ query = config[:query]
55
+
56
+ ui.msg "Search nodes '#{query}'"
57
+ search.search('node', query) do |node|
58
+ if node['chef'] && node['chef']['client_version']
59
+ version = node['chef']['client_version']
60
+
61
+ list[version] ||= []
62
+ list[version] << node
63
+ end
64
+ end
65
+
66
+ list
67
+ end
68
+
69
+ # List of all compatible run_list items
70
+ def run_list_items(nodes)
71
+ nodes.inject([]) do |memo, node|
72
+ node.run_list.each do |item|
73
+ memo << item.to_s unless memo.include?(item.to_s)
74
+ end
75
+
76
+ memo
77
+ end
78
+ end
79
+
80
+ # Returns target update chef version
81
+ def target_version
82
+ config[:target_version] || Chef::VERSION
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,117 @@
1
+ #
2
+ # Author:: Tobias Schmidt (<ts@soundcloud.com>)
3
+ # Copyright:: Copyright (c) 2013 SoundCloud, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife'
20
+
21
+ class Chef
22
+ class Knife
23
+ class UpdateRun < Knife
24
+ banner 'knife update run (options)'
25
+
26
+ option :ssh_user,
27
+ :short => "-x USERNAME",
28
+ :long => "--ssh-user USERNAME",
29
+ :description => "The ssh username",
30
+ :default => "root"
31
+
32
+ option :ssh_password,
33
+ :short => "-P PASSWORD",
34
+ :long => "--ssh-password PASSWORD",
35
+ :description => "The ssh password"
36
+
37
+ option :ssh_port,
38
+ :short => "-p PORT",
39
+ :long => "--ssh-port PORT",
40
+ :description => "The ssh port",
41
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
42
+
43
+ option :ssh_gateway,
44
+ :short => "-G GATEWAY",
45
+ :long => "--ssh-gateway GATEWAY",
46
+ :description => "The ssh gateway",
47
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
48
+
49
+ option :identity_file,
50
+ :short => "-i IDENTITY_FILE",
51
+ :long => "--identity-file IDENTITY_FILE",
52
+ :description => "The SSH identity file used for authentication"
53
+
54
+ option :host_key_verify,
55
+ :long => "--[no-]host-key-verify",
56
+ :description => "Verify host key, enabled by default.",
57
+ :boolean => true,
58
+ :default => true
59
+
60
+ option :use_sudo,
61
+ :long => "--sudo",
62
+ :description => "Execute the update via sudo",
63
+ :boolean => true
64
+
65
+ include Chef::Knife::Update
66
+
67
+ def run
68
+ list = node_list
69
+ items = run_list_items(list[target_version])
70
+
71
+ nodes = list.inject([]) do |memo, (version, nodes)|
72
+ if version != target_version
73
+ nodes.each do |node|
74
+ if node.run_list.all? { |item| items.include?(item.to_s) }
75
+ memo << node
76
+ end
77
+ end
78
+ end
79
+
80
+ memo
81
+ end
82
+
83
+ ui.msg "\nUpdate #{nodes.size} nodes to chef #{target_version}"
84
+ update_ssh(nodes, target_version).run
85
+ end
86
+
87
+ # Update given nodes to specified version
88
+ def update_ssh(nodes, version)
89
+ fqdns = nodes.map { |node| node.fqdn }.join(' ')
90
+
91
+ ssh = Chef::Knife::Ssh.new
92
+ ssh.ui = ui
93
+ ssh.name_args = [ fqdns, ssh_command(version) ]
94
+ ssh.config[:manual] = true
95
+ ssh.config[:ssh_user] = Chef::Config[:knife][:ssh_user] || config[:ssh_user]
96
+ ssh.config[:ssh_password] = config[:ssh_password]
97
+ ssh.config[:ssh_port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
98
+ ssh.config[:ssh_gateway] = Chef::Config[:knife][:ssh_gateway] || config[:ssh_gateway]
99
+ ssh.config[:identity_file] = Chef::Config[:knife][:identity_file] || config[:identity_file]
100
+ ssh.config[:host_key_verify] = Chef::Config[:knife][:host_key_verify] || config[:host_key_verify]
101
+ ssh.config[:on_error] = :raise
102
+ ssh
103
+ end
104
+
105
+ def ssh_command(version)
106
+ command = "gem install chef -v #{version}"
107
+
108
+ if config[:use_sudo]
109
+ command = "sudo #{command}"
110
+ end
111
+
112
+ command
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,53 @@
1
+ #
2
+ # Author:: Tobias Schmidt (<ts@soundcloud.com>)
3
+ # Copyright:: Copyright (c) 2013 SoundCloud, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife'
20
+
21
+ class Chef
22
+ class Knife
23
+ class UpdateRunList < Knife
24
+ banner 'knife update run_list (options)'
25
+
26
+ include Chef::Knife::Update
27
+
28
+ def run
29
+ list = node_list
30
+ old_nodes = list.inject([]) do |memo, (version, nodes)|
31
+ version == target_version ? memo : memo += nodes
32
+ end
33
+ compatible = run_list_items(list[target_version])
34
+ incompatible = run_list_items(old_nodes)
35
+
36
+ if compatible.any?
37
+ ui.msg "\nCompatbile Chef #{target_version} run_list items:"
38
+ compatible.sort.each do |item|
39
+ ui.msg " #{item}"
40
+ end
41
+ end
42
+
43
+ if incompatible.any?
44
+ ui.msg "\Incompatible / not tested run_list items:"
45
+ incompatible.sort.each do |item|
46
+ ui.msg " #{item}"
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Author:: Tobias Schmidt (<ts@soundcloud.com>)
3
+ # Copyright:: Copyright (c) 2013 SoundCloud, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife'
20
+
21
+ class Chef
22
+ class Knife
23
+ class UpdateStatus < Knife
24
+ banner 'knife update status (options)'
25
+
26
+ include Chef::Knife::Update
27
+
28
+ def run
29
+ list = node_list
30
+
31
+ ui.msg "\nCurrently installed chef-client versions:"
32
+ list.sort.each do |version, nodes|
33
+ ui.msg " Chef #{version}: #{nodes.size} nodes"
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,24 @@
1
+ #
2
+ # Author:: Tobias Schmidt (<ts@soundcloud.com>)
3
+ # Copyright:: Copyright (c) 2013 SoundCloud, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module Knife
20
+ module Update
21
+ VERSION = '0.0.1'
22
+ MAJOR, MINOR, TINY = VERSION.split('.')
23
+ end
24
+ end
@@ -0,0 +1,4 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'chef'
4
+ require 'chef/knife/update'
@@ -0,0 +1,23 @@
1
+ #
2
+ # Author:: Tobias Schmidt (<ts@soundcloud.com>)
3
+ # Copyright:: Copyright (c) 2013 SoundCloud, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require File.expand_path('../../spec_helper', __FILE__)
20
+ require 'chef/knife/update'
21
+
22
+ describe Chef::Knife::Update do
23
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knife-update
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tobias Schmidt
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: chef
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.10.24
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.10.24
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: A small collection of knife plugins to ease the process of safely updating
47
+ chef in a setup with hundreds of nodes.
48
+ email:
49
+ - ts@soundcloud.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - .rspec
56
+ - Gemfile
57
+ - README.md
58
+ - Rakefile
59
+ - knife-update.gemspec
60
+ - lib/chef/knife/update.rb
61
+ - lib/chef/knife/update_run.rb
62
+ - lib/chef/knife/update_run_list.rb
63
+ - lib/chef/knife/update_status.rb
64
+ - lib/knife-update/version.rb
65
+ - spec/spec_helper.rb
66
+ - spec/unit/update_spec.rb
67
+ homepage: https://github.com/soundcloud/knife-update
68
+ licenses: []
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ segments:
80
+ - 0
81
+ hash: -334156609659399358
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ segments:
89
+ - 0
90
+ hash: -334156609659399358
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 1.8.25
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: knife update plugin
97
+ test_files:
98
+ - spec/spec_helper.rb
99
+ - spec/unit/update_spec.rb