stash

package
v0.19.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 4, 2026 License: MIT Imports: 17 Imported by: 0

README

Stash Go Client

Go client library for the Stash key-value configuration service.

Installation

go get github.com/umputun/stash/lib/stash

Usage

Basic Usage
package main

import (
    "context"
    "log"

    "github.com/umputun/stash/lib/stash"
)

func main() {
    // create client with just the base URL
    client, err := stash.New("http://localhost:8080")
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()

    // store a value with format
    err = client.SetWithFormat(ctx, "app/config", `{"debug": true}`, stash.FormatJSON)
    if err != nil {
        log.Fatal(err)
    }

    // retrieve a value
    value, err := client.Get(ctx, "app/config")
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("value: %s", value)

    // list all keys
    keys, err := client.List(ctx, "")
    if err != nil {
        log.Fatal(err)
    }
    for _, k := range keys {
        log.Printf("key: %s, size: %d, format: %s", k.Key, k.Size, k.Format)
    }

    // delete a key
    err = client.Delete(ctx, "app/config")
    if err != nil {
        log.Fatal(err)
    }
}
With Authentication
client, err := stash.New("http://localhost:8080",
    stash.WithToken("your-api-token"),
)
With Custom Options
client, err := stash.New("http://localhost:8080",
    stash.WithToken("your-api-token"),
    stash.WithTimeout(10*time.Second),      // default: 30s
    stash.WithRetry(5, 200*time.Millisecond), // default: 3 retries, 100ms delay
)
With Custom HTTP Client
httpClient := &http.Client{
    Transport: &http.Transport{
        MaxIdleConns: 10,
    },
}

client, err := stash.New("http://localhost:8080",
    stash.WithHTTPClient(httpClient),
)
With Zero-Knowledge Encryption

Client-side encryption where the server never sees plaintext values:

client, err := stash.New("http://localhost:8080",
    stash.WithZKKey("your-secret-passphrase"), // min 16 characters
)

// values are encrypted before sending to server
err = client.Set(ctx, "app/credentials", `{"api_key": "secret123"}`)

// values are decrypted automatically when retrieved
value, err := client.Get(ctx, "app/credentials")
// value = `{"api_key": "secret123"}`

The server stores encrypted blobs with $ZK$ prefix. Without the passphrase, values cannot be decrypted. The web UI shows a lock icon for ZK-encrypted keys and disables editing.

Algorithm: AES-256-GCM with Argon2id key derivation.

API

Constructor
func New(baseURL string, opts ...Option) (*Client, error)

Creates a new Stash client. The base URL is required; all other options are optional.

Options
Option Description Default
WithToken(token) Set Bearer token for authentication none
WithTimeout(duration) HTTP request timeout 30s
WithRetry(count, delay) Retry configuration 3 retries, 100ms
WithHTTPClient(client) Custom http.Client default client
WithZKKey(passphrase) Enable client-side ZK encryption (min 16 chars) none
Methods
Get
func (c *Client) Get(ctx context.Context, key string) (string, error)

Retrieves a value by key as a string. Returns ErrNotFound if the key doesn't exist.

GetOrDefault
func (c *Client) GetOrDefault(ctx context.Context, key string, defaultValue string) (string, error)

Retrieves a value by key, returning defaultValue if the key doesn't exist. Other errors are still returned.

GetBytes
func (c *Client) GetBytes(ctx context.Context, key string) ([]byte, error)

Retrieves a value by key as raw bytes. Use this for binary data.

Set
func (c *Client) Set(ctx context.Context, key string, value string) error

Stores a value with default text format.

SetWithFormat
func (c *Client) SetWithFormat(ctx context.Context, key string, value string, format Format) error

Stores a value with explicit format. Available formats: FormatText, FormatJSON, FormatYAML, FormatXML, FormatTOML, FormatINI, FormatHCL, FormatShell.

Delete
func (c *Client) Delete(ctx context.Context, key string) error

Removes a key. Returns ErrNotFound if the key doesn't exist.

List
func (c *Client) List(ctx context.Context, prefix string) ([]KeyInfo, error)

Returns all keys, optionally filtered by prefix. Pass empty string to list all keys.

Info
func (c *Client) Info(ctx context.Context, key string) (KeyInfo, error)

Retrieves metadata for a specific key. Returns ErrNotFound if the key doesn't exist.

Ping
func (c *Client) Ping(ctx context.Context) error

Checks server connectivity.

Subscribe
func (c *Client) Subscribe(ctx context.Context, key string) (*Subscription, error)

Subscribes to changes for an exact key. Returns a Subscription that delivers events in real-time.

SubscribePrefix
func (c *Client) SubscribePrefix(ctx context.Context, prefix string) (*Subscription, error)

Subscribes to changes for all keys matching the prefix (e.g., "app" matches "app/config", "app/db").

SubscribeAll
func (c *Client) SubscribeAll(ctx context.Context) (*Subscription, error)

Subscribes to changes for all keys.

Subscription example:

sub, err := client.Subscribe(ctx, "app/config")
if err != nil {
    log.Fatal(err)
}
defer sub.Close()

for {
    select {
    case ev := <-sub.Events():
        log.Printf("Key %s changed: %s at %s", ev.Key, ev.Action, ev.Timestamp)
    case err := <-sub.Errors():
        log.Printf("Subscription error: %v", err)
    case <-ctx.Done():
        return
    }
}

Event actions: create, update, delete

Subscriptions retry indefinitely with exponential backoff (1s initial, up to 30s max). Use context cancellation or Close() to terminate.

Types
type KeyInfo struct {
    Key         string
    Size        int
    Format      string
    Secret      bool      // true if key is in a secrets path
    ZKEncrypted bool      // true if value is ZK-encrypted
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

type Subscription struct {}

func (s *Subscription) Events() <-chan Event  // channel for receiving events
func (s *Subscription) Errors() <-chan error  // channel for connection errors
func (s *Subscription) Close()                // terminate subscription

type Event struct {
    Key       string // the key that changed
    Action    string // create, update, or delete
    Timestamp string // RFC3339 timestamp
}
Errors
var (
    ErrNotFound     = errors.New("key not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrForbidden    = errors.New("forbidden")
)

// ResponseError wraps HTTP errors with status code
type ResponseError struct {
    StatusCode int
}

Use errors.Is to check for sentinel errors:

value, err := client.Get(ctx, "missing-key")
if errors.Is(err, stash.ErrNotFound) {
    // handle not found
}

License

MIT License - see LICENSE for details.

Documentation

Overview

Package stash provides a Go client for the Stash KV configuration service.

Basic usage:

client, err := stash.New("http://localhost:8080")
if err != nil {
    log.Fatal(err)
}

// store a value (default text format)
err = client.Set(ctx, "app/config", `{"debug": true}`)

// store a value with explicit format
err = client.SetWithFormat(ctx, "app/config", `{"debug": true}`, stash.FormatJSON)

// retrieve a value
value, err := client.Get(ctx, "app/config")

// retrieve with default
value, err := client.GetOrDefault(ctx, "app/config", "fallback")

// list all keys
keys, err := client.List(ctx, "")

With authentication:

client, err := stash.New("http://localhost:8080",
    stash.WithToken("your-api-token"),
)

With custom options:

client, err := stash.New("http://localhost:8080",
    stash.WithToken("your-api-token"),
    stash.WithTimeout(10*time.Second),
    stash.WithRetry(5, 200*time.Millisecond),
)

Code generated by enum generator; DO NOT EDIT.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotFound     = errors.New("key not found")
	ErrUnauthorized = errors.New("unauthorized")
	ErrForbidden    = errors.New("forbidden")
)

sentinel errors for common API responses

View Source
var (
	FormatText  = Format{/* contains filtered or unexported fields */}
	FormatJSON  = Format{/* contains filtered or unexported fields */}
	FormatYAML  = Format{/* contains filtered or unexported fields */}
	FormatXML   = Format{/* contains filtered or unexported fields */}
	FormatTOML  = Format{/* contains filtered or unexported fields */}
	FormatINI   = Format{/* contains filtered or unexported fields */}
	FormatHCL   = Format{/* contains filtered or unexported fields */}
	FormatShell = Format{/* contains filtered or unexported fields */}
)

Public constants for format values

View Source
var ErrZKDecryptionFailed = errors.New("zk decryption failed")

ErrZKDecryptionFailed is returned when ZK decryption fails (wrong key or corrupted data).

View Source
var FormatNames = []string{
	"text",
	"json",
	"yaml",
	"xml",
	"toml",
	"ini",
	"hcl",
	"shell",
}

FormatNames contains all possible enum names

FormatValues contains all possible enum values

Functions

func FormatIter

func FormatIter() func(yield func(Format) bool)

FormatIter returns a function compatible with Go 1.23's range-over-func syntax. It yields all Format values in declaration order. Example:

for v := range FormatIter() {
    // use v
}

func IsValidZKPayload added in v0.18.1

func IsValidZKPayload(value []byte) bool

IsValidZKPayload checks if a ZK value has valid format. Returns true if value has $ZK$ prefix followed by valid base64 of sufficient length. This validates format only, not cryptographic correctness (zero-knowledge preserved).

func IsZKEncrypted added in v0.18.1

func IsZKEncrypted(value []byte) bool

IsZKEncrypted checks if a value is ZK-encrypted by looking for the $ZK$ prefix.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a Stash KV service client.

func New

func New(baseURL string, opts ...Option) (*Client, error)

New creates a new Stash client with the given base URL and options.

func (*Client) Close added in v0.16.0

func (c *Client) Close()

Close clears sensitive data from memory. Call this when the client is no longer needed.

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, key string) error

Delete removes a key.

func (*Client) Get

func (c *Client) Get(ctx context.Context, key string) (string, error)

Get retrieves a value by key as a string.

func (*Client) GetBytes

func (c *Client) GetBytes(ctx context.Context, key string) ([]byte, error)

GetBytes retrieves a value by key as raw bytes.

func (*Client) GetOrDefault

func (c *Client) GetOrDefault(ctx context.Context, key, defaultValue string) (string, error)

GetOrDefault retrieves a value by key, returning defaultValue if the key doesn't exist.

func (*Client) Info

func (c *Client) Info(ctx context.Context, key string) (KeyInfo, error)

Info retrieves metadata for a key. Note: this method uses List with prefix filtering, which may be inefficient for large keyspaces with many keys sharing the same prefix.

func (*Client) List

func (c *Client) List(ctx context.Context, prefix string) ([]KeyInfo, error)

List returns all keys, optionally filtered by prefix. Pass empty string to list all keys.

func (*Client) Ping

func (c *Client) Ping(ctx context.Context) error

Ping checks server connectivity.

func (*Client) Set

func (c *Client) Set(ctx context.Context, key, value string) error

Set stores a value with default text format.

func (*Client) SetWithFormat

func (c *Client) SetWithFormat(ctx context.Context, key, value string, format Format) error

SetWithFormat stores a value with explicit format.

func (*Client) Subscribe added in v0.19.0

func (c *Client) Subscribe(ctx context.Context, key string) (*Subscription, error)

Subscribe creates a subscription for exact key changes. The subscription remains active until context is canceled or Close is called.

func (*Client) SubscribeAll added in v0.19.0

func (c *Client) SubscribeAll(ctx context.Context) (*Subscription, error)

SubscribeAll creates a subscription for all key changes. The subscription remains active until context is canceled or Close is called.

func (*Client) SubscribePrefix added in v0.19.0

func (c *Client) SubscribePrefix(ctx context.Context, prefix string) (*Subscription, error)

SubscribePrefix creates a subscription for all keys matching the prefix. The subscription remains active until context is canceled or Close is called.

type Event added in v0.19.0

type Event struct {
	Key       string `json:"key"`
	Action    string `json:"action"` // create, update, delete
	Timestamp string `json:"timestamp"`
}

Event represents a key change event from the server.

type Format

type Format struct {
	// contains filtered or unexported fields
}

Format is the exported type for the enum

func MustFormat

func MustFormat(v string) Format

MustFormat is like ParseFormat but panics if string is invalid

func ParseFormat

func ParseFormat(v string) (Format, error)

ParseFormat converts string to format enum value. Parsing is always case-insensitive.

func (Format) ContentType

func (f Format) ContentType() string

ContentType returns the HTTP Content-Type for the format.

func (Format) Index

func (e Format) Index() int

Index returns the underlying integer value

func (Format) MarshalText

func (e Format) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler

func (Format) String

func (e Format) String() string

func (*Format) UnmarshalText

func (e *Format) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler

type KeyInfo

type KeyInfo struct {
	Key         string    `json:"key"`
	Size        int       `json:"size"`
	Format      string    `json:"format"`
	Secret      bool      `json:"secret"`
	ZKEncrypted bool      `json:"zk_encrypted"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

KeyInfo contains metadata about a stored key.

type Option

type Option func(*clientConfig)

Option is a functional option for configuring the client.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets a custom http.Client. Note: when using WithHTTPClient, the WithTimeout option has no effect since timeout is configured on the http.Client directly.

func WithRetry

func WithRetry(count int, delay time.Duration) Option

WithRetry configures retry behavior.

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout sets the HTTP request timeout.

func WithToken

func WithToken(token string) Option

WithToken sets the Bearer token for authentication.

func WithZKKey added in v0.16.0

func WithZKKey(passphrase string) Option

WithZKKey enables client-side zero-knowledge encryption with the given passphrase. When enabled, all values stored via Set/SetWithFormat will be encrypted before sending to the server, and values retrieved via Get/GetBytes will be decrypted if they have the $ZK$ prefix. The server never sees plaintext values. Passphrase must be at least 16 characters.

type ResponseError

type ResponseError struct {
	StatusCode int
}

ResponseError represents an HTTP error response from the server.

func (*ResponseError) Error

func (e *ResponseError) Error() string

Error implements the error interface.

type Subscription added in v0.19.0

type Subscription struct {
	// contains filtered or unexported fields
}

Subscription manages an SSE connection for key change events.

func (*Subscription) Close added in v0.19.0

func (s *Subscription) Close()

Close terminates the subscription and releases resources.

func (*Subscription) Errors added in v0.19.0

func (s *Subscription) Errors() <-chan error

Errors returns the channel for receiving connection errors.

func (*Subscription) Events added in v0.19.0

func (s *Subscription) Events() <-chan Event

Events returns the channel for receiving events.

type ZKCrypto added in v0.18.1

type ZKCrypto struct {
	// contains filtered or unexported fields
}

ZKCrypto handles client-side zero-knowledge encryption using AES-256-GCM with Argon2id key derivation.

func NewZKCrypto added in v0.18.1

func NewZKCrypto(passphrase []byte) (*ZKCrypto, error)

NewZKCrypto creates a new ZKCrypto instance with the given passphrase. Passphrase must be at least 16 bytes.

func (*ZKCrypto) Clear added in v0.18.1

func (z *ZKCrypto) Clear()

Clear securely clears the passphrase from memory. note: this is best-effort; Go's GC may have copied the data and the compiler may optimize away the zeroing if it determines the memory won't be read again.

func (*ZKCrypto) Decrypt added in v0.18.1

func (z *ZKCrypto) Decrypt(encrypted []byte) ([]byte, error)

Decrypt decrypts a ZK-encrypted value.

func (*ZKCrypto) Encrypt added in v0.18.1

func (z *ZKCrypto) Encrypt(plaintext []byte) ([]byte, error)

Encrypt encrypts plaintext using AES-256-GCM with Argon2id key derivation. Format: $ZK$<base64(salt || nonce || ciphertext || tag)>

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL