Documentation
¶
Overview ¶
Package diffcache caches per-resource Diff results so a wfctl invocation that re-runs a Plan against unchanged inputs can skip the (sometimes network-expensive) provider-side Diff call. The cache is purely an amortization optimization, NOT a correctness mechanism — apply paths remain correct on a 100% miss rate (which is exactly what CI sees on every fresh runner).
Storage backends ¶
Cache selection is driven by the WFCTL_DIFFCACHE env var, resolved by New:
- `disabled` → noop cache (every Get misses; Put is a no-op). Use this when an operator wants fully-deterministic Plan/Apply timing with no shared state across invocations.
- `:memory:` → in-memory cache that lives only for the current process. CI workflows in this repo set WFCTL_DIFFCACHE=:memory: explicitly so containerized runners never write to disk.
- any other value (or unset) → filesystem cache rooted at `~/.cache/wfctl/diff/`.
CI ephemerality (load-bearing) ¶
CI runners are ephemeral — each job starts with an empty cache. Workflow correctness MUST NOT depend on a cache hit. The diff cache is an operator-local performance optimization for repeated `wfctl infra plan` invocations against the same checkout.
Cache key ¶
The cache key is a Key tuple of (PluginVersion, Type, ProviderID, SHAConfig, SHAOutputs). Plugin downgrades naturally invalidate entries since PluginVersion is part of the key — old entries persist on disk until the LRU eviction reclaims them; the size cap (1024 entries / 64 MiB) bounds the disk waste.
Schema versioning ¶
Each cache file embeds [cacheSchemaVersion] in a JSON envelope. On Get, a mismatched version is treated identically to file corruption: the entry is silently evicted and the caller re-Diffs.
Known limitations ¶
Windows: the filesystem cache uses os.Rename for the atomic publish step on Put. On Windows, os.Rename fails when the destination already exists, so updating an existing cache entry will fail (the entry is treated as a write failure and the operator gets a cache miss on the next Get — correct, since apply does not depend on cache hits). A future improvement is to vendor github.com/google/renameio for cross-platform atomic rename; deferred until there's a Windows-supported wfctl use case.
T3.5 / W-3a status ¶
This package ships in W-3a. The consumer that wires it into platform.ComputePlan lands in W-3b/T3.6f. Until then, the cache is callable but never invoked from production code paths.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache interface {
// Get returns the cached DiffResult for key. The boolean is true
// iff the entry was found and successfully decoded; corruption,
// schema-version mismatch, and missing entries all yield
// (zero-value, false).
Get(key Key) (interfaces.DiffResult, bool)
// Put stores result under key. Errors during Put (e.g., disk
// full, serialization failure) are silently swallowed because
// cache misses are correct — apply behavior must not depend on
// Put success.
Put(key Key, result interfaces.DiffResult)
}
Cache is the diff-result cache. Implementations are safe for concurrent use (the backing fs cache uses os-level atomic file ops; the in-memory cache locks internally).
func New ¶
func New() Cache
New returns a Cache configured by the WFCTL_DIFFCACHE env var.
- "disabled" → NewNoop
- ":memory:" → NewMemory
- default → NewFilesystem rooted at the user cache directory (typically `~/.cache/wfctl/diff/`).
When the user cache directory cannot be resolved, falls back to the in-memory cache so the caller still gets a working Cache (the filesystem path being unavailable is the operator's hint that disk caching is off).
func NewFilesystem ¶
NewFilesystem returns a Cache whose entries are persisted under dir. The directory is created on first Put if absent. Callers may pass any directory; the New factory uses `~/.cache/wfctl/diff/`.
type Key ¶
type Key struct {
// PluginVersion is the plugin's name@version string, e.g.,
// "do@v0.10.0". Plugin downgrades naturally invalidate cache
// entries via this field.
PluginVersion string `json:"plugin_version"`
// Type is the canonical resource type, e.g., "infra.vpc".
Type string `json:"type"`
// ProviderID is the resource's cloud-side identifier; empty for
// net-new resources.
ProviderID string `json:"provider_id"`
// SHAConfig is the sha256-hex of canonical-marshal(spec.Config).
SHAConfig string `json:"sha_config"`
// SHAOutputs is the sha256-hex of canonical-marshal(currentState.Outputs);
// empty for net-new resources.
SHAOutputs string `json:"sha_outputs"`
}
Key tuples the inputs to a single Diff. Two Keys are equal iff every field matches; the canonical sha256 fingerprint of the key determines the on-disk filename.
JSON tags on the fields are for log / transcript serialization only — cache keying uses NUL-separated string concatenation in [keyFingerprint], not JSON marshaling. A reader checking the fingerprint shape should follow the keyFingerprint code, not the tags.