chef-dk 0.7.0 → 0.8.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
- data/README.md +2 -2
- data/lib/chef-dk/builtin_commands.rb +10 -0
- data/lib/chef-dk/command/base.rb +2 -2
- data/lib/chef-dk/command/clean_policy_cookbooks.rb +116 -0
- data/lib/chef-dk/command/clean_policy_revisions.rb +113 -0
- data/lib/chef-dk/command/delete_policy.rb +122 -0
- data/lib/chef-dk/command/delete_policy_group.rb +122 -0
- data/lib/chef-dk/command/export.rb +3 -3
- data/lib/chef-dk/command/generate.rb +8 -0
- data/lib/chef-dk/command/generator_commands/app.rb +1 -1
- data/lib/chef-dk/command/generator_commands/cookbook.rb +1 -1
- data/lib/chef-dk/command/generator_commands/policyfile.rb +1 -1
- data/lib/chef-dk/command/generator_commands/repo.rb +1 -1
- data/lib/chef-dk/command/install.rb +22 -5
- data/lib/chef-dk/command/provision.rb +0 -4
- data/lib/chef-dk/command/push.rb +1 -2
- data/lib/chef-dk/command/shell_init.rb +65 -6
- data/lib/chef-dk/command/show_policy.rb +1 -2
- data/lib/chef-dk/command/undelete.rb +155 -0
- data/lib/chef-dk/command/update.rb +5 -5
- data/lib/chef-dk/command/verify.rb +61 -17
- data/lib/chef-dk/completions/bash.sh.erb +5 -0
- data/lib/chef-dk/completions/chef.fish.erb +10 -0
- data/lib/chef-dk/completions/zsh.zsh.erb +21 -0
- data/lib/chef-dk/exceptions.rb +12 -0
- data/lib/chef-dk/helpers.rb +17 -0
- data/lib/chef-dk/policyfile/community_cookbook_source.rb +0 -3
- data/lib/chef-dk/policyfile/lister.rb +3 -1
- data/lib/chef-dk/policyfile/undo_record.rb +142 -0
- data/lib/chef-dk/policyfile/undo_stack.rb +130 -0
- data/lib/chef-dk/policyfile_lock.rb +30 -0
- data/lib/chef-dk/policyfile_services/clean_policies.rb +5 -4
- data/lib/chef-dk/policyfile_services/clean_policy_cookbooks.rb +125 -0
- data/lib/chef-dk/policyfile_services/rm_policy.rb +142 -0
- data/lib/chef-dk/policyfile_services/rm_policy_group.rb +86 -0
- data/lib/chef-dk/policyfile_services/show_policy.rb +1 -1
- data/lib/chef-dk/policyfile_services/undelete.rb +108 -0
- data/lib/chef-dk/service_exceptions.rb +11 -0
- data/lib/chef-dk/skeletons/code_generator/files/default/chefignore +6 -2
- data/lib/chef-dk/skeletons/code_generator/files/default/repo/README.md +1 -1
- data/lib/chef-dk/skeletons/code_generator/files/default/repo/cookbooks/example/attributes/default.rb +1 -1
- data/lib/chef-dk/skeletons/code_generator/files/default/repo/cookbooks/example/recipes/default.rb +1 -1
- data/lib/chef-dk/version.rb +1 -1
- data/lib/kitchen/provisioner/policyfile_zero.rb +4 -1
- data/spec/unit/command/base_spec.rb +26 -1
- data/spec/unit/command/clean_policy_cookbooks_spec.rb +181 -0
- data/spec/unit/command/clean_policy_revisions_spec.rb +181 -0
- data/spec/unit/command/delete_policy_group_spec.rb +207 -0
- data/spec/unit/command/delete_policy_spec.rb +207 -0
- data/spec/unit/command/generate_spec.rb +41 -1
- data/spec/unit/command/generator_commands/cookbook_spec.rb +1 -1
- data/spec/unit/command/generator_commands/policyfile_spec.rb +1 -1
- data/spec/unit/command/install_spec.rb +24 -0
- data/spec/unit/command/shell_init_spec.rb +176 -5
- data/spec/unit/command/undelete_spec.rb +246 -0
- data/spec/unit/helpers_spec.rb +24 -0
- data/spec/unit/policyfile/lister_spec.rb +16 -0
- data/spec/unit/policyfile/undo_record_spec.rb +260 -0
- data/spec/unit/policyfile/undo_stack_spec.rb +266 -0
- data/spec/unit/policyfile_lock_serialization_spec.rb +41 -0
- data/spec/unit/policyfile_services/clean_policy_cookbooks_spec.rb +275 -0
- data/spec/unit/policyfile_services/rm_policy_group_spec.rb +241 -0
- data/spec/unit/policyfile_services/rm_policy_spec.rb +266 -0
- data/spec/unit/policyfile_services/show_policy_spec.rb +52 -2
- data/spec/unit/policyfile_services/undelete_spec.rb +304 -0
- metadata +43 -91
@@ -37,8 +37,7 @@ NOTE: `chef update` does not yet support granular updates (e.g., just updating
|
|
37
37
|
the `run_list` or a specific cookbook version). Support will be added in a
|
38
38
|
future version.
|
39
39
|
|
40
|
-
|
41
|
-
for more information.
|
40
|
+
See our detailed README for more information:
|
42
41
|
|
43
42
|
https://github.com/opscode/chef-dk/blob/master/POLICYFILE_README.md
|
44
43
|
|
@@ -74,7 +73,7 @@ BANNER
|
|
74
73
|
end
|
75
74
|
|
76
75
|
def run(params = [])
|
77
|
-
apply_params!(params)
|
76
|
+
return 1 unless apply_params!(params)
|
78
77
|
if update_attributes?
|
79
78
|
attributes_updater.run
|
80
79
|
else
|
@@ -116,10 +115,11 @@ BANNER
|
|
116
115
|
def apply_params!(params)
|
117
116
|
remaining_args = parse_options(params)
|
118
117
|
if remaining_args.size > 1
|
119
|
-
ui.err(
|
120
|
-
|
118
|
+
ui.err(opt_parser)
|
119
|
+
false
|
121
120
|
else
|
122
121
|
@policyfile_relative_path = remaining_args.first
|
122
|
+
true
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
@@ -127,11 +127,17 @@ module ChefDK
|
|
127
127
|
|
128
128
|
c.smoke_test do
|
129
129
|
# ------------
|
130
|
-
# we want to avoid hard-coding driver names, but calling Gem::Specification produces a warning;
|
131
|
-
# seems to be the best way to silence it.
|
130
|
+
# we want to avoid hard-coding driver names, but calling Gem::Specification produces a warning;
|
131
|
+
# changing $VERBOSE seems to be the best way to silence it.
|
132
132
|
verbose = $VERBOSE
|
133
133
|
$VERBOSE = nil
|
134
134
|
|
135
|
+
# construct a hash of { driver_name => [version1, version2, ...]}
|
136
|
+
driver_versions = {}
|
137
|
+
Gem::Specification.all.map { |gs| [gs.name, gs.version] }.
|
138
|
+
select { |n| n[0] =~ /^chef-provisioning-/ }.
|
139
|
+
each { |gem, version| (driver_versions[gem] ||= []) << version }
|
140
|
+
|
135
141
|
drivers = Gem::Specification.all.map { |gs| gs.name }.
|
136
142
|
select { |n| n =~ /^chef-provisioning-/ }.
|
137
143
|
uniq
|
@@ -141,6 +147,48 @@ module ChefDK
|
|
141
147
|
# ------------
|
142
148
|
failures = []
|
143
149
|
|
150
|
+
# ------------
|
151
|
+
# fail the verify if we have more than one version of chef-provisioning or any of its drivers.
|
152
|
+
def format_gem_failure(name, versions)
|
153
|
+
<<-EOS
|
154
|
+
#{name} has multiple versions installed:
|
155
|
+
#{versions.sort.map { |gv| " #{gv.to_s}" }.join("\n")}
|
156
|
+
EOS
|
157
|
+
end
|
158
|
+
|
159
|
+
failures << format_gem_failure("chef-provisioning", versions) if versions.size > 1
|
160
|
+
|
161
|
+
driver_versions.keys.sort.each do |driver_name|
|
162
|
+
v = driver_versions[driver_name]
|
163
|
+
failures << format_gem_failure(driver_name, v) if v.size > 1
|
164
|
+
end
|
165
|
+
|
166
|
+
if failures.size > 0
|
167
|
+
failures << <<-EOS
|
168
|
+
|
169
|
+
Some applications may need or prefer different versions of the chef-provisioning gem or its drivers, so
|
170
|
+
this multiple-version check can fail if a user has installed new versions of those libraries.
|
171
|
+
EOS
|
172
|
+
end
|
173
|
+
|
174
|
+
# ------------
|
175
|
+
# load the core gem and all of the drivers (ignoring versions).
|
176
|
+
require "chef/provisioning"
|
177
|
+
drivers.map { |d| "#{d.gsub('-', '/')}_driver" }.each do |driver_gem|
|
178
|
+
begin
|
179
|
+
begin
|
180
|
+
require driver_gem
|
181
|
+
rescue LoadError
|
182
|
+
# anomalously, chef-provisioning-fog does not have a fog_driver.rb. (9/2015)
|
183
|
+
require "#{driver_gem}/driver.rb"
|
184
|
+
end
|
185
|
+
rescue LoadError => ex
|
186
|
+
puts ex
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# ------------
|
191
|
+
# look for version dependency conflicts.
|
144
192
|
tmpdir do |cwd|
|
145
193
|
versions.each do |provisioning_version|
|
146
194
|
gemfile = "chef-provisioning-#{provisioning_version}-chefdk-test.gemfile"
|
@@ -154,15 +202,11 @@ module ChefDK
|
|
154
202
|
result = sh("bundle install --local --quiet", cwd: cwd, env: {"BUNDLE_GEMFILE" => gemfile })
|
155
203
|
|
156
204
|
if result.exitstatus != 0
|
157
|
-
failures << result
|
205
|
+
failures << result.stdout
|
158
206
|
end
|
159
|
-
|
160
207
|
end # end provisioning versions.
|
161
208
|
|
162
|
-
|
163
|
-
failures.each { |fail| puts fail.stdout }
|
164
|
-
puts "\nDriver list (no version restrictions):\n #{drivers.join("\n ")}"
|
165
|
-
end
|
209
|
+
failures.each { |fail| puts fail }
|
166
210
|
|
167
211
|
# dubious on Windows.
|
168
212
|
# this is weird, but we seem to require a Mixlib::ShellOut as the return value. suggestions
|
@@ -238,13 +282,13 @@ end
|
|
238
282
|
|
239
283
|
c.smoke_test do
|
240
284
|
|
241
|
-
if File.directory?(
|
242
|
-
sh!("
|
285
|
+
if File.directory?(usr_bin_prefix)
|
286
|
+
sh!("#{usr_bin_path("berks")} -v")
|
243
287
|
|
244
|
-
sh!("
|
288
|
+
sh!("#{usr_bin_path("chef")} -v")
|
245
289
|
|
246
|
-
sh!("
|
247
|
-
sh!("
|
290
|
+
sh!("#{usr_bin_path("chef-client")} -v")
|
291
|
+
sh!("#{usr_bin_path("chef-solo")} -v")
|
248
292
|
|
249
293
|
# In `knife`, `knife -v` follows a different code path that skips
|
250
294
|
# command/plugin loading; `knife -h` loads commands and plugins, but
|
@@ -253,17 +297,17 @@ end
|
|
253
297
|
# exits 0, which runs most of the code.
|
254
298
|
#
|
255
299
|
# See also: https://github.com/opscode/chef-dk/issues/227
|
256
|
-
sh!("
|
300
|
+
sh!("#{usr_bin_path("knife")} exec -E true")
|
257
301
|
|
258
302
|
tmpdir do |dir|
|
259
303
|
# Kitchen tries to create a .kitchen dir even when just running
|
260
304
|
# `kitchen -v`:
|
261
|
-
sh!("
|
305
|
+
sh!("#{usr_bin_path("kitchen")} -v", cwd: dir)
|
262
306
|
end
|
263
307
|
|
264
|
-
sh!("
|
308
|
+
sh!("#{usr_bin_path("ohai")} -v")
|
265
309
|
|
266
|
-
sh!("
|
310
|
+
sh!("#{usr_bin_path("foodcritic")} -V")
|
267
311
|
end
|
268
312
|
|
269
313
|
# Test blocks are expected to return a Mixlib::ShellOut compatible
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Fish Shell command-line completions for ChefDK
|
2
|
+
|
3
|
+
function __fish_chef_no_command --description 'Test if chef has yet to be given the main command'
|
4
|
+
set -l cmd (commandline -opc)
|
5
|
+
test (count $cmd) -eq 1
|
6
|
+
end
|
7
|
+
|
8
|
+
<% commands.each do |command, desc| -%>
|
9
|
+
complete -c chef -f -n '__fish_chef_no_command' -a <%= command %> -d "<%= desc %>"
|
10
|
+
<% end -%>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
function _chef() {
|
2
|
+
|
3
|
+
local -a _1st_arguments
|
4
|
+
_1st_arguments=(
|
5
|
+
<% commands.each do |command, desc| -%>
|
6
|
+
'<%=command%>:<%=desc%>'
|
7
|
+
<% end -%>
|
8
|
+
)
|
9
|
+
|
10
|
+
_arguments \
|
11
|
+
'(-v --version)'{-v,--version}'[version information]' \
|
12
|
+
'*:: :->subcmds' && return 0
|
13
|
+
|
14
|
+
if (( CURRENT == 1 )); then
|
15
|
+
_describe -t commands "chef subcommand" _1st_arguments
|
16
|
+
return
|
17
|
+
fi
|
18
|
+
}
|
19
|
+
|
20
|
+
compdef _chef chef
|
21
|
+
|
data/lib/chef-dk/exceptions.rb
CHANGED
@@ -68,6 +68,18 @@ module ChefDK
|
|
68
68
|
class InvalidPolicyfileFilename < StandardError
|
69
69
|
end
|
70
70
|
|
71
|
+
class InvalidUndoRecord < StandardError
|
72
|
+
end
|
73
|
+
|
74
|
+
class CantUndo < StandardError
|
75
|
+
end
|
76
|
+
|
77
|
+
class UndoRecordNotFound < StandardError
|
78
|
+
end
|
79
|
+
|
80
|
+
class MultipleErrors < StandardError
|
81
|
+
end
|
82
|
+
|
71
83
|
class BUG < RuntimeError
|
72
84
|
end
|
73
85
|
|
data/lib/chef-dk/helpers.rb
CHANGED
@@ -86,6 +86,18 @@ module ChefDK
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
+
# Returns the directory that contains our main symlinks.
|
90
|
+
# On Mac we place all of our symlinks under /usr/local/bin on other
|
91
|
+
# platforms they are under /usr/bin
|
92
|
+
def usr_bin_prefix
|
93
|
+
@usr_bin_prefix ||= os_x? ? "/usr/local/bin" : "/usr/bin"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns the full path to the given command under usr_bin_prefix
|
97
|
+
def usr_bin_path(command)
|
98
|
+
File.join(usr_bin_prefix, command)
|
99
|
+
end
|
100
|
+
|
89
101
|
private
|
90
102
|
|
91
103
|
def omnibus_expand_path(*paths)
|
@@ -138,5 +150,10 @@ module ChefDK
|
|
138
150
|
self.instance_variable_set(ivar, nil)
|
139
151
|
end
|
140
152
|
end
|
153
|
+
|
154
|
+
# Returns true if we are on Mac OS X. Otherwise false
|
155
|
+
def os_x?
|
156
|
+
!!(RUBY_PLATFORM =~ /darwin/)
|
157
|
+
end
|
141
158
|
end
|
142
159
|
end
|
@@ -67,6 +67,8 @@ module ChefDK
|
|
67
67
|
|
68
68
|
class PolicyGroupRevIDMap
|
69
69
|
|
70
|
+
include Enumerable
|
71
|
+
|
70
72
|
attr_reader :policy_name
|
71
73
|
attr_reader :revision_ids_by_group
|
72
74
|
|
@@ -215,7 +217,7 @@ module ChefDK
|
|
215
217
|
|
216
218
|
def set_policies_by_group_from_api(policy_group_data)
|
217
219
|
@policies_by_group = policy_group_data.inject({}) do |map, (policy_group, policy_info)|
|
218
|
-
map[policy_group] = policy_info["policies"].inject({}) do |rev_map, (policy_name, rev_info)|
|
220
|
+
map[policy_group] = (policy_info["policies"] || []).inject({}) do |rev_map, (policy_name, rev_info)|
|
219
221
|
rev_map[policy_name] = rev_info["revision_id"]; rev_map
|
220
222
|
end
|
221
223
|
|
@@ -0,0 +1,142 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2015 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'chef-dk/exceptions'
|
19
|
+
|
20
|
+
module ChefDK
|
21
|
+
module Policyfile
|
22
|
+
|
23
|
+
class UndoRecord
|
24
|
+
|
25
|
+
PolicyGroupRestoreData = Struct.new(:policy_name, :policy_group, :data) do
|
26
|
+
|
27
|
+
def load(data)
|
28
|
+
self.policy_name = data["policy_name"]
|
29
|
+
self.policy_group = data["policy_group"]
|
30
|
+
self.data = data["data"]
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def for_serialization
|
35
|
+
{
|
36
|
+
"policy_name" => policy_name,
|
37
|
+
"policy_group" => policy_group,
|
38
|
+
"data" => data
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :policy_groups
|
45
|
+
|
46
|
+
attr_reader :policy_revisions
|
47
|
+
|
48
|
+
attr_accessor :description
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
reset!
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
other.kind_of?(UndoRecord) &&
|
56
|
+
other.policy_groups == policy_groups &&
|
57
|
+
other.policy_revisions == policy_revisions
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_policy_group(name)
|
61
|
+
@policy_groups << name
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_policy_revision(policy_name, policy_group, data)
|
65
|
+
@policy_revisions << PolicyGroupRestoreData.new(policy_name, policy_group, data)
|
66
|
+
end
|
67
|
+
|
68
|
+
def load(data)
|
69
|
+
reset!
|
70
|
+
|
71
|
+
unless data.kind_of?(Hash)
|
72
|
+
raise InvalidUndoRecord, "Undo data is incorrectly formatted. Must be a Hash, got '#{data}'."
|
73
|
+
end
|
74
|
+
missing_fields = %w[ format_version description backup_data ].select { |key| !data.key?(key) }
|
75
|
+
unless missing_fields.empty?
|
76
|
+
raise InvalidUndoRecord, "Undo data is missing mandatory field(s) #{missing_fields.join(', ')}. Undo data: '#{data}'"
|
77
|
+
end
|
78
|
+
|
79
|
+
@description = data["description"]
|
80
|
+
|
81
|
+
policy_data = data["backup_data"]
|
82
|
+
unless policy_data.kind_of?(Hash)
|
83
|
+
raise InvalidUndoRecord, "'backup_data' in the undo record is incorrectly formatted. Must be a Hash, got '#{policy_data}'"
|
84
|
+
end
|
85
|
+
missing_policy_data_fields = %w[ policy_groups policy_revisions ].select { |key| !policy_data.key?(key) }
|
86
|
+
unless missing_policy_data_fields.empty?
|
87
|
+
raise InvalidUndoRecord,
|
88
|
+
"'backup_data' in the undo record is missing mandatory field(s) #{missing_policy_data_fields.join(', ')}. Backup data: #{policy_data}"
|
89
|
+
end
|
90
|
+
|
91
|
+
policy_groups = policy_data["policy_groups"]
|
92
|
+
|
93
|
+
unless policy_groups.kind_of?(Array)
|
94
|
+
raise InvalidUndoRecord,
|
95
|
+
"'policy_groups' data in the undo record is incorrectly formatted. Must be an Array, got '#{policy_groups}'"
|
96
|
+
end
|
97
|
+
|
98
|
+
@policy_groups = policy_groups
|
99
|
+
|
100
|
+
policy_revisions = policy_data["policy_revisions"]
|
101
|
+
unless policy_revisions.kind_of?(Array)
|
102
|
+
raise InvalidUndoRecord,
|
103
|
+
"'policy_revisions' data in the undo record is incorrectly formatted. Must be an Array, got '#{policy_revisions}'"
|
104
|
+
end
|
105
|
+
|
106
|
+
policy_revisions.each do |revision|
|
107
|
+
unless revision.kind_of?(Hash)
|
108
|
+
raise InvalidUndoRecord,
|
109
|
+
"Invalid item in 'policy_revisions' in the undo record. Must be a Hash, got '#{revision}'"
|
110
|
+
end
|
111
|
+
|
112
|
+
@policy_revisions << PolicyGroupRestoreData.new.load(revision)
|
113
|
+
end
|
114
|
+
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
def for_serialization
|
119
|
+
{
|
120
|
+
"format_version" => 1,
|
121
|
+
"description" => description,
|
122
|
+
"backup_data" => {
|
123
|
+
"policy_groups" => policy_groups,
|
124
|
+
"policy_revisions" => policy_revisions.map(&:for_serialization)
|
125
|
+
}
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def reset!
|
132
|
+
@description = ""
|
133
|
+
@policy_groups = []
|
134
|
+
@policy_revisions = []
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
|
@@ -0,0 +1,130 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2015 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'fileutils'
|
19
|
+
|
20
|
+
require 'ffi_yajl'
|
21
|
+
|
22
|
+
require 'chef-dk/helpers'
|
23
|
+
require 'chef-dk/policyfile/undo_record'
|
24
|
+
|
25
|
+
module ChefDK
|
26
|
+
module Policyfile
|
27
|
+
|
28
|
+
class UndoStack
|
29
|
+
|
30
|
+
MAX_SIZE = 10
|
31
|
+
|
32
|
+
include Helpers
|
33
|
+
|
34
|
+
def undo_dir
|
35
|
+
File.join(Helpers.chefdk_home, "undo")
|
36
|
+
end
|
37
|
+
|
38
|
+
def size
|
39
|
+
undo_record_files.size
|
40
|
+
end
|
41
|
+
|
42
|
+
def empty?
|
43
|
+
size == 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def has_id?(id)
|
47
|
+
File.exist?(undo_file_for(id))
|
48
|
+
end
|
49
|
+
|
50
|
+
def each_with_id
|
51
|
+
undo_record_files.each do |filename|
|
52
|
+
yield File.basename(filename), load_undo_record(filename)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def undo_records
|
57
|
+
undo_record_files.map { |f| load_undo_record(f) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def push(undo_record)
|
61
|
+
ensure_undo_dir_exists
|
62
|
+
|
63
|
+
record_id = Time.new.utc.strftime("%Y%m%d%H%M%S")
|
64
|
+
path = File.join(undo_dir, record_id)
|
65
|
+
|
66
|
+
with_file(path) do |f|
|
67
|
+
f.print(FFI_Yajl::Encoder.encode(undo_record.for_serialization, pretty: true))
|
68
|
+
end
|
69
|
+
|
70
|
+
records_to_delete = undo_record_files.size - MAX_SIZE
|
71
|
+
if records_to_delete > 0
|
72
|
+
undo_record_files.take(records_to_delete).each do |file|
|
73
|
+
File.unlink(file)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def pop
|
81
|
+
file_to_pop = undo_record_files.last
|
82
|
+
if file_to_pop.nil?
|
83
|
+
raise CantUndo, "No undo records exist in #{undo_dir}"
|
84
|
+
end
|
85
|
+
|
86
|
+
record = load_undo_record(file_to_pop)
|
87
|
+
# if this hits an exception, we skip unlink
|
88
|
+
yield record if block_given?
|
89
|
+
File.unlink(file_to_pop)
|
90
|
+
record
|
91
|
+
end
|
92
|
+
|
93
|
+
def delete(id)
|
94
|
+
undo_file = undo_file_for(id)
|
95
|
+
unless File.exist?(undo_file)
|
96
|
+
raise UndoRecordNotFound, "No undo record for id '#{id}' exists at #{undo_file}"
|
97
|
+
end
|
98
|
+
|
99
|
+
record = load_undo_record(undo_file)
|
100
|
+
yield record if block_given?
|
101
|
+
File.unlink(undo_file)
|
102
|
+
record
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def undo_file_for(id)
|
108
|
+
File.join(undo_dir, id)
|
109
|
+
end
|
110
|
+
|
111
|
+
def load_undo_record(file)
|
112
|
+
data = FFI_Yajl::Parser.parse(IO.read(file))
|
113
|
+
UndoRecord.new.load(data)
|
114
|
+
end
|
115
|
+
|
116
|
+
def undo_record_files
|
117
|
+
Dir[File.join(undo_dir, '*')].sort
|
118
|
+
end
|
119
|
+
|
120
|
+
def ensure_undo_dir_exists
|
121
|
+
return false if File.directory?(undo_dir)
|
122
|
+
|
123
|
+
|
124
|
+
FileUtils.mkdir_p(undo_dir)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|