internal

package
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: May 17, 2026 License: MIT Imports: 35 Imported by: 0

Documentation

Overview

Package internal — typed pb.IaCProvider*Server implementation.

Per the strict-contracts force-cutover plan (Task 9): docs/plans/2026-05-10-strict-contracts-force-cutover.md, rev5.

doIaCServer is the SERVER side of the typed IaC contract. It satisfies pb.IaCProviderRequiredServer plus the six optional pb.IaCProvider*Server interfaces by delegating each typed RPC to the matching Go-interface method on the underlying *DOProvider. Marshalling between pb messages and the Go interfaces.* types lives in marshalling helpers below; the shape mirrors the inverse direction implemented by cmd/wfctl/iac_typed_adapter.go in workflow itself, so a single canonical pb<->Go mapping holds across both ends of the gRPC bridge.

Hard invariants (per cycle 4 force-cutover §Acceptance):

  • NO structpb.Struct, NO Any.UnmarshalTo on the wire — provider- specific config / outputs cross as JSON bytes (config_json, outputs_json).
  • REQUIRED service methods MUST be implemented; the SDK type-assert in sdk.RegisterAllIaCProviderServices fails at plugin startup otherwise.
  • OPTIONAL services are auto-registered when satisfied at the Go interface level — no manual Register* call required.

Package internal — typed pb.ResourceDriverServer implementation (Task 11 of the strict-contracts force-cutover plan, docs/plans/2026-05-10-strict-contracts-force-cutover.md, rev5).

The pb.ResourceDriverServer surface is a single gRPC service that dispatches per-resource-type CRUD by carrying resource_type on every RPC. This file extends *doIaCServer (declared in iacserver.go) with the 9 RPC methods Required by the interface; routing happens by looking up the per-type interfaces.ResourceDriver via *DOProvider.ResourceDriver(resourceType) and forwarding the call.

Once *doIaCServer satisfies pb.ResourceDriverServer at the Go type level, sdk.RegisterAllIaCProviderServices auto-registers it (iacserver.go: type-assertion gate). No manual Register* call needed.

Package internal — typed pb.IaCStateBackendServer implementation.

Per decisions/0035 (one type carries both concerns), doIaCServer ALSO serves the typed IaC state-backend contract: it persists IaC state via a SpacesIaCStateStore (ported from workflow core) and answers ListBackendNames with the single backend name "spaces".

Hard invariants (strict-contracts force-cutover):

  • NO structpb.Struct on the wire — the free-form Outputs / Config map[string]any fields of IaCState cross as JSON bytes (outputs_json, config_json), converted locally via encoding/json below.
  • The store is lazily constructed: the host delivers the iac.state module config via the Configure RPC (decisions/0036); until then GetState/etc. return a clear FailedPrecondition error rather than panicking.

Index

Constants

View Source
const (

	// EnvDebugAPI is the environment variable that enables API call logging.
	// Set to any non-empty value (e.g. WFCTL_DEBUG_API=1) to activate.
	EnvDebugAPI = "WFCTL_DEBUG_API"
)

Variables

View Source
var ErrApplyV1Removed = errors.New("DOProvider.Apply: v1 dispatch removed since DO v1.4.0 (workflow#695 Phase 2.5 + Phase 3); upgrade wfctl to v0.55.0+ for v2 dispatch via wfctlhelpers.ApplyPlanWithHooks + IaCProviderFinalizer.FinalizeApply")

ErrApplyV1Removed is the sentinel returned by DOProvider.Apply when called post-Phase-3 cleanup. Callers can errors.Is for diagnostic classification.

View Source
var Version = "dev"

Version is set at build time via -ldflags "-X github.com/GoCodeAlone/workflow-plugin-digitalocean/internal.Version=X.Y.Z". Lived in plugin.go before the strict-contracts cutover; moved here when the legacy doPlugin / NewDOPlugin entrypoint was deleted (Task 9).

Functions

func NewIaCServer added in v1.0.0

func NewIaCServer() *doIaCServer

NewIaCServer is the package entrypoint used by cmd/plugin/main.go. It constructs a fresh *DOProvider and wraps it in the typed pb.IaCProvider* server surface. The returned value is suitable to pass to sdk.ServeIaCPlugin; the SDK auto-registers every typed gRPC service the server satisfies via Go type-assertion at plugin startup.

Types

type DOProvider

type DOProvider struct {
	// contains filtered or unexported fields
}

DOProvider implements interfaces.IaCProvider for DigitalOcean.

func NewDOProvider

func NewDOProvider() *DOProvider

NewDOProvider creates an uninitialised DOProvider.

func (*DOProvider) Apply

Apply returns ErrApplyV1Removed unconditionally. v1 dispatch was removed in DO v1.4.0 (workflow#695 Phase 3 cleanup). wfctl bypasses this method when ComputePlanVersion="v2" is declared in Capabilities; deferred-flush behavior moved to doIaCServer.FinalizeApply via IaCProviderFinalizer RPC. Reaching this method indicates a misconfigured caller (in-process embedder using a pre-Phase-2.5 wfctl tag, OR gRPC consumer that opted out of v2 dispatch). Stub preserves interfaces.IaCProvider contract per ADR 0024; interface segregation deferred to separate refactor design.

func (*DOProvider) AppsClient added in v1.0.12

func (p *DOProvider) AppsClient() steps.IaCLogsClient

AppsClient returns the godo Apps service for use by pipeline steps. Returns nil when the provider has not yet been initialized (Initialize not called).

func (*DOProvider) AppsScaleClient added in v1.0.12

func (p *DOProvider) AppsScaleClient() steps.AppsScaleClient

AppsScaleClient returns the godo Apps service as steps.AppsScaleClient for use by step.iac_scale. Returns nil when Initialize has not been called.

func (*DOProvider) BootstrapStateBackend added in v0.7.4

func (p *DOProvider) BootstrapStateBackend(ctx context.Context, cfg map[string]any) (*interfaces.BootstrapResult, error)

BootstrapStateBackend ensures the DO Spaces state bucket exists. It is idempotent: if the bucket already exists it returns the metadata without error. Providers that do not manage a state backend should return (nil, nil).

Required cfg keys: "bucket", and credentials supplied as either camelCase ("accessKey"/"secretKey", e.g. BMW infra.yaml) or snake_case ("access_key"/"secret_key"); camelCase is checked first. Optional cfg keys: "region" (falls back to the provider's configured region, then "nyc3" as the ultimate default).

Returns a BootstrapResult with:

  • Bucket, Region, Endpoint fields populated
  • EnvVars: WFCTL_STATE_BUCKET and SPACES_BUCKET set to the bucket name

func (*DOProvider) Capabilities

func (p *DOProvider) Capabilities() []interfaces.IaCCapabilityDeclaration

Capabilities returns the resource types this provider supports.

func (*DOProvider) Close

func (p *DOProvider) Close() error

Close is a no-op; the godo client has no persistent connection to close.

func (*DOProvider) Destroy

func (p *DOProvider) Destroy(ctx context.Context, resources []interfaces.ResourceRef) (*interfaces.DestroyResult, error)

Destroy deletes the given resources.

func (*DOProvider) DetectDrift

func (p *DOProvider) DetectDrift(ctx context.Context, resources []interfaces.ResourceRef) ([]interfaces.DriftResult, error)

DetectDrift checks for ghost resources (state entries whose cloud counterpart no longer exists) and classifies each ref as Ghost, InSync, or Unknown.

  • errors.Is(err, interfaces.ErrResourceNotFound) → DriftClassGhost (Drifted=true; state has the resource but cloud returns 404). Caller may prune state via wfctl infra apply --refresh.
  • any other Read error → propagate (transient API failure; do NOT classify as drift).
  • Read succeeds → DriftClassInSync (Drifted=false).
  • driver registry lookup fails → DriftClassUnknown (Drifted=true; operator must investigate).

Config-drift detection (DriftClassConfig) requires the desired spec and is available via DetectDriftWithSpecs, which is called by invokeProviderDetectDrift when the workflow caller injects specs from state's recorded outputs.

Production-safety invariant: only genuine 404s (wrapped with interfaces.ErrResourceNotFound) trigger the ghost path. Rate-limit, auth, or network errors propagate unchanged so callers cannot accidentally prune state on transient failures.

func (*DOProvider) DetectDriftWithSpecs added in v0.10.5

func (p *DOProvider) DetectDriftWithSpecs(ctx context.Context, resources []interfaces.ResourceRef, specs map[string]interfaces.ResourceSpec) ([]interfaces.DriftResult, error)

DetectDriftWithSpecs is the spec-injection variant of DetectDrift. It runs the same ghost/transient/unknown classification as DetectDrift, and when the caller supplies a desired ResourceSpec for a ref (keyed by ref.Name), it additionally calls driver.Diff to detect config-level drift (DriftClassConfig).

Classification rules (per ref):

  • driver lookup fails → DriftClassUnknown (Drifted=true).
  • Read returns ErrResourceNotFound → DriftClassGhost (Drifted=true); spec is ignored because the resource does not exist in the cloud.
  • Read returns any other error → propagate; discard accumulated results.
  • Read succeeds and no spec provided → DriftClassInSync (Drifted=false).
  • Read succeeds and spec provided:
  • Diff error → propagate; discard accumulated results.
  • Diff reports NeedsUpdate → DriftClassConfig (Drifted=true); Fields lists the changed config paths.
  • Diff reports no changes → DriftClassInSync (Drifted=false).

A nil or empty specs map is equivalent to calling DetectDrift.

func (*DOProvider) EnumerateAll added in v0.14.0

func (p *DOProvider) EnumerateAll(ctx context.Context, resourceType string) ([]*interfaces.ResourceOutput, error)

EnumerateAll lists every resource of resourceType in the DO account, regardless of tag. Used for resource types that don't support tags (e.g. spaces_key, where the DO API has no tag surface). Paginates transparently using godo.Response.Links so callers don't have to.

Returns []*ResourceOutput per the workflow EnumeratorAll contract (interfaces/iac_provider.go) — full metadata is populated so downstream callers (wfctl infra audit-keys, prune, rotate-and-prune) can filter without re-reading each resource individually.

Implements interfaces.EnumeratorAll (workflow v0.26.0+; opt-in interface, type-asserted by the host's IaCProvider proxy).

func (*DOProvider) EnumerateByTag added in v0.10.1

func (p *DOProvider) EnumerateByTag(ctx context.Context, tag string) ([]interfaces.ResourceRef, error)

EnumerateByTag implements the opt-in interfaces.Enumerator interface.

Lists DO resources tagged with the given tag and returns them as interfaces.ResourceRef values keyed on the same (Name, Type, ProviderID) tuple that the corresponding ResourceDriver(type).Delete consumes. Used by `wfctl infra cleanup --tag <name>` to drive tag-scoped teardown.

Implementation strategy:

  • Tags.Get is queried first as a probe: if the tag itself does not exist in the DO account (404), the result is an empty slice (not an error). This matches operator expectation that running cleanup against a tag that has never been used reports "no resources" rather than failing. A 200 here indicates the tag is known to DO, but the per-resource queries below are what actually populate the result slice — Tags.Get's own response only carries counts + last-tagged metadata, not a full list of tagged resources.
  • Droplets uses the native ListByTag endpoint.
  • Volumes and Databases do not expose a tag-filter parameter, so the full list is fetched and filtered client-side on the Tags slice.
  • Other DO resource types (load balancers, k8s clusters, app platform, etc.) either do not support tags or have not yet been wired here. The cleanup subcommand documents per-provider coverage in workflow/docs/WFCTL.md `#### infra cleanup`.

The contract for the returned ResourceRef.ProviderID matches what each ResourceDriver expects on Delete: a string-formatted droplet ID for droplets, the volume ID for volumes, and the database cluster UUID for databases. Name is the user-facing resource name. Type is the canonical `infra.<kind>` matching DOProvider's driver registration.

func (*DOProvider) Import

func (p *DOProvider) Import(ctx context.Context, cloudID string, resourceType string) (*interfaces.ResourceState, error)

Import brings an existing cloud resource under management.

func (*DOProvider) Initialize

func (p *DOProvider) Initialize(ctx context.Context, config map[string]any) error

Initialize configures the godo client using the provided config map. Required: "token". Optional: "region" (default "nyc3"), "spaces_access_key", "spaces_secret_key".

The provided ctx is threaded into oauth2.NewClient so callers that inject a custom *http.Client via oauth2.HTTPClient (tests, custom transports, proxy configurations) flow through to the godo client. Per-request cancellation remains controlled by the ctx passed to each subsequent driver call — godo wraps each request in ctx via http.Request.WithContext.

addresses workflow-plugin-digitalocean#62: prior implementation hardcoded context.Background(), silently dropping any HTTPClient injection.

func (*DOProvider) Name

func (p *DOProvider) Name() string

func (*DOProvider) Plan

Plan computes the set of actions needed to reach the desired state by delegating to the canonical platform.ComputePlan helper. The helper dispatches per-resource Diff in parallel, classifies replace vs update (including the ForceNew → replace promotion), emits creates/deletes in dependency-correct order, and consults the diff cache.

The 2-statement form (call + pointer-bridge return) is mandated by the W-Refactor / iac-codemod analyzer (cmd/iac-codemod AssertPlanDelegatesToHelper); see workflow CHANGELOG entry referencing platform.ComputePlan as the canonical target for v2 IaC providers.

addresses workflow-plugin-digitalocean#63: the prior hand-rolled body duplicated ComputePlan's classification logic and silently dropped the ForceNew → replace upgrade path (only NeedsReplace was honored).

func (*DOProvider) RepairDirtyMigration added in v0.7.13

func (*DOProvider) ResolveSizing

func (p *DOProvider) ResolveSizing(resourceType string, size interfaces.Size, hints *interfaces.ResourceHints) (*interfaces.ProviderSizing, error)

ResolveSizing maps abstract size tiers to DigitalOcean SKUs.

func (*DOProvider) ResourceDriver

func (p *DOProvider) ResourceDriver(resourceType string) (interfaces.ResourceDriver, error)

ResourceDriver returns the driver for the given resource type.

func (*DOProvider) RevokeProviderCredential added in v0.10.7

func (p *DOProvider) RevokeProviderCredential(ctx context.Context, source string, credentialID string) error

RevokeProviderCredential satisfies interfaces.ProviderCredentialRevoker for source "digitalocean.spaces". credentialID is the access_key_id of the OLD DO Spaces key to revoke. Used by `wfctl infra bootstrap --force-rotate` to invalidate the old key after minting its replacement (see ADR 0012).

HTTP response handling:

  • 204 No Content → success
  • 404 Not Found → treated as success (key already gone)
  • 401/403 → auth error, propagated as fatal
  • 5xx → transient error, propagated to caller (caller logs + continues)

func (*DOProvider) Status

Status returns the live status of the given resources.

func (*DOProvider) SupportedCanonicalKeys added in v0.7.0

func (p *DOProvider) SupportedCanonicalKeys() []string

SupportedCanonicalKeys returns the canonical IaC config keys that this DO provider currently maps. Keys in doUnsupportedCanonicalKeys are excluded until their Task implementation lands (see comments there).

func (*DOProvider) ValidatePlan added in v0.10.0

func (p *DOProvider) ValidatePlan(plan *interfaces.IaCPlan) []interfaces.PlanDiagnostic

ValidatePlan implements interfaces.ProviderValidator (W-4): a read-only, no-remote-call cross-resource constraint check that runs at `wfctl infra align` time before any cloud API call. Diagnostics surface as PlanDiagnosticError|Warning|Info with severity-driven exit-code mapping (Error always fails align; Warning fails only under --strict; Info never affects exit).

PR P-DO TP3 — first DO region-constraint pass:

  1. App Platform `infra.container_service` MUST use one of DO App Platform's supported region GROUPS (nyc, ams, fra, sfo, sgp, syd, tor, blr, lon). If `region` is set to a Droplet/VPC zone slug (nyc1, nyc3, sfo2, sfo3 …) it is rejected — App Platform configures by group, not zone.

  2. Droplet/VPC/Volume zone-bound resources MUST use a zone slug (nyc1, nyc2, nyc3, sfo2, sfo3, ams2, ams3, fra1, sgp1, lon1, tor1, blr1, syd1) — bare group slugs (nyc, sfo) are rejected for these types because the DO API for these resources requires a specific zone.

  3. App Platform with `vpc_ref` (referencing a VPC by name in the same plan) — the referenced VPC's region zone MUST belong to the App Platform's region group (e.g., AppPlatform region nyc → VPC in nyc1 OR nyc3; AppPlatform region sfo → VPC in sfo2 OR sfo3). Mismatch is the recurring "App Platform in nyc cannot reach VPC in sfo3" production bug (root-cause issue D from the conformance design).

Future extensions (deferred follow-ups): database/cache zone slugs, load balancer zone matching against attached droplets, registry regional restrictions.

func (*DOProvider) Version

func (p *DOProvider) Version() string

Directories

Path Synopsis
Package drivers contains ResourceDriver implementations for each DigitalOcean resource type.
Package drivers contains ResourceDriver implementations for each DigitalOcean resource type.
Package statebackend provides S3-compatible IaC state storage backends for the DigitalOcean plugin.
Package statebackend provides S3-compatible IaC state storage backends for the DigitalOcean plugin.
Package steps provides pipeline step factories for the DigitalOcean plugin.
Package steps provides pipeline step factories for the DigitalOcean plugin.

Jump to

Keyboard shortcuts

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