All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- CLI skeleton with
reconcileandvalidatesubcommands - Global flags:
--json,--insecure-tls RESTIUM_*environment variable support for all flags- Exit code convention: 0 (success), 1 (runtime error), 2 (input/validation error)
- Structured logging with JSON (
--json) and human-readable text modes - Automatic secret redaction for sensitive key-value pairs (authorization, token, password, api_key, client_secret, etc.)
- URL query parameter redaction for secrets in logged URLs
- YAML spec file parsing with
globalsettings andresourceslist - Global settings inheritance: base_url, default_headers, auth (with per-resource override)
- Structured payloads (nested objects, arrays) preserved through YAML-to-JSON
depends_onfield for explicit resource orderingdeny_unknown_fieldson spec types for typo detection- AuthConfig type definitions (bearer, basic, api_key, oidc, mtls) — type stubs for Epic 2
validateandreconcilecommands now parse and report spec file contents- Spec validation: reference checking (
${resource.output.field}), duplicate resource name detection, circular dependency detection validatecommand reports ALL errors (not just the first), exits with code 2 on validation failure- Dependency graph module (
src/graph/mod.rs) withpetgraph-based topological sort and cycle detection DependencyGraph::build()merges explicitdepends_onand implicit${resource.output.field}references into a single DAGDependencyGraph::topological_sort()returns execution order or cycle path error- Reconcile module (
src/reconcile/) with state discovery, diff computation, and action determination ResourceActionenum: Create, Update, Skip, Delete- Key-order-independent JSON comparison (
json_equal) for idempotent reconciliation - State discovery via GET with 404 handling (resource does not exist → Create)
- Resource execution module (
src/reconcile/execute.rs) for create/update/skip operations - Actionable error messages with resource name, HTTP method, endpoint, status code, and hints
- Error hints for common HTTP status codes (401, 403, 404, 409, 422, 429, 5xx)
- Update operations auto-switch from POST to PUT when resource method is POST
- Explicit resource deletion via
action: deletewith DELETE HTTP request - Delete of absent resource (404) logged as "already absent" and skipped
- Reconcile command orchestration: parse → graph → sort → for each resource: resolve refs → discover state → compute diff → execute → extract outputs
- Reconciliation summary logged on completion (created, updated, deleted, skipped, failed counts)
- Delete resources processed in reverse topological order (dependents deleted before dependencies)
- Exit code 1 on reconciliation failure, exit code 0 on success
- Reference module (
src/reference/mod.rs) for output extraction and${resource.output.field}resolution OutputStoretype for storing extracted outputs across resourcesextract_outputs()extracts fields from API responses (string, number, bool, null, complex)resolve_references()recursively substitutes template references in JSON payloadsresolve_string()resolves references in individual strings (endpoints, read_endpoints)- HTTP client wrapper (
HttpClient) with configurable TLS viaureq+rustls - TLS certificate verification enabled by default (webpki roots)
--insecure-tlsflag skips certificate verification (explicit opt-in, NFR3)- Custom CA bundle support via
ca_bundlefield in global config AuthProvidertrait for pluggable authentication strategies- Bearer token authentication via
token_envenvironment variable - Basic auth (username/password) via environment variables with base64 encoding
- Auth provider factory (
create_auth_provider) for config-driven provider selection - API key authentication via header or query parameter mode
- OIDC/OAuth2 client credentials authentication (token fetched at startup via POST to token endpoint)
- mTLS client certificate authentication via
client_cert_pathandclient_key_path - mTLS works in combination with custom CA bundles
- Per-resource auth override: resources can specify their own
authconfig, overriding global auth - Shared
read_env_credentialhelper validates env vars are set, non-empty, and UTF-8 across all auth providers --sidecarflag (andRESTIUM_SIDECARenv var) to keep the process alive after reconciliation completes — for K8s sidecar container deployments where the container must not exit- Distroless Docker image (
FROM scratch) with multi-stage build — 3.5MB, zero CVEs, non-root user - Multi-arch container support (linux/amd64, linux/arm64) via Docker buildx with QEMU in release pipeline
make crosstarget for local musl cross-compilation via cargo-zigbuildmake docker-multiarchtarget for local multi-arch Docker builds- CI pipeline (GitHub Actions): fmt check, clippy with
--all-targets, test, build on every PR and push to main - Release pipeline: automated cargo publish + multi-arch GHCR push on version tags, with SBOM and provenance
- Auto-tag workflow: automatically creates git tags when Cargo.toml version changes on main
- Comprehensive README with value proposition, quick start, Netbird example, spec reference, auth docs, security posture, CLI reference, and deployment guide
- Example spec files:
examples/simple.yaml(minimal),examples/netbird.yaml(real-world Netbird bootstrapping) - Helm chart (
charts/restium/) for K8s Job deployment with ConfigMap-mounted spec and Secret-based credentials
validatecommand no longer creates an HTTP client (pure syntax/semantic validation)- Spec file loading uses
SpecFile::loaderror directly instead of redundant existence check - Validation now scans
endpointfield for template references (previously onlypayloadandread_endpoint) - Cycle detection replaced from manual DFS to
petgraph-based implementation viaDependencyGraph - Reference extraction functions (
extract_refs_from_string,extract_references_from_value) made public for reuse by graph module --insecure-tlsand mTLS client certificates are now mutually exclusive (previously mTLS cert was silently dropped)- Lint target now includes test code (
cargo clippy --all-targets) - Test fixture paths use
CARGO_MANIFEST_DIRfor CWD-independent test execution HttpClient::get()now acceptsauth: Option<&dyn AuthProvider>for consistency withsend_json()andrequest()NoVerifier::supported_verify_schemes()caches signature schemes viaLazyLockinstead of re-instantiatingCryptoProviderper TLS handshake- mTLS file validation deferred to
HttpClient::new()— removed redundantmetadata()check fromMtlsAuthProvider::new()to eliminate TOCTOU window OidcAuthProvider::with_token()marked#[doc(hidden)](testing escape hatch, not public API)auth::mtlsandauth::oidcsubmodules changed frompub modtomod(types re-exported viapub use)- OIDC token request now uses the configured
HttpClientagent (inherits TLS settings: custom CA bundle,--insecure-tls) create_auth_provider()accepts optional&ureq::Agentfor OIDC TLS configurationHttpClient::agent()method exposes the underlying ureq Agent for auth provider useDependencyGraph::build()usesupdate_edgeinstead ofadd_edgeto prevent duplicate edges when explicit and implicit deps overlapMtlsAuthProvidersimplified to unit struct — cert/key paths extracted fromAuthConfigdirectly, not stored redundantly on the providerexecute_actionreturnsExecuteResultenum (Performed/AlreadyOk) instead ofOption<Value>— allows callers to distinguish successful operations from skips/already-absent- Success log in
execute_mutationmoved after response body parse (previously logged success before JSON parse could fail) - Non-JSON 2xx responses treated as success with no body (previously hard-failed as invalid JSON)
- Update method auto-switch uses case-insensitive comparison (
eq_ignore_ascii_case("POST")) error_hintchecks transport errors before HTTP status codes to avoid false matches on URL digits- Delete 404 ("already absent") now increments
skippedcounter instead ofdeleted - State discovery GET skipped when resource has no payload (previously wasted a network request)
- OIDC error messages no longer expose
client_secret— controlled error formatting instead of raw ureq error propagation - OIDC error messages now extract OAuth error fields (
error,error_description) from the response body for actionable diagnostics - Auth providers (Bearer, Basic, ApiKey, OIDC) now implement custom
Debugthat redacts credentials instead of using#[derive(Debug)] - Empty env var values rejected with actionable error (previously silently sent empty credentials)
VarError::NotUnicodenow produces distinct error message instead of misleading "not set"- API key auth validates
header_nameandquery_paramare non-empty
process_delete_resourcenow resolves${resource.output.field}template references in endpoints (was using raw template strings, causing 404s or wrong-resource deletion)execute_mutationnow propagates JSON parse errors from API responses instead of silently substitutingnull- Spec validation rejects unknown
actionvalues (onlydeleteis supported; previously any string was silently treated as create/update) - OIDC
url_encoderenamed toform_url_encodeand uses+for spaces (HTML form encoding per RFC 1866) instead of%20(RFC 3986), fixing multi-word scope values on strict token endpoints - OIDC
token_urlis now validated as non-empty before making the HTTP request - Redundant "Error:" prefix in spec file not found message (logger already prepends
[ERROR]) format!+push_strreplaced withwrite!to avoid unnecessary allocations in OIDC URL encoding and text log formatting- Misleading
// Safety:comment changed to// Note:(not an unsafe block) - JSON log fallback replaced with
expect()(serialization ofBTreeMap<String, Value::String>is infallible) - Unnecessary
body.clone()removed inHttpClient::send_json(reference already satisfiesSerialize) - Malformed template references like
${.output.field}(empty resource name) are now silently ignored instead of producing confusing errors - Use
sort_unstable()instead ofsort()for primitive&strslice in cycle detection