Secret Manager¶
- Client:
google-cloud-secret-manager - Transport: gRPC (the client default). drongo forces the client onto its REST transport for the mock scope, so no code change is needed.
- Backend: per-project.
Use the normal client, with no transport argument.
Why forced REST?
Secret Manager's default client speaks gRPC, which can't be intercepted over
HTTP. The client also ships a REST transport, so drongo transparently
switches the client to transport="rest" while a mock scope is active and
serves it from the in-memory backend. Your application code stays exactly as
it is in production.
Secrets and versions¶
from drongo import mock_gcp
@mock_gcp
def test_secret():
from google.cloud import secretmanager
client = secretmanager.SecretManagerServiceClient() # default, no transport arg
secret = client.create_secret(
request={
"parent": "projects/my-project",
"secret_id": "api-key",
"secret": {"replication": {"automatic": {}}},
}
)
client.add_secret_version(
request={"parent": secret.name, "payload": {"data": b"s3cr3t"}}
)
accessed = client.access_secret_version(
request={"name": f"{secret.name}/versions/latest"}
)
assert accessed.payload.data == b"s3cr3t"
Enable, disable, destroy¶
@mock_gcp
def test_version_lifecycle():
from google.cloud import secretmanager
client = secretmanager.SecretManagerServiceClient()
secret = client.create_secret(
request={
"parent": "projects/p",
"secret_id": "token",
"secret": {"replication": {"automatic": {}}},
}
)
version = client.add_secret_version(
request={"parent": secret.name, "payload": {"data": b"v1"}}
)
client.disable_secret_version(request={"name": version.name})
client.enable_secret_version(request={"name": version.name})
client.destroy_secret_version(request={"name": version.name})
Listing and deleting¶
@mock_gcp
def test_list_delete():
from google.cloud import secretmanager
client = secretmanager.SecretManagerServiceClient()
for secret_id in ("a", "b"):
client.create_secret(
request={
"parent": "projects/p",
"secret_id": secret_id,
"secret": {"replication": {"automatic": {}}},
}
)
names = [
s.name.rsplit("/", 1)[-1] for s in client.list_secrets(parent="projects/p")
]
assert sorted(names) == ["a", "b"]
client.delete_secret(request={"name": "projects/p/secrets/a"})
Per-project isolation¶
Secrets are scoped per project, so the same secret id in two projects does not collide:
from drongo import get_backend, mock_gcp
@mock_gcp
def test_isolation():
from google.cloud import secretmanager
client = secretmanager.SecretManagerServiceClient()
for project in ("alpha", "beta"):
client.create_secret(
request={
"parent": f"projects/{project}",
"secret_id": "shared-name",
"secret": {"replication": {"automatic": {}}},
}
)
assert "shared-name" in get_backend("secretmanager")["alpha"].secrets
assert "shared-name" in get_backend("secretmanager")["beta"].secrets
Coverage¶
| Operation | Status |
|---|---|
| Create / get / list / delete secret | Supported |
| Update secret (labels) | Partial |
| Add / get / list versions | Supported |
Access version (incl. latest) |
Supported |
| Enable / disable / destroy version | Supported |
| IAM policy | Planned |