Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Project ID Detection #245

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/AppEngine/AppIdentity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

/**
* Copyright 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Google\Cloud\AppEngine;

/*
* The AppIdentityS class is automatically defined on App Engine,
* so including this dependency is not necessary, and will result in a
* PHP fatal error in the App Engine environment.
*/
use google\appengine\api\app_identity\AppIdentityService;

/**
* A wrapper for the AppEngine AppIdentity service
* This class will only work when running on AppEngine, or
* if the App Engine SDK is available.
*
* ```
* use Google\Cloud\AppEngine\AppIdentity;
*
* $appIdentity = new AppIdentity();
* $projectId = $identity->getApplicationId();
* ```
*/
class AppIdentity
{
/**
* Get the current application ID from the App Identity Service
*
* Example:
* ```
* $projectId = $appIdentity->getApplicationId();
* ```
*
* @return string
*/
public function getApplicationId()
{
if (!class_exists('google\appengine\api\app_identity\AppIdentityService')) {
throw new \Exception('This class must be run in App Engine');
}

return AppIdentityService::getApplicationId();
}
}
58 changes: 26 additions & 32 deletions src/ClientTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
*/
trait ClientTrait
{
use DetectProjectTrait;

/**
* @var string The project ID created in the Google Developers Console.
*/
Expand Down Expand Up @@ -134,9 +136,14 @@ private function getKeyFile(array $config = [])
* 1. If $config['projectId'] is set, use that.
* 2. If $config['keyFile'] is set, attempt to retrieve a project ID from
* that.
* 3. If code is running on compute engine, try to get the project ID from
* 3. If the GCLOUD_PROJECT environment variable is set, return this value
* 4. If code is running on App Engine, try to get the project ID from the
* App Identity service
* 5. If code is running on compute engine, try to get the project ID from
* the metadata store
* 4. Throw exception.
* 6. If the OS-specific gcloud configuration file is set, load it and get
* the project ID from that.
* 7. Throw exception.
*
* @param array $config
* @return string
Expand All @@ -157,40 +164,27 @@ private function detectProjectId(array $config)
return $config['keyFile']['project_id'];
}

if ($this->onGce($config['httpHandler'])) {
$metadata = $this->getMetaData();
$projectId = $metadata->getProjectId();
if ($projectId) {
return $projectId;
}
$projectId = $this->projectFromEnvVar();
if (!$projectId) {
$projectId = $this->projectFromAppEngine();
}

throw new GoogleException(
'No project ID was provided, ' .
'and we were unable to detect a default project ID.'
);
}
if (!$projectId) {
$projectId = $this->projectFromGce();
}

/**
* Abstract the GCECredentials call so we can mock it in the unit tests!
*
* @codeCoverageIgnore
* @return bool
*/
protected function onGce($httpHandler)
{
return GCECredentials::onGce($httpHandler);
}
if (!$projectId) {
$projectId = $this->projectFromGcloudConfig();
}

/**
* Abstract the Metadata instantiation for unit testing
*
* @codeCoverageIgnore
* @return Metadata
*/
protected function getMetaData()
{
return new Metadata;
if (!$projectId) {
throw new GoogleException(
'No project ID was provided, ' .
'and we were unable to detect a default project ID.'
);
}

return $projectId;
}

/**
Expand Down
182 changes: 182 additions & 0 deletions src/DetectProjectTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<?php
/*
* Copyright 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud;

use Google\Auth\Credentials\AppIdentityCredentials;
use Google\Auth\Credentials\GCECredentials;
use Google\Auth\CredentialsLoader;
use Google\Cloud\AppEngine\AppIdentity;
use Google\Cloud\Compute\Metadata;

/**
* DetectProjectTrait contains the behaviour used to locate and find the
* default project
*/
trait DetectProjectTrait
{
/**
* Return a project ID from the GCLOUD_PROJECT environment variable
* This is available in App Engine Flexible.
*
* @return string|null $projectId
*/
protected function projectFromEnvVar()
{
return getenv($this->getEnvVar());
}

/**
* Return a project ID from App Engine.
*
* @return string|null $projectId
*/
private function projectFromAppEngine()
{
if ($this->onAppEngine()) {
$appIdentity = $this->getAppIdentity();
$projectId = $appIdentity->getApplicationId();
if ($projectId) {
return $projectId;
}
}
}

/**
* Return a project ID from GCE.
*
* @return string|null $projectId
*/
private function projectFromGce($httpHandler = null)
{
if ($this->onGce($httpHandler)) {
$metadata = $this->getMetaData();
$projectId = $metadata->getProjectId();
if ($projectId) {
return $projectId;
}
}
}

/**
* The gcloud config path is OS dependent:
* - windows: %APPDATA%/gcloud/configurations/config_default
* - others: $HOME/.config/gcloud/configurations/config_default
*
* If the file does not exists, this returns null.
*
* @return string|null $projectId
*/
private function projectFromGcloudConfig()
{
$path = $this->pathToGcloudConfig();
if (!file_exists($path)) {
return;
}
$configDefault = parse_ini_file($path, true);
if (isset($configDefault['core']['project'])) {
return $configDefault['core']['project'];
}
}

/**
* Determine the path to the gcloud configuration path
*
* @codeCoverageIgnore
* @return bool
*/
protected function pathToGcloudConfig()
{
if ($this->onWindows()) {
$path = [getenv('APPDATA')];
} else {
$path = [
getenv('HOME'),
CredentialsLoader::NON_WINDOWS_WELL_KNOWN_PATH_BASE
];
}
$path[] = 'gcloud/configurations/config_default';
return implode(DIRECTORY_SEPARATOR, $path);
}

/**
* Abstract the CredentialsLoader call so we can mock it in the unit tests!
*
* @codeCoverageIgnore
* @return bool
*/
protected function onWindows()
{
return CredentialsLoader::isOnWindows();
}

/**
* Abstract the AppIdentity call so we can mock it in the unit tests!
*
* @codeCoverageIgnore
* @return bool
*/
protected function onAppEngine()
{
return AppIdentityCredentials::onAppEngine() &&
!GCECredentials::onAppEngineFlexible();
}

/**
* Abstract the AppIdentity instantiation for unit testing
*
* @codeCoverageIgnore
* @return Metadata
*/
protected function getAppIdentity()
{
return new AppIdentity;
}

/**
* Abstract the GCECredentials call so we can mock it in the unit tests!
*
* @codeCoverageIgnore
* @return bool
*/
protected function onGce($httpHandler)
{
return GCECredentials::onGce($httpHandler);
}

/**
* Abstract the Metadata instantiation for unit testing
*
* @codeCoverageIgnore
* @return Metadata
*/
protected function getMetaData()
{
return new Metadata;
}

/**
* Abstract the Environment Variable name for unit testing
*
* @codeCoverageIgnore
* @return string
*/
protected function getEnvVar()
{
return 'GCLOUD_PROJECT';
}
}
Loading