rds-rotate-db-snapshots 0.5.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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