chef-rundeck 2.0.0 → 2.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: 9ce7ece53bc0c72a4fe221d30e8babef9077dacf
4
- data.tar.gz: 98f8966b2cda5a2eeb852333e7c409af35c6bfbb
3
+ metadata.gz: acd9c147df627c29f79ec208b7ad4796a1933c81
4
+ data.tar.gz: 312f65e10a495d38b4e752fefef49099073b872a
5
5
  SHA512:
6
- metadata.gz: adb94d82eab00bc70ee9c8ae0a9a21c269f4cd27a27cc4620c66e85777760ca39862e5d89b20b59465beca6255ea6a058d727d4845939ab6c18e0ebf68f07943
7
- data.tar.gz: f522ced6479217a1ff953757f80dae46241bc9cfad96b3b923babd4dddb05c14c558ae66a917ab715d9a3f0eb2717a445bc876d5c6a8db2d4c4ff7fb08e5a6fd
6
+ metadata.gz: 6427c173c17c256172eb7b81c67047aa724d91e34004c2ddb3873aade19844977dbeebe38952964ac36cd14047b77cbfc2b4a863bc2aeeb9ebf9f704ba8faeb9
7
+ data.tar.gz: 207ed6aaaf9fad065ec4c06a44e7b9791d766569fcf59c8a8991d4dce4ffd8a3019f6fce8d1021d61ac780d571005ffc4ee9ac63429c39348fa8b5c26a96b654
@@ -75,6 +75,23 @@ class ChefRundeckCLI
75
75
  :long => "--pidfile FILE",
76
76
  :description => "Prefix for our PID file, default: /var/run/chef-rundeck- ('PORT.pid' will be added)",
77
77
  :default => "/var/run/chef-rundeck"
78
+
79
+ option :env,
80
+ :short => "-e ENV",
81
+ :long => "--env ENV",
82
+ :description => "Sets the environment for chef-rundeck to execute under. Use 'development' for more logging.",
83
+ :default => "production"
84
+
85
+ option :cache_timeout,
86
+ :short => "-t CACHE_TIMEOUT",
87
+ :long => "--timeout CACHE_TIMEOUT",
88
+ :description => "Sets the response timeout in seconds for the query data.",
89
+ :default => "30"
90
+
91
+ option :partial_search,
92
+ :long => "--partial-search TRUE/FALSE",
93
+ :description => "Enables partial searching, Chef 11 or greater.",
94
+ :default => "false"
78
95
  end
79
96
 
80
97
  cli = ChefRundeckCLI.new
@@ -86,6 +103,9 @@ ChefRundeck.web_ui_url = cli.config[:web_ui_url]
86
103
  ChefRundeck.api_url = cli.config[:api_url]
87
104
  ChefRundeck.client_key = cli.config[:client_key]
88
105
  ChefRundeck.project_config = cli.config[:project_config]
106
+ ChefRundeck.environment = cli.config[:env].to_sym
107
+ ChefRundeck.cache_timeout = cli.config[:cache_timeout].to_i
108
+ ChefRundeck.partial_search = cli.config[:partial_search] == "true"
89
109
  ChefRundeck.configure
90
110
 
91
111
  begin
@@ -19,6 +19,11 @@ require 'chef'
19
19
  require 'chef/node'
20
20
  require 'chef/mixin/xml_escape'
21
21
  require 'chef/rest'
22
+ require 'chef/role'
23
+ require 'chef/environment'
24
+ require 'chef/data_bag'
25
+ require 'chef/data_bag_item'
26
+ require 'partial_search'
22
27
 
23
28
  REQUIRED_ATTRS = [ :kernel, :fqdn, :platform, :platform_version ]
24
29
 
@@ -40,6 +45,8 @@ class ChefRundeck < Sinatra::Base
40
45
  attr_accessor :api_url
41
46
  attr_accessor :client_key
42
47
  attr_accessor :project_config
48
+ attr_accessor :cache_timeout
49
+ attr_accessor :partial_search
43
50
 
44
51
  def configure
45
52
  Chef::Config.from_file(ChefRundeck.config_file)
@@ -53,6 +60,7 @@ class ChefRundeck < Sinatra::Base
53
60
  ChefRundeck.client_key = Chef::Config[:client_key]
54
61
  end
55
62
 
63
+
56
64
  if (File.exists?(ChefRundeck.project_config)) then
57
65
  Chef::Log.info("Using JSON project file #{ChefRundeck.project_config}")
58
66
  projects = File.open(ChefRundeck.project_config, "r") { |f| JSON.parse(f.read) }
@@ -60,45 +68,99 @@ class ChefRundeck < Sinatra::Base
60
68
  get "/#{project}" do
61
69
  content_type 'text/xml'
62
70
  Chef::Log.info("Loading nodes for /#{project}")
63
- response = build_project projects[project]['pattern'], projects[project]['username'], (projects[project]['hostname'].nil? ? "fqdn" : projects[project]['hostname']), projects[project]['attributes']
64
- response
71
+ send_file build_project project, projects[project]['pattern'], projects[project]['username'], (projects[project]['hostname'].nil? ? "fqdn" : projects[project]['hostname']), projects[project]['attributes']
65
72
  end
73
+ cache_file = "#{Dir.tmpdir}/chef-rundeck-#{project}.xml"
74
+ at_exit { File.delete(cache_file) if File.exist?(cache_file) }
66
75
  end
67
76
  end
68
77
 
69
78
  get '/' do
70
79
  content_type 'text/xml'
71
80
  Chef::Log.info("Loading all nodes for /")
72
- response = build_project
73
- response
81
+ send_file build_project
74
82
  end
83
+
84
+ cache_file = "#{Dir.tmpdir}/chef-rundeck-default.xml"
85
+ at_exit { File.delete(cache_file) if File.exist?(cache_file) }
75
86
  end
76
87
  end
77
88
 
78
- def build_project (pattern="*:*", username=ChefRundeck.username, hostname="fqdn", custom_attributes=nil)
79
- response = '<?xml version="1.0" encoding="UTF-8"?>'
80
- response << '<!DOCTYPE project PUBLIC "-//DTO Labs Inc.//DTD Resources Document 1.0//EN" "project.dtd">'
81
- response << '<project>'
89
+ def build_project (project="default", pattern="*:*", username=ChefRundeck.username, hostname="fqdn", custom_attributes=nil)
90
+ response = nil
91
+ begin
82
92
 
83
- q = Chef::Search::Query.new
84
- q.search("node",pattern) do |node|
85
-
86
- begin
87
- if node_is_valid? node
88
- response << build_node(node, username, hostname, custom_attributes)
89
- else
90
- Chef::Log.warn("invalid node element: #{node.inspect}")
93
+ # file is too new use it again
94
+ if (File.exists?("#{Dir.tmpdir}/chef-rundeck-#{project}.xml") && (Time.now - File.atime("#{Dir.tmpdir}/chef-rundeck-#{project}.xml") < ChefRundeck.cache_timeout)) then
95
+ return "#{Dir.tmpdir}/chef-rundeck-#{project}.xml"
91
96
  end
92
- rescue Exception => e
93
- Chef::Log.error("=== could not generate xml for Node: #{node.name} - #{e.message}")
94
- Chef::Log.debug(e.backtrace.join('\n'))
97
+
98
+ results = []
99
+ if ChefRundeck.partial_search then
100
+ keys = { 'name' => ['name'],
101
+ 'kernel_machine' => [ 'kernel', 'machine' ],
102
+ 'kernel_os' => [ 'kernel', 'os' ],
103
+ 'fqdn' => [ 'fqdn' ],
104
+ 'run_list' => [ 'run_list' ],
105
+ 'roles' => [ 'roles' ],
106
+ 'recipes' => [ 'recipes' ],
107
+ 'chef_environment' => [ 'chef_environment' ],
108
+ 'platform' => [ 'platform'],
109
+ 'platform_version' => [ 'platform_version' ],
110
+ 'hostname' => [hostname]
111
+ }
112
+ if !custom_attributes.nil? then
113
+ custom_attributes.each do |attr|
114
+ attr_name = attr.gsub('.', '_')
115
+ attr_value = attr.split('.')
116
+ keys[attr_name] = attr_value
117
+ end
118
+ end
119
+ # do search
120
+ Chef::Log.info("partial search started (project: '#{project}')")
121
+ results = partial_search(:node,pattern, :keys => keys)
122
+ Chef::Log.info("partial search finshed (project: '#{project}', count: #{results.length})")
123
+ else
124
+ q = Chef::Search::Query.new
125
+ Chef::Log.info("search started (project: '#{project}')")
126
+ results = q.search("node",pattern)[0]
127
+ Chef::Log.info("search finshed (project: '#{project}', count: #{results.length})")
128
+ results = convert_results(results, hostname, custom_attributes)
95
129
  end
130
+
131
+ response = File.open("#{Dir.tmpdir}/chef-rundeck-#{project}.xml", 'w')
132
+ response.write '<?xml version="1.0" encoding="UTF-8"?>'
133
+ response.write '<!DOCTYPE project PUBLIC "-//DTO Labs Inc.//DTD Resources Document 1.0//EN" "project.dtd">'
134
+ response.write '<project>'
135
+
136
+ Chef::Log.info("building nodes (project: '#{project}')")
137
+ failed = 0
138
+ results.each do |node|
139
+ begin
140
+ # validate the node
141
+ begin
142
+ node_is_valid? node
143
+ rescue ArgumentError => ae
144
+ Chef::Log.warn("invalid node element: #{ae}")
145
+ failed = failed +1
146
+ next
147
+ end
148
+
149
+ #write the node to the project
150
+ response.write build_node(node, username, hostname, custom_attributes)
151
+ rescue Exception => e
152
+ Chef::Log.error("=== could not generate xml for #{node}: #{e.message}")
153
+ Chef::Log.debug(e.backtrace.join('\n'))
154
+ end
155
+ end
156
+ Chef::Log.info("nodes complete (project: '#{project}', total: #{results.length - failed}, failed: #{failed})")
157
+
158
+ response.write "</project>"
159
+ Chef::Log.debug(response)
160
+ ensure
161
+ response.close unless response == nil
96
162
  end
97
-
98
- response << "</project>"
99
- Chef::Log.debug(response)
100
-
101
- return response
163
+ return response.path
102
164
  end
103
165
  end
104
166
 
@@ -107,41 +169,41 @@ def build_node (node, username, hostname, custom_attributes)
107
169
  # Certain features in Rundeck require the osFamily value to be set to 'unix' to work appropriately. - SRK
108
170
  #++
109
171
  data = ''
110
- os_family = node[:kernel][:os] =~ /winnt|windows/i ? 'winnt' : 'unix'
111
- nodeexec = node[:kernel][:os] =~ /winnt|windows/i ? "node-executor=\"overthere-winrm\"" : ''
172
+ os_family = node['kernel_os'] =~ /winnt|windows/i ? 'winnt' : 'unix'
173
+ nodeexec = node['kernel_os'] =~ /winnt|windows/i ? "node-executor=\"overthere-winrm\"" : ''
112
174
  data << <<-EOH
113
- <node name="#{xml_escape(node[:fqdn])}" #{nodeexec}
175
+ <node name="#{xml_escape(node['fqdn'])}" #{nodeexec}
114
176
  type="Node"
115
- description="#{xml_escape(node.name)}"
116
- osArch="#{xml_escape(node[:kernel][:machine])}"
177
+ description="#{xml_escape(node['name'])}"
178
+ osArch="#{xml_escape(node['kernel_machine'])}"
117
179
  osFamily="#{xml_escape(os_family)}"
118
- osName="#{xml_escape(node[:platform])}"
119
- osVersion="#{xml_escape(node[:platform_version])}"
120
- tags="#{xml_escape(node.run_list.roles.concat(node.run_list.recipes).join(',') + ',' + node.chef_environment)}"
121
- roles="#{xml_escape(node.run_list.roles.join(','))}"
122
- recipes="#{xml_escape(node.run_list.recipes.join(','))}"
123
- environment="#{xml_escape(node.chef_environment)}"
180
+ osName="#{xml_escape(node['platform'])}"
181
+ osVersion="#{xml_escape(node['platform_version'])}"
182
+ roles="#{xml_escape(node['roles'].join(','))}"
183
+ recipes="#{xml_escape(node['recipes'].join(','))}"
184
+ tags="#{xml_escape(node['roles'].concat(node['recipes']).join(',') + ',' + node['chef_environment'])}"
185
+ environment="#{xml_escape(node['chef_environment'])}"
124
186
  username="#{xml_escape(username)}"
125
- hostname="#{xml_escape(node[hostname])}"
126
- editUrl="#{xml_escape(ChefRundeck.web_ui_url)}/nodes/#{xml_escape(node.name)}/edit" #{custom_attributes.nil? ? '/': ''}>
187
+ hostname="#{xml_escape(node['hostname'])}"
188
+ editUrl="#{xml_escape(ChefRundeck.web_ui_url)}/nodes/#{xml_escape(node['name'])}/edit" #{custom_attributes.nil? ? '/': ''}>
127
189
  EOH
128
190
  if !custom_attributes.nil? then
129
191
  custom_attributes.each do |attr|
130
- attr_name = attr
131
- attr_value = get_custom_attr(node, attr.split('.'))
132
- data << <<-EOH
192
+ attr_name = attr
193
+ attr_value = node[attr.gsub('.','_')]
194
+ data << <<-EOH
133
195
  <attribute name="#{attr_name}"><![CDATA[#{attr_value}]]></attribute>
134
196
  EOH
135
- end
136
- data << "</node>"
137
- end
197
+ end
198
+ data << "</node>"
199
+ end
138
200
 
139
201
  return data
140
202
  end
141
203
 
142
204
  def get_custom_attr (obj, params)
143
205
  value = obj
144
- Chef::Log.debug("loading custom attributes for node: #{obj} with #{params}")
206
+ Chef::Log.debug("loading custom attributes for node: #{obj['name']} with #{params}")
145
207
  params.each do |p|
146
208
  value = value[p.to_sym]
147
209
  if value.nil? then
@@ -151,13 +213,113 @@ def get_custom_attr (obj, params)
151
213
  return value.nil? ? "" : value.to_s
152
214
  end
153
215
 
216
+ # Convert results to be compatiable with Chef 11 format
217
+ def convert_results(results, hostname, custom_attributes)
218
+ new_results = []
219
+ results.each do |node|
220
+ n = {}
221
+ n['name'] = node.name
222
+ n['chef_environment'] = node.chef_environment
223
+ n['run_list'] = node.run_list
224
+ n['recipes'] = !node.run_list.nil? ? node.run_list.recipes : nil
225
+ n['roles'] = !node.run_list.nil? ? node.run_list.roles : nil
226
+ n['fqdn'] = node['fqdn']
227
+ n['hostname'] = node[hostname.to_sym]
228
+ n['kernel_machine'] = !node['kernel'].nil? ? node['kernel']['machine'] : nil
229
+ n['kernel_os'] = !node['kernel'].nil? ? node['kernel']['os'] : nil
230
+ n['platform'] = node['platform']
231
+ n['platform_version'] = node['platform_version']
232
+
233
+ if !custom_attributes.nil? then
234
+ custom_attributes.each do |attr|
235
+ ps_name = attr.gsub('.','_')
236
+ n[ps_name] = get_custom_attr(node, attr.split('.'))
237
+ end
238
+ end
239
+ new_results << n
240
+ end
241
+ return new_results
242
+ end
243
+
244
+
245
+ # Helper def to validate the node
154
246
  def node_is_valid?(node)
155
- node[:fqdn] and
156
- node.name and
157
- node[:kernel] and
158
- node[:kernel][:machine] and
159
- node[:kernel][:os] and
160
- node[:platform] and
161
- node[:platform_version] and
162
- node.chef_environment
247
+ raise ArgumentError, "#{node} missing 'name'" if !node['name']
248
+ raise ArgumentError, "#{node} missing 'chef_environment'" if !node['chef_environment']
249
+ raise ArgumentError, "#{node} missing 'run_list'" if !node['run_list']
250
+ raise ArgumentError, "#{node} missing 'recipes'" if !node['recipes']
251
+ raise ArgumentError, "#{node} missing 'roles'" if !node['roles']
252
+ raise ArgumentError, "#{node} missing 'fqdn'" if !node['fqdn']
253
+ raise ArgumentError, "#{node} missing 'hostname'" if !node['hostname']
254
+ raise ArgumentError, "#{node} missing 'kernel.machine'" if !node['kernel_machine']
255
+ raise ArgumentError, "#{node} missing 'kernel.os'" if !node['kernel_os']
256
+ raise ArgumentError, "#{node} missing 'platform'" if !node['platform']
257
+ raise ArgumentError, "#{node} missing 'platform_version'" if !node['platform_version']
258
+ end
259
+
260
+
261
+ # partial_search(type, query, options, &block)
262
+ #
263
+ # Searches for nodes, roles, etc. and returns the results. This method may
264
+ # perform more than one search request, if there are a large number of results.
265
+ #
266
+ # ==== Parameters
267
+ # * +type+: index type (:role, :node, :client, :environment, data bag name)
268
+ # * +query+: SOLR query. "*:*", "role:blah", "not role:blah", etc. Defaults to '*:*'
269
+ # * +options+: hash with options:
270
+ # ** +:start+: First row to return (:start => 50, :rows => 100 means "return the
271
+ # 50th through 150th result")
272
+ # ** +:rows+: Number of rows to return. Defaults to 1000.
273
+ # ** +:sort+: a SOLR sort specification. Defaults to 'X_CHEF_id_CHEF_X asc'.
274
+ # ** +:keys+: partial search keys. If this is not specified, the search will
275
+ # not be partial.
276
+ #
277
+ # ==== Returns
278
+ #
279
+ # This method returns an array of search results. Partial search results will
280
+ # be JSON hashes with the structure specified in the +keys+ option. Other
281
+ # results include +Chef::Node+, +Chef::Role+, +Chef::Client+, +Chef::Environment+,
282
+ # +Chef::DataBag+ and +Chef::DataBagItem+ objects, depending on the search type.
283
+ #
284
+ # If a block is specified, the block will be called with each result instead of
285
+ # returning an array. This method will not block if it returns
286
+ #
287
+ # If start or row is specified, and no block is given, the result will be a
288
+ # triple containing the list, the start and total:
289
+ #
290
+ # [ [ row1, row2, ... ], start, total ]
291
+ #
292
+ # ==== Example
293
+ #
294
+ # partial_search(:node, 'role:webserver',
295
+ # keys: {
296
+ # name: [ 'name' ],
297
+ # ip: [ 'amazon', 'ip', 'public' ]
298
+ # }
299
+ # ).each do |node|
300
+ # puts "#{node[:name]}: #{node[:ip]}"
301
+ # end
302
+ #
303
+ def partial_search(type, query='*:*', *args, &block)
304
+ # Support both the old (positional args) and new (hash args) styles of calling
305
+ if args.length == 1 && args[0].is_a?(Hash)
306
+ args_hash = args[0]
307
+ else
308
+ args_hash = {}
309
+ args_hash[:sort] = args[0] if args.length >= 1
310
+ args_hash[:start] = args[1] if args.length >= 2
311
+ args_hash[:rows] = args[2] if args.length >= 3
312
+ end
313
+ # If you pass a block, or have the start or rows arguments, do raw result parsing
314
+ if Kernel.block_given? || args_hash[:start] || args_hash[:rows]
315
+ PartialSearch.new.search(type, query, args_hash, &block)
316
+
317
+ # Otherwise, do the iteration for the end user
318
+ else
319
+ results = Array.new
320
+ PartialSearch.new.search(type, query, args_hash) do |o|
321
+ results << o
322
+ end
323
+ results
324
+ end
163
325
  end
@@ -0,0 +1,80 @@
1
+ #
2
+ # Author:: Adam Jacob (<adam@opscode.com>)
3
+ # Author:: John Keiser (<jkeiser@opscode.com>)
4
+ # Copyright:: Copyright (c) 2012 Opscode, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'chef/config'
21
+ require 'uri'
22
+ require 'chef/rest'
23
+ # These are needed so that JSON can inflate search results
24
+ require 'chef/node'
25
+ require 'chef/role'
26
+ require 'chef/environment'
27
+ require 'chef/data_bag'
28
+ require 'chef/data_bag_item'
29
+
30
+ class PartialSearch
31
+
32
+ attr_accessor :rest
33
+
34
+ def initialize(url=nil)
35
+ @rest = ::Chef::REST.new(url || ::Chef::Config[:chef_server_url])
36
+ end
37
+
38
+ # Search Solr for objects of a given type, for a given query. If you give
39
+ # it a block, it will handle the paging for you dynamically.
40
+ def search(type, query='*:*', args={}, &block)
41
+ raise ArgumentError, "Type must be a string or a symbol!" unless (type.kind_of?(String) || type.kind_of?(Symbol))
42
+
43
+ sort = args.include?(:sort) ? args[:sort] : 'X_CHEF_id_CHEF_X asc'
44
+ start = args.include?(:start) ? args[:start] : 0
45
+ rows = args.include?(:rows) ? args[:rows] : 1000
46
+ query_string = "search/#{type}?q=#{escape(query)}&sort=#{escape(sort)}&start=#{escape(start)}&rows=#{escape(rows)}"
47
+ if args[:keys]
48
+ response = @rest.post_rest(query_string, args[:keys])
49
+ response_rows = response['rows'].map { |row| row['data'] }
50
+ else
51
+ response = @rest.get_rest(query_string)
52
+ response_rows = response['rows']
53
+ end
54
+ if block
55
+ response_rows.each { |o| block.call(o) unless o.nil?}
56
+ unless (response["start"] + response_rows.length) >= response["total"]
57
+ nstart = response["start"] + rows
58
+ args_hash = {
59
+ :keys => args[:keys],
60
+ :sort => sort,
61
+ :start => nstart,
62
+ :rows => rows
63
+ }
64
+ search(type, query, args_hash, &block)
65
+ end
66
+ true
67
+ else
68
+ [ response_rows, response["start"], response["total"] ]
69
+ end
70
+ end
71
+
72
+ def list_indexes
73
+ response = @rest.get_rest("search")
74
+ end
75
+
76
+ private
77
+ def escape(s)
78
+ s && URI.escape(s.to_s)
79
+ end
80
+ end
@@ -8,6 +8,8 @@ describe 'ChefRundeck' do
8
8
  ChefRundeck.username = ENV['USER']
9
9
  ChefRundeck.web_ui_url = 'https://manage.opscode.com'
10
10
  ChefRundeck.project_config = "#{ENV['TRAVIS_BUILD_DIR']}/spec/support/chef-rundeck.json"
11
+ ChefRundeck.cache_timeout = 0
12
+ ChefRundeck.environment = :development
11
13
  ChefRundeck.configure
12
14
  end
13
15
  it 'fetch to root should return 200' do
@@ -24,6 +26,16 @@ describe 'ChefRundeck' do
24
26
  Nokogiri::XML(last_response.body).xpath("//project/node[@name='node1.chefrundeck.local']").length().should == 1
25
27
  Nokogiri::XML(last_response.body).xpath("//project/node[@name='node2.chefrundeck.local']").length().should == 0
26
28
  end
29
+ it 'fetched document should be node1 only verify hostname override' do
30
+ get '/node1_systems'
31
+ last_response.should be_ok
32
+ Nokogiri::XML(last_response.body).xpath("//project/node[@name='node1.chefrundeck.local']/@hostname").text().should == "10.0.0.1"
33
+ end
34
+ it 'fetched document should be node2 only verify hostname' do
35
+ get '/node2_systems'
36
+ last_response.should be_ok
37
+ Nokogiri::XML(last_response.body).xpath("//project/node[@name='node2.chefrundeck.local']/@hostname").text().should == "node2.chefrundeck.local"
38
+ end
27
39
  it 'fetched document should be node2 only' do
28
40
  get '/node2_systems'
29
41
  last_response.should be_ok
@@ -33,5 +45,26 @@ describe 'ChefRundeck' do
33
45
  it 'check custom attributes on node2 only' do
34
46
  get '/node2_systems'
35
47
  Nokogiri::XML(last_response.body).xpath("//project/node[@name='node2.chefrundeck.local']/attribute").length().should == 2
48
+ Nokogiri::XML(last_response.body).xpath("//project/node[@name='node2.chefrundeck.local']/attribute")[0].text.should == "linux"
49
+ Nokogiri::XML(last_response.body).xpath("//project/node[@name='node2.chefrundeck.local']/attribute")[1].text.should == "centos"
50
+ end
51
+ it 'check partial search' do
52
+ ChefRundeck.partial_search = true
53
+ get '/node2_systems'
54
+ Nokogiri::XML(last_response.body).xpath("//project/node[@name='node2.chefrundeck.local']/attribute").length().should == 2
55
+ Nokogiri::XML(last_response.body).xpath("//project/node[@name='node2.chefrundeck.local']/attribute")[0].text.should == "linux"
56
+ Nokogiri::XML(last_response.body).xpath("//project/node[@name='node2.chefrundeck.local']/attribute")[1].text.should == "centos"
57
+ end
58
+ it 'partial search: fetched document should be node1 only verify hostname override' do
59
+ ChefRundeck.partial_search = true
60
+ get '/node1_systems'
61
+ last_response.should be_ok
62
+ Nokogiri::XML(last_response.body).xpath("//project/node[@name='node1.chefrundeck.local']/@hostname").text().should == "10.0.0.1"
63
+ end
64
+ it 'partial search: fetched document should be node2 only verify hostname' do
65
+ ChefRundeck.partial_search = true
66
+ get '/node2_systems'
67
+ last_response.should be_ok
68
+ Nokogiri::XML(last_response.body).xpath("//project/node[@name='node2.chefrundeck.local']/@hostname").text().should == "node2.chefrundeck.local"
36
69
  end
37
70
  end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chef-rundeck
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Jacob
8
8
  - Brian Scott
9
9
  - Steven Wagner
10
+ - Peter Crossley
10
11
  autorequire:
11
12
  bindir: bin
12
13
  cert_chain: []
@@ -16,98 +17,98 @@ dependencies:
16
17
  name: sinatra
17
18
  requirement: !ruby/object:Gem::Requirement
18
19
  requirements:
19
- - - ~>
20
+ - - '>='
20
21
  - !ruby/object:Gem::Version
21
22
  version: '0'
22
23
  type: :runtime
23
24
  prerelease: false
24
25
  version_requirements: !ruby/object:Gem::Requirement
25
26
  requirements:
26
- - - ~>
27
+ - - '>='
27
28
  - !ruby/object:Gem::Version
28
29
  version: '0'
29
30
  - !ruby/object:Gem::Dependency
30
31
  name: chef
31
32
  requirement: !ruby/object:Gem::Requirement
32
33
  requirements:
33
- - - ~>
34
+ - - '>='
34
35
  - !ruby/object:Gem::Version
35
36
  version: '0'
36
37
  type: :runtime
37
38
  prerelease: false
38
39
  version_requirements: !ruby/object:Gem::Requirement
39
40
  requirements:
40
- - - ~>
41
+ - - '>='
41
42
  - !ruby/object:Gem::Version
42
43
  version: '0'
43
44
  - !ruby/object:Gem::Dependency
44
45
  name: mixlib-cli
45
46
  requirement: !ruby/object:Gem::Requirement
46
47
  requirements:
47
- - - ~>
48
+ - - '>='
48
49
  - !ruby/object:Gem::Version
49
50
  version: '0'
50
51
  type: :runtime
51
52
  prerelease: false
52
53
  version_requirements: !ruby/object:Gem::Requirement
53
54
  requirements:
54
- - - ~>
55
+ - - '>='
55
56
  - !ruby/object:Gem::Version
56
57
  version: '0'
57
58
  - !ruby/object:Gem::Dependency
58
59
  name: rspec
59
60
  requirement: !ruby/object:Gem::Requirement
60
61
  requirements:
61
- - - ~>
62
+ - - '>='
62
63
  - !ruby/object:Gem::Version
63
64
  version: 1.2.9
64
65
  type: :development
65
66
  prerelease: false
66
67
  version_requirements: !ruby/object:Gem::Requirement
67
68
  requirements:
68
- - - ~>
69
+ - - '>='
69
70
  - !ruby/object:Gem::Version
70
71
  version: 1.2.9
71
72
  - !ruby/object:Gem::Dependency
72
73
  name: yard
73
74
  requirement: !ruby/object:Gem::Requirement
74
75
  requirements:
75
- - - ~>
76
+ - - '>='
76
77
  - !ruby/object:Gem::Version
77
78
  version: '0'
78
79
  type: :development
79
80
  prerelease: false
80
81
  version_requirements: !ruby/object:Gem::Requirement
81
82
  requirements:
82
- - - ~>
83
+ - - '>='
83
84
  - !ruby/object:Gem::Version
84
85
  version: '0'
85
86
  - !ruby/object:Gem::Dependency
86
87
  name: rack-test
87
88
  requirement: !ruby/object:Gem::Requirement
88
89
  requirements:
89
- - - ~>
90
+ - - '>='
90
91
  - !ruby/object:Gem::Version
91
92
  version: '0'
92
93
  type: :development
93
94
  prerelease: false
94
95
  version_requirements: !ruby/object:Gem::Requirement
95
96
  requirements:
96
- - - ~>
97
+ - - '>='
97
98
  - !ruby/object:Gem::Version
98
99
  version: '0'
99
100
  - !ruby/object:Gem::Dependency
100
101
  name: nokogiri
101
102
  requirement: !ruby/object:Gem::Requirement
102
103
  requirements:
103
- - - ~>
104
+ - - '>='
104
105
  - !ruby/object:Gem::Version
105
106
  version: '0'
106
107
  type: :development
107
108
  prerelease: false
108
109
  version_requirements: !ruby/object:Gem::Requirement
109
110
  requirements:
110
- - - ~>
111
+ - - '>='
111
112
  - !ruby/object:Gem::Version
112
113
  version: '0'
113
114
  description: Provides a resource endpoint for RunDeck from a Chef Server
@@ -126,6 +127,7 @@ files:
126
127
  - Rakefile
127
128
  - bin/chef-rundeck
128
129
  - lib/chef-rundeck.rb
130
+ - lib/partial_search.rb
129
131
  - spec/chef-rundeck_spec.rb
130
132
  - spec/spec.opts
131
133
  - spec/spec_helper.rb