rds-rotate-db-snapshots 0.5.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +5 -40
- data/.github/workflows/codeql.yml +2 -2
- data/Gemfile +10 -10
- data/README.md +4 -8
- data/Rakefile +19 -4
- data/VERSION +1 -1
- data/bin/rds-rotate-db-snapshots +257 -25
- data/rds-rotate-db-snapshots.gemspec +12 -27
- metadata +3 -18
- data/.rspec +0 -1
- data/.rubocop.yml +0 -93
- data/.rubocop_todo.yml +0 -127
- data/lib/rds_rotate_db_snapshots/action_wrappers.rb +0 -26
- data/lib/rds_rotate_db_snapshots/actions.rb +0 -112
- data/lib/rds_rotate_db_snapshots/options_parser/options.rb +0 -80
- data/lib/rds_rotate_db_snapshots/options_parser.rb +0 -58
- data/lib/rds_rotate_db_snapshots/rds_client.rb +0 -20
- data/lib/rds_rotate_db_snapshots.rb +0 -59
- data/spec/helper.rb +0 -81
- data/spec/lib/rds_rotate_db_snapshots/action_wrappers_spec.rb +0 -45
- data/spec/lib/rds_rotate_db_snapshots/actions_spec.rb +0 -116
- data/spec/lib/rds_rotate_db_snapshots/options_parser_spec.rb +0 -30
- data/spec/lib/rds_rotate_db_snapshots/rds_client_spec.rb +0 -34
- data/spec/lib/rds_rotate_db_snapshots_spec.rb +0 -63
data/.rubocop.yml
DELETED
@@ -1,93 +0,0 @@
|
|
1
|
-
inherit_from: .rubocop_todo.yml
|
2
|
-
|
3
|
-
require:
|
4
|
-
- rubocop-rspec
|
5
|
-
|
6
|
-
AllCops:
|
7
|
-
NewCops: enable
|
8
|
-
TargetRubyVersion: 3.2.0
|
9
|
-
Exclude:
|
10
|
-
- 'bin/{bundle,rails,rake}'
|
11
|
-
- vendor/**/*
|
12
|
-
|
13
|
-
Layout/LineLength:
|
14
|
-
Max: 120
|
15
|
-
|
16
|
-
Bundler/OrderedGems:
|
17
|
-
Enabled: false
|
18
|
-
|
19
|
-
Lint/RescueException:
|
20
|
-
Enabled: true
|
21
|
-
|
22
|
-
Style/EmptyElse:
|
23
|
-
EnforcedStyle: empty
|
24
|
-
|
25
|
-
Style/FrozenStringLiteralComment:
|
26
|
-
Enabled: false
|
27
|
-
|
28
|
-
Style/RescueStandardError:
|
29
|
-
Enabled: false
|
30
|
-
|
31
|
-
Style/StringLiterals:
|
32
|
-
Enabled: false
|
33
|
-
|
34
|
-
Layout/SpaceInLambdaLiteral:
|
35
|
-
Enabled: false
|
36
|
-
|
37
|
-
Style/Lambda:
|
38
|
-
Enabled: false
|
39
|
-
|
40
|
-
Style/NumericLiteralPrefix:
|
41
|
-
Enabled: false
|
42
|
-
|
43
|
-
Style/EmptyMethod:
|
44
|
-
Enabled: false
|
45
|
-
|
46
|
-
Style/ParallelAssignment:
|
47
|
-
Enabled: false
|
48
|
-
|
49
|
-
Layout/EmptyLinesAroundExceptionHandlingKeywords:
|
50
|
-
Enabled: false
|
51
|
-
|
52
|
-
Layout/FirstHashElementIndentation:
|
53
|
-
Enabled: false
|
54
|
-
|
55
|
-
Layout/ParameterAlignment:
|
56
|
-
Enabled: false
|
57
|
-
|
58
|
-
Naming/RescuedExceptionsVariableName:
|
59
|
-
Enabled: false
|
60
|
-
|
61
|
-
Style/Documentation:
|
62
|
-
Enabled: false
|
63
|
-
|
64
|
-
Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1)
|
65
|
-
Enabled: true
|
66
|
-
Lint/EmptyBlock: # (new in 1.1)
|
67
|
-
Enabled: true
|
68
|
-
Lint/NoReturnInBeginEndBlocks: # (new in 1.2)
|
69
|
-
Enabled: true
|
70
|
-
Lint/ToEnumArguments: # (new in 1.1)
|
71
|
-
Enabled: true
|
72
|
-
Lint/UnmodifiedReduceAccumulator: # (new in 1.1)
|
73
|
-
Enabled: true
|
74
|
-
Style/ArgumentsForwarding: # (new in 1.1)
|
75
|
-
Enabled: true
|
76
|
-
Style/CollectionCompact: # (new in 1.2)
|
77
|
-
Enabled: true
|
78
|
-
Style/DocumentDynamicEvalDefinition: # (new in 1.1)
|
79
|
-
Enabled: true
|
80
|
-
Style/NegatedIfElseCondition: # (new in 1.2)
|
81
|
-
Enabled: true
|
82
|
-
Style/SwapValues: # (new in 1.1)
|
83
|
-
Enabled: true
|
84
|
-
|
85
|
-
Metrics/BlockLength:
|
86
|
-
Exclude:
|
87
|
-
- 'spec/**/*' # Swagger specs require large blocks
|
88
|
-
|
89
|
-
Style/HashSyntax:
|
90
|
-
EnforcedShorthandSyntax: never
|
91
|
-
|
92
|
-
RSpec/MultipleMemoizedHelpers:
|
93
|
-
Enabled: false
|
data/.rubocop_todo.yml
DELETED
@@ -1,127 +0,0 @@
|
|
1
|
-
# This configuration was generated by
|
2
|
-
# `rubocop --auto-gen-config`
|
3
|
-
# on 2023-01-10 16:06:29 UTC using RuboCop version 1.43.0.
|
4
|
-
# The point is for the user to remove these configuration records
|
5
|
-
# one by one as the offenses are removed from the code base.
|
6
|
-
# Note that changes in the inspected code, or installation of new
|
7
|
-
# versions of RuboCop, may require this file to be generated again.
|
8
|
-
|
9
|
-
# Offense count: 1
|
10
|
-
# Configuration parameters: Severity, Include.
|
11
|
-
# Include: **/*.gemspec
|
12
|
-
Gemspec/RequiredRubyVersion:
|
13
|
-
Exclude:
|
14
|
-
- 'rds-rotate-db-snapshots.gemspec'
|
15
|
-
|
16
|
-
# Offense count: 3
|
17
|
-
# This cop supports safe autocorrection (--autocorrect).
|
18
|
-
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns.
|
19
|
-
# URISchemes: http, https
|
20
|
-
Layout/LineLength:
|
21
|
-
Max: 146
|
22
|
-
|
23
|
-
# Offense count: 1
|
24
|
-
Lint/UnreachableCode:
|
25
|
-
Exclude:
|
26
|
-
- 'lib/rds_rotate_db_snapshots/options_parser.rb'
|
27
|
-
|
28
|
-
# Offense count: 4
|
29
|
-
Lint/UselessAssignment:
|
30
|
-
Exclude:
|
31
|
-
- 'bin/rds-rotate-db-snapshots'
|
32
|
-
- 'lib/rds_rotate_db_snapshots/actions.rb'
|
33
|
-
|
34
|
-
# Offense count: 5
|
35
|
-
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods, CountRepeatedAttributes.
|
36
|
-
Metrics/AbcSize:
|
37
|
-
Max: 56
|
38
|
-
|
39
|
-
# Offense count: 2
|
40
|
-
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods, inherit_mode.
|
41
|
-
# AllowedMethods: refine
|
42
|
-
Metrics/BlockLength:
|
43
|
-
Max: 42
|
44
|
-
|
45
|
-
# Offense count: 1
|
46
|
-
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods.
|
47
|
-
Metrics/CyclomaticComplexity:
|
48
|
-
Max: 15
|
49
|
-
|
50
|
-
# Offense count: 6
|
51
|
-
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods.
|
52
|
-
Metrics/MethodLength:
|
53
|
-
Max: 44
|
54
|
-
|
55
|
-
# Offense count: 1
|
56
|
-
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods.
|
57
|
-
Metrics/PerceivedComplexity:
|
58
|
-
Max: 16
|
59
|
-
|
60
|
-
# Offense count: 1
|
61
|
-
# Configuration parameters: EnforcedStyleForLeadingUnderscores.
|
62
|
-
# SupportedStylesForLeadingUnderscores: disallowed, required, optional
|
63
|
-
Naming/MemoizedInstanceVariableName:
|
64
|
-
Exclude:
|
65
|
-
- 'lib/rds_rotate_db_snapshots/options_parser.rb'
|
66
|
-
|
67
|
-
# Offense count: 1
|
68
|
-
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
69
|
-
# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
|
70
|
-
Naming/MethodParameterName:
|
71
|
-
Exclude:
|
72
|
-
- 'lib/rds_rotate_db_snapshots/options_parser.rb'
|
73
|
-
|
74
|
-
# Offense count: 3
|
75
|
-
# Configuration parameters: CountAsOne.
|
76
|
-
RSpec/ExampleLength:
|
77
|
-
Max: 9
|
78
|
-
|
79
|
-
# Offense count: 10
|
80
|
-
# Configuration parameters: .
|
81
|
-
# SupportedStyles: have_received, receive
|
82
|
-
RSpec/MessageSpies:
|
83
|
-
EnforcedStyle: receive
|
84
|
-
|
85
|
-
# Offense count: 6
|
86
|
-
RSpec/MultipleExpectations:
|
87
|
-
Max: 6
|
88
|
-
|
89
|
-
# Offense count: 17
|
90
|
-
# Configuration parameters: EnforcedStyle, IgnoreSharedExamples.
|
91
|
-
# SupportedStyles: always, named_only
|
92
|
-
RSpec/NamedSubject:
|
93
|
-
Exclude:
|
94
|
-
- 'spec/lib/rds_rotate_db_snapshots/action_wrappers_spec.rb'
|
95
|
-
- 'spec/lib/rds_rotate_db_snapshots/options_parser_spec.rb'
|
96
|
-
- 'spec/lib/rds_rotate_db_snapshots_spec.rb'
|
97
|
-
|
98
|
-
# Offense count: 1
|
99
|
-
RSpec/StubbedMock:
|
100
|
-
Exclude:
|
101
|
-
- 'spec/lib/rds_rotate_db_snapshots/actions_spec.rb'
|
102
|
-
|
103
|
-
# Offense count: 6
|
104
|
-
RSpec/SubjectStub:
|
105
|
-
Exclude:
|
106
|
-
- 'spec/lib/rds_rotate_db_snapshots/action_wrappers_spec.rb'
|
107
|
-
- 'spec/lib/rds_rotate_db_snapshots/actions_spec.rb'
|
108
|
-
- 'spec/lib/rds_rotate_db_snapshots_spec.rb'
|
109
|
-
|
110
|
-
# Offense count: 1
|
111
|
-
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
|
112
|
-
RSpec/VerifiedDoubles:
|
113
|
-
Exclude:
|
114
|
-
- 'spec/lib/rds_rotate_db_snapshots/actions_spec.rb'
|
115
|
-
|
116
|
-
# Offense count: 1
|
117
|
-
# Configuration parameters: AllowedVariables.
|
118
|
-
Style/GlobalVars:
|
119
|
-
Exclude:
|
120
|
-
- 'spec/helper.rb'
|
121
|
-
|
122
|
-
# Offense count: 3
|
123
|
-
# This cop supports safe autocorrection (--autocorrect).
|
124
|
-
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns.
|
125
|
-
# URISchemes: http, https
|
126
|
-
Layout/LineLength:
|
127
|
-
Max: 146
|
@@ -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
|
-
|
15
|
-
# TODO: re-work
|
16
|
-
puts "Error: #{e}"
|
17
|
-
backoff
|
18
|
-
retry
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
prepend wrapper
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,112 +0,0 @@
|
|
1
|
-
class RdsRotateDbSnapshots
|
2
|
-
module Actions
|
3
|
-
def rotate_em(snapshots)
|
4
|
-
snapshots.each do |snapshot|
|
5
|
-
time = snapshot[:snapshot_create_time]
|
6
|
-
snapshot_id = snapshot[:db_snapshot_identifier]
|
7
|
-
description = snapshot_id
|
8
|
-
|
9
|
-
if options[:pattern] && description !~ /#{options[:pattern]}/
|
10
|
-
puts " #{time.strftime '%Y-%m-%d %H:%M:%S'} #{snapshot_id} Skipping snapshot with description #{description}"
|
11
|
-
next
|
12
|
-
end
|
13
|
-
|
14
|
-
keep_reason = rotate_period_based_decision_with(time, snapshot)
|
15
|
-
keep_reason = 'last snapshot' if keep_reason.nil? && snapshot == snapshots.last && options[:keep_last]
|
16
|
-
rotate_or_keep_snapshot(keep_reason, time, snapshot_id)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def create_snapshot(name, db_indentifier_ids)
|
21
|
-
return if name.nil?
|
22
|
-
|
23
|
-
name = name.gsub(/[^a-zA-Z0-9-]/, '')
|
24
|
-
if name.size.positive?
|
25
|
-
create_db_snapshots(name: name, db_indentifier_ids: db_indentifier_ids)
|
26
|
-
else
|
27
|
-
puts "invalid snapshot name format - #{name}"
|
28
|
-
exit 1
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def get_db_snapshots(options)
|
33
|
-
snapshots = []
|
34
|
-
response = client.describe_db_snapshots(options)
|
35
|
-
loop do
|
36
|
-
snapshots += response.db_snapshots
|
37
|
-
break unless response[:marker]
|
38
|
-
|
39
|
-
response = client.describe_db_snapshots(options.merge(marker: response[:marker]))
|
40
|
-
end
|
41
|
-
snapshots
|
42
|
-
end
|
43
|
-
|
44
|
-
def rotate_by_tags
|
45
|
-
snapshots = []
|
46
|
-
options[:by_tags].each do |tag, value|
|
47
|
-
snapshots = rrds.client.describe_tags(
|
48
|
-
snapshot_type: 'manual', filters: { 'resource-type' => "snapshot", 'key' => tag, 'value' => value }
|
49
|
-
).delete_if { |e| e.status != 'available' }
|
50
|
-
# TODO: re-work
|
51
|
-
if snapshots.empty?
|
52
|
-
puts "(tag,value)=(#{tag},#{value}) found no snapshots; nothing to rotate!"
|
53
|
-
exit 0
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
snapshots = get_db_snapshots(db_instance_identifier: snapshots.map(&:db_instance_identifier).uniq)
|
58
|
-
.delete_if { |e| !snapshots.include?(e.db_snapshot_identifier) }
|
59
|
-
.sort { |a, b| a[:snapshot_create_time] <=> b[:snapshot_create_time] }
|
60
|
-
|
61
|
-
rotate_em snapshots
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def rotate_period_based_decision_with(time, snapshot)
|
67
|
-
# poor man's way to get a deep copy of our time_periods definition hash
|
68
|
-
periods = Marshal.load(Marshal.dump(time_periods))
|
69
|
-
keep_reason = nil
|
70
|
-
|
71
|
-
periods.keys.sort { |a, b| periods[a][:seconds] <=> periods[b][:seconds] }.each do |period|
|
72
|
-
period_info = periods[period]
|
73
|
-
keep = period_info[:keep]
|
74
|
-
keeping = period_info[:keeping]
|
75
|
-
|
76
|
-
time_string = time.strftime period_info[:format]
|
77
|
-
next unless Time.now - time < keep * period_info[:seconds]
|
78
|
-
|
79
|
-
if !keeping.key?(time_string) && keeping.length < keep
|
80
|
-
keep_reason = period
|
81
|
-
keeping[time_string] = snapshot
|
82
|
-
end
|
83
|
-
break
|
84
|
-
end
|
85
|
-
|
86
|
-
keep_reason
|
87
|
-
end
|
88
|
-
|
89
|
-
def rotate_or_keep_snapshot(keep_reason, time, snapshot_id)
|
90
|
-
if keep_reason.nil?
|
91
|
-
puts " #{time.strftime '%Y-%m-%d %H:%M:%S'} #{snapshot_id} Deleting"
|
92
|
-
client.delete_db_snapshot(db_snapshot_identifier: snapshot_id) unless options[:dry_run]
|
93
|
-
else
|
94
|
-
puts " #{time.strftime '%Y-%m-%d %H:%M:%S'} #{snapshot_id} Keeping for #{keep_reason}"
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def create_db_snapshots(name:, db_indentifier_ids:)
|
99
|
-
name = "#{name}-#{Time.now.strftime('%Y%m%d%H%M%S')}"
|
100
|
-
db_indentifier_ids.each do |db_id|
|
101
|
-
unless options[:dry_run]
|
102
|
-
client.create_db_snapshot(db_snapshot_identifier: name,
|
103
|
-
db_instance_identifier: db_id)
|
104
|
-
end
|
105
|
-
puts " #{Time.now.strftime '%Y-%m-%d %H:%M:%S'} Creation snapshot #{name} is pending (db: #{db_id})"
|
106
|
-
rescue Aws::RDS::Errors::InvalidDBInstanceStateFault
|
107
|
-
backoff
|
108
|
-
retry
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
@@ -1,80 +0,0 @@
|
|
1
|
-
class RdsRotateDbSnapshots
|
2
|
-
class OptionsParser
|
3
|
-
module Options
|
4
|
-
def split_tag(hash, value)
|
5
|
-
value.split(',').each do |pair|
|
6
|
-
tag, value = pair.split('=', 2)
|
7
|
-
raise InvalidArgument, "invalid tag=value format" if value.nil?
|
8
|
-
|
9
|
-
hash[tag] = value
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def banner_opts(opt)
|
14
|
-
opt.banner = "Usage: #{script_name} [options] <db_indentifier>\nUsage: #{script_name} " \
|
15
|
-
"--by-tags <tag=value,...> [other options]"
|
16
|
-
opt.separator ""
|
17
|
-
end
|
18
|
-
|
19
|
-
def aws_opts(opt)
|
20
|
-
opt.on("--aws-access-key ACCESS_KEY", "AWS Access Key") do |v|
|
21
|
-
@options[:aws_access_key] = v
|
22
|
-
end
|
23
|
-
|
24
|
-
opt.on("--aws-secret-access-key SECRET_KEY", "AWS Secret Access Key") do |v|
|
25
|
-
@options[:aws_secret_access_key] = v
|
26
|
-
end
|
27
|
-
|
28
|
-
opt.on("--aws-region REGION", "AWS Region") do |v|
|
29
|
-
@options[:aws_region] = v
|
30
|
-
end
|
31
|
-
|
32
|
-
opt.on("--aws-session-token SESSION_TOKEN", "AWS session token") do |v|
|
33
|
-
@options[:aws_session_token] = v
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def not_supported_opts(opt)
|
38
|
-
opt.on("--by-tags TAG=VALUE,TAG=VALUE",
|
39
|
-
"Instead of rotating specific snapshots, rotate over all the snapshots having the intersection of all " \
|
40
|
-
"given TAG=VALUE pairs.") do |_v|
|
41
|
-
@options[:by_tags] = {}
|
42
|
-
raise NotImplementedError,
|
43
|
-
'Hey! It\'s not implemented in RDS yet. Who knows, maybe they will add Tagging in RDS later.'
|
44
|
-
# split_tag(@options[:by_tags], v)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def snapshot_create_opts(opt)
|
49
|
-
opt.on("--create-snapshot STRING", "Use this option if you want to create a snapshot") do |v|
|
50
|
-
@options[:create_snapshot] = v
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def time_period_opts(opt)
|
55
|
-
@time_periods.keys.sort { |a, b| @time_periods[a][:seconds] <=> @time_periods[b][:seconds] }.each do |period|
|
56
|
-
opt.on("--keep-#{period} NUMBER", Integer, "Number of #{period} snapshots to keep") do |v|
|
57
|
-
@time_periods[period][:keep] = v
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
opt.on("--keep-last", "Keep the most recent snapshot, regardless of time-based policy") do |_v|
|
62
|
-
@options[:keep_last] = true
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def extra_opts(opt)
|
67
|
-
opt.on("--pattern STRING", "Snapshots without this string in the description will be ignored") do |v|
|
68
|
-
@options[:pattern] = v
|
69
|
-
end
|
70
|
-
opt.on("--backoff-limit LIMIT",
|
71
|
-
"Backoff and retry when hitting RDS Error exceptions no more than this many times. Default is 15") do |v|
|
72
|
-
@options[:backoff_limit] = v
|
73
|
-
end
|
74
|
-
opt.on("--dry-run", "Shows what would happen without doing anything") do |_v|
|
75
|
-
@options[:dry_run] = true
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
@@ -1,58 +0,0 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
require_relative 'options_parser/options'
|
3
|
-
class RdsRotateDbSnapshots
|
4
|
-
class OptionsParser
|
5
|
-
class NotImplementedError < StandardError; end
|
6
|
-
class InvalidArgument < StandardError; end
|
7
|
-
|
8
|
-
include RdsRotateDbSnapshots::OptionsParser::Options
|
9
|
-
|
10
|
-
attr_reader :options, :script_name, :time_periods
|
11
|
-
|
12
|
-
def initialize(script_name: nil, cli: false)
|
13
|
-
@script_name = script_name
|
14
|
-
@options = {
|
15
|
-
aws_access_key: ENV.fetch("AWS_ACCESS_KEY_ID", nil),
|
16
|
-
aws_secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY", nil),
|
17
|
-
aws_session_token: ENV.fetch("AWS_SESSION_TOKEN", nil),
|
18
|
-
aws_region: ENV.fetch("AWS_REGION", nil),
|
19
|
-
pattern: nil,
|
20
|
-
by_tags: nil,
|
21
|
-
dry_run: false,
|
22
|
-
backoff_limit: 15,
|
23
|
-
create_snapshot: nil
|
24
|
-
}
|
25
|
-
@time_periods = {
|
26
|
-
hourly: { seconds: 60 * 60, format: '%Y-%m-%d-%H', keep: 0, keeping: {} },
|
27
|
-
daily: { seconds: 24 * 60 * 60, format: '%Y-%m-%d', keep: 0, keeping: {} },
|
28
|
-
weekly: { seconds: 7 * 24 * 60 * 60, format: '%Y-%W', keep: 0, keeping: {} },
|
29
|
-
monthly: { seconds: 30 * 24 * 60 * 60, format: '%Y-%m', keep: 0, keeping: {} },
|
30
|
-
yearly: { seconds: 12 * 30 * 24 * 60 * 60, format: '%Y', keep: 0, keeping: {} }
|
31
|
-
}
|
32
|
-
@cli = cli
|
33
|
-
init_cli_parser if cli?
|
34
|
-
end
|
35
|
-
|
36
|
-
def parse!
|
37
|
-
@parser.parse!
|
38
|
-
@options.merge(time_periods: @time_periods)
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def cli?
|
44
|
-
!!@cli
|
45
|
-
end
|
46
|
-
|
47
|
-
def init_cli_parser
|
48
|
-
@parser ||= OptionParser.new do |o|
|
49
|
-
banner_opts o
|
50
|
-
aws_opts o
|
51
|
-
snapshot_create_opts o
|
52
|
-
time_period_opts o
|
53
|
-
extra_opts o
|
54
|
-
not_supported_opts o
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
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,59 +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
|
-
|
13
|
-
with_backoff :get_db_snapshots, :create_snapshot, :rotate_em
|
14
|
-
|
15
|
-
def initialize(script_name: nil, cli: false, options: {})
|
16
|
-
@script_name = script_name
|
17
|
-
@options = options
|
18
|
-
@cli = cli
|
19
|
-
parse_options if cli?
|
20
|
-
@backoff_counter = 0
|
21
|
-
end
|
22
|
-
|
23
|
-
def rds_client
|
24
|
-
@rds_client ||= RdsRotateDbSnapshots::RdsClient.new(@options)
|
25
|
-
end
|
26
|
-
alias client rds_client
|
27
|
-
|
28
|
-
def reset_backoff
|
29
|
-
@backoff_counter = 0
|
30
|
-
end
|
31
|
-
|
32
|
-
def time_periods
|
33
|
-
@options[:time_periods]
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def cli?
|
39
|
-
!!@cli
|
40
|
-
end
|
41
|
-
|
42
|
-
def parse_options
|
43
|
-
@options = RdsRotateDbSnapshots::OptionsParser.new(script_name: @script_name, cli: @cli).parse!
|
44
|
-
end
|
45
|
-
|
46
|
-
def backoff
|
47
|
-
@backoff_counter += 1
|
48
|
-
|
49
|
-
# TODO: re-work
|
50
|
-
if options && options[:backoff_limit].positive? && options[:backoff_limit] < @backoff_counter
|
51
|
-
puts "Too many backoff attempts. Sorry it didn't work out."
|
52
|
-
exit 2
|
53
|
-
end
|
54
|
-
|
55
|
-
naptime = rand(60) * @backoff_counter
|
56
|
-
puts "Backing off for #{naptime} seconds..."
|
57
|
-
sleep naptime
|
58
|
-
end
|
59
|
-
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 += 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
|