Fetching Google Consolidated Bills

Google announced support for the GCP equivalent of the AWS consolidated bill this Friday. This feature substantially simplifies the complexity of managing billing across multiple projects, allowing you to consolidate the billing and usage statements from all projects into a single bucket.

Attached is a simple Ruby script to allow you to fetch your consolidated bills from a bucket:

#!/usr/bin/env ruby

require 'rubygems'
require 'google/api_client'
require 'json'

if ARGV.size < 7
  puts "gcp_get_bill.rb [project name] [certificate file] [certificate passphrase] [service account] [bucket] [object prefix] [date to fetch]"
  puts "e.g. gcp_get_bill.rb lovey-cache-373 ./my-project.p12 notasecret 972767832863-li4ihy7251800fr6v585umelvh6dcht9@developer.gserviceaccount.com my-bucket billing 2014-10-13"
  exit
end

project_name = ARGV[0]
certificate_file = ARGV[1]
certificate_passphrase = ARGV[2]
service_account = ARGV[3]
bucket = ARGV[4]
object_prefix = ARGV[5]
fetch_date = Date.parse(ARGV[6])

client = Google::APIClient.new(
  :application_name => 'Google Bill Fetcher',
  :application_version => '1.0.0'
)

storage = client.discovered_api('storage')

key = Google::APIClient::KeyUtils.load_from_pkcs12(certificate_file, certificate_passphrase)
client.authorization = Signet::OAuth2::Client.new(
  :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
  :audience => 'https://accounts.google.com/o/oauth2/token',
  :scope => 'https://www.googleapis.com/auth/devstorage.read_only',
  :issuer => service_account,
  :signing_key => key)
client.authorization.fetch_access_token!

page_token = nil
begin
  response = client.execute(:api_method => storage.objects.list, :parameters => {:project=>project_name, :zone=>'us-central1-a', :bucket=>bucket, :prefix=>object_prefix, :maxResults=>1000, :pageToken=>page_token})
  body = JSON.parse(response.body)
  page_token = body["nextPageToken"]
  response.data.items.each do |obj|
    next unless obj.name.end_with?(".csv")

    # Must parse object name to get date since object updated date can be misleading
    updated_at = Date.parse(obj.name.slice(obj.name.size-14, 14))
    next if updated_at < fetch_date

    puts "Writing #{obj.name} to disk"
    open obj.name, 'w' do |io|
      obj_response = client.execute(:api_method => storage.objects.get, :parameters => {:project=>project_name, :zone=>'us-central1-a', :bucket=>bucket, :object=>obj.name, :alt=>'media'})
      io.write obj_response.body
    end 
  end
end while !page_token.nil?

puts "...complete"