iaclint

package
v0.20.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 2, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package iaclint provides cross-provider review discipline as executable test helpers for IaC plugin authors.

The bug-class taxonomy and rationale for each helper live in the project review checklist:

docs/IAC_PLUGIN_REVIEW_CHECKLIST.md

Three matchers are exposed:

  • AssertOutputsStructpbCompatible (BC-2): verifies Outputs map values are structpb-compatible (NewStruct accepts them). Catches typed-slice writes that would degrade at the wfctl→plugin gRPC boundary. Does NOT exercise the full NewStruct → AsMap round-trip; if your plugin's Diff reads typed values from current.Outputs, write a post-roundtrip test in your plugin's test suite. Use for plugins on legacy compat dispatch.

  • AssertDiffPopulatesAllOutputFields (BC-3): verifies every Outputs[*] key the driver's Diff reads is populated by the matching Create writer. Use for every ResourceDriver in the plugin.

  • AssertValidationMatrix (BC-4): exercises edge values for a config-field parser (TCP port, integer-only float, non-empty string, string enum, non-negative int). Use for every field-level config validator.

IaC plugins import this package in their test suites so the bug classes surfaced during the workflow-plugin-digitalocean v0.8.0 review cycle are caught at CI time rather than during production gRPC dispatch or in code review. Plugins on legacy compat dispatch (no internal/contracts/ proto package, plugin.json mode != "strict") MUST call AssertOutputsStructpbCompatible for every Outputs map written by Create/Update/Read. Strict-mode plugins (plugin.json mode == "strict") are immune to BC-2 and may skip that matcher.

Example

Example shows a typical IaC plugin test importing iaclint to assert all three bug-class invariants on a single driver.

package main

import (
	"github.com/GoCodeAlone/workflow/plugin/sdk/iaclint"
)

func main() {
	// In a real plugin test:
	//
	//   driver := &MyFirewallDriver{client: mockClient}
	//   iaclint.AssertOutputsStructpbCompatible(t, mustCreate(t, driver).Outputs)
	//   iaclint.AssertDiffPopulatesAllOutputFields(t, driver, sampleSpec)
	//   iaclint.AssertValidationMatrix(t, parsePort, "port", iaclint.KindTCPPort)
	//
	// See docs/IAC_PLUGIN_REVIEW_CHECKLIST.md for the bug-class taxonomy.
	_ = iaclint.KindTCPPort
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertDiffPopulatesAllOutputFields

func AssertDiffPopulatesAllOutputFields(t TB, driver interfaces.ResourceDriver, sampleSpec interfaces.ResourceSpec)

AssertDiffPopulatesAllOutputFields verifies that for every key the driver's Diff implementation reads from current.Outputs, the driver's Create call populates that key. Closes BC-3 (Outputs-vs-Diff invariant) for the Create path.

The driver must implement DiffOutputKeyDeclarer to declare its read set. The matcher invokes Create with the provided sample spec and inspects the returned ResourceOutput.Outputs map for the declared keys.

sampleSpec must be a representative input that exercises the writer's happy path. Use the spec the plugin's own tests use.

Scope note: this matcher only verifies the Create writer. Read and Update population are NOT exercised here — if your driver only populates a key on the Read or Update path (e.g., post-deployment status, in-place rotation), add a separate test that calls Read/Update against a representative ref and asserts the same key set. Drift between writer paths is a recurring BC-3 sub-class.

func AssertOutputsStructpbCompatible

func AssertOutputsStructpbCompatible(t TB, outputs map[string]any)

AssertOutputsStructpbCompatible verifies that every value in outputs is structpb-compatible — that is, that structpb.NewStruct accepts the outputs map without error. structpb's encoding rejects typed slices ([]int, []string, []godo.X) outright, so this matcher catches the most common BC-2 failure mode: typed-slice writes that would silently degrade at the wfctl→plugin gRPC boundary.

Scope note: this matcher does NOT exercise the full NewStruct → AsMap round-trip. Values that NewStruct accepts but degrade structurally on AsMap (e.g., godo structs becoming map[string]any so reader-side type assertions to the original struct type fail silently) are not caught. If your plugin's Diff reads typed values from current.Outputs, also write a post-roundtrip test in your plugin's test suite that builds an Outputs map, round-trips through structpb.NewStruct/AsMap(), then asserts your reader-side helpers still extract the values correctly.

Plugins on legacy compat dispatch (no internal/contracts/ proto package, plugin.json mode != "strict") MUST call this matcher in their test suite for every Outputs map written by Create/Update/Read, so the canonical-shape invariant is enforced at CI time.

Strict-mode plugins (plugin.json mode == "strict") are immune to BC-2 and do not need this matcher.

Returns silently for nil or empty outputs; both are trivially structpb- compatible. Detecting whether Outputs is populated at all (the writer-side invariant) is BC-3's job — pair this matcher with AssertDiffPopulatesAllOutputFields for full coverage.

func AssertValidationMatrix

func AssertValidationMatrix(t TB, parser ConfigParser, fieldName string, kind ValidationKind)

AssertValidationMatrix runs the standard {field, value-class} battery against parser. Closes BC-4 (validation matrix) by exercising the edge values that have historically been silently accepted by IaC plugin parsers.

fieldName is the cfg key the parser reads (e.g., "port", "droplet_ids"). kind selects which battery to run.

Types

type ConfigParser

type ConfigParser func(cfg map[string]any) (any, error)

ConfigParser is a closure that extracts and validates one config field. The parser receives a config map (mirroring the cfg map[string]any shape used across IaC drivers) and returns the parsed value or an error.

AssertValidationMatrix calls the parser repeatedly with edge-case inputs and asserts the parser correctly accepts/rejects each.

type DiffOutputKeyDeclarer

type DiffOutputKeyDeclarer interface {
	DiffReadsOutputKeys() []string
}

DiffOutputKeyDeclarer is an optional interface a ResourceDriver may implement to declare which Outputs[*] keys its Diff implementation reads. AssertDiffPopulatesAllOutputFields uses this to verify the writer side (Create/Read/Update) populates all declared keys.

Plugins typically implement this as a sibling method that returns a static slice of canonical key names — small surface, easy to keep in sync.

WARNING: the returned slice is the source of truth for AssertDiffPopulatesAllOutputFields — drift between this slice and the actual Diff implementation makes the matcher silently vacuous (it'll happily verify a stale, shrinking key set while real Diff reads grow uncovered). Treat additions or removals to the Diff body and this slice as paired commits, ideally enforced by code review per BC-3 in docs/IAC_PLUGIN_REVIEW_CHECKLIST.md.

type TB

type TB interface {
	Helper()
	Fatalf(format string, args ...any)
	Errorf(format string, args ...any)
}

TB is the subset of testing.TB the iaclint matchers use. Accepting an interface (rather than *testing.T) lets the matchers be unit-tested with a mock that captures failures.

type ValidationKind

type ValidationKind int

ValidationKind enumerates the standard {field, value-class} probes used by AssertValidationMatrix. Each kind exercises a battery of edge values that match the bug-class definitions in the project review checklist.

const (
	// KindTCPPort probes 0, -1, 1, 65535, 65536 in BOTH int and float64
	// shapes. The float64 probes cover the gRPC-dispatch coercion path
	// (structpb collapses every numeric to float64), so a single call to
	// AssertValidationMatrix is sufficient for plugins on either dispatch
	// path. Closes BC-4 port-range gap.
	KindTCPPort ValidationKind = iota
	// KindNonNegativeInt probes 0, -1, 1.
	KindNonNegativeInt
	// KindNonEmptyString probes "", "  ", "valid".
	KindNonEmptyString
	// KindStringEnum probes each known value, "" (absent), random string,
	// non-string Go types. The bare constant carries no allowed-values set,
	// so it is NOT directly usable with AssertValidationMatrix — pass
	// WithStringEnumOptions([]string{...}) instead. AssertValidationMatrix
	// fails loudly with that guidance if called with the bare constant.
	KindStringEnum
	// KindIntegerOnlyFloat probes 1.0, 1.9, NaN, Inf. Closes BC-4 fractional-float gap.
	KindIntegerOnlyFloat
)

func WithStringEnumOptions

func WithStringEnumOptions(allowed []string) ValidationKind

WithStringEnumOptions returns a StringEnum kind bound to the given allowed values. Use this instead of the bare KindStringEnum constant when calling AssertValidationMatrix:

iaclint.AssertValidationMatrix(t, parser, "expose",
    iaclint.WithStringEnumOptions([]string{"public", "internal"}))

Note on internal state: each call registers the allowed-values slice in a package-level map keyed by a unique ValidationKind ID. Map entries are not reclaimed after a test exits — practically harmless for `go test` (process exits, OS reclaims memory). If iaclint is ever embedded in a long-running review server, consider threading a *RegistryHandle through the matcher signature instead (deferred to v2).

func (ValidationKind) String

func (k ValidationKind) String() string

String returns the human-readable name of the kind, suitable for test output.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL