Workspaces
Storage connections
client.storage_connections manages the storage connections a new workspace can keep its files in, instead of Nace's managed storage. See Use your own storage for the concept, and the API reference for every field.
- A connection names one destination: an S3 bucket and region, or an Azure storage account and container, with an optional base prefix. Each workspace gets its own folder beneath it. The destination can't change.
- Who can do what: any member of the account lists and reads connections and binds a ready one to a new workspace. Only a connection's creator, or an owner or admin of the account, changes it; anyone else raises
NacePermissionDeniedError(access_denied). - Cost: managing connections costs nothing and works at any balance.
- Availability: a deployment that doesn't offer customer storage raises
NaceNotFoundError(storage_connection_unavailable) on every call.
A connection's status is one of:
| State | What you can do |
|---|---|
draft | Set it up, save credentials and validate it. It can't hold a workspace yet. |
ready | Give it to a new workspace. Validated: Nace can write, read and delete in the destination. |
degraded | A later check failed. Fix the access in your cloud and validate it again. |
revoked | Nace stopped using it, and workspaces in it stop reading and writing. Validate one you revoked with reactivate=True to restore it. |
A connection is set up in five steps:
createmakes adraft.setupreturns what to create in your cloud: for S3, an IAM role trust policy carrying the connection'sexternal_id, its permissions policy, a CloudFormation template and a quick-create link; for Azure, theazcommands that give a service principal access to the container. Save your AWS account id and role name, or your Azure subscription and resource group, withset_setupfirst, and the setup comes back filled in.- Create the role or service principal in your cloud.
set_credentialssaves the role's ARN, or the service principal and its secret.validatestarts astorage_validationjob, andwait_for_validationwaits for it. Once it succeeds, the connection isready.
A new IAM role or Azure role assignment can take a few minutes to apply, so a fresh one may fail its first validation: wait and validate again. Then pass the connection's id as storage_connection_id to client.workspaces.create. Only a connection whose is_ready is True can be bound: status is ready, and credentials_pending, validation_running and reactivation_requires_approval are all False.
| Method | Parameters | Returns | Description |
|---|---|---|---|
create(*, name, destination, idempotency_key=None) | name: str (1 to 200 characters), destination: S3Destination | AzureBlobDestination | dict, idempotency_key: str (up to 200 characters) | StorageConnection | Creates a draft connection. It carries an Idempotency-Key, yours or one minted per call, so a retry returns the same connection. |
list(**options) | limit: int (1 to 200), cursor: str | StorageConnectionPage | One page of the account's connections. |
iter() | Iterator[StorageConnection] | Every connection across every page, 200 a page. | |
get(connection_id) | connection_id: str | StorageConnection | One connection and its state. |
setup(connection_id) | connection_id: str | StorageConnectionSetup | What to create in your cloud, generated from the saved options. |
set_setup(connection_id, options) | connection_id: str, options: S3SetupOptions | AzureSetupOptions | dict | StorageConnectionSetup | Saves the non-secret inputs the setup is generated from, and returns the setup. Their type must match the destination's. |
set_credentials(connection_id, credential) | connection_id: str, credential: AwsRoleCredential | AzureServicePrincipalCredential | dict | StorageConnection | Saves the role or service principal Nace uses. They are never returned. The connection has credentials_pending until a validation passes. |
validate(connection_id, *, reactivate=None) | connection_id: str, reactivate: bool | DocumentJob | Starts a storage_validation job that checks Nace can write, read and delete in the destination. reactivate=True restores a connection you revoked once it passes. |
validation(connection_id) | connection_id: str | DocumentJob | The latest validation job. A failed job's error says what Nace could not do. |
wait_for_validation(connection_id, *, timeout=180.0, poll_interval=2.0) | connection_id: str, timeout: float, poll_interval: float | DocumentJob | Polls validation every poll_interval seconds until the job is succeeded, failed or cancelled, for up to timeout seconds, and returns it. A failed job is returned, not raised. |
revoke(connection_id) | connection_id: str | StorageConnection | Stops Nace using the connection. Workspaces bound to it stop reading and writing until a validation with reactivate=True passes. Their files stay in your bucket. |
delete(connection_id) | connection_id: str | None | Deletes a connection no workspace uses. Nothing in the bucket is touched. |
- The Azure secret is a
SecretStronAzureServicePrincipalCredential: its repr,str()andmodel_dump_json()show**********, a validation error never quotes it, and SDK debug logs maskclient_secretin request bodies. Only the request to Drex carries it. Adictcredential works too, with the secret as astror aSecretStr. - Retries:
list,iter,get,setup,set_setup,set_credentials,validationandrevokeare retried, and so iscreate, which carries an idempotency key.validateanddeletearen't, unless you passretry: a retry after the first call landed starts a second validation, or raisesNaceNotFoundErrorfor the deleted connection.
| When | It raises |
|---|---|
A connection_id your account doesn't have, or customer storage is off on the deployment | NaceNotFoundError (storage_connection_unavailable) |
| A change by someone who isn't the connection's creator, or an owner or admin | NacePermissionDeniedError (access_denied) |
set_credentials with credentials the cloud refuses | NaceUnprocessableEntityError (storage_credentials_refused). The message never quotes them. |
delete on a connection a workspace uses, or validate on one Nace revoked | NaceConflictError |
wait_for_validation still running after timeout seconds | NaceJobTimeoutError; the validation keeps running |
A limit outside 1 to 200, or a connection_id holding anything but letters, digits, - and _ | NaceError, before any request |
List connections
from nace_sdk import NaceClient
with NaceClient() as client:
page = client.storage_connections.list(limit=20)
for connection in page.items:
print(connection.id, connection.name, connection.status)
for connection in client.storage_connections.iter():
print(connection.id, connection.destination.type, connection.is_ready)Connect an S3 bucket
from nace_sdk import AwsRoleCredential, S3Destination, S3SetupOptions
connection = client.storage_connections.create(
name="Audit evidence bucket",
destination=S3Destination(bucket="acme-audit-evidence", region="us-east-1", prefix="nace"),
)
setup = client.storage_connections.set_setup(
connection.id, S3SetupOptions(aws_account_id="444455556666", role_name="nace-workspaces")
)
print(setup.launch_url) # opens the CloudFormation stack that creates the role
# Once the stack has created the role:
client.storage_connections.set_credentials(connection.id, AwsRoleCredential(role_arn=setup.role_arn))
client.storage_connections.validate(connection.id)
job = client.storage_connections.wait_for_validation(connection.id)
print(job.status, job.error_message)Connect an Azure Blob container
import os
from nace_sdk import AzureBlobDestination, AzureServicePrincipalCredential, AzureSetupOptions
connection = client.storage_connections.create(
name="Audit evidence container",
destination=AzureBlobDestination(account="acmeaudit", container="evidence"),
)
setup = client.storage_connections.set_setup(
connection.id, AzureSetupOptions(subscription_id="<subscription id>", resource_group="audit-rg")
)
print(setup.azure_cli) # run these to give your service principal access
client.storage_connections.set_credentials(
connection.id,
AzureServicePrincipalCredential(
tenant_id="<tenant id>", client_id="<client id>", client_secret=os.environ["AZURE_CLIENT_SECRET"]
),
)
client.storage_connections.validate(connection.id)
print(client.storage_connections.wait_for_validation(connection.id).status)Create a workspace in your storage
Pass a ready connection's id as storage_connection_id. See Create a workspace.
connection = client.storage_connections.get(connection.id)
if connection.is_ready:
workspace = client.workspaces.create(name="fy25-audit", storage_connection_id=connection.id)
print(workspace.storage.location)