rds-rotate-db-snapshots 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89f6c03c428ea666793c1a5b1f8c73f9667466db382e4f69358aff1dee3026a1
4
- data.tar.gz: 1a2ef773c19beeaefe8f393d26e97c4e1eb022288268f8881280978ad2ada7d6
3
+ metadata.gz: 7ddfaa42b87e8ad4f9cabafcea086ce82d0f351068d05db7da97b53fa5080a0f
4
+ data.tar.gz: dac01f0570c16a5a5a205253f0ca3e6cd26d7f7ed22377da3f9f1bad2e99d87b
5
5
  SHA512:
6
- metadata.gz: 860abf62c29df38f7028c6466992af8c150217b9f0b5502343c895cb8620857ff8871795cccc3120d5ea226220dc897b8607652cf5f20c972e15306a1d25ecc3
7
- data.tar.gz: c2d7fde7e855cbbc9fbd45edf7f935cfa4d310c6677ab9b0171b48840690ecee6ccca3af753c81fbae93ca425ef3d2df611c1323e953da14fd5bc57b835087d7
6
+ metadata.gz: 4d5d586b5ff033df7c7a4d131259d7f47b1f30e2592b616fc0e6644fcbd4999d0e24815dba96c9d335e8d6e35446293bee2b59f83ab286eda4a32a3f39cbaf38
7
+ data.tar.gz: 5f9339752c7a963981f1d6be97ed321a0897f8028d52f632b6dae0fb81e2e495327e127f7dec3d4040b35610ad3a7c56d9206b8220a376485c87f4372fe88470
@@ -0,0 +1,11 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "bundler" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
@@ -0,0 +1,43 @@
1
+ name: "CI"
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ branches: ["main"]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-20.04
12
+ strategy:
13
+ matrix:
14
+ ruby_version: [2.7, 3.0, 3.1]
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v3
18
+ - name: Install Ruby and gems
19
+ uses: ruby/setup-ruby@03b78bdda287ae04217ee12e4b64996630a03542 #v1.131.0
20
+ with:
21
+ bundler-cache: true
22
+ ruby-version: ${{ matrix.ruby_version }}
23
+ - name: Install Bundler
24
+ run: gem install bundler
25
+ - name: Bundle Install
26
+ run: bundle install
27
+ - name: Test
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
@@ -0,0 +1,76 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL"
13
+
14
+ on:
15
+ push:
16
+ branches: [ "main" ]
17
+ pull_request:
18
+ # The branches below must be a subset of the branches above
19
+ branches: [ "main" ]
20
+ schedule:
21
+ - cron: '22 4 * * 2'
22
+
23
+ jobs:
24
+ analyze:
25
+ name: Analyze
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ actions: read
29
+ contents: read
30
+ security-events: write
31
+
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ language: [ 'ruby' ]
36
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37
+ # Use only 'java' to analyze code written in Java, Kotlin or both
38
+ # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
39
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
40
+
41
+ steps:
42
+ - name: Checkout repository
43
+ uses: actions/checkout@v3
44
+
45
+ # Initializes the CodeQL tools for scanning.
46
+ - name: Initialize CodeQL
47
+ uses: github/codeql-action/init@v2
48
+ with:
49
+ languages: ${{ matrix.language }}
50
+ # If you wish to specify custom queries, you can do so here or in a config file.
51
+ # By default, queries listed here will override any specified in a config file.
52
+ # Prefix the list here with "+" to use these queries and those in the config file.
53
+
54
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
55
+ # queries: security-extended,security-and-quality
56
+
57
+
58
+ # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
59
+ # If this step fails, then you should remove it and run the build manually (see below)
60
+ - name: Autobuild
61
+ uses: github/codeql-action/autobuild@v2
62
+
63
+ # ℹ️ Command-line programs to run using the OS shell.
64
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
65
+
66
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
67
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
68
+
69
+ # - run: |
70
+ # echo "Run, Build Application using script"
71
+ # ./location_of_script_within_repo/buildscript.sh
72
+
73
+ - name: Perform CodeQL Analysis
74
+ uses: github/codeql-action/analyze@v2
75
+ with:
76
+ category: "/language:${{matrix.language}}"
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -w --color
data/Gemfile CHANGED
@@ -1,20 +1,19 @@
1
- source "http://rubygems.org"
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 'rake'
18
- gem 'shoulda'
19
- gem 'minitest'
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 ADDED
@@ -0,0 +1,82 @@
1
+ # rds-rotate-db-snapshots
2
+
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%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
+
6
+ Provides a simple way to rotate db snapshots in Amazon Relational Database
7
+ Service (RDS).
8
+
9
+ ## Tested on Rubies
10
+
11
+ - 2.7
12
+ - 3.1
13
+ - 3.2
14
+
15
+ ## Usage
16
+
17
+ Gem installation:
18
+
19
+ ```bash
20
+ gem install rds-rotate-db-snapshots
21
+ ```
22
+
23
+ Usage:
24
+
25
+ ```bash
26
+ rds-rotate-db-snapshots [options] <db_indentifier>
27
+ ```
28
+
29
+ Add this script to CRON (let's say it will run this script every X hours) and it will do the job well
30
+
31
+ ```bash
32
+ #/usr/bin/bash
33
+ AWS_ACCESS_KEY='xxxxxxxxxxxxxxxxxxxx'
34
+ AWS_SECRET_ACCESS_KEY='yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
35
+ AWS_REGION='eu-west-1'
36
+ DESCRIPTION_PREFIX='automatic-backup-'
37
+ RDS_ROTATOR=/here/is/the/path/to/rds-rotate-db-snapshots
38
+ DB_NAME='db_name_here'
39
+
40
+ $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
41
+ ```
42
+
43
+ ## Options
44
+
45
+ - `--aws-access-key ACCESS_KEY` "AWS Access Key"
46
+ - `--aws-secret-access-key SECRET_KEY` "AWS Secret Access Key"
47
+ - `--aws-region REGION` "AWS Region"
48
+ - `--pattern STRING` "Snapshots without this string in the description will be ignored"
49
+ - `--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."
50
+ - `--backoff-limit INTEGER` "Backoff and retry when hitting RDS Error exceptions no more than this many times. Default is 15"
51
+ - `--create-snapshot STRING` "Use this option if you want to create a snapshot"
52
+ - `--keep-hourly INTEGER` "Number of hourly snapshots to keep"
53
+ - `--keep-daily INTEGER` "Number of daily snapshots to keep"
54
+ - `--keep-weekly INTEGER` "Number of weekly snapshots to keep"
55
+ - `--keep-last` "Keep the most recent snapshot, regardless of time-based policy"
56
+ - `--dry-run` "Shows what would happen without doing anything"
57
+
58
+ ## Tips
59
+
60
+ If you are not sure what happen - add option `--dry-run`.
61
+
62
+ In that case the script will not destroy/create anything in RDS, it will just
63
+ show the messages.
64
+
65
+ ## Contributing to rds-rotate-db-snapshots
66
+
67
+ - Check out the latest main to make sure the feature hasn't been
68
+ implemented or the bug hasn't been fixed yet
69
+ - Check out the issue tracker to make sure someone already hasn't requested
70
+ it and/or contributed it
71
+ - Fork the project
72
+ - Start a feature/bugfix branch
73
+ - Commit and push until you are happy with your contribution
74
+ - Make sure to add tests for it. This is important so I don't break it in a
75
+ future version unintentionally.
76
+ - Please try not to mess with the Rakefile, version, or history. If you want
77
+ to have your own version, or is otherwise necessary, that is fine, but
78
+ please isolate to its own commit so I can cherry-pick around it.
79
+
80
+ ## Copyright
81
+
82
+ Copyright (c) 2014 Siarhei Kavaliou. See LICENSE.txt for further details.
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.4.0
1
+ 0.5.0
@@ -1,200 +1,25 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
4
- require 'aws-sdk-rds'
5
- require 'optparse'
3
+ require_relative '../lib/rds_rotate_db_snapshots'
6
4
 
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
- }
5
+ rrds = RdsRotateDbSnapshots.new(script_name: File.basename($0), cli: true)
18
6
 
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
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
- 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"
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 $opts[:by_tags].nil?
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 $opts[:by_tags].nil?
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 $opts[:by_tags].length == 0
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 $opts[:backoff_limit] < 0
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
- $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)
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 $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)
52
+ if rrds.options[:by_tags]
53
+ rrds.rotate_by_tags
276
54
  else
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
-
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
- all_snapshots.select {|ss| ss[:db_instance_identifier] == db_id }.
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,36 +2,43 @@
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.4.0 ruby lib
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.4.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 = "2022-12-24"
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]
18
18
  s.extra_rdoc_files = [
19
19
  "LICENSE.txt",
20
- "README.rdoc"
20
+ "README.md"
21
21
  ]
22
22
  s.files = [
23
23
  ".document",
24
- ".travis.yml",
24
+ ".github/dependabot.yml",
25
+ ".github/workflows/ci.yml",
26
+ ".github/workflows/codeql.yml",
27
+ ".rspec",
25
28
  "Gemfile",
26
29
  "LICENSE.txt",
27
- "README.rdoc",
30
+ "README.md",
28
31
  "Rakefile",
29
32
  "VERSION",
30
33
  "bin/rds-rotate-db-snapshots",
31
- "lib/.empty",
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",
32
39
  "rds-rotate-db-snapshots.gemspec",
33
- "test/helper.rb",
34
- "test/test_rds-rotate-db-snapshots.rb"
40
+ "spec/helper.rb",
41
+ "spec/rds_rotate_db_snapshots_spec.rb"
35
42
  ]
36
43
  s.homepage = "http://github.com/serg-kovalev/rds-rotate-db-snapshots".freeze
37
44
  s.licenses = ["MIT".freeze]
@@ -44,14 +51,16 @@ Gem::Specification.new do |s|
44
51
 
45
52
  if s.respond_to? :add_runtime_dependency then
46
53
  s.add_runtime_dependency(%q<aws-sdk-rds>.freeze, ["~> 1"])
47
- s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
48
- s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
54
+ s.add_runtime_dependency(%q<rake>.freeze, [">= 0"])
49
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"])
50
58
  else
51
59
  s.add_dependency(%q<aws-sdk-rds>.freeze, ["~> 1"])
52
- s.add_dependency(%q<bundler>.freeze, [">= 0"])
53
- s.add_dependency(%q<simplecov>.freeze, [">= 0"])
60
+ s.add_dependency(%q<rake>.freeze, [">= 0"])
54
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"])
55
64
  end
56
65
  end
57
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.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: 2022-12-24 00:00:00.000000000 Z
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: bundler
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: simplecov
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: juwelier
70
+ name: pry-byebug
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - ">="
@@ -74,20 +88,27 @@ executables:
74
88
  extensions: []
75
89
  extra_rdoc_files:
76
90
  - LICENSE.txt
77
- - README.rdoc
91
+ - README.md
78
92
  files:
79
93
  - ".document"
80
- - ".travis.yml"
94
+ - ".github/dependabot.yml"
95
+ - ".github/workflows/ci.yml"
96
+ - ".github/workflows/codeql.yml"
97
+ - ".rspec"
81
98
  - Gemfile
82
99
  - LICENSE.txt
83
- - README.rdoc
100
+ - README.md
84
101
  - Rakefile
85
102
  - VERSION
86
103
  - bin/rds-rotate-db-snapshots
87
- - lib/.empty
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
88
109
  - rds-rotate-db-snapshots.gemspec
89
- - test/helper.rb
90
- - test/test_rds-rotate-db-snapshots.rb
110
+ - spec/helper.rb
111
+ - spec/rds_rotate_db_snapshots_spec.rb
91
112
  homepage: http://github.com/serg-kovalev/rds-rotate-db-snapshots
92
113
  licenses:
93
114
  - MIT
data/.travis.yml DELETED
@@ -1,9 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 1.9.3
4
- - 2.1
5
- - 2.2
6
- #- ruby-head
7
- - jruby
8
- #- jruby-head
9
- - rbx-2
data/README.rdoc DELETED
@@ -1,65 +0,0 @@
1
- = rds-rotate-db-snapshots
2
- {<img src="https://badge.fury.io/rb/rds-rotate-db-snapshots.svg" alt="Gem Version" />}[http://badge.fury.io/rb/rds-rotate-db-snapshots] {<img src="https://travis-ci.org/serg-kovalev/rds-rotate-db-snapshots.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/serg-kovalev/rds-rotate-db-snapshots] {<img src="https://hakiri.io/github/serg-kovalev/rds-rotate-db-snapshots/master.svg" alt="security" />}[https://hakiri.io/github/serg-kovalev/rds-rotate-db-snapshots/master]
3
-
4
- Provides a simple way to rotate db snapshots in Amazon Relational Database Service (RDS).
5
-
6
- == Tested on Rubies
7
- - 1.9.3
8
- - 2.1
9
- - 2.2
10
- - jruby
11
- - rbx-2
12
-
13
- == Usage
14
-
15
- [Gem installation]
16
- gem install rds-rotate-db-snapshots
17
-
18
- [Usage]
19
- rds-rotate-db-snapshots [options] <db_indentifier>
20
-
21
- Add this script to CRON (let's say it will run this script every X hours) and it will do the job well
22
- #/usr/bin/bash
23
- AWS_ACCESS_KEY='xxxxxxxxxxxxxxxxxxxx'
24
- AWS_SECRET_ACCESS_KEY='yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
25
- AWS_REGION='eu-west-1'
26
- DESCRIPTION_PREFIX='automatic-backup-'
27
- RDS_ROTATOR=/here/is/the/path/to/rds-rotate-db-snapshots
28
- DB_NAME='db_name_here'
29
-
30
- $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
31
-
32
- == Options
33
-
34
- [--aws-access-key ACCESS_KEY] "AWS Access Key"
35
- [--aws-secret-access-key SECRET_KEY] "AWS Secret Access Key"
36
- [--aws-region REGION] "AWS Region"
37
- [--pattern STRING] "Snapshots without this string in the description will be ignored"
38
- [--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."
39
- [--backoff-limit INTEGER] "Backoff and retry when hitting RDS Error exceptions no more than this many times. Default is 15"
40
- [--create-snapshot STRING] "Use this option if you want to create a snapshot"
41
- [--keep-hourly INTEGER] "Number of hourly snapshots to keep"
42
- [--keep-daily INTEGER] "Number of daily snapshots to keep"
43
- [--keep-weekly INTEGER] "Number of weekly snapshots to keep"
44
- [--keep-last] "Keep the most recent snapshot, regardless of time-based policy"
45
- [--dry-run] "Shows what would happen without doing anything"
46
-
47
- == Tips
48
-
49
- If you are not sure what happen - add option "dry-run".
50
-
51
- In that case the script will not destroy/create anything in RDS, it will just show the messages.
52
-
53
- == Contributing to rds-rotate-db-snapshots
54
-
55
- * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
56
- * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
57
- * Fork the project
58
- * Start a feature/bugfix branch
59
- * Commit and push until you are happy with your contribution
60
- * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
61
- * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
62
-
63
- == Copyright
64
-
65
- Copyright (c) 2014 Siarhei Kavaliou. See LICENSE.txt for further details.
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
@@ -1,7 +0,0 @@
1
- require_relative 'helper'
2
-
3
- class TestRdsRotateDbSnapshots < Minitest::Test
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
6
- end
7
- end