Documentation
¶
Overview ¶
Package soliconfig is a thin Go client for the soliconfig REST core, intended for machine/service usage (agents, CI, servers). It is the foundation for the soliconfig Terraform provider.
The client talks to the env-scoped "agent" endpoints (/v1/agent/...) and authenticates with an env-scoped API key (sc_...) via Authorization: Bearer.
Only the standard library is used — there are no external dependencies.
Index ¶
- Constants
- Variables
- func IsEncrypted(value string) bool
- type APIError
- type ApprovalPendingError
- type Client
- func (c *Client) DecryptSecrets(ctx context.Context, orgID, envID string) (map[string]string, error)
- func (c *Client) DecryptSecretsWithApproval(ctx context.Context, orgID, envID, approvalID string) (map[string]string, error)
- func (c *Client) FetchConfig(ctx context.Context, params FetchConfigParams) (*Config, error)
- func (c *Client) PullSecrets(ctx context.Context) (*Secrets, error)
- type Config
- type Environment
- type EvalContext
- type EvaluatedValue
- type FetchConfigParams
- type HTTPDoer
- type Option
- type Secrets
Constants ¶
const ( TemplateClient = "client" TemplateServer = "server" )
Template types for a config fetch. When unspecified the server defaults to TemplateClient.
const ( CodeQuotaExceeded = "quota_exceeded" CodeOverageBlocked = "overage_blocked" CodeSeatLimit = "seat_limit" // CodeApprovalRequired is returned with HTTP 409 by the unattended sync // surface (GET /v1/eso/secrets) when the vault requires manual approval. // The interactive decrypt endpoint does NOT use it — it answers HTTP 202 // and is surfaced as *ApprovalPendingError, see ErrApprovalPending. CodeApprovalRequired = "approval_required" )
Machine-readable codes surfaced by entitlement/quota gates.
const DefaultBaseURL = "https://api.soliconfig.com"
DefaultBaseURL is the production soliconfig API base URL.
const DefaultUserAgent = "soliconfig-go/0.1.0"
DefaultUserAgent identifies this SDK in outgoing requests. Higher-level integrations built on this SDK (e.g. the Terraform provider) prepend their own identifier via WithUserAgent so the server can apply integration tier gates.
Variables ¶
var ErrApprovalPending = errors.New("soliconfig: vault decrypt is awaiting approval")
ErrApprovalPending is the sentinel behind every approval-pending failure. Use it with errors.Is when the approval id itself is not needed:
if errors.Is(err, soliconfig.ErrApprovalPending) { /* wait for a human */ }
Functions ¶
func IsEncrypted ¶
IsEncrypted reports whether a value is ECIES ciphertext (i.e. carries the "encrypted:" prefix) rather than plaintext.
Types ¶
type APIError ¶
type APIError struct {
// StatusCode is the HTTP status code of the response.
StatusCode int
// Code is the machine-readable error code (e.g. "quota_exceeded"), if any.
Code string
// Message is the human-readable error message.
Message string
}
APIError is returned for any non-2xx response from the soliconfig API.
The API reports failures in two shapes:
- Generic HTTP failures carry a human message: {"error": "..."}.
- Quota/entitlement gates carry a machine code: {"code": "quota_exceeded"} (also "overage_blocked", "seat_limit"), typically with HTTP 429.
Both are captured here: Code holds the machine code (when present) and Message holds the human message (falling back to the code or the HTTP status text). Callers can branch on Code, e.g.:
var apiErr *soliconfig.APIError
if errors.As(err, &apiErr) && apiErr.Code == soliconfig.CodeQuotaExceeded {
// back off / surface upgrade prompt
}
type ApprovalPendingError ¶ added in v0.2.0
type ApprovalPendingError struct {
// ApprovalID is the id of the pending vault approval request.
ApprovalID string
// Status is the server-reported approval status (currently "pending").
Status string
}
ApprovalPendingError reports that a vault decrypt could not be served because the environment's vault has requireApproval enabled and a human still has to approve the request.
The API answers such a request with HTTP 202 and {"approvalId","status"} — a *success* status carrying no secret values. Returning it as an error is deliberate: a caller that ignored it would otherwise silently proceed with an empty secret set.
ApprovalID identifies the pending (or freshly created) approval request. Once a second admin approves it in the dashboard, the same caller redeems it via DecryptSecretsWithApproval. Match it with errors.As:
var pending *soliconfig.ApprovalPendingError
if errors.As(err, &pending) {
log.Printf("waiting for approval %s", pending.ApprovalID)
}
func (*ApprovalPendingError) Error ¶ added in v0.2.0
func (e *ApprovalPendingError) Error() string
Error implements the error interface.
func (*ApprovalPendingError) Unwrap ¶ added in v0.2.0
func (e *ApprovalPendingError) Unwrap() error
Unwrap makes errors.Is(err, ErrApprovalPending) report true.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a soliconfig REST client. Create one with New. It is safe for concurrent use by multiple goroutines.
func New ¶
New creates a Client authenticated with the given env-scoped API key (sc_...). Options may override the base URL and HTTP client.
func (*Client) DecryptSecrets ¶
func (c *Client) DecryptSecrets(ctx context.Context, orgID, envID string) (map[string]string, error)
DecryptSecrets fetches the latest env version and returns fully-decrypted, plaintext secret values via the server-side decrypt endpoint POST /v1/orgs/{orgID}/environments/{envID}/vault/decrypt.
Unlike PullSecrets — which returns "encrypted:"-prefixed ciphertext verbatim — this performs the decrypt on the server. It requires a read or admin scoped API key (sc_...); an encrypt-only key is rejected with a 403 *APIError. Entitlement/plan gates surface as a 402 *APIError.
When the environment's vault has requireApproval enabled, the server does not return secrets: it registers an approval request and answers HTTP 202 with {"approvalId","status":"pending"}. That case is returned as an *ApprovalPendingError (which matches errors.Is(err, ErrApprovalPending)) and never as a nil error with an empty map. Once a second admin approves the request, redeem it with DecryptSecretsWithApproval.
The returned map is keyed by secret name with plaintext values.
func (*Client) DecryptSecretsWithApproval ¶ added in v0.2.0
func (c *Client) DecryptSecretsWithApproval(ctx context.Context, orgID, envID, approvalID string) (map[string]string, error)
DecryptSecretsWithApproval is DecryptSecrets for the requireApproval flow: it redeems the approval request identified by approvalID (obtained from an *ApprovalPendingError returned by an earlier call).
Approvals are single-use, time-boxed, and may only be redeemed by the same actor that requested them — redeeming with a different API key fails with a 403 *APIError. A request that is still awaiting a decision comes back as an *ApprovalPendingError again; a denied one as 403, an expired one as 410.
An empty approvalID behaves exactly like DecryptSecrets.
func (*Client) FetchConfig ¶
FetchConfig performs a one-shot, server-evaluated config fetch against POST /v1/agent/config/fetch. The API key must be env-scoped and readable (an encrypt-only key is rejected with an *APIError).
func (*Client) PullSecrets ¶
PullSecrets fetches the latest env version via GET /v1/agent/env. The API key must be env-scoped and readable (an encrypt-only, write-only key is rejected with an *APIError). When no version has been pushed yet, Version is 0 and Entries is empty.
See the Secrets docs: entry values may be "encrypted:"-prefixed ciphertext; this client returns them verbatim and does not decrypt.
type Config ¶
type Config struct {
Version int `json:"version"`
Evaluated map[string]EvaluatedValue `json:"evaluated"`
}
Config is the result of FetchConfig: the published version and the evaluated parameter map keyed by parameter name.
type Environment ¶
type Environment struct {
ID string `json:"id"`
Name string `json:"name"`
PublicKey string `json:"publicKey"`
}
Environment describes the env-scoped target resolved from the API key, plus its public key (used by producers to encrypt values before push).
type EvalContext ¶
type EvalContext struct {
// RandomizationID is the sticky bucketing id (required, min length 1).
RandomizationID string `json:"randomizationId"`
// CustomSignals are arbitrary string key/value signals for rule matching.
CustomSignals map[string]string `json:"customSignals,omitempty"`
// Platform is one of "web", "ios", "android", "server".
Platform string `json:"platform,omitempty"`
// Country is an ISO 3166-1 alpha-2 code (2 letters).
Country string `json:"country,omitempty"`
// Language is a BCP-47-ish language tag.
Language string `json:"language,omitempty"`
// Now is an optional ISO-8601 timestamp override for time-based rules.
Now string `json:"now,omitempty"`
}
EvalContext is the server-side evaluation context (Firebase Remote Config parity). RandomizationID is required; all other fields are optional.
type EvaluatedValue ¶
type EvaluatedValue struct {
// Value is the raw string value.
Value string `json:"value"`
// ValueType is one of "string", "boolean", "number", "json".
ValueType string `json:"valueType"`
// Source is "conditional" or "default".
Source string `json:"source"`
// MatchedCondition is the name of the matched condition, when Source is
// "conditional".
MatchedCondition string `json:"matchedCondition,omitempty"`
}
EvaluatedValue is a single evaluated parameter. Value is always the raw string representation; ValueType indicates how to interpret it. Use the typed accessors (Bool, Int, Float, JSON) to cast.
func (EvaluatedValue) Bool ¶
func (v EvaluatedValue) Bool() bool
Bool parses the value as a boolean (true when Value == "true"), matching the server-side cast semantics.
func (EvaluatedValue) Float ¶
func (v EvaluatedValue) Float() (float64, error)
Float parses the value as a float.
func (EvaluatedValue) Int ¶
func (v EvaluatedValue) Int() (int64, error)
Int parses the value as an integer.
func (EvaluatedValue) JSON ¶
func (v EvaluatedValue) JSON(out any) error
JSON unmarshals the value (a JSON document) into out.
type FetchConfigParams ¶
type FetchConfigParams struct {
// Context is the evaluation context. RandomizationID must be set.
Context EvalContext
// TemplateType is "client" or "server" (default "client" server-side).
TemplateType string
}
FetchConfigParams are the inputs to FetchConfig.
type HTTPDoer ¶
HTTPDoer is the subset of *http.Client used by the client. It lets callers inject a custom transport (retries, tracing, mocks, ...) via WithHTTPClient.
type Option ¶
type Option func(*Client)
Option configures a Client in New.
func WithBaseURL ¶
WithBaseURL overrides the API base URL (default DefaultBaseURL). A trailing slash is trimmed. Useful for self-hosted deployments or tests.
func WithHTTPClient ¶
WithHTTPClient sets a custom HTTP client (or any HTTPDoer). When unset a *http.Client with a 30s timeout is used.
func WithUserAgent ¶ added in v0.1.1
WithUserAgent overrides the outgoing User-Agent header (default DefaultUserAgent). Integrations built on this SDK use it to identify their surface — e.g. the Terraform provider sends a "terraform-provider-soliconfig/..." User-Agent so the server can apply the Team+ integration tier gate. An empty value is ignored and the default is retained.
type Secrets ¶
type Secrets struct {
Environment Environment `json:"environment"`
Version int `json:"version"`
Entries map[string]string `json:"entries"`
}
Secrets is the result of PullSecrets: the environment metadata, the current version, and the entries map.
Entries values are stored server-side as ciphertext and are returned with the "encrypted:" prefix (base64 ECIES over secp256k1 + AES-256-GCM, dotenvx wire-compatible). The server never sees plaintext and there is no server-side decrypt endpoint, so decryption is a client-side concern requiring the environment's private key and a secp256k1 ECIES implementation — which is out of scope for this stdlib-only client. Use IsEncrypted to detect ciphertext; values without the prefix are already plaintext.