Documentation
¶
Overview ¶
Package internal implements the DigitalOcean workflow engine plugin.
Index ¶
- Variables
- func NewDOPlugin() sdk.PluginProvider
- type DOProvider
- func (p *DOProvider) Apply(ctx context.Context, plan *interfaces.IaCPlan) (*interfaces.ApplyResult, error)
- func (p *DOProvider) BootstrapStateBackend(ctx context.Context, cfg map[string]any) (*interfaces.BootstrapResult, error)
- func (p *DOProvider) Capabilities() []interfaces.IaCCapabilityDeclaration
- func (p *DOProvider) Close() error
- func (p *DOProvider) Destroy(ctx context.Context, resources []interfaces.ResourceRef) (*interfaces.DestroyResult, error)
- func (p *DOProvider) DetectDrift(ctx context.Context, resources []interfaces.ResourceRef) ([]interfaces.DriftResult, error)
- func (p *DOProvider) EnumerateByTag(ctx context.Context, tag string) ([]interfaces.ResourceRef, error)
- func (p *DOProvider) Import(ctx context.Context, cloudID string, resourceType string) (*interfaces.ResourceState, error)
- func (p *DOProvider) Initialize(ctx context.Context, config map[string]any) error
- func (p *DOProvider) Name() string
- func (p *DOProvider) Plan(ctx context.Context, desired []interfaces.ResourceSpec, ...) (*interfaces.IaCPlan, error)
- func (p *DOProvider) RepairDirtyMigration(ctx context.Context, req interfaces.MigrationRepairRequest) (*interfaces.MigrationRepairResult, error)
- func (p *DOProvider) ResolveSizing(resourceType string, size interfaces.Size, hints *interfaces.ResourceHints) (*interfaces.ProviderSizing, error)
- func (p *DOProvider) ResourceDriver(resourceType string) (interfaces.ResourceDriver, error)
- func (p *DOProvider) Status(ctx context.Context, resources []interfaces.ResourceRef) ([]interfaces.ResourceStatus, error)
- func (p *DOProvider) SupportedCanonicalKeys() []string
- func (p *DOProvider) ValidatePlan(plan *interfaces.IaCPlan) []interfaces.PlanDiagnostic
- func (p *DOProvider) Version() string
Constants ¶
This section is empty.
Variables ¶
var Version = "dev"
Version is set at build time via -ldflags "-X github.com/GoCodeAlone/workflow-plugin-digitalocean/internal.Version=X.Y.Z"
Functions ¶
func NewDOPlugin ¶
func NewDOPlugin() sdk.PluginProvider
NewDOPlugin returns a new DigitalOcean plugin instance.
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 ¶
func (p *DOProvider) Apply(ctx context.Context, plan *interfaces.IaCPlan) (*interfaces.ApplyResult, error)
Apply executes the plan via wfctlhelpers.ApplyPlan, then runs the DO-specific deferred-update second pass.
PR P-DO TP2: under iacProvider.computePlanVersion: v2 wfctl dispatches directly through wfctlhelpers.ApplyPlan and does not call this method. The implementation here remains for legacy v1 callers (wfctl < v0.21.0 or any in-process embedder of the gRPC plugin) and for the deferred-flush behavior that wfctlhelpers does not yet hoist.
Per-action upsert recovery, JIT substitution, the Replace cascade, and the input-drift postcondition all live in wfctlhelpers.ApplyPlan now — drivers that opt into the upsert recovery path implement interfaces.UpsertSupporter (DO drivers AppPlatform, VPC, Firewall, Database all do; signature: SupportsUpsert() bool). The local upsertSupporter interface previously declared here is no longer needed: its SupportsUpsert() bool method is structurally identical to interfaces.UpsertSupporter, so the existing driver implementations satisfy the canonical interface without code change.
wfctl:skip-iac-codemod
The body intentionally wraps wfctlhelpers.ApplyPlan rather than matching the codemod's canonical single-statement `return wfctlhelpers.ApplyPlan(ctx, p, plan)` shape: the post-helper deferred-update flush below is a DO-plugin-specific regression gate (see provider_deferred_test.go and CHANGELOG entry for staging-deploy-blockers Blocker 2) that wfctlhelpers does not hoist. The skip marker tells the codemod's AssertApplyDelegatesToHelper analyzer this deviation is intentional. When wfctlhelpers grows a deferred-update lifecycle hook, the wrapper can collapse and the marker can drop.
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) is out of scope here: the IaCProvider interface receives only refs, not the parsed declared config, so passing an empty ResourceSpec to driver Diff methods causes false positives (e.g. VPC reads ip_range from spec.Config; AppPlatform canonicalExpose defaults to "public" on an empty spec). Use `wfctl infra plan` for config-drift detection — it has access to the full declared spec and surfaces config drift as update actions.
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) 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 ¶
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 ¶
func (p *DOProvider) Plan(ctx context.Context, desired []interfaces.ResourceSpec, current []interfaces.ResourceState) (*interfaces.IaCPlan, error)
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 (p *DOProvider) RepairDirtyMigration(ctx context.Context, req interfaces.MigrationRepairRequest) (*interfaces.MigrationRepairResult, error)
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) Status ¶
func (p *DOProvider) Status(ctx context.Context, resources []interfaces.ResourceRef) ([]interfaces.ResourceStatus, error)
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:
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.
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.
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