knife-ec-backup 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|