Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base: fe94b73c02
...
compare: f5c4db059f
  • 2 commits
  • 2 files changed
  • 0 commit comments
  • 2 contributors
Commits on Oct 25, 2011
Edward Sargisson Can now handle specifying snapshots by tag (or multiple tags) instead
of using the volume ids. This allows a configuration where there is 
a single instance which may be re-initialized from a new base image
with new volumes built from old snapshots. The volume id changes but
taking a snapshot with the same tags allows this script to purge the
snapshots as if they were from a single volume.
3d942a9
Commits on Oct 26, 2011
@stiang Merge pull request #1 from ejsarge/master
Can now handle specifying snapshots by tag (or multiple tags) instead of using the volume ids.
f5c4db0
Showing with 106 additions and 89 deletions.
  1. +2 −1  README.md
  2. +104 −88 ec2-purge-snapshots.rb
View
3  README.md
@@ -25,9 +25,10 @@ say, every hour, then run this script periodically to clean up.
hours -> days -> weeks -> months
- MANDATORY options:
+ MANDATORY options (one of -v or -t must be used):
-v, --volumes VOL1,VOL2,... Comma-separated list (no spaces) of volume-ids,
or 'all' for all volumes
+ -t, --tag KEY=VALUE Tag to use to filter the snapshot. May specify multiple tags.
MANDATORY rules:
-h, --hours HOURS The number of hours to keep ALL snapshots
View
192 ec2-purge-snapshots.rb
@@ -9,6 +9,76 @@
require 'optparse'
require 'rubygems'
+def purge_snapshots(ec2, options, vol, vol_snaps, volume_counts)
+ newest = vol_snaps.last
+ prev_start_date = nil
+ delete_count = 0
+ keep_count = 0
+
+ vol_snaps.each do |snap|
+ snap_date = Time.parse(snap['startTime'])
+ snap_age = ((NOW.to_i - snap_date.to_i).to_f / HOUR.to_f).to_i
+ # Hourly
+ if snap_age > options[:hours]
+ # Daily
+ if snap_age <= START_WEEKS_AFTER
+ type_str = "day"
+ start_date_str = snap_date.strftime("%Y-%m-%d")
+ start_date = Time.parse("#{start_date_str}")
+ else
+ # Weekly
+ if snap_age <= START_MONTHS_AFTER
+ type_str = "week"
+ week_day = snap_date.strftime("%w").to_i
+ start_date = Time.at(snap_date.to_i - (week_day * DAY))
+ start_date_str = start_date.strftime("%Y-%m-%d")
+ else
+ # Monthly
+ type_str = "month"
+ start_date_str = snap_date.strftime("%Y-%m")
+ start_date = Time.parse("#{start_date_str}-01")
+ end
+ end
+ if start_date_str != prev_start_date
+ # Keep
+ prev_start_date = start_date_str
+ msg = "[#{vol}] Keeping #{snap['snapshotId']}: #{snap['startTime']}, #{(snap_age.to_f / 24.to_f).to_i} "
+ msg += "days old - keeping it for the #{type_str} of #{start_date_str}"
+ puts msg unless options[:quiet]
+ keep_count += 1
+ else
+ # Never delete the newest snapshot
+ if snap['snapshotId'] == newest['snapshotId']
+ msg = "[#{vol}] Keeping #{snap['snapshotId']}: #{snap['startTime']}, #{snap_age} hours old - "
+ msg += "will never delete newest snapshot for a volume"
+ puts msg unless options[:quiet]
+ keep_count += 1
+ else
+ # Delete it
+ not_really_str = options[:noop] ? "(not really) " : ""
+ msg = "[#{vol}] - Deleting #{not_really_str}#{snap['snapshotId']}: #{snap['startTime']}, "
+ msg += "#{(snap_age.to_f / 24.to_f).to_i} days old"
+ puts msg unless options[:silent]
+ begin
+ ec2.delete_snapshot(:snapshot_id => snap['snapshotId']) unless options[:noop]
+ rescue AWS::Error => e
+ puts e
+ else
+ delete_count += 1
+ sleep [delete_count, 20].min * 0.05
+ end
+ end
+ end
+ else
+ msg = "[#{vol}] Keeping #{snap['snapshotId']}: #{snap['startTime']}, #{snap_age} hours old - "
+ msg += "less than or equal to the #{options[:hours]}-hour threshold"
+ puts msg unless options[:quiet]
+ keep_count += 1
+ end
+ end
+ volume_counts[vol] = [delete_count, keep_count]
+end
+
NOW = Time.now
HOUR = 3600
DAY = 86400
@@ -20,6 +90,7 @@
end
options = {}
+filter_tags = []
opts_parser = OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options]"
opts.separator ""
@@ -28,11 +99,15 @@
opts.separator ""
opts.separator " hours -> days -> weeks -> months"
opts.separator ""
- opts.separator "MANDATORY options:"
+ opts.separator "MANDATORY options (one of -v or -t must be used):"
opts.on("-v", "--volumes VOL1,VOL2,...", Array, "Comma-separated list (no spaces) of volume-ids,",
"or 'all' for all volumes") do |v|
options[:volumes] = v
end
+ opts.on("-t", "--tag key=value", "Tag to use to filter the snapshot. May specify multiple.") do |tag|
+ tagParts = tag.split('=')
+ filter_tags << tagParts
+ end
opts.separator ""
opts.separator "MANDATORY rules:"
opts.on("-h", "--hours HOURS", "The number of hours to keep ALL snapshots") do |hours|
@@ -92,7 +167,7 @@
require 'AWS' # sudo gem install amazon-ec2
# Check for mandatory options/rules
-if options[:volumes].nil? or options[:hours].nil? or options[:days].nil? or
+if (options[:volumes].nil? and filter_tags.empty?) or options[:hours].nil? or options[:days].nil? or
options[:weeks].nil? or options[:months].nil?
puts opts_parser.help
exit 1
@@ -117,97 +192,38 @@
# Make sure we have some snapshots to work with
unless snapshots.empty?
- if options[:volumes].size == 1 and options[:volumes][0] == "all"
- volumes = snapshots.collect {|s| s['volumeId']}.uniq
- else
- volumes = options[:volumes]
- end
-
volume_counts = {}
-
- volumes.each do |vol|
- # Find snapshots for this volume and sort them by date (oldest first)
- vol_snaps = snapshots.find_all {|s| s['volumeId'] == vol}.sort_by {|v| v['startTime']}
- puts "---- VOLUME #{vol} (#{vol_snaps.size} snapshots) ---" unless options[:quiet]
- newest = vol_snaps.last
- prev_start_date = nil
- delete_count = 0
- keep_count = 0
-
- vol_snaps.each do |snap|
- snap_date = Time.parse(snap['startTime'])
- snap_age = ((NOW.to_i - snap_date.to_i).to_f / HOUR.to_f).to_i
- # Hourly
- if snap_age > options[:hours]
- # Daily
- if snap_age <= START_WEEKS_AFTER
- type_str = "day"
- start_date_str = snap_date.strftime("%Y-%m-%d")
- start_date = Time.parse("#{start_date_str}")
- else
- # Weekly
- if snap_age <= START_MONTHS_AFTER
- type_str = "week"
- week_day = snap_date.strftime("%w").to_i
- start_date = Time.at(snap_date.to_i - (week_day * DAY))
- start_date_str = start_date.strftime("%Y-%m-%d")
- else
- # Monthly
- type_str = "month"
- start_date_str = snap_date.strftime("%Y-%m")
- start_date = Time.parse("#{start_date_str}-01")
- end
- end
- if start_date_str != prev_start_date
- # Keep
- prev_start_date = start_date_str
- msg = "[#{vol}] Keeping #{snap['snapshotId']}: #{snap['startTime']}, #{(snap_age.to_f / 24.to_f).to_i} "
- msg += "days old - keeping it for the #{type_str} of #{start_date_str}"
- puts msg unless options[:quiet]
- keep_count += 1
- else
- # Never delete the newest snapshot
- if snap['snapshotId'] == newest['snapshotId']
- msg = "[#{vol}] Keeping #{snap['snapshotId']}: #{snap['startTime']}, #{snap_age} hours old - "
- msg += "will never delete newest snapshot for a volume"
- puts msg unless options[:quiet]
- keep_count += 1
- else
- # Delete it
- not_really_str = options[:noop] ? "(not really) " : ""
- msg = "[#{vol}] - Deleting #{not_really_str}#{snap['snapshotId']}: #{snap['startTime']}, "
- msg += "#{(snap_age.to_f / 24.to_f).to_i} days old"
- puts msg unless options[:silent]
- begin
- ec2.delete_snapshot(:snapshot_id => snap['snapshotId']) unless options[:noop]
- rescue AWS::Error => e
- puts e
- else
- delete_count += 1
- sleep [delete_count, 20].min * 0.05
- end
- end
- end
- else
- msg = "[#{vol}] Keeping #{snap['snapshotId']}: #{snap['startTime']}, #{snap_age} hours old - "
- msg += "less than or equal to the #{options[:hours]}-hour threshold"
- puts msg unless options[:quiet]
- keep_count += 1
- end
+ if filter_tags.empty?
+ if options[:volumes].size == 1 and options[:volumes][0] == "all"
+ volumes = snapshots.collect {|s| s['volumeId']}.uniq
+ else
+ volumes = options[:volumes]
end
- volume_counts[vol] = [delete_count, keep_count]
+
+ volumes.each do |vol|
+ # Find snapshots for this volume and sort them by date (oldest first)
+ vol_snaps = snapshots.find_all {|s| s['volumeId'] == vol}.sort_by {|v| v['startTime']}
+ puts "---- VOLUME #{vol} (#{vol_snaps.size} snapshots) ---" unless options[:quiet]
+
+ purge_snapshots ec2, options, vol, vol_snaps, volume_counts
+
+ end
+ else
+ vol_snaps = snapshots_set.snapshotSet.item.find_all {|s| s['status'] == "completed" && !s['tagSet'].nil? && filter_tags.all? {|f| s['tagSet'].item.detect {|t| t['key']==f[0] && t['value']==f[1]}} }.sort_by {|v| v['startTime']}
+ tag_id = filter_tags.collect{|f| "#{f[0]}=#{f[1]}"}.join(", ")
+ purge_snapshots ec2, options, tag_id, vol_snaps, {}
end
if not options[:xsilent] and not options[:no_summary]
- puts ""
- puts "SUMMARY:"
- puts ""
- volume_counts.each do |vol, counts|
- puts "#{vol}:"
- puts " deleted: #{counts[0]}"
- puts " kept: #{counts[1]}"
puts ""
+ puts "SUMMARY:"
+ puts ""
+ volume_counts.each do |vol, counts|
+ puts "#{vol}:"
+ puts " deleted: #{counts[0]}"
+ puts " kept: #{counts[1]}"
+ puts ""
+ end
end
- end
else
puts "No snapshots found, exiting."
exit 2
@@ -218,4 +234,4 @@
puts "#{$0} --help for more info"
puts ""
exit 2
-end
+end

No commit comments for this range

Something went wrong with that request. Please try again.