Skip to content

Commit

Permalink
feat: support configuring emulators from environment variable (#687)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajupazhamayil authored Feb 28, 2024
1 parent 7cd894f commit 5409e38
Show file tree
Hide file tree
Showing 52 changed files with 10,128 additions and 1 deletion.
12 changes: 12 additions & 0 deletions src/Ast/AST.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ abstract class AST
/** @var string Constant to reference `E_USER_ERROR`. */
public const E_USER_ERROR = "\0E_USER_ERROR";

/** @var string Constant to reference `parse_url`. */
public const PARSE_URL = "\0parse_url";

/** @var string Constant to reference `str_replace`. */
public const STRING_REPLACE = "\0str_replace";

/** @var string Constant to reference `empty`. */
public const EMPTY = "\0empty";

/** @var string Constant to reference `getenv`. */
public const GET_ENV = "\0getenv";

protected static function deref($obj): string
{
return $obj === static::SELF || $obj instanceof ResolvedType ? '::' : '->';
Expand Down
105 changes: 105 additions & 0 deletions src/Generation/EmulatorSupportGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php
/*
* Copyright 2024 Google LLC
*
* 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
*
* https://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.
*/
declare(strict_types=1);

namespace Google\Generator\Generation;

use Google\Generator\Ast\AST;
use Google\Generator\Ast\Access;
use Google\Generator\Ast\PhpDoc;
use Google\Generator\Utils\Type;

class EmulatorSupportGenerator
{
/**
* Map of emulator support required clients and their expected env variables.
*/
private static $emulatorSupportClients = [
'\Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient' => 'SPANNER_EMULATOR_HOST',
'\Google\Cloud\Spanner\Admin\Instance\V1\Client\InstanceAdminClient' => 'SPANNER_EMULATOR_HOST',
// Added for unittesting
'\Testing\Basic\Client\BasicClient' => 'BASIC_EMULATOR_HOST'
];

/** @var string Name of the default emulator config function. */
public const DEFAULT_EMULATOR_CONFIG_FN = 'getDefaultEmulatorConfig';

public static function generateEmulatorSupport(ServiceDetails $serviceDetails, SourceFileContext $ctx)
{
$fullClassName = $serviceDetails->gapicClientV2Type->getFullName();
$emulatorHostVar = AST::var('emulatorHost');
$phpUrlSchemeConst = AST::constant('PHP_URL_SCHEME');
$schemeVar = AST::var('scheme');
$searchVar = AST::var('search');

if (!array_key_exists($fullClassName, self::$emulatorSupportClients)) {
return null;
}

return AST::method(self::DEFAULT_EMULATOR_CONFIG_FN)
->withAccess(Access::PRIVATE)
->withBody(AST::block(
AST::assign($emulatorHostVar, AST::call(AST::GET_ENV)(self::$emulatorSupportClients[$fullClassName])),
AST::if(AST::call(AST::EMPTY)($emulatorHostVar))->then(AST::return(AST::array([]))),
AST::if(AST::assign($schemeVar, AST::call(AST::PARSE_URL)($emulatorHostVar, $phpUrlSchemeConst)))
->then(AST::block(
AST::assign($searchVar, AST::binaryOp($schemeVar, '.', '://')),
AST::assign($emulatorHostVar, AST::call(AST::STRING_REPLACE)($searchVar, '', $emulatorHostVar)),
)),
AST::return(
AST::array([
'apiEndpoint' => $emulatorHostVar,
'transportConfig' => [
'grpc' => [
'stubOpts' => [
'credentials' => AST::staticCall(
$ctx->type((Type::fromName("Grpc\ChannelCredentials"))),
AST::method('createInsecure')
)()
]
]
],
'credentials' => AST::new($ctx->type((Type::fromName("Google\ApiCore\InsecureCredentialsWrapper"))))(),
])
)
), AST::return(AST::array([])))
->withPhpDoc(PhpDoc::block(
PhpDoc::text("Configure the gapic configuration to use a service emulator.")
))
->withReturnType($ctx->type(Type::array()));
}

public static function generateEmulatorOptions(ServiceDetails $serviceDetails, AST $options)
{
$getDefaultEmulatorConfig = AST::method(self::DEFAULT_EMULATOR_CONFIG_FN);

if (!array_key_exists($serviceDetails->gapicClientV2Type->getFullName(), self::$emulatorSupportClients)) {
return null;
}

return Ast::assign($options, Ast::binaryOp($options, '+', AST::call(AST::THIS, $getDefaultEmulatorConfig)()));
}

public static function generateEmulatorPhpDoc(ServiceDetails $serviceDetails)
{
return array_key_exists($serviceDetails->gapicClientV2Type->getFullName(), self::$emulatorSupportClients) ?
PhpDoc::text(sprintf('Setting the "%s" environment variable will automatically set the API Endpoint to ' .
'the value specified in the variable, as well as ensure that empty credentials are used in ' .
'the transport layer.', self::$emulatorSupportClients[$serviceDetails->gapicClientV2Type->getFullName()])) :
null;
}
}
5 changes: 4 additions & 1 deletion src/Generation/GapicClientV2Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ private function generateClass(): PhpClass
->withMembers($this->resourceMethods())
->withMember($this->construct())
->withMember($this->magicMethod())
->withMembers($this->serviceDetails->methods->map(fn ($x) => $this->rpcMethod($x)));
->withMembers($this->serviceDetails->methods->map(fn ($x) => $this->rpcMethod($x)))
->withMember(EmulatorSupportGenerator::generateEmulatorSupport($this->serviceDetails, $this->ctx));
}

private function serviceName(): PhpClassMember
Expand Down Expand Up @@ -560,6 +561,7 @@ private function construct(): PhpClassMember
->withParams($optionsParam)
->withAccess(Access::PUBLIC)
->withBody(AST::block(
EmulatorSupportGenerator::generateEmulatorOptions($this->serviceDetails, $options),
Ast::assign($clientOptions, AST::call(AST::THIS, $buildClientOptions)($options)),
Ast::call(AST::THIS, $setClientOptions)($clientOptions),
$this->serviceDetails->hasLro || $this->serviceDetails->hasCustomOp
Expand All @@ -571,6 +573,7 @@ private function construct(): PhpClassMember
))
->withPhpDoc(PhpDoc::block(
PhpDoc::text('Constructor.'),
EmulatorSupportGenerator::generateEmulatorPhpDoc($this->serviceDetails),
PhpDoc::param($optionsParam, PhpDoc::block(
PhpDoc::text('Optional. Options for configuring the service API wrapper.'),
PhpDoc::type(
Expand Down
34 changes: 34 additions & 0 deletions tests/Integration/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ INTEGRATION_TEST_LIBRARIES = [
"logging", # Singleton resource names.
"redis", # sample_code field in gapic.yaml.
"retail", # ALpha.
"spanner", # Emulator support.
"speech", # sample_code field in gapic.yaml.
"securitycenter", # Exercises pathTemplateMap.
"talent", # Beta; path template and duplicate name template properties.
Expand Down Expand Up @@ -580,3 +581,36 @@ php_proto_library(
name = "compliance_php_proto",
deps = [":compliance_proto"],
)

# Spanner - Emulator support.
php_proto_library(
name = "spanner_php_proto",
deps = ["@com_google_googleapis//google/spanner/admin/database/v1:spanner_database_admin_proto"],
)

php_grpc_library(
name = "spanner_php_grpc",
srcs = ["@com_google_googleapis//google/spanner/admin/database/v1:spanner_database_admin_proto"],
deps = [":spanner_php_proto"],
)

php_gapic_library(
name = "spanner_php_gapic",
migration_mode = "MIGRATING",
srcs = ["@com_google_googleapis//google/spanner/admin/database/v1:database_proto_with_info"],
service_yaml = "@com_google_googleapis//google/spanner/admin/database/v1:spanner.yaml",
rest_numeric_enums = True,
deps = [
":spanner_php_grpc",
":spanner_php_proto",
],
)

php_gapic_assembly_pkg(
name = "google-spanner-admin-database-v1-php",
deps = [
":spanner_php_gapic",
":spanner_php_grpc",
":spanner_php_proto",
],
)
10 changes: 10 additions & 0 deletions tests/Integration/goldens/spanner/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package(default_visibility = ["//visibility:public"])

filegroup(
name = "goldens_files",
srcs = glob([
"*/**/*.php",
"*/**/*.json",
"*/**/*.build.txt",
]),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

/**
* @param string $parent Required. The name of the destination instance that will contain the backup copy.
* Values are of the form: `projects/<project>/instances/<instance>`. Please see
* {@see DatabaseAdminClient::instanceName()} for help formatting this field.
* @param string $backupId Required. The id of the backup copy.
* The `backup_id` appended to `parent` forms the full backup_uri of the form
* `projects/<project>/instances/<instance>/backups/<backup>`.
* @param string $sourceBackup Required. The source backup to be copied.
* The source backup needs to be in READY state for it to be copied.
* Once CopyBackup is in progress, the source backup cannot be deleted or
* cleaned up on expiration until CopyBackup is finished.
* Values are of the form:
* `projects/<project>/instances/<instance>/backups/<backup>`. Please see
* {@see DatabaseAdminClient::backupName()} for help formatting this field.
* @param \Google\Protobuf\Timestamp $expireTime Required. The expiration time of the backup in microsecond granularity.
* The expiration time must be at least 6 hours and at most 366 days
* from the `create_time` of the source backup. Once the `expire_time` has
* passed, the backup is eligible to be automatically deleted by Cloud Spanner
* to free the resources used by the backup.
*
* @return \Google\Cloud\Spanner\Admin\Database\V1\CopyBackupRequest
*
* @experimental
*/
public static function build(string $parent, string $backupId, string $sourceBackup, \Google\Protobuf\Timestamp $expireTime): self
{
return (new self())
->setParent($parent)
->setBackupId($backupId)
->setSourceBackup($sourceBackup)
->setExpireTime($expireTime);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

/**
* @param string $parent Required. The name of the instance in which the backup will be
* created. This must be the same instance that contains the database the
* backup will be created from. The backup will be stored in the
* location(s) specified in the instance configuration of this
* instance. Values are of the form
* `projects/<project>/instances/<instance>`. Please see
* {@see DatabaseAdminClient::instanceName()} for help formatting this field.
* @param \Google\Cloud\Spanner\Admin\Database\V1\Backup $backup Required. The backup to create.
* @param string $backupId Required. The id of the backup to be created. The `backup_id` appended to
* `parent` forms the full backup name of the form
* `projects/<project>/instances/<instance>/backups/<backup_id>`.
*
* @return \Google\Cloud\Spanner\Admin\Database\V1\CreateBackupRequest
*
* @experimental
*/
public static function build(string $parent, \Google\Cloud\Spanner\Admin\Database\V1\Backup $backup, string $backupId): self
{
return (new self())
->setParent($parent)
->setBackup($backup)
->setBackupId($backupId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

/**
* @param string $parent Required. The name of the instance that will serve the new database.
* Values are of the form `projects/<project>/instances/<instance>`. Please see
* {@see DatabaseAdminClient::instanceName()} for help formatting this field.
* @param string $createStatement Required. A `CREATE DATABASE` statement, which specifies the ID of the
* new database. The database ID must conform to the regular expression
* `[a-z][a-z0-9_\-]*[a-z0-9]` and be between 2 and 30 characters in length.
* If the database ID is a reserved word or if it contains a hyphen, the
* database ID must be enclosed in backticks (`` ` ``).
*
* @return \Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest
*
* @experimental
*/
public static function build(string $parent, string $createStatement): self
{
return (new self())
->setParent($parent)
->setCreateStatement($createStatement);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

/**
* @param string $name Required. Name of the backup to delete.
* Values are of the form
* `projects/<project>/instances/<instance>/backups/<backup>`. Please see
* {@see DatabaseAdminClient::backupName()} for help formatting this field.
*
* @return \Google\Cloud\Spanner\Admin\Database\V1\DeleteBackupRequest
*
* @experimental
*/
public static function build(string $name): self
{
return (new self())
->setName($name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

/**
* @param string $database Required. The database to be dropped. Please see
* {@see DatabaseAdminClient::databaseName()} for help formatting this field.
*
* @return \Google\Cloud\Spanner\Admin\Database\V1\DropDatabaseRequest
*
* @experimental
*/
public static function build(string $database): self
{
return (new self())
->setDatabase($database);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

/**
* @param string $name Required. Name of the backup.
* Values are of the form
* `projects/<project>/instances/<instance>/backups/<backup>`. Please see
* {@see DatabaseAdminClient::backupName()} for help formatting this field.
*
* @return \Google\Cloud\Spanner\Admin\Database\V1\GetBackupRequest
*
* @experimental
*/
public static function build(string $name): self
{
return (new self())
->setName($name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

/**
* @param string $database Required. The database whose schema we wish to get.
* Values are of the form
* `projects/<project>/instances/<instance>/databases/<database>`
* Please see {@see DatabaseAdminClient::databaseName()} for help formatting this field.
*
* @return \Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseDdlRequest
*
* @experimental
*/
public static function build(string $database): self
{
return (new self())
->setDatabase($database);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

/**
* @param string $name Required. The name of the requested database. Values are of the form
* `projects/<project>/instances/<instance>/databases/<database>`. Please see
* {@see DatabaseAdminClient::databaseName()} for help formatting this field.
*
* @return \Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseRequest
*
* @experimental
*/
public static function build(string $name): self
{
return (new self())
->setName($name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

/**
* @param string $parent Required. The instance of the backup operations. Values are of
* the form `projects/<project>/instances/<instance>`. Please see
* {@see DatabaseAdminClient::instanceName()} for help formatting this field.
*
* @return \Google\Cloud\Spanner\Admin\Database\V1\ListBackupOperationsRequest
*
* @experimental
*/
public static function build(string $parent): self
{
return (new self())
->setParent($parent);
}
Loading

0 comments on commit 5409e38

Please sign in to comment.