rds-rotate-db-snapshots 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +19 -4
- data/.github/workflows/codeql.yml +2 -2
- data/.rspec +1 -0
- data/Gemfile +9 -10
- data/README.md +2 -3
- data/Rakefile +0 -15
- data/VERSION +1 -1
- data/bin/rds-rotate-db-snapshots +18 -247
- data/lib/rds_rotate_db_snapshots/action_wrappers.rb +23 -0
- data/lib/rds_rotate_db_snapshots/actions.rb +105 -0
- data/lib/rds_rotate_db_snapshots/options_parser.rb +110 -0
- data/lib/rds_rotate_db_snapshots/rds_client.rb +20 -0
- data/lib/rds_rotate_db_snapshots.rb +58 -0
- data/rds-rotate-db-snapshots.gemspec +17 -10
- data/spec/helper.rb +81 -0
- data/spec/rds_rotate_db_snapshots_spec.rb +62 -0
- metadata +27 -8
- data/lib/.empty +0 -1
- data/test/helper.rb +0 -18
- data/test/test_rds-rotate-db-snapshots.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ddfaa42b87e8ad4f9cabafcea086ce82d0f351068d05db7da97b53fa5080a0f
|
4
|
+
data.tar.gz: dac01f0570c16a5a5a205253f0ca3e6cd26d7f7ed22377da3f9f1bad2e99d87b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d5d586b5ff033df7c7a4d131259d7f47b1f30e2592b616fc0e6644fcbd4999d0e24815dba96c9d335e8d6e35446293bee2b59f83ab286eda4a32a3f39cbaf38
|
7
|
+
data.tar.gz: 5f9339752c7a963981f1d6be97ed321a0897f8028d52f632b6dae0fb81e2e495327e127f7dec3d4040b35610ad3a7c56d9206b8220a376485c87f4372fe88470
|
data/.github/workflows/ci.yml
CHANGED
@@ -2,16 +2,16 @@ name: "CI"
|
|
2
2
|
|
3
3
|
on:
|
4
4
|
push:
|
5
|
-
branches: ["
|
5
|
+
branches: ["main"]
|
6
6
|
pull_request:
|
7
|
-
branches: ["
|
7
|
+
branches: ["main"]
|
8
8
|
|
9
9
|
jobs:
|
10
10
|
test:
|
11
11
|
runs-on: ubuntu-20.04
|
12
12
|
strategy:
|
13
13
|
matrix:
|
14
|
-
ruby_version: [2.7, 3.0, 3.1
|
14
|
+
ruby_version: [2.7, 3.0, 3.1]
|
15
15
|
steps:
|
16
16
|
- name: Checkout code
|
17
17
|
uses: actions/checkout@v3
|
@@ -25,4 +25,19 @@ jobs:
|
|
25
25
|
- name: Bundle Install
|
26
26
|
run: bundle install
|
27
27
|
- name: Test
|
28
|
-
run: bundle exec
|
28
|
+
run: bundle exec rspec
|
29
|
+
- name: Coveralls Parallel
|
30
|
+
uses: coverallsapp/github-action@master
|
31
|
+
with:
|
32
|
+
github-token: ${{ secrets.github_token }}
|
33
|
+
flag-name: run-${{ matrix.ruby_version }}
|
34
|
+
parallel: true
|
35
|
+
finish:
|
36
|
+
needs: test
|
37
|
+
runs-on: ubuntu-latest
|
38
|
+
steps:
|
39
|
+
- name: Coveralls Finished
|
40
|
+
uses: coverallsapp/github-action@master
|
41
|
+
with:
|
42
|
+
github-token: ${{ secrets.github_token }}
|
43
|
+
parallel-finished: true
|
@@ -13,10 +13,10 @@ name: "CodeQL"
|
|
13
13
|
|
14
14
|
on:
|
15
15
|
push:
|
16
|
-
branches: [ "
|
16
|
+
branches: [ "main" ]
|
17
17
|
pull_request:
|
18
18
|
# The branches below must be a subset of the branches above
|
19
|
-
branches: [ "
|
19
|
+
branches: [ "main" ]
|
20
20
|
schedule:
|
21
21
|
- cron: '22 4 * * 2'
|
22
22
|
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-w --color
|
data/Gemfile
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
2
|
|
3
3
|
gem 'aws-sdk-rds', '~> 1'
|
4
|
-
|
5
|
-
# Add dependencies to develop your gem here.
|
6
|
-
# Include everything needed to run rake, tests, features, etc.
|
7
|
-
group :development, :test do
|
8
|
-
gem 'bundler'
|
9
|
-
gem 'simplecov'
|
10
|
-
end
|
4
|
+
gem "rake"
|
11
5
|
|
12
6
|
group :development do
|
13
7
|
gem 'juwelier'
|
8
|
+
gem "pry"
|
9
|
+
gem "pry-byebug"
|
14
10
|
end
|
15
11
|
|
16
12
|
group :test do
|
17
|
-
gem
|
18
|
-
gem
|
19
|
-
gem
|
13
|
+
gem "rspec", ">= 3.2"
|
14
|
+
gem "rspec-mocks", ">= 3"
|
15
|
+
gem "rubocop", "~> 0.50.0"
|
16
|
+
gem "simplecov", ">= 0.13"
|
17
|
+
gem 'simplecov-lcov', '~> 0.8.0'
|
18
|
+
gem "webmock"
|
20
19
|
end
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# rds-rotate-db-snapshots
|
2
2
|
|
3
3
|
[<img src="https://badge.fury.io/rb/rds-rotate-db-snapshots.svg" alt="Gem
|
4
|
-
Version" />](https://badge.fury.io/rb/rds-rotate-db-snapshots) [![CI](https://github.com/serg-kovalev/rds-rotate-db-snapshots/actions/workflows/ci.yml/badge.svg?query=branch%
|
4
|
+
Version" />](https://badge.fury.io/rb/rds-rotate-db-snapshots) [![CI](https://github.com/serg-kovalev/rds-rotate-db-snapshots/actions/workflows/ci.yml/badge.svg?query=branch%3Amain+event%3Apush)](https://github.com/serg-kovalev/rds-rotate-db-snapshots/actions/workflows/ci.yml?query=branch%3Amain+event%3Apush) [![CodeQL](https://github.com/serg-kovalev/rds-rotate-db-snapshots/actions/workflows/codeql.yml/badge.svg?query=branch%3Amain+event%3Apush)](https://github.com/serg-kovalev/rds-rotate-db-snapshots/actions/workflows/codeql.yml?query=branch%3Amain+event%3Apush)
|
5
5
|
|
6
6
|
Provides a simple way to rotate db snapshots in Amazon Relational Database
|
7
7
|
Service (RDS).
|
@@ -11,7 +11,6 @@ Service (RDS).
|
|
11
11
|
- 2.7
|
12
12
|
- 3.1
|
13
13
|
- 3.2
|
14
|
-
- jruby
|
15
14
|
|
16
15
|
## Usage
|
17
16
|
|
@@ -65,7 +64,7 @@ show the messages.
|
|
65
64
|
|
66
65
|
## Contributing to rds-rotate-db-snapshots
|
67
66
|
|
68
|
-
- Check out the latest
|
67
|
+
- Check out the latest main to make sure the feature hasn't been
|
69
68
|
implemented or the bug hasn't been fixed yet
|
70
69
|
- Check out the issue tracker to make sure someone already hasn't requested
|
71
70
|
it and/or contributed it
|
data/Rakefile
CHANGED
@@ -22,21 +22,6 @@ Juwelier::Tasks.new do |gem|
|
|
22
22
|
end
|
23
23
|
Juwelier::RubygemsDotOrgTasks.new
|
24
24
|
|
25
|
-
require 'rake/testtask'
|
26
|
-
Rake::TestTask.new(:test) do |test|
|
27
|
-
test.pattern = 'test/**/test_*.rb'
|
28
|
-
test.verbose = true
|
29
|
-
end
|
30
|
-
|
31
|
-
# require 'simplecov'
|
32
|
-
# Rcov::RcovTask.new do |test|
|
33
|
-
# test.libs << 'test'
|
34
|
-
# test.pattern = 'test/**/test_*.rb'
|
35
|
-
# test.verbose = true
|
36
|
-
# end
|
37
|
-
|
38
|
-
task :default => :test
|
39
|
-
|
40
25
|
require 'rdoc/task'
|
41
26
|
Rake::RDocTask.new do |rdoc|
|
42
27
|
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/bin/rds-rotate-db-snapshots
CHANGED
@@ -1,200 +1,25 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
require 'aws-sdk-rds'
|
5
|
-
require 'optparse'
|
3
|
+
require_relative '../lib/rds_rotate_db_snapshots'
|
6
4
|
|
7
|
-
|
8
|
-
:aws_access_key => ENV["AWS_ACCESS_KEY_ID"],
|
9
|
-
:aws_secret_access_key => ENV["AWS_SECRET_ACCESS_KEY"],
|
10
|
-
:aws_session_token => ENV["AWS_SESSION_TOKEN"],
|
11
|
-
:aws_region => ENV["AWS_REGION"],
|
12
|
-
:pattern => nil,
|
13
|
-
:by_tags => nil,
|
14
|
-
:dry_run => false,
|
15
|
-
:backoff_limit => 15,
|
16
|
-
:create_snapshot => nil
|
17
|
-
}
|
5
|
+
rrds = RdsRotateDbSnapshots.new(script_name: File.basename($0), cli: true)
|
18
6
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
:weekly => { :seconds => 7 * 24 * 60 * 60, :format => '%Y-%W', :keep => 0, :keeping => {} },
|
23
|
-
:monthly => { :seconds => 30 * 24 * 60 * 60, :format => '%Y-%m', :keep => 0, :keeping => {} },
|
24
|
-
:yearly => { :seconds => 12 * 30 * 24 * 60 * 60, :format => '%Y', :keep => 0, :keeping => {} },
|
25
|
-
}
|
26
|
-
def backoff()
|
27
|
-
$backoffed = $backoffed + 1
|
28
|
-
|
29
|
-
if $opts[:backoff_limit] > 0 && $opts[:backoff_limit] < $backoffed
|
30
|
-
puts "Too many backoff attempts. Sorry it didn't work out."
|
31
|
-
exit 2
|
32
|
-
end
|
33
|
-
|
34
|
-
naptime = rand(60) * $backoffed
|
35
|
-
puts "Backing off for #{naptime} seconds..."
|
36
|
-
sleep naptime
|
37
|
-
end
|
38
|
-
|
39
|
-
def rotate_em(snapshots)
|
40
|
-
# poor man's way to get a deep copy of our time_periods definition hash
|
41
|
-
periods = Marshal.load(Marshal.dump($time_periods))
|
42
|
-
|
43
|
-
snapshots.each do |snapshot|
|
44
|
-
time = snapshot[:snapshot_create_time]
|
45
|
-
db_id = snapshot[:db_instance_identifier]
|
46
|
-
snapshot_id = snapshot[:db_snapshot_identifier]
|
47
|
-
description = snapshot_id
|
48
|
-
keep_reason = nil
|
49
|
-
|
50
|
-
if $opts[:pattern] && description !~ /#{$opts[:pattern]}/
|
51
|
-
puts " #{time.strftime '%Y-%m-%d %H:%M:%S'} #{snapshot_id} Skipping snapshot with description #{description}"
|
52
|
-
next
|
53
|
-
end
|
54
|
-
|
55
|
-
periods.keys.sort { |a, b| periods[a][:seconds] <=> periods[b][:seconds] }.each do |period|
|
56
|
-
period_info = periods[period]
|
57
|
-
keep = period_info[:keep]
|
58
|
-
keeping = period_info[:keeping]
|
59
|
-
|
60
|
-
time_string = time.strftime period_info[:format]
|
61
|
-
if Time.now - time < keep * period_info[:seconds]
|
62
|
-
if !keeping.key?(time_string) && keeping.length < keep
|
63
|
-
keep_reason = period
|
64
|
-
keeping[time_string] = snapshot
|
65
|
-
end
|
66
|
-
break
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
if keep_reason.nil? && snapshot == snapshots.last && $opts[:keep_last]
|
71
|
-
keep_reason = 'last snapshot'
|
72
|
-
end
|
73
|
-
|
74
|
-
if !keep_reason.nil?
|
75
|
-
puts " #{time.strftime '%Y-%m-%d %H:%M:%S'} #{snapshot_id} Keeping for #{keep_reason}"
|
76
|
-
else
|
77
|
-
puts " #{time.strftime '%Y-%m-%d %H:%M:%S'} #{snapshot_id} Deleting"
|
78
|
-
begin
|
79
|
-
$rds.delete_db_snapshot(db_snapshot_identifier: snapshot_id) unless $opts[:dry_run]
|
80
|
-
rescue Aws::RDS::Errors => e
|
81
|
-
backoff()
|
82
|
-
retry
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def create_snapshot(name, db_indentifier_ids)
|
89
|
-
if !!name
|
90
|
-
name = name.gsub(/[^a-zA-Z0-9\-]/, '')
|
91
|
-
if name.size > 0
|
92
|
-
name = "#{name}-#{Time.now.strftime('%Y%m%d%H%M%S')}"
|
93
|
-
db_indentifier_ids.each do |db_id|
|
94
|
-
begin
|
95
|
-
$rds.create_db_snapshot(db_snapshot_identifier: name, db_instance_identifier: db_id) unless $opts[:dry_run]
|
96
|
-
puts " #{Time.now.strftime '%Y-%m-%d %H:%M:%S'} Creation snapshot #{name} is pending (db: #{db_id})"
|
97
|
-
rescue Aws::RDS::Errors::InvalidDBInstanceStateFault => e
|
98
|
-
backoff()
|
99
|
-
retry
|
100
|
-
end
|
101
|
-
end
|
102
|
-
else
|
103
|
-
puts "invalid snapshot name format - #{name}"
|
104
|
-
exit 1
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def split_tag(hash,v)
|
110
|
-
v.split(',').each do |pair|
|
111
|
-
tag, value = pair.split('=',2)
|
112
|
-
if value.nil?
|
113
|
-
puts "invalid tag=value format"
|
114
|
-
exit 1
|
115
|
-
end
|
116
|
-
hash[tag] = value
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
def get_db_snapshots(options)
|
121
|
-
snapshots = []
|
122
|
-
response = $rds.describe_db_snapshots(options)
|
123
|
-
while true do
|
124
|
-
snapshots += response.db_snapshots
|
125
|
-
break unless response[:marker]
|
126
|
-
|
127
|
-
response = $rds.describe_db_snapshots(options.merge(marker: response[:marker]))
|
128
|
-
end
|
129
|
-
snapshots
|
7
|
+
if rrds.options[:aws_access_key].nil? || rrds.options[:aws_secret_access_key].nil?
|
8
|
+
puts "You must specify your Amazon credentials via --aws-access-key and --aws-secret_access-key and --aws-session-token"
|
9
|
+
exit 1
|
130
10
|
end
|
131
11
|
|
132
|
-
|
133
|
-
|
134
|
-
o.banner = "Usage: #{script_name} [options] <db_indentifier>\nUsage: #{script_name} --by-tags <tag=value,...> [other options]"
|
135
|
-
o.separator ""
|
136
|
-
|
137
|
-
o.on("--aws-access-key ACCESS_KEY", "AWS Access Key") do |v|
|
138
|
-
$opts[:aws_access_key] = v
|
139
|
-
end
|
140
|
-
|
141
|
-
o.on("--aws-secret-access-key SECRET_KEY", "AWS Secret Access Key") do |v|
|
142
|
-
$opts[:aws_secret_access_key] = v
|
143
|
-
end
|
144
|
-
|
145
|
-
o.on("--aws-region REGION", "AWS Region") do |v|
|
146
|
-
$opts[:aws_region] = v
|
147
|
-
end
|
148
|
-
|
149
|
-
o.on("--aws-session-token SESSION_TOKEN", "AWS session token") do |v|
|
150
|
-
$opts[:aws_session_token] = v
|
151
|
-
end
|
152
|
-
|
153
|
-
o.on("--pattern STRING", "Snapshots without this string in the description will be ignored") do |v|
|
154
|
-
$opts[:pattern] = v
|
155
|
-
end
|
156
|
-
|
157
|
-
o.on("--by-tags TAG=VALUE,TAG=VALUE", "Instead of rotating specific snapshots, rotate over all the snapshots having the intersection of all given TAG=VALUE pairs.") do |v|
|
158
|
-
$opts[:by_tags] = {}
|
159
|
-
puts 'Hey! It\'s not implemented in RDS yet. Who knows, maybe they will add Tagging in RDS later.'
|
160
|
-
exit 0
|
161
|
-
split_tag($opts[:by_tags],v)
|
162
|
-
end
|
163
|
-
|
164
|
-
o.on("--backoff-limit LIMIT", "Backoff and retry when hitting RDS Error exceptions no more than this many times. Default is 15") do |v|
|
165
|
-
$opts[:backoff_limit] = v
|
166
|
-
end
|
167
|
-
|
168
|
-
o.on("--create-snapshot STRING", "Use this option if you want to create a snapshot") do |v|
|
169
|
-
$opts[:create_snapshot] = v
|
170
|
-
end
|
171
|
-
|
172
|
-
$time_periods.keys.sort { |a, b| $time_periods[a][:seconds] <=> $time_periods[b][:seconds] }.each do |period|
|
173
|
-
o.on("--keep-#{period} NUMBER", Integer, "Number of #{period} snapshots to keep") do |v|
|
174
|
-
$time_periods[period][:keep] = v
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
o.on("--keep-last", "Keep the most recent snapshot, regardless of time-based policy") do |v|
|
179
|
-
$opts[:keep_last] = true
|
180
|
-
end
|
181
|
-
|
182
|
-
o.on("--dry-run", "Shows what would happen without doing anything") do |v|
|
183
|
-
$opts[:dry_run] = true
|
184
|
-
end
|
185
|
-
end.parse!
|
186
|
-
|
187
|
-
if $opts[:aws_access_key].nil? || $opts[:aws_secret_access_key].nil?
|
188
|
-
puts "You must specify your Amazon credentials via --aws-access-key and --aws-secret_access-key"
|
12
|
+
if rrds.options[:aws_region].nil?
|
13
|
+
puts "You must specify your AWS Region via --aws-region"
|
189
14
|
exit 1
|
190
15
|
end
|
191
16
|
|
192
|
-
if ARGV.empty? and
|
17
|
+
if ARGV.empty? and rrds.options[:by_tags].nil?
|
193
18
|
puts "You must provide at least one DB Indentifier when not rotating by tags"
|
194
19
|
exit 1
|
195
20
|
end
|
196
21
|
|
197
|
-
if
|
22
|
+
if rrds.options[:by_tags].nil?
|
198
23
|
db_indentifier_ids = ARGV
|
199
24
|
|
200
25
|
db_indentifier_ids.each do |db_id|
|
@@ -208,83 +33,29 @@ else
|
|
208
33
|
if !ARGV.empty?
|
209
34
|
puts "Ignoring supplied db_indentifier_ids because we're rotating by tags."
|
210
35
|
end
|
211
|
-
if
|
36
|
+
if rrds.options[:by_tags].length == 0
|
212
37
|
puts "Rotating by tags but no tags specified? Refusing to rotate all snapshots!"
|
213
38
|
exit 1
|
214
39
|
end
|
215
40
|
end
|
216
41
|
|
217
|
-
if
|
42
|
+
if rrds.options[:backoff_limit] < 0
|
218
43
|
puts "A negative backoff limit doesn't make much sense."
|
219
44
|
exit 1
|
220
45
|
end
|
221
46
|
|
222
|
-
|
223
|
-
|
224
|
-
Aws.config.update(
|
225
|
-
access_key_id: $opts[:aws_access_key],
|
226
|
-
secret_access_key: $opts[:aws_secret_access_key],
|
227
|
-
region: $opts[:aws_region],
|
228
|
-
session_token: $opts[:aws_session_token]
|
229
|
-
)
|
230
|
-
$rds = Aws::RDS::Client.new
|
231
|
-
rescue Aws::RDS::Errors => e
|
232
|
-
backoff()
|
233
|
-
retry
|
234
|
-
end
|
235
|
-
|
236
|
-
if $opts[:create_snapshot]
|
237
|
-
create_snapshot($opts[:create_snapshot], db_indentifier_ids)
|
47
|
+
if rrds.options[:create_snapshot]
|
48
|
+
rrds.create_snapshot(rrds.options[:create_snapshot], db_indentifier_ids)
|
238
49
|
end
|
239
50
|
|
240
51
|
all_snapshots = []
|
241
|
-
if
|
242
|
-
|
243
|
-
begin
|
244
|
-
these_snapshots = $rds.describe_tags(snapshot_type: 'manual', filters: {'resource-type'=>"snapshot", 'key'=>tag, 'value'=>value}).
|
245
|
-
delete_if{ |e| e.status != 'available' }
|
246
|
-
rescue Aws::RDS::Errors => e
|
247
|
-
backoff()
|
248
|
-
retry
|
249
|
-
end
|
250
|
-
if these_snapshots.length == 0
|
251
|
-
puts "(tag,value)=(#{tag},#{value}) found no snapshots; nothing to rotate!"
|
252
|
-
exit 0
|
253
|
-
end
|
254
|
-
if all_snapshots.length == 0
|
255
|
-
remaining_snapshots = these_snapshots
|
256
|
-
else
|
257
|
-
remaining_snapshots = all_snapshots & these_snapshots
|
258
|
-
end
|
259
|
-
if remaining_snapshots.length == 0
|
260
|
-
puts "No remaining snapshots after applying (tag,value)=(#{tag},#{value}) filter; nothing to rotate!"
|
261
|
-
exit 0
|
262
|
-
end
|
263
|
-
all_snapshots = remaining_snapshots
|
264
|
-
end
|
265
|
-
|
266
|
-
begin
|
267
|
-
rotate_these = get_db_snapshots(db_instance_identifier: all_snapshots.map(&:db_instance_identifier).uniq).
|
268
|
-
delete_if{ |e| !all_snapshots.include?(e.db_snapshot_identifier) }.
|
269
|
-
sort {|a,b| a[:snapshot_create_time] <=> b[:snapshot_create_time] }
|
270
|
-
rescue Aws::RDS::Errors => e
|
271
|
-
backoff()
|
272
|
-
retry
|
273
|
-
end
|
274
|
-
|
275
|
-
rotate_em(rotate_these)
|
52
|
+
if rrds.options[:by_tags]
|
53
|
+
rrds.rotate_by_tags
|
276
54
|
else
|
277
|
-
|
278
|
-
all_snapshots = get_db_snapshots(snapshot_type: 'manual').
|
279
|
-
delete_if{ |e| e[:status] != 'available' }
|
280
|
-
rescue Aws::RDS::Errors => e
|
281
|
-
backoff()
|
282
|
-
retry
|
283
|
-
end
|
284
|
-
|
55
|
+
snapshots = rrds.get_db_snapshots(snapshot_type: 'manual').delete_if{ |e| e[:status] != 'available' }
|
285
56
|
db_indentifier_ids.each do |db_id|
|
286
|
-
rotate_em(
|
287
|
-
|
57
|
+
rrds.rotate_em(
|
58
|
+
snapshots.select {|ss| ss[:db_instance_identifier] == db_id }.
|
288
59
|
sort {|a,b| a[:snapshot_create_time] <=> b[:snapshot_create_time] }
|
289
60
|
)
|
290
61
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'aws-sdk-rds'
|
2
|
+
|
3
|
+
class RdsRotateDbSnapshots
|
4
|
+
module ActionWrappers
|
5
|
+
def with_backoff(*method_names)
|
6
|
+
method_names.each do |m|
|
7
|
+
wrapper = Module.new do
|
8
|
+
define_method(m) do |*args|
|
9
|
+
reset_backoff
|
10
|
+
begin
|
11
|
+
super *args
|
12
|
+
rescue Aws::RDS::Errors => e
|
13
|
+
backoff
|
14
|
+
retry
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
self.prepend wrapper
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
class RdsRotateDbSnapshots
|
2
|
+
module Actions
|
3
|
+
def rotate_em(snapshots)
|
4
|
+
# poor man's way to get a deep copy of our time_periods definition hash
|
5
|
+
periods = Marshal.load(Marshal.dump(time_periods))
|
6
|
+
|
7
|
+
snapshots.each do |snapshot|
|
8
|
+
time = snapshot[:snapshot_create_time]
|
9
|
+
db_id = snapshot[:db_instance_identifier]
|
10
|
+
snapshot_id = snapshot[:db_snapshot_identifier]
|
11
|
+
description = snapshot_id
|
12
|
+
keep_reason = nil
|
13
|
+
|
14
|
+
if options[:pattern] && description !~ /#{options[:pattern]}/
|
15
|
+
puts " #{time.strftime '%Y-%m-%d %H:%M:%S'} #{snapshot_id} Skipping snapshot with description #{description}"
|
16
|
+
next
|
17
|
+
end
|
18
|
+
|
19
|
+
periods.keys.sort { |a, b| periods[a][:seconds] <=> periods[b][:seconds] }.each do |period|
|
20
|
+
period_info = periods[period]
|
21
|
+
keep = period_info[:keep]
|
22
|
+
keeping = period_info[:keeping]
|
23
|
+
|
24
|
+
time_string = time.strftime period_info[:format]
|
25
|
+
if Time.now - time < keep * period_info[:seconds]
|
26
|
+
if !keeping.key?(time_string) && keeping.length < keep
|
27
|
+
keep_reason = period
|
28
|
+
keeping[time_string] = snapshot
|
29
|
+
end
|
30
|
+
break
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if keep_reason.nil? && snapshot == snapshots.last && options[:keep_last]
|
35
|
+
keep_reason = 'last snapshot'
|
36
|
+
end
|
37
|
+
|
38
|
+
if !keep_reason.nil?
|
39
|
+
puts " #{time.strftime '%Y-%m-%d %H:%M:%S'} #{snapshot_id} Keeping for #{keep_reason}"
|
40
|
+
else
|
41
|
+
puts " #{time.strftime '%Y-%m-%d %H:%M:%S'} #{snapshot_id} Deleting"
|
42
|
+
begin
|
43
|
+
client.delete_db_snapshot(db_snapshot_identifier: snapshot_id) unless options[:dry_run]
|
44
|
+
rescue Aws::RDS::Errors => e
|
45
|
+
backoff
|
46
|
+
retry
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_snapshot(name, db_indentifier_ids)
|
53
|
+
if !!name
|
54
|
+
name = name.gsub(/[^a-zA-Z0-9\-]/, '')
|
55
|
+
if name.size > 0
|
56
|
+
name = "#{name}-#{Time.now.strftime('%Y%m%d%H%M%S')}"
|
57
|
+
db_indentifier_ids.each do |db_id|
|
58
|
+
begin
|
59
|
+
client.create_db_snapshot(db_snapshot_identifier: name, db_instance_identifier: db_id) unless options[:dry_run]
|
60
|
+
puts " #{Time.now.strftime '%Y-%m-%d %H:%M:%S'} Creation snapshot #{name} is pending (db: #{db_id})"
|
61
|
+
rescue Aws::RDS::Errors::InvalidDBInstanceStateFault => e
|
62
|
+
backoff
|
63
|
+
retry
|
64
|
+
end
|
65
|
+
end
|
66
|
+
else
|
67
|
+
puts "invalid snapshot name format - #{name}"
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_db_snapshots(options)
|
74
|
+
snapshots = []
|
75
|
+
response = client.describe_db_snapshots(options)
|
76
|
+
while true do
|
77
|
+
snapshots += response.db_snapshots
|
78
|
+
break unless response[:marker]
|
79
|
+
|
80
|
+
response = client.describe_db_snapshots(options.merge(marker: response[:marker]))
|
81
|
+
end
|
82
|
+
snapshots
|
83
|
+
end
|
84
|
+
|
85
|
+
def rotate_by_tags
|
86
|
+
snapshots = []
|
87
|
+
options[:by_tags].each do |tag, value|
|
88
|
+
snapshots = rrds.client.describe_tags(
|
89
|
+
snapshot_type: 'manual', filters: {'resource-type'=>"snapshot", 'key'=>tag, 'value'=>value}
|
90
|
+
).delete_if{ |e| e.status != 'available' }
|
91
|
+
# TODO: re-work
|
92
|
+
if snapshots.length == 0
|
93
|
+
puts "(tag,value)=(#{tag},#{value}) found no snapshots; nothing to rotate!"
|
94
|
+
exit 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
snapshots = get_db_snapshots(db_instance_identifier: snapshots.map(&:db_instance_identifier).uniq).
|
99
|
+
delete_if{ |e| !snapshots.include?(e.db_snapshot_identifier) }.
|
100
|
+
sort {|a,b| a[:snapshot_create_time] <=> b[:snapshot_create_time] }
|
101
|
+
|
102
|
+
rotate_em snapshots
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
class RdsRotateDbSnapshots
|
4
|
+
class OptionsParser
|
5
|
+
class NotImplementedError < StandardError; end
|
6
|
+
class InvalidArgument < StandardError; end
|
7
|
+
|
8
|
+
attr_reader :options, :script_name, :time_periods
|
9
|
+
|
10
|
+
def initialize(script_name: nil, cli: false)
|
11
|
+
@script_name = script_name
|
12
|
+
@options = {
|
13
|
+
:aws_access_key => ENV["AWS_ACCESS_KEY_ID"],
|
14
|
+
:aws_secret_access_key => ENV["AWS_SECRET_ACCESS_KEY"],
|
15
|
+
:aws_session_token => ENV["AWS_SESSION_TOKEN"],
|
16
|
+
:aws_region => ENV["AWS_REGION"],
|
17
|
+
:pattern => nil,
|
18
|
+
:by_tags => nil,
|
19
|
+
:dry_run => false,
|
20
|
+
:backoff_limit => 15,
|
21
|
+
:create_snapshot => nil
|
22
|
+
}
|
23
|
+
@time_periods = {
|
24
|
+
:hourly => { :seconds => 60 * 60, :format => '%Y-%m-%d-%H', :keep => 0, :keeping => {} },
|
25
|
+
:daily => { :seconds => 24 * 60 * 60, :format => '%Y-%m-%d', :keep => 0, :keeping => {} },
|
26
|
+
:weekly => { :seconds => 7 * 24 * 60 * 60, :format => '%Y-%W', :keep => 0, :keeping => {} },
|
27
|
+
:monthly => { :seconds => 30 * 24 * 60 * 60, :format => '%Y-%m', :keep => 0, :keeping => {} },
|
28
|
+
:yearly => { :seconds => 12 * 30 * 24 * 60 * 60, :format => '%Y', :keep => 0, :keeping => {} },
|
29
|
+
}
|
30
|
+
@cli = cli
|
31
|
+
init_cli_parser if cli?
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse!
|
35
|
+
@parser.parse!
|
36
|
+
@options.merge(time_periods: @time_periods)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def cli?
|
42
|
+
!!@cli
|
43
|
+
end
|
44
|
+
|
45
|
+
def init_cli_parser
|
46
|
+
@parser ||= OptionParser.new do |o|
|
47
|
+
o.banner = "Usage: #{script_name} [options] <db_indentifier>\nUsage: #{script_name} --by-tags <tag=value,...> [other options]"
|
48
|
+
o.separator ""
|
49
|
+
|
50
|
+
o.on("--aws-access-key ACCESS_KEY", "AWS Access Key") do |v|
|
51
|
+
@options[:aws_access_key] = v
|
52
|
+
end
|
53
|
+
|
54
|
+
o.on("--aws-secret-access-key SECRET_KEY", "AWS Secret Access Key") do |v|
|
55
|
+
@options[:aws_secret_access_key] = v
|
56
|
+
end
|
57
|
+
|
58
|
+
o.on("--aws-region REGION", "AWS Region") do |v|
|
59
|
+
@options[:aws_region] = v
|
60
|
+
end
|
61
|
+
|
62
|
+
o.on("--aws-session-token SESSION_TOKEN", "AWS session token") do |v|
|
63
|
+
@options[:aws_session_token] = v
|
64
|
+
end
|
65
|
+
|
66
|
+
o.on("--pattern STRING", "Snapshots without this string in the description will be ignored") do |v|
|
67
|
+
@options[:pattern] = v
|
68
|
+
end
|
69
|
+
|
70
|
+
o.on("--by-tags TAG=VALUE,TAG=VALUE", "Instead of rotating specific snapshots, rotate over all the snapshots having the intersection of all given TAG=VALUE pairs.") do |v|
|
71
|
+
@options[:by_tags] = {}
|
72
|
+
raise NotImplementedError, 'Hey! It\'s not implemented in RDS yet. Who knows, maybe they will add Tagging in RDS later.'
|
73
|
+
split_tag(@options[:by_tags],v)
|
74
|
+
end
|
75
|
+
|
76
|
+
o.on("--backoff-limit LIMIT", "Backoff and retry when hitting RDS Error exceptions no more than this many times. Default is 15") do |v|
|
77
|
+
@options[:backoff_limit] = v
|
78
|
+
end
|
79
|
+
|
80
|
+
o.on("--create-snapshot STRING", "Use this option if you want to create a snapshot") do |v|
|
81
|
+
@options[:create_snapshot] = v
|
82
|
+
end
|
83
|
+
|
84
|
+
@time_periods.keys.sort { |a, b| @time_periods[a][:seconds] <=> @time_periods[b][:seconds] }.each do |period|
|
85
|
+
o.on("--keep-#{period} NUMBER", Integer, "Number of #{period} snapshots to keep") do |v|
|
86
|
+
@time_periods[period][:keep] = v
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
o.on("--keep-last", "Keep the most recent snapshot, regardless of time-based policy") do |v|
|
91
|
+
@options[:keep_last] = true
|
92
|
+
end
|
93
|
+
|
94
|
+
o.on("--dry-run", "Shows what would happen without doing anything") do |v|
|
95
|
+
@options[:dry_run] = true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def split_tag(hash,v)
|
100
|
+
v.split(',').each do |pair|
|
101
|
+
tag, value = pair.split('=',2)
|
102
|
+
if value.nil?
|
103
|
+
raise InvalidArgument, "invalid tag=value format"
|
104
|
+
end
|
105
|
+
hash[tag] = value
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'aws-sdk-rds'
|
3
|
+
|
4
|
+
class RdsRotateDbSnapshots
|
5
|
+
class RdsClient
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :@client, :describe_db_snapshots, :create_db_snapshot, :delete_db_snapshot
|
9
|
+
|
10
|
+
def initialize(options)
|
11
|
+
Aws.config.update(
|
12
|
+
access_key_id: options[:aws_access_key],
|
13
|
+
secret_access_key: options[:aws_secret_access_key],
|
14
|
+
region: options[:aws_region],
|
15
|
+
session_token: options[:aws_session_token]
|
16
|
+
)
|
17
|
+
@client = Aws::RDS::Client.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require_relative 'rds_rotate_db_snapshots/actions'
|
3
|
+
require_relative 'rds_rotate_db_snapshots/action_wrappers'
|
4
|
+
require_relative 'rds_rotate_db_snapshots/options_parser'
|
5
|
+
require_relative 'rds_rotate_db_snapshots/rds_client'
|
6
|
+
|
7
|
+
class RdsRotateDbSnapshots
|
8
|
+
extend RdsRotateDbSnapshots::ActionWrappers
|
9
|
+
include RdsRotateDbSnapshots::Actions
|
10
|
+
|
11
|
+
attr_reader :options
|
12
|
+
with_backoff :get_db_snapshots, :create_snapshot, :rotate_em
|
13
|
+
|
14
|
+
def initialize(script_name: nil, cli: false, options: {})
|
15
|
+
@script_name = script_name
|
16
|
+
@options = options
|
17
|
+
@cli = cli
|
18
|
+
parse_options if cli?
|
19
|
+
@backoff_counter = 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def rds_client
|
23
|
+
@rds_client ||= RdsRotateDbSnapshots::RdsClient.new(@options)
|
24
|
+
end
|
25
|
+
alias_method :client, :rds_client
|
26
|
+
|
27
|
+
def reset_backoff
|
28
|
+
@backoff_counter = 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def time_periods
|
32
|
+
@options[:time_periods]
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def cli?
|
38
|
+
!!@cli
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_options
|
42
|
+
@options = RdsRotateDbSnapshots::OptionsParser.new(script_name: @script_name, cli: @cli).parse!
|
43
|
+
end
|
44
|
+
|
45
|
+
def backoff
|
46
|
+
@backoff_counter = @backoff_counter + 1
|
47
|
+
|
48
|
+
# TODO: re-work
|
49
|
+
if options && options[:backoff_limit] > 0 && options[:backoff_limit] < @backoff_counter
|
50
|
+
puts "Too many backoff attempts. Sorry it didn't work out."
|
51
|
+
exit 2
|
52
|
+
end
|
53
|
+
|
54
|
+
naptime = rand(60) * @backoff_counter
|
55
|
+
puts "Backing off for #{naptime} seconds..."
|
56
|
+
sleep naptime
|
57
|
+
end
|
58
|
+
end
|
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: rds-rotate-db-snapshots 0.
|
5
|
+
# stub: rds-rotate-db-snapshots 0.5.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "rds-rotate-db-snapshots".freeze
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.5.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Siarhei Kavaliou".freeze]
|
14
|
-
s.date = "
|
14
|
+
s.date = "2023-01-10"
|
15
15
|
s.description = "Provides a simple way to rotate RDS DB snapshots with configurable retention periods.".freeze
|
16
16
|
s.email = "kovserg@gmail.com".freeze
|
17
17
|
s.executables = ["rds-rotate-db-snapshots".freeze]
|
@@ -24,16 +24,21 @@ Gem::Specification.new do |s|
|
|
24
24
|
".github/dependabot.yml",
|
25
25
|
".github/workflows/ci.yml",
|
26
26
|
".github/workflows/codeql.yml",
|
27
|
+
".rspec",
|
27
28
|
"Gemfile",
|
28
29
|
"LICENSE.txt",
|
29
30
|
"README.md",
|
30
31
|
"Rakefile",
|
31
32
|
"VERSION",
|
32
33
|
"bin/rds-rotate-db-snapshots",
|
33
|
-
"lib
|
34
|
+
"lib/rds_rotate_db_snapshots.rb",
|
35
|
+
"lib/rds_rotate_db_snapshots/action_wrappers.rb",
|
36
|
+
"lib/rds_rotate_db_snapshots/actions.rb",
|
37
|
+
"lib/rds_rotate_db_snapshots/options_parser.rb",
|
38
|
+
"lib/rds_rotate_db_snapshots/rds_client.rb",
|
34
39
|
"rds-rotate-db-snapshots.gemspec",
|
35
|
-
"
|
36
|
-
"
|
40
|
+
"spec/helper.rb",
|
41
|
+
"spec/rds_rotate_db_snapshots_spec.rb"
|
37
42
|
]
|
38
43
|
s.homepage = "http://github.com/serg-kovalev/rds-rotate-db-snapshots".freeze
|
39
44
|
s.licenses = ["MIT".freeze]
|
@@ -46,14 +51,16 @@ Gem::Specification.new do |s|
|
|
46
51
|
|
47
52
|
if s.respond_to? :add_runtime_dependency then
|
48
53
|
s.add_runtime_dependency(%q<aws-sdk-rds>.freeze, ["~> 1"])
|
49
|
-
s.
|
50
|
-
s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
|
54
|
+
s.add_runtime_dependency(%q<rake>.freeze, [">= 0"])
|
51
55
|
s.add_development_dependency(%q<juwelier>.freeze, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<pry>.freeze, [">= 0"])
|
57
|
+
s.add_development_dependency(%q<pry-byebug>.freeze, [">= 0"])
|
52
58
|
else
|
53
59
|
s.add_dependency(%q<aws-sdk-rds>.freeze, ["~> 1"])
|
54
|
-
s.add_dependency(%q<
|
55
|
-
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
60
|
+
s.add_dependency(%q<rake>.freeze, [">= 0"])
|
56
61
|
s.add_dependency(%q<juwelier>.freeze, [">= 0"])
|
62
|
+
s.add_dependency(%q<pry>.freeze, [">= 0"])
|
63
|
+
s.add_dependency(%q<pry-byebug>.freeze, [">= 0"])
|
57
64
|
end
|
58
65
|
end
|
59
66
|
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
$TESTING = true
|
2
|
+
|
3
|
+
require "simplecov"
|
4
|
+
|
5
|
+
SimpleCov.start do
|
6
|
+
add_filter "/spec"
|
7
|
+
# minimum_coverage(70)
|
8
|
+
|
9
|
+
if ENV['CI']
|
10
|
+
require 'simplecov-lcov'
|
11
|
+
|
12
|
+
SimpleCov::Formatter::LcovFormatter.config do |c|
|
13
|
+
c.report_with_single_file = true
|
14
|
+
c.single_report_path = 'coverage/lcov.info'
|
15
|
+
end
|
16
|
+
|
17
|
+
formatter SimpleCov::Formatter::LcovFormatter
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
22
|
+
require "rds_rotate_db_snapshots"
|
23
|
+
|
24
|
+
require "rdoc"
|
25
|
+
require "rspec"
|
26
|
+
require "diff/lcs" # You need diff/lcs installed to run specs.
|
27
|
+
# require 'stringio'
|
28
|
+
require "webmock/rspec"
|
29
|
+
|
30
|
+
WebMock.disable_net_connect!(:allow => "coveralls.io")
|
31
|
+
|
32
|
+
$0 = "rds_rotate_db_snapshots"
|
33
|
+
ARGV.clear
|
34
|
+
|
35
|
+
RSpec.configure do |config|
|
36
|
+
config.before do
|
37
|
+
ARGV.replace []
|
38
|
+
end
|
39
|
+
|
40
|
+
config.expect_with :rspec do |c|
|
41
|
+
c.syntax = :expect
|
42
|
+
end
|
43
|
+
|
44
|
+
# def capture(stream)
|
45
|
+
# begin
|
46
|
+
# stream = stream.to_s
|
47
|
+
# eval "$#{stream} = StringIO.new"
|
48
|
+
# yield
|
49
|
+
# result = eval("$#{stream}").string
|
50
|
+
# ensure
|
51
|
+
# eval("$#{stream} = #{stream.upcase}")
|
52
|
+
# end
|
53
|
+
|
54
|
+
# result
|
55
|
+
# end
|
56
|
+
|
57
|
+
def source_root
|
58
|
+
File.join(File.dirname(__FILE__), "fixtures")
|
59
|
+
end
|
60
|
+
|
61
|
+
def destination_root
|
62
|
+
File.join(File.dirname(__FILE__), "sandbox")
|
63
|
+
end
|
64
|
+
|
65
|
+
def silence_warnings
|
66
|
+
old_verbose = $VERBOSE
|
67
|
+
$VERBOSE = nil
|
68
|
+
yield
|
69
|
+
ensure
|
70
|
+
$VERBOSE = old_verbose
|
71
|
+
end
|
72
|
+
|
73
|
+
# true if running on windows, used for conditional spec skips
|
74
|
+
#
|
75
|
+
# @return [TrueClass/FalseClass]
|
76
|
+
def windows?
|
77
|
+
Gem.win_platform?
|
78
|
+
end
|
79
|
+
|
80
|
+
# alias silence capture
|
81
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
describe RdsRotateDbSnapshots do
|
4
|
+
subject { described_class.new(script_name: script_name, cli: cli) }
|
5
|
+
|
6
|
+
let(:script_name) { "test" }
|
7
|
+
let(:cli) { true }
|
8
|
+
before do
|
9
|
+
allow(Aws::RDS::Client).to receive(:new)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "on include" do
|
13
|
+
it "adds action methods to the base class" do
|
14
|
+
expect(described_class.instance_methods).to include(:rotate_em)
|
15
|
+
expect(described_class.instance_methods).to include(:create_snapshot)
|
16
|
+
expect(described_class.instance_methods).to include(:get_db_snapshots)
|
17
|
+
expect(described_class.instance_methods).to include(:rotate_by_tags)
|
18
|
+
expect(described_class.instance_methods).to include(:client)
|
19
|
+
expect(described_class.instance_methods).to include(:time_periods)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#client" do
|
24
|
+
it "returns an RdsClient" do
|
25
|
+
expect(subject.client).to be_a(RdsRotateDbSnapshots::RdsClient)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#rds_client" do
|
30
|
+
it "returns an RdsClient" do
|
31
|
+
expect(subject.rds_client).to be_a(RdsRotateDbSnapshots::RdsClient)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#reset_backoff" do
|
36
|
+
it "resets backoff counter" do
|
37
|
+
subject.instance_variable_set(:@backoff_counter, 1)
|
38
|
+
subject.reset_backoff
|
39
|
+
expect(subject.instance_variable_get(:@backoff_counter)).to eq(0)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#time_periods" do
|
44
|
+
it "returns time periods" do
|
45
|
+
expect(subject.time_periods).to eq(
|
46
|
+
:daily=>{:format=>"%Y-%m-%d", :keep=>0, :keeping=>{}, :seconds=>86400},
|
47
|
+
:hourly => {:format=>"%Y-%m-%d-%H", :keep=>0, :keeping=>{}, :seconds=>3600},
|
48
|
+
:monthly => {:format=>"%Y-%m", :keep=>0, :keeping=>{}, :seconds=>2592000},
|
49
|
+
:weekly => {:format=>"%Y-%W", :keep=>0, :keeping=>{}, :seconds=>604800},
|
50
|
+
:yearly=>{:format=>"%Y", :keep=>0, :keeping=>{}, :seconds=>31104000}
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#backoff" do
|
56
|
+
it "backs off" do
|
57
|
+
subject.instance_variable_set(:@backoff_counter, 1)
|
58
|
+
expect(subject).to receive(:sleep)
|
59
|
+
subject.send(:backoff)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rds-rotate-db-snapshots
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Siarhei Kavaliou
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-rds
|
@@ -25,7 +25,21 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: juwelier
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - ">="
|
@@ -39,7 +53,7 @@ dependencies:
|
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
56
|
+
name: pry
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - ">="
|
@@ -53,7 +67,7 @@ dependencies:
|
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
70
|
+
name: pry-byebug
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - ">="
|
@@ -80,16 +94,21 @@ files:
|
|
80
94
|
- ".github/dependabot.yml"
|
81
95
|
- ".github/workflows/ci.yml"
|
82
96
|
- ".github/workflows/codeql.yml"
|
97
|
+
- ".rspec"
|
83
98
|
- Gemfile
|
84
99
|
- LICENSE.txt
|
85
100
|
- README.md
|
86
101
|
- Rakefile
|
87
102
|
- VERSION
|
88
103
|
- bin/rds-rotate-db-snapshots
|
89
|
-
- lib
|
104
|
+
- lib/rds_rotate_db_snapshots.rb
|
105
|
+
- lib/rds_rotate_db_snapshots/action_wrappers.rb
|
106
|
+
- lib/rds_rotate_db_snapshots/actions.rb
|
107
|
+
- lib/rds_rotate_db_snapshots/options_parser.rb
|
108
|
+
- lib/rds_rotate_db_snapshots/rds_client.rb
|
90
109
|
- rds-rotate-db-snapshots.gemspec
|
91
|
-
-
|
92
|
-
-
|
110
|
+
- spec/helper.rb
|
111
|
+
- spec/rds_rotate_db_snapshots_spec.rb
|
93
112
|
homepage: http://github.com/serg-kovalev/rds-rotate-db-snapshots
|
94
113
|
licenses:
|
95
114
|
- MIT
|
data/lib/.empty
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
Juwelier doesn't like me not having a lib/ directory.
|
data/test/helper.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
require 'minitest'
|
4
|
-
require 'shoulda'
|
5
|
-
|
6
|
-
begin
|
7
|
-
Bundler.setup(:default, :development)
|
8
|
-
rescue Bundler::BundlerError => e
|
9
|
-
$stderr.puts e.message
|
10
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
11
|
-
exit e.status_code
|
12
|
-
end
|
13
|
-
|
14
|
-
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
15
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
16
|
-
|
17
|
-
class Minitest::Test
|
18
|
-
end
|