1
# Copyright 2008 Amazon.com, Inc. or its affiliates. All Rights
2
# Reserved. Licensed under the Amazon Software License (the
3
# "License"). You may not use this file except in compliance with the
4
# License. A copy of the License is located at
5
# http://aws.amazon.com/asl or in the "license" file accompanying this
6
# file. This file is distributed on an "AS IS" BASIS, WITHOUT
7
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
8
# the License for the specific language governing permissions and
9
# limitations under the License.
11
require 'ec2/amitools/crypto'
12
require 'ec2/amitools/exception'
13
require 'ec2/amitools/deletebundleparameters'
15
require 'rexml/document'
18
require 'ec2/common/http'
20
NAME = 'ec2-delete-bundle'
22
#------------------------------------------------------------------------------#
25
#{NAME} is a command line tool to delete a bundled Amazon Image from S3 storage.
26
An Amazon Image may be one of the following:
27
- Amazon Machine Image (AMI)
28
- Amazon Kernel Image (AKI)
29
- Amazon Ramdisk Image (ARI)
31
#{NAME} will delete a bundled AMI specified by either its manifest file or the
32
prefix of the bundled AMI filenames.
35
- delete the manifest and parts from the s3 bucket
36
- remove the bucket if and only if it is empty and you request its deletion
39
#------------------------------------------------------------------------------#
44
#------------------------------------------------------------------------------#
46
class DeleteFileError < RuntimeError
47
def initialize( file, reason )
48
super "Could not delete file '#{file}': #{reason}"
52
#----------------------------------------------------------------------------#
54
# Delete the specified file.
55
def delete( s3_url, bucket, file, retry_delete, user = nil, pass = nil)
56
basename = File::basename( file )
57
url = "#{s3_url}/#{bucket}/#{basename}"
61
response = EC2::Common::HTTP::delete( url, {}, user, pass )
62
break if response.success?
63
error = "HTTP DELETE returned #{response.code}"
65
raise DeleteFileError.new( path, error )
67
rescue EC2::Common::HTTP::Error => e
70
STDERR.puts "Error deleting #{file}: #{error}"
71
STDOUT.puts "Retrying in #{RETRY_WAIT_PERIOD} seconds..."
72
sleep( RETRY_WAIT_PERIOD )
76
#----------------------------------------------------------------------------#
78
# Return a list of bundle part filenames from the manifest.
79
def get_part_filenames( manifest )
81
manifest_doc = REXML::Document.new(manifest).root
82
REXML::XPath.each( manifest_doc, 'image/parts/part/filename/text()' ) do |part|
88
#------------------------------------------------------------------------------#
91
s = "#{uri.scheme}://#{uri.host}:#{uri.port}#{uri.path}"
92
# Remove the trailing '/'.
93
return ( s[s.size - 1 ] == 47 ? s.slice( 0..( s.size - 2 ) ) : s )
96
#------------------------------------------------------------------------------#
98
def get_file_list_from_s3( s3_url, p )
100
response = EC2::Common::HTTP::get( "#{s3_url}/#{p.bucket}?prefix=#{p.prefix}&max-keys=1500",
105
unless response.success?
106
raise "unable to list contents of bucket #{p.bucket}: HTTP #{response.code} response: #{response.body}"
108
REXML::XPath.each( REXML::Document.new(response.body), "//Key/text()" ) do |entry|
110
files_to_delete << entry if entry =~ /^#{p.prefix}\.part\./
111
files_to_delete << entry if entry =~ /^#{p.prefix}\.manifest$/
112
files_to_delete << entry if entry =~ /^#{p.prefix}\.manifest\.xml$/
117
#------------------------------------------------------------------------------#
120
# Get parameters and display help or manual if necessary.
124
p = DeleteBundleParameters.new( ARGV, NAME )
135
rescue RuntimeError => e
136
STDERR.puts e.message
137
STDERR.puts "Try #{NAME} --help"
145
s3_uri = URI.parse( p.url )
146
s3_url = uri2string( s3_uri )
147
retry_delete = p.retry
152
# Get list of files to delete from the AMI manifest.
154
manifest_path = p.manifest
155
File.open( manifest_path ) { |f| xml << f.read }
156
files_to_delete << File::basename(p.manifest)
157
get_part_filenames( xml ).each do |part_info|
158
files_to_delete << part_info
161
files_to_delete = get_file_list_from_s3( s3_url, p )
164
if files_to_delete.empty?
165
STDOUT.puts "No files to delete."
167
STDOUT.puts "Deleting files:"
168
files_to_delete.each { |file| STDOUT.puts( ' -' + File::join( p.bucket, file )) }
172
STDOUT.print "Continue [y/N]: "
174
Timeout::timeout(PROMPT_TIMEOUT) do
175
continue = gets.strip =~ /^y/i
177
rescue Timeout::Error
178
STDOUT.puts "\nNo response given, skipping the files."
183
files_to_delete.each do |file|
184
delete( s3_url, p.bucket, file, retry_delete, p.user, p.pass )
185
STDOUT.puts "Deleted #{File::join( p.bucket, file )}"
191
STDOUT.puts "Attempting to delete bucket #{p.bucket}..."
192
EC2::Common::HTTP::delete("#{s3_url}/#{p.bucket}", {}, p.user, p.pass)
195
rescue EC2::Common::HTTP::Error => e
196
STDERR.puts e.message
198
rescue StandardError => e
199
STDERR.puts "Error: #{e.message}."
200
STDERR.puts e.backtrace if p.debug
204
STDOUT.puts "#{NAME} complete."
206
STDOUT.puts "#{NAME} failed."
211
#------------------------------------------------------------------------------#
212
# Script entry point. Execute only if this file is being executed.
217
STDERR.puts "\n#{NAME} interrupted."