knife-ec-backup 2.1.0 → 2.2.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/lib/chef/knife/ec_backup.rb +19 -1
- data/lib/chef/knife/ec_base.rb +7 -0
- data/lib/chef/knife/ec_error_handler.rb +114 -0
- data/lib/chef/knife/ec_restore.rb +34 -19
- data/lib/chef/server.rb +2 -1
- data/lib/knife_ec_backup/version.rb +1 -1
- data/spec/chef/knife/ec_backup_spec.rb +85 -6
- data/spec/chef/knife/ec_error_handler_spec.rb +88 -0
- data/spec/chef/knife/ec_key_base_spec.rb +5 -4
- data/spec/chef/knife/ec_restore_spec.rb +14 -1
- data/spec/chef/server_spec.rb +3 -3
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 090c5b339d237877732a3d620eb92a4466addb75
|
4
|
+
data.tar.gz: 76cbbc8bb669fd36a722515b618838d3a100603b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d074dfc01e8be51da10c38831d02aeff7b3aa8cd672c93540f9e53cbc161bc4c8ba5b97be95ef5a978d131aa0f5b02883a77243f7ef34bc5e77df440eab2b7ba
|
7
|
+
data.tar.gz: b092806e811fa6b550b145d42c2ac55ab543281eafaf46c53f68118d1f8e498e2fd4196af48252d7a40105f7947e13638cca334a29dd56457d27501094aaa1fa
|
data/lib/chef/knife/ec_backup.rb
CHANGED
@@ -77,13 +77,21 @@ class Chef
|
|
77
77
|
remote_users.each_pair do |name, url|
|
78
78
|
yield name, url
|
79
79
|
end
|
80
|
+
rescue Net::HTTPServerException => ex
|
81
|
+
knife_ec_error_handler.add(ex)
|
80
82
|
end
|
81
83
|
|
82
84
|
def for_each_organization
|
83
85
|
rest.get('/organizations').each_pair do |name, url|
|
84
86
|
next unless (config[:org].nil? || config[:org] == name)
|
85
87
|
ui.msg "Downloading organization object for #{name} from #{url}"
|
86
|
-
|
88
|
+
begin
|
89
|
+
org = rest.get(url)
|
90
|
+
rescue Net::HTTPServerException => ex
|
91
|
+
ui.error "Failed to find organization '#{name}'."
|
92
|
+
knife_ec_error_handler.add(ex)
|
93
|
+
next
|
94
|
+
end
|
87
95
|
# Enterprise Chef 11 and below uses a pool of precreated
|
88
96
|
# organizations to account for slow organization creation
|
89
97
|
# using CouchDB. Thus, on server versions < 12 we want to
|
@@ -102,12 +110,16 @@ class Chef
|
|
102
110
|
File.open("#{dest_dir}/users/#{username}.json", 'w') do |file|
|
103
111
|
file.write(Chef::JSONCompat.to_json_pretty(rest.get(url)))
|
104
112
|
end
|
113
|
+
rescue Net::HTTPServerException => ex
|
114
|
+
knife_ec_error_handler.add(ex)
|
105
115
|
end
|
106
116
|
|
107
117
|
def download_user_acl(username)
|
108
118
|
File.open("#{dest_dir}/user_acls/#{username}.json", 'w') do |file|
|
109
119
|
file.write(Chef::JSONCompat.to_json_pretty(user_acl_rest.get("users/#{username}/_acl")))
|
110
120
|
end
|
121
|
+
rescue Net::HTTPServerException => ex
|
122
|
+
knife_ec_error_handler.add(ex)
|
111
123
|
end
|
112
124
|
|
113
125
|
def export_from_sql
|
@@ -137,6 +149,8 @@ class Chef
|
|
137
149
|
File.open("#{dest_dir}/organizations/#{name}/members.json", 'w') do |file|
|
138
150
|
file.write(Chef::JSONCompat.to_json_pretty(rest.get("/organizations/#{name}/users")))
|
139
151
|
end
|
152
|
+
rescue Net::HTTPServerException => ex
|
153
|
+
knife_ec_error_handler.add(ex)
|
140
154
|
end
|
141
155
|
|
142
156
|
def download_org_invitations(name)
|
@@ -144,6 +158,8 @@ class Chef
|
|
144
158
|
File.open("#{dest_dir}/organizations/#{name}/invitations.json", 'w') do |file|
|
145
159
|
file.write(Chef::JSONCompat.to_json_pretty(rest.get("/organizations/#{name}/association_requests")))
|
146
160
|
end
|
161
|
+
rescue Net::HTTPServerException => ex
|
162
|
+
knife_ec_error_handler.add(ex)
|
147
163
|
end
|
148
164
|
|
149
165
|
def ensure_dir(dir)
|
@@ -225,6 +241,8 @@ class Chef
|
|
225
241
|
chef_fs_config.local_fs, nil,
|
226
242
|
config, ui,
|
227
243
|
proc { |entry| chef_fs_config.format_path(entry) })
|
244
|
+
rescue Net::HTTPServerException => ex
|
245
|
+
knife_ec_error_handler.add(ex)
|
228
246
|
end
|
229
247
|
end
|
230
248
|
end
|
data/lib/chef/knife/ec_base.rb
CHANGED
@@ -19,6 +19,7 @@
|
|
19
19
|
require 'chef/knife'
|
20
20
|
require 'chef/server_api'
|
21
21
|
require 'veil'
|
22
|
+
require 'chef/knife/ec_error_handler'
|
22
23
|
|
23
24
|
class Chef
|
24
25
|
class Knife
|
@@ -115,6 +116,8 @@ class Chef
|
|
115
116
|
else
|
116
117
|
admin_users[0]
|
117
118
|
end
|
119
|
+
rescue Net::HTTPServerException => ex
|
120
|
+
knife_ec_error_handler.add(ex)
|
118
121
|
end
|
119
122
|
end
|
120
123
|
|
@@ -150,6 +153,10 @@ class Chef
|
|
150
153
|
raise Chef::Knife::EcBase::UnImplemented
|
151
154
|
end
|
152
155
|
|
156
|
+
def knife_ec_error_handler
|
157
|
+
@knife_ec_error_handler ||= Chef::Knife::EcErrorHandler.new(dest_dir, self.class)
|
158
|
+
end
|
159
|
+
|
153
160
|
def user_acl_rest
|
154
161
|
@user_acl_rest ||= if config[:skip_version]
|
155
162
|
rest
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# Author:: Jeremy Miller (<jmiller@chef.io>)
|
2
|
+
# Copyright:: Copyright (c) 2017 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
|
+
class Chef
|
18
|
+
class Knife
|
19
|
+
# This class handles the errors that we might encounter on a
|
20
|
+
# backup or restore, it stors the errors inside a specific file
|
21
|
+
# inside the working directory.
|
22
|
+
class EcErrorHandler
|
23
|
+
|
24
|
+
attr_reader :err_file
|
25
|
+
|
26
|
+
# Creates a new instance of the EcErrorHandler to start
|
27
|
+
# adding errors during a backup or restore.
|
28
|
+
def initialize(working_dir, process)
|
29
|
+
@err_dir = "#{working_dir}/errors"
|
30
|
+
FileUtils.mkdir_p(@err_dir)
|
31
|
+
|
32
|
+
# Create an specific error file name depending
|
33
|
+
# of where the process comes from.
|
34
|
+
@err_file = if process == Chef::Knife::EcRestore
|
35
|
+
File.join(@err_dir, "restore-#{Time.now.iso8601}.json")
|
36
|
+
elsif process == Chef::Knife::EcBackup
|
37
|
+
File.join(@err_dir, "backup-#{Time.now.iso8601}.json")
|
38
|
+
else
|
39
|
+
File.join(@err_dir, "other-#{Time.now.iso8601}.json")
|
40
|
+
end
|
41
|
+
|
42
|
+
# exit handler
|
43
|
+
at_exit { display(@err_file) }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Add an exception to the error file.
|
47
|
+
#
|
48
|
+
# For now we are writing all the errors to a single file, but
|
49
|
+
# in the future we would like to be able to generate a full path
|
50
|
+
# just as we do when we are backing up the Server, just that putting
|
51
|
+
# the file inside `{work_dir}/errors/{path}`
|
52
|
+
#
|
53
|
+
# Example:
|
54
|
+
# A failed user
|
55
|
+
# => {work_dir}/errors/users/afiune.json
|
56
|
+
# A failed cookbook
|
57
|
+
# => {work_dir}/errors/cookbooks/burger.json
|
58
|
+
# A failed environment
|
59
|
+
# => {work_dir}/errors/environment/dev.json
|
60
|
+
#
|
61
|
+
# The advantages of this schema is the ability to retry the backup or
|
62
|
+
# restore and pick up where we left.
|
63
|
+
def add(ex)
|
64
|
+
msg = {
|
65
|
+
timestamp: Time.now,
|
66
|
+
message: ex.message,
|
67
|
+
backtrace: ex.backtrace,
|
68
|
+
exception: ex.class
|
69
|
+
}
|
70
|
+
|
71
|
+
if ex.respond_to?(:chef_rest_request=) && ex.chef_rest_request
|
72
|
+
msg.merge!( {
|
73
|
+
req_path: ex.chef_rest_request.path,
|
74
|
+
req_method: ex.chef_rest_request.method
|
75
|
+
})
|
76
|
+
end
|
77
|
+
|
78
|
+
lock_file(@err_file, 'a') do |f|
|
79
|
+
f.write(Chef::JSONCompat.to_json_pretty(msg))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Why lock the error file?
|
84
|
+
#
|
85
|
+
# Well because ec-backup has a concurrency options that
|
86
|
+
# will allow you to backup and restore things in parallel,
|
87
|
+
# therefor we need to ensure that only one process is
|
88
|
+
# writing to the error file.
|
89
|
+
def lock_file(file_name, mode)
|
90
|
+
File.open(file_name, mode) do |f|
|
91
|
+
begin
|
92
|
+
f.flock ::File::LOCK_EX
|
93
|
+
yield f
|
94
|
+
ensure
|
95
|
+
f.flock ::File::LOCK_UN
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def display(file_name = @err_file)
|
101
|
+
# Print summary report only if error file exist
|
102
|
+
return unless File.exist?(file_name)
|
103
|
+
|
104
|
+
puts "\nError Summary Report"
|
105
|
+
lock_file(file_name, 'r') do |f|
|
106
|
+
f.each_line do |line|
|
107
|
+
puts line
|
108
|
+
end
|
109
|
+
end
|
110
|
+
puts "\nError(s) Summary file located at: '#{file_name}'"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -26,7 +26,7 @@ class Chef
|
|
26
26
|
require 'chef/chef_fs/file_pattern'
|
27
27
|
# Work around bug in chef_fs
|
28
28
|
require 'chef/chef_fs/command_line'
|
29
|
-
require 'chef/chef_fs/file_system/acl_entry'
|
29
|
+
require 'chef/chef_fs/file_system/chef_server/acl_entry'
|
30
30
|
require 'chef/chef_fs/data_handler/acl_data_handler'
|
31
31
|
require 'securerandom'
|
32
32
|
require 'chef/chef_fs/parallelizer'
|
@@ -63,11 +63,11 @@ class Chef
|
|
63
63
|
def create_organization(orgname)
|
64
64
|
org = JSONCompat.from_json(File.read("#{dest_dir}/organizations/#{orgname}/org.json"))
|
65
65
|
rest.post('organizations', org)
|
66
|
-
rescue Net::HTTPServerException =>
|
67
|
-
if
|
66
|
+
rescue Net::HTTPServerException => ex
|
67
|
+
if ex.response.code == "409"
|
68
68
|
rest.put("organizations/#{orgname}", org)
|
69
69
|
else
|
70
|
-
|
70
|
+
knife_ec_error_handler.add(ex)
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
@@ -76,9 +76,10 @@ class Chef
|
|
76
76
|
invitations.each do |invitation|
|
77
77
|
begin
|
78
78
|
rest.post("organizations/#{orgname}/association_requests", { 'user' => invitation['username'] })
|
79
|
-
rescue Net::HTTPServerException =>
|
80
|
-
if
|
79
|
+
rescue Net::HTTPServerException => ex
|
80
|
+
if ex.response.code != "409"
|
81
81
|
ui.error("Cannot create invitation #{invitation['id']}")
|
82
|
+
knife_ec_error_handler.add(ex)
|
82
83
|
end
|
83
84
|
end
|
84
85
|
end
|
@@ -92,10 +93,8 @@ class Chef
|
|
92
93
|
response = rest.post("organizations/#{orgname}/association_requests", { 'user' => username })
|
93
94
|
association_id = response["uri"].split("/").last
|
94
95
|
rest.put("users/#{username}/association_requests/#{association_id}", { 'response' => 'accept' })
|
95
|
-
rescue Net::HTTPServerException =>
|
96
|
-
if
|
97
|
-
raise
|
98
|
-
end
|
96
|
+
rescue Net::HTTPServerException => ex
|
97
|
+
knife_ec_error_handler.add(ex) if ex.response.code != "409"
|
99
98
|
end
|
100
99
|
end
|
101
100
|
end
|
@@ -137,12 +136,12 @@ class Chef
|
|
137
136
|
user_with_password = user.dup
|
138
137
|
user_with_password['password'] = SecureRandom.hex
|
139
138
|
rest.post('users', user_with_password)
|
140
|
-
rescue Net::HTTPServerException =>
|
141
|
-
if
|
139
|
+
rescue Net::HTTPServerException => ex
|
140
|
+
if ex.response.code == "409"
|
142
141
|
rest.put("users/#{name}", user)
|
143
|
-
|
144
|
-
raise
|
142
|
+
next
|
145
143
|
end
|
144
|
+
knife_ec_error_handler.add(ex)
|
146
145
|
end
|
147
146
|
end
|
148
147
|
purge_users_on_restore
|
@@ -245,10 +244,7 @@ class Chef
|
|
245
244
|
end
|
246
245
|
|
247
246
|
['/acls/groups/billing-admins.json', '/acls/groups/public_key_read_access.json'].each do |acl|
|
248
|
-
|
249
|
-
Chef::ChefFS::FileSystem.copy_to(pattern, chef_fs_config.local_fs,
|
250
|
-
chef_fs_config.chef_fs, nil, config, ui,
|
251
|
-
proc { |entry| chef_fs_config.format_path(entry)})
|
247
|
+
chef_fs_copy_pattern(acl, chef_fs_config)
|
252
248
|
end
|
253
249
|
|
254
250
|
Chef::Config.node_name = if config[:skip_version]
|
@@ -276,7 +272,7 @@ class Chef
|
|
276
272
|
# - groups must be uploaded twice to account for Chef Server versions that don't
|
277
273
|
# accept group members on POST
|
278
274
|
(top_level_paths + group_paths*2 + group_acl_paths + acl_paths).each do |path|
|
279
|
-
|
275
|
+
chef_fs_copy_pattern(path, chef_fs_config)
|
280
276
|
end
|
281
277
|
|
282
278
|
# restore clients to groups, using the pivotal user again
|
@@ -289,6 +285,23 @@ class Chef
|
|
289
285
|
end
|
290
286
|
end
|
291
287
|
|
288
|
+
# ChefFS copy pattern inside the EcRestore class will
|
289
|
+
# copy from the local_fs to the Chef Server.
|
290
|
+
#
|
291
|
+
# NOTE: Do not get confused, this is the other way around
|
292
|
+
# from how we implemented in EcBackup. Therefor we can't
|
293
|
+
# abstract it inside EcBase.
|
294
|
+
def chef_fs_copy_pattern(pattern_str, chef_fs_config)
|
295
|
+
ui.msg "Copying #{pattern_str}"
|
296
|
+
pattern = Chef::ChefFS::FilePattern.new(pattern_str)
|
297
|
+
Chef::ChefFS::FileSystem.copy_to(pattern, chef_fs_config.local_fs,
|
298
|
+
chef_fs_config.chef_fs, nil,
|
299
|
+
config, ui,
|
300
|
+
proc { |entry| chef_fs_config.format_path(entry) })
|
301
|
+
rescue Net::HTTPServerException => ex
|
302
|
+
knife_ec_error_handler.add(ex)
|
303
|
+
end
|
304
|
+
|
292
305
|
# Takes an array of group objects
|
293
306
|
# and topologically sorts them
|
294
307
|
def sort_groups_for_upload(groups)
|
@@ -347,6 +360,8 @@ class Chef
|
|
347
360
|
rest.put("#{url}/#{permission}", { permission => acls[permission] })
|
348
361
|
end
|
349
362
|
end
|
363
|
+
rescue Net::HTTPServerException => ex
|
364
|
+
knife_ec_error_handler.add(ex)
|
350
365
|
end
|
351
366
|
end
|
352
367
|
end
|
data/lib/chef/server.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'uri'
|
2
2
|
require 'openssl'
|
3
|
+
require 'chef/server_api'
|
3
4
|
|
4
5
|
class Chef
|
5
6
|
class Server
|
@@ -29,7 +30,7 @@ class Chef
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def direct_account_access?
|
32
|
-
Chef::
|
33
|
+
Chef::ServerAPI.new("http://127.0.0.1:9465").get("users")
|
33
34
|
true
|
34
35
|
rescue
|
35
36
|
false
|
@@ -3,6 +3,7 @@ require 'chef/knife/ec_backup'
|
|
3
3
|
require 'fakefs/spec_helpers'
|
4
4
|
|
5
5
|
describe Chef::Knife::EcBackup do
|
6
|
+
let(:dest_dir) { Dir.mktmpdir }
|
6
7
|
USER_RESPONSE = {
|
7
8
|
"foo" => "organizations/bar/users/foo",
|
8
9
|
"bar" => "organizations/bar/users/bar"
|
@@ -30,9 +31,10 @@ describe Chef::Knife::EcBackup do
|
|
30
31
|
before(:each) do
|
31
32
|
Chef::Knife::EcBackup.load_deps
|
32
33
|
@knife = Chef::Knife::EcBackup.new
|
33
|
-
@rest = double('Chef::
|
34
|
+
@rest = double('Chef::ServerAPI')
|
34
35
|
allow(@knife).to receive(:rest).and_return(@rest)
|
35
36
|
allow(@knife).to receive(:user_acl_rest).and_return(@rest)
|
37
|
+
allow_any_instance_of(Chef::Knife::EcBase).to receive(:dest_dir).and_return(dest_dir)
|
36
38
|
end
|
37
39
|
|
38
40
|
describe "#for_each_user" do
|
@@ -40,6 +42,18 @@ describe Chef::Knife::EcBackup do
|
|
40
42
|
allow(@rest).to receive(:get).with("/users").and_return(USER_RESPONSE)
|
41
43
|
expect{ |b| @knife.for_each_user(&b) }.to yield_successive_args(["foo", USER_RESPONSE["foo"]], ["bar", USER_RESPONSE["bar"]])
|
42
44
|
end
|
45
|
+
|
46
|
+
context "when there are HTTP failures" do
|
47
|
+
let(:ec_error_handler) { double("Chef::Knife::EcErrorHandler") }
|
48
|
+
|
49
|
+
it "adds exceptions to error handler" do
|
50
|
+
exception = net_exception(500)
|
51
|
+
allow(Chef::Knife::EcErrorHandler).to receive(:new).and_return(ec_error_handler)
|
52
|
+
allow(@rest).to receive(:get).with("/users").and_raise(exception)
|
53
|
+
expect(ec_error_handler).to receive(:add).at_least(1).with(exception)
|
54
|
+
@knife.for_each_user
|
55
|
+
end
|
56
|
+
end
|
43
57
|
end
|
44
58
|
|
45
59
|
describe "#for_each_organization" do
|
@@ -71,12 +85,39 @@ describe Chef::Knife::EcBackup do
|
|
71
85
|
expect{ |b| @knife.for_each_organization(&b) }.to yield_successive_args(org_response("bar"),
|
72
86
|
org_response("foo", true))
|
73
87
|
end
|
88
|
+
|
89
|
+
context "when there are HTTP failures" do
|
90
|
+
let(:ec_error_handler) { double("Chef::Knife::EcErrorHandler") }
|
91
|
+
|
92
|
+
before(:each) do
|
93
|
+
server = double('Chef::Server')
|
94
|
+
allow(Chef::Server).to receive(:new).and_return(server)
|
95
|
+
allow(Chef::Knife::EcErrorHandler).to receive(:new).and_return(ec_error_handler)
|
96
|
+
allow(server).to receive(:version).and_return(Gem::Version.new("12.0.0"))
|
97
|
+
end
|
98
|
+
|
99
|
+
it "adds exception and continues with the rest of the orgs" do
|
100
|
+
exception = net_exception(404)
|
101
|
+
allow(@rest).to receive(:get).with("organizations/foo").and_return(org_response("foo"))
|
102
|
+
allow(@rest).to receive(:get).with("organizations/bar").and_raise(exception)
|
103
|
+
expect(ec_error_handler).to receive(:add).at_least(1).with(exception)
|
104
|
+
expect{ |b| @knife.for_each_organization(&b) }.to yield_successive_args(org_response("foo"))
|
105
|
+
end
|
106
|
+
|
107
|
+
it "adds exceptions to error handler" do
|
108
|
+
allow(@rest).to receive(:get).with("organizations/foo").and_raise(net_exception(500))
|
109
|
+
allow(@rest).to receive(:get).with("organizations/bar").and_raise(net_exception(404))
|
110
|
+
expect(ec_error_handler).to receive(:add).at_least(2)
|
111
|
+
@knife.for_each_organization
|
112
|
+
end
|
113
|
+
end
|
74
114
|
end
|
75
115
|
|
76
116
|
describe "#download_user" do
|
77
117
|
include FakeFS::SpecHelpers
|
78
118
|
let (:username) { "foo" }
|
79
119
|
let (:url) { "users/foo" }
|
120
|
+
before(:each) { FileUtils.mkdir_p(File.join(dest_dir, "users")) }
|
80
121
|
|
81
122
|
it "downloads a named user from the api" do
|
82
123
|
expect(@rest).to receive(:get).with(url)
|
@@ -87,13 +128,14 @@ describe Chef::Knife::EcBackup do
|
|
87
128
|
user_response = {"username" => "foo"}
|
88
129
|
allow(@rest).to receive(:get).with(url).and_return(user_response)
|
89
130
|
@knife.download_user(username, url)
|
90
|
-
expect(JSON.parse(File.read("/users/foo.json"))).to eq(user_response)
|
131
|
+
expect(JSON.parse(File.read("#{dest_dir}/users/foo.json"))).to eq(user_response)
|
91
132
|
end
|
92
133
|
end
|
93
134
|
|
94
135
|
describe "#download_user_acl" do
|
95
136
|
include FakeFS::SpecHelpers
|
96
137
|
let (:username) {"foo"}
|
138
|
+
before(:each) { FileUtils.mkdir_p(File.join(dest_dir, "user_acls")) }
|
97
139
|
|
98
140
|
it "downloads a user acl from the API" do
|
99
141
|
expect(@rest).to receive(:get).with("users/#{username}/_acl")
|
@@ -104,7 +146,19 @@ describe Chef::Knife::EcBackup do
|
|
104
146
|
user_acl_response = {"create" => {}}
|
105
147
|
allow(@rest).to receive(:get).with("users/#{username}/_acl").and_return(user_acl_response)
|
106
148
|
@knife.download_user_acl(username)
|
107
|
-
expect(JSON.parse(File.read("/user_acls/foo.json"))).to eq(user_acl_response)
|
149
|
+
expect(JSON.parse(File.read("#{dest_dir}/user_acls/foo.json"))).to eq(user_acl_response)
|
150
|
+
end
|
151
|
+
|
152
|
+
context "when there are HTTP failures" do
|
153
|
+
let(:ec_error_handler) { double("Chef::Knife::EcErrorHandler") }
|
154
|
+
|
155
|
+
it "adds exceptions to error handler" do
|
156
|
+
exception = net_exception(500)
|
157
|
+
allow(Chef::Knife::EcErrorHandler).to receive(:new).and_return(ec_error_handler)
|
158
|
+
allow(@rest).to receive(:get).with("users/#{username}/_acl").and_raise(exception)
|
159
|
+
expect(ec_error_handler).to receive(:add).at_least(1).with(exception)
|
160
|
+
@knife.download_user_acl(username)
|
161
|
+
end
|
108
162
|
end
|
109
163
|
end
|
110
164
|
|
@@ -113,7 +167,7 @@ describe Chef::Knife::EcBackup do
|
|
113
167
|
it "writes the given object to disk" do
|
114
168
|
org_object = { "name" => "bob" }
|
115
169
|
@knife.write_org_object_to_disk(org_object)
|
116
|
-
expect(JSON.parse(File.read("/organizations/bob/org.json"))).to eq(org_object)
|
170
|
+
expect(JSON.parse(File.read("#{dest_dir}/organizations/bob/org.json"))).to eq(org_object)
|
117
171
|
end
|
118
172
|
end
|
119
173
|
|
@@ -137,8 +191,21 @@ describe Chef::Knife::EcBackup do
|
|
137
191
|
it "writes the org members to a JSON file" do
|
138
192
|
expect(@rest).to receive(:get).with("/organizations/bob/users").and_return(users)
|
139
193
|
@knife.download_org_members("bob")
|
140
|
-
expect(JSON.parse(File.read("/organizations/bob/members.json"))).to eq(users)
|
194
|
+
expect(JSON.parse(File.read("#{dest_dir}/organizations/bob/members.json"))).to eq(users)
|
141
195
|
end
|
196
|
+
|
197
|
+
context "when there are HTTP failures" do
|
198
|
+
let(:ec_error_handler) { double("Chef::Knife::EcErrorHandler") }
|
199
|
+
|
200
|
+
it "adds exceptions to error handler" do
|
201
|
+
exception = net_exception(500)
|
202
|
+
allow(Chef::Knife::EcErrorHandler).to receive(:new).and_return(ec_error_handler)
|
203
|
+
allow(@rest).to receive(:get).with("/organizations/bob/users").and_raise(exception)
|
204
|
+
expect(ec_error_handler).to receive(:add).at_least(1).with(exception)
|
205
|
+
@knife.download_org_members("bob")
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
142
209
|
end
|
143
210
|
|
144
211
|
describe "#download_org_inivitations" do
|
@@ -152,7 +219,19 @@ describe Chef::Knife::EcBackup do
|
|
152
219
|
it "writes the invitations to a JSON file" do
|
153
220
|
expect(@rest).to receive(:get).with("/organizations/bob/association_requests").and_return(invites)
|
154
221
|
@knife.download_org_invitations("bob")
|
155
|
-
expect(JSON.parse(File.read("/organizations/bob/invitations.json"))).to eq(invites)
|
222
|
+
expect(JSON.parse(File.read("#{dest_dir}/organizations/bob/invitations.json"))).to eq(invites)
|
223
|
+
end
|
224
|
+
|
225
|
+
context "when there are HTTP failures" do
|
226
|
+
let(:ec_error_handler) { double("Chef::Knife::EcErrorHandler") }
|
227
|
+
|
228
|
+
it "adds exceptions to error handler" do
|
229
|
+
exception = net_exception(500)
|
230
|
+
allow(Chef::Knife::EcErrorHandler).to receive(:new).and_return(ec_error_handler)
|
231
|
+
allow(@rest).to receive(:get).with("/organizations/bob/association_requests").and_raise(exception)
|
232
|
+
expect(ec_error_handler).to receive(:add).at_least(1).with(exception)
|
233
|
+
@knife.download_org_invitations("bob")
|
234
|
+
end
|
156
235
|
end
|
157
236
|
end
|
158
237
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
|
2
|
+
require 'chef/knife/ec_error_handler'
|
3
|
+
require 'chef/knife/ec_backup'
|
4
|
+
require 'chef/knife/ec_restore'
|
5
|
+
require 'fakefs/spec_helpers'
|
6
|
+
|
7
|
+
def net_exception(code)
|
8
|
+
s = double("status", :code => code.to_s)
|
9
|
+
Net::HTTPServerException.new("I'm not real!", s)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Chef::Knife::EcErrorHandler do
|
13
|
+
let(:dest_dir) { Dir.mktmpdir }
|
14
|
+
let(:err_dir) { File.join(dest_dir, "errors") }
|
15
|
+
|
16
|
+
before(:each) do
|
17
|
+
allow(Time).to receive(:now).and_return(Time.new(1988, 04, 17, 0, 0, 0, "+00:00")) #=> 1988-04-17 00:00:00 +0000
|
18
|
+
@knife_ec_error_handler = described_class.new(dest_dir, Class)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#initialize" do
|
22
|
+
it "creates an error directory" do
|
23
|
+
expect(FileUtils).to receive(:mkdir_p).with("#{dest_dir}/errors")
|
24
|
+
described_class.new(dest_dir, Class)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "sets up an err_file depending of the class that comes from" do
|
28
|
+
ec_backup = described_class.new(dest_dir, Chef::Knife::EcBackup)
|
29
|
+
expect(ec_backup.err_file).to match File.join(err_dir, "backup-1988-04-17T00:00:00+00:00.json")
|
30
|
+
ec_restore = described_class.new(dest_dir, Chef::Knife::EcRestore)
|
31
|
+
expect(ec_restore.err_file).to match File.join(err_dir, "restore-1988-04-17T00:00:00+00:00.json")
|
32
|
+
ec_other = described_class.new(dest_dir, Class)
|
33
|
+
expect(ec_other.err_file).to match File.join(err_dir, "other-1988-04-17T00:00:00+00:00.json")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "#display" do
|
38
|
+
@knife_ec_error_handler.add(net_exception(123))
|
39
|
+
expect { @knife_ec_error_handler.display }.to output(/
|
40
|
+
Error Summary Report
|
41
|
+
{
|
42
|
+
"timestamp": "1988-04-17 00:00:00 \+0000",
|
43
|
+
"message": "I'm not real!",
|
44
|
+
"backtrace": null,
|
45
|
+
"exception": "Net::HTTPServerException"
|
46
|
+
}
|
47
|
+
/).to_stdout
|
48
|
+
end
|
49
|
+
|
50
|
+
it "#add" do
|
51
|
+
mock_content = <<-EOF
|
52
|
+
{
|
53
|
+
"timestamp": "1988-04-17 00:00:00 +0000",
|
54
|
+
"message": "I'm not real!",
|
55
|
+
"backtrace": null,
|
56
|
+
"exception": "Net::HTTPServerException"
|
57
|
+
}{
|
58
|
+
"timestamp": "1988-04-17 00:00:00 +0000",
|
59
|
+
"message": "I'm not real!",
|
60
|
+
"backtrace": null,
|
61
|
+
"exception": "Net::HTTPServerException"
|
62
|
+
}{
|
63
|
+
"timestamp": "1988-04-17 00:00:00 +0000",
|
64
|
+
"message": "I'm not real!",
|
65
|
+
"backtrace": null,
|
66
|
+
"exception": "Net::HTTPServerException"
|
67
|
+
}{
|
68
|
+
"timestamp": "1988-04-17 00:00:00 +0000",
|
69
|
+
"message": "I'm not real!",
|
70
|
+
"backtrace": null,
|
71
|
+
"exception": "Net::HTTPServerException"
|
72
|
+
}
|
73
|
+
EOF
|
74
|
+
err_file = @knife_ec_error_handler.err_file
|
75
|
+
expect(Chef::JSONCompat).to receive(:to_json_pretty)
|
76
|
+
.at_least(4)
|
77
|
+
.and_call_original
|
78
|
+
expect(File).to receive(:open)
|
79
|
+
.at_least(4)
|
80
|
+
.with(err_file, "a")
|
81
|
+
.and_call_original
|
82
|
+
@knife_ec_error_handler.add(net_exception(500))
|
83
|
+
@knife_ec_error_handler.add(net_exception(409))
|
84
|
+
@knife_ec_error_handler.add(net_exception(404))
|
85
|
+
@knife_ec_error_handler.add(net_exception(123))
|
86
|
+
expect(File.read(err_file)).to match mock_content.strip
|
87
|
+
end
|
88
|
+
end
|
@@ -9,25 +9,26 @@ describe Chef::Knife::EcKeyBase do
|
|
9
9
|
let (:knife) { Chef::Knife::KeyBaseTester.new }
|
10
10
|
|
11
11
|
let(:running_server_postgresql_sql_config_json) {
|
12
|
-
'{"private_chef": { "opscode-erchef":{}, "postgresql": { "sql_user": "jiminy", "sql_password": "secret"} }}'
|
12
|
+
'{"private_chef": { "opscode-erchef":{}, "postgresql": { "sql_user": "jiminy", "sql_password": "secret"} }, "postgresql": { "sql_user": "jiminy", "sql_password": "secret"} }'
|
13
13
|
}
|
14
14
|
|
15
15
|
|
16
16
|
let(:running_server_erchef_config_json) {
|
17
|
-
'{"private_chef": { "opscode-erchef": { "sql_user": "cricket", "sql_password": "secrete"}, "
|
17
|
+
'{"private_chef": { "opscode-erchef": { "sql_user": "cricket", "sql_password": "secrete"}}, "opscode_erchef": { "sql_user": "cricket", "sql_password": "secrete"}}'
|
18
18
|
}
|
19
19
|
describe "#load_config_from_file!" do
|
20
20
|
before(:each) do
|
21
21
|
allow(File).to receive(:exists?).and_return(true)
|
22
|
+
allow(File).to receive(:size).and_return(1)
|
22
23
|
end
|
23
24
|
it "correctly sets sql options when they live under postgresql settings" do
|
24
|
-
allow(
|
25
|
+
allow(IO).to receive(:read).and_return(running_server_postgresql_sql_config_json)
|
25
26
|
knife.load_config_from_file!
|
26
27
|
expect(knife.config[:sql_user]).to eq("jiminy")
|
27
28
|
expect(knife.config[:sql_password]).to eq("secret")
|
28
29
|
end
|
29
30
|
it "correctly sets sql options when they live under opscode-erchef settings" do
|
30
|
-
allow(
|
31
|
+
allow(IO).to receive(:read).and_return(running_server_erchef_config_json)
|
31
32
|
knife.load_config_from_file!
|
32
33
|
expect(knife.config[:sql_user]).to eq("cricket")
|
33
34
|
expect(knife.config[:sql_password]).to eq("secrete")
|
@@ -23,7 +23,7 @@ describe Chef::Knife::EcRestore do
|
|
23
23
|
before(:each) do
|
24
24
|
Chef::Knife::EcRestore.load_deps
|
25
25
|
@knife = Chef::Knife::EcRestore.new
|
26
|
-
@rest = double('Chef::
|
26
|
+
@rest = double('Chef::ServerAPI')
|
27
27
|
allow(@knife).to receive(:rest).and_return(@rest)
|
28
28
|
allow(@knife).to receive(:user_acl_rest).and_return(@rest)
|
29
29
|
end
|
@@ -134,6 +134,19 @@ describe Chef::Knife::EcRestore do
|
|
134
134
|
expect(@rest).to receive(:put).with("users/jane", {"username" => "jane"})
|
135
135
|
@knife.restore_users
|
136
136
|
end
|
137
|
+
|
138
|
+
context "when there are HTTP failures with different code than 409" do
|
139
|
+
let(:ec_error_handler) { double("Chef::Knife::EcErrorHandler") }
|
140
|
+
|
141
|
+
it "adds exceptions to error handler" do
|
142
|
+
make_user "jane"
|
143
|
+
exception = net_exception(500)
|
144
|
+
allow(Chef::Knife::EcErrorHandler).to receive(:new).and_return(ec_error_handler)
|
145
|
+
allow(@rest).to receive(:post).with("users", anything).and_raise(exception)
|
146
|
+
expect(ec_error_handler).to receive(:add).at_least(1).with(exception)
|
147
|
+
@knife.restore_users
|
148
|
+
end
|
149
|
+
end
|
137
150
|
end
|
138
151
|
|
139
152
|
describe "#restore_group" do
|
data/spec/chef/server_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
|
2
2
|
require 'chef/server'
|
3
|
-
require 'chef/
|
3
|
+
require 'chef/server_api'
|
4
4
|
require 'stringio'
|
5
5
|
|
6
6
|
describe Chef::Server do
|
@@ -35,7 +35,7 @@ describe Chef::Server do
|
|
35
35
|
it "knows when account is directly accessible" do
|
36
36
|
s = Chef::Server.new("http://api.example.com")
|
37
37
|
rest = double('rest')
|
38
|
-
allow(Chef::
|
38
|
+
allow(Chef::ServerAPI).to receive(:new).and_return(rest)
|
39
39
|
allow(rest).to receive(:get).and_return("")
|
40
40
|
expect(s.direct_account_access?).to eq(true)
|
41
41
|
end
|
@@ -43,7 +43,7 @@ describe Chef::Server do
|
|
43
43
|
it "knows when account is not directly accessible" do
|
44
44
|
s = Chef::Server.new("http://api.example.com")
|
45
45
|
rest = double('rest')
|
46
|
-
allow(Chef::
|
46
|
+
allow(Chef::ServerAPI).to receive(:new).and_return(rest)
|
47
47
|
allow(rest).to receive(:get).and_raise(Errno::ECONNREFUSED)
|
48
48
|
expect(s.direct_account_access?).to eq(false)
|
49
49
|
end
|
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: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Keiser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -79,6 +79,7 @@ files:
|
|
79
79
|
- Rakefile
|
80
80
|
- lib/chef/knife/ec_backup.rb
|
81
81
|
- lib/chef/knife/ec_base.rb
|
82
|
+
- lib/chef/knife/ec_error_handler.rb
|
82
83
|
- lib/chef/knife/ec_key_base.rb
|
83
84
|
- lib/chef/knife/ec_key_export.rb
|
84
85
|
- lib/chef/knife/ec_key_import.rb
|
@@ -89,6 +90,7 @@ files:
|
|
89
90
|
- lib/knife_ec_backup/version.rb
|
90
91
|
- spec/chef/knife/ec_backup_spec.rb
|
91
92
|
- spec/chef/knife/ec_base_spec.rb
|
93
|
+
- spec/chef/knife/ec_error_handler_spec.rb
|
92
94
|
- spec/chef/knife/ec_key_base_spec.rb
|
93
95
|
- spec/chef/knife/ec_key_export_spec.rb
|
94
96
|
- spec/chef/knife/ec_key_import_spec.rb
|
@@ -116,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
118
|
version: '0'
|
117
119
|
requirements: []
|
118
120
|
rubyforge_project:
|
119
|
-
rubygems_version: 2.6.
|
121
|
+
rubygems_version: 2.6.11
|
120
122
|
signing_key:
|
121
123
|
specification_version: 4
|
122
124
|
summary: Backup and Restore of Enterprise Chef
|