rds-rotate-db-snapshots 0.5.1 → 1.0.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: ca82ea61b9da51d2888c98f21fda30ce73850b265dcb01b75c2e99c0b84d597d
4
- data.tar.gz: 778fc695f23d0c713bedad76b2ce4893401c50d270df596c1324b52dcd0546b7
3
+ metadata.gz: 1f21f380e6c4adfe878d359b84cf4842cc143225c81656e10e77a8e7f1d5efa0
4
+ data.tar.gz: e9e43b9f8fc687a263a3f5cf49f5bfecd3b5fcb69200546adbcb75856f7348ce
5
5
  SHA512:
6
- metadata.gz: f8bca5be1bb1d4324ae674677f574707782f92cb9717ec66010a8686ceca450d0127017c109289738c487f25131f6cf355ad60d4a33d8c71d0b2c2278ea24b68
7
- data.tar.gz: b00fe71aab391fa82e64b52f1de28bfc68632f0ae5a7961b5e1358fb55a28d19f1d9dd44dbcd59e81ce06204b8d26f290c2c253e303386144b0525c75ff1c3ec
6
+ metadata.gz: 0d0a8ef6a1b168e0bac4d93b4b13ba2b7907bdbe7fd6307a596c739d36247dc252af8d665faf728a50402edb7e69940ee9c568b3601b4e3c219308861596b9c8
7
+ data.tar.gz: '09a5010edb7bad75fc94e4c64c0c523e0acf3e547224cfe33e107a0616cdc5eb8920ccf0fe91aafd25073216f1460f767996fdcc952228ca9d92daa4527491f3'
@@ -2,16 +2,16 @@ name: "CI"
2
2
 
3
3
  on:
4
4
  push:
5
- branches: ["main"]
5
+ branches: ["master"]
6
6
  pull_request:
7
- branches: ["main"]
7
+ branches: ["master"]
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, 'jruby']
15
15
  steps:
16
16
  - name: Checkout code
17
17
  uses: actions/checkout@v3
@@ -25,19 +25,4 @@ jobs:
25
25
  - name: Bundle Install
26
26
  run: bundle install
27
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
28
+ run: bundle exec rake test
@@ -13,10 +13,10 @@ name: "CodeQL"
13
13
 
14
14
  on:
15
15
  push:
16
- branches: [ "main" ]
16
+ branches: [ "master" ]
17
17
  pull_request:
18
18
  # The branches below must be a subset of the branches above
19
- branches: [ "main" ]
19
+ branches: [ "master" ]
20
20
  schedule:
21
21
  - cron: '22 4 * * 2'
22
22
 
data/Gemfile CHANGED
@@ -1,19 +1,20 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gem 'aws-sdk-rds', '~> 1'
4
- gem "rake"
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
5
11
 
6
12
  group :development do
7
13
  gem 'juwelier'
8
- gem "pry"
9
- gem "pry-byebug"
10
14
  end
11
15
 
12
16
  group :test do
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"
17
+ gem 'rake'
18
+ gem 'shoulda'
19
+ gem 'minitest'
19
20
  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%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)
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)
5
5
 
6
6
  Provides a simple way to rotate db snapshots in Amazon Relational Database
7
7
  Service (RDS).
@@ -11,6 +11,7 @@ Service (RDS).
11
11
  - 2.7
12
12
  - 3.1
13
13
  - 3.2
14
+ - jruby
14
15
 
15
16
  ## Usage
16
17
 
@@ -64,7 +65,7 @@ show the messages.
64
65
 
65
66
  ## Contributing to rds-rotate-db-snapshots
66
67
 
67
- - Check out the latest main to make sure the feature hasn't been
68
+ - Check out the latest master to make sure the feature hasn't been
68
69
  implemented or the bug hasn't been fixed yet
69
70
  - Check out the issue tracker to make sure someone already hasn't requested
70
71
  it and/or contributed it
data/Rakefile CHANGED
@@ -22,6 +22,21 @@ 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
+
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.5.1
1
+ 0.4.3
@@ -1,25 +1,200 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../lib/rds_rotate_db_snapshots'
3
+ require 'rubygems'
4
+ require 'aws-sdk-rds'
5
+ require 'optparse'
4
6
 
5
- rrds = RdsRotateDbSnapshots.new(script_name: File.basename($0), cli: true)
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
- 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
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
10
86
  end
11
87
 
12
- if rrds.options[:aws_region].nil?
13
- puts "You must specify your AWS Region via --aws-region"
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"
14
189
  exit 1
15
190
  end
16
191
 
17
- if ARGV.empty? and rrds.options[:by_tags].nil?
192
+ if ARGV.empty? and $opts[:by_tags].nil?
18
193
  puts "You must provide at least one DB Indentifier when not rotating by tags"
19
194
  exit 1
20
195
  end
21
196
 
22
- if rrds.options[:by_tags].nil?
197
+ if $opts[:by_tags].nil?
23
198
  db_indentifier_ids = ARGV
24
199
 
25
200
  db_indentifier_ids.each do |db_id|
@@ -33,29 +208,83 @@ else
33
208
  if !ARGV.empty?
34
209
  puts "Ignoring supplied db_indentifier_ids because we're rotating by tags."
35
210
  end
36
- if rrds.options[:by_tags].length == 0
211
+ if $opts[:by_tags].length == 0
37
212
  puts "Rotating by tags but no tags specified? Refusing to rotate all snapshots!"
38
213
  exit 1
39
214
  end
40
215
  end
41
216
 
42
- if rrds.options[:backoff_limit] < 0
217
+ if $opts[:backoff_limit] < 0
43
218
  puts "A negative backoff limit doesn't make much sense."
44
219
  exit 1
45
220
  end
46
221
 
47
- if rrds.options[:create_snapshot]
48
- rrds.create_snapshot(rrds.options[:create_snapshot], db_indentifier_ids)
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)
49
238
  end
50
239
 
51
240
  all_snapshots = []
52
- if rrds.options[:by_tags]
53
- rrds.rotate_by_tags
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)
54
276
  else
55
- snapshots = rrds.get_db_snapshots(snapshot_type: 'manual').delete_if{ |e| e[:status] != 'available' }
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
+
56
285
  db_indentifier_ids.each do |db_id|
57
- rrds.rotate_em(
58
- snapshots.select {|ss| ss[:db_instance_identifier] == db_id }.
286
+ rotate_em(
287
+ all_snapshots.select {|ss| ss[:db_instance_identifier] == db_id }.
59
288
  sort {|a,b| a[:snapshot_create_time] <=> b[:snapshot_create_time] }
60
289
  )
61
290
  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.1 ruby lib
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.5.1"
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 = "2023-01-10"
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,47 +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
27
  "Gemfile",
29
28
  "LICENSE.txt",
30
29
  "README.md",
31
30
  "Rakefile",
32
31
  "VERSION",
33
32
  "bin/rds-rotate-db-snapshots",
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",
39
- "rds-rotate-db-snapshots.gemspec",
40
- "spec/helper.rb",
41
- "spec/lib/rds_rotate_db_snapshots/action_wrappers_spec.rb",
42
- "spec/lib/rds_rotate_db_snapshots/actions_spec.rb",
43
- "spec/lib/rds_rotate_db_snapshots/options_parser_spec.rb",
44
- "spec/lib/rds_rotate_db_snapshots/rds_client_spec.rb",
45
- "spec/lib/rds_rotate_db_snapshots_spec.rb"
33
+ "rds-rotate-db-snapshots.gemspec"
46
34
  ]
47
35
  s.homepage = "http://github.com/serg-kovalev/rds-rotate-db-snapshots".freeze
48
36
  s.licenses = ["MIT".freeze]
49
- s.rubygems_version = "3.3.7".freeze
37
+ s.rubygems_version = "3.5.3".freeze
50
38
  s.summary = "Amazon RDS DB snapshot rotator".freeze
51
39
 
52
- if s.respond_to? :specification_version then
53
- s.specification_version = 4
54
- end
40
+ s.specification_version = 4
55
41
 
56
- if s.respond_to? :add_runtime_dependency then
57
- s.add_runtime_dependency(%q<aws-sdk-rds>.freeze, ["~> 1"])
58
- s.add_runtime_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
- else
63
- s.add_dependency(%q<aws-sdk-rds>.freeze, ["~> 1"])
64
- s.add_dependency(%q<rake>.freeze, [">= 0"])
65
- s.add_dependency(%q<juwelier>.freeze, [">= 0"])
66
- s.add_dependency(%q<pry>.freeze, [">= 0"])
67
- s.add_dependency(%q<pry-byebug>.freeze, [">= 0"])
68
- end
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])
69
49
  end
70
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.5.1
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: 2023-01-10 00:00:00.000000000 Z
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
@@ -31,7 +31,7 @@ dependencies:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
- type: :runtime
34
+ type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description: Provides a simple way to rotate RDS DB snapshots with configurable retention
84
112
  periods.
85
113
  email: kovserg@gmail.com
@@ -94,25 +122,13 @@ files:
94
122
  - ".github/dependabot.yml"
95
123
  - ".github/workflows/ci.yml"
96
124
  - ".github/workflows/codeql.yml"
97
- - ".rspec"
98
125
  - Gemfile
99
126
  - LICENSE.txt
100
127
  - README.md
101
128
  - Rakefile
102
129
  - VERSION
103
130
  - bin/rds-rotate-db-snapshots
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
109
131
  - rds-rotate-db-snapshots.gemspec
110
- - spec/helper.rb
111
- - spec/lib/rds_rotate_db_snapshots/action_wrappers_spec.rb
112
- - spec/lib/rds_rotate_db_snapshots/actions_spec.rb
113
- - spec/lib/rds_rotate_db_snapshots/options_parser_spec.rb
114
- - spec/lib/rds_rotate_db_snapshots/rds_client_spec.rb
115
- - spec/lib/rds_rotate_db_snapshots_spec.rb
116
132
  homepage: http://github.com/serg-kovalev/rds-rotate-db-snapshots
117
133
  licenses:
118
134
  - MIT
@@ -132,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
148
  - !ruby/object:Gem::Version
133
149
  version: '0'
134
150
  requirements: []
135
- rubygems_version: 3.3.7
151
+ rubygems_version: 3.5.3
136
152
  signing_key:
137
153
  specification_version: 4
138
154
  summary: Amazon RDS DB snapshot rotator
data/.rspec DELETED
@@ -1 +0,0 @@
1
- -w --color
@@ -1,26 +0,0 @@
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::ServiceError => e
13
- raise if e.is_a? Aws::RDS::Errors::ExpiredToken
14
- # TODO: re-work
15
- puts "Error: #{e}"
16
- backoff
17
- retry
18
- end
19
-
20
- end
21
- end
22
- self.prepend wrapper
23
- end
24
- end
25
- end
26
- end
@@ -1,105 +0,0 @@
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
@@ -1,110 +0,0 @@
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
- end
99
-
100
- def split_tag(hash,v)
101
- v.split(',').each do |pair|
102
- tag, value = pair.split('=',2)
103
- if value.nil?
104
- raise InvalidArgument, "invalid tag=value format"
105
- end
106
- hash[tag] = value
107
- end
108
- end
109
- end
110
- end
@@ -1,20 +0,0 @@
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
@@ -1,58 +0,0 @@
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
data/spec/helper.rb DELETED
@@ -1,81 +0,0 @@
1
- $TESTING = true
2
-
3
- require "simplecov"
4
-
5
- SimpleCov.start do
6
- add_filter "/spec"
7
- minimum_coverage(75)
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
@@ -1,45 +0,0 @@
1
- require 'helper'
2
-
3
- class TestClass
4
- def initialize
5
- @backoff_counter = 0
6
- @options = { backoff_limit: 5 }
7
- end
8
-
9
- def test_method
10
- raise Aws::RDS::Errors::ServiceError.new(nil, 'service error')
11
- end
12
-
13
- def reset_backoff
14
- @backoff_counter = 0
15
- end
16
-
17
- def backoff
18
- @backoff_counter = @backoff_counter + 1
19
-
20
- raise StandardError, 'gave up' if @options[:backoff_limit] > 0 && @options[:backoff_limit] < @backoff_counter
21
- end
22
-
23
- extend RdsRotateDbSnapshots::ActionWrappers
24
-
25
- with_backoff :test_method
26
- end
27
-
28
- describe RdsRotateDbSnapshots::ActionWrappers do
29
- subject { TestClass.new }
30
-
31
- describe "#with_backoff" do
32
- it "does not retry if the exception raised is Aws::RDS::Errors::ExpiredToken" do
33
- allow(subject).to receive(:test_method).and_raise(Aws::RDS::Errors::ExpiredToken.new(nil, 'token expired'))
34
- expect(subject).not_to receive(:reset_backoff)
35
- expect(subject).not_to receive(:backoff)
36
- expect{subject.test_method}.to raise_error(Aws::RDS::Errors::ExpiredToken)
37
- end
38
-
39
- it "retries if the exception raised is Aws::RDS::Errors::ServiceError" do
40
- expect(subject).to receive(:backoff).exactly(6).and_call_original
41
-
42
- expect{ subject.test_method }.to raise_error(StandardError, 'gave up')
43
- end
44
- end
45
- end
@@ -1,96 +0,0 @@
1
- require 'helper'
2
-
3
- RSpec.shared_examples 'rds_rotate_db_snapshots actions' do
4
- let(:rds_client) { instance_double(RdsRotateDbSnapshots::RdsClient) }
5
- let(:client) { rds_client }
6
- let(:options) do
7
- {
8
- aws_access_key: 'ACCESS_KEY',
9
- aws_secret_access_key: 'SECRET_KEY',
10
- aws_region: 'REGION',
11
- pattern: 'test',
12
- dry_run: true,
13
- backoff_limit: 15
14
- }
15
- end
16
- let(:rds_snapshots) do
17
- [
18
- { snapshot_create_time: Time.now, db_instance_identifier: 'test_db', db_snapshot_identifier: 'test_snapshot' }
19
- ]
20
- end
21
-
22
- before do
23
- allow(subject).to receive(:client).and_return(rds_client)
24
- allow(rds_client).to receive(:describe_db_snapshots).and_return(rds_snapshots)
25
- allow(rds_client).to receive(:create_db_snapshot)
26
- allow(rds_client).to receive(:delete_db_snapshot)
27
- end
28
-
29
- describe "#rotate_em" do
30
- it "deletes the snapshots that are not part of the specified time periods" do
31
- expect(rds_client).to receive(:delete_db_snapshot)
32
- subject.rotate_em(rds_snapshots)
33
- end
34
- end
35
-
36
- describe "#create_snapshot" do
37
- it "creates a snapshot with the specified name" do
38
- expect(rds_client).to receive(:create_db_snapshot)
39
- subject.create_snapshot('test', ['test_db'])
40
- end
41
- end
42
-
43
- describe "#get_db_snapshots" do
44
- let(:snapshots) { double('snapshots', db_snapshots: rds_snapshots) }
45
-
46
- it "returns the list of snapshots from the client" do
47
- allow(snapshots).to receive(:[]).with(:marker).and_return(nil)
48
- expect(rds_client).to receive(:describe_db_snapshots).and_return(snapshots)
49
- snapshots = subject.get_db_snapshots(options)
50
- expect(snapshots).to eq(rds_snapshots)
51
- end
52
- end
53
-
54
- end
55
-
56
- class Test
57
- include RdsRotateDbSnapshots::Actions
58
-
59
- attr_reader :options
60
-
61
- def initialize(script_name: nil, cli: false, options: {})
62
- @script_name = script_name
63
- @options = options
64
- @cli = cli
65
- parse_options if cli?
66
- @backoff_counter = 0
67
- end
68
-
69
- def reset_backoff
70
- @backoff_counter = 0
71
- end
72
-
73
- def time_periods
74
- @options[:time_periods] || {}
75
- end
76
-
77
- private
78
-
79
- def cli?
80
- !!@cli
81
- end
82
-
83
- def parse_options
84
- @options = RdsRotateDbSnapshots::OptionsParser.new(script_name: @script_name, cli: @cli).parse!
85
- end
86
-
87
- def backoff
88
- @backoff_counter = @backoff_counter + 1
89
- end
90
- end
91
-
92
- describe RdsRotateDbSnapshots::Actions do
93
- subject { Test.new }
94
-
95
- it_behaves_like 'rds_rotate_db_snapshots actions'
96
- end
@@ -1,33 +0,0 @@
1
- require 'helper'
2
-
3
- describe RdsRotateDbSnapshots::OptionsParser do
4
- let(:script_name) { "rds_rotate_snapshots.rb" }
5
- subject { RdsRotateDbSnapshots::OptionsParser.new(script_name: script_name, cli: true).parse! }
6
-
7
- describe "#parse!" do
8
- before { ARGV.clear }
9
-
10
- it "parses options correctly" do
11
- ARGV.concat(["--aws-access-key", "ACCESS_KEY",
12
- "--aws-secret-access-key", "SECRET_KEY",
13
- "--aws-region", "REGION",
14
- "--pattern", "PATTERN",
15
- "--backoff-limit", "20",
16
- "--create-snapshot", "snapshot"])
17
- options = subject
18
-
19
- expect(options[:aws_access_key]).to eq("ACCESS_KEY")
20
- expect(options[:aws_secret_access_key]).to eq("SECRET_KEY")
21
- expect(options[:aws_region]).to eq("REGION")
22
- expect(options[:pattern]).to eq("PATTERN")
23
- expect(options[:backoff_limit]).to eq("20")
24
- expect(options[:create_snapshot]).to eq("snapshot")
25
- end
26
-
27
- it "raises NotImplementedError when by-tags option is passed and it is not implemented" do
28
- ARGV.concat(["--by-tags", "tag=value,tag2=value"])
29
-
30
- expect { subject }.to raise_error(RdsRotateDbSnapshots::OptionsParser::NotImplementedError)
31
- end
32
- end
33
- end
@@ -1,32 +0,0 @@
1
- require 'helper'
2
-
3
- describe RdsRotateDbSnapshots::RdsClient do
4
- let(:options) { {
5
- :aws_access_key => "ACCESS_KEY",
6
- :aws_secret_access_key => "SECRET_KEY",
7
- :aws_session_token => "SESSION_TOKEN",
8
- :aws_region => "REGION"
9
- } }
10
- let(:rds_client) { RdsRotateDbSnapshots::RdsClient.new(options) }
11
-
12
- it 'configures the client with the correct credentials and region' do
13
- expect(rds_client.instance_variable_get(:@client).config.credentials).
14
- to have_attributes(access_key_id: "ACCESS_KEY", secret_access_key: "SECRET_KEY", session_token: "SESSION_TOKEN")
15
- expect(rds_client.instance_variable_get(:@client).config.region).to eq("REGION")
16
- end
17
-
18
- it 'delegates describe_db_snapshots method to the @client object' do
19
- expect(rds_client.instance_variable_get(:@client)).to receive(:describe_db_snapshots)
20
- rds_client.describe_db_snapshots
21
- end
22
-
23
- it 'delegates create_db_snapshot method to the @client object' do
24
- expect(rds_client.instance_variable_get(:@client)).to receive(:create_db_snapshot)
25
- rds_client.create_db_snapshot("test-snapshot")
26
- end
27
-
28
- it 'delegates delete_db_snapshot method to the @client object' do
29
- expect(rds_client.instance_variable_get(:@client)).to receive(:delete_db_snapshot)
30
- rds_client.delete_db_snapshot("test-snapshot")
31
- end
32
- end
@@ -1,62 +0,0 @@
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