# A quick and dirty image deletion API_KEY = '' require 'uri' require 'net/http' require 'openssl' require 'json' def json_get(url) http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER request = Net::HTTP::Get.new(url) request['Authorization'] = "Bearer #{API_KEY}" response = http.request(request) JSON.parse(response.read_body) end def delete(url) http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER request = Net::HTTP::Delete.new(url) request['Authorization'] = "Bearer #{API_KEY}" response = http.request(request) raise "Error deleting #{url}" if response.code != '204' response end Pagination = Struct.new(:page, :next_page) do def more? next_page != nil end end def parse_pagination(request_json) pagination = request_json['meta']['pagination'] Pagination.new.tap do |pagy| pagy.page = pagination['page'] pagy.next_page = pagination['next_page'] end end Image = Struct.new(:id, :name, :description, :deleted, :type) do def snapshot? type == 'snapshot' end end def parse_server_images(request_json) [].tap do |images| request_json['servers'].each do |server| images << Image.new.tap do |image| image.id = server['image']['id'] image.name = server['image']['labels']['imagename'] image.description = server['image']['description'] image.deleted = server['image']['deleted'] image.type = server['image']['type'] end end end end def parse_images(request_json) [].tap do |images| request_json['images'].each do |image_json| images << Image.new.tap do |image| image.id = image_json['id'] image.name = image_json['labels']['imagename'] image.description = image_json['description'] image.deleted = image_json['deleted'] image.type = image_json['type'] end end end end images_in_use = [].tap do |images| page_id = 1 loop do json = json_get(URI("https://api.hetzner.cloud/v1/servers?per_page=3&page=#{page_id}&status=running")) images.concat(parse_server_images(json)) pagy = parse_pagination(json) break unless pagy.more? page_id += 1 end end.uniq(&:id).select(&:snapshot?) images_available = [].tap do |images| page_id = 1 loop do json = json_get(URI("https://api.hetzner.cloud/v1/images?per_page=3&page=#{page_id}&type=snapshot")) images.concat(parse_images(json)) pagy = parse_pagination(json) break unless pagy.more? page_id += 1 end end.select(&:snapshot?) images_to_delete = [].tap do |images| images_available.each do |available_image| next if images_in_use.any? { |i| i.id == available_image.id } images << available_image end end puts "#{images_in_use.size} image(s) in use:" images_in_use.each do |image| puts "- #{image.id}: #{image.name}" end puts '' if images_to_delete.any? puts "#{images_to_delete.size} image(s) to delete:" images_to_delete.each do |image| puts "- #{image.id}: #{image.name}" end puts '' print ".. delete #{images_to_delete.size} images? (Y/n)" answer = gets.chomp if answer == 'Y' images_to_delete.each do |image| print "Deleting #{image.name}.." delete(URI("https://api.hetzner.cloud/v1/images/#{image.id}")) print "!\n" end else puts 'Bye!' end else puts 'No unused images' puts '.. bye!' end