bard-backup 0.8.0 → 0.9.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/.ruby-version +1 -1
- data/README.md +1 -1
- data/config/cucumber.yml +1 -0
- data/lib/bard/backup/destination/s3_destination.rb +48 -0
- data/lib/bard/backup/destination/upload_destination.rb +71 -0
- data/lib/bard/backup/destination.rb +23 -0
- data/lib/bard/backup/latest_finder.rb +47 -0
- data/lib/bard/backup/railtie.rb +12 -0
- data/lib/bard/backup/s3_dir.rb +3 -3
- data/lib/bard/backup/tasks.rake +7 -0
- data/lib/bard/backup/version.rb +1 -1
- data/lib/bard/backup.rb +22 -47
- metadata +25 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 27e2f3a66518e1d3449f448b15a587fb1839d7fb8d978e969ef583821dc01f9e
|
|
4
|
+
data.tar.gz: 3deae6b4c09d9492aa194f5511dc91d478de9311a559bf8620fdc0a6ca339ea1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d941d50b9c96292732262452f71c127525e446b9b4aa10b146b8e1bf8488026d02f1816240bb1e5721d47947215d6dff566437a7b6bcf0c6675390b4d8453580
|
|
7
|
+
data.tar.gz: a9c8f3c8d09f8e38b738e78966bf8a585ea15851867e8ae837ced8ede3c8769d50248789c265bd604194a7e7e52557192e974aa2317c00aadfc59c398b52bedc
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ruby-3.
|
|
1
|
+
ruby-3.4.2
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Bard::Backup does 3 things in a bard project
|
|
|
9
9
|
|
|
10
10
|
## Usage
|
|
11
11
|
|
|
12
|
-
Run with `Bard::Backup.call path: "s3_bucket/optional_subfolder",
|
|
12
|
+
Run with `Bard::Backup.call path: "s3_bucket/optional_subfolder", access_key_id: "...", secret_access_key: "...", region: "..."`
|
|
13
13
|
|
|
14
14
|
Or just run via the `bard-rake` gem: `rake db:backup`, which wires up the above for you.
|
|
15
15
|
|
data/config/cucumber.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
default: --publish-quiet
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require "bard/backup/s3_dir"
|
|
2
|
+
require "bard/backup/deleter"
|
|
3
|
+
require "bard/backup/local_backhoe"
|
|
4
|
+
require "bard/backup/cached_local_backhoe"
|
|
5
|
+
|
|
6
|
+
module Bard
|
|
7
|
+
class Backup
|
|
8
|
+
class S3Destination < Destination
|
|
9
|
+
def call
|
|
10
|
+
strategy.call(s3_dir, now)
|
|
11
|
+
Deleter.new(s3_dir, now).call
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def s3_dir
|
|
15
|
+
@s3_dir ||= S3Dir.new(**config.slice(:endpoint, :path, :access_key_id, :secret_access_key, :region))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def info
|
|
19
|
+
config.slice(:name, :type, :path, :region)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def config
|
|
25
|
+
@config ||= begin
|
|
26
|
+
config = {}
|
|
27
|
+
|
|
28
|
+
if defined?(Rails)
|
|
29
|
+
credentials = Rails.application.credentials.bard_backup || []
|
|
30
|
+
credentials = [credentials] if credentials.is_a?(Hash)
|
|
31
|
+
config = credentials.find { |c| c[:name] == super[:name] } || {}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
config = { type: :s3, region: "us-west-2" }.merge(config).merge(super)
|
|
35
|
+
config[:endpoint] ||= "https://s3.#{config[:region]}.amazonaws.com"
|
|
36
|
+
config
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def strategy
|
|
41
|
+
return @strategy if @strategy
|
|
42
|
+
@strategy = config.fetch(:strategy, LocalBackhoe)
|
|
43
|
+
@strategy = Bard::Backup.const_get(@strategy) if @strategy.is_a?(String)
|
|
44
|
+
@strategy
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "uri"
|
|
3
|
+
require "net/http"
|
|
4
|
+
|
|
5
|
+
module Bard
|
|
6
|
+
class Backup
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
|
|
9
|
+
class UploadDestination < Destination
|
|
10
|
+
def call
|
|
11
|
+
timestamp = now
|
|
12
|
+
filename = "#{timestamp.iso8601}.sql.gz"
|
|
13
|
+
temp_path = "/tmp/#{filename}"
|
|
14
|
+
errors = []
|
|
15
|
+
|
|
16
|
+
begin
|
|
17
|
+
Backhoe.dump(temp_path)
|
|
18
|
+
size = File.size(temp_path)
|
|
19
|
+
|
|
20
|
+
threads = urls.map do |url|
|
|
21
|
+
Thread.new do
|
|
22
|
+
upload_to_url(url, temp_path)
|
|
23
|
+
rescue => e
|
|
24
|
+
errors << e
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
threads.each(&:join)
|
|
29
|
+
ensure
|
|
30
|
+
FileUtils.rm_f(temp_path)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
raise Error, "Upload failed: #{errors.map(&:message).join(", ")}" unless errors.empty?
|
|
34
|
+
|
|
35
|
+
Bard::Backup.new(timestamp:, size:, destinations: [])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def info
|
|
39
|
+
{ type: :upload, name: config[:name] }.compact
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def urls
|
|
45
|
+
@urls ||= begin
|
|
46
|
+
Array(config[:urls]).compact
|
|
47
|
+
raise Error, "No URLs provided" if urls.empty?
|
|
48
|
+
urls
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def upload_to_url(url, file_path)
|
|
53
|
+
uri = URI.parse(url)
|
|
54
|
+
|
|
55
|
+
File.open(file_path, "rb") do |file|
|
|
56
|
+
request = Net::HTTP::Put.new(uri)
|
|
57
|
+
request.body = file.read
|
|
58
|
+
request.content_type = "application/octet-stream"
|
|
59
|
+
|
|
60
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
61
|
+
http.request(request)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
65
|
+
raise Error, "Upload failed with status #{response.code}: #{response.body}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Bard
|
|
2
|
+
class Backup
|
|
3
|
+
class Destination < Struct.new(:config)
|
|
4
|
+
def self.build(config)
|
|
5
|
+
klass = Bard::Backup.const_get("#{config[:type].to_s.capitalize}Destination")
|
|
6
|
+
klass.new(config)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
raise NotImplementedError
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def now
|
|
16
|
+
@now ||= config.fetch(:now, Time.now.utc)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
require "bard/backup/destination/s3_destination"
|
|
23
|
+
require "bard/backup/destination/upload_destination"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require "bard/config"
|
|
2
|
+
|
|
3
|
+
module Bard
|
|
4
|
+
class Backup
|
|
5
|
+
class NotFound < StandardError; end
|
|
6
|
+
|
|
7
|
+
class LatestFinder
|
|
8
|
+
def call
|
|
9
|
+
destinations = Bard::Config.current.backup.destinations.map do |hash|
|
|
10
|
+
Destination.build(hash)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
all_backups = destinations.flat_map do |dest|
|
|
14
|
+
dest.s3_dir.files.filter_map do |filename|
|
|
15
|
+
timestamp = parse_timestamp(filename)
|
|
16
|
+
next unless timestamp
|
|
17
|
+
|
|
18
|
+
{ timestamp: timestamp, destination: dest, filename: filename }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
raise NotFound, "No backups found" if all_backups.empty?
|
|
23
|
+
|
|
24
|
+
latest = all_backups.max_by { |b| b[:timestamp] }
|
|
25
|
+
|
|
26
|
+
Bard::Backup.new(
|
|
27
|
+
timestamp: latest[:timestamp],
|
|
28
|
+
size: get_file_size(latest[:destination].s3_dir, latest[:filename]),
|
|
29
|
+
destinations: all_backups
|
|
30
|
+
.select { |b| b[:timestamp] == latest[:timestamp] }
|
|
31
|
+
.map { |b| b[:destination].info }
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def parse_timestamp(filename)
|
|
38
|
+
filename =~ /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)/ ? Time.parse($1) : nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def get_file_size(s3_dir, filename)
|
|
42
|
+
key = [s3_dir.folder_prefix, filename].compact.join("/")
|
|
43
|
+
s3_dir.send(:client).head_object(bucket: s3_dir.bucket_name, key: key).content_length
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/bard/backup/s3_dir.rb
CHANGED
|
@@ -3,7 +3,7 @@ require "rexml"
|
|
|
3
3
|
|
|
4
4
|
module Bard
|
|
5
5
|
class Backup
|
|
6
|
-
class S3Dir < Data.define(:endpoint, :path, :
|
|
6
|
+
class S3Dir < Data.define(:endpoint, :path, :access_key_id, :secret_access_key, :region)
|
|
7
7
|
def initialize **kwargs
|
|
8
8
|
kwargs[:endpoint] ||= "https://s3.#{kwargs[:region]}.amazonaws.com"
|
|
9
9
|
super
|
|
@@ -77,8 +77,8 @@ module Bard
|
|
|
77
77
|
Aws::S3::Client.new({
|
|
78
78
|
endpoint: endpoint,
|
|
79
79
|
region: region,
|
|
80
|
-
access_key_id:
|
|
81
|
-
secret_access_key:
|
|
80
|
+
access_key_id: access_key_id,
|
|
81
|
+
secret_access_key: secret_access_key,
|
|
82
82
|
})
|
|
83
83
|
end
|
|
84
84
|
end
|
data/lib/bard/backup/version.rb
CHANGED
data/lib/bard/backup.rb
CHANGED
|
@@ -1,62 +1,37 @@
|
|
|
1
|
-
require "bard/backup/
|
|
2
|
-
require "bard/backup/
|
|
3
|
-
require "bard/backup/
|
|
4
|
-
require "bard/backup/deleter"
|
|
1
|
+
require "bard/backup/destination"
|
|
2
|
+
require "bard/backup/latest_finder"
|
|
3
|
+
require "bard/backup/railtie" if defined?(Rails)
|
|
5
4
|
|
|
6
5
|
module Bard
|
|
7
6
|
class Backup
|
|
8
|
-
def self.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
new(config).call
|
|
7
|
+
def self.create!(destination_hashes = nil, **config)
|
|
8
|
+
if destination_hashes.nil? && !config.empty?
|
|
9
|
+
destination_hashes = [config]
|
|
12
10
|
end
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@config = config
|
|
17
|
-
end
|
|
18
|
-
attr_reader :config
|
|
19
|
-
|
|
20
|
-
def call
|
|
21
|
-
strategy.call(s3_dir, now)
|
|
22
|
-
Deleter.new(s3_dir, now).call
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def s3_dir
|
|
26
|
-
@s3_dir ||= S3Dir.new(endpoint:, path:, access_key:, secret_key:, region:)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def strategy
|
|
30
|
-
return @strategy if @strategy
|
|
31
|
-
@strategy = config.fetch(:strategy, LocalBackhoe)
|
|
32
|
-
if @strategy.is_a?(String)
|
|
33
|
-
@strategy = Bard::Backup.const_get(@strategy)
|
|
11
|
+
destination_hashes ||= Bard::Config.current.backup.destinations
|
|
12
|
+
Array(destination_hashes).each do |hash|
|
|
13
|
+
Destination.build(hash).call
|
|
34
14
|
end
|
|
35
|
-
@strategy
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def path
|
|
39
|
-
config.fetch(:path)
|
|
40
15
|
end
|
|
41
16
|
|
|
42
|
-
def
|
|
43
|
-
|
|
17
|
+
def self.latest
|
|
18
|
+
LatestFinder.new.call
|
|
44
19
|
end
|
|
45
20
|
|
|
46
|
-
|
|
47
|
-
config[:secret_access_key] || config[:secret_key]
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def region
|
|
51
|
-
config.fetch(:region, "us-west-2")
|
|
52
|
-
end
|
|
21
|
+
attr_reader :timestamp, :size, :destinations
|
|
53
22
|
|
|
54
|
-
def
|
|
55
|
-
@
|
|
23
|
+
def initialize(timestamp:, size: nil, destinations: [])
|
|
24
|
+
@timestamp = timestamp
|
|
25
|
+
@size = size
|
|
26
|
+
@destinations = destinations
|
|
56
27
|
end
|
|
57
28
|
|
|
58
|
-
def
|
|
59
|
-
|
|
29
|
+
def as_json(*)
|
|
30
|
+
{
|
|
31
|
+
timestamp: timestamp&.iso8601,
|
|
32
|
+
size: size,
|
|
33
|
+
destinations: destinations
|
|
34
|
+
}.compact
|
|
60
35
|
end
|
|
61
36
|
end
|
|
62
37
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bard-backup
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Micah Geisel
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
10
|
+
date: 2025-12-11 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: backhoe
|
|
@@ -53,7 +52,21 @@ dependencies:
|
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
53
|
version: '0'
|
|
55
54
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
55
|
+
name: rails
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: bard
|
|
57
70
|
requirement: !ruby/object:Gem::Requirement
|
|
58
71
|
requirements:
|
|
59
72
|
- - ">="
|
|
@@ -66,7 +79,6 @@ dependencies:
|
|
|
66
79
|
- - ">="
|
|
67
80
|
- !ruby/object:Gem::Version
|
|
68
81
|
version: '0'
|
|
69
|
-
description:
|
|
70
82
|
email:
|
|
71
83
|
- micah@botandrose.com
|
|
72
84
|
executables: []
|
|
@@ -79,12 +91,19 @@ files:
|
|
|
79
91
|
- LICENSE.txt
|
|
80
92
|
- README.md
|
|
81
93
|
- Rakefile
|
|
94
|
+
- config/cucumber.yml
|
|
82
95
|
- lib/bard-backup.rb
|
|
83
96
|
- lib/bard/backup.rb
|
|
84
97
|
- lib/bard/backup/cached_local_backhoe.rb
|
|
85
98
|
- lib/bard/backup/deleter.rb
|
|
99
|
+
- lib/bard/backup/destination.rb
|
|
100
|
+
- lib/bard/backup/destination/s3_destination.rb
|
|
101
|
+
- lib/bard/backup/destination/upload_destination.rb
|
|
102
|
+
- lib/bard/backup/latest_finder.rb
|
|
86
103
|
- lib/bard/backup/local_backhoe.rb
|
|
104
|
+
- lib/bard/backup/railtie.rb
|
|
87
105
|
- lib/bard/backup/s3_dir.rb
|
|
106
|
+
- lib/bard/backup/tasks.rake
|
|
88
107
|
- lib/bard/backup/version.rb
|
|
89
108
|
- sig/bard/backup.rbs
|
|
90
109
|
homepage: https://github.com/botandrose/bard-backup
|
|
@@ -92,7 +111,6 @@ licenses:
|
|
|
92
111
|
- MIT
|
|
93
112
|
metadata:
|
|
94
113
|
homepage_uri: https://github.com/botandrose/bard-backup
|
|
95
|
-
post_install_message:
|
|
96
114
|
rdoc_options: []
|
|
97
115
|
require_paths:
|
|
98
116
|
- lib
|
|
@@ -107,8 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
107
125
|
- !ruby/object:Gem::Version
|
|
108
126
|
version: '0'
|
|
109
127
|
requirements: []
|
|
110
|
-
rubygems_version: 3.
|
|
111
|
-
signing_key:
|
|
128
|
+
rubygems_version: 3.6.2
|
|
112
129
|
specification_version: 4
|
|
113
130
|
summary: Provides automated db backups for bard projects
|
|
114
131
|
test_files: []
|