feat: datumctl compute plugin — deploy and manage workloads from the CLI - #113
Merged
Conversation
Workloads targeting a city location are now automatically routed to the correct physical site via a Karmada-based federation layer. Each POP cell operates independently, instance health is surfaced back to the control plane in real time, and the platform remains available even when parts of the control plane are temporarily unreachable. Controllers added: - WorkloadDeploymentFederator: replicates WDs into Karmada and manages PropagationPolicies per city code - InstanceProjector: mirrors Instance write-backs from Karmada into the project namespace on the control plane ResourceInterpreterCustomization deployed at config time teaches Karmada how to aggregate replica counts and conditions across POP cells. Operator flags --enable-management-controllers and --enable-cell-controllers allow each deployment to opt into only the controllers it needs. Includes a 6-test Chainsaw e2e suite covering federation, deletion cascade, propagation policy lifecycle, instance projection, instance write-back, and the full end-to-end chain. Resolves #85 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…edge Introduces management-plane and cell overlay paths to the compute OCI artifact so the infra repo can deploy compute-manager in the correct mode for each tier of the federation architecture. The management-plane overlay deploys compute-manager with only WorkloadDeploymentFederator and InstanceProjector enabled, connected to the Karmada downstream control plane via projected ServiceAccount token auth. The cell overlay deploys compute-manager with only WorkloadDeploymentReconciler and InstanceReconciler enabled, with no downstream connection or webhook server. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ts for webhook TLS Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the hardcoded datum-control-plane ClusterIssuer from the csi-webhook-cert component. DNS names stay since they are fixed by the service name and namespace. Each consuming overlay now supplies the issuer via a strategic merge patch, allowing different environments to use different cert issuers without forking the component. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each WorkloadDeployment is routed to exactly one cell cluster via its PropagationPolicy, so aggregation across multiple members is not needed. Replace the summing logic with a direct pass-through of the single member's status. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The cert issuer name is environment-specific configuration that belongs in the infra repo, not the compute overlay. The infra repo's base manager patch already owns the full webhook-server-tls volume definition including the issuer. Consumers deploying outside infra must patch the issuer in their own overlay. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…moval dev: inline self-signed Issuer + Certificate for host.docker.internal, replace kustomize replacements block with direct annotation patch, remove Certificate-patching from webhook_patch.yaml, and clear webhookServer secretRef from config.yaml. single-cluster: replace cert-manager Certificate approach with the csi-webhook-cert component, matching the main branch overlay. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The WorkloadReconciler watches networkingv1alpha.Network objects, which requires the network-services-operator CRDs to be installed. Cell clusters don't have those CRDs, causing the manager to crash on startup. Gate the WorkloadReconciler behind enableManagementControllers so it only runs where the Network CRDs are present. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracts server config file reading and decoding into a dedicated loadServerConfig helper, reducing main's cyclomatic complexity from 31 to 29 to satisfy the gocyclo linter limit of 30. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Milo's authorization webhook uses Extra claims on the admission request (iam.miloapis.com/parent-name, iam.miloapis.com/parent-type, etc.) to resolve the correct project-scoped policy binding. Dropping them caused the SAR to return Allowed=false even for users with networks.use, because the authorizer couldn't locate the binding without the project context. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
metricRules belongs under spec.quota, not spec.billing. The field is not declared in the ServiceBillingConfig schema, causing Flux dry-run failures in staging with: .spec.billing.metricRules: field not declared in schema
Previously, InstanceReconciler wrote ResourceClaim objects against the local deployment cluster via managementCluster.GetClient(). Those claims were never seen by the Milo quota system, leaving every Instance in QuotaGranted=Unknown indefinitely. This change routes claim creation and deletion to the correct Milo project control plane for each instance using a new ProjectQuotaClientManager that builds per-project REST clients by rewriting the host path — mirroring the URL construction already used by the milomulticluster provider. The management-cluster claim watch is replaced with a multicluster Watches call so that grant/denial status changes in project control planes re-trigger instance reconciles. Claims are stamped with a source-cluster label (discovery.clusterName) so each edge controller only reacts to the claims it created. Co-Authored-By: Claude <claude@anthropic.com>
The admission webhook requires that all metrics referenced in spec.quota.limits[].metric and spec.quota.metricRules[].metricCosts match a name declared in spec.metrics[]. The four quota-tracking metrics (workloads, instances, vcpus, memory) were missing from spec.metrics[], causing the webhook to reject the resource.
…o cell setup Controller flags --enable-management-controllers and --enable-cell-controllers now default to false so kustomize components must explicitly opt in, rather than both groups running by default. This prevented the management-plane deployment from crashing when discovery.clusterName was unset — that field is only required by the InstanceReconciler (a cell controller), so the validation now lives in InstanceReconciler.SetupWithManager instead of initializeClusterDiscovery. Also adds cell-controllers and management-controllers components to the single-cluster overlay, which was silently running with no controllers enabled. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…scovery The rebase during cherry-pick propagation introduced a mixed state where cmd/main.go had the edgeClusterName/projectRestConfig return values partially reverted. This cleans up the function signature and call sites to be consistent, while keeping the validation removed from initializeClusterDiscovery (it belongs in InstanceReconciler.SetupWithManager per the original fix intent). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… RBAC The workload-deployment-federator calls ensureDownstreamNamespace before federating WorkloadDeployment resources, but the compute-manager ClusterRole was missing core-group namespace permissions, causing every reconcile to fail with a forbidden error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Workload scheduling and admission now consult LocationBinding objects (project-scoped, created by the service catalog) rather than the global Location list. This ensures consumers only see locations that are both healthy and available to their specific project. Also upgrades network-services-operator and milo dependencies to versions that introduce LocationBinding and address multicluster-runtime v0.23 API changes (ClusterName type, ProviderRunnable Start lifecycle, generic webhook builder). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ources WorkloadDeploymentReconciler creates and owns NetworkBinding and SubnetClaim resources, and watches Location, NetworkContext, and Subnet. InstanceReconciler watches ResourceClaim for quota. Neither was granted the necessary ClusterRole rules, causing watch failures on cell clusters. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
From the cell cluster's perspective, Karmada is upstream (the federation control plane), not downstream. Rename the flag, env var, and related variables throughout to reflect the actual relationship. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…viderRunnable fix Points go.miloapis.com/milo to the feature branch commit that implements multicluster.ProviderRunnable on the Milo provider, enabling the mc manager to auto-call provider.Start() and set p.mcAware so project clusters can be registered. Without this, p.mcAware was always nil and every project reconcile logged "Multicluster manager not yet started" forever. Also removes the & from ResourceRef in ResourceClaimSpec — the feature branch has ResourceRef as a value type, not a pointer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove non-existent QuotaRestConfig() call and fix SetupWithManager argument count; pass nil quota config to skip quota enforcement for now. Single-tenant cell mode uses namespace-as-project-id and the fixed 'single' cluster name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wires up Milo ResourceClaim-based quota accounting for cells running in single-cell discovery mode (mode: single), where the multicluster ClusterName is always "single" rather than the Milo project name. Key changes: - Add QuotaKubeconfigPath config field and QuotaRestConfig() method so quota REST config can be configured independently of discovery mode. Returns (nil, nil) when neither path is set, disabling quota rather than silently targeting the local apiserver. - Add projectIDForInstance and clusterNameForProject func fields to InstanceReconciler. In single mode, project ID is derived from instance.Namespace; the watch map func always enqueues ClusterName "single" rather than the project namespace, avoiding ErrClusterNotFound on every quota-grant event. - Guard ResourceClaim watch map func against claims with empty ResourceRef to prevent a nil-dereference panic when a label-matching claim from another actor has no ResourceRef set. - Add TestReconcileQuotaSingleMode covering the full single-mode quota flow: project ID from namespace, watch re-enqueue to "single" cluster. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v2.1.5 was built with Go 1.24 and refuses to lint Go 1.25 modules. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tatus change Write-back was only triggered inside the statusChanged||readyChanged block, so instances stuck in a scheduling gate (no status transitions) were never replicated to Karmada. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nged Use apiequality.Semantic.DeepEqual to avoid unnecessary API calls to Karmada on every reconcile when nothing has actually changed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
From the cell cluster's perspective, Karmada is upstream. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scotwells
force-pushed
the
feat/federated-deployment-scheduling
branch
5 times, most recently
from
June 11, 2026 00:56
5b638bb to
7dc94a0
Compare
This was referenced Jul 6, 2026
Replace the bespoke enablement preflight with the shared serviceactivation SDK. The old flow created a request, waited a fixed 15 seconds, then always printed "Error: ... try again in a moment" — even though the request had succeeded — and pointed at datumctl commands that don't exist. The gate now derives its behavior from the entitlement's status: a submitted request reads as submitted (not an error), pending approval is a legible waiting state, and denied/unavailable states carry the platform's own explanation plus a real recovery command. Add `datumctl compute access` (status, -o json|yaml) and `datumctl compute access request [--message] [--renew] [--wait] [--timeout]`, both exempt from the gate. The plugin entry point maps the SDK's typed errors onto the documented process exit codes (9-13) so scripts get deterministic results instead of a blanket exit 1.
The compute plugin consumes the new go.datum.net/datumctl/serviceactivation package. Pin datumctl to the pseudo-version that carries it so CI resolves the dependency without a go.work workspace. This pulls datumctl's own requirements forward, notably go.miloapis.com/milo 0.26 -> 0.29.3, and moves service-catalog to an indirect dependency (now reached through the SDK). Temporary: re-pin to a tagged or merged datumctl release before this merges.
The plugin branch had never had a full golangci-lint run in PR CI. Resolve
the findings so the gate is green:
- Extract the repeated instance-status strings ("Available", "Starting",
"Pending", the "Failed (...)" and "Not available — ..." variants) into
named constants shared by the list and describe views and their tests
(goconst).
- Drop two unused controller constants (unused).
- Annotate the two list commands whose branching over flags, filters, and
output formats is inherently above the cyclomatic-complexity threshold
(gocyclo).
Bump go.datum.net/datumctl to the pseudo-version whose service-activation SDK is backed by the generated service-catalog clientset. This pulls go.miloapis.com/service-catalog forward to its clientset release (now an indirect dependency, reached through the SDK). Still temporary: the whole chain (compute -> datumctl -> service-catalog) must be re-pinned to tagged/merged releases before this merges.
Replace the deprecated archives keys (archives.builds, archives.format, archives.format_overrides.format) with their current equivalents (archives.ids, archives.formats, archives.format_overrides.formats) so plugin releases stop emitting deprecation warnings and keep working when a future goreleaser major removes the old keys. Verified with goreleaser 2.16.0: snapshot release produces the same six platform archives with identical names and zero deprecation warnings. Fixes #124 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ives-schema chore: migrate .goreleaser-plugin.yaml to goreleaser v2 archives schema
This was referenced Jul 13, 2026
feat: datumctl compute build
Brings the compute CLI plugin branch up to date with 203 commits of platform work on main. Most of this branch's non-CLI content (federated deployment scheduling, quota enforcement, feature gates, instance projection/write-back) already landed on main through the PR decomposition stack in a further-evolved form, so main's version was taken wholesale for those files. What survives from this branch is the user-facing piece: the `datumctl compute` plugin, its service-activation gate, and the `access` commands. Resolution summary: - Build/CI (Makefile, Taskfile.yaml, workflows): main's, which is a superset — it already carries the Go 1.26 / golangci-lint v2.12.2 bumps this branch introduced, plus the interpreter test, UI plugin publish jobs, and the full federation e2e topology. - .gitignore, .golangci.yml: union — main's content plus the plugin's goreleaser dist/, local plugin binary, and the errcheck exclusion for internal/cmd (CLI output helpers have no actionable error). - api/v1alpha: main's, which supersedes this branch's Instance "Available" rename, restart annotation, instance labels, and Up-to-date print column. The three Programmed failure reasons the CLI renders as human-readable status (ImageUnavailable, InstanceCrashing, ConfigurationError) are carried forward. - internal/controller, internal/config, internal/features, internal/validation, internal/webhook, cmd/main.go, config/, hack/e2e, test/e2e: main's. The plugin depends only on api/v1alpha, so none of this branch's controller-era changes are load-bearing for it. - go.mod: main's module graph plus the plugin's direct dependencies (datumctl SDK, cobra, and the local unikernel build pipeline). The branch's stale milo replace directive is dropped in favour of main's newer direct pin; go.sum regenerated with go mod tidy. - Generated artefacts (zz_generated*, CRDs, controller_rbac/role.yaml) regenerated from source markers; no drift from main. Also removes dead scaffolding this branch carried that main has since replaced: the unreferenced test/e2e/env helper and orphaned chainsaw assert files, and internal/provider/milo, a local fork of the upstream Milo multicluster provider that was only needed for a multicluster-runtime bump main did not take.
scotwells
marked this pull request as ready for review
August 20, 2026 17:27
savme
approved these changes
Aug 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the
datumctl computeplugin so developers can deploy and manage containerized workloads on Datum Cloud directly from the CLI.Commands shipped:
deploy— push a container image as a workload with flags or a manifest file; waits for rolloutdestroy— tear down a workload with a confirmation promptstatus— show workload health, per-city placement summary, and the active revisioninstances— list all running instances across cities, with describe for full detailscale— adjust minimum replica count across all placementsrollout— watch live rollout progress, browse revision history, and roll back to any prior revisionrestart— trigger a rolling restart of a workload or a specific cityquota— inspect per-city instance usage and surface quota-exceeded messagesaccess— show or request Compute service access for the project (see below)Revision history is stored as a ConfigMap per workload so
rollout historyandrollout undowork without server-side tracking.Service activation & access UX
Compute is enabled per project through a
ServiceEntitlement, and enabling it awaits a manual approval on the provider side. The plugin gates every data command on that enablement — and until now it told the user the wrong thing at every step: it created the request, waited a fixed 15 seconds, then printedError:and "try again in a moment" even though the request had succeeded, and pointed atdatumctl services …commands that don't exist.This branch now derives the whole experience from the entitlement's own status, so the CLI is honest in every state.
Before (verbatim):
After:
A submitted request no longer reads as an error, pending approval is a legible waiting state, denied/unavailable states carry the platform's own explanation plus a real recovery command, and every printed command exists. New gate-exempt verbs back this up:
datumctl compute access [-o json|yaml]— the current access state (with age and the server's message); always exits 0.datumctl compute access request [--message] [--renew] [--wait] [--timeout]— the explicit request verb; safe in scripts, never prompts.Scripts get a documented exit-code contract (10–13 for not-enabled / denied-or-revoked / pending / unavailable) instead of a blanket exit 1.
The gate logic lives in a shared, service-agnostic
serviceactivationpackage in datumctl (see Dependencies), so the next plugin adopts the flow instead of forking it. Design and rationale:docs/compute/development/rfcs/cli-service-activation.md.Dependencies
This is a three-repo dependency chain — compute → datumctl → service-catalog — and it must be merged and re-pinned in reverse order (service-catalog first, then datumctl, then compute), replacing each branch pseudo-version with a tagged/merged release before the next merges.
serviceactivationSDK.go.modpinsgo.datum.net/datumctlto that PR's commit by pseudo-version. The pin also pulls datumctl's own requirements forward, notablygo.miloapis.com/milo0.26 → 0.29.3 across the whole compute module — the controller suite should be validated in CI against that bump (it is: Tests and E2E Tests are green).go.miloapis.com/service-catalogis pinned (indirect) to that PR's commit.What's not included
logs— telemetry service not yet implementedcities/instance-typesresource listing commandsRelated
Closes #98. Design proposal in #111. Activation UX design in
docs/compute/development/rfcs/cli-service-activation.md. Shared SDK: datum-cloud/datumctl#242.