blender-chef 0.0.1 → 0.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 02bfeb4a25b9c9ee8449472f569c2fa581ae6895
4
- data.tar.gz: 3c73b3cbbc3b9c4244be213328c9b33965906bd3
3
+ metadata.gz: 0678c7aeb1d0d4a93bc0a1cf94807248d3860b7a
4
+ data.tar.gz: 6a3369819b869119d59021f45d65af65ff380858
5
5
  SHA512:
6
- metadata.gz: 22813586dd6ec066baaabc5b667f209fe7db62be786a85a2a0019de206574df54da987f6f6d180a5f12231bd3659b5dd9b9d86f7ddf639ffdda65fcb5a8bfc25
7
- data.tar.gz: b5f89d15b5f7398232ab5a8d61a7b45cbf07fc6a2d803f51d3b9b63cd335d79479292aed9f0631cafd7d33177d1dc53c991b92f23ebd21bc99ad85ae99e3d870
6
+ metadata.gz: a931c089da34be8cc2811ee8f1a7b0c266307967f0ab8932c31476bebe1e1e9e4480247c3d7faefe17644d152495695fb870d3d983f379be3ce0a24ebc16ab92
7
+ data.tar.gz: ac3bb2c4e26e32671b44120bd60b0c98923149631c9673f06482d3a42dac2560a23ae46625dd84cbcfbcad466887a3bf12632519f0d94a26749b7609071f8a16
@@ -1,7 +1,6 @@
1
1
  before_install:
2
2
  - bundle install --path .bundle
3
3
  rvm:
4
- - 1.9.3
5
4
  - 2.1.0
6
5
  - 2.1.2
7
6
  branches:
@@ -0,0 +1,65 @@
1
+ # Blender-Chef
2
+
3
+ A [chef](https://www.chef.io/chef) based host discovery plugin for [Blender](https://github.com/PagerDuty/blender)
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ gem install blender-chef
9
+ ```
10
+
11
+ ## Usage
12
+ With blender-chef, host list for `blender` jobs can be automatically
13
+ fetched from Chef server. Following is an example of dynamically obtaining
14
+ all servers with `db` role, and enlisting their `iptables` rule.
15
+
16
+ ```ruby
17
+ require 'belnder/chef'
18
+ config(:chef, chef_sever_url: 'https://foo.bar.com', node_name: 'admin', client_key: 'admin.pem')
19
+ members(search(:chef, 'roles:db'))
20
+ ssh_task 'sudo iptables -L'
21
+ ```
22
+ Aany valid chef search can be used. You can pass the `node_name`, `chef_server_url` and `client_key` for chef server config.
23
+ ```ruby
24
+ config(:chef, node_name: 'admin', client_key: 'admin.pem', chef_server_url: 'https://example.com')
25
+ members(search(:chef, 'ec2_local_ipv4:10.13.12.11'))
26
+ ssh_task 'sudo iptables -L'
27
+ ```
28
+ Alternatively, you can also use a config file lile `client.rb` or `knife.rb`
29
+ ```ruby
30
+ config(:chef, config_file: '/etc/chef/client.rb')
31
+ members(search(:chef, 'roles:db'))
32
+ ```
33
+
34
+ By default `blender-chef` will pass the FQDN of chef nodes as member list,
35
+ in this case as ssh targets. This can be customized by passing the attribute
36
+ option.
37
+
38
+ ```ruby
39
+ config(:chef, node_name: 'admin', client_key: 'admin.pem')
40
+ members(search(:chef, 'roles:db', attribute: 'ec2_public_ipv4'))
41
+ ```
42
+
43
+
44
+ ## Supported ruby versions
45
+
46
+ Blender-chef uses Chef 12 (for partial search). For chef 11, use 0.0.1 version of blender-chef.
47
+
48
+ Blender-chef currently support the following MRI versions:
49
+
50
+ * *Ruby 1.9.3*
51
+ * *Ruby 2.1.0*
52
+ * *Ruby 2.1.2*
53
+
54
+ ## License
55
+
56
+ [Apache 2](http://www.apache.org/licenses/LICENSE-2.0)
57
+
58
+ ## Contributing
59
+
60
+ 1. Fork it ( https://github.com/PagerDuty/blender-chef/fork )
61
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
62
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
63
+ 4. Push to the branch (`git push origin my-new-feature`)
64
+ 5. Create a new Pull Request
65
+ ```
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'blender-chef'
7
- spec.version = '0.0.1'
7
+ spec.version = '0.1.0'
8
8
  spec.authors = ['Ranjib Dey']
9
9
  spec.email = ['ranjib@pagerduty.com']
10
10
  spec.summary = %q{Chef search based host discovery for blender}
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.require_paths = ['lib']
18
18
 
19
19
  spec.add_dependency 'pd-blender'
20
- spec.add_dependency 'chef'
20
+ spec.add_dependency 'chef', '>= 12.0.0'
21
21
 
22
22
  spec.add_development_dependency 'bundler'
23
23
  spec.add_development_dependency 'rake'
@@ -25,4 +25,5 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency 'rubocop'
26
26
  spec.add_development_dependency 'simplecov'
27
27
  spec.add_development_dependency 'yard'
28
+ spec.add_development_dependency 'fauxhai'
28
29
  end
@@ -26,43 +26,25 @@ module Blender
26
26
  @options = options
27
27
  end
28
28
 
29
- def search(search_term = '*:*')
30
- if options[:config_file]
31
- ::Chef::Config.from_file options[:config_file]
32
- end
33
- if options[:node_name]
34
- ::Chef::Config[:node_name] = options[:node_name]
35
- end
36
- if options[:client_key]
37
- ::Chef::Config[:client_key] = options[:client_key]
38
- end
39
- if options[:chef_server_url]
40
- ::Chef::Config[:chef_server_url] = options[:chef_server_url]
41
- end
29
+ def search(opts = {})
42
30
  attr = options[:attribute] || 'fqdn'
43
- q = ::Chef::Search::Query.new
44
- res = q.search(:node, search_term)
45
- res.first.collect{|n| node_attribute(n, attr)}
46
- end
47
-
48
- private
49
- def node_attribute(data, nested_value_spec)
50
- nested_value_spec.split(".").each do |attr|
51
- if data.nil?
52
- nil # don't get no method error on nil
53
- elsif data.respond_to?(attr.to_sym)
54
- data = data.send(attr.to_sym)
55
- elsif data.respond_to?(:[])
56
- data = data[attr]
57
- else
58
- data = begin
59
- data.send(attr.to_sym)
60
- rescue NoMethodError
61
- nil
62
- end
63
- end
31
+ case opts
32
+ when String
33
+ search_term = opts
34
+ when Hash
35
+ search_term = opts[:search_term]
36
+ attr = opts[:attribute] if opts.key?(:attribute)
37
+ else
38
+ raise ArgumentError, "Invalid argument type #{opts.class}"
64
39
  end
65
- ( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data
40
+ search_term ||= '*:*'
41
+ ::Chef::Config.from_file(options[:config_file]) if options[:config_file]
42
+ ::Chef::Config[:node_name] = options[:node_name] if options[:node_name]
43
+ ::Chef::Config[:client_key] = options[:client_key] if options[:client_key]
44
+ ::Chef::Config[:chef_server_url] = options[:chef_server_url] if options[:chef_server_url]
45
+ q = ::Chef::Search::Query.new
46
+ res = q.search(:node, search_term, filter_result: {attribute: attr.split('.')})
47
+ res.first.collect{|node_data| node_data['data']['attribute']}
66
48
  end
67
49
  end
68
50
  end
@@ -0,0 +1,165 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2015 PagerDuty, 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 Blend < Chef::Knife
24
+
25
+ banner 'knife blend FILE (options)'
26
+
27
+ deps do
28
+ require 'blender'
29
+ require 'blender/chef'
30
+ end
31
+
32
+ option :search,
33
+ short: '-s SEARCH_TERM',
34
+ long: '--search SEARCH_TERM',
35
+ description: 'Chef search query',
36
+ default: '*:*'
37
+
38
+ option :attribute,
39
+ short: '-a ATTRIBUTE',
40
+ long: '--attribute ATTRIBUTE',
41
+ description: 'Node attribute that will used as SSH hostname',
42
+ default: 'fqdn'
43
+
44
+ option :blender_config,
45
+ default: nil,
46
+ long: :'--blender-config CONFIG_FILE',
47
+ description: 'Provide blender configuration via json file'
48
+
49
+ option :noop,
50
+ default: false,
51
+ boolean: true,
52
+ short: '-n',
53
+ long: '--noop',
54
+ description: 'no-op aka dry-run mode, run blender without executing jobs'
55
+
56
+ option :quiet,
57
+ default: false,
58
+ boolean: true,
59
+ short: '-q',
60
+ description: 'Quiet mode. Disable printing running job details'
61
+
62
+ option :user,
63
+ default: ENV['USER'],
64
+ short: '-u USER',
65
+ long: '--user USER',
66
+ description: 'SSH User'
67
+
68
+ option :password,
69
+ short: '-p PASSWORD',
70
+ long: '--password PASSWORD',
71
+ description: 'SSH password'
72
+
73
+ option :quiet,
74
+ default: false,
75
+ boolean: true,
76
+ short: '-q',
77
+ description: 'Quiet mode. Disable printing running job details'
78
+
79
+ option :stream,
80
+ default: true,
81
+ boolean: true,
82
+ long: '--stream',
83
+ description: 'Stream STDOUT of commands(works only if quiet mode is not used)'
84
+
85
+ option :strategy,
86
+ default: :default,
87
+ long: '--strategy STRATEGY',
88
+ description: 'Strategy of execution (default, per_host or per_task)',
89
+ proc: lambda{|strategy| strategy.to_sym}
90
+
91
+ option :identity_file,
92
+ short: '-i IDENTITY_FILE',
93
+ long: '--identity-file IDENTITY_FILE',
94
+ description: 'Identity file for SSH authentication'
95
+
96
+ option :recipe_mode,
97
+ long: '--recipe-mode',
98
+ description: 'Treat input files as chef recipe and compose blender tasks to execute them (scp + ssh)',
99
+ boolean: true,
100
+ default: false
101
+
102
+ option :recipe_mode,
103
+ long: '--recipe-mode',
104
+ description: 'Treat input files as chef recipe and compose blender tasks to execute them (scp + ssh)',
105
+ boolean: true,
106
+ default: false
107
+
108
+ option :chef_apply,
109
+ long: '--chef-apply',
110
+ short: '-A',
111
+ description: 'chef-apply command to be used (effective only in recipe mode)',
112
+ default: 'chef-apply'
113
+
114
+ def run
115
+ ssh_options = {
116
+ user: config[:user],
117
+ stdout: $stdout
118
+ }
119
+ ssh_options[:stdout] = $stdout if config[:stream]
120
+ if config[:password]
121
+ ssh_options[:password] = config[:password]
122
+ elsif config[:prompt]
123
+ ssh_options[:password] = ui.ask('SSH password: ') {|q|q.echo = false}
124
+ end
125
+ if config[:identity_file]
126
+ ssh_options[:keys] = Array(config[:identity_file])
127
+ end
128
+ scheduler_options = {
129
+ config_file: config[:blender_config],
130
+ no_doc: config[:quiet]
131
+ }
132
+ discovery_options = {
133
+ attribute: config[:attribute]
134
+ }
135
+ Blender::Configuration[:noop] = config[:noop]
136
+ members = Blender::Discovery::Chef.new(discovery_options).search(config[:search])
137
+
138
+ @name_args.each do |file|
139
+ if config[:recipe_mode]
140
+ remote_path = File.join('/tmp', SecureRandom.hex(10))
141
+ Blender.blend(options[:file], scheduler_options) do |scheduler|
142
+ scheduler.strategy(config[:strategy])
143
+ scheduler.config(:ssh, ssh_options)
144
+ scheduler.config(:scp, ssh_options)
145
+ scheduler.members(members)
146
+ scheduler.scp_upload(remote_path) do
147
+ from file
148
+ end
149
+ scheduler.ssh_task "#{config[:chef_apply]} #{remote_path}"
150
+ scheduler.ssh_task "rm #{remote_path}"
151
+ end
152
+ else
153
+ job = File.read(file)
154
+ Blender.blend(options[:file], scheduler_options) do |scheduler|
155
+ scheduler.strategy(config[:strategy])
156
+ scheduler.config(:ssh, ssh_options)
157
+ scheduler.members(members)
158
+ scheduler.instance_eval(job, __FILE__, __LINE__)
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -4,28 +4,25 @@ require 'blender/discoveries/chef'
4
4
  describe Blender::Discovery::Chef do
5
5
  let(:discovery){described_class.new}
6
6
  it '#search' do
7
- query = double(Chef::Search::Query)
8
- node = Chef::Node.new
9
- node.set['fqdn'] = 'a'
10
- expect(query).to receive(:search).with(:node, '*:*').and_return([[node]])
11
- expect(Chef::Search::Query).to receive(:new).and_return(query)
12
- expect(discovery.search).to eq(['a'])
7
+ expect(discovery.search.size).to be(10)
13
8
  end
14
- it '#search with options' do
15
- disco = described_class.new(
16
- config_file: 'foo.rb',
17
- node_name: 'bar',
18
- client_key: 'baz.rb',
19
- attribute: 'x.y.z'
20
- )
21
- query = double(Chef::Search::Query)
22
- node = Chef::Node.new
23
- node.set['x'] = { 'y' => { 'z' => 123 } }
24
- expect(Chef::Config).to receive(:from_file).with('foo.rb')
25
- expect(query).to receive(:search).with(:node, 'name:x').and_return([[node]])
26
- expect(Chef::Search::Query).to receive(:new).and_return(query)
27
- expect(disco.search('name:x')).to eq([123])
28
- expect(Chef::Config[:client_key]).to eq('baz.rb')
29
- expect(Chef::Config[:node_name]).to eq('bar')
9
+ context '#against chef zero' do
10
+ let(:disco) do
11
+ described_class.new(
12
+ chef_server_url: 'http://localhost:8889',
13
+ node_name: 'admin',
14
+ client_key: SpecHelper.client_key.path,
15
+ attribute: 'name'
16
+ )
17
+ end
18
+ it '#search without options' do
19
+ expect(disco.search('name:node-1')).to eq(['node-1'])
20
+ end
21
+ it '#search with role based predicate' do
22
+ expect(disco.search('roles:even')).to eq(["node-1", "node-3", "node-5", "node-7", "node-9"])
23
+ end
24
+ it '#search with attribute predicate' do
25
+ expect(disco.search(search_term: 'roles:even', attribute: 'ipaddress').size).to eq(5)
26
+ end
30
27
  end
31
28
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ require 'chef/knife/blender'
4
+
5
+ describe Chef::Knife::Blend do
6
+ before(:each) do
7
+ Chef::Knife::Blend.load_deps
8
+ @knife = Chef::Knife::Blend.new
9
+ @knife.config[:user] = 'test-user'
10
+ @knife.config[:passsword] = 'test-password'
11
+ @knife.config[:search] = 'roles:db'
12
+ @knife.config[:strategy] = :default
13
+ @knife.name_args = ["job.rb"]
14
+ end
15
+ it '#non recipe mode' do
16
+ disco = double(Blender::Discovery::Chef)
17
+ expect(Blender::Discovery::Chef).to receive(:new).and_return(disco)
18
+ expect(disco).to receive(:search).and_return(['host1', 'host2'])
19
+ expect(File).to receive(:read).with('job.rb').and_return('')
20
+ @knife.run
21
+ end
22
+ it '#recipe mode' do
23
+ @knife.config[:recipe_mode] = true
24
+ disco = double(Blender::Discovery::Chef)
25
+ expect(Blender::Discovery::Chef).to receive(:new).and_return(disco)
26
+ expect(disco).to receive(:search).and_return([])
27
+ @knife.run
28
+ end
29
+ end
@@ -1,8 +1,50 @@
1
1
  require 'blender'
2
+ require 'chef_zero/server'
3
+ require 'fileutils'
4
+ require 'chef/node'
5
+ require 'tempfile'
6
+ require 'fauxhai'
7
+
8
+ module SpecHelper
9
+ extend self
10
+ def server
11
+ $server ||= ChefZero::Server.new
12
+ end
13
+ def client_key
14
+ $client_key ||= Tempfile.new('client.pem')
15
+ end
16
+ def setup
17
+ server.start_background
18
+ Chef::Config[:chef_server_url] = 'http://127.0.0.1:8889'
19
+ Chef::Config[:node_name] = 'admin'
20
+ Chef::Config[:client_key] = client_key.path
21
+ client_key.write(server.gen_key_pair.first)
22
+ client_key.close
23
+ 10.times do |n|
24
+ node = Chef::Node.new
25
+ node.name('node-'+ (n+1).to_s)
26
+ node.set['roles'] = n.odd? ? ['odd'] : ['even']
27
+ node.consume_attributes(
28
+ Fauxhai.mock(platform: 'ubuntu', version: '14.04').data
29
+ )
30
+ node.save
31
+ end
32
+ end
33
+ def cleanse
34
+ client_key.unlink
35
+ server.stop
36
+ end
37
+ end
2
38
 
3
39
  RSpec.configure do |config|
4
40
  config.mock_with :rspec do |mocks|
5
41
  mocks.verify_doubled_constant_names = true
6
42
  end
43
+ config.before(:suite) do
44
+ SpecHelper.setup
45
+ end
46
+ config.after(:suite) do
47
+ SpecHelper.cleanse
48
+ end
7
49
  config.backtrace_exclusion_patterns = []
8
50
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blender-chef
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ranjib Dey
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-10 00:00:00.000000000 Z
11
+ date: 2015-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pd-blender
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 12.0.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 12.0.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: fauxhai
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
125
139
  description: Discover hosts using chef search for blender
126
140
  email:
127
141
  - ranjib@pagerduty.com
@@ -132,11 +146,14 @@ files:
132
146
  - ".gitignore"
133
147
  - ".travis.yml"
134
148
  - Gemfile
149
+ - README.md
135
150
  - Rakefile
136
151
  - blender-chef.gemspec
137
152
  - lib/blender/chef.rb
138
153
  - lib/blender/discoveries/chef.rb
154
+ - lib/chef/knife/blender.rb
139
155
  - spec/blender/discoveries/chef_spec.rb
156
+ - spec/blender/knife_spec.rb
140
157
  - spec/spec_helper.rb
141
158
  homepage: http://github.com/PagerDuty/blender-chef
142
159
  licenses:
@@ -164,5 +181,6 @@ specification_version: 4
164
181
  summary: Chef search based host discovery for blender
165
182
  test_files:
166
183
  - spec/blender/discoveries/chef_spec.rb
184
+ - spec/blender/knife_spec.rb
167
185
  - spec/spec_helper.rb
168
186
  has_rdoc: