Documentation
¶
Overview ¶
Package dryrun records what a mutating command WOULD have sent, and stops it from being sent.
The mechanism is a transport rather than a check at each call site, for the same reason internal/httplog is: there are two places in this CLI where an HTTP client is built, and roughly fifty verbs that issue writes through them. A per-verb flag test would be fifty chances to forget one. Intercepting below the generated clients also means the reported request is the ACTUAL bytes those clients assembled — a preview rendered independently at the command layer could drift from what the client really sends, and a dry run that lies about the request is worse than no dry run.
The Run is the channel, not the error. RoundTrip must return an error to abort — returning a synthetic response would let the caller parse a fabricated body and report a fabricated result — but nothing downstream may be required to preserve that error. It cannot be: internal/controlplane/cmd.networkError formats its cause with %v into an ExitError carrying no Unwrap, so the chain is already gone by the time any write site returns. Callers therefore ask the Run whether an interception happened, and the error is only what unwound the stack.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Wrap ¶
func Wrap(base http.RoundTripper, run *Run, mutatingGETs ...*regexp.Regexp) http.RoundTripper
Wrap returns base wrapped so that any request that changes state is recorded into run and never sent.
mutatingGETs are path patterns for GET requests that change state anyway. THE METHOD IS NOT ENOUGH, and assuming it was shipped a bug: Control Plane declares `GET /v1/cluster/init` and `GET /v1/databases/{id}/tasks/{id}/cancel`, so a method-only rule let `pgedge controlplane task cancel --dry-run` really cancel the task and report success. Callers supply the patterns for their own API because the knowledge belongs to whoever vendored the spec — the starfleet module passes none, having no such operation in any of its three specs.
With a nil run it returns base itself rather than a wrapper that decides to do nothing, matching httplog.Wrap: the quiet path is every normal command and should add neither an allocation nor a layer.
Types ¶
type Request ¶
type Request struct {
Method string
// URL comes from url.URL.Redacted, so an --api-url carrying
// userinfo credentials cannot be echoed back out of the report.
URL string
Header http.Header
// Body has already been through httplog.RedactBody: secret values
// are masked and the whole thing is bounded. It is never the raw
// payload, so no consumer of this field can leak one.
Body []byte
// BodyIsJSON reports whether Body still parses as JSON after
// masking. It does not when RedactBody took its fail-closed path
// and returned prose instead of a document, which is exactly when a
// renderer must not present the value as JSON.
BodyIsJSON bool
}
Request is the write that would have been sent.
type Run ¶
type Run struct {
// contains filtered or unexported fields
}
Run is one dry run's state: the checks that passed, and the write that was stopped.
A nil *Run means dry-run is off, and every method tolerates one. That is what lets check sites call Pass unconditionally instead of each guarding on a mode flag — fifty guarded call sites is fifty chances to write the guard backwards.
The mutex is not speculative. Commands that poll (the --wait paths) run requests from more than one goroutine, and `make test` runs under -race, so an unsynchronised append here would surface as a flake in somebody else's test rather than as a bug in this file.
func (*Run) Checks ¶
Checks returns a copy of the ledger, in the order it was recorded. A copy because the caller is a renderer and the Run may still be written to by a goroutine that has not finished unwinding.
func (*Run) Intercepted ¶
Intercepted reports whether a write was stopped. A true here is the command's outcome, whatever error the caller also received.
func (*Run) Pass ¶
Pass records that a client-side check proved something. Callers state what was PROVED, in the past tense, naming the concrete subject: "database 7f3a5c1e-0000-4000-8000-000000000000 resolved" rather than "checked the name".
It is a no-op on a nil receiver — the normal, non-dry-run path.