You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every milo-os service that ships a datumctl plugin has to hand-write the same transport layer before it can make a single API call: derive the scoped control-plane URL from the injected org/project, mint a token, build a client-go rest.Config, and fall back to a kubeconfig for local/dev use. datumctl itself, the compute plugin, and now the milo-os IPAM plugin (milo-ipam) each carry their own copy of this. It's boilerplate that's easy to get subtly wrong, and mistakes don't fail loudly — for example, a scheme-less API host silently routes to an HTML endpoint and surfaces as a confusing "serializer for text/html" error.
What we'd like
The SDK should hand a plugin a ready-to-use client path, so authors write their commands, not their transport. Two pieces:
The scoped control-plane URL, derived from the injected context. The SDK already exposes org / project / api-host; it should also turn them into the fully-qualified control-plane URL (and guarantee a scheme). That construction is identical across every plugin and is the single most error-prone step. It's pure string logic with no new dependencies, so it fits the core plugin package.
A ready rest.Config, plus a kubeconfig fallback. An opt-in helper that builds a client-go rest.Config from the injected context — fresh token, scoped host, user-agent — plus the standard selection: use the injected context in production, fall back to KUBECONFIG / in-cluster for dev and e2e. Because this pulls in client-go, it belongs in a separate sub-package so the core SDK stays dependency-light.
With these, a plugin's transport collapses from ~150 lines to a couple of calls, and every milo-os plugin reaches its API the same way.
Why now
The plugin-marketplace enhancement (datum-cloud/enhancements#783) names standardizing "the credentials a host passes to a plugin — across every milo-os platform" as the natural next step beyond the binary-name convention, so "build once, run on any milo-os platform" holds end-to-end rather than just for the binary name. This is that step for transport. The IPAM plugin is the first external consumer and a concrete reference for what the SDK should expose.
Engineer notes
Concrete surface that would have replaced our code: PluginContext.ControlPlaneURL() string in package plugin (core, no new deps), and a plugin/kube (or similar) sub-package exposing something like RESTConfig(...) (*rest.Config, namespace string, err error) that performs the mode selection (an explicit --kubeconfig forces kubeconfig; otherwise the injected context when present; otherwise kubeconfig / in-cluster).
Scope addressing we implement today: project → <host>/apis/resourcemanager.miloapis.com/v1alpha1/projects/<project>/control-plane; org → .../organizations/<org>/control-plane; neither → bare host (platform/operator scope). Same construction datumctl and the compute plugin use.
The scheme footgun to fold in: DATUM_API_HOST arrives without a scheme; client-go needs an absolute URL or it misroutes.
Problem
Every milo-os service that ships a
datumctlplugin has to hand-write the same transport layer before it can make a single API call: derive the scoped control-plane URL from the injected org/project, mint a token, build a client-gorest.Config, and fall back to a kubeconfig for local/dev use.datumctlitself, the compute plugin, and now the milo-os IPAM plugin (milo-ipam) each carry their own copy of this. It's boilerplate that's easy to get subtly wrong, and mistakes don't fail loudly — for example, a scheme-less API host silently routes to an HTML endpoint and surfaces as a confusing "serializer for text/html" error.What we'd like
The SDK should hand a plugin a ready-to-use client path, so authors write their commands, not their transport. Two pieces:
The scoped control-plane URL, derived from the injected context. The SDK already exposes org / project / api-host; it should also turn them into the fully-qualified control-plane URL (and guarantee a scheme). That construction is identical across every plugin and is the single most error-prone step. It's pure string logic with no new dependencies, so it fits the core plugin package.
A ready
rest.Config, plus a kubeconfig fallback. An opt-in helper that builds a client-gorest.Configfrom the injected context — fresh token, scoped host, user-agent — plus the standard selection: use the injected context in production, fall back toKUBECONFIG/ in-cluster for dev and e2e. Because this pulls in client-go, it belongs in a separate sub-package so the core SDK stays dependency-light.With these, a plugin's transport collapses from ~150 lines to a couple of calls, and every milo-os plugin reaches its API the same way.
Why now
The plugin-marketplace enhancement (datum-cloud/enhancements#783) names standardizing "the credentials a host passes to a plugin — across every milo-os platform" as the natural next step beyond the binary-name convention, so "build once, run on any milo-os platform" holds end-to-end rather than just for the binary name. This is that step for transport. The IPAM plugin is the first external consumer and a concrete reference for what the SDK should expose.
Engineer notes
PluginContext.ControlPlaneURL() stringin packageplugin(core, no new deps), and aplugin/kube(or similar) sub-package exposing something likeRESTConfig(...) (*rest.Config, namespace string, err error)that performs the mode selection (an explicit--kubeconfigforces kubeconfig; otherwise the injected context when present; otherwise kubeconfig / in-cluster).<host>/apis/resourcemanager.miloapis.com/v1alpha1/projects/<project>/control-plane; org →.../organizations/<org>/control-plane; neither → bare host (platform/operator scope). Same constructiondatumctland the compute plugin use.DATUM_API_HOSTarrives without a scheme; client-go needs an absolute URL or it misroutes.cmd/milo-ipam/transport.goinmilo-os/ipam(PR fix(deps): update module go.miloapis.com/milo to v0.16.0 #36).