From 0000fe8c3e2202908d0902536466052238ede387 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Thu, 19 Mar 2015 11:06:28 -0600 Subject: [PATCH 1/4] SDK credentials lookup Look for the default application credentials file. This file is created by the gcloud application. --- lib/gcloud/credentials.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/gcloud/credentials.rb b/lib/gcloud/credentials.rb index d53447a2cfb7..5ee9e36ed5dc 100644 --- a/lib/gcloud/credentials.rb +++ b/lib/gcloud/credentials.rb @@ -36,6 +36,7 @@ class Credentials #:nodoc: :scope, :issuer, :signing_key def initialize keyfile, options = {} + keyfile ||= sdk_default_creds if keyfile.nil? fail "You must provide a keyfile to connect with." elsif !::File.exist?(keyfile) @@ -51,6 +52,18 @@ def initialize keyfile, options = {} init_signet_client! options end + ## + # The filepath of the default application credentials used by + # the gcloud SDK. + # + # This file is created when running gcloud auth login + def self.sdk_default_creds #:nodoc: + # This method will likely be moved once we gain better + # support for running in a GCE environment. + sdk_creds = "~/.config/gcloud/application_default_credentials.json" + File.expand_path sdk_creds + end + protected ## From fd5aa69c51efee57d9a7a5267e5108f069a194f0 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Tue, 24 Mar 2015 10:22:21 -0600 Subject: [PATCH 2/4] Prefer File.file? File.file? ensures that the path is a file, while File.exist? will return true for a directory. --- lib/gcloud/credentials.rb | 2 +- lib/gcloud/storage/bucket.rb | 2 +- test/gcloud/datastore/test_credentials.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gcloud/credentials.rb b/lib/gcloud/credentials.rb index 5ee9e36ed5dc..c55530bf187c 100644 --- a/lib/gcloud/credentials.rb +++ b/lib/gcloud/credentials.rb @@ -39,7 +39,7 @@ def initialize keyfile, options = {} keyfile ||= sdk_default_creds if keyfile.nil? fail "You must provide a keyfile to connect with." - elsif !::File.exist?(keyfile) + elsif !::File.file?(keyfile) fail "The keyfile '#{keyfile}' is not a valid file." end diff --git a/lib/gcloud/storage/bucket.rb b/lib/gcloud/storage/bucket.rb index 13c689d0b5be..de992a2f6211 100644 --- a/lib/gcloud/storage/bucket.rb +++ b/lib/gcloud/storage/bucket.rb @@ -169,7 +169,7 @@ def create_file file, path = nil, options = {} ensure_connection! # TODO: Raise if file doesn't exist # ensure_file_exists! - fail unless ::File.exist? file + fail unless ::File.file? file options[:acl] = File::Acl.predefined_rule_for options[:acl] diff --git a/test/gcloud/datastore/test_credentials.rb b/test/gcloud/datastore/test_credentials.rb index 6eb9d0efa05d..f5ee8c298797 100644 --- a/test/gcloud/datastore/test_credentials.rb +++ b/test/gcloud/datastore/test_credentials.rb @@ -26,7 +26,7 @@ client_mock = Minitest::Mock.new client_mock.expect :fetch_access_token!, true Signet::OAuth2::Client.stub :new, client_mock do - File.stub :exist?, true do + File.stub :file?, true do File.stub :read, keyfile_json do credz = Gcloud::Datastore::Credentials.new "fake.json" end From 6ff9c6c288fecde02ce68331fceae0288e66e4cf Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Tue, 24 Mar 2015 10:47:20 -0600 Subject: [PATCH 3/4] Add Credentials.default The logic to determine default credenitals is about to become more complex. This new method will be where the heavy lifting will take place. --- lib/gcloud/credentials.rb | 14 +++++++++++++- lib/gcloud/datastore.rb | 8 ++++++-- lib/gcloud/datastore/credentials.rb | 1 + lib/gcloud/storage.rb | 8 ++++++-- lib/gcloud/storage/credentials.rb | 1 + 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/lib/gcloud/credentials.rb b/lib/gcloud/credentials.rb index c55530bf187c..22d6e0dfeecf 100644 --- a/lib/gcloud/credentials.rb +++ b/lib/gcloud/credentials.rb @@ -25,6 +25,7 @@ class Credentials #:nodoc: TOKEN_CREDENTIAL_URI = "https://accounts.google.com/o/oauth2/token" AUDIENCE = "https://accounts.google.com/o/oauth2/token" SCOPE = [] + ENV_VARS = ["GOOGLE_CLOUD_KEYFILE"] attr_accessor :client @@ -36,7 +37,6 @@ class Credentials #:nodoc: :scope, :issuer, :signing_key def initialize keyfile, options = {} - keyfile ||= sdk_default_creds if keyfile.nil? fail "You must provide a keyfile to connect with." elsif !::File.file?(keyfile) @@ -52,6 +52,18 @@ def initialize keyfile, options = {} init_signet_client! options end + ## + # Returns the default credentials. + # + def self.default + self::ENV_VARS.each do |env_var| + keyfile = ENV[env_var].to_s + return new keyfile if ::File.file? keyfile + end + return new sdk_default_creds if ::File.file? sdk_default_creds + nil + end + ## # The filepath of the default application credentials used by # the gcloud SDK. diff --git a/lib/gcloud/datastore.rb b/lib/gcloud/datastore.rb index 51037dee54b6..9a0ffe0d9f0f 100644 --- a/lib/gcloud/datastore.rb +++ b/lib/gcloud/datastore.rb @@ -41,8 +41,12 @@ module Gcloud # # See Gcloud::Datastore::Dataset def self.datastore project = ENV["DATASTORE_PROJECT"], - keyfile = ENV["DATASTORE_KEYFILE"] - credentials = Gcloud::Datastore::Credentials.new keyfile + keyfile = nil + if keyfile.nil? + credentials = Gcloud::Datastore::Credentials.default + else + credentials = Gcloud::Datastore::Credentials.new keyfile + end Gcloud::Datastore::Dataset.new project, credentials end diff --git a/lib/gcloud/datastore/credentials.rb b/lib/gcloud/datastore/credentials.rb index 3b8d11929e44..168496ab5c02 100644 --- a/lib/gcloud/datastore/credentials.rb +++ b/lib/gcloud/datastore/credentials.rb @@ -25,6 +25,7 @@ module Datastore class Credentials < Gcloud::Credentials #:nodoc: SCOPE = ["https://www.googleapis.com/auth/datastore", "https://www.googleapis.com/auth/userinfo.email"] + ENV_VARS = ["DATASTORE_KEYFILE"] ## # Sign Oauth2 API calls. diff --git a/lib/gcloud/storage.rb b/lib/gcloud/storage.rb index 4dd8e6436226..b74007a4c18a 100644 --- a/lib/gcloud/storage.rb +++ b/lib/gcloud/storage.rb @@ -34,8 +34,12 @@ module Gcloud # # See Gcloud::Storage::Project def self.storage project = ENV["STORAGE_PROJECT"], - keyfile = ENV["STORAGE_KEYFILE"] - credentials = Gcloud::Storage::Credentials.new keyfile + keyfile = nil + if keyfile.nil? + credentials = Gcloud::Storage::Credentials.default + else + credentials = Gcloud::Storage::Credentials.new keyfile + end Gcloud::Storage::Project.new project, credentials end diff --git a/lib/gcloud/storage/credentials.rb b/lib/gcloud/storage/credentials.rb index f21f8ce693e9..f34d85aa0779 100644 --- a/lib/gcloud/storage/credentials.rb +++ b/lib/gcloud/storage/credentials.rb @@ -20,6 +20,7 @@ module Storage # Represents the Oauth2 signing logic for Storage. class Credentials < Gcloud::Credentials #:nodoc: SCOPE = ["https://www.googleapis.com/auth/devstorage.full_control"] + ENV_VARS = ["STORAGE_KEYFILE"] end end end From 390aca925d89aa919946e6ef6c2958e0cf96c938 Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Tue, 24 Mar 2015 11:54:07 -0600 Subject: [PATCH 4/4] Support Google Cloud Engine credentials The googleauth library is now used by google_api_client, and it supports resolving the application default settings from ENV and from GCE. --- lib/gcloud/credentials.rb | 60 ++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/lib/gcloud/credentials.rb b/lib/gcloud/credentials.rb index 22d6e0dfeecf..18bf9bf4d491 100644 --- a/lib/gcloud/credentials.rb +++ b/lib/gcloud/credentials.rb @@ -15,6 +15,7 @@ require "json" require "signet/oauth_2/client" require "forwardable" +require "googleauth" module Gcloud ## @@ -37,19 +38,12 @@ class Credentials #:nodoc: :scope, :issuer, :signing_key def initialize keyfile, options = {} - if keyfile.nil? - fail "You must provide a keyfile to connect with." - elsif !::File.file?(keyfile) - fail "The keyfile '#{keyfile}' is not a valid file." + if keyfile.is_a? Signet::OAuth2::Client + @client = keyfile + else + @client = init_client keyfile, options end - - # Turn keys to strings - options = stringify_hash_keys options - # Constructor options override default options - options = default_options.merge options - # Keyfile options override everything - options = options.merge JSON.parse(::File.read(keyfile)) - init_signet_client! options + @client.fetch_access_token! end ## @@ -61,7 +55,8 @@ def self.default return new keyfile if ::File.file? keyfile end return new sdk_default_creds if ::File.file? sdk_default_creds - nil + client = Google::Auth.get_application_default self::SCOPE + new client end ## @@ -78,6 +73,24 @@ def self.sdk_default_creds #:nodoc: protected + ## + # Initializes the Signet client. + def init_client keyfile, options + verify_keyfile! keyfile + client_opts = client_options keyfile, options + Signet::OAuth2::Client.new client_opts + end + + ## + # Initializes the Signet client. + def verify_keyfile! keyfile + if keyfile.nil? + fail "You must provide a keyfile to connect with." + elsif !::File.file?(keyfile) + fail "The keyfile '#{keyfile}' is not a valid file." + end + end + ## # returns a new Hash with string keys instead of symbol keys. def stringify_hash_keys hash @@ -92,19 +105,20 @@ def default_options "scope" => self.class::SCOPE } end - ## - # Initializes the Signet client. - def init_signet_client! options - client_opts = { - token_credential_uri: options["token_credential_uri"], + def client_options keyfile, options + # Turn keys to strings + options = stringify_hash_keys options + # Constructor options override default options + options = default_options.merge options + # Keyfile options override everything + options = options.merge JSON.parse(::File.read(keyfile)) + + # client options for initializing signet client + { token_credential_uri: options["token_credential_uri"], audience: options["audience"], scope: options["scope"], issuer: options["client_email"], - signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) - } - - @client = Signet::OAuth2::Client.new client_opts - @client.fetch_access_token! + signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) } end end end