rds-rotate-db-snapshots 0.5.2 → 1.0.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 +5 -40
- data/.github/workflows/codeql.yml +2 -2
- data/Gemfile +10 -10
- data/README.md +4 -8
- data/Rakefile +19 -4
- data/VERSION +1 -1
- data/bin/rds-rotate-db-snapshots +257 -25
- data/rds-rotate-db-snapshots.gemspec +12 -27
- metadata +3 -18
- data/.rspec +0 -1
- data/.rubocop.yml +0 -93
- data/.rubocop_todo.yml +0 -127
- data/lib/rds_rotate_db_snapshots/action_wrappers.rb +0 -26
- data/lib/rds_rotate_db_snapshots/actions.rb +0 -112
- data/lib/rds_rotate_db_snapshots/options_parser/options.rb +0 -80
- data/lib/rds_rotate_db_snapshots/options_parser.rb +0 -58
- data/lib/rds_rotate_db_snapshots/rds_client.rb +0 -20
- data/lib/rds_rotate_db_snapshots.rb +0 -59
- data/spec/helper.rb +0 -81
- data/spec/lib/rds_rotate_db_snapshots/action_wrappers_spec.rb +0 -45
- data/spec/lib/rds_rotate_db_snapshots/actions_spec.rb +0 -116
- data/spec/lib/rds_rotate_db_snapshots/options_parser_spec.rb +0 -30
- data/spec/lib/rds_rotate_db_snapshots/rds_client_spec.rb +0 -34
- data/spec/lib/rds_rotate_db_snapshots_spec.rb +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f21f380e6c4adfe878d359b84cf4842cc143225c81656e10e77a8e7f1d5efa0
|
4
|
+
data.tar.gz: e9e43b9f8fc687a263a3f5cf49f5bfecd3b5fcb69200546adbcb75856f7348ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d0a8ef6a1b168e0bac4d93b4b13ba2b7907bdbe7fd6307a596c739d36247dc252af8d665faf728a50402edb7e69940ee9c568b3601b4e3c219308861596b9c8
|
7
|
+
data.tar.gz: '09a5010edb7bad75fc94e4c64c0c523e0acf3e547224cfe33e107a0616cdc5eb8920ccf0fe91aafd25073216f1460f767996fdcc952228ca9d92daa4527491f3'
|
data/.github/workflows/ci.yml
CHANGED
@@ -2,62 +2,27 @@ name: "CI"
|
|
2
2
|
|
3
3
|
on:
|
4
4
|
push:
|
5
|
-
branches: ["
|
5
|
+
branches: ["master"]
|
6
6
|
pull_request:
|
7
|
-
branches: ["
|
8
|
-
|
9
|
-
env:
|
10
|
-
RUBY_MAIN_VERSION: '3.2'
|
7
|
+
branches: ["master"]
|
11
8
|
|
12
9
|
jobs:
|
13
|
-
rubocop:
|
14
|
-
runs-on: ubuntu-20.04
|
15
|
-
steps:
|
16
|
-
- name: Checkout code
|
17
|
-
uses: actions/checkout@v3
|
18
|
-
- name: Install Ruby and gems
|
19
|
-
uses: ruby/setup-ruby@319066216501fbd5e2d568f14b7d68c19fb67a5d #v1.133.1
|
20
|
-
with:
|
21
|
-
bundler-cache: true
|
22
|
-
ruby-version: ${{ env.RUBY_MAIN_VERSION }}.0
|
23
|
-
- name: Install Bundler
|
24
|
-
run: gem install bundler
|
25
|
-
- name: Bundle Install
|
26
|
-
run: bundle install
|
27
|
-
- name: Rubocop
|
28
|
-
run: bundle exec rubocop
|
29
|
-
|
30
10
|
test:
|
31
11
|
runs-on: ubuntu-20.04
|
32
12
|
strategy:
|
33
13
|
matrix:
|
34
|
-
ruby_version: [
|
14
|
+
ruby_version: [2.7, 3.0, 3.1, 'jruby']
|
35
15
|
steps:
|
36
16
|
- name: Checkout code
|
37
17
|
uses: actions/checkout@v3
|
38
18
|
- name: Install Ruby and gems
|
39
|
-
uses: ruby/setup-ruby@
|
19
|
+
uses: ruby/setup-ruby@03b78bdda287ae04217ee12e4b64996630a03542 #v1.131.0
|
40
20
|
with:
|
41
21
|
bundler-cache: true
|
42
22
|
ruby-version: ${{ matrix.ruby_version }}
|
43
23
|
- name: Install Bundler
|
44
24
|
run: gem install bundler
|
45
|
-
- name: Setup Code Climate test-reporter
|
46
|
-
if: ${{ matrix.ruby_version == env.RUBY_MAIN_VERSION }}
|
47
|
-
run: |
|
48
|
-
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
49
|
-
chmod +x ./cc-test-reporter
|
50
|
-
./cc-test-reporter before-build
|
51
25
|
- name: Bundle Install
|
52
26
|
run: bundle install
|
53
27
|
- name: Test
|
54
|
-
run: bundle exec
|
55
|
-
- name: Publish Codeclimate Code Coverage
|
56
|
-
if: ${{ matrix.ruby_version == env.RUBY_MAIN_VERSION }}
|
57
|
-
run: |
|
58
|
-
./cc-test-reporter after-build -r ${{secrets.CC_TEST_REPORTER_ID}}
|
59
|
-
- name: Coveralls Parallel
|
60
|
-
if: ${{ matrix.ruby_version == env.RUBY_MAIN_VERSION }}
|
61
|
-
uses: coverallsapp/github-action@master
|
62
|
-
with:
|
63
|
-
github-token: ${{ secrets.github_token }}
|
28
|
+
run: bundle exec rake test
|
@@ -13,10 +13,10 @@ name: "CodeQL"
|
|
13
13
|
|
14
14
|
on:
|
15
15
|
push:
|
16
|
-
branches: [ "
|
16
|
+
branches: [ "master" ]
|
17
17
|
pull_request:
|
18
18
|
# The branches below must be a subset of the branches above
|
19
|
-
branches: [ "
|
19
|
+
branches: [ "master" ]
|
20
20
|
schedule:
|
21
21
|
- cron: '22 4 * * 2'
|
22
22
|
|
data/Gemfile
CHANGED
@@ -2,19 +2,19 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
gem 'aws-sdk-rds', '~> 1'
|
4
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
|
11
|
+
|
5
12
|
group :development do
|
6
|
-
gem "rake"
|
7
13
|
gem 'juwelier'
|
8
|
-
gem "pry"
|
9
|
-
gem "pry-byebug"
|
10
|
-
gem "rubocop"
|
11
|
-
gem "rubocop-rspec"
|
12
14
|
end
|
13
15
|
|
14
16
|
group :test do
|
15
|
-
gem
|
16
|
-
gem
|
17
|
-
gem
|
18
|
-
gem 'simplecov-lcov', '~> 0.8.0'
|
19
|
-
gem "webmock"
|
17
|
+
gem 'rake'
|
18
|
+
gem 'shoulda'
|
19
|
+
gem 'minitest'
|
20
20
|
end
|
data/README.md
CHANGED
@@ -1,9 +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%
|
5
|
-
[![Maintainability](https://api.codeclimate.com/v1/badges/45bfe7dc4f21ac1403cb/maintainability)](https://codeclimate.com/github/serg-kovalev/rds-rotate-db-snapshots/maintainability)
|
6
|
-
[![Test Coverage](https://api.codeclimate.com/v1/badges/45bfe7dc4f21ac1403cb/test_coverage)](https://codeclimate.com/github/serg-kovalev/rds-rotate-db-snapshots/test_coverage)
|
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%3Amaster+event%3Apush)](https://github.com/serg-kovalev/rds-rotate-db-snapshots/actions/workflows/ci.yml?query=branch%3Amaster+event%3Apush) [![CodeQL](https://github.com/serg-kovalev/rds-rotate-db-snapshots/actions/workflows/codeql.yml/badge.svg?query=branch%3Amaster+event%3Apush)](https://github.com/serg-kovalev/rds-rotate-db-snapshots/actions/workflows/codeql.yml?query=branch%3Amaster+event%3Apush)
|
7
5
|
|
8
6
|
Provides a simple way to rotate db snapshots in Amazon Relational Database
|
9
7
|
Service (RDS).
|
@@ -11,9 +9,9 @@ Service (RDS).
|
|
11
9
|
## Tested on Rubies
|
12
10
|
|
13
11
|
- 2.7
|
14
|
-
- 3.0
|
15
12
|
- 3.1
|
16
13
|
- 3.2
|
14
|
+
- jruby
|
17
15
|
|
18
16
|
## Usage
|
19
17
|
|
@@ -35,20 +33,18 @@ Add this script to CRON (let's say it will run this script every X hours) and it
|
|
35
33
|
#/usr/bin/bash
|
36
34
|
AWS_ACCESS_KEY='xxxxxxxxxxxxxxxxxxxx'
|
37
35
|
AWS_SECRET_ACCESS_KEY='yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
|
38
|
-
AWS_SESSION_TOKEN='session_token_goes_here'
|
39
36
|
AWS_REGION='eu-west-1'
|
40
37
|
DESCRIPTION_PREFIX='automatic-backup-'
|
41
38
|
RDS_ROTATOR=/here/is/the/path/to/rds-rotate-db-snapshots
|
42
39
|
DB_NAME='db_name_here'
|
43
40
|
|
44
|
-
$RDS_ROTATOR --aws-region $AWS_REGION --aws-access-key $AWS_ACCESS_KEY --aws-secret-access-key $AWS_SECRET_ACCESS_KEY --
|
41
|
+
$RDS_ROTATOR --aws-region $AWS_REGION --aws-access-key $AWS_ACCESS_KEY --aws-secret-access-key $AWS_SECRET_ACCESS_KEY --pattern $DESCRIPTION_PREFIX --keep-hourly 24 --keep-daily 7 --keep-weekly 4 --keep-monthly 1 --keep-yearly 0 --create-snapshot $DESCRIPTION_PREFIX$DB_NAME $DB_NAME
|
45
42
|
```
|
46
43
|
|
47
44
|
## Options
|
48
45
|
|
49
46
|
- `--aws-access-key ACCESS_KEY` "AWS Access Key"
|
50
47
|
- `--aws-secret-access-key SECRET_KEY` "AWS Secret Access Key"
|
51
|
-
- `--aws-session-token $AWS_SESSION_TOKEN` "AWS Session Token"
|
52
48
|
- `--aws-region REGION` "AWS Region"
|
53
49
|
- `--pattern STRING` "Snapshots without this string in the description will be ignored"
|
54
50
|
- `--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."
|
@@ -69,7 +65,7 @@ show the messages.
|
|
69
65
|
|
70
66
|
## Contributing to rds-rotate-db-snapshots
|
71
67
|
|
72
|
-
- Check out the latest
|
68
|
+
- Check out the latest master to make sure the feature hasn't been
|
73
69
|
implemented or the bug hasn't been fixed yet
|
74
70
|
- Check out the issue tracker to make sure someone already hasn't requested
|
75
71
|
it and/or contributed it
|
data/Rakefile
CHANGED
@@ -3,8 +3,8 @@ require 'bundler'
|
|
3
3
|
begin
|
4
4
|
Bundler.setup(:default, :development)
|
5
5
|
rescue Bundler::BundlerError => e
|
6
|
-
|
7
|
-
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
8
|
exit e.status_code
|
9
9
|
end
|
10
10
|
require 'rake'
|
@@ -14,14 +14,29 @@ Juwelier::Tasks.new do |gem|
|
|
14
14
|
gem.name = "rds-rotate-db-snapshots"
|
15
15
|
gem.homepage = "http://github.com/serg-kovalev/rds-rotate-db-snapshots"
|
16
16
|
gem.license = "MIT"
|
17
|
-
gem.summary = %
|
18
|
-
gem.description = %
|
17
|
+
gem.summary = %Q{Amazon RDS DB snapshot rotator}
|
18
|
+
gem.description = %Q{Provides a simple way to rotate RDS DB snapshots with configurable retention periods.}
|
19
19
|
gem.email = "kovserg@gmail.com"
|
20
20
|
gem.authors = ["Siarhei Kavaliou"]
|
21
21
|
gem.version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
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
|
+
|
25
40
|
require 'rdoc/task'
|
26
41
|
Rake::RDocTask.new do |rdoc|
|
27
42
|
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.3
|
data/bin/rds-rotate-db-snapshots
CHANGED
@@ -1,59 +1,291 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
3
|
+
require 'rubygems'
|
4
|
+
require 'aws-sdk-rds'
|
5
|
+
require 'optparse'
|
4
6
|
|
5
|
-
|
7
|
+
$opts = {
|
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
|
+
}
|
6
18
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
19
|
+
$time_periods = {
|
20
|
+
:hourly => { :seconds => 60 * 60, :format => '%Y-%m-%d-%H', :keep => 0, :keeping => {} },
|
21
|
+
:daily => { :seconds => 24 * 60 * 60, :format => '%Y-%m-%d', :keep => 0, :keeping => {} },
|
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
|
11
37
|
end
|
12
38
|
|
13
|
-
|
14
|
-
|
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
|
130
|
+
end
|
131
|
+
|
132
|
+
OptionParser.new do |o|
|
133
|
+
script_name = File.basename($0)
|
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"
|
15
189
|
exit 1
|
16
190
|
end
|
17
191
|
|
18
|
-
if ARGV.empty?
|
192
|
+
if ARGV.empty? and $opts[:by_tags].nil?
|
19
193
|
puts "You must provide at least one DB Indentifier when not rotating by tags"
|
20
194
|
exit 1
|
21
195
|
end
|
22
196
|
|
23
|
-
if
|
197
|
+
if $opts[:by_tags].nil?
|
24
198
|
db_indentifier_ids = ARGV
|
25
199
|
|
26
200
|
db_indentifier_ids.each do |db_id|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
201
|
+
if db_id.nil? or db_id.gsub(/\s/, '').size < 1
|
202
|
+
# sanity check
|
203
|
+
puts "Invalid db indentifier: #{db_id}"
|
204
|
+
exit 1
|
205
|
+
end
|
32
206
|
end
|
33
207
|
else
|
34
|
-
|
35
|
-
|
208
|
+
if !ARGV.empty?
|
209
|
+
puts "Ignoring supplied db_indentifier_ids because we're rotating by tags."
|
210
|
+
end
|
211
|
+
if $opts[:by_tags].length == 0
|
36
212
|
puts "Rotating by tags but no tags specified? Refusing to rotate all snapshots!"
|
37
213
|
exit 1
|
38
214
|
end
|
39
215
|
end
|
40
216
|
|
41
|
-
if
|
217
|
+
if $opts[:backoff_limit] < 0
|
42
218
|
puts "A negative backoff limit doesn't make much sense."
|
43
219
|
exit 1
|
44
220
|
end
|
45
221
|
|
46
|
-
|
222
|
+
$backoffed = 0
|
223
|
+
begin
|
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)
|
238
|
+
end
|
47
239
|
|
48
240
|
all_snapshots = []
|
49
|
-
if
|
50
|
-
|
241
|
+
if $opts[:by_tags]
|
242
|
+
$opts[:by_tags].each do |tag, value|
|
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)
|
51
276
|
else
|
52
|
-
|
277
|
+
begin
|
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
|
+
|
53
285
|
db_indentifier_ids.each do |db_id|
|
54
|
-
|
55
|
-
|
56
|
-
|
286
|
+
rotate_em(
|
287
|
+
all_snapshots.select {|ss| ss[:db_instance_identifier] == db_id }.
|
288
|
+
sort {|a,b| a[:snapshot_create_time] <=> b[:snapshot_create_time] }
|
57
289
|
)
|
58
290
|
end
|
59
291
|
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 1.0.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 = "1.0.0".freeze
|
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 = "2024-01-02"
|
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,42 +24,27 @@ Gem::Specification.new do |s|
|
|
24
24
|
".github/dependabot.yml",
|
25
25
|
".github/workflows/ci.yml",
|
26
26
|
".github/workflows/codeql.yml",
|
27
|
-
".rspec",
|
28
|
-
".rubocop.yml",
|
29
|
-
".rubocop_todo.yml",
|
30
27
|
"Gemfile",
|
31
28
|
"LICENSE.txt",
|
32
29
|
"README.md",
|
33
30
|
"Rakefile",
|
34
31
|
"VERSION",
|
35
32
|
"bin/rds-rotate-db-snapshots",
|
36
|
-
"
|
37
|
-
"lib/rds_rotate_db_snapshots/action_wrappers.rb",
|
38
|
-
"lib/rds_rotate_db_snapshots/actions.rb",
|
39
|
-
"lib/rds_rotate_db_snapshots/options_parser.rb",
|
40
|
-
"lib/rds_rotate_db_snapshots/options_parser/options.rb",
|
41
|
-
"lib/rds_rotate_db_snapshots/rds_client.rb",
|
42
|
-
"rds-rotate-db-snapshots.gemspec",
|
43
|
-
"spec/helper.rb",
|
44
|
-
"spec/lib/rds_rotate_db_snapshots/action_wrappers_spec.rb",
|
45
|
-
"spec/lib/rds_rotate_db_snapshots/actions_spec.rb",
|
46
|
-
"spec/lib/rds_rotate_db_snapshots/options_parser_spec.rb",
|
47
|
-
"spec/lib/rds_rotate_db_snapshots/rds_client_spec.rb",
|
48
|
-
"spec/lib/rds_rotate_db_snapshots_spec.rb"
|
33
|
+
"rds-rotate-db-snapshots.gemspec"
|
49
34
|
]
|
50
35
|
s.homepage = "http://github.com/serg-kovalev/rds-rotate-db-snapshots".freeze
|
51
36
|
s.licenses = ["MIT".freeze]
|
52
|
-
s.rubygems_version = "3.
|
37
|
+
s.rubygems_version = "3.5.3".freeze
|
53
38
|
s.summary = "Amazon RDS DB snapshot rotator".freeze
|
54
39
|
|
55
40
|
s.specification_version = 4
|
56
41
|
|
57
|
-
s.add_runtime_dependency(%q<aws-sdk-rds>.freeze, ["~> 1"])
|
58
|
-
s.add_development_dependency(%q<rake>.freeze, [">= 0"])
|
59
|
-
s.add_development_dependency(%q<juwelier>.freeze, [">= 0"])
|
60
|
-
s.add_development_dependency(%q<pry>.freeze, [">= 0"])
|
61
|
-
s.add_development_dependency(%q<pry-byebug>.freeze, [">= 0"])
|
62
|
-
s.add_development_dependency(%q<rubocop>.freeze, [">= 0"])
|
63
|
-
s.add_development_dependency(%q<rubocop-rspec>.freeze, [">= 0"])
|
42
|
+
s.add_runtime_dependency(%q<aws-sdk-rds>.freeze, ["~> 1".freeze])
|
43
|
+
s.add_development_dependency(%q<rake>.freeze, [">= 0".freeze])
|
44
|
+
s.add_development_dependency(%q<juwelier>.freeze, [">= 0".freeze])
|
45
|
+
s.add_development_dependency(%q<pry>.freeze, [">= 0".freeze])
|
46
|
+
s.add_development_dependency(%q<pry-byebug>.freeze, [">= 0".freeze])
|
47
|
+
s.add_development_dependency(%q<rubocop>.freeze, [">= 0".freeze])
|
48
|
+
s.add_development_dependency(%q<rubocop-rspec>.freeze, [">= 0".freeze])
|
64
49
|
end
|
65
50
|
|
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: 1.0.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: 2024-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-rds
|
@@ -122,28 +122,13 @@ files:
|
|
122
122
|
- ".github/dependabot.yml"
|
123
123
|
- ".github/workflows/ci.yml"
|
124
124
|
- ".github/workflows/codeql.yml"
|
125
|
-
- ".rspec"
|
126
|
-
- ".rubocop.yml"
|
127
|
-
- ".rubocop_todo.yml"
|
128
125
|
- Gemfile
|
129
126
|
- LICENSE.txt
|
130
127
|
- README.md
|
131
128
|
- Rakefile
|
132
129
|
- VERSION
|
133
130
|
- bin/rds-rotate-db-snapshots
|
134
|
-
- lib/rds_rotate_db_snapshots.rb
|
135
|
-
- lib/rds_rotate_db_snapshots/action_wrappers.rb
|
136
|
-
- lib/rds_rotate_db_snapshots/actions.rb
|
137
|
-
- lib/rds_rotate_db_snapshots/options_parser.rb
|
138
|
-
- lib/rds_rotate_db_snapshots/options_parser/options.rb
|
139
|
-
- lib/rds_rotate_db_snapshots/rds_client.rb
|
140
131
|
- rds-rotate-db-snapshots.gemspec
|
141
|
-
- spec/helper.rb
|
142
|
-
- spec/lib/rds_rotate_db_snapshots/action_wrappers_spec.rb
|
143
|
-
- spec/lib/rds_rotate_db_snapshots/actions_spec.rb
|
144
|
-
- spec/lib/rds_rotate_db_snapshots/options_parser_spec.rb
|
145
|
-
- spec/lib/rds_rotate_db_snapshots/rds_client_spec.rb
|
146
|
-
- spec/lib/rds_rotate_db_snapshots_spec.rb
|
147
132
|
homepage: http://github.com/serg-kovalev/rds-rotate-db-snapshots
|
148
133
|
licenses:
|
149
134
|
- MIT
|
@@ -163,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
148
|
- !ruby/object:Gem::Version
|
164
149
|
version: '0'
|
165
150
|
requirements: []
|
166
|
-
rubygems_version: 3.
|
151
|
+
rubygems_version: 3.5.3
|
167
152
|
signing_key:
|
168
153
|
specification_version: 4
|
169
154
|
summary: Amazon RDS DB snapshot rotator
|
data/.rspec
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
-w --color
|