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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +40 -4
- data/CONTRIBUTING.md +7 -6
- data/KNIFE.md +151 -0
- data/README.md +70 -192
- data/Rakefile +27 -14
- data/TESTING.md +18 -7
- data/TODO.md +2 -5
- data/lib/chef-encrypted-attributes.rb +7 -1
- data/lib/chef/encrypted_attribute.rb +282 -121
- data/lib/chef/encrypted_attribute/api.rb +521 -0
- data/lib/chef/encrypted_attribute/assertions.rb +16 -6
- data/lib/chef/encrypted_attribute/cache_lru.rb +54 -13
- data/lib/chef/encrypted_attribute/config.rb +198 -89
- data/lib/chef/encrypted_attribute/encrypted_mash.rb +127 -33
- data/lib/chef/encrypted_attribute/encrypted_mash/version0.rb +236 -48
- data/lib/chef/encrypted_attribute/encrypted_mash/version1.rb +249 -36
- data/lib/chef/encrypted_attribute/encrypted_mash/version2.rb +133 -19
- data/lib/chef/encrypted_attribute/exceptions.rb +19 -3
- data/lib/chef/encrypted_attribute/local_node.rb +15 -4
- data/lib/chef/encrypted_attribute/remote_clients.rb +33 -17
- data/lib/chef/encrypted_attribute/remote_node.rb +84 -29
- data/lib/chef/encrypted_attribute/remote_nodes.rb +62 -11
- data/lib/chef/encrypted_attribute/remote_users.rb +58 -19
- data/lib/chef/encrypted_attribute/search_helper.rb +214 -74
- data/lib/chef/encrypted_attribute/version.rb +3 -1
- data/lib/chef/encrypted_attributes.rb +20 -0
- data/lib/chef/knife/core/config.rb +4 -1
- data/lib/chef/knife/core/encrypted_attribute_base.rb +179 -0
- data/lib/chef/knife/core/encrypted_attribute_depends.rb +43 -0
- data/lib/chef/knife/core/encrypted_attribute_editor_options.rb +125 -61
- data/lib/chef/knife/encrypted_attribute_create.rb +51 -31
- data/lib/chef/knife/encrypted_attribute_delete.rb +32 -40
- data/lib/chef/knife/encrypted_attribute_edit.rb +51 -32
- data/lib/chef/knife/encrypted_attribute_show.rb +30 -55
- data/lib/chef/knife/encrypted_attribute_update.rb +43 -28
- data/spec/benchmark_helper.rb +2 -1
- data/spec/integration_helper.rb +1 -0
- data/spec/spec_helper.rb +21 -7
- metadata +75 -36
- metadata.gz.sig +1 -1
- data/API.md +0 -174
- 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 =
|
37
|
-
query.
|
38
|
-
"( #{item} )"
|
39
|
-
|
40
|
-
|
41
|
-
|
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.
|
96
|
+
return false unless keys.is_a?(Hash)
|
48
97
|
keys.reduce(true) do |r, (k, v)|
|
49
|
-
r &&
|
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.
|
61
|
-
|
118
|
+
query.is_a?(String) && query.empty? ||
|
119
|
+
query.is_a?(Array) && query.count == 0
|
62
120
|
end
|
63
121
|
|
64
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
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 =
|
98
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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.
|
246
|
+
if row.is_a?(Hash) && row['data'].is_a?(Hash)
|
135
247
|
row['data']
|
136
248
|
else
|
137
|
-
|
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
|
-
|
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
|
-
|
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
|