Documentation
¶
Overview ¶
Package external implements a provider that runs as a subprocess and communicates with Orchard over JSON Lines on stdio. A pure byte-mover — it evaluates nothing itself, just marshals calls across the process boundary and returns whatever the subprocess produces. The engine already hands providers fully-evaluated attribute maps, so the adapter is the same shape regardless of whether the provider lives in-process (pkg/provider/postgres) or across a pipe (this package).
Index ¶
- Variables
- type Client
- type Provider
- func (p *Provider) Close() error
- func (p *Provider) Configure(ctx context.Context, config map[string]cty.Value) error
- func (p *Provider) Execute(ctx context.Context, action string, attrs map[string]cty.Value) (map[string]cty.Value, error)
- func (p *Provider) ExecuteUndo(ctx context.Context, action string, attrs map[string]cty.Value, ...) (map[string]cty.Value, *provider.Invocation, error)
- func (p *Provider) Schema() protocol.ProviderSchema
- type SchemaProvider
Constants ¶
This section is empty.
Variables ¶
var ErrClientPoisoned = errors.New("external: client abandoned a prior call; cannot be reused")
ErrClientPoisoned is returned when Roundtrip is called after a previous call was abandoned via context cancellation. Once a caller walks away from an in-flight read, the pipe's reader goroutine is still blocked and we cannot safely start another without racing it. Callers should tear the provider down and spawn a fresh one.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the low-level JSON-Lines transport. It takes a reader and writer — usually a subprocess's stdout and stdin, but pipes in tests — and exposes a single Roundtrip method for request/ response pairs.
Roundtrip holds a mutex for the duration of the exchange, making the client safe for concurrent callers but serial on the wire, so parallel actions on one external provider queue here. That matches the JSON Lines framing (which has no way to interleave partial responses). The upgrade, if one provider becomes the bottleneck, is a background reader goroutine dispatching to a pending-request map — a local change that does not affect callers.
func NewClient ¶
NewClient wraps r and w. The reader is buffered internally; the writer is used directly so callers can compose it with their own buffering if needed.
func (*Client) Roundtrip ¶
Roundtrip sends a request and decodes the matching response.
params is any JSON-marshalable value. result must be a pointer into which the response's result field is decoded; pass nil if the method returns no meaningful payload. Errors returned by the provider are surfaced as *protocol.Error values so callers can inspect the code field if they need to distinguish error classes.
When ctx is cancelled mid-exchange, Roundtrip returns ctx.Err and marks the client as poisoned. Subsequent calls fail fast with ErrClientPoisoned; the caller is expected to force-close the underlying transport (typically by killing the subprocess), which unblocks the still-in-flight reader goroutine and lets it exit cleanly. This avoids ever having two goroutines reading from the same pipe.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider is an Orchard provider that lives in a separate process and communicates over JSON Lines on stdio. It satisfies the same provider.Provider interface as builtins, so the engine does not distinguish between the two — one code path covers both kinds.
Lifecycle: Spawn starts the subprocess, completes the describe handshake, and caches the schema. Configure, Execute, and Close then behave like any in-process provider.
Concurrency: the stderr-drain goroutine runs alongside the handshake, so it reads p.schema while Spawn is about to write it. Every access to schema goes through schemaMu; drainStderr, Schema, and any other reader stay read-locked; Spawn and Close take the write lock briefly when mutating it.
func Spawn ¶
Spawn launches the provider binary at path and performs the describe handshake. The returned Provider is ready to Configure and Execute against. If the subprocess fails to start, responds with an unparseable frame, or returns an invalid schema, Spawn kills the process and returns an error.
Stderr from the subprocess is streamed to the default logger prefixed with the provider's name. Providers are expected to use stderr for diagnostics and reserve stdout exclusively for JSON Lines protocol traffic.
func (*Provider) Close ¶
Close attempts a graceful shutdown of the subprocess and force- kills it if the total shutdown window elapses without a clean exit. The shutdown RPC and the subsequent process wait share a single deadline — total close time is bounded by shutdownTimeout regardless of whether the provider hangs on the RPC, hangs on exit, or hangs on both.
func (*Provider) Configure ¶
Configure marshals the config map to JSON using the provider's declared config types and sends it to the subprocess.
func (*Provider) Execute ¶
func (p *Provider) Execute(ctx context.Context, action string, attrs map[string]cty.Value) (map[string]cty.Value, error)
Execute marshals attrs, invokes the subprocess, and decodes the returned outputs as dynamically-typed cty values. Output typing from the action schema is not yet used for decoding — matching how in-process providers return dynamic output maps — and will come in the plan-time reference-typing milestone.
func (*Provider) ExecuteUndo ¶ added in v0.3.0
func (p *Provider) ExecuteUndo(ctx context.Context, action string, attrs map[string]cty.Value, opts provider.InverseOptions) (map[string]cty.Value, *provider.Invocation, error)
ExecuteUndo runs the action like Execute and asks the subprocess for the call that undoes it. Providers that don't describe undos return none, and the action gets no automatic teardown.
func (*Provider) Schema ¶
func (p *Provider) Schema() protocol.ProviderSchema
Schema returns the schema returned by the subprocess during the describe handshake.
type SchemaProvider ¶
type SchemaProvider struct {
// contains filtered or unexported fields
}
SchemaProvider satisfies provider.Provider with schema-only behaviour. It is the returned type of LoadSchema and exists so plan-mode registries can validate scenarios against provider schemas without being able to execute anything.
Configure and Execute deliberately error — if either runs during plan, something has reached into a provider that should not have been addressable in this mode, and the loud failure is safer than a silent no-op.
func LoadSchema ¶
func LoadSchema(path string) (*SchemaProvider, error)
LoadSchema asks an external provider binary to describe itself without starting a long-lived process. It runs `<path> schema`, reads the JSON schema document the binary writes to stdout, and returns a Provider that responds to Schema() but errors on Configure / Execute.
This is the entry point used by `orchard plan`. Plan is a read-only pass — it type-checks scenario references against provider schemas and does not run any actions. Using Spawn in plan would launch the full subprocess, run the describe handshake, and leave a subprocess alive until Close, which violates the plan contract (no side effects) and makes plan unnecessarily expensive for a pure type check.
The binary must treat "schema" as a first-class subcommand: write the ProviderSchema as JSON on stdout, then exit with status 0. Any stderr output is surfaced in the error message when LoadSchema fails, so authors can see what went wrong.
func (*SchemaProvider) Close ¶
func (p *SchemaProvider) Close() error
Close is a no-op. LoadSchema already waited for the probe subprocess to exit, so there is nothing to tear down.
func (*SchemaProvider) Execute ¶
func (p *SchemaProvider) Execute(context.Context, string, map[string]cty.Value) (map[string]cty.Value, error)
Execute refuses to run in plan mode.
func (*SchemaProvider) Schema ¶
func (p *SchemaProvider) Schema() protocol.ProviderSchema
Schema returns the schema decoded during LoadSchema.