tessl install tessl/golang-cloud-google-com--go--kms@1.24.0Go Client Library for Google Cloud Key Management Service (KMS) API for managing cryptographic keys and performing cryptographic operations
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
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.
func (op *CreateKeyHandleOperation) Done() boolReports 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)
}func (op *CreateKeyHandleOperation) Name() stringReturns 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 retrievalfunc (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 emptyfunc (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:
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...")
}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)You can resume monitoring an operation from a different process or after a restart using the operation name.
func (c *AutokeyClient) CreateKeyHandleOperation(name string) *CreateKeyHandleOperationReturns 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)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 nilUse 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 nilPoll 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 nilStart 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")
}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
}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| Aspect | Operation.Wait() | Operation.Poll() | GetOperation RPC |
|---|---|---|---|
| Blocking | Yes | No | No |
| Automatic retry | Yes | No | No |
| Custom intervals | No | Yes | Yes |
| Network calls | Multiple (automatic) | One per call | One per call |
| Best for | Simple cases | Custom polling | Advanced use cases |