knife-ec-backup 3.0.5 → 3.0.9
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 +53 -8
- data/lib/chef/knife/ec_base.rb +9 -5
- data/lib/chef/knife/ec_error_handler.rb +1 -1
- data/lib/chef/knife/ec_import.rb +422 -0
- data/lib/chef/knife/ec_key_base.rb +14 -7
- data/lib/chef/knife/ec_key_export.rb +1 -1
- data/lib/chef/knife/ec_key_import.rb +1 -1
- data/lib/chef/knife/ec_restore.rb +58 -0
- data/lib/chef/org_id_cache.rb +1 -1
- data/lib/knife_ec_backup/version.rb +1 -1
- data/spec/chef/knife/ec_import_spec.rb +798 -0
- data/spec/chef/knife/ec_key_base_spec.rb +51 -0
- data/spec/chef/knife/ec_restore_spec.rb +190 -0
- metadata +4 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
|
|
2
2
|
require 'chef/knife/ec_key_base'
|
|
3
3
|
require 'chef/automate'
|
|
4
|
+
require 'sequel'
|
|
4
5
|
|
|
5
6
|
class KeyBaseTester < Chef::Knife
|
|
6
7
|
include Chef::Knife::EcKeyBase
|
|
@@ -38,4 +39,54 @@ describe Chef::Knife::EcKeyBase do
|
|
|
38
39
|
expect(knife.config[:sql_password]).to eq("secrete")
|
|
39
40
|
end
|
|
40
41
|
end
|
|
42
|
+
|
|
43
|
+
# Regression coverage for the external-PostgreSQL bug: the SQL host/port must
|
|
44
|
+
# be sourced from chef-server-running.json (like sql_user/sql_password/sql_db),
|
|
45
|
+
# while an explicit --sql-host / --sql-port supplied on the CLI must still win.
|
|
46
|
+
describe "#load_config_from_file! PostgreSQL host/port autoconfiguration" do
|
|
47
|
+
let(:external_pg_running_config) {
|
|
48
|
+
'{"private_chef": { "opscode-erchef": {}, "postgresql": { "vip": "db.external.example.com", "port": 6432, "sql_user": "jiminy", "sql_password": "secret" } } }'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
before(:each) do
|
|
52
|
+
allow(Chef::Automate).to receive(:is_installed?).and_return(false)
|
|
53
|
+
allow(File).to receive(:exist?).and_call_original
|
|
54
|
+
allow(File).to receive(:exist?).with("/etc/opscode/chef-server-running.json").and_return(true)
|
|
55
|
+
allow(File).to receive(:read).and_call_original
|
|
56
|
+
allow(File).to receive(:read).with("/etc/opscode/chef-server-running.json").and_return(external_pg_running_config)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "adopts the PostgreSQL host and port from chef-server-running.json when no CLI flag is given" do
|
|
60
|
+
knife.load_config_from_file!
|
|
61
|
+
expect(knife.config[:sql_host]).to eq("db.external.example.com")
|
|
62
|
+
expect(knife.config[:sql_port]).to eq(6432)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "lets an explicit --sql-host / --sql-port win over the autoconfigured value" do
|
|
66
|
+
knife.config[:sql_host] = "cli-host.example.com"
|
|
67
|
+
knife.config[:sql_port] = 2345
|
|
68
|
+
knife.load_config_from_file!
|
|
69
|
+
expect(knife.config[:sql_host]).to eq("cli-host.example.com")
|
|
70
|
+
expect(knife.config[:sql_port]).to eq(2345)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Regression coverage for #181: the configured database name must be written
|
|
75
|
+
# into the PostgreSQL connection URI. It was previously resolved but never
|
|
76
|
+
# applied, so PostgreSQL silently fell back to a database named after the user.
|
|
77
|
+
describe "#db connection URI" do
|
|
78
|
+
before(:each) do
|
|
79
|
+
# Capture the connection string instead of opening a real connection.
|
|
80
|
+
allow(Sequel).to receive(:connect) { |uri, *| uri }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "includes the configured database name in the connection URI" do
|
|
84
|
+
knife.config[:sql_db] = "custom_db"
|
|
85
|
+
expect(knife.db).to include("/custom_db")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "falls back to localhost:5432 when host and port are unset" do
|
|
89
|
+
expect(knife.db).to start_with("postgres://localhost:5432")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
41
92
|
end
|
|
@@ -184,4 +184,194 @@ describe Chef::Knife::EcRestore do
|
|
|
184
184
|
end
|
|
185
185
|
end
|
|
186
186
|
end
|
|
187
|
+
|
|
188
|
+
describe "#restore_cookbook_frozen_status" do
|
|
189
|
+
include FakeFS::SpecHelpers
|
|
190
|
+
|
|
191
|
+
let(:org_name) { "test_org" }
|
|
192
|
+
let(:chef_fs_config) { double("Chef::ChefFS::Config") }
|
|
193
|
+
let(:cookbooks_path) { "/organizations/#{org_name}/cookbooks" }
|
|
194
|
+
|
|
195
|
+
before(:each) do
|
|
196
|
+
allow(@knife).to receive(:dest_dir).and_return("")
|
|
197
|
+
allow(@knife).to receive(:ui).and_return(double("ui", :msg => nil, :warn => nil))
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
context "when cookbooks directory does not exist" do
|
|
201
|
+
it "returns early without processing" do
|
|
202
|
+
expect(@knife).not_to receive(:freeze_cookbook)
|
|
203
|
+
@knife.restore_cookbook_frozen_status(org_name, chef_fs_config)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
context "when cookbooks directory exists" do
|
|
208
|
+
before(:each) do
|
|
209
|
+
FileUtils.mkdir_p(cookbooks_path)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
context "with no cookbook directories" do
|
|
213
|
+
it "does not process any cookbooks" do
|
|
214
|
+
expect(@knife).not_to receive(:freeze_cookbook)
|
|
215
|
+
@knife.restore_cookbook_frozen_status(org_name, chef_fs_config)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
context "with cookbook directories" do
|
|
220
|
+
let(:cookbook_name) { "apache2" }
|
|
221
|
+
let(:version) { "1.2.3" }
|
|
222
|
+
let(:cookbook_dir) { "#{cookbook_name}-#{version}" }
|
|
223
|
+
let(:cookbook_path) { File.join(cookbooks_path, cookbook_dir) }
|
|
224
|
+
let(:status_file) { File.join(cookbook_path, "status.json") }
|
|
225
|
+
|
|
226
|
+
before(:each) do
|
|
227
|
+
FileUtils.mkdir_p(cookbook_path)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
context "when status.json does not exist" do
|
|
231
|
+
it "skips the cookbook" do
|
|
232
|
+
expect(@knife).not_to receive(:freeze_cookbook)
|
|
233
|
+
@knife.restore_cookbook_frozen_status(org_name, chef_fs_config)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
context "when status.json exists" do
|
|
238
|
+
context "and cookbook is frozen" do
|
|
239
|
+
before(:each) do
|
|
240
|
+
File.write(status_file, '{"frozen": true}')
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it "calls freeze_cookbook with correct parameters" do
|
|
244
|
+
expect(@knife).to receive(:freeze_cookbook).with(cookbook_name, version, org_name)
|
|
245
|
+
@knife.restore_cookbook_frozen_status(org_name, chef_fs_config)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
context "and cookbook is not frozen" do
|
|
250
|
+
before(:each) do
|
|
251
|
+
File.write(status_file, '{"frozen": false}')
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it "does not call freeze_cookbook" do
|
|
255
|
+
expect(@knife).not_to receive(:freeze_cookbook)
|
|
256
|
+
@knife.restore_cookbook_frozen_status(org_name, chef_fs_config)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
context "and status.json has invalid JSON" do
|
|
261
|
+
before(:each) do
|
|
262
|
+
File.write(status_file, 'invalid json')
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
it "warns about parse error and continues" do
|
|
266
|
+
ui = double("ui")
|
|
267
|
+
allow(@knife).to receive(:ui).and_return(ui)
|
|
268
|
+
expect(ui).to receive(:msg).with("Restoring cookbook frozen status")
|
|
269
|
+
expect(ui).to receive(:warn).with(/Failed to parse status\.json/)
|
|
270
|
+
expect(@knife).not_to receive(:freeze_cookbook)
|
|
271
|
+
@knife.restore_cookbook_frozen_status(org_name, chef_fs_config)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
context "with multiple cookbooks" do
|
|
276
|
+
let(:frozen_cookbook) { "frozen-cookbook-2.0.0" }
|
|
277
|
+
let(:unfrozen_cookbook) { "unfrozen-cookbook-1.5.0" }
|
|
278
|
+
|
|
279
|
+
before(:each) do
|
|
280
|
+
# Create frozen cookbook
|
|
281
|
+
FileUtils.mkdir_p(File.join(cookbooks_path, frozen_cookbook))
|
|
282
|
+
File.write(File.join(cookbooks_path, frozen_cookbook, "status.json"), '{"frozen": true}')
|
|
283
|
+
|
|
284
|
+
# Create unfrozen cookbook
|
|
285
|
+
FileUtils.mkdir_p(File.join(cookbooks_path, unfrozen_cookbook))
|
|
286
|
+
File.write(File.join(cookbooks_path, unfrozen_cookbook, "status.json"), '{"frozen": false}')
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it "only freezes the frozen cookbook" do
|
|
290
|
+
expect(@knife).to receive(:freeze_cookbook).with("frozen-cookbook", "2.0.0", org_name).once
|
|
291
|
+
expect(@knife).not_to receive(:freeze_cookbook).with("unfrozen-cookbook", "1.5.0", org_name)
|
|
292
|
+
@knife.restore_cookbook_frozen_status(org_name, chef_fs_config)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
context "with cookbook name containing hyphens" do
|
|
297
|
+
let(:cookbook_name) { "multi-word-cookbook" }
|
|
298
|
+
let(:version) { "1.0.0" }
|
|
299
|
+
|
|
300
|
+
before(:each) do
|
|
301
|
+
cookbook_dir = "#{cookbook_name}-#{version}"
|
|
302
|
+
cookbook_path = File.join(cookbooks_path, cookbook_dir)
|
|
303
|
+
FileUtils.mkdir_p(cookbook_path)
|
|
304
|
+
File.write(File.join(cookbook_path, "status.json"), '{"frozen": true}')
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
it "correctly parses cookbook name with hyphens" do
|
|
308
|
+
expect(@knife).to receive(:freeze_cookbook).with(cookbook_name, version, org_name)
|
|
309
|
+
@knife.restore_cookbook_frozen_status(org_name, chef_fs_config)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
describe "#freeze_cookbook" do
|
|
318
|
+
let(:cookbook_name) { "apache2" }
|
|
319
|
+
let(:version) { "1.2.3" }
|
|
320
|
+
let(:org_name) { "test_org" }
|
|
321
|
+
let(:manifest) { { "name" => cookbook_name, "version" => version, "frozen?" => false } }
|
|
322
|
+
|
|
323
|
+
before(:each) do
|
|
324
|
+
allow(@knife).to receive(:ui).and_return(double("ui", :msg => nil, :warn => nil))
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
context "when cookbook is not already frozen" do
|
|
328
|
+
before(:each) do
|
|
329
|
+
allow(@rest).to receive(:get).with("organizations/#{org_name}/cookbooks/#{cookbook_name}/#{version}").and_return(manifest)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
it "freezes the cookbook successfully" do
|
|
333
|
+
frozen_manifest = manifest.dup.tap { |h| h["frozen?"] = true }
|
|
334
|
+
expect(@rest).to receive(:put).with("organizations/#{org_name}/cookbooks/#{cookbook_name}/#{version}?freeze=true", frozen_manifest)
|
|
335
|
+
@knife.freeze_cookbook(cookbook_name, version, org_name)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
context "when cookbook is already frozen" do
|
|
340
|
+
let(:frozen_manifest) { { "name" => cookbook_name, "version" => version, "frozen?" => true } }
|
|
341
|
+
|
|
342
|
+
before(:each) do
|
|
343
|
+
allow(@rest).to receive(:get).with("organizations/#{org_name}/cookbooks/#{cookbook_name}/#{version}").and_return(frozen_manifest)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it "skips freezing and warns" do
|
|
347
|
+
ui = double("ui")
|
|
348
|
+
allow(@knife).to receive(:ui).and_return(ui)
|
|
349
|
+
expect(ui).to receive(:msg).with("Freezing cookbook #{cookbook_name} version #{version}")
|
|
350
|
+
expect(ui).to receive(:warn).with(/already frozen/)
|
|
351
|
+
expect(@rest).not_to receive(:put)
|
|
352
|
+
@knife.freeze_cookbook(cookbook_name, version, org_name)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
context "when API call fails" do
|
|
357
|
+
before(:each) do
|
|
358
|
+
allow(@rest).to receive(:get).with("organizations/#{org_name}/cookbooks/#{cookbook_name}/#{version}").and_return(manifest)
|
|
359
|
+
allow(@rest).to receive(:put).and_raise(net_exception(500))
|
|
360
|
+
allow(@knife).to receive(:knife_ec_error_handler).and_return(double("error_handler", :add => nil))
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
it "handles the exception and adds to error handler" do
|
|
364
|
+
ui = double("ui")
|
|
365
|
+
error_handler = double("error_handler")
|
|
366
|
+
allow(@knife).to receive(:ui).and_return(ui)
|
|
367
|
+
allow(@knife).to receive(:knife_ec_error_handler).and_return(error_handler)
|
|
368
|
+
|
|
369
|
+
expect(ui).to receive(:msg).with("Freezing cookbook #{cookbook_name} version #{version}")
|
|
370
|
+
expect(ui).to receive(:warn).with(/Failed to freeze cookbook/)
|
|
371
|
+
expect(error_handler).to receive(:add)
|
|
372
|
+
|
|
373
|
+
@knife.freeze_cookbook(cookbook_name, version, org_name)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
187
377
|
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: 3.0.
|
|
4
|
+
version: 3.0.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Keiser
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-06-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sequel
|
|
@@ -95,6 +95,7 @@ files:
|
|
|
95
95
|
- lib/chef/knife/ec_backup.rb
|
|
96
96
|
- lib/chef/knife/ec_base.rb
|
|
97
97
|
- lib/chef/knife/ec_error_handler.rb
|
|
98
|
+
- lib/chef/knife/ec_import.rb
|
|
98
99
|
- lib/chef/knife/ec_key_base.rb
|
|
99
100
|
- lib/chef/knife/ec_key_export.rb
|
|
100
101
|
- lib/chef/knife/ec_key_import.rb
|
|
@@ -106,6 +107,7 @@ files:
|
|
|
106
107
|
- spec/chef/knife/ec_backup_spec.rb
|
|
107
108
|
- spec/chef/knife/ec_base_spec.rb
|
|
108
109
|
- spec/chef/knife/ec_error_handler_spec.rb
|
|
110
|
+
- spec/chef/knife/ec_import_spec.rb
|
|
109
111
|
- spec/chef/knife/ec_key_base_spec.rb
|
|
110
112
|
- spec/chef/knife/ec_key_export_spec.rb
|
|
111
113
|
- spec/chef/knife/ec_key_import_spec.rb
|