diff --git a/secretmanager/README.md b/secretmanager/README.md index 278ff9da1..e802df65a 100644 --- a/secretmanager/README.md +++ b/secretmanager/README.md @@ -21,6 +21,7 @@ This simple command-line application demonstrates how to invoke - GOOGLE_CLOUD_PUBSUB_TOPIC - Full name of topic (projects/{project}/topics/{topic}). - GOOGLE_CLOUD_KMS_KEY - Full name of global KMS key (projects/{project}/locations/global/keyRings/{keyring}/cryptoKeys/{key}). - GOOGLE_CLOUD_REGIONAL_KMS_KEY - Full name of regional KMS key (projects/{project}/locations/{location}/keyRings/{keyring}/cryptoKeys/{key}). + - CLOUD_SQL_INSTANCE / CLOUD_SQL_USER - Bare Cloud SQL instance ID and database username, required by the Cloud SQL managed-rotation tests. 1. **Download The Credentials** - Click "Go to credentials" after enabling the APIs. Click "New Credentials" and select "Service Account Key". Create a new diff --git a/secretmanager/composer.json b/secretmanager/composer.json index 2a20b5355..38f064bb6 100644 --- a/secretmanager/composer.json +++ b/secretmanager/composer.json @@ -1,6 +1,6 @@ { "require": { - "google/cloud-secret-manager": "^2.1.0", + "google/cloud-secret-manager": "^2.4.0", "google/cloud-resource-manager": "^1.0" } } diff --git a/secretmanager/src/create_regional_secret_with_cloud_sql_credentials.php b/secretmanager/src/create_regional_secret_with_cloud_sql_credentials.php new file mode 100644 index 000000000..9076afc29 --- /dev/null +++ b/secretmanager/src/create_regional_secret_with_cloud_sql_credentials.php @@ -0,0 +1,79 @@ + "secretmanager.$locationId.rep.googleapis.com"]; + + // Create the Secret Manager client. + $client = new SecretManagerServiceClient($options); + + // Build the resource name of the parent project. + $parent = $client->locationName($projectId, $locationId); + + $secret = new Secret([ + 'secret_type' => SecretType::CLOUD_SQL_DB_CREDENTIALS, + ]); + + $request = CreateSecretRequest::build($parent, $secretId, $secret); + + // Create the secret. + $newSecret = $client->createSecret($request); + + printf('Created secret: %s%s', $newSecret->getName(), PHP_EOL); + + // This built-in identity is what you grant Cloud SQL IAM permissions to, + // so that Secret Manager can rotate the database password on its behalf. + printf( + 'Grant this identity Cloud SQL IAM permissions to enable rotation: %s%s', + $newSecret->getPolicyMember()->getIamPolicyUidPrincipal(), + PHP_EOL + ); +} +// [END secretmanager_create_regional_secret_with_cloud_sql_credentials] + +// The following 2 lines are only needed to execute the samples on the CLI +require_once __DIR__ . '/../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/secretmanager/src/create_secret_with_type.php b/secretmanager/src/create_secret_with_type.php new file mode 100644 index 000000000..8253ee847 --- /dev/null +++ b/secretmanager/src/create_secret_with_type.php @@ -0,0 +1,78 @@ +projectName($projectId); + + $secret = new Secret([ + 'replication' => new Replication([ + 'automatic' => new Automatic(), + ]), + 'secret_type' => SecretType::value($secretType), + ]); + + // Build the request. + $request = CreateSecretRequest::build($parent, $secretId, $secret); + + // Create the secret, with the given secret type restriction. + $newSecret = $client->createSecret($request); + + // Print the new secret name. + printf('Created secret with secret type: %s%s', $newSecret->getName(), PHP_EOL); +} +// [END secretmanager_create_secret_with_type] + +// The following 2 lines are only needed to execute the samples on the CLI +require_once __DIR__ . '/../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/secretmanager/src/enable_regional_secret_managed_rotation.php b/secretmanager/src/enable_regional_secret_managed_rotation.php new file mode 100644 index 000000000..00491616d --- /dev/null +++ b/secretmanager/src/enable_regional_secret_managed_rotation.php @@ -0,0 +1,86 @@ + "secretmanager.$locationId.rep.googleapis.com"]; + + // Create the Secret Manager client. + $client = new SecretManagerServiceClient($options); + + // Build the resource name of the secret. + $parent = $client->projectLocationSecretName($projectId, $locationId, $secretId); + + $credentials = new CloudSQLSingleUserCredentials([ + 'instance_id' => $instanceId, + 'username' => $username, + // Leaving password unset lets Secret Manager generate a secure + // password itself. + ]); + + $request = (new EnableManagedRotationRequest()) + ->setParent($parent) + ->setCloudSqlSingleUserCredentials($credentials); + + // Enable managed rotation. + $version = $client->enableManagedRotation($request); + + printf('Enabled managed rotation, created secret version: %s%s', $version->getName(), PHP_EOL); +} +// [END secretmanager_enable_regional_secret_managed_rotation] + +// The following 2 lines are only needed to execute the samples on the CLI +require_once __DIR__ . '/../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/secretmanager/src/get_regional_secret_type.php b/secretmanager/src/get_regional_secret_type.php new file mode 100644 index 000000000..8f30a5a31 --- /dev/null +++ b/secretmanager/src/get_regional_secret_type.php @@ -0,0 +1,71 @@ + "secretmanager.$locationId.rep.googleapis.com"]; + + // Create the Secret Manager client. + $client = new SecretManagerServiceClient($options); + + // Build the resource name of the secret. + $name = $client->projectLocationSecretName($projectId, $locationId, $secretId); + + // Build the request. + $request = GetSecretRequest::build($name); + + // Get the secret. + $secret = $client->getSecret($request); + + printf( + 'Found regional secret %s with secret type %s%s', + $secret->getName(), + SecretType::name($secret->getSecretType()), + PHP_EOL + ); +} +// [END secretmanager_get_regional_secret_type] + +// The following 2 lines are only needed to execute the samples on the CLI +require_once __DIR__ . '/../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/secretmanager/src/get_secret_type.php b/secretmanager/src/get_secret_type.php new file mode 100644 index 000000000..ba5d00b9e --- /dev/null +++ b/secretmanager/src/get_secret_type.php @@ -0,0 +1,67 @@ +secretName($projectId, $secretId); + + // Build the request. + $request = GetSecretRequest::build($name); + + // Get the secret. + $secret = $client->getSecret($request); + + printf( + 'Found secret %s with secret type %s%s', + $secret->getName(), + SecretType::name($secret->getSecretType()), + PHP_EOL + ); +} +// [END secretmanager_get_secret_type] + +// The following 2 lines are only needed to execute the samples on the CLI +require_once __DIR__ . '/../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/secretmanager/src/rotate_regional_secret.php b/secretmanager/src/rotate_regional_secret.php new file mode 100644 index 000000000..9f251a9cc --- /dev/null +++ b/secretmanager/src/rotate_regional_secret.php @@ -0,0 +1,68 @@ + "secretmanager.$locationId.rep.googleapis.com"]; + + // Create the Secret Manager client. + $client = new SecretManagerServiceClient($options); + + // Build the resource name of the secret. Note that although the field is + // named "parent", its value is the full secret resource name, not a + // collection parent. + $parent = $client->projectLocationSecretName($projectId, $locationId, $secretId); + + $request = (new RotateSecretRequest()) + ->setParent($parent); + + // Rotate the secret. + $version = $client->rotateSecret($request); + + printf('Rotated secret, created secret version: %s%s', $version->getName(), PHP_EOL); +} +// [END secretmanager_rotate_regional_secret] + +// The following 2 lines are only needed to execute the samples on the CLI +require_once __DIR__ . '/../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/secretmanager/src/update_regional_secret_with_managed_rotation_schedule.php b/secretmanager/src/update_regional_secret_with_managed_rotation_schedule.php new file mode 100644 index 000000000..4f3485829 --- /dev/null +++ b/secretmanager/src/update_regional_secret_with_managed_rotation_schedule.php @@ -0,0 +1,100 @@ + "secretmanager.$locationId.rep.googleapis.com"]; + + // Create the Secret Manager client. + $client = new SecretManagerServiceClient($options); + + // Build the resource name of the secret. + $name = $client->projectLocationSecretName($projectId, $locationId, $secretId); + + // next_rotation_time and rotation_period must be set together. + $nextRotationTimeSeconds = time() + $rotationPeriodSeconds; + + $rotation = new Rotation([ + 'next_rotation_time' => new Timestamp(['seconds' => $nextRotationTimeSeconds]), + 'rotation_period' => new Duration(['seconds' => $rotationPeriodSeconds]), + ]); + + $secret = new Secret([ + 'name' => $name, + 'rotation' => $rotation, + ]); + + // Mask only the two subfields being set here, not the whole "rotation" + // submessage -- that would also include managed_rotation_status, which + // is output-only and rejects a whole-submessage replace with "immutable + // and cannot be updated" (confirmed empirically against a live + // project). + $fieldMask = new FieldMask(); + $fieldMask->setPaths(['rotation.next_rotation_time', 'rotation.rotation_period']); + + $request = (new UpdateSecretRequest()) + ->setSecret($secret) + ->setUpdateMask($fieldMask); + + // Update the secret. + $updatedSecret = $client->updateSecret($request); + + printf('Updated regional secret rotation schedule: %s%s', $updatedSecret->getName(), PHP_EOL); +} +// [END secretmanager_update_regional_secret_with_managed_rotation_schedule] + +// The following 2 lines are only needed to execute the samples on the CLI +require_once __DIR__ . '/../../testing/sample_helpers.php'; +\Google\Cloud\Samples\execute_sample(__FILE__, __NAMESPACE__, $argv); diff --git a/secretmanager/test/regionalsecretmanagerTest.php b/secretmanager/test/regionalsecretmanagerTest.php index b778c84ea..01071a600 100644 --- a/secretmanager/test/regionalsecretmanagerTest.php +++ b/secretmanager/test/regionalsecretmanagerTest.php @@ -20,8 +20,12 @@ namespace Google\Cloud\Samples\SecretManager; use Google\ApiCore\ApiException as GaxApiException; +use Google\Cloud\Iam\V1\Binding; +use Google\Cloud\Iam\V1\GetIamPolicyRequest; +use Google\Cloud\Iam\V1\SetIamPolicyRequest; use Google\Cloud\ResourceManager\V3\DeleteTagKeyRequest; use Google\Cloud\ResourceManager\V3\DeleteTagValueRequest; +use Google\Cloud\ResourceManager\V3\Client\ProjectsClient; use Google\Cloud\ResourceManager\V3\Client\TagKeysClient; use Google\Cloud\ResourceManager\V3\CreateTagKeyRequest; use Google\Cloud\ResourceManager\V3\TagKey; @@ -35,6 +39,7 @@ use Google\Cloud\SecretManager\V1\DisableSecretVersionRequest; use Google\Cloud\SecretManager\V1\GetSecretRequest; use Google\Cloud\SecretManager\V1\Secret; +use Google\Cloud\SecretManager\V1\Secret\SecretType; use Google\Cloud\SecretManager\V1\SecretPayload; use Google\Cloud\SecretManager\V1\SecretVersion; use Google\Cloud\TestUtils\TestTrait; @@ -44,9 +49,17 @@ class regionalsecretmanagerTest extends TestCase { use TestTrait; + // Role granted to a Cloud SQL DB credentials secret's built-in identity + // so that managed rotation can update the Cloud SQL user's password. + // This grant is per-secret (the member is the secret's own generated + // principal), so it has to be made fresh for every secret the Cloud SQL + // managed-rotation tests create. + private const CLOUD_SQL_ROLE = 'roles/cloudsql.admin'; + private static $client; private static $tagKeyClient; private static $tagValuesClient; + private static $projectsClient; private static $testSecret; private static $testSecretToDelete; @@ -68,6 +81,7 @@ class regionalsecretmanagerTest extends TestCase private static $testSecretWithCMEKToCreateName; private static $testSecretWithTopicToCreateName; private static $testSecretWithRotationToCreateName; + private static $testSecretCloudSqlToCreateName; private static $iamUser = 'user:kapishsingh@google.com'; private static $locationId = 'us-central1'; @@ -85,12 +99,17 @@ class regionalsecretmanagerTest extends TestCase private static $skipRotationTests = false; private static $testRotationTopic; + private static $skipCloudSqlTests = false; + private static $cloudSqlInstanceId; + private static $cloudSqlUsername; + public static function setUpBeforeClass(): void { $options = ['apiEndpoint' => 'secretmanager.' . self::$locationId . '.rep.googleapis.com' ]; self::$client = new SecretManagerServiceClient($options); self::$tagKeyClient = new TagKeysClient(); self::$tagValuesClient = new TagValuesClient(); + self::$projectsClient = new ProjectsClient(); self::$testSecret = self::createSecret(); self::$testSecretToDelete = self::createSecret(); @@ -105,6 +124,7 @@ public static function setUpBeforeClass(): void self::$testSecretWithCMEKToCreateName = self::$client->projectLocationSecretName(self::$projectId, self::$locationId, self::randomSecretId()); self::$testSecretWithTopicToCreateName = self::$client->projectLocationSecretName(self::$projectId, self::$locationId, self::randomSecretId()); self::$testSecretWithRotationToCreateName = self::$client->projectLocationSecretName(self::$projectId, self::$locationId, self::randomSecretId()); + self::$testSecretCloudSqlToCreateName = self::$client->projectLocationSecretName(self::$projectId, self::$locationId, self::randomSecretId()); self::$testSecretVersion = self::addSecretVersion(self::$testSecretWithVersions); self::$testSecretVersionToDestroy = self::addSecretVersion(self::$testSecretWithVersions); @@ -127,6 +147,22 @@ public static function setUpBeforeClass(): void } else { self::$testRotationTopic = $envTopic; } + + // CLOUD_SQL_INSTANCE is the bare Cloud SQL instance ID (no project + // or region prefix). The instance must be in self::$locationId's + // region, and CLOUD_SQL_USER must already exist as a database user + // on it. Standing up a real Cloud SQL instance per test run is + // expensive, so it's supplied as a pre-provisioned fixture via env + // vars rather than created here. + $cloudSqlInstance = getenv('CLOUD_SQL_INSTANCE'); + $cloudSqlUser = getenv('CLOUD_SQL_USER'); + if ($cloudSqlInstance === false || $cloudSqlInstance === '' || $cloudSqlUser === false || $cloudSqlUser === '') { + self::$skipCloudSqlTests = true; + printf('Skipping Cloud SQL managed-rotation tests dependent on CLOUD_SQL_INSTANCE/CLOUD_SQL_USER as they are not set.%s', PHP_EOL); + } else { + self::$cloudSqlInstanceId = $cloudSqlInstance; + self::$cloudSqlUsername = $cloudSqlUser; + } } public static function tearDownAfterClass(): void @@ -147,6 +183,7 @@ public static function tearDownAfterClass(): void self::deleteSecret(self::$testSecretWithCMEKToCreateName); self::deleteSecret(self::$testSecretWithTopicToCreateName); self::deleteSecret(self::$testSecretWithRotationToCreateName); + self::deleteSecret(self::$testSecretCloudSqlToCreateName); sleep(15); // Added a sleep to wait for the tag unbinding self::deleteTagValue(); self::deleteTagKey(); @@ -206,6 +243,110 @@ private static function getSecret(string $projectId, string $locationId, string return self::$client->getSecret($getSecretRequest); } + private static function createCloudSqlCredentialsSecret(): Secret + { + $parent = self::$client->locationName(self::$projectId, self::$locationId); + $secretId = self::randomSecretId(); + $secret = new Secret(['secret_type' => SecretType::CLOUD_SQL_DB_CREDENTIALS]); + $createSecretRequest = CreateSecretRequest::build($parent, $secretId, $secret); + + return self::$client->createSecret($createSecretRequest); + } + + /** + * Grants self::CLOUD_SQL_ROLE to $member on the project. SetIamPolicy + * replaces the whole policy, so this reads the current policy, adds the + * member to the existing (or a new) binding for the role, and writes it + * back -- retrying the whole read-modify-write if another writer raced + * us (an ABORTED status from an etag mismatch). + */ + private static function grantCloudSqlRole(string $member): void + { + $resource = 'projects/' . self::$projectId; + + for ($attempt = 0; $attempt < 5; $attempt++) { + $policy = self::$projectsClient->getIamPolicy( + (new GetIamPolicyRequest())->setResource($resource) + ); + + $bindings = $policy->getBindings(); + $found = false; + foreach ($bindings as $binding) { + if ($binding->getRole() === self::CLOUD_SQL_ROLE) { + $members = $binding->getMembers(); + if (!in_array($member, iterator_to_array($members), true)) { + $members[] = $member; + } + $found = true; + break; + } + } + if (!$found) { + $bindings[] = new Binding([ + 'role' => self::CLOUD_SQL_ROLE, + 'members' => [$member], + ]); + } + + try { + self::$projectsClient->setIamPolicy( + (new SetIamPolicyRequest())->setResource($resource)->setPolicy($policy) + ); + // IAM grants are eventually consistent; give it a moment + // before a caller tries to use it for managed rotation. + sleep(10); + return; + } catch (GaxApiException $e) { + if ($e->getStatus() !== 'ABORTED' || $attempt === 4) { + throw $e; + } + } + } + } + + /** Removes $member from self::CLOUD_SQL_ROLE on the project, added by grantCloudSqlRole(). */ + private static function revokeCloudSqlRole(string $member): void + { + $resource = 'projects/' . self::$projectId; + + for ($attempt = 0; $attempt < 5; $attempt++) { + $policy = self::$projectsClient->getIamPolicy( + (new GetIamPolicyRequest())->setResource($resource) + ); + + $changed = false; + foreach ($policy->getBindings() as $binding) { + if ($binding->getRole() === self::CLOUD_SQL_ROLE) { + $members = $binding->getMembers(); + $remaining = array_values(array_filter( + iterator_to_array($members), + fn ($m) => $m !== $member + )); + if (count($remaining) !== count($members)) { + $binding->setMembers($remaining); + $changed = true; + } + break; + } + } + + if (!$changed) { + return; + } + + try { + self::$projectsClient->setIamPolicy( + (new SetIamPolicyRequest())->setResource($resource)->setPolicy($policy) + ); + return; + } catch (GaxApiException $e) { + if ($e->getStatus() !== 'ABORTED' || $attempt === 4) { + throw $e; + } + } + } + } + private static function createTagKey(string $short_name): string { $parent = self::$client->projectName(self::$projectId); @@ -942,4 +1083,139 @@ public function testDeleteTagFromSecret() $this->assertStringContainsString('Deleted tag binding', $output); } + + public function testCreateSecretWithCloudSqlCredentials() + { + $name = self::$client->parseName(self::$testSecretCloudSqlToCreateName); + + $output = $this->runFunctionSnippet('create_regional_secret_with_cloud_sql_credentials', [ + $name['project'], + $name['location'], + $name['secret'], + ]); + + $this->assertStringContainsString('Created secret', $output); + $this->assertStringContainsString('Grant this identity Cloud SQL IAM permissions', $output); + + $secret = self::getSecret($name['project'], $name['location'], $name['secret']); + $this->assertSame(SecretType::CLOUD_SQL_DB_CREDENTIALS, $secret->getSecretType()); + } + + public function testEnableRegionalSecretManagedRotation() + { + if (self::$skipCloudSqlTests) { + $this->markTestSkipped('CLOUD_SQL_INSTANCE/CLOUD_SQL_USER not set'); + } + + $secret = self::createCloudSqlCredentialsSecret(); + $member = $secret->getPolicyMember()->getIamPolicyUidPrincipal(); + + try { + self::grantCloudSqlRole($member); + $name = self::$client->parseName($secret->getName()); + + $output = $this->runFunctionSnippet('enable_regional_secret_managed_rotation', [ + $name['project'], + $name['location'], + $name['secret'], + self::$cloudSqlInstanceId, + self::$cloudSqlUsername, + ]); + + $this->assertStringContainsString('Enabled managed rotation', $output); + } finally { + self::revokeCloudSqlRole($member); + self::deleteSecret($secret->getName()); + } + } + + public function testRotateRegionalSecret() + { + if (self::$skipCloudSqlTests) { + $this->markTestSkipped('CLOUD_SQL_INSTANCE/CLOUD_SQL_USER not set'); + } + + $secret = self::createCloudSqlCredentialsSecret(); + $member = $secret->getPolicyMember()->getIamPolicyUidPrincipal(); + + try { + self::grantCloudSqlRole($member); + $name = self::$client->parseName($secret->getName()); + + $this->runFunctionSnippet('enable_regional_secret_managed_rotation', [ + $name['project'], + $name['location'], + $name['secret'], + self::$cloudSqlInstanceId, + self::$cloudSqlUsername, + ]); + + $output = $this->runFunctionSnippet('rotate_regional_secret', [ + $name['project'], + $name['location'], + $name['secret'], + ]); + + $this->assertStringContainsString('Rotated secret', $output); + } finally { + self::revokeCloudSqlRole($member); + self::deleteSecret($secret->getName()); + } + } + + public function testUpdateRegionalSecretWithManagedRotationSchedule() + { + if (self::$skipCloudSqlTests) { + $this->markTestSkipped('CLOUD_SQL_INSTANCE/CLOUD_SQL_USER not set'); + } + + $secret = self::createCloudSqlCredentialsSecret(); + $member = $secret->getPolicyMember()->getIamPolicyUidPrincipal(); + + try { + self::grantCloudSqlRole($member); + $name = self::$client->parseName($secret->getName()); + + $this->runFunctionSnippet('enable_regional_secret_managed_rotation', [ + $name['project'], + $name['location'], + $name['secret'], + self::$cloudSqlInstanceId, + self::$cloudSqlUsername, + ]); + + $rotationPeriodSeconds = 3600; + $output = $this->runFunctionSnippet('update_regional_secret_with_managed_rotation_schedule', [ + $name['project'], + $name['location'], + $name['secret'], + $rotationPeriodSeconds, + ]); + + $this->assertStringContainsString('Updated regional secret rotation schedule', $output); + + $updatedSecret = self::getSecret($name['project'], $name['location'], $name['secret']); + $this->assertSame($rotationPeriodSeconds, $updatedSecret->getRotation()->getRotationPeriod()->getSeconds()); + } finally { + self::revokeCloudSqlRole($member); + self::deleteSecret($secret->getName()); + } + } + + /** + * @depends testCreateSecretWithCloudSqlCredentials + */ + public function testGetRegionalSecretType() + { + $name = self::$client->parseName(self::$testSecretCloudSqlToCreateName); + + $output = $this->runFunctionSnippet('get_regional_secret_type', [ + $name['project'], + $name['location'], + $name['secret'], + ]); + + $this->assertStringContainsString('Found regional secret', $output); + $this->assertStringContainsString('CLOUD_SQL_DB_CREDENTIALS', $output); + } } diff --git a/secretmanager/test/secretmanagerTest.php b/secretmanager/test/secretmanagerTest.php index 0e02bea7d..b41decb22 100644 --- a/secretmanager/test/secretmanagerTest.php +++ b/secretmanager/test/secretmanagerTest.php @@ -37,6 +37,7 @@ use Google\Cloud\SecretManager\V1\Replication; use Google\Cloud\SecretManager\V1\Replication\Automatic; use Google\Cloud\SecretManager\V1\Secret; +use Google\Cloud\SecretManager\V1\Secret\SecretType; use Google\Cloud\SecretManager\V1\SecretPayload; use Google\Cloud\SecretManager\V1\SecretVersion; use Google\Cloud\TestUtils\TestTrait; @@ -71,6 +72,7 @@ class secretmanagerTest extends TestCase private static $testSecretWithCMEKToCreateName; private static $testSecretWithTopicToCreateName; private static $testSecretWithRotationToCreateName; + private static $testSecretWithTypeToCreateName; private static $iamUser = 'user:sethvargo@google.com'; private static $testLabelKey = 'test-label-key'; @@ -107,6 +109,7 @@ public static function setUpBeforeClass(): void self::$testSecretWithCMEKToCreateName = self::$client->secretName(self::$projectId, self::randomSecretId()); self::$testSecretWithTopicToCreateName = self::$client->secretName(self::$projectId, self::randomSecretId()); self::$testSecretWithRotationToCreateName = self::$client->secretName(self::$projectId, self::randomSecretId()); + self::$testSecretWithTypeToCreateName = self::$client->secretName(self::$projectId, self::randomSecretId()); self::$testSecretVersion = self::addSecretVersion(self::$testSecretWithVersions); self::$testSecretVersionToDestroy = self::addSecretVersion(self::$testSecretWithVersions); @@ -148,6 +151,7 @@ public static function tearDownAfterClass(): void self::deleteSecret(self::$testSecretWithCMEKToCreateName); self::deleteSecret(self::$testSecretWithTopicToCreateName); self::deleteSecret(self::$testSecretWithRotationToCreateName); + self::deleteSecret(self::$testSecretWithTypeToCreateName); sleep(15); // Added a sleep to wait for the tag unbinding self::deleteTagValue(); self::deleteTagKey(); @@ -919,4 +923,36 @@ public function testDeleteTagFromSecret() $this->assertStringContainsString('Deleted tag binding', $output); } + + public function testCreateSecretWithType() + { + $name = self::$client->parseName(self::$testSecretWithTypeToCreateName); + + $output = $this->runFunctionSnippet('create_secret_with_type', [ + $name['project'], + $name['secret'], + 'ACCESS_KEY', + ]); + + $this->assertStringContainsString('Created secret with secret type', $output); + + $secret = self::getSecret($name['project'], $name['secret']); + $this->assertSame(SecretType::ACCESS_KEY, $secret->getSecretType()); + } + + /** + * @depends testCreateSecretWithType + */ + public function testGetSecretType() + { + $name = self::$client->parseName(self::$testSecretWithTypeToCreateName); + + $output = $this->runFunctionSnippet('get_secret_type', [ + $name['project'], + $name['secret'], + ]); + + $this->assertStringContainsString('Found secret', $output); + $this->assertStringContainsString('ACCESS_KEY', $output); + } }