or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
golangpkg:golang/cloud.google.com/go/storage@1.59.x

docs

access-control.mdadvanced-features.mdclient-and-buckets.mdcontrol-api.mdexperimental-features.mdindex.mdobject-operations.mdperformance-features.md
tile.json

tessl/golang-cloud-google-com-go-storage

tessl install tessl/golang-cloud-google-com-go-storage@1.59.0

Google Cloud Storage client library for Go providing comprehensive APIs for bucket and object operations, access control, and advanced features

access-control.mddocs/

Access Control

This document covers access control mechanisms including Access Control Lists (ACLs), IAM policies, signed URLs for time-limited access, and POST policies for browser-based uploads.

Capabilities

Access Control Lists (ACLs)

Manage fine-grained access control for buckets and objects.

/**
 * ACLHandle provides operations on an access control list.
 * Use BucketHandle.ACL() for bucket ACLs.
 * Use BucketHandle.DefaultObjectACL() for default object ACLs.
 * Use ObjectHandle.ACL() for object ACLs.
 */
type ACLHandle struct {
    // contains filtered or unexported fields
}

/**
 * Lists all ACL rules.
 * @param ctx - Context for the operation
 * @returns Slice of ACLRule and error
 */
func (a *ACLHandle) List(ctx context.Context) ([]ACLRule, error)

/**
 * Sets the role for an entity.
 * @param ctx - Context for the operation
 * @param entity - ACL entity (user, group, domain, etc.)
 * @param role - ACL role (OWNER, READER, WRITER)
 * @returns Error if set fails
 */
func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) error

/**
 * Deletes the ACL entry for an entity.
 * @param ctx - Context for the operation
 * @param entity - ACL entity to delete
 * @returns Error if delete fails
 */
func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) error

ACL Types

Types for defining access control rules.

/**
 * ACLEntity refers to a user or group (grantee).
 * Can be in the form of:
 * - "user-<userId>" or "user-<email>"
 * - "group-<groupId>" or "group-<email>"
 * - "domain-<domain>"
 * - "project-team-<projectId>"
 * - Predefined: AllUsers, AllAuthenticatedUsers
 */
type ACLEntity string

/**
 * ACLRole is the level of access to grant.
 */
type ACLRole string

// Predefined ACL entities
const (
    // AllUsers grants access to anyone on the internet
    AllUsers ACLEntity = "allUsers"

    // AllAuthenticatedUsers grants access to any authenticated user
    AllAuthenticatedUsers ACLEntity = "allAuthenticatedUsers"
)

// ACL roles
const (
    // RoleOwner grants full control
    RoleOwner ACLRole = "OWNER"

    // RoleReader grants read access
    RoleReader ACLRole = "READER"

    // RoleWriter grants write access (buckets only)
    RoleWriter ACLRole = "WRITER"
)

/**
 * ACLRule represents a grant for a role to an entity.
 */
type ACLRule struct {
    // Entity is the grantee (user, group, domain, etc.)
    Entity ACLEntity

    // EntityID is the ID for the entity (read-only)
    EntityID string

    // Role is the access level granted
    Role ACLRole

    // Domain is the domain for domain entities (read-only)
    Domain string

    // Email is the email for user/group entities (read-only)
    Email string

    // ProjectTeam is the project team info (read-only)
    ProjectTeam *ProjectTeam
}

/**
 * ProjectTeam is the project team associated with an entity.
 */
type ProjectTeam struct {
    // ProjectNumber is the project number
    ProjectNumber string

    // Team is the team name (editors, owners, viewers)
    Team string
}

Usage Examples:

import (
    "context"
    "cloud.google.com/go/storage"
)

ctx := context.Background()
bucket := client.Bucket("my-bucket")

// List bucket ACL
rules, err := bucket.ACL().List(ctx)
if err != nil {
    log.Fatal(err)
}
for _, rule := range rules {
    fmt.Printf("Entity: %s, Role: %s\n", rule.Entity, rule.Role)
}

// Grant read access to a user
entity := storage.ACLEntity("user-alice@example.com")
err = bucket.ACL().Set(ctx, entity, storage.RoleReader)
if err != nil {
    log.Fatal(err)
}

// Grant public read access
err = bucket.ACL().Set(ctx, storage.AllUsers, storage.RoleReader)
if err != nil {
    log.Fatal(err)
}

// Revoke access
err = bucket.ACL().Delete(ctx, entity)
if err != nil {
    log.Fatal(err)
}

// Set object ACL
obj := bucket.Object("my-object.txt")
err = obj.ACL().Set(ctx, storage.AllAuthenticatedUsers, storage.RoleReader)

// Set default object ACL (applied to new objects)
err = bucket.DefaultObjectACL().Set(ctx, storage.AllUsers, storage.RoleReader)

IAM Policies

Manage Identity and Access Management policies for buckets.

/**
 * Returns an IAM handle for the bucket.
 * IAM policies provide centralized access control.
 * @returns iam.Handle for policy management
 */
func (b *BucketHandle) IAM() *iam.Handle

Note: The IAM handle comes from cloud.google.com/go/iam package. Common IAM operations:

import (
    "cloud.google.com/go/iam"
    "cloud.google.com/go/iam/apiv1/iampb"
)

// Get IAM policy
handle := bucket.IAM()
policy, err := handle.Policy(ctx)
if err != nil {
    log.Fatal(err)
}

// Add a member to a role
policy.Add("user:alice@example.com", "roles/storage.objectViewer")

// Set the updated policy
err = handle.SetPolicy(ctx, policy)
if err != nil {
    log.Fatal(err)
}

// Test permissions
permissions := []string{
    "storage.objects.get",
    "storage.objects.list",
}
allowed, err := handle.TestPermissions(ctx, permissions)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Allowed permissions: %v\n", allowed)

Signed URLs

Generate time-limited URLs for restricted resource access without authentication.

/**
 * Generates a signed URL for restricted resource access.
 * Allows unauthenticated access for a limited time.
 * @param bucket - Bucket name
 * @param object - Object name
 * @param opts - Signed URL options
 * @returns Signed URL string and error
 */
func SignedURL(bucket, object string, opts *SignedURLOptions) (string, error)

/**
 * Generates a signed URL via the bucket handle.
 * Supports automatic credential detection.
 * @param object - Object name
 * @param opts - Signed URL options
 * @returns Signed URL string and error
 */
func (b *BucketHandle) SignedURL(object string, opts *SignedURLOptions) (string, error)

/**
 * SignedURLOptions configures signed URL generation.
 */
type SignedURLOptions struct {
    // GoogleAccessID is the service account email address
    // Required (unless auto-detected)
    GoogleAccessID string

    // PrivateKey is the service account private key (PEM format)
    // Exactly one of PrivateKey or SignBytes must be provided
    PrivateKey []byte

    // SignBytes is a custom signing function
    // Exactly one of PrivateKey or SignBytes must be provided
    SignBytes func([]byte) ([]byte, error)

    // Method is the HTTP method for the signed URL
    // Valid values: GET, HEAD, PUT, POST, DELETE
    // Required
    Method string

    // Expires is the expiration time
    // Must be in the future
    // For V4, maximum 7 days from now
    // Required
    Expires time.Time

    // ContentType is the required Content-Type header
    // Optional
    ContentType string

    // Headers are additional required headers
    // Optional
    Headers []string

    // QueryParameters are additional query parameters
    // Optional
    QueryParameters url.Values

    // MD5 is the required MD5 hash (base64-encoded)
    // Optional
    MD5 string

    // Style configures the URL style
    // Use URLStyle functions: PathStyle(), VirtualHostedStyle(), BucketBoundHostname()
    // Optional
    Style *URLStyle

    // Insecure allows HTTP instead of HTTPS
    // Not recommended for production
    // Optional
    Insecure bool

    // Scheme is the signing scheme (V2 or V4)
    // V4 is recommended
    // Optional (defaults to V2 for compatibility)
    Scheme SigningScheme

    // Hostname is a custom hostname for the URL
    // Optional (defaults to storage.googleapis.com)
    Hostname string
}

/**
 * SigningScheme determines the version of signed URL.
 */
type SigningScheme int

const (
    // SigningSchemeV2 uses V2 signing (deprecated)
    SigningSchemeV2 SigningScheme = iota

    // SigningSchemeV4 uses V4 signing (recommended)
    SigningSchemeV4
)

/**
 * URLStyle configures the URL format.
 */
type URLStyle struct {
    // contains filtered or unexported fields
}

/**
 * Returns path-style URL configuration.
 * Format: https://storage.googleapis.com/bucket/object
 * @returns URLStyle for path-style URLs
 */
func PathStyle() *URLStyle

/**
 * Returns virtual-hosted-style URL configuration.
 * Format: https://bucket.storage.googleapis.com/object
 * @returns URLStyle for virtual-hosted-style URLs
 */
func VirtualHostedStyle() *URLStyle

/**
 * Returns custom hostname URL configuration.
 * Format: https://custom.example.com/object
 * @param hostname - Custom hostname
 * @returns URLStyle for custom hostname URLs
 */
func BucketBoundHostname(hostname string) *URLStyle

Usage Examples:

import (
    "time"
    "cloud.google.com/go/storage"
)

// Generate signed URL for download (GET)
opts := &storage.SignedURLOptions{
    GoogleAccessID: "service-account@project.iam.gserviceaccount.com",
    PrivateKey:     privateKeyBytes, // Load from key file
    Method:         "GET",
    Expires:        time.Now().Add(15 * time.Minute),
}
url, err := storage.SignedURL("my-bucket", "my-object.txt", opts)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Download URL: %s\n", url)

// Generate signed URL for upload (PUT)
opts = &storage.SignedURLOptions{
    GoogleAccessID: "service-account@project.iam.gserviceaccount.com",
    PrivateKey:     privateKeyBytes,
    Method:         "PUT",
    Expires:        time.Now().Add(1 * time.Hour),
    ContentType:    "image/jpeg",
}
url, err = storage.SignedURL("my-bucket", "upload.jpg", opts)

// Use V4 signing (recommended)
opts = &storage.SignedURLOptions{
    GoogleAccessID: "service-account@project.iam.gserviceaccount.com",
    PrivateKey:     privateKeyBytes,
    Method:         "GET",
    Expires:        time.Now().Add(24 * time.Hour),
    Scheme:         storage.SigningSchemeV4,
}
url, err = storage.SignedURL("my-bucket", "my-object.txt", opts)

// Use virtual-hosted-style URL
opts = &storage.SignedURLOptions{
    GoogleAccessID: "service-account@project.iam.gserviceaccount.com",
    PrivateKey:     privateKeyBytes,
    Method:         "GET",
    Expires:        time.Now().Add(1 * time.Hour),
    Style:          storage.VirtualHostedStyle(),
}
url, err = storage.SignedURL("my-bucket", "my-object.txt", opts)

// Use custom hostname
opts = &storage.SignedURLOptions{
    GoogleAccessID: "service-account@project.iam.gserviceaccount.com",
    PrivateKey:     privateKeyBytes,
    Method:         "GET",
    Expires:        time.Now().Add(1 * time.Hour),
    Style:          storage.BucketBoundHostname("cdn.example.com"),
}
url, err = storage.SignedURL("my-bucket", "my-object.txt", opts)

// Auto-detect credentials (using bucket handle)
bucket := client.Bucket("my-bucket")
opts = &storage.SignedURLOptions{
    Method:  "GET",
    Expires: time.Now().Add(15 * time.Minute),
}
url, err = bucket.SignedURL("my-object.txt", opts)
// GoogleAccessID and PrivateKey are auto-detected from client credentials

// With custom signing function (e.g., for App Engine)
opts = &storage.SignedURLOptions{
    GoogleAccessID: "service-account@project.iam.gserviceaccount.com",
    SignBytes: func(b []byte) ([]byte, error) {
        // Custom signing logic (e.g., using KMS, HSM, etc.)
        return signFunction(b)
    },
    Method:  "GET",
    Expires: time.Now().Add(1 * time.Hour),
}
url, err = storage.SignedURL("my-bucket", "my-object.txt", opts)

POST Policies

Generate POST policies for browser-based file uploads.

/**
 * Generates a PostPolicyV4 for browser-based uploads.
 * Allows unauthenticated multipart form uploads.
 * @param bucket - Bucket name
 * @param object - Object name (empty string with prefix condition for variable names)
 * @param opts - POST policy options
 * @returns PostPolicyV4 and error
 */
func GenerateSignedPostPolicyV4(bucket, object string, opts *PostPolicyV4Options) (*PostPolicyV4, error)

/**
 * Generates a PostPolicyV4 via the bucket handle.
 * Supports automatic credential detection.
 * @param object - Object name
 * @param opts - POST policy options
 * @returns PostPolicyV4 and error
 */
func (b *BucketHandle) GenerateSignedPostPolicyV4(object string, opts *PostPolicyV4Options) (*PostPolicyV4, error)

/**
 * PostPolicyV4Options configures POST policy generation.
 */
type PostPolicyV4Options struct {
    // GoogleAccessID is the service account email address
    // Required (unless auto-detected)
    GoogleAccessID string

    // PrivateKey is the service account private key (PEM format)
    // Exactly one of PrivateKey, SignBytes, or SignRawBytes must be provided
    PrivateKey []byte

    // SignBytes is a custom signing function
    SignBytes func([]byte) ([]byte, error)

    // SignRawBytes is a custom raw signing function
    SignRawBytes func([]byte) ([]byte, error)

    // Expires is the expiration time
    // Must be in the future, maximum 7 days from now
    // Required
    Expires time.Time

    // Fields are form fields to include in the policy
    // Optional
    Fields *PolicyV4Fields

    // Conditions are policy conditions
    // Optional
    Conditions []PostPolicyV4Condition

    // Hostname is a custom hostname
    // Optional (defaults to storage.googleapis.com)
    Hostname string

    // Style configures the URL style
    // Optional
    Style *URLStyle
}

/**
 * PolicyV4Fields contains form fields for the POST form.
 */
type PolicyV4Fields struct {
    // ACL is the access control list
    ACL string

    // CacheControl is the Cache-Control header
    CacheControl string

    // ContentType is the Content-Type header
    ContentType string

    // ContentDisposition is the Content-Disposition header
    ContentDisposition string

    // ContentEncoding is the Content-Encoding header
    ContentEncoding string

    // Expires is the Expires header
    Expires time.Time

    // SuccessActionRedirect is the redirect URL after successful upload
    SuccessActionRedirect string

    // SuccessActionStatus is the HTTP status code to return after successful upload
    SuccessActionStatus int

    // Metadata is custom metadata
    Metadata map[string]string
}

/**
 * PostPolicyV4Condition represents a policy condition.
 */
type PostPolicyV4Condition interface {
    // contains filtered or unexported methods
}

/**
 * Creates a condition that a field starts with a prefix.
 * @param key - Form field name
 * @param prefix - Required prefix
 * @returns PostPolicyV4Condition
 */
func ConditionStartsWith(key, prefix string) PostPolicyV4Condition

/**
 * Creates a content-length range condition.
 * @param start - Minimum length in bytes
 * @param end - Maximum length in bytes
 * @returns PostPolicyV4Condition
 */
func ConditionContentLengthRange(start, end uint64) PostPolicyV4Condition

/**
 * PostPolicyV4 contains the generated POST policy.
 */
type PostPolicyV4 struct {
    // URL is the form action URL
    URL string

    // Fields are the hidden form fields to include
    Fields map[string]string
}

Usage Examples:

import (
    "time"
    "cloud.google.com/go/storage"
)

// Generate POST policy for file upload
opts := &storage.PostPolicyV4Options{
    GoogleAccessID: "service-account@project.iam.gserviceaccount.com",
    PrivateKey:     privateKeyBytes,
    Expires:        time.Now().Add(30 * time.Minute),
}
policy, err := storage.GenerateSignedPostPolicyV4("my-bucket", "upload.jpg", opts)
if err != nil {
    log.Fatal(err)
}

// Use in HTML form
html := fmt.Sprintf(`
<form action="%s" method="post" enctype="multipart/form-data">
    <input type="hidden" name="key" value="%s">
    <input type="hidden" name="x-goog-signature" value="%s">
    <input type="hidden" name="x-goog-algorithm" value="%s">
    <input type="hidden" name="x-goog-credential" value="%s">
    <input type="hidden" name="x-goog-date" value="%s">
    <input type="hidden" name="policy" value="%s">
    <input type="file" name="file">
    <input type="submit" value="Upload">
</form>
`, policy.URL, policy.Fields["key"], policy.Fields["x-goog-signature"],
   policy.Fields["x-goog-algorithm"], policy.Fields["x-goog-credential"],
   policy.Fields["x-goog-date"], policy.Fields["policy"])

// POST policy with metadata
opts = &storage.PostPolicyV4Options{
    GoogleAccessID: "service-account@project.iam.gserviceaccount.com",
    PrivateKey:     privateKeyBytes,
    Expires:        time.Now().Add(1 * time.Hour),
    Fields: &storage.PolicyV4Fields{
        ContentType: "image/jpeg",
        Metadata: map[string]string{
            "uploaded-by": "web-form",
        },
    },
}
policy, err = storage.GenerateSignedPostPolicyV4("my-bucket", "photo.jpg", opts)

// POST policy with conditions
opts = &storage.PostPolicyV4Options{
    GoogleAccessID: "service-account@project.iam.gserviceaccount.com",
    PrivateKey:     privateKeyBytes,
    Expires:        time.Now().Add(1 * time.Hour),
    Conditions: []storage.PostPolicyV4Condition{
        storage.ConditionContentLengthRange(0, 10*1024*1024), // Max 10MB
        storage.ConditionStartsWith("$key", "uploads/"),
    },
}
policy, err = storage.GenerateSignedPostPolicyV4("my-bucket", "", opts)

// POST policy with variable object name
opts = &storage.PostPolicyV4Options{
    GoogleAccessID: "service-account@project.iam.gserviceaccount.com",
    PrivateKey:     privateKeyBytes,
    Expires:        time.Now().Add(1 * time.Hour),
    Conditions: []storage.PostPolicyV4Condition{
        storage.ConditionStartsWith("$key", "user-uploads/"),
    },
}
// Empty object name allows any name matching conditions
policy, err = storage.GenerateSignedPostPolicyV4("my-bucket", "", opts)

// Auto-detect credentials (using bucket handle)
bucket := client.Bucket("my-bucket")
opts = &storage.PostPolicyV4Options{
    Expires: time.Now().Add(30 * time.Minute),
    Fields: &storage.PolicyV4Fields{
        ContentType: "text/plain",
    },
}
policy, err = bucket.GenerateSignedPostPolicyV4("upload.txt", opts)

HMAC Keys

HMAC keys for S3-compatible authentication.

/**
 * Creates a new HMAC key for the service account.
 * @param ctx - Context for the operation
 * @param projectID - Google Cloud project ID
 * @param serviceAccountEmail - Service account email
 * @param opts - Optional HMAC key options
 * @returns HMACKey and error
 */
func (c *Client) CreateHMACKey(ctx context.Context, projectID, serviceAccountEmail string, opts ...HMACKeyOption) (*HMACKey, error)

/**
 * Returns a handle to an HMAC key.
 * @param projectID - Google Cloud project ID
 * @param accessID - HMAC key access ID
 * @returns HMACKeyHandle
 */
func (c *Client) HMACKeyHandle(projectID, accessID string) *HMACKeyHandle

/**
 * Returns an iterator over HMAC keys.
 * @param ctx - Context for the operation
 * @param projectID - Google Cloud project ID
 * @param opts - Optional filter options
 * @returns HMACKeysIterator
 */
func (c *Client) ListHMACKeys(ctx context.Context, projectID string, opts ...HMACKeyOption) *HMACKeysIterator

/**
 * HMACKey contains HMAC key information.
 */
type HMACKey struct {
    // Secret is the secret key (only returned on creation)
    Secret string

    // AccessID is the access ID (public)
    AccessID string

    // Etag is the entity tag (read-only)
    Etag string

    // ID is the HMAC key identifier
    ID string

    // ProjectID is the project ID
    ProjectID string

    // ServiceAccountEmail is the service account email
    ServiceAccountEmail string

    // CreatedTime is the creation time (read-only)
    CreatedTime time.Time

    // UpdatedTime is the last update time (read-only)
    UpdatedTime time.Time

    // State is the key state (Active, Inactive, Deleted)
    State HMACState
}

/**
 * HMACKeyHandle provides operations on an HMAC key.
 */
type HMACKeyHandle struct {
    // contains filtered or unexported fields
}

/**
 * Retrieves the HMAC key metadata.
 * @param ctx - Context for the operation
 * @param opts - Optional HMAC key options
 * @returns HMACKey and error
 */
func (h *HMACKeyHandle) Get(ctx context.Context, opts ...HMACKeyOption) (*HMACKey, error)

/**
 * Updates the HMAC key.
 * @param ctx - Context for the operation
 * @param attrs - Attributes to update
 * @param opts - Optional HMAC key options
 * @returns Updated HMACKey and error
 */
func (h *HMACKeyHandle) Update(ctx context.Context, attrs HMACKeyAttrsToUpdate, opts ...HMACKeyOption) (*HMACKey, error)

/**
 * Deletes the HMAC key.
 * Key must be inactive before deletion.
 * @param ctx - Context for the operation
 * @param opts - Optional HMAC key options
 * @returns Error if deletion fails
 */
func (h *HMACKeyHandle) Delete(ctx context.Context, opts ...HMACKeyOption) error

/**
 * HMACKeyAttrsToUpdate specifies HMAC key attributes to update.
 */
type HMACKeyAttrsToUpdate struct {
    // State is the key state to set
    // Valid values: Active, Inactive
    State HMACState

    // Etag is the entity tag for concurrency control
    Etag string
}

/**
 * HMACState represents the state of an HMAC key.
 */
type HMACState string

const (
    // Active key state
    Active HMACState = "ACTIVE"

    // Inactive key state
    Inactive HMACState = "INACTIVE"

    // Deleted key state (read-only)
    Deleted HMACState = "DELETED"
)

/**
 * HMACKeysIterator is an iterator over HMAC keys.
 */
type HMACKeysIterator struct {
    // contains filtered or unexported fields
}

/**
 * Returns the next HMAC key. Returns iterator.Done when complete.
 * @returns HMACKey and error
 */
func (it *HMACKeysIterator) Next() (*HMACKey, error)

/**
 * Returns pagination information.
 * @returns iterator.PageInfo
 */
func (it *HMACKeysIterator) PageInfo() *iterator.PageInfo

/**
 * HMACKeyOption configures HMAC key operations.
 */
type HMACKeyOption interface {
    // contains filtered or unexported methods
}

/**
 * Filters HMAC keys by service account.
 * @param email - Service account email
 * @returns HMACKeyOption
 */
func ForHMACKeyServiceAccountEmail(email string) HMACKeyOption

/**
 * Includes deleted keys in listing.
 * @returns HMACKeyOption
 */
func ShowDeletedHMACKeys() HMACKeyOption

/**
 * Bills HMAC key requests against the specified user project.
 * @param userProjectID - Project ID to bill
 * @returns HMACKeyOption
 */
func UserProjectForHMACKeys(userProjectID string) HMACKeyOption

Usage Examples:

import (
    "cloud.google.com/go/storage"
    "google.golang.org/api/iterator"
)

ctx := context.Background()

// Create HMAC key
key, err := client.CreateHMACKey(ctx, "my-project", "service-account@my-project.iam.gserviceaccount.com")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Access ID: %s\n", key.AccessID)
fmt.Printf("Secret: %s\n", key.Secret) // Only available on creation
fmt.Printf("State: %s\n", key.State)

// List HMAC keys
it := client.ListHMACKeys(ctx, "my-project")
for {
    key, err := it.Next()
    if err == iterator.Done {
        break
    }
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Key: %s, State: %s\n", key.AccessID, key.State)
}

// List keys for specific service account
it = client.ListHMACKeys(ctx, "my-project",
    storage.ForHMACKeyServiceAccountEmail("service-account@my-project.iam.gserviceaccount.com"))

// Get HMAC key
handle := client.HMACKeyHandle("my-project", "access-id")
key, err = handle.Get(ctx)
if err != nil {
    log.Fatal(err)
}

// Deactivate HMAC key
update := storage.HMACKeyAttrsToUpdate{
    State: storage.Inactive,
}
key, err = handle.Update(ctx, update)
if err != nil {
    log.Fatal(err)
}

// Delete HMAC key (must be inactive first)
err = handle.Delete(ctx)
if err != nil {
    log.Fatal(err)
}

// List including deleted keys
it = client.ListHMACKeys(ctx, "my-project", storage.ShowDeletedHMACKeys())