Documentation
¶
Overview ¶
Package vaultclient resolves the Vault client an adapter talks to — once and shared, or afresh per operation, as the caller chooses.
Vault is the provider where the client *is* the connection prerequisite. There is no separate configuration object to hand a service constructor: a *vaultapi.Client carries the address, the namespace, the token and the retry policy, and every adapter takes that one type.
Choosing a rung ¶
vaultclient.FromClient(client) // you built it; used as-is vaultclient.FromConfig(cfg) // you configured it; the client is built here vaultclient.Ambient() // VAULT_ADDR and friends, resolved once and shared vaultclient.PerCall() // the same, resolved afresh every time
Four rungs rather than three, because for Vault a client and a config are both things a caller plausibly already holds, and the two are type-distinct so neither can be confused for the other.
This module does not guess, but Vault does ¶
Its AWS counterpart refuses to default a region, because guessing one names a key in an account nobody chose. Vault is different: vaultapi.DefaultConfig documents its own default of https://127.0.0.1:8200, overridden by VAULT_ADDR. Adopting a provider's documented default is not guessing, so Ambient takes it as it stands.
What this module does add is a check the SDK makes easy to miss. vaultapi.DefaultConfig reports failure by populating the Error field on the config it returns rather than by returning an error, so a caller who does not look gets a config that appears fine and fails later. Every rung here inspects it.
Sharing is explicit ¶
Two adapters that each call Ambient get two independent sources. A caller who wants one client builds one Source and hands it to each adapter.
Specified by org 0003 (P-2, P-3, P-12, P-13, P-14), which applies org 0002's connection lifecycle across the estate.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNoClient reports a nil client or config supplied to an injecting rung. ErrNoClient = errors.NewSentinel("vaultclient.no_client", "no Vault client or config supplied; pass one, or use Ambient to read VAULT_ADDR") // ErrNoAddress reports a config that names no Vault address. // // It cannot arise from [Ambient], which inherits Vault's own documented // default, but a caller assembling a *vaultapi.Config by hand can leave it // empty — and an empty address fails at the first request rather than here. ErrNoAddress = errors.NewSentinel("vaultclient.no_address", "no Vault address configured; set Address on the config, pass WithAddress, or set VAULT_ADDR") )
Functions ¶
This section is empty.
Types ¶
type Option ¶
type Option func(*options)
Option configures a source.
func WithAddress ¶
WithAddress overrides the Vault address the ambient config resolved. An empty string is ignored, so an unset flag cannot clear an address another option set.
func WithBuildTimeout ¶
WithBuildTimeout bounds a single build attempt. Per attempt, not a total budget: a failure caches nothing, so a later call gets a fresh allowance.
func WithLifetimeContext ¶
WithLifetimeContext scopes an Ambient source's build attempts to the life of whatever owns it. PerCall ignores it, being already scoped to its caller.
func WithLogger ¶
WithLogger enables DEBUG diagnostics. nil disables them, which is the default.
Records carry only non-secret identifiers — the address and how long the build took. A Vault token is never logged, at any level, and nothing is logged above DEBUG.
func WithNamespace ¶
WithNamespace sets the Vault Enterprise namespace on the built client. An empty string is ignored.
type Source ¶
type Source interface {
// VaultClient returns the client, building it if the source's strategy says
// to. It is safe for concurrent use.
VaultClient(ctx context.Context) (*vaultapi.Client, error)
}
Source yields the Vault client an adapter talks to.
The method is named for what it returns rather than a generic Get, so a call site reads as what it is and two providers' sources are not accidentally interchangeable.
func Ambient ¶
Ambient returns a source that builds its client from the ambient environment — VAULT_ADDR, VAULT_TOKEN, VAULT_NAMESPACE and the rest — the first time it is asked, and shares the result.
It returns immediately and performs no I/O.
func FromClient ¶
FromClient returns a source over a client the caller already built.
The client is used as-is and nothing is resolved, so this rung has no build policy to apply.
func FromConfig ¶
FromConfig returns a source over a config the caller assembled, building the client here.
vaultapi.NewClient does no I/O, so this rung never runs the lazy build either — the client is constructed now and any error returned now.
Example ¶
ExampleFromConfig shows the config rung. NewClient does no I/O, so the client is built now and any error reported now.
package main
import (
"context"
"fmt"
vaultapi "github.com/hashicorp/vault/api"
"gitlab.com/phpboyscout/go/vaultclient"
)
func main() {
src, err := vaultclient.FromConfig(&vaultapi.Config{Address: "https://vault.example:8200"})
if err != nil {
fmt.Println("construct:", err)
return
}
client, err := src.VaultClient(context.Background())
if err != nil {
fmt.Println("resolve:", err)
return
}
fmt.Println(client.Address())
}
Output: https://vault.example:8200
Example (ConfigError) ¶
ExampleFromConfig_configError shows the check the SDK makes easy to miss: DefaultConfig reports failure by populating Error rather than returning one, so a config that looks fine can already be broken.
package main
import (
"errors"
"fmt"
vaultapi "github.com/hashicorp/vault/api"
"gitlab.com/phpboyscout/go/vaultclient"
)
func main() {
cfg := &vaultapi.Config{Address: "https://vault.example:8200"}
cfg.Error = errors.New("VAULT_MAX_RETRIES is not a number")
_, err := vaultclient.FromConfig(cfg)
fmt.Println(err)
}
Output: reading the Vault configuration: VAULT_MAX_RETRIES is not a number
Example (NoAddress) ¶
ExampleFromConfig_noAddress shows the other refusal.
package main
import (
"errors"
"fmt"
vaultapi "github.com/hashicorp/vault/api"
"gitlab.com/phpboyscout/go/vaultclient"
)
func main() {
_, err := vaultclient.FromConfig(&vaultapi.Config{})
fmt.Println(errors.Is(err, vaultclient.ErrNoAddress))
}
Output: true