Skip to content

Wire node-policy delete, replace-on-rename for rules, safe defaults, API resync and field alignment, CRUD test harness - #56

Merged
Tzvonimir merged 8 commits into
mainfrom
fix/pulumi-provider-full-pass
Aug 21, 2026
Merged

Wire node-policy delete, replace-on-rename for rules, safe defaults, API resync and field alignment, CRUD test harness#56
Tzvonimir merged 8 commits into
mainfrom
fix/pulumi-provider-full-pass

Conversation

@Tzvonimir

Copy link
Copy Markdown
Contributor

What?

Brings the Pulumi provider back in line with the current DAKR API and fixes every critical found in the provider audit: pulumi destroy now actually deletes node policies and disables node-policy targets, renaming a workload rule replaces it instead of orphaning it, enabled defaults to true on targets as documented, out-of-band deletions drop from state instead of failing refresh, and string enums are validated instead of silently dropped. Protos are resynced to services HEAD, three deleted HPA fields are removed, the full set of new backend fields (v1 policies, v2 rules, targets, node policies) is exposed, and schema.json plus the Node/Python/Go SDKs are regenerated. Also removes a scratch file from the repo root that contained a live bearer token and gitignores the pattern.

Why?

The provider had drifted ~2 months behind the backend and had several bugs with real blast radius: destroy left Karpenter NodePools provisioning nodes, an omitted enabled silently created a disabled target while the docs said "default true", the retry interceptor could duplicate resources by retrying Creates, and hpaRule.targetUtilization/primaryMetric were silent no-ops (the backend deleted those fields in July). Terraform got the same treatment in devzero-inc/terraform-provider-devzero#28; this PR gives Pulumi users parity.

How?

  • Breaking: hpaRule.targetUtilization, targetMemoryUtilization, primaryMetric are removed (already ignored server-side); hpaRule.metrics is the replacement. Cluster updates also no longer auto-rotate the token when state has none (post-import) — rotating on an unrelated rename silently invalidated the running agent's credential.
  • WorkloadRule identity: the upsert API keys rules on (clusterId, namespace, kind, name), so Update on a renamed rule created a second rule while state kept the old ID. A CustomDiff now marks those four fields as replace-on-change.
  • disabled on workload rules is create-only in the upsert API; updates route through the ToggleWorkloadRuleDisabled RPC.
  • Node policy target destroy disables the target (no delete RPC exists); clusterIds validates to exactly one entry because the server rejects more.
  • Strict enums (validation.go): the API silently ignores unknown enum strings (an unknown trigger is dropped, an unknown kind matches every workload), so Create/Update now fail fast with the valid-values list. Selector operators gain Gt/Lt.
  • Retries only apply to Get*/List* RPCs — CreateNodePolicies without a client-set ID always inserts, so a retried Create duplicated resources.
  • Refresh drift: float32 values read back via shortest-round-trip conversion (0.7 stays 0.7, not 0.699999988079071); omitted booleans no longer materialize as &false; HPA metric metadata strips the serverAddress/query keys the backend folds in.
  • Node-policy reads skip the read-only source == "cluster" virtual policies ListNodePolicies mixes in.
  • Makefile VERSION falls back to 0.0.0-dev (valid semver) — pulumi package get-schema rejected the old dev fallback, which broke gen-schema/gen-sdk in any clone without tags, including CI's fetch-depth: 1 checkout.

Testing?

  • fake_server_test.go: an in-memory connect server (httptest + generated handlers) mimicking dakr semantics — upsert keys, NotFound codes, target invariants, fields.disabled rejection on update, virtual cluster policies — driving real Create/Read/Update/Delete lifecycles. Covers out-of-band deletion, delete idempotency, disable-on-destroy, the disabled-toggle path, exact float round-trips, and the enabled default.
  • Existing unit tests updated to the new semantics (e.g. multi-cluster targets now assert rejection; NotFound reads assert drop-from-state; a new test asserts mutating RPCs are never retried and that updates never rotate tokens).
  • go build ./..., go vet ./..., full suite green; the regenerated Go SDK compiles.

Anything Else?

The leaked bearer token that was in curl.txt should be rotated — deleting the file doesn't invalidate it. Deferred to a follow-up (same as the Terraform PR): gcp/oci node classes, kedaScaledObject, jvmHeapRule; plus release-workflow publish ordering and stale example version pins.

…s; remove leaked curl.txt

- make proto now also syncs hpa_connector.proto/pb.go (recommendation.proto
  imports it upstream).
- Remove workloadRule hpaRule.targetUtilization / targetMemoryUtilization /
  primaryMetric: dakr reserved HPARuleConfig fields 4/5/12 on 2026-07-07 and
  dropped the columns, so the server ignored and never echoed them. Use
  hpaRule.metrics (CPU/Memory/external triggers) instead. BREAKING for
  programs setting them.
- Delete the untracked curl.txt scratch file that contained a live bearer
  token and gitignore the pattern. ROTATE THAT TOKEN.
…ts, NotFound handling, strict enums

- NodePolicy.Delete now calls DeleteNodePolicy (the RPC exists and cascades
  targets server-side); it was a state-only no-op that left Karpenter
  NodePools running after 'pulumi destroy'. NodePolicy.Read also skips
  read-only source=="cluster" virtual policies that ListNodePolicies mixes in.
- NodePolicyTarget.Delete disables the target via UpdateNodePolicyTarget (no
  delete RPC exists server-side) instead of silently leaving it enabled;
  clusterIds is validated to exactly one entry (the server rejects more).
- WorkloadRule implements CustomDiff: changing clusterId/namespace/kind/name
  now replaces the resource. Update previously upserted a second rule
  server-side (the API keys on that 4-tuple) while state kept the old ID.
- enabled on NodePolicyTarget and WorkloadPolicyTarget is now *bool with a
  default of true — omitting it used to silently create a DISABLED target
  while the docs said 'default true'.
- Every Read returns an empty response on CodeNotFound (or not-found-in-list)
  so out-of-band deletions drop from state instead of failing refresh; every
  Delete tolerates CodeNotFound.
- String enums (actionTriggers, detectionTriggers, kindFilter, selector
  operators, primaryMetric) are validated up front — the API silently drops
  unknown values, which previously produced policies with no triggers or
  targets matching every kind. Selector operators gain Gt/Lt mappings.
- The retry interceptor now retries only idempotent (Get*/List*) RPCs: a
  transient failure on a Create whose first attempt landed server-side
  produced duplicate resources.
- float32 values read back from the API convert via shortest-round-trip
  formatting (0.7 stays 0.7, not 0.699999988079071) — kills the perpetual
  diff after 'pulumi refresh'. enablePmaxProtection no longer materializes
  as &false when omitted.
- Cluster.Update no longer silently rotates the cluster token when state has
  none (post-import): rotating on an unrelated change invalidated the running
  agent's credential. Imported clusters keep an empty token. Update also uses
  the same CustomName->Name fallback as Read.
… policies with the current API

workloadPolicy (v1) new inputs: enableInPlaceVerticalScaling,
allowInPlaceMemoryLimitDecrease, pdbEnabled, cpu/memory (limit)
floor/ceiling percents, 9 JVM heap knobs, vertical requestUseRss/limitUseRss
(memory only), horizontal networkTargetThroughputBytesPerSec,
targetMemoryUtilization, compositeFormula, scaleDownCooldownSeconds.

workloadPolicyTarget: annotationSelector, workloadNamesNotIn,
kindFilterNotIn (validated); nodeGroupNames marked deprecated (unused
upstream).

workloadRule (v2): disabled (create via upsert; updates go through the
ToggleWorkloadRuleDisabled RPC since the upsert rejects it),
lookbackPeriodSeconds, per-axis initialRequest/floorPercent/ceilingPercent/
initialLimit/limitFloorPercent/limitCeilingPercent/requestUseRss/limitUseRss
(also on container rules), hpaRule.metrics connectorId; metric metadata
strips the serverAddress/query keys the backend folds in.

nodePolicy: startupTaints, zonalShift (AWS only), instanceLocalNvme (AWS
only), cloudProviderId, masterOverrideRoleName, azure.imageVersion
(operator >= 1.8.4).
In-memory connect server (httptest + generated handlers with Unimplemented*
embeds) mimicking the dakr semantics that matter to CRUD correctness: upsert
keys, NotFound codes, node-policy-target invariants (empty target_id on
create, at most one cluster), fields.disabled rejection on workload-rule
update, and the virtual source=="cluster" policies mixed into
ListNodePolicies.

Lifecycle tests drive each resource's real Create/Read/Update/Delete against
it and assert: create round-trips (ids, nested blocks, taints, exact float
percentiles), refresh after out-of-band deletion drops from state, node
policy delete actually deletes and is idempotent, node policy target destroy
disables the target, flipping workloadRule.disabled goes through
ToggleWorkloadRuleDisabled, and omitting enabled creates an ENABLED target.
… fallback

- Makefile VERSION falls back to 0.0.0-dev instead of dev — 'pulumi package
  get-schema' rejects non-semver versions, which broke gen-schema/gen-sdk in
  any clone without tags (including the gen-sdk workflow's fetch-depth:1
  checkout).
- schema.json + nodejs/python/go SDKs regenerated: picks up the new
  workloadPolicy/workloadRule/nodePolicy/target fields, the enabled defaults,
  and drops the removed hpaRule.targetUtilization/targetMemoryUtilization/
  primaryMetric fields.
Comment thread provider/pkg/resources/node_policy.go
Comment thread provider/pkg/provider/client.go
Comment thread provider/pkg/resources/validation.go
… skip, retry guard, kind validation, TS 5.2 consumer fix

- emergencyResponse float32 fields (oomMemoryMultiplier, cpuThrottling*)
  read back via shortest-round-trip conversion like every other float — the
  raw float64 cast produced 0.800000011920929-style refresh drift.
- NodePolicy.Read skips exactly the known-virtual source ("cluster")
  instead of any unknown source, so a future backend source tag cannot
  silently orphan a managed policy.
- retryInterceptor passes straight through when maxAttempts < 1 instead of
  returning (nil, nil).
- workloadRule.kind validated against the supported kinds
  (Deployment/StatefulSet/DaemonSet/CronJob/Job) — the API accepts any
  string and a typo would create a rule that never matches.
- Stale cluster token docs removed (Describe + Update comment still said
  tokens rotate on import).
- NodeJS consumer test pins @types/node to ^18 via npm overrides: the
  latest @pulumi/pulumi pulls an @types/node that needs TS >= 5.6, which
  has been failing the TS 5.2 matrix on main's nightly since Aug 9.
…arer names

- isNotFound(err) collapses the repeated connect.CodeOf NotFound checks
  across all Read/Delete paths; truePtr() collapses the proto-bool to
  optional-pointer read-back idiom.
- f32 renamed to f32to64 (the old name read as the opposite direction).
- fakeIdempotentRequest renamed to fakeProcedureRequest — it also backs
  newMutatingRequest.
…arget lifecycle

- roundtrip_test.go: a reflection-based populator fills EVERY pulumi-tagged
  field of every args struct (enum-valued strings get valid members), then
  each resource converts args -> proto -> args -> proto and requires the two
  protos to be identical — so a newly added field that stops round-tripping
  in either direction fails the suite. Spot-checks assert the specific new
  field groups (zonal shift, startup taints, JVM knobs, floor/ceiling bounds,
  RSS flags, HPA extras, lookback/disabled, connectorId) survive the cycle.
  A guard test fails if a future field type isn't handled by the populator.
- WorkloadRule.Diff unit tests: identity fields (clusterId/namespace/kind/
  name) require replacement, non-identity changes update in place, and a
  no-change plan reports no changes.
- Fake server gains the WorkloadPolicyTarget handlers plus a full lifecycle
  test (create with annotationSelector/notIn filters, enabled default,
  update propagation, drop-from-state after out-of-band deletion, tolerant
  delete).
@gitar-bot

gitar-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 3 resolved / 3 findings

Aligns the Pulumi provider with current API semantics, implementing real node-policy deletes, replace-on-rename for rules, safe target defaults, API resyncs, and a CRUD test harness. Addressed findings for NodePolicy.Read source filtering, retryInterceptor maxAttempts guards, and WorkloadRule.Kind validation.

✅ 3 resolved
Edge Case: NodePolicy.Read blacklists source, may drop provider-managed policies

📄 provider/pkg/resources/node_policy.go:461-475
Read skips any policy whose Source is non-empty and not exactly "dakr". This is a blacklist keyed on one known bad value ("cluster"). If the backend ever tags API/IaC-created node policies with a different non-empty source string (e.g. "pulumi", "api"), Read would skip the managed policy, return an empty response, and the engine would drop it from state — causing a spurious re-create and a duplicate NodePool. Consider whitelisting the known-managed sources (empty and "dakr") explicitly and only skipping the specific virtual source(s) you intend to exclude (source == "cluster"), so an unexpected source value doesn't silently orphan a resource.

Bug: retryInterceptor returns (nil,nil) if maxAttempts <= 0

📄 provider/pkg/provider/client.go:62-76
If maxAttempts is ever <= 0 the for loop body never executes and the interceptor falls through to return nil, nil, handing callers a nil response with a nil error, which will panic on the first field access. It is currently hardcoded to 3 at the call site so this is not reachable today, but the fallthrough is a latent trap. Guard maxAttempts (clamp to >= 1) or return an explicit error instead of nil,nil.

Quality: WorkloadRule.Kind not validated against valid kinds

📄 provider/pkg/resources/validation.go:96-100 📄 provider/pkg/resources/workload_rule.go:255-262
validateWorkloadRuleArgs only validates actionTriggers and detectionTriggers; the Kind field (documented as one of Deployment/StatefulSet/DaemonSet/CronJob/Job) is passed straight through to UpsertManualWorkloadRule. Since the stated goal of the strict-enum work is to fail fast on unknown enum strings the API silently mishandles, and WorkloadPolicyTarget.kindFilter is validated against validKinds, consider validating WorkloadRule.Kind the same way so a typo'd kind fails with an actionable message rather than creating a rule the backend can't match.

Was this helpful? React with 👍 / 👎 | Gitar

@Tzvonimir
Tzvonimir merged commit bb56674 into main Aug 21, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants