chef-encrypted-attributes 0.3.0 → 0.4.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +8 -0
  5. data/CHANGELOG.md +40 -4
  6. data/CONTRIBUTING.md +7 -6
  7. data/KNIFE.md +151 -0
  8. data/README.md +70 -192
  9. data/Rakefile +27 -14
  10. data/TESTING.md +18 -7
  11. data/TODO.md +2 -5
  12. data/lib/chef-encrypted-attributes.rb +7 -1
  13. data/lib/chef/encrypted_attribute.rb +282 -121
  14. data/lib/chef/encrypted_attribute/api.rb +521 -0
  15. data/lib/chef/encrypted_attribute/assertions.rb +16 -6
  16. data/lib/chef/encrypted_attribute/cache_lru.rb +54 -13
  17. data/lib/chef/encrypted_attribute/config.rb +198 -89
  18. data/lib/chef/encrypted_attribute/encrypted_mash.rb +127 -33
  19. data/lib/chef/encrypted_attribute/encrypted_mash/version0.rb +236 -48
  20. data/lib/chef/encrypted_attribute/encrypted_mash/version1.rb +249 -36
  21. data/lib/chef/encrypted_attribute/encrypted_mash/version2.rb +133 -19
  22. data/lib/chef/encrypted_attribute/exceptions.rb +19 -3
  23. data/lib/chef/encrypted_attribute/local_node.rb +15 -4
  24. data/lib/chef/encrypted_attribute/remote_clients.rb +33 -17
  25. data/lib/chef/encrypted_attribute/remote_node.rb +84 -29
  26. data/lib/chef/encrypted_attribute/remote_nodes.rb +62 -11
  27. data/lib/chef/encrypted_attribute/remote_users.rb +58 -19
  28. data/lib/chef/encrypted_attribute/search_helper.rb +214 -74
  29. data/lib/chef/encrypted_attribute/version.rb +3 -1
  30. data/lib/chef/encrypted_attributes.rb +20 -0
  31. data/lib/chef/knife/core/config.rb +4 -1
  32. data/lib/chef/knife/core/encrypted_attribute_base.rb +179 -0
  33. data/lib/chef/knife/core/encrypted_attribute_depends.rb +43 -0
  34. data/lib/chef/knife/core/encrypted_attribute_editor_options.rb +125 -61
  35. data/lib/chef/knife/encrypted_attribute_create.rb +51 -31
  36. data/lib/chef/knife/encrypted_attribute_delete.rb +32 -40
  37. data/lib/chef/knife/encrypted_attribute_edit.rb +51 -32
  38. data/lib/chef/knife/encrypted_attribute_show.rb +30 -55
  39. data/lib/chef/knife/encrypted_attribute_update.rb +43 -28
  40. data/spec/benchmark_helper.rb +2 -1
  41. data/spec/integration_helper.rb +1 -0
  42. data/spec/spec_helper.rb +21 -7
  43. metadata +75 -36
  44. metadata.gz.sig +1 -1
  45. data/API.md +0 -174
  46. data/INTERNAL.md +0 -166
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  #
2
3
  # Author:: Xabier de Zuazo (<xabier@onddo.com>)
3
4
  # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
@@ -21,124 +22,263 @@ require 'chef/encrypted_attribute/exceptions'
21
22
 
22
23
  class Chef
23
24
  class EncryptedAttribute
25
+ # Search Helpers to do normal or partial searches.
24
26
  module SearchHelper
25
27
  extend self
26
28
 
29
+ # Gets a Chef Search Query object.
30
+ #
31
+ # @return [Chef::Search::Query] search query object instance.
32
+ # @api private
27
33
  def query
28
34
  Chef::Search::Query.new
29
35
  end
30
36
 
37
+ # Escapes a search query string to be used in URLs.
38
+ #
39
+ # @param str [String] query to escape.
40
+ # @return [String] escaped query string.
41
+ # @api private
31
42
  def escape(str)
32
43
  URI.escape(str.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
33
44
  end
34
45
 
46
+ # Escapes a search query array.
47
+ #
48
+ # When multiple queries are provided, the result will be *OR*-ed.
49
+ #
50
+ # @param query [Array<String>, String] search query.
51
+ # @return [String] escaped query string.
35
52
  def escape_query(query)
36
- query_s = if query.kind_of?(Array)
37
- query.map do |item|
38
- "( #{item} )"
39
- end.compact.join(' OR ')
40
- else
41
- query.to_s
42
- end
53
+ query_s =
54
+ if query.is_a?(Array)
55
+ query.map { |item| "( #{item} )" }.compact.join(' OR ')
56
+ else
57
+ query.to_s
58
+ end
43
59
  escape(query_s)
44
60
  end
45
61
 
62
+ # Checks if a Hash key from a search keys structure format is correct.
63
+ #
64
+ # @param k [Mixed] hash key to check.
65
+ # @return [Boolean] `true` if key is a `String` or a `Symbol`.
66
+ # @api private
67
+ def valid_search_keys_key?(k)
68
+ k.is_a?(String) || k.is_a?(Symbol)
69
+ end
70
+
71
+ # Checks if a Hash value from a search keys structure format is correct.
72
+ #
73
+ # @param v [Mixed] hash value to check.
74
+ # @return [Boolean] `true` if value is a `Array<String>`.
75
+ # @api private
76
+ def valid_search_keys_value?(v)
77
+ return false unless v.is_a?(Array)
78
+ v.reduce(true) { |a, e| a && e.is_a?(String) }
79
+ end
80
+
81
+ # Checks if a search keys structure format is correct.
82
+ #
83
+ # This is an example of a correct search structure:
84
+ #
85
+ # ```ruby
86
+ # {
87
+ # ipaddress: %w(ipaddress),
88
+ # mysql_version: %w(mysql version)
89
+ # }
90
+ # ```
91
+ #
92
+ # @param keys [Hash] search keys structure.
93
+ # @return [Boolean] `true` if search keys structure format is correct.
94
+ # @api private
46
95
  def valid_search_keys?(keys)
47
- return false unless keys.kind_of?(Hash)
96
+ return false unless keys.is_a?(Hash)
48
97
  keys.reduce(true) do |r, (k, v)|
49
- r && unless k.kind_of?(String) || k.kind_of?(Symbol) and v.kind_of?(Array)
50
- false
51
- else
52
- v.reduce(true) do |r, x|
53
- r and x.kind_of?(String)
54
- end
55
- end
98
+ r && valid_search_keys_key?(k) && valid_search_keys_value?(v)
56
99
  end
57
100
  end
58
101
 
102
+ # Assert that the search keys structure format is correct.
103
+ #
104
+ # @return void
105
+ # @raise [InvalidSearchKeys] if search keys structure is wrong.
106
+ # @api private
107
+ def assert_search_keys(keys)
108
+ return if valid_search_keys?(keys)
109
+ fail InvalidSearchKeys, "Invalid search keys: #{keys.inspect}"
110
+ end
111
+
112
+ # Check if search query is empty.
113
+ #
114
+ # @param query [Array<String>, String] search query.
115
+ # @return [Boolean] `true` if search query is empty.
116
+ # @api private
59
117
  def empty_search?(query)
60
- query.kind_of?(String) && query.empty? or
61
- query.kind_of?(Array) && query.count == 0
118
+ query.is_a?(String) && query.empty? ||
119
+ query.is_a?(Array) && query.count == 0
62
120
  end
63
121
 
64
- def search(type, query, keys, rows=1000, partial_search=true)
122
+ # Does a search in the Chef Server.
123
+ #
124
+ # @param type [Symbol] search index to use. See [Chef Search Indexes]
125
+ # (http://docs.getchef.com/chef_search.html#search-indexes).
126
+ # @param query [Array<String>, String] search query. For example:
127
+ # `%w(admin:true)`. Results will be *OR*-ed when multiple string queries
128
+ # are provided.
129
+ # @param keys [Hash] search keys structure. For example:
130
+ # `{ipaddress: %w(ipaddress), mysql_version: %w(mysql version) }`.
131
+ # @param rows [Fixnum, String] maximum number of rows to return.
132
+ # @param partial_search [Boolean] whether to use partial search.
133
+ # @return [Array<Hash>] An array with the response, for example:
134
+ # `[{ 'ipaddress' => '192.168.1.1' }]`
135
+ # @raise [SearchFailure] if there is a Chef search error.
136
+ # @raise [SearchFatalError] if the Chef search response is wrong.
137
+ # @raise [InvalidSearchKeys] if search keys structure is wrong.
138
+ def search(type, query, keys, rows = 1000, partial_search = true)
65
139
  return [] if empty_search?(query) # avoid empty searches
66
- if partial_search
67
- partial_search(type, query, keys, rows)
68
- else
69
- normal_search(type, query, keys, rows)
140
+ search_method = partial_search ? :partial_search : :normal_search
141
+ send(search_method, type, query, keys, rows)
142
+ rescue Net::HTTPServerException => e
143
+ unless e.response.is_a?(Net::HTTPResponse) && e.response.code == '404'
144
+ raise SearchFailure, "Search exception #{e.class}: #{e}"
70
145
  end
146
+ return []
147
+ rescue Net::HTTPFatalError => e
148
+ raise SearchFailure, "Search exception #{e.class}: #{e}"
71
149
  end
72
150
 
73
- def normal_search(type, query, keys, rows=1000)
74
- escaped_query = escape_query(query)
75
- Chef::Log.info("Normal Search query: #{escaped_query}, keys: #{keys.inspect}")
76
- unless valid_search_keys?(keys)
77
- raise InvalidSearchKeys, "Invalid search keys: #{keys.inspect}"
78
- end
151
+ # Assert that the normal (no partial) search response is correct.
152
+ #
153
+ # @param resp [Array] normal search result.
154
+ # @return void
155
+ # @raise [SearchFatalError] if the Chef search response is wrong.
156
+ # @api private
157
+ def assert_normal_search_response(resp)
158
+ return if resp.is_a?(Array)
159
+ fail SearchFatalError,
160
+ "Wrong response received from Normal Search: #{resp.inspect}"
161
+ end
79
162
 
80
- begin
81
- resp = self.query.search(type, escaped_query, nil, 0, rows)[0]
82
- rescue Net::HTTPServerException => e
83
- if e.response.kind_of?(Net::HTTPResponse) and e.response.code == '404' # Not Found
84
- return []
85
- else
86
- raise SearchFailure, "Partial Search exception #{e.class.name}: #{e.to_s}"
163
+ # Parses a normal (no partial) search response row.
164
+ #
165
+ # @param row [Array] the normal search result row.
166
+ # @param attr_ary [Array<String>] key path as Array.
167
+ # @return [Hash] A hash with the response row, for example:
168
+ # `[ 'ipaddress' => '192.168.1.1' }`
169
+ # @api private
170
+ def parse_normal_search_row_attribute(row, attr_ary)
171
+ attr_ary.reduce(row) do |r, attr|
172
+ if r.respond_to?(attr)
173
+ r.send(attr)
174
+ elsif r.respond_to?(:key?)
175
+ r[attr.to_s] if r.key?(attr.to_s)
87
176
  end
88
- rescue Net::HTTPFatalError => e
89
- raise SearchFailure, "Normal Search exception #{e.class.name}: #{e.to_s}"
90
- end
91
- unless resp.kind_of?(Array)
92
- raise SearchFatalError, "Wrong response received from Normal Search: #{resp.inspect}"
93
177
  end
94
- # TODO too complex, refactorize
178
+ end
179
+
180
+ # Parses a normal (no partial) full search search response.
181
+ #
182
+ # @param resp [Array] normal search result.
183
+ # @param keys [Hash] search keys structure. For example:
184
+ # `{ipaddress: %w(ipaddress), mysql_version: %w(mysql version) }`.
185
+ # @return [Array<Hash>] An array with the response, for example:
186
+ # `[{ 'ipaddress' => '192.168.1.1' }]`
187
+ # @api private
188
+ def parse_normal_search_response(resp, keys)
95
189
  resp.map do |row|
96
190
  Hash[keys.map do |key_name, attr_ary|
97
- value = attr_ary.reduce(row) do |r, attr|
98
- if r.respond_to?(attr.to_sym)
99
- r.send(attr.to_sym)
100
- elsif r.respond_to?(:has_key?)
101
- if r.has_key?(attr.to_s)
102
- r[attr.to_s]
103
- end
104
- end
105
- end
106
- [ key_name, value ]
191
+ value = parse_normal_search_row_attribute(row, attr_ary)
192
+ [key_name, value]
107
193
  end]
108
194
  end
109
195
  end
110
196
 
111
- def partial_search(type, query, keys, rows=1000)
112
- escaped_query = "search/#{escape(type)}?q=#{escape_query(query)}&start=0&rows=#{rows}"
113
- Chef::Log.info("Partial Search query: #{escaped_query}, keys: #{keys.inspect}")
114
- unless valid_search_keys?(keys)
115
- raise InvalidSearchKeys, "Invalid search keys: #{keys.inspect}"
116
- end
197
+ # Does a normal (no partial) search in the Chef Server.
198
+ #
199
+ # @param type [Symbol] search index to use. See [Chef Search Indexes]
200
+ # (http://docs.getchef.com/chef_search.html#search-indexes).
201
+ # @param query [String, Array<String>] search query. For example:
202
+ # `%w(admin:true)`. Results will be *OR*-ed when multiple string queries
203
+ # are provided.
204
+ # @param keys [Hash] search keys structure. For example:
205
+ # `{ipaddress: %w(ipaddress), mysql_version: %w(mysql version) }`.
206
+ # @param rows [Fixnum, String] maximum number of rows to return.
207
+ # @return [Array<Hash>] An array with the response, for example:
208
+ # `[{ 'ipaddress' => '192.168.1.1' }]`
209
+ # @raise [InvalidSearchKeys] if search keys structure is wrong.
210
+ def normal_search(type, query, keys, rows = 1000)
211
+ escaped_query = escape_query(query)
212
+ Chef::Log.info(
213
+ "Normal Search query: #{escaped_query}, keys: #{keys.inspect}"
214
+ )
215
+ assert_search_keys(keys)
117
216
 
118
- rest = Chef::REST.new(Chef::Config[:chef_server_url])
119
- begin
120
- resp = rest.post_rest(escaped_query, keys)
121
- rescue Net::HTTPServerException => e
122
- if e.response.kind_of?(Net::HTTPResponse) and e.response.code == '404' # Not Found
123
- return []
124
- else
125
- raise SearchFailure, "Partial Search exception #{e.class.name}: #{e.to_s}"
126
- end
127
- rescue Net::HTTPFatalError => e
128
- raise SearchFailure, "Partial Search exception #{e.class.name}: #{e.to_s}"
129
- end
130
- unless resp.kind_of?(Hash) and resp.has_key?('rows') and resp['rows'].kind_of?(Array)
131
- raise SearchFatalError, "Wrong response received from Partial Search: #{resp.inspect}"
132
- end
217
+ resp = self.query.search(type, escaped_query, nil, 0, rows)[0]
218
+ assert_normal_search_response(resp)
219
+ parse_normal_search_response(resp, keys)
220
+ end
221
+
222
+ # Assert that the partial search response is correct.
223
+ #
224
+ # @param resp [Hash] partial search result. For example:
225
+ # `{ 'rows' => [ 'data' => { 'ipaddress' => '192.168.1.1' } }] }`.
226
+ # @return void
227
+ # @raise [SearchFatalError] if the Chef search response is wrong.
228
+ # @api private
229
+ def assert_partial_search_response(resp)
230
+ return if resp.is_a?(Hash) && resp.key?('rows') &&
231
+ resp['rows'].is_a?(Array)
232
+ fail SearchFatalError,
233
+ "Wrong response received from Partial Search: #{resp.inspect}"
234
+ end
235
+
236
+ # Parses a partial full search search response.
237
+ #
238
+ # @param resp [Hash] partial search result. For example:
239
+ # `{ 'rows' => [ 'data' => { 'ipaddress' => '192.168.1.1' } }] }`.
240
+ # @return [Array<Hash>] An array with the response, for example:
241
+ # `[{ 'ipaddress' => '192.168.1.1' }]`
242
+ # @raise [SearchFatalError] if the Chef search response is wrong.
243
+ # @api private
244
+ def parse_partial_search_response(resp)
133
245
  resp['rows'].map do |row|
134
- if row.kind_of?(Hash) and row['data'].kind_of?(Hash)
246
+ if row.is_a?(Hash) && row['data'].is_a?(Hash)
135
247
  row['data']
136
248
  else
137
- raise SearchFatalError, "Wrong row format received from Partial Search: #{row.inspect}"
249
+ fail SearchFatalError,
250
+ "Wrong row format received from Partial Search: #{row.inspect}"
138
251
  end
139
252
  end.compact
140
253
  end
141
254
 
255
+ # Does a partial search in the Chef Server.
256
+ #
257
+ # @param type [Symbol] search index to use. See [Chef Search Indexes]
258
+ # (http://docs.getchef.com/chef_search.html#search-indexes).
259
+ # @param query [String, Array<String>] search query. For example:
260
+ # `%w(admin:true)`. Results will be *OR*-ed when multiple string queries
261
+ # are provided.
262
+ # @param keys [Hash] search keys structure. For example:
263
+ # `{ipaddress: %w(ipaddress), mysql_version: %w(mysql version) }`.
264
+ # @param rows [Fixnum, String] maximum number of rows to return.
265
+ # @return [Array<Hash>] An array with the response, for example:
266
+ # `[{ 'ipaddress' => '192.168.1.1' }]`
267
+ # @raise [InvalidSearchKeys] if search keys structure is wrong.
268
+ # @raise [SearchFatalError] if the Chef search response is wrong.
269
+ def partial_search(type, query, keys, rows = 1000)
270
+ escaped_query =
271
+ "search/#{escape(type)}?q=#{escape_query(query)}&start=0&rows=#{rows}"
272
+ Chef::Log.info(
273
+ "Partial Search query: #{escaped_query}, keys: #{keys.inspect}"
274
+ )
275
+ assert_search_keys(keys)
276
+
277
+ rest = Chef::REST.new(Chef::Config[:chef_server_url])
278
+ resp = rest.post_rest(escaped_query, keys)
279
+ assert_partial_search_response(resp)
280
+ parse_partial_search_response(resp)
281
+ end
142
282
  end
143
283
  end
144
284
  end
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  #
2
3
  # Author:: Xabier de Zuazo (<xabier@onddo.com>)
3
4
  # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
@@ -18,6 +19,7 @@
18
19
 
19
20
  class Chef
20
21
  class EncryptedAttribute
21
- VERSION = '0.3.0'
22
+ # `chef-encrypted-attributes` gem version.
23
+ VERSION = '0.4.0'
22
24
  end
23
25
  end
@@ -0,0 +1,20 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author:: Xabier de Zuazo (<xabier@onddo.com>)
4
+ # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
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/encrypted_attribute'
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  #
2
3
  # Author:: Xabier de Zuazo (<xabier@onddo.com>)
3
4
  # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
@@ -16,4 +17,6 @@
16
17
  # limitations under the License.
17
18
  #
18
19
 
19
- Chef::Config[:knife][:encrypted_attributes] = Mash.new unless Chef::Config[:knife][:encrypted_attributes].kind_of?(Hash)
20
+ unless Chef::Config[:knife][:encrypted_attributes].is_a?(Hash)
21
+ Chef::Config[:knife][:encrypted_attributes] = Mash.new
22
+ end
@@ -0,0 +1,179 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author:: Xabier de Zuazo (<xabier@onddo.com>)
4
+ # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
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/knife'
21
+
22
+ class Chef
23
+ class Knife
24
+ module Core
25
+ # knife encrypted attribute commands base class.
26
+ #
27
+ # All encrypted attribute knife commands inherit some common method from
28
+ # this class.
29
+ class EncryptedAttributeBase < Knife
30
+ # Prints a fatal error and exits without success.
31
+ #
32
+ # @param msg [String] message to print.
33
+ # @return void
34
+ def die(msg)
35
+ ui.fatal(msg)
36
+ exit 1
37
+ end
38
+
39
+ # Asserts that the option value is not `nil`.
40
+ #
41
+ # Shows usage and exists if `nil`.
42
+ #
43
+ # @param option [Mixed] value to check.
44
+ # @param msg [String] error message to print in case value is `nil`.
45
+ # @return void
46
+ def option_assert(option, msg)
47
+ return unless option.nil?
48
+ show_usage
49
+ die(msg)
50
+ end
51
+
52
+ # Asserts that an encrypted attribute exists.
53
+ #
54
+ # Exits with error if the attribute does no exist.
55
+ #
56
+ # @param node_name [String] Chef node name.
57
+ # @param attr_ary [Array<String>] node attribute path as Array.
58
+ # @return void
59
+ # @raise [ArgumentError] if the attribute path format is wrong.
60
+ def assert_attribute_exists(node_name, attr_ary)
61
+ return if Chef::EncryptedAttribute.exist_on_node?(node_name, attr_ary)
62
+ die('Encrypted attribute not found')
63
+ end
64
+
65
+ # Asserts that an encrypted attribute does not exist.
66
+ #
67
+ # Exits with error if the attribute exist.
68
+ #
69
+ # @param node_name [String] Chef node name.
70
+ # @param attr_ary [Array<String>] node attribute path as Array.
71
+ # @return void
72
+ # @raise [ArgumentError] if the attribute path format is wrong.
73
+ def assert_attribute_does_not_exist(node_name, attr_ary)
74
+ return unless
75
+ Chef::EncryptedAttribute.exist_on_node?(node_name, attr_ary)
76
+ die('Encrypted attribute already exists')
77
+ end
78
+
79
+ # Parses knife arguments.
80
+ #
81
+ # Exits with error if the arguments are wrong.
82
+ #
83
+ # @return void
84
+ # @see #assert_valid_args
85
+ def parse_args
86
+ @node_name = @name_args[0]
87
+ @attr_path = @name_args[1]
88
+ option_assert(@node_name, 'You must specify a node name')
89
+ option_assert(
90
+ @attr_path, 'You must specify an encrypted attribute name'
91
+ )
92
+ @attr_ary = attribute_path_to_ary(@attr_path)
93
+
94
+ assert_valid_args
95
+ end
96
+
97
+ # Asserts that the arguments are valid.
98
+ #
99
+ # Exits with error if arguments are wrong.
100
+ #
101
+ # @return void
102
+ def assert_valid_args
103
+ # nop
104
+ end
105
+
106
+ # Asserts that I can decrypt an encrypted attribute from a remote node.
107
+ #
108
+ # Exists with an error if the attribute cannot be decrypted.
109
+ #
110
+ # @param node_name [String] Chef node name.
111
+ # @param attr_ary [Array<String>] node attribute path as Array.
112
+ # @return void
113
+ # @raise [ArgumentError] if the attribute path format is wrong.
114
+ # @raise [UnacceptableEncryptedAttributeFormat] if encrypted attribute
115
+ # format is wrong.
116
+ # @raise [UnsupportedEncryptedAttributeFormat] if encrypted attribute
117
+ # format is not supported or unknown.
118
+ # @raise [SearchFailure] if there is a Chef search error.
119
+ # @raise [SearchFatalError] if the Chef search response is wrong.
120
+ # @raise [InvalidSearchKeys] if search keys structure is wrong.
121
+ def assert_attribute_readable(node_name, attr_ary)
122
+ # try to read the attribute
123
+ Chef::EncryptedAttribute.load_from_node(node_name, attr_ary)
124
+ end
125
+
126
+ # Parses the escape character from an array path string.
127
+ #
128
+ # @param str [String] the full string to parse.
129
+ # @param i [String] string position that contains the escape character.
130
+ # @param delim [String] delimiter used for string notation.
131
+ # @return [String] the character unscaped.
132
+ # see #attribute_path_to_ary
133
+ # @api private
134
+ def attribute_path_to_ary_read_escape(str, i, delim)
135
+ if str[i + 1] == delim
136
+ str[i + 1]
137
+ else
138
+ str[i] + (str[i + 1].nil? ? '' : str[i + 1])
139
+ end
140
+ end
141
+
142
+ # Parses an array path in or escaped string notation.
143
+ #
144
+ # Literal delimiter values can be escaped using the escape character.
145
+ #
146
+ # Uses dot notation by default, using `'.'` as delimiter and `'\'` as
147
+ # escape character.
148
+ #
149
+ # For example, for `'encrypted.attr\.ibute'` will return
150
+ # `%w(encrypted attr.ibute)`.
151
+ #
152
+ # @param str [String] array path in dot notation.
153
+ # @param delim [String] delimiter used for string notation.
154
+ # @param escape [String] escape character to use.
155
+ # @return [Array<String>] attribute path as array.
156
+ def attribute_path_to_ary(str, delim = '.', escape = '\\')
157
+ # cool, but doesn't work for some edge cases
158
+ # return str.scan(/(?:[^.\\]|\\.)+/).map {|x| x.gsub('\\.', '.') }
159
+ result = []
160
+ current = ''
161
+ i = 0
162
+ until str[i].nil?
163
+ if str[i] == escape
164
+ current << attribute_path_to_ary_read_escape(str, i, delim)
165
+ i += 1 # skip the next char
166
+ elsif str[i] == delim
167
+ result << current
168
+ current = ''
169
+ else
170
+ current << str[i]
171
+ end
172
+ i += 1
173
+ end
174
+ result << current
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end