#!/usr/bin/ruby

require 'ruby-debug'
require 'right_cloud_api'
require 'cloud/azure/storage/manager'
require File.expand_path("../../lib/azure_helper",__FILE__)
require 'uri'
require 'yaml'

include AzureHelper

class CopyState
  attr_accessor :account
  attr_accessor :api
  attr_accessor :state
  def initialize(api, account, state = "pending")
    @api = api
    @account = account
    @state = state
  end
end

url_src = ARGV.shift
url_dst = ARGV.shift
image_name = ARGV.shift
unless url_src =~ /^http/ and url_dst =~ /^http|^(linux|rswin|mswin|windows)$/
puts <<-EOF
USAGE: azure_migrate URL_TO_SRC_BLOB URL_TO_DEST_BLOB
USAGE: azure_migrate URL_TO_SRC_BLOB <linux|rswin|mswin> <image name>
       if URL_TO_DEST_BLOB is 'windows' or 'linux', program will copy
       image to all linux or windows image accounts. 'rswin' for rightscale 
       accounts (for testing), and mswin is microsoft (official publish)
       also acceptable parameters
       For the first usage you might use this for migrating to non-standard 
       accounts, such as test or services
       Second usage is preferred for a publishing type event
EXAMPLE: azure_migrate https://linuxros.blob.core.windows.net/vm-images/RightImage-CentOS-6.2-x64-v5.8.8.1.vhd https://rightscalewestus.blob.core.windows.net/vm-images/RightImage-CentOS-6.2-x64-v5.8.8.1.vhd
EXAMPLE: azure_migrate https://linuxros.blob.core.windows.net/vm-images/RightImage-CentOS-6.2-x64-v5.8.8.1.vhd linux RightImage-CentOS-6.2-x64-v5.8.8.1.vhd 
EXAMPLE: azure_migrate http://ds1p6hm133skyrl.blob.core.windows.net/vhds/1vqtgdr5.xwr20120719072616.vhd rswin RightImage-Windows-2008R2-SP1-x64-sqlsvr2012-v5.8.8.vhd
EOF
  exit 1
end

# This function takes an url to a blob and grants read access to it
# by signing a request for read access with the account's shared key
# The request is made using GET parameters described in bits hash
def generate_shared_access_key(url)
  (account,password,endpoint,container,blob) = parse_url(url)

  url_new = url.dup
  t = Time.now.utc
#  t1 = Time.now.utc + (60*60*24)
#  fmt = '%Y-%m-%d'
  t1 = Time.now.utc + (3600*8)
  fmt = '%Y-%m-%dT%H:%MZ'
  sig_fields = ["sp","st","se","crsrc","si", "sv"]
  get_param_fields  = ["sp","st","se","sr", "sv", "sig"]
  bits = {
    'sp' => 'r', # grant holder of this url read access
    'sr' => 'b', # for a blob
    'crsrc' => "/#{account}/#{container}/#{blob}", # denoted by this resource
    'st' => t.strftime(fmt), # starting now
    'se' => t1.strftime(fmt), # and ending in 8 hours
    'sv' => '2012-02-12' #api version, required
  }
  header = sig_fields.map {|b| bits[b] || ""}.join("\x0A")
  header = header.toutf8 if header.respond_to?(:toutf8)
  # Now sign the request with the shared key
  bits["sig"] = Base64.encode64(HMAC::SHA256.new(Base64.decode64(password)).update(URI.decode(header)).digest).chomp
  get_params = get_param_fields.map do |p|
    p+"="+URI.escape(bits[p], Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
  end
  url_new << "?" << get_params.join("&")
#  puts "HEADER: ---------------"
  puts header
  puts "-----------------------"
  puts "URL: #{url}"
  puts "SIGNATURE: #{bits["sig"]}"
  puts "URL_SIGNED: #{url_new}"
  return url_new
end

if url_dst == "linux"
  account_names = RS_LINUX_ACCOUNTS
  container = "rightimage-linux"
  blob = image_name
elsif url_dst == "windows"
  account_names = RS_LINUX_ACCOUNTS + RS_WINDOWS_ACCOUNTS
  container = "rightimage-windows"
  blob = image_name
elsif url_dst == "rswin"
  account_names = RS_LINUX_ACCOUNTS
  container = "rightimage-windows"
  blob = image_name
elsif url_dst == "mswin"
  account_names = RS_WINDOWS_ACCOUNTS
  container = "rightimage-windows"
  blob = image_name
else
  (account,password,endpoint,container,blob) = parse_url(url_dst)
  account_names = [account]
end

if url_dst =~ /^linux|^rswin|^mswin/
  raise "Destination vhd name must be supplied when using mass copy" unless image_name and image_name =~ /./
  raise "Image name must end in .vhd" unless image_name and image_name =~ /.vhd/
end

transfers = account_names.map do |account|
  password = lookup_password(account)
  endpoint = "https://#{account}.blob.core.windows.net"
  CopyState.new(
    RightScale::CloudApi::Azure::Storage::Manager.new(account, password, endpoint, :api_version=>'2012-02-12'),
    account
  )
end


url_src_signed = generate_shared_access_key(url_src)
puts "COPYING #{url_src}"
transfers.each do |t|
  puts "TO #{t.account}/#{container}/#{blob}"
  begin
    t.api.GetBlobProperties('Container' => container, 'Blob' => blob)
  rescue
  end
  if t.api.response.code.to_s =~ /^2/
    puts "Blob already exists for #{t.account}, polling status"
  else
    puts "Running CopyBlob command for #{t.account} then polling status"
    api_dump(t.api, "CopyBlob","Container"=>container,"Blob"=>blob,"Source"=>url_src_signed)
    if t.api.response.code.to_s !~ /^2/
      raise "ERROR account #{t.account} response code for CopyBlob not in 2xx range"
    end
  end
end

240.times do
  transfers.each_with_index do |t,i|
    t.api.GetBlobProperties('Container' => container, 'Blob' => blob)
    status = t.api.response.headers["x-ms-copy-status"].first
    if t.api.response.code.to_s !~ /^2/
      raise "ERROR account #{t.account} container #{container} blob #{blob} response code for GetBlobProperties not in 2xx range"
    end
    progress = t.api.response.headers["x-ms-copy-progress"].first
    if progress =~ /[0-9]+\/[0-9]+/
      (a,b) = progress.split("/")
      progress = sprintf("%3s%" % [(a.to_i*100/b.to_i).to_s])
    end
    if status =~ /success/
      progress = "DONE"
    elsif status =~ /fail/
      progress = "FAIL"
    end
    case status
    when /fail/ then t.state = :failure
    when /success/ then t.state = :success
    when /pending/ then t.state = :pending
    else t.state = :unknown
    end
    print "#{t.account.sub("rightscale","")}[#{progress}] "
    print "\n" if (i % RS_LINUX_ACCOUNTS.size == (RS_LINUX_ACCOUNTS.size - 1))
  end
#  print "\n"
  break unless transfers.find {|t| t.state == :pending }
  sleep 30
end
if transfers.all? { |t| t.state == :success }
  puts "ALL successful!"
  exit 0
else
  puts "ERROR occurred, or we timed out, maybe run this command again"
  exit 1
end
