~awstools-dev/ubuntu/maverick/ec2-ami-tools/maverick

« back to all changes in this revision

Viewing changes to lib/ec2/amitools/deletebundle.rb

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2008-10-14 08:35:25 UTC
  • Revision ID: james.westby@ubuntu.com-20081014083525-c0n69wr7r7aqfb8w
Tags: 1.3-26357-0ubuntu2
* New upstream version.
* Update the debian copyright file.
* Added quilt patch system to make it easier to maintain. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
10
 
 
11
require 'ec2/amitools/crypto'
 
12
require 'ec2/amitools/exception'
 
13
require 'ec2/amitools/deletebundleparameters'
 
14
require 'net/https'
 
15
require 'rexml/document'
 
16
require 'tempfile'
 
17
require 'uri'
 
18
require 'ec2/common/http'
 
19
 
 
20
NAME = 'ec2-delete-bundle'
 
21
 
 
22
#------------------------------------------------------------------------------#
 
23
 
 
24
MANUAL=<<TEXT
 
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)
 
30
 
 
31
#{NAME} will delete a bundled AMI specified by either its manifest file or the
 
32
prefix of the bundled AMI filenames.
 
33
 
 
34
#{NAME} will:
 
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
 
37
TEXT
 
38
 
 
39
#------------------------------------------------------------------------------#
 
40
 
 
41
RETRY_WAIT_PERIOD = 5
 
42
PROMPT_TIMEOUT = 30
 
43
 
 
44
#------------------------------------------------------------------------------#
 
45
 
 
46
class DeleteFileError < RuntimeError
 
47
  def initialize( file, reason )
 
48
    super "Could not delete file '#{file}': #{reason}"
 
49
  end
 
50
end
 
51
 
 
52
#----------------------------------------------------------------------------#
 
53
 
 
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}"
 
58
  loop do
 
59
    begin
 
60
      error = ''
 
61
      response = EC2::Common::HTTP::delete( url, {}, user, pass )
 
62
      break if response.success?
 
63
      error = "HTTP DELETE returned #{response.code}"
 
64
      unless retry_delete
 
65
        raise DeleteFileError.new( path, error )
 
66
      end
 
67
    rescue EC2::Common::HTTP::Error => e
 
68
      error = e.message
 
69
    end
 
70
    STDERR.puts "Error deleting #{file}: #{error}"
 
71
    STDOUT.puts "Retrying in #{RETRY_WAIT_PERIOD} seconds..."
 
72
    sleep( RETRY_WAIT_PERIOD )
 
73
  end
 
74
end
 
75
 
 
76
#----------------------------------------------------------------------------#
 
77
 
 
78
# Return a list of bundle part filenames from the manifest.
 
79
def get_part_filenames( manifest )
 
80
  parts = []
 
81
  manifest_doc = REXML::Document.new(manifest).root
 
82
  REXML::XPath.each( manifest_doc, 'image/parts/part/filename/text()' ) do |part|
 
83
    parts << part.to_s
 
84
  end
 
85
  return parts
 
86
end
 
87
 
 
88
#------------------------------------------------------------------------------#
 
89
 
 
90
def uri2string( uri )
 
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 )
 
94
end
 
95
 
 
96
#------------------------------------------------------------------------------#
 
97
 
 
98
def get_file_list_from_s3( s3_url, p )
 
99
  files_to_delete = []
 
100
  response = EC2::Common::HTTP::get( "#{s3_url}/#{p.bucket}?prefix=#{p.prefix}&max-keys=1500",
 
101
                                     nil,
 
102
                                     {},
 
103
                                     p.user,
 
104
                                     p.pass )
 
105
  unless response.success?
 
106
    raise "unable to list contents of bucket #{p.bucket}: HTTP #{response.code} response: #{response.body}"
 
107
  end
 
108
  REXML::XPath.each( REXML::Document.new(response.body), "//Key/text()" ) do |entry|
 
109
    entry = entry.to_s
 
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$/
 
113
  end
 
114
  files_to_delete
 
115
end
 
116
 
 
117
#------------------------------------------------------------------------------#
 
118
  
 
119
#
 
120
# Get parameters and display help or manual if necessary.
 
121
#
 
122
def main
 
123
  begin
 
124
    p = DeleteBundleParameters.new( ARGV, NAME )
 
125
    
 
126
    if p.show_help 
 
127
      STDOUT.puts p.help
 
128
      return 0
 
129
    end
 
130
    
 
131
    if p.manual
 
132
      STDOUT.puts MANUAL
 
133
      return 0
 
134
    end
 
135
  rescue RuntimeError => e
 
136
    STDERR.puts e.message
 
137
    STDERR.puts "Try #{NAME} --help"
 
138
    return 1
 
139
  end
 
140
  
 
141
  status = 1
 
142
  
 
143
  begin
 
144
    # Get the S3 URL.
 
145
    s3_uri = URI.parse( p.url )
 
146
    s3_url = uri2string( s3_uri )
 
147
    retry_delete = p.retry
 
148
    
 
149
    files_to_delete = []
 
150
    
 
151
    if p.manifest
 
152
      # Get list of files to delete from the AMI manifest.
 
153
      xml = String.new
 
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
 
159
      end
 
160
    else
 
161
      files_to_delete = get_file_list_from_s3( s3_url, p )
 
162
    end
 
163
  
 
164
    if files_to_delete.empty?
 
165
      STDOUT.puts "No files to delete."
 
166
    else
 
167
      STDOUT.puts "Deleting files:"
 
168
      files_to_delete.each { |file| STDOUT.puts( '   -' + File::join( p.bucket, file )) }
 
169
      continue = p.yes
 
170
      unless continue
 
171
        begin
 
172
          STDOUT.print "Continue [y/N]: "
 
173
          STDOUT.flush
 
174
          Timeout::timeout(PROMPT_TIMEOUT) do
 
175
            continue = gets.strip =~ /^y/i
 
176
          end
 
177
        rescue Timeout::Error
 
178
          STDOUT.puts "\nNo response given, skipping the files."
 
179
          continue = false
 
180
        end
 
181
      end
 
182
      if continue
 
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 )}"
 
186
        end
 
187
      end
 
188
    end
 
189
    
 
190
    if p.clear
 
191
      STDOUT.puts "Attempting to delete bucket #{p.bucket}..."
 
192
      EC2::Common::HTTP::delete("#{s3_url}/#{p.bucket}", {}, p.user, p.pass)
 
193
    end
 
194
    status = 0
 
195
  rescue EC2::Common::HTTP::Error => e
 
196
    STDERR.puts e.message
 
197
    status = e.code
 
198
  rescue StandardError => e
 
199
    STDERR.puts "Error: #{e.message}."
 
200
    STDERR.puts e.backtrace if p.debug
 
201
  end
 
202
  
 
203
  if status == 0
 
204
    STDOUT.puts "#{NAME} complete."
 
205
  else
 
206
    STDOUT.puts "#{NAME} failed."
 
207
  end
 
208
  return status
 
209
end
 
210
 
 
211
#------------------------------------------------------------------------------#
 
212
# Script entry point. Execute only if this file is being executed.
 
213
if __FILE__ == $0
 
214
  begin
 
215
    status = main
 
216
  rescue Interrupt
 
217
    STDERR.puts "\n#{NAME} interrupted."
 
218
    status = 255
 
219
  end
 
220
  exit status
 
221
end