or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
golangpkg:golang/cloud.google.com/go/kms@v1.24.0

docs

autokey-client.mdcore-types.mdekm-client.mdenums.mdindex.mdinventory-clients.mditerators.mdkey-management-client.mdoperations.mdrequest-response-types.md
tile.json

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

tessl install tessl/golang-cloud-google-com--go--kms@1.24.0

Go Client Library for Google Cloud Key Management Service (KMS) API for managing cryptographic keys and performing cryptographic operations

operations.mddocs/

Long-Running Operations

Overview

Some KMS operations are long-running and return operation handles that can be polled or waited on for completion. Currently, the main long-running operation in KMS is CreateKeyHandle for Autokey provisioning.

Package: cloud.google.com/go/kms/apiv1

CreateKeyHandleOperation

type CreateKeyHandleOperation struct {
    // Has unexported fields
}

Manages a long-running operation from AutokeyClient.CreateKeyHandle(). This operation tracks the asynchronous provisioning of a CryptoKey by Autokey.

Methods

Done

func (op *CreateKeyHandleOperation) Done() bool

Reports whether the long-running operation has completed.

Returns: true if the operation is complete (successfully or with error), false if still in progress.

Example:

op, err := client.CreateKeyHandle(ctx, req)
if err != nil {
    // Handle error
}

if op.Done() {
    // Operation completed immediately (rare)
    keyHandle, err := op.Wait(ctx)
}

Name

func (op *CreateKeyHandleOperation) Name() string

Returns the name of the long-running operation. The name is assigned by the server and is unique within the service from which the operation is created.

Returns: Operation name in the format "projects/{project}/locations/{location}/operations/{operation}"

Example:

op, err := client.CreateKeyHandle(ctx, req)
if err != nil {
    // Handle error
}

opName := op.Name()
fmt.Printf("Operation: %s\n", opName)

// Store opName for later retrieval

Metadata

func (op *CreateKeyHandleOperation) Metadata() (*kmspb.CreateKeyHandleMetadata, error)

Returns metadata associated with the long-running operation. Metadata itself does not contact the server, but Poll() does. To get the latest metadata, call this method after a successful call to Poll(). If the metadata is not available, the returned metadata and error are both nil.

Returns: *kmspb.CreateKeyHandleMetadata (currently an empty message), or nil if not available.

Example:

op, err := client.CreateKeyHandle(ctx, req)
if err != nil {
    // Handle error
}

// Poll to update metadata
_, _ = op.Poll(ctx)

// Get metadata
metadata, err := op.Metadata()
if err != nil {
    // Handle error
}
// Note: CreateKeyHandleMetadata is currently empty

Poll

func (op *CreateKeyHandleOperation) Poll(ctx context.Context, opts ...gax.CallOption) (*kmspb.KeyHandle, error)

Fetches the latest state of the long-running operation. Poll also fetches the latest metadata, which can be retrieved by Metadata().

Behavior:

  • If Poll fails, the error is returned and op is unmodified
  • If Poll succeeds and the operation has completed with failure, the error is returned and op.Done() will return true
  • If Poll succeeds and the operation has completed successfully, op.Done() will return true, and the response of the operation is returned
  • If Poll succeeds and the operation has not completed, the returned response and error are both nil

Example:

op, err := client.CreateKeyHandle(ctx, req)
if err != nil {
    // Handle error
}

// Poll periodically
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()

for range ticker.C {
    keyHandle, err := op.Poll(ctx)
    if err != nil {
        // Operation failed
        log.Fatalf("Operation failed: %v", err)
    }
    if keyHandle != nil {
        // Operation completed successfully
        fmt.Printf("KeyHandle created: %s\n", keyHandle.Name)
        fmt.Printf("Provisioned CryptoKey: %s\n", keyHandle.KmsKey)
        break
    }
    // Still in progress, continue polling
    fmt.Println("Operation still in progress...")
}

Wait

func (op *CreateKeyHandleOperation) Wait(ctx context.Context, opts ...gax.CallOption) (*kmspb.KeyHandle, error)

Blocks until the long-running operation is completed, returning the response and any errors encountered. See documentation of Poll() for error-handling information.

Best Practice: Use Wait() for simple cases where you want to block until completion. Use Poll() when you need custom polling intervals or want to perform other work while waiting.

Example:

op, err := client.CreateKeyHandle(ctx, req)
if err != nil {
    // Handle error creating operation
    log.Fatalf("Failed to create KeyHandle: %v", err)
}

// Wait for completion (blocks)
keyHandle, err := op.Wait(ctx)
if err != nil {
    // Operation failed
    log.Fatalf("KeyHandle creation failed: %v", err)
}

// Operation succeeded
fmt.Printf("KeyHandle created: %s\n", keyHandle.Name)
fmt.Printf("Provisioned CryptoKey: %s\n", keyHandle.KmsKey)

Resuming Operations

You can resume monitoring an operation from a different process or after a restart using the operation name.

CreateKeyHandleOperation (from name)

func (c *AutokeyClient) CreateKeyHandleOperation(name string) *CreateKeyHandleOperation

Returns a new CreateKeyHandleOperation from a given name. The name must be that of a previously created CreateKeyHandleOperation, possibly from a different process.

Example:

// Process 1: Start operation and save name
op1, err := client.CreateKeyHandle(ctx, req)
if err != nil {
    // Handle error
}
opName := op1.Name()
// Save opName to database/file

// Process 2: Resume operation
opName := loadOperationName() // Load from database/file
op2 := client.CreateKeyHandleOperation(opName)

// Continue monitoring
keyHandle, err := op2.Wait(ctx)
if err != nil {
    // Handle error
}
fmt.Printf("KeyHandle created: %s\n", keyHandle.Name)

Usage Patterns

Simple Wait

The simplest pattern - just wait for completion:

op, err := client.CreateKeyHandle(ctx, req)
if err != nil {
    return err
}

keyHandle, err := op.Wait(ctx)
if err != nil {
    return err
}

fmt.Printf("Created KeyHandle: %s\n", keyHandle.Name)
return nil

Wait with Timeout

Use context timeout to limit wait time:

op, err := client.CreateKeyHandle(ctx, req)
if err != nil {
    return err
}

// Wait with 5-minute timeout
waitCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()

keyHandle, err := op.Wait(waitCtx)
if err != nil {
    if waitCtx.Err() == context.DeadlineExceeded {
        return fmt.Errorf("operation timed out after 5 minutes")
    }
    return err
}

fmt.Printf("Created KeyHandle: %s\n", keyHandle.Name)
return nil

Custom Polling with Progress Updates

Poll manually for custom intervals or progress reporting:

op, err := client.CreateKeyHandle(ctx, req)
if err != nil {
    return err
}

fmt.Println("KeyHandle creation started...")
fmt.Printf("Operation: %s\n", op.Name())

// Poll every 10 seconds
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()

attempts := 0
maxAttempts := 30 // 5 minutes total

for range ticker.C {
    attempts++
    
    keyHandle, err := op.Poll(ctx)
    if err != nil {
        return fmt.Errorf("operation failed: %w", err)
    }
    
    if keyHandle != nil {
        // Success
        fmt.Println("Operation completed successfully!")
        fmt.Printf("KeyHandle: %s\n", keyHandle.Name)
        fmt.Printf("Provisioned CryptoKey: %s\n", keyHandle.KmsKey)
        return nil
    }
    
    // Still in progress
    fmt.Printf("Still waiting... (attempt %d/%d)\n", attempts, maxAttempts)
    
    if attempts >= maxAttempts {
        return fmt.Errorf("operation timed out after %d attempts", maxAttempts)
    }
}

return nil

Async with Goroutine

Start operation and continue with other work:

op, err := client.CreateKeyHandle(ctx, req)
if err != nil {
    return err
}

// Start waiting in background
resultChan := make(chan *kmspb.KeyHandle)
errChan := make(chan error)

go func() {
    keyHandle, err := op.Wait(ctx)
    if err != nil {
        errChan <- err
        return
    }
    resultChan <- keyHandle
}()

// Do other work here
fmt.Println("Doing other work while operation completes...")

// Wait for result when needed
select {
case keyHandle := <-resultChan:
    fmt.Printf("KeyHandle created: %s\n", keyHandle.Name)
    return nil
case err := <-errChan:
    return fmt.Errorf("operation failed: %w", err)
case <-time.After(5 * time.Minute):
    return fmt.Errorf("operation timed out")
}

Persistent Monitoring

Save operation name for monitoring across process restarts:

import "encoding/json"

type OperationState struct {
    OperationName string    `json:"operation_name"`
    StartTime     time.Time `json:"start_time"`
}

// Start operation and persist state
func startKeyHandleCreation(client *kms.AutokeyClient, req *kmspb.CreateKeyHandleRequest) error {
    ctx := context.Background()
    
    op, err := client.CreateKeyHandle(ctx, req)
    if err != nil {
        return err
    }
    
    // Save operation state
    state := OperationState{
        OperationName: op.Name(),
        StartTime:     time.Now(),
    }
    
    data, err := json.Marshal(state)
    if err != nil {
        return err
    }
    
    return os.WriteFile("operation-state.json", data, 0644)
}

// Resume monitoring operation
func resumeKeyHandleCreation(client *kms.AutokeyClient) (*kmspb.KeyHandle, error) {
    ctx := context.Background()
    
    // Load operation state
    data, err := os.ReadFile("operation-state.json")
    if err != nil {
        return nil, err
    }
    
    var state OperationState
    if err := json.Unmarshal(data, &state); err != nil {
        return nil, err
    }
    
    // Resume operation
    op := client.CreateKeyHandleOperation(state.OperationName)
    
    // Wait for completion
    keyHandle, err := op.Wait(ctx)
    if err != nil {
        return nil, err
    }
    
    // Clean up state file
    os.Remove("operation-state.json")
    
    return keyHandle, nil
}

Error Handling

import (
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

op, err := client.CreateKeyHandle(ctx, req)
if err != nil {
    // Error creating operation
    return fmt.Errorf("failed to create KeyHandle operation: %w", err)
}

keyHandle, err := op.Wait(ctx)
if err != nil {
    // Operation failed - check error type
    st, ok := status.FromError(err)
    if ok {
        switch st.Code() {
        case codes.DeadlineExceeded:
            return fmt.Errorf("operation timed out: %v", st.Message())
        case codes.PermissionDenied:
            return fmt.Errorf("insufficient permissions: %v", st.Message())
        case codes.AlreadyExists:
            return fmt.Errorf("KeyHandle already exists: %v", st.Message())
        default:
            return fmt.Errorf("operation failed: %v", st.Message())
        }
    }
    return err
}

// Success
return nil

Best Practices

  1. Use Wait() for simplicity - Unless you need custom polling intervals
  2. Set context timeouts - Always limit how long you'll wait
  3. Save operation names - For resuming after failures or restarts
  4. Handle all error cases - Check for permission, timeout, and existence errors
  5. Monitor in background - Use goroutines if you have other work to do
  6. Check Done() before Poll() - Avoid unnecessary network calls if already complete

Comparison with Direct API Calls

AspectOperation.Wait()Operation.Poll()GetOperation RPC
BlockingYesNoNo
Automatic retryYesNoNo
Custom intervalsNoYesYes
Network callsMultiple (automatic)One per callOne per call
Best forSimple casesCustom pollingAdvanced use cases

See Also

  • AutokeyClient
  • Core Types
  • google.golang.org/api/gax