blender-chef 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: