rds-rotate-db-snapshots 0.5.2 → 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 +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
|