knife-ec-backup 3.0.9 → 3.0.11
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/lib/chef/knife/ec_error_handler.rb +48 -1
- data/lib/chef/knife/ec_import.rb +32 -11
- data/lib/knife_ec_backup/version.rb +1 -1
- data/spec/chef/knife/ec_error_handler_spec.rb +43 -0
- data/spec/chef/knife/ec_import_spec.rb +12 -1
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d288abb4c07544382487a1ea61612a485fa0651e5ebb20a39db277278b900237
|
|
4
|
+
data.tar.gz: 21cb4ac04036f42faff0a5e93ddc3af4bed52efd8905ddf58840103d415747d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 022deb1b7431715bfdb5b21cf30da21bded07ae68c1cd4bbeaefeaa8b01a68cdd07bfdc335839b46f69fce64d926063e5cbfeb98fba27330319c20bc6cc591ea
|
|
7
|
+
data.tar.gz: c87aef9ad1f2e606d1b119ebdaa8f9ff0838a202deacf42c19f09c1849cb3657fb6650630d380cc98a70e706e7083a628ad049bd8c965dad445fdfd3938ee5c9
|
|
@@ -28,6 +28,8 @@ class Chef
|
|
|
28
28
|
def initialize(working_dir, process)
|
|
29
29
|
@err_dir = "#{working_dir}/errors"
|
|
30
30
|
FileUtils.mkdir_p(@err_dir)
|
|
31
|
+
@error_count = 0
|
|
32
|
+
@process = process
|
|
31
33
|
|
|
32
34
|
# Create an specific error file name depending
|
|
33
35
|
# of where the process comes from.
|
|
@@ -40,7 +42,51 @@ class Chef
|
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
# exit handler
|
|
43
|
-
at_exit
|
|
45
|
+
at_exit do
|
|
46
|
+
display(@err_file)
|
|
47
|
+
override_exit_status
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Forces a consistent, non-zero exit status when errors were recorded so
|
|
52
|
+
# that a run with a high-verbosity flag (e.g. -VVV) and one without it
|
|
53
|
+
# report the same status. Only applies to `knife ec import`; other
|
|
54
|
+
# commands keep their original exit status. Skipped under RSpec so the
|
|
55
|
+
# test suite's own exit status is not altered.
|
|
56
|
+
def override_exit_status
|
|
57
|
+
return if defined?(RSpec)
|
|
58
|
+
return unless defined?(Chef::Knife::EcImport) && @process == Chef::Knife::EcImport
|
|
59
|
+
|
|
60
|
+
status = maybe_exit_status($!)
|
|
61
|
+
exit(status) unless status.nil?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Decides the process exit status so it is identical regardless of
|
|
65
|
+
# verbosity. Under -VVV knife re-raises and Ruby exits 1; without it knife
|
|
66
|
+
# rescues and exits with a different code (e.g. 100). Both are error cases
|
|
67
|
+
# and must agree.
|
|
68
|
+
#
|
|
69
|
+
# +error+ is the exception propagating at exit (Ruby's $!). Returns the
|
|
70
|
+
# status to force, or nil to leave the current status untouched.
|
|
71
|
+
def maybe_exit_status(error = $!)
|
|
72
|
+
case error
|
|
73
|
+
when SystemExit
|
|
74
|
+
# Normalize any non-zero exit (e.g. knife's exit 100) to 1.
|
|
75
|
+
return 1 unless error.success?
|
|
76
|
+
has_errors? ? 1 : nil
|
|
77
|
+
when nil
|
|
78
|
+
# Normal termination: exit 1 only if errors were logged.
|
|
79
|
+
has_errors? ? 1 : nil
|
|
80
|
+
else
|
|
81
|
+
# A raw exception is propagating (typically under -VVV); Ruby already
|
|
82
|
+
# exits 1 and prints the backtrace, so leave it untouched.
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns true when any error has been recorded.
|
|
88
|
+
def has_errors?
|
|
89
|
+
@error_count > 0
|
|
44
90
|
end
|
|
45
91
|
|
|
46
92
|
# Add an exception to the error file.
|
|
@@ -61,6 +107,7 @@ class Chef
|
|
|
61
107
|
# The advantages of this schema is the ability to retry the backup or
|
|
62
108
|
# restore and pick up where we left.
|
|
63
109
|
def add(ex)
|
|
110
|
+
@error_count += 1
|
|
64
111
|
msg = {
|
|
65
112
|
timestamp: Time.now,
|
|
66
113
|
message: ex.message,
|
data/lib/chef/knife/ec_import.rb
CHANGED
|
@@ -31,6 +31,22 @@ class Chef
|
|
|
31
31
|
ADMIN_GROUP_FILES = ['billing-admins.json', 'public_key_read_access.json'].freeze
|
|
32
32
|
NOT_FOUND_STATUS = '404'
|
|
33
33
|
|
|
34
|
+
# Network/server errors that should be logged and skipped so the rest of
|
|
35
|
+
# the import can proceed instead of aborting. Covers 5xx "Internal Server
|
|
36
|
+
# Error" responses and dropped connections such as "Connection reset by
|
|
37
|
+
# peer".
|
|
38
|
+
RECOVERABLE_NETWORK_ERRORS = [
|
|
39
|
+
Net::HTTPClientException, # 4xx
|
|
40
|
+
Net::HTTPFatalError, # 5xx, e.g. Internal Server Error
|
|
41
|
+
Errno::ECONNRESET, # Connection reset by peer
|
|
42
|
+
Errno::ECONNREFUSED,
|
|
43
|
+
Errno::ETIMEDOUT,
|
|
44
|
+
].freeze
|
|
45
|
+
|
|
46
|
+
# Substring of the RuntimeError some Ruby builds raise while computing
|
|
47
|
+
# cookbook checksums. It must be logged and skipped, not abort the import.
|
|
48
|
+
DIGEST_INHERITANCE_ERROR = 'Digest::Base cannot be directly inherited'.freeze
|
|
49
|
+
|
|
34
50
|
option :tenant_id_header,
|
|
35
51
|
:long => '--tenant-id TENANT_ID',
|
|
36
52
|
:description => 'Tenant identifier added as X-Tenant-Id header for import requests'
|
|
@@ -146,13 +162,11 @@ class Chef
|
|
|
146
162
|
def organization_exists?(orgname)
|
|
147
163
|
rest.get(org_url(orgname))
|
|
148
164
|
true
|
|
149
|
-
rescue
|
|
150
|
-
if ex.response.code == NOT_FOUND_STATUS
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
false
|
|
155
|
-
end
|
|
165
|
+
rescue *RECOVERABLE_NETWORK_ERRORS => ex
|
|
166
|
+
return false if ex.is_a?(Net::HTTPClientException) && ex.response.code == NOT_FOUND_STATUS
|
|
167
|
+
ui.error "Failed to verify organization #{orgname}: #{ex.message}"
|
|
168
|
+
knife_ec_error_handler.add(ex)
|
|
169
|
+
false
|
|
156
170
|
end
|
|
157
171
|
|
|
158
172
|
def restore_user_acls
|
|
@@ -285,13 +299,19 @@ class Chef
|
|
|
285
299
|
chef_fs_config.chef_fs, nil,
|
|
286
300
|
config, ui,
|
|
287
301
|
proc { |entry| chef_fs_config.format_path(entry) })
|
|
288
|
-
rescue
|
|
302
|
+
rescue *RECOVERABLE_NETWORK_ERRORS,
|
|
289
303
|
Chef::ChefFS::FileSystem::NotFoundError,
|
|
290
304
|
Chef::ChefFS::FileSystem::OperationFailedError,
|
|
291
305
|
Chef::Exceptions::JSON::ParseError,
|
|
292
306
|
JSON::ParserError => ex
|
|
293
307
|
ui.error "#{pattern_str} failed to copy: #{ex.message}"
|
|
294
308
|
knife_ec_error_handler.add(ex)
|
|
309
|
+
rescue RuntimeError => ex
|
|
310
|
+
# Only swallow the known Digest::Base inheritance error so the import
|
|
311
|
+
# continues; re-raise anything else.
|
|
312
|
+
raise unless ex.message.to_s.include?(DIGEST_INHERITANCE_ERROR)
|
|
313
|
+
ui.error "#{pattern_str} failed to copy: #{ex.message}"
|
|
314
|
+
knife_ec_error_handler.add(ex)
|
|
295
315
|
end
|
|
296
316
|
|
|
297
317
|
def sort_groups_for_upload(groups)
|
|
@@ -399,8 +419,8 @@ class Chef
|
|
|
399
419
|
|
|
400
420
|
rest.put(cookbook_url(org_name, cookbook_name, version, 'freeze=true'),
|
|
401
421
|
manifest.tap { |h| h[FROZEN_STATUS_KEY] = true })
|
|
402
|
-
rescue
|
|
403
|
-
ui.
|
|
422
|
+
rescue *RECOVERABLE_NETWORK_ERRORS => ex
|
|
423
|
+
ui.error "Failed to freeze cookbook #{cookbook_name} #{version}: #{ex.message}"
|
|
404
424
|
knife_ec_error_handler.add(ex)
|
|
405
425
|
end
|
|
406
426
|
|
|
@@ -414,7 +434,8 @@ class Chef
|
|
|
414
434
|
rest.put("#{url}/#{permission}", { permission => acls[permission] })
|
|
415
435
|
end
|
|
416
436
|
end
|
|
417
|
-
rescue
|
|
437
|
+
rescue *RECOVERABLE_NETWORK_ERRORS => ex
|
|
438
|
+
ui.error "Failed to update ACL #{url}: #{ex.message}"
|
|
418
439
|
knife_ec_error_handler.add(ex)
|
|
419
440
|
end
|
|
420
441
|
end
|
|
@@ -115,4 +115,47 @@ EOF
|
|
|
115
115
|
@knife_ec_error_handler.add(cheffs_filesystem_exception('OperationFailedError'))
|
|
116
116
|
expect(File.read(err_file)).to match mock_content.strip
|
|
117
117
|
end
|
|
118
|
+
|
|
119
|
+
describe "#has_errors?" do
|
|
120
|
+
it "is false before any error is added" do
|
|
121
|
+
expect(@knife_ec_error_handler.has_errors?).to be false
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "is true after an error is added" do
|
|
125
|
+
@knife_ec_error_handler.add(net_exception(500))
|
|
126
|
+
expect(@knife_ec_error_handler.has_errors?).to be true
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe "#maybe_exit_status" do
|
|
131
|
+
context "when no error was recorded" do
|
|
132
|
+
it "leaves a clean exit untouched" do
|
|
133
|
+
expect(@knife_ec_error_handler.maybe_exit_status(nil)).to be_nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "normalizes knife's non-zero exit (e.g. 100) to 1" do
|
|
137
|
+
expect(@knife_ec_error_handler.maybe_exit_status(SystemExit.new(100))).to eq 1
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "leaves a clean SystemExit untouched" do
|
|
141
|
+
expect(@knife_ec_error_handler.maybe_exit_status(SystemExit.new(0))).to be_nil
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "leaves a raw exception (e.g. under -VVV) untouched" do
|
|
145
|
+
expect(@knife_ec_error_handler.maybe_exit_status(RuntimeError.new('boom'))).to be_nil
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context "when an error was recorded" do
|
|
150
|
+
before(:each) { @knife_ec_error_handler.add(net_exception(500)) }
|
|
151
|
+
|
|
152
|
+
it "forces exit 1 on an otherwise clean exit" do
|
|
153
|
+
expect(@knife_ec_error_handler.maybe_exit_status(nil)).to eq 1
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "normalizes knife's exit 100 to 1" do
|
|
157
|
+
expect(@knife_ec_error_handler.maybe_exit_status(SystemExit.new(100))).to eq 1
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
118
161
|
end
|
|
@@ -463,6 +463,7 @@ describe Chef::Knife::EcImport do
|
|
|
463
463
|
it HANDLES_ERRORS do
|
|
464
464
|
allow(@rest).to receive(:get).and_raise(import_net_exception(500))
|
|
465
465
|
expect(@error_handler).to receive(:add)
|
|
466
|
+
expect(@knife.ui).to receive(:error).with(/Failed to update ACL/)
|
|
466
467
|
@knife.put_acl(@rest, "url", {})
|
|
467
468
|
end
|
|
468
469
|
end
|
|
@@ -562,7 +563,7 @@ describe Chef::Knife::EcImport do
|
|
|
562
563
|
it HANDLES_ERRORS do
|
|
563
564
|
allow(@rest).to receive(:get).and_raise(import_net_exception(500))
|
|
564
565
|
expect(@error_handler).to receive(:add)
|
|
565
|
-
expect(@knife.ui).to receive(:
|
|
566
|
+
expect(@knife.ui).to receive(:error).with(/Failed to freeze cookbook/)
|
|
566
567
|
@knife.freeze_cookbook("mycb", "1.0.0", "foo")
|
|
567
568
|
end
|
|
568
569
|
end
|
|
@@ -598,6 +599,16 @@ describe Chef::Knife::EcImport do
|
|
|
598
599
|
expect(@error_handler).to receive(:add)
|
|
599
600
|
@knife.chef_fs_copy_pattern("/foo", chef_fs_config)
|
|
600
601
|
end
|
|
602
|
+
|
|
603
|
+
[Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ETIMEDOUT].each do |socket_error|
|
|
604
|
+
it "recovers from #{socket_error} socket failures" do
|
|
605
|
+
chef_fs_config = double("config", :local_fs => double, :chef_fs => double)
|
|
606
|
+
allow(Chef::ChefFS::FileSystem).to receive(:copy_to).and_raise(socket_error)
|
|
607
|
+
expect(@knife.ui).to receive(:error).with(/\/foo failed to copy:/)
|
|
608
|
+
expect(@error_handler).to receive(:add)
|
|
609
|
+
@knife.chef_fs_copy_pattern("/foo", chef_fs_config)
|
|
610
|
+
end
|
|
611
|
+
end
|
|
601
612
|
end
|
|
602
613
|
|
|
603
614
|
describe "#sort_groups_for_upload" do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: knife-ec-backup
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Keiser
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sequel
|
|
@@ -119,7 +119,7 @@ homepage: https://www.chef.io
|
|
|
119
119
|
licenses:
|
|
120
120
|
- Apache-2.0
|
|
121
121
|
metadata: {}
|
|
122
|
-
post_install_message:
|
|
122
|
+
post_install_message:
|
|
123
123
|
rdoc_options: []
|
|
124
124
|
require_paths:
|
|
125
125
|
- lib
|
|
@@ -135,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
135
135
|
version: '0'
|
|
136
136
|
requirements: []
|
|
137
137
|
rubygems_version: 3.3.27
|
|
138
|
-
signing_key:
|
|
138
|
+
signing_key:
|
|
139
139
|
specification_version: 4
|
|
140
140
|
summary: Backup and Restore of Enterprise Chef
|
|
141
141
|
test_files: []
|