tartarus-rb 0.3.0 → 0.4.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/.env.sample +4 -0
- data/.gitignore +2 -0
- data/.travis.yml +7 -0
- data/Changelog.md +6 -0
- data/Gemfile.lock +83 -4
- data/README.md +90 -5
- data/lib/tartarus.rb +9 -0
- data/lib/tartarus/archivable_item.rb +5 -1
- data/lib/tartarus/archive_model_with_tenant.rb +4 -2
- data/lib/tartarus/archive_model_without_tenant.rb +3 -2
- data/lib/tartarus/rb/version.rb +1 -1
- data/lib/tartarus/remote_storage.rb +2 -0
- data/lib/tartarus/remote_storage/glacier.rb +60 -0
- data/lib/tartarus/remote_storage/glacier/client.rb +26 -0
- data/lib/tartarus/remote_storage/glacier/configuration.rb +43 -0
- data/lib/tartarus/remote_storage/glacier/csv_export.rb +38 -0
- data/lib/tartarus/remote_storage/glacier/file.rb +31 -0
- data/lib/tartarus/remote_storage/glacier/register_upload.rb +28 -0
- data/lib/tartarus/remote_storage/null.rb +8 -0
- data/tartarus-rb.gemspec +7 -1
- metadata +96 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee9118a9365d2a74dd802218b0b0b36286c6494ecffe4f81cbf9bfed35597057
|
|
4
|
+
data.tar.gz: f85ffe0e1f182667b5b9b522746a85b45cc9cd3452270f583549a0616d06337c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08e1ab969f1681fca948bdfb4ae98fb919e7caef4e95a1cf5a3a2dffc6fa79a59c73655927be759408f951ad0846ff5c1dea6b7396220801f31c179730e9d6e3'
|
|
7
|
+
data.tar.gz: 72f485499631b46912293650672cd79e8df364ebbc7b18144731c1077e7fa8157be8b20e4078212b04dfb539a3de19af99abcca720163785c3132f51bf574968
|
data/.env.sample
ADDED
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
|
@@ -3,6 +3,13 @@ language: ruby
|
|
|
3
3
|
cache: bundler
|
|
4
4
|
rvm:
|
|
5
5
|
- 2.7.2
|
|
6
|
+
env:
|
|
7
|
+
global:
|
|
8
|
+
- AWS_KEY=AWS_KEY
|
|
9
|
+
- AWS_SECRET=AWS_SECRET
|
|
10
|
+
- AWS_REGION="us-east-2"
|
|
11
|
+
- VAULT_NAME=VAULT_NAME
|
|
6
12
|
before_install: gem install bundler -v 2.1.4
|
|
7
13
|
services:
|
|
8
14
|
- redis-server
|
|
15
|
+
- postgresql
|
data/Changelog.md
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
tartarus-rb (0.
|
|
4
|
+
tartarus-rb (0.4.0)
|
|
5
|
+
aws-sdk-glacier
|
|
5
6
|
sidekiq (>= 5)
|
|
6
7
|
sidekiq-cron (~> 1)
|
|
7
8
|
|
|
8
9
|
GEM
|
|
9
10
|
remote: https://rubygems.org/
|
|
10
11
|
specs:
|
|
12
|
+
actionpack (6.1.0.rc1)
|
|
13
|
+
actionview (= 6.1.0.rc1)
|
|
14
|
+
activesupport (= 6.1.0.rc1)
|
|
15
|
+
rack (~> 2.0, >= 2.0.9)
|
|
16
|
+
rack-test (>= 0.6.3)
|
|
17
|
+
rails-dom-testing (~> 2.0)
|
|
18
|
+
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
|
19
|
+
actionview (6.1.0.rc1)
|
|
20
|
+
activesupport (= 6.1.0.rc1)
|
|
21
|
+
builder (~> 3.1)
|
|
22
|
+
erubi (~> 1.4)
|
|
23
|
+
rails-dom-testing (~> 2.0)
|
|
24
|
+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
|
11
25
|
activemodel (6.1.0.rc1)
|
|
12
26
|
activesupport (= 6.1.0.rc1)
|
|
13
27
|
activerecord (6.1.0.rc1)
|
|
@@ -19,21 +33,74 @@ GEM
|
|
|
19
33
|
minitest (>= 5.1)
|
|
20
34
|
tzinfo (~> 2.0)
|
|
21
35
|
zeitwerk (~> 2.3)
|
|
36
|
+
addressable (2.7.0)
|
|
37
|
+
public_suffix (>= 2.0.2, < 5.0)
|
|
38
|
+
aws-eventstream (1.1.0)
|
|
39
|
+
aws-partitions (1.424.0)
|
|
40
|
+
aws-sdk-core (3.112.0)
|
|
41
|
+
aws-eventstream (~> 1, >= 1.0.2)
|
|
42
|
+
aws-partitions (~> 1, >= 1.239.0)
|
|
43
|
+
aws-sigv4 (~> 1.1)
|
|
44
|
+
jmespath (~> 1.0)
|
|
45
|
+
aws-sdk-glacier (1.36.0)
|
|
46
|
+
aws-sdk-core (~> 3, >= 3.112.0)
|
|
47
|
+
aws-sigv4 (~> 1.1)
|
|
48
|
+
aws-sigv4 (1.2.2)
|
|
49
|
+
aws-eventstream (~> 1, >= 1.0.2)
|
|
50
|
+
builder (3.2.4)
|
|
22
51
|
concurrent-ruby (1.1.7)
|
|
23
52
|
connection_pool (2.2.3)
|
|
53
|
+
crack (0.4.3)
|
|
54
|
+
safe_yaml (~> 1.0.0)
|
|
55
|
+
crass (1.0.6)
|
|
24
56
|
diff-lcs (1.4.4)
|
|
57
|
+
dotenv (2.7.6)
|
|
58
|
+
erubi (1.10.0)
|
|
25
59
|
et-orbi (1.2.4)
|
|
26
60
|
tzinfo
|
|
27
|
-
fugit (1.4.
|
|
61
|
+
fugit (1.4.2)
|
|
28
62
|
et-orbi (~> 1.1, >= 1.1.8)
|
|
29
63
|
raabro (~> 1.4)
|
|
64
|
+
hashdiff (1.0.0)
|
|
30
65
|
i18n (1.8.5)
|
|
31
66
|
concurrent-ruby (~> 1.0)
|
|
67
|
+
jmespath (1.4.0)
|
|
68
|
+
loofah (2.9.0)
|
|
69
|
+
crass (~> 1.0.2)
|
|
70
|
+
nokogiri (>= 1.5.9)
|
|
71
|
+
method_source (1.0.0)
|
|
72
|
+
mini_portile2 (2.5.0)
|
|
32
73
|
minitest (5.14.2)
|
|
74
|
+
nokogiri (1.11.1)
|
|
75
|
+
mini_portile2 (~> 2.5.0)
|
|
76
|
+
racc (~> 1.4)
|
|
77
|
+
pg (1.2.3)
|
|
78
|
+
postgres-copy (1.5.0)
|
|
79
|
+
activerecord (>= 5.1)
|
|
80
|
+
pg (>= 0.17)
|
|
81
|
+
responders
|
|
82
|
+
public_suffix (4.0.4)
|
|
33
83
|
raabro (1.4.0)
|
|
84
|
+
racc (1.5.2)
|
|
34
85
|
rack (2.2.3)
|
|
86
|
+
rack-test (1.1.0)
|
|
87
|
+
rack (>= 1.0, < 3)
|
|
88
|
+
rails-dom-testing (2.0.3)
|
|
89
|
+
activesupport (>= 4.2.0)
|
|
90
|
+
nokogiri (>= 1.6)
|
|
91
|
+
rails-html-sanitizer (1.3.0)
|
|
92
|
+
loofah (~> 2.3)
|
|
93
|
+
railties (6.1.0.rc1)
|
|
94
|
+
actionpack (= 6.1.0.rc1)
|
|
95
|
+
activesupport (= 6.1.0.rc1)
|
|
96
|
+
method_source
|
|
97
|
+
rake (>= 0.8.7)
|
|
98
|
+
thor (~> 1.0)
|
|
35
99
|
rake (13.0.1)
|
|
36
100
|
redis (4.2.2)
|
|
101
|
+
responders (3.0.1)
|
|
102
|
+
actionpack (>= 5.0)
|
|
103
|
+
railties (>= 5.0)
|
|
37
104
|
rspec (3.9.0)
|
|
38
105
|
rspec-core (~> 3.9.0)
|
|
39
106
|
rspec-expectations (~> 3.9.0)
|
|
@@ -50,6 +117,7 @@ GEM
|
|
|
50
117
|
rspec-core (~> 3.0, >= 3.0.0)
|
|
51
118
|
sidekiq (>= 2.4.0)
|
|
52
119
|
rspec-support (3.9.3)
|
|
120
|
+
safe_yaml (1.0.5)
|
|
53
121
|
sidekiq (6.1.2)
|
|
54
122
|
connection_pool (>= 2.2.2)
|
|
55
123
|
rack (~> 2.0)
|
|
@@ -57,9 +125,15 @@ GEM
|
|
|
57
125
|
sidekiq-cron (1.2.0)
|
|
58
126
|
fugit (~> 1.1)
|
|
59
127
|
sidekiq (>= 4.2.1)
|
|
60
|
-
|
|
128
|
+
thor (1.1.0)
|
|
129
|
+
timecop (0.9.2)
|
|
61
130
|
tzinfo (2.0.2)
|
|
62
131
|
concurrent-ruby (~> 1.0)
|
|
132
|
+
vcr (6.0.0)
|
|
133
|
+
webmock (3.7.6)
|
|
134
|
+
addressable (>= 2.3.6)
|
|
135
|
+
crack (>= 0.3.2)
|
|
136
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
63
137
|
zeitwerk (2.4.1)
|
|
64
138
|
|
|
65
139
|
PLATFORMS
|
|
@@ -67,11 +141,16 @@ PLATFORMS
|
|
|
67
141
|
|
|
68
142
|
DEPENDENCIES
|
|
69
143
|
activerecord (~> 6)
|
|
144
|
+
dotenv
|
|
145
|
+
pg
|
|
146
|
+
postgres-copy
|
|
70
147
|
rake (~> 13.0)
|
|
71
148
|
rspec (~> 3.0)
|
|
72
149
|
rspec-sidekiq
|
|
73
|
-
sqlite3
|
|
74
150
|
tartarus-rb!
|
|
151
|
+
timecop
|
|
152
|
+
vcr
|
|
153
|
+
webmock
|
|
75
154
|
|
|
76
155
|
BUNDLED WITH
|
|
77
156
|
2.1.4
|
data/README.md
CHANGED
|
@@ -56,12 +56,22 @@ if File.exist?(schedule_file) && Sidekiq.server?
|
|
|
56
56
|
item.timestamp_field = :created_at
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
glacier_configuration = Tartarus::RemoteStorage::Glacier::Configuration.build(
|
|
60
|
+
aws_key: ENV.fetch("AWS_KEY"),
|
|
61
|
+
aws_secret: ENV.fetch("AWS_SECRET"),
|
|
62
|
+
aws_region: ENV.fetch("AWS_REGION"),
|
|
63
|
+
vault_name: ENV.fetch("GLACIER_VAULT_NAME"),
|
|
64
|
+
root_path: Rails.root.to_s,
|
|
65
|
+
archive_registry_factory: ArchiveRegistry,
|
|
66
|
+
)
|
|
67
|
+
|
|
59
68
|
tartarus.register do |item|
|
|
60
69
|
item.model = YetAnotherModel
|
|
61
70
|
item.cron = "5 6 * * *"
|
|
62
71
|
item.queue = "default"
|
|
63
72
|
item.timestamp_field = :created_at
|
|
64
73
|
item.archive_items_older_than = -> { 1.week.ago }
|
|
74
|
+
item.remote_storage = Tartarus::RemoteStorage::Glacier.new(glacier_configuration)
|
|
65
75
|
end
|
|
66
76
|
|
|
67
77
|
tartarus.schedule # this method must be called to create jobs for sidekiq-cron!
|
|
@@ -80,6 +90,86 @@ You can use the following config params:
|
|
|
80
90
|
- `timestamp_field` - required, used for performing a query using the value from `archive_items_older_than`
|
|
81
91
|
- `archive_with` - optional (defaults to `delete_all`). Could be `delete_all`, `destroy_all`, `delete_all_without_batches`, `destroy_all_without_batches`, `delete_all_using_limit_in_batches`
|
|
82
92
|
- `batch_size` - optional (defaults to `10_000`, used with `delete_all_using_limit_in_batches` strategy)
|
|
93
|
+
- `remote_storage` - optional (defaults to `Tartarus::RemoteStorage::Null` which does nothing). Use this option if you want store the data somewhere before deleting it.
|
|
94
|
+
|
|
95
|
+
### Remote Storage
|
|
96
|
+
|
|
97
|
+
Currently, only `Glacier` (for AWS Glacier) is supported. Also, it works only with Postgres database and requires [postgres-copy](https://github.com/diogob/postgres-copy).
|
|
98
|
+
|
|
99
|
+
To take advantage of this feature you will need a couple of things:
|
|
100
|
+
1. Apply `acts_as_copy_target` to the archivable model (from `postgres-copy` gem).
|
|
101
|
+
2. Create a model that will be used as a registry for all uploads that happened.
|
|
102
|
+
|
|
103
|
+
If you want to make `Version` model archivable and use `ArchiveRegistry` as the registry, you will need the following models and tables:
|
|
104
|
+
|
|
105
|
+
``` rb
|
|
106
|
+
database.create_table(:archive_registries) do |t|
|
|
107
|
+
t.string :glacier_location, null: false
|
|
108
|
+
t.string :glacier_checksum, null: false
|
|
109
|
+
t.string :glacier_archive_id, null: false
|
|
110
|
+
t.string :archivable_model, null: false
|
|
111
|
+
t.string :tenant_id_field
|
|
112
|
+
t.string :tenant_id
|
|
113
|
+
t.datetime :completed_at, null: false
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
database.create_table(:versions) do |t|
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class Version < ApplicationRecord
|
|
120
|
+
acts_as_copy_target
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class ArchiveRegistry < ApplicationRecord
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
You can use the above schema for the registry model as it contains all needed fields.
|
|
128
|
+
|
|
129
|
+
To initialize the service:
|
|
130
|
+
|
|
131
|
+
``` rb
|
|
132
|
+
glacier_configuration = Tartarus::RemoteStorage::Glacier::Configuration.build(
|
|
133
|
+
aws_key: ENV.fetch("AWS_KEY"),
|
|
134
|
+
aws_secret: ENV.fetch("AWS_SECRET"),
|
|
135
|
+
aws_region: ENV.fetch("AWS_REGION"),
|
|
136
|
+
vault_name: ENV.fetch("GLACIER_VAULT_NAME"),
|
|
137
|
+
root_path: Rails.root.to_s,
|
|
138
|
+
archive_registry_factory: ArchiveRegistry,
|
|
139
|
+
)
|
|
140
|
+
Tartarus::RemoteStorage::Glacier.new(glacier_configuration)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
You can also pass `account_id` (by default "-" string will be used):
|
|
144
|
+
|
|
145
|
+
``` rb
|
|
146
|
+
glacier_configuration = Tartarus::RemoteStorage::Glacier::Configuration.build(
|
|
147
|
+
aws_key: ENV.fetch("AWS_KEY"),
|
|
148
|
+
aws_secret: ENV.fetch("AWS_SECRET"),
|
|
149
|
+
aws_region: ENV.fetch("AWS_REGION"),
|
|
150
|
+
vault_name: ENV.fetch("GLACIER_VAULT_NAME"),
|
|
151
|
+
root_path: Rails.root.to_s,
|
|
152
|
+
archive_registry_factory: ArchiveRegistry,
|
|
153
|
+
account_id: "some_account_id"
|
|
154
|
+
)
|
|
155
|
+
Tartarus::RemoteStorage::Glacier.new(glacier_configuration)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
If you know what you are doing, you can add your own storage, as long as it complies with the following interface:
|
|
159
|
+
|
|
160
|
+
``` rb
|
|
161
|
+
class Glacier
|
|
162
|
+
attr_reader :configuration
|
|
163
|
+
private :configuration
|
|
164
|
+
|
|
165
|
+
def initialize(configuration)
|
|
166
|
+
@configuration = configuration
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def store(collection, archivable_model, tenant_id: nil, tenant_id_field: nil)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
```
|
|
83
173
|
|
|
84
174
|
### Testing before actually using it
|
|
85
175
|
|
|
@@ -88,7 +178,6 @@ You might want to verify that the gem works in the way you expect it to work. Fo
|
|
|
88
178
|
1. scheduling/enqueueing: use `Tartarus::ScheduleArchivingModel#schedule` - for example, `Tartarus::ScheduleArchivingModel.new.schedule("PaperTrailVersion")`, it's going to enqueue either `Tartarus::Sidekiq::ArchiveModelWithTenantJob` or `Tartarus::Sidekiq::ArchiveModelWithoutTenantJob`, depending on the config.
|
|
89
179
|
2. execution of the archiving logic: use `Tartarus::ArchiveModelWithTenant#archive` (for example, `Tartarus::ArchiveModelWithTenant.new.archive("PaperTrailVersion", "User")`) or `Tartarus::ArchiveModelWithoutTenant#archive` (for example, `Tartarus::ArchiveModelWithoutTenant.new.archive("PaperTrailVersion")`)
|
|
90
180
|
|
|
91
|
-
|
|
92
181
|
You might also want to check `spec/integration` to get an idea how the integration tests were written.
|
|
93
182
|
|
|
94
183
|
## Development
|
|
@@ -97,10 +186,6 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
|
97
186
|
|
|
98
187
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
99
188
|
|
|
100
|
-
## TODO
|
|
101
|
-
|
|
102
|
-
- add support for uploading archives to AWS Glacier before deleting items
|
|
103
|
-
|
|
104
189
|
## Contributing
|
|
105
190
|
|
|
106
191
|
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/tartarus-rb.
|
data/lib/tartarus.rb
CHANGED
|
@@ -18,7 +18,16 @@ require "tartarus/rb/version"
|
|
|
18
18
|
require "tartarus/registry"
|
|
19
19
|
require "tartarus/repository"
|
|
20
20
|
require "tartarus/schedule_archiving_model"
|
|
21
|
+
require "tartarus/remote_storage"
|
|
22
|
+
require "tartarus/remote_storage/null"
|
|
23
|
+
require "tartarus/remote_storage/glacier"
|
|
24
|
+
require "tartarus/remote_storage/glacier/client"
|
|
25
|
+
require "tartarus/remote_storage/glacier/file"
|
|
26
|
+
require "tartarus/remote_storage/glacier/csv_export"
|
|
27
|
+
require "tartarus/remote_storage/glacier/register_upload"
|
|
28
|
+
require "tartarus/remote_storage/glacier/configuration"
|
|
21
29
|
require "sidekiq/cron/job"
|
|
30
|
+
require "aws-sdk-glacier"
|
|
22
31
|
require "sidekiq"
|
|
23
32
|
|
|
24
33
|
class Tartarus
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class Tartarus::ArchivableItem
|
|
2
2
|
REQUIRED_ATTRIBUTES_NAMES = %i(model cron queue archive_items_older_than timestamp_field active_job
|
|
3
3
|
archive_with tenant_value_source).freeze
|
|
4
|
-
OPTIONAL_ATTRIBUTES_NAMES = %i(tenants_range tenant_id_field batch_size).freeze
|
|
4
|
+
OPTIONAL_ATTRIBUTES_NAMES = %i(tenants_range tenant_id_field batch_size remote_storage).freeze
|
|
5
5
|
|
|
6
6
|
attr_accessor *(REQUIRED_ATTRIBUTES_NAMES + OPTIONAL_ATTRIBUTES_NAMES)
|
|
7
7
|
|
|
@@ -67,6 +67,10 @@ class Tartarus::ArchivableItem
|
|
|
67
67
|
model.to_s == provided_model_name.to_s
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
def remote_storage
|
|
71
|
+
@remote_storage || Tartarus::RemoteStorage::Null
|
|
72
|
+
end
|
|
73
|
+
|
|
70
74
|
private
|
|
71
75
|
|
|
72
76
|
def validate_presence
|
|
@@ -9,8 +9,10 @@ class Tartarus::ArchiveModelWithTenant
|
|
|
9
9
|
|
|
10
10
|
def archive(model_name, tenant_id)
|
|
11
11
|
archivable_item = registry.find_by_model(model_name)
|
|
12
|
-
|
|
13
|
-
archivable_item.
|
|
12
|
+
collection = collection_to_archive(model_name, archivable_item, tenant_id)
|
|
13
|
+
archivable_item.remote_storage.store(collection, model_name, tenant_id: tenant_id,
|
|
14
|
+
tenant_id_field: archivable_item.tenant_id_field)
|
|
15
|
+
archivable_item.archive_strategy.call(collection)
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
private
|
|
@@ -9,8 +9,9 @@ class Tartarus::ArchiveModelWithoutTenant
|
|
|
9
9
|
|
|
10
10
|
def archive(model_name)
|
|
11
11
|
archivable_item = registry.find_by_model(model_name)
|
|
12
|
-
|
|
13
|
-
archivable_item.
|
|
12
|
+
collection = collection_to_archive(model_name, archivable_item)
|
|
13
|
+
archivable_item.remote_storage.store(collection, model_name)
|
|
14
|
+
archivable_item.archive_strategy.call(collection)
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
private
|
data/lib/tartarus/rb/version.rb
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
class Tartarus
|
|
2
|
+
module RemoteStorage
|
|
3
|
+
class Glacier
|
|
4
|
+
attr_reader :configuration, :clock
|
|
5
|
+
private :configuration, :clock
|
|
6
|
+
|
|
7
|
+
def initialize(configuration, clock: Time)
|
|
8
|
+
@configuration = configuration
|
|
9
|
+
@clock = clock
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def store(collection, archivable_model, tenant_id: nil, tenant_id_field: nil)
|
|
13
|
+
path_to_file = path_to_file_for(archivable_model, tenant_id_field, tenant_id)
|
|
14
|
+
export_to_csv(collection, path_to_file)
|
|
15
|
+
glacier_file = Tartarus::RemoteStorage::Glacier::File.new(::File.new(path_to_file))
|
|
16
|
+
glacier_response = upload(glacier_file)
|
|
17
|
+
register_upload(glacier_response, archivable_model, tenant_id_field, tenant_id)
|
|
18
|
+
ensure
|
|
19
|
+
glacier_file.delete_from_local_storage if glacier_file
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def upload(file)
|
|
25
|
+
client.upload_archive(configuration.vault_name, file)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def client
|
|
29
|
+
@client ||= begin
|
|
30
|
+
Tartarus::RemoteStorage::Glacier::Client.new(
|
|
31
|
+
key: configuration.aws_key,
|
|
32
|
+
secret: configuration.aws_secret,
|
|
33
|
+
region: configuration.aws_region,
|
|
34
|
+
account_id: configuration.account_id,
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def export_to_csv(collection, path_to_file)
|
|
40
|
+
Tartarus::RemoteStorage::Glacier::CsvExport
|
|
41
|
+
.new(configuration.storage_directory)
|
|
42
|
+
.export(collection, path_to_file)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def register_upload(glacier_response, archivable_model, tenant_id_field, tenant_id)
|
|
47
|
+
Tartarus::RemoteStorage::Glacier::RegisterUpload.new(configuration.archive_registry_factory).register(
|
|
48
|
+
glacier_response,
|
|
49
|
+
archivable_model,
|
|
50
|
+
tenant_id_field,
|
|
51
|
+
tenant_id
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def path_to_file_for(archivable_model, tenant_id_field, tenant_id)
|
|
56
|
+
"#{configuration.storage_directory}/#{archivable_model}_#{tenant_id_field}_#{tenant_id}_#{clock.now.to_i}.csv"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Tartarus
|
|
4
|
+
module RemoteStorage
|
|
5
|
+
class Glacier
|
|
6
|
+
class Client
|
|
7
|
+
attr_reader :client, :account_id
|
|
8
|
+
private :client, :account_id
|
|
9
|
+
|
|
10
|
+
def initialize(key:, secret:, region:, account_id:)
|
|
11
|
+
@client = Aws::Glacier::Client.new(credentials: Aws::Credentials.new(key, secret), region: region)
|
|
12
|
+
@account_id = account_id
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def upload_archive(vault_name, file)
|
|
16
|
+
client.upload_archive(
|
|
17
|
+
account_id: account_id,
|
|
18
|
+
archive_description: file.description,
|
|
19
|
+
body: file.body,
|
|
20
|
+
vault_name: vault_name
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
class Tartarus
|
|
2
|
+
module RemoteStorage
|
|
3
|
+
class Glacier
|
|
4
|
+
class Configuration
|
|
5
|
+
DEFAULT_ACCOUNT_ID = "-"
|
|
6
|
+
private_constant :DEFAULT_ACCOUNT_ID
|
|
7
|
+
|
|
8
|
+
REQUIRED_ATTRIBUTES_NAMES = %i(aws_key aws_secret aws_region account_id vault_name root_path
|
|
9
|
+
archive_registry_factory).freeze
|
|
10
|
+
attr_accessor *REQUIRED_ATTRIBUTES_NAMES
|
|
11
|
+
|
|
12
|
+
def self.build(aws_key:, aws_secret:, aws_region:, account_id: DEFAULT_ACCOUNT_ID, vault_name:, root_path:, archive_registry_factory:)
|
|
13
|
+
new.tap do |config|
|
|
14
|
+
config.aws_key = aws_key
|
|
15
|
+
config.aws_secret = aws_secret
|
|
16
|
+
config.aws_region = aws_region
|
|
17
|
+
config.account_id = account_id
|
|
18
|
+
config.vault_name = vault_name
|
|
19
|
+
config.root_path = root_path
|
|
20
|
+
config.archive_registry_factory = archive_registry_factory
|
|
21
|
+
config.validate!
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def validate!
|
|
26
|
+
validate_presence
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def storage_directory
|
|
30
|
+
"#{root_path}/tmp/tartarus/#{archive_registry_factory}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def validate_presence
|
|
36
|
+
REQUIRED_ATTRIBUTES_NAMES.each do |attribute|
|
|
37
|
+
raise ":#{attribute} must be present" if public_send(attribute).nil?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class Tartarus
|
|
2
|
+
module RemoteStorage
|
|
3
|
+
class Glacier
|
|
4
|
+
class CsvExport
|
|
5
|
+
FILE_MODE = "w"
|
|
6
|
+
DELIMITER = ";"
|
|
7
|
+
NO_PATH_FOR_EXPORT = nil
|
|
8
|
+
ENCODING = "UTF-8"
|
|
9
|
+
private_constant :FILE_MODE, :DELIMITER, :NO_PATH_FOR_EXPORT, :ENCODING
|
|
10
|
+
|
|
11
|
+
attr_reader :storage_directory, :file_service, :file_utils
|
|
12
|
+
private :storage_directory, :file_service, :file_utils
|
|
13
|
+
|
|
14
|
+
def initialize(storage_directory, file_service: ::File, file_utils: FileUtils)
|
|
15
|
+
@storage_directory = storage_directory
|
|
16
|
+
@file_service = file_service
|
|
17
|
+
@file_utils = file_utils
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def export(collection, path_to_file)
|
|
21
|
+
with_csv_export_file(path_to_file) do |file|
|
|
22
|
+
collection.copy_to(NO_PATH_FOR_EXPORT, delimiter: DELIMITER) do |line|
|
|
23
|
+
file.write(line.force_encoding(ENCODING))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def with_csv_export_file(path_to_file, &block)
|
|
31
|
+
file_utils.mkdir_p(storage_directory) if !file_service.exist?(storage_directory)
|
|
32
|
+
|
|
33
|
+
file_service.open(path_to_file, FILE_MODE, &block)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "delegate"
|
|
2
|
+
|
|
3
|
+
class Tartarus
|
|
4
|
+
module RemoteStorage
|
|
5
|
+
class Glacier
|
|
6
|
+
class File < SimpleDelegator
|
|
7
|
+
def description
|
|
8
|
+
file_service.basename(self, ".*")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def body
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def checksum
|
|
16
|
+
Digest::SHA256.file(path)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def delete_from_local_storage
|
|
20
|
+
file_service.delete(path)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def file_service
|
|
26
|
+
::File
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class Tartarus
|
|
2
|
+
module RemoteStorage
|
|
3
|
+
class Glacier
|
|
4
|
+
class RegisterUpload
|
|
5
|
+
attr_reader :archive_registry_factory, :clock
|
|
6
|
+
private :archive_registry_factory, :clock
|
|
7
|
+
|
|
8
|
+
def initialize(archive_registry_factory, clock: Time)
|
|
9
|
+
@archive_registry_factory = archive_registry_factory
|
|
10
|
+
@clock = clock
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def register(glacier_response, archivable_model, tenant_id_field, tenant_id)
|
|
14
|
+
archive_registry_factory.new.tap do |archive_registry|
|
|
15
|
+
archive_registry.glacier_location = glacier_response.location
|
|
16
|
+
archive_registry.glacier_checksum = glacier_response.checksum
|
|
17
|
+
archive_registry.glacier_archive_id = glacier_response.archive_id
|
|
18
|
+
archive_registry.archivable_model = archivable_model
|
|
19
|
+
archive_registry.tenant_id_field = tenant_id_field
|
|
20
|
+
archive_registry.tenant_id = tenant_id
|
|
21
|
+
archive_registry.completed_at = clock.now
|
|
22
|
+
archive_registry.save!
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/tartarus-rb.gemspec
CHANGED
|
@@ -29,11 +29,17 @@ Gem::Specification.new do |spec|
|
|
|
29
29
|
|
|
30
30
|
spec.add_dependency "sidekiq", ">= 5"
|
|
31
31
|
spec.add_dependency "sidekiq-cron", "~> 1"
|
|
32
|
+
spec.add_dependency "aws-sdk-glacier"
|
|
32
33
|
|
|
33
34
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
34
35
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
35
36
|
spec.add_development_dependency "rspec-sidekiq"
|
|
36
37
|
|
|
37
38
|
spec.add_development_dependency "activerecord", "~> 6"
|
|
38
|
-
spec.add_development_dependency "
|
|
39
|
+
spec.add_development_dependency "pg"
|
|
40
|
+
spec.add_development_dependency "vcr"
|
|
41
|
+
spec.add_development_dependency "webmock"
|
|
42
|
+
spec.add_development_dependency "dotenv"
|
|
43
|
+
spec.add_development_dependency "postgres-copy"
|
|
44
|
+
spec.add_development_dependency "timecop"
|
|
39
45
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tartarus-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Karol Galanciak
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-
|
|
11
|
+
date: 2021-02-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sidekiq
|
|
@@ -38,6 +38,20 @@ dependencies:
|
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '1'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: aws-sdk-glacier
|
|
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'
|
|
41
55
|
- !ruby/object:Gem::Dependency
|
|
42
56
|
name: rake
|
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -95,7 +109,77 @@ dependencies:
|
|
|
95
109
|
- !ruby/object:Gem::Version
|
|
96
110
|
version: '6'
|
|
97
111
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
112
|
+
name: pg
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: vcr
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: webmock
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '0'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: dotenv
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - ">="
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '0'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ">="
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: postgres-copy
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - ">="
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '0'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - ">="
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '0'
|
|
181
|
+
- !ruby/object:Gem::Dependency
|
|
182
|
+
name: timecop
|
|
99
183
|
requirement: !ruby/object:Gem::Requirement
|
|
100
184
|
requirements:
|
|
101
185
|
- - ">="
|
|
@@ -116,6 +200,7 @@ executables: []
|
|
|
116
200
|
extensions: []
|
|
117
201
|
extra_rdoc_files: []
|
|
118
202
|
files:
|
|
203
|
+
- ".env.sample"
|
|
119
204
|
- ".gitignore"
|
|
120
205
|
- ".rspec"
|
|
121
206
|
- ".travis.yml"
|
|
@@ -144,6 +229,14 @@ files:
|
|
|
144
229
|
- lib/tartarus/rb.rb
|
|
145
230
|
- lib/tartarus/rb/version.rb
|
|
146
231
|
- lib/tartarus/registry.rb
|
|
232
|
+
- lib/tartarus/remote_storage.rb
|
|
233
|
+
- lib/tartarus/remote_storage/glacier.rb
|
|
234
|
+
- lib/tartarus/remote_storage/glacier/client.rb
|
|
235
|
+
- lib/tartarus/remote_storage/glacier/configuration.rb
|
|
236
|
+
- lib/tartarus/remote_storage/glacier/csv_export.rb
|
|
237
|
+
- lib/tartarus/remote_storage/glacier/file.rb
|
|
238
|
+
- lib/tartarus/remote_storage/glacier/register_upload.rb
|
|
239
|
+
- lib/tartarus/remote_storage/null.rb
|
|
147
240
|
- lib/tartarus/repository.rb
|
|
148
241
|
- lib/tartarus/schedule_archiving_model.rb
|
|
149
242
|
- lib/tartarus/sidekiq.rb
|