bard-backup 0.1.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55ece259f7be3aae82e05c75b510f18bb1d7f70b9d0c77b3b9f6e71a641ddf4c
4
- data.tar.gz: aed5da390d8f657fe3db65b009cde22ef0e6baf545fd4aafb2222e62258c3ad1
3
+ metadata.gz: 30f8ca36634a8ee2f414e6aa238a27227d71b9033904b19ff55c649dd8bfe71b
4
+ data.tar.gz: 2c858195ad3e2e23d84cbce7be7d99ab0000fec2d39f2cd1d409774987067f2d
5
5
  SHA512:
6
- metadata.gz: dcc9f83eb793a5ed76446c862ce95a4b2b019eb38aa2f5c6589f5e6a516642f5cbad1292054e2f72f4a676d40f91c79b11cb6f26457fcdea565509c337a72dc2
7
- data.tar.gz: 9a61e8264d5f3e4fe0247e88aba019b9971099a4325ff523c0d2cb195b104d51d2c0a2b3b497b1a15016c9a83d7e2f97c1a2e5920686fd5da732d579689d139a
6
+ metadata.gz: 44346cf310237c1d24c60733328dff6ac819ce050a45a183af50b269e61d9ebef420af721e961e612d5fff5d083d53c70dbf8b214b1443d9eee265df6f5b25ba
7
+ data.tar.gz: 5bcf351a8697340af6e2388b42cf513a7dab8b76da456c0d4ba659dd846e32455eb447148cf77006dd32dd4992a4100ffd6b0f9b7662efe55ac633cf544fd3cf
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Bard::Backup does 3 things in a bard project
4
4
  1. Takes a database dump and uploads it to our s3 bucket
5
- 2. Deletes old backups using a backoff heuristic: 48 hours, 60 days, 48 months, then yearly
5
+ 2. Deletes old backups using a backoff heuristic: 72 hours, 60 days, 52 weeks, 48 months, then yearly
6
6
  3. Raises an error if we don't have a backup from the previous hour
7
7
 
8
8
  ## Installation
@@ -0,0 +1,12 @@
1
+ module Bard
2
+ module Backup
3
+ class Controller < Struct.new(:dumper, :s3_dir, :now)
4
+ def call
5
+ filename = "#{now.iso8601}.sql.gz"
6
+ path = "/tmp/#{filename}"
7
+ dumper.dump path
8
+ s3_dir.put path
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,41 @@
1
+ require "active_support"
2
+ require "active_support/core_ext/date_time/calculations"
3
+ require "active_support/core_ext/integer/time"
4
+
5
+ module Bard
6
+ module Backup
7
+ class Deleter < Struct.new(:s3_dir, :now)
8
+ def call
9
+ s3_dir.delete keys_to_delete
10
+ end
11
+
12
+ def keys_to_delete
13
+ s3_dir.keys.select do |key|
14
+ [
15
+ Filter.new(now, 72, :hours),
16
+ Filter.new(now, 60, :days),
17
+ Filter.new(now, 52, :weeks),
18
+ Filter.new(now, 48, :months),
19
+ Filter.new(now, 100, :years),
20
+ ].all? { |filter| !filter.cover?(key) }
21
+ end
22
+ end
23
+
24
+ class Filter < Struct.new(:now, :limit, :unit)
25
+ def cover? key
26
+ remote = DateTime.parse(key).beginning_of_hour
27
+ limit.times.any? do |count|
28
+ remote == ago(count)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def ago count
35
+ now.send(:"beginning_of_#{unit.to_s.singularize}") - count.send(unit)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,61 @@
1
+ require "aws-sdk-s3"
2
+ require "rexml"
3
+
4
+ module Bard
5
+ module Backup
6
+ class S3Dir < Data.define(:path, :access_key, :secret_key)
7
+ def keys
8
+ response = client.list_objects_v2({
9
+ bucket: bucket_name,
10
+ prefix: folder_prefix,
11
+ })
12
+ raise if response.is_truncated
13
+ response.contents.map(&:key)
14
+ end
15
+
16
+ def put file_path, body: File.read(file_path)
17
+ client.put_object({
18
+ bucket: bucket_name,
19
+ key: "#{folder_prefix}/#{File.basename(file_path)}",
20
+ body: body,
21
+ })
22
+ end
23
+
24
+ def delete keys
25
+ objects_to_delete = Array(keys).map { |key| { key: key } }
26
+ client.delete_objects({
27
+ bucket: bucket_name,
28
+ delete: {
29
+ objects: objects_to_delete,
30
+ quiet: true,
31
+ }
32
+ })
33
+ end
34
+
35
+ def empty!
36
+ keys.each_slice(1000) do |key_batch|
37
+ delete key_batch
38
+ end
39
+ end
40
+
41
+ def bucket_name
42
+ path.split("/").first
43
+ end
44
+
45
+ def folder_prefix
46
+ path.split("/")[1..].join("/")
47
+ end
48
+
49
+ private
50
+
51
+ def client
52
+ Aws::S3::Client.new({
53
+ region: "us-west-2",
54
+ access_key_id: access_key,
55
+ secret_access_key: secret_key,
56
+ })
57
+ end
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,6 @@
1
+ module Bard
2
+ module Backup
3
+ VERSION = "0.3.0"
4
+ end
5
+ end
6
+
data/lib/bard/backup.rb CHANGED
@@ -1,91 +1,16 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "openssl"
5
- require "base64"
1
+ require "bard/backup/controller"
2
+ require "bard/backup/deleter"
3
+ require "bard/backup/s3_dir"
6
4
  require "backhoe"
7
5
 
8
6
  module Bard
9
- class Backup < Struct.new(:s3_path, :access_key, :secret_key)
10
- def self.call s3_path, access_key:, secret_key:
11
- new(s3_path, access_key, secret_key).call
12
- end
13
-
14
- def call
15
- @time = Time.now
16
-
17
- Backhoe.dump path
18
-
19
- self.full_s3_path = "/#{s3_path}/#{filename}"
20
-
21
- if s3_path.include?("/")
22
- s3_bucket = s3_path.split("/").first
23
- self.s3_path = s3_path.split("/")[1..].join("/")
24
- uri = URI("https://#{s3_bucket}.s3.amazonaws.com/#{s3_path}/#{filename}")
25
- else
26
- uri = URI("https://#{s3_path}.s3.amazonaws.com/#{filename}")
27
- end
28
-
29
- request = Net::HTTP::Put.new(uri, {
30
- "Content-Length": File.size(path).to_s,
31
- "Content-Type": content_type,
32
- "Date": date,
33
- "Authorization": "AWS #{access_key}:#{signature}",
34
- "x-amz-storage-class": "STANDARD",
35
- "x-amz-acl": "private",
36
- })
37
- request.body_stream = File.open(path)
38
-
39
- Net::HTTP.start(uri.hostname) do |http|
40
- response = http.request(request)
41
- response.value # raises if not success
42
- end
43
- end
44
-
45
- private
46
-
47
- attr_accessor :full_s3_path
48
-
49
- def signature
50
- digester = OpenSSL::Digest::SHA1.new
51
- digest = OpenSSL::HMAC.digest(digester, secret_key, key)
52
- Base64.strict_encode64(digest)
53
- end
54
-
55
- def key
56
- [
57
- "PUT",
58
- "",
59
- content_type,
60
- date,
61
- acl,
62
- storage_type,
63
- full_s3_path,
64
- ].join("\n")
65
- end
66
-
67
- def content_type
68
- "application/gzip"
69
- end
70
-
71
- def date
72
- @time.rfc2822
73
- end
74
-
75
- def acl
76
- "x-amz-acl:private"
77
- end
78
-
79
- def storage_type
80
- "x-amz-storage-class:STANDARD"
81
- end
82
-
83
- def path
84
- "/tmp/#{filename}"
85
- end
86
-
87
- def filename
88
- "#{@time.utc.iso8601}.sql.gz"
7
+ module Backup
8
+ def self.call s3_path, access_key:, secret_key:, now: Time.now.utc
9
+ dumper = Backhoe
10
+ s3_dir = S3Dir.new(path: s3_path, access_key:, secret_key:)
11
+ Controller.new(dumper, s3_dir, now).call
12
+ Deleter.new(s3_dir, now).call
89
13
  end
90
14
  end
91
15
  end
16
+
metadata CHANGED
@@ -1,15 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bard-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-22 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-07-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: backhoe
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-s3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rexml
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
13
69
  description:
14
70
  email:
15
71
  - micah@botandrose.com
@@ -25,6 +81,10 @@ files:
25
81
  - Rakefile
26
82
  - lib/bard-backup.rb
27
83
  - lib/bard/backup.rb
84
+ - lib/bard/backup/controller.rb
85
+ - lib/bard/backup/deleter.rb
86
+ - lib/bard/backup/s3_dir.rb
87
+ - lib/bard/backup/version.rb
28
88
  - sig/bard/backup.rbs
29
89
  homepage: https://github.com/botandrose/bard-backup
30
90
  licenses: