Documentation
¶
Overview ¶
Package kubernetesx provides the unified Kubernetes SDK abstraction for Aisphere Kernel.
kubernetesx is the ONLY Kubernetes client abstraction that Hub business code should depend on. It exposes a stable, application-oriented Client interface backed by controller-runtime and client-go, with Server-Side Apply, discovery, probe, error normalization, and metrics hooks.
Kernel owns the Kubernetes SDK surface only. It does NOT persist cluster records, store kubeconfig, manage organizations/users/shares, or define the Cluster/Namespace product model — those belong to Hub. Business code must never call client-go / controller-runtime directly.
Design principle ¶
kubernetesx wraps the raw Kubernetes SDK into an application-layer surface so that Hub request handlers stay free of client-go types:
HTTP/gRPC Request -> authz (Hub) -> kubernetesx.Client -> Kubernetes API Server -> response
Phase 1 is request-driven: no per-cluster Manager, Informer, Cache, or long-lived Watch. Periodic probe and state sync run through taskx. A later phase may add a dedicated controller-runtime process when WarmPool, Sandbox Controller, or long-lived Watch is genuinely needed.
30-second quickstart ¶
client, err := kubernetesx.New(kubernetesx.Config{
Host: "https://10.0.0.1:6443",
Kubeconfig: kubeconfigBytes,
FieldManager: "aisphere-hub",
QPS: 50,
Burst: 100,
Timeout: 30 * time.Second,
Logger: logx.DefaultLogger(),
Metrics: metricsx.Noop(),
})
if err != nil { return err }
// Server-Side Apply a Namespace
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "demo"}}
err = client.Apply(ctx, ns, kubernetesx.ApplyOptions{FieldManager: "aisphere-hub-namespace"})
// Probe before registering a cluster
result, err := client.Probe(ctx, kubernetesx.ProbeRequest{})
Server-Side Apply ¶
AISphere-managed resources (Namespace, Pod, CRD, Sandbox) use SSA. Each resource class uses its own Field Manager (e.g. aisphere-hub-namespace) and only owns the fields it explicitly declares. ForceOwnership is allowed only for fields AISphere exclusively owns. Field conflicts surface as KUBERNETES_FIELD_CONFLICT and must never be silently overwritten.
Errors ¶
All foreign Kubernetes errors are normalized through NormalizeError into stable errorx.Code constants (KUBERNETES_*). Error metadata never carries kubeconfig, token, or client-certificate private key material.
Escape hatches ¶
RESTConfig(), Dynamic(), and Discovery() expose raw client-go objects. They are infrastructure-layer escape hatches: ONLY Hub Data Adapter code may use them. Hub Biz layer must not depend on these.
Forbidden patterns ¶
Do not import `k8s.io/client-go`, `k8s.io/apimachinery`, or `sigs.k8s.io/controller-runtime` in business code. Use the kubernetesx.Client interface.
Index ¶
- Constants
- Variables
- func DefaultScheme() *runtime.Scheme
- func NewUnstructured(gvk schema.GroupVersionKind, namespace, name string) *unstructured.Unstructured
- func NormalizeError(err error) error
- func ParseHost(host string) string
- func UnstructuredFromObject(scheme *runtime.Scheme, obj runtime.Object) (*unstructured.Unstructured, error)
- type APIResource
- type AccessReview
- type AddToScheme
- type ApplyOptions
- type Capabilities
- type Client
- type Config
- type Credential
- type CredentialKind
- type Option
- func WithAddToScheme(add AddToScheme) Option
- func WithDiscoveryClient(d discovery.DiscoveryInterface) Option
- func WithDynamicClient(d dynamic.Interface) Option
- func WithLogger(logger logx.Logger) Option
- func WithMetrics(m metricsx.Manager) Option
- func WithMetricsEnabled(enabled bool) Option
- func WithRESTConfig(cfg *rest.Config) Option
- func WithScheme(scheme *runtime.Scheme) Option
- type ProbeRequest
- type ProbeResult
- type VersionInfo
Constants ¶
const ( CodeConfigInvalid = errorx.Code("KUBERNETES_CONFIG_INVALID") CodeCredentialInvalid = errorx.Code("KUBERNETES_CREDENTIAL_INVALID") CodeForbidden = errorx.Code("KUBERNETES_FORBIDDEN") CodeNotFound = errorx.Code("KUBERNETES_NOT_FOUND") CodeAlreadyExists = errorx.Code("KUBERNETES_ALREADY_EXISTS") CodeConflict = errorx.Code("KUBERNETES_CONFLICT") CodeFieldConflict = errorx.Code("KUBERNETES_FIELD_CONFLICT") CodeTimeout = errorx.Code("KUBERNETES_TIMEOUT") CodeUnreachable = errorx.Code("KUBERNETES_UNREACHABLE") )
Stable errorx codes. Business code reads these via errorx.CodeOf; they mirror the design §4.7 table exactly and are part of the public contract.
const CodeOperationFailed = errorx.Code("KUBERNETES_OPERATION_FAILED")
CodeOperationFailed is the catch-all code returned by NormalizeError for errors that do not match a more specific classification. It is exported so callers can compare with errorx.CodeOf == kubernetesx.CodeOperationFailed.
const DefaultFieldManager = "aisphere-hub"
DefaultFieldManager is the SSA field manager used when Config.FieldManager and ApplyOptions.FieldManager are both empty.
const DefaultUserAgent = "aisphere-kubernetesx"
DefaultUserAgent is the User-Agent sent when Config.UserAgent is empty.
Variables ¶
var ( // ErrConfigInvalid is returned by New when Config fails Validate. ErrConfigInvalid = errors.New("kubernetesx: config is invalid") // ErrCredentialInvalid is returned when a Credential fails Validate. ErrCredentialInvalid = errors.New("kubernetesx: credential is invalid") ErrUnauthorized = errors.New("kubernetesx: unauthorized") // ErrForbidden is the normalized 403 from the API server. ErrForbidden = errors.New("kubernetesx: forbidden") // ErrNotFound is the normalized 404 from the API server. ErrNotFound = errors.New("kubernetesx: resource not found") // ErrAlreadyExists is the normalized 409 AlreadyExists. ErrAlreadyExists = errors.New("kubernetesx: resource already exists") // ErrConflict is a generic 409 Conflict (e.g. optimistic concurrency). ErrConflict = errors.New("kubernetesx: conflict") // ErrFieldConflict is an SSA field-ownership conflict. It must never be // silently overwritten; the caller decides whether to escalate or // force-apply. ErrFieldConflict = errors.New("kubernetesx: server-side apply field conflict") // ErrTimeout is a client-side or server-side timeout. ErrTimeout = errors.New("kubernetesx: operation timed out") // ErrUnreachable is a connection / DNS / TLS failure before the API // server responded. ErrUnreachable = errors.New("kubernetesx: api server unreachable") ErrAPIUnavailable = errors.New("kubernetesx: api server unavailable") )
Sentinel errors. These wrap foreign k8s errors into stable kernel sentinels so that callers can use errors.Is without importing k8s apierrors. Business code should prefer the errorx.Code constants below and NormalizeError.
Functions ¶
func DefaultScheme ¶
DefaultScheme returns a fresh runtime.Scheme pre-registered with the API groups AISphere commonly manages:
- core/v1 (Pod, Namespace, Service, ConfigMap, Secret, …)
- apps/v1 (Deployment, StatefulSet, DaemonSet, ReplicaSet)
- batch/v1 (Job, CronJob)
- networking.k8s.io/v1 (Ingress, NetworkPolicy)
- rbac.authorization.k8s.io/v1 (Role, ClusterRole, Binding)
- apiextensions.k8s.io/v1 (CustomResourceDefinition)
The returned scheme is a new instance; mutating it does not affect future callers. Third-party CRD types should be injected via WithScheme at construction time.
func NewUnstructured ¶
func NewUnstructured(gvk schema.GroupVersionKind, namespace, name string) *unstructured.Unstructured
NewUnstructured builds an *unstructured.Unstructured with the supplied GVK and namespaced name. It is the entry point for third-party CRDs that have no generated Go type. The returned object is ready for ApplyUnstructured.
func NormalizeError ¶
NormalizeError converts a foreign Kubernetes / client-go error into a stable errorx-wrapped error carrying one of the KUBERNETES_* codes. It is idempotent: errors already produced by errorx pass through unchanged.
Metadata never includes kubeconfig, token, or private-key material — only api_group / kind / namespace / name / reason / retryable, sourced from the apierrors.StatusError when available.
func ParseHost ¶
ParseHost strips a trailing scheme marker from a Host string for logging. It is intentionally permissive: it only lowercases for comparison.
func UnstructuredFromObject ¶
func UnstructuredFromObject(scheme *runtime.Scheme, obj runtime.Object) (*unstructured.Unstructured, error)
UnstructuredFromObject converts a typed client.Object into an *unstructured.Unstructured via the runtime.Scheme used by the client. It is useful when an apply caller has a typed object but wants to manage only a subset of fields through the unstructured path.
Types ¶
type APIResource ¶
type APIResource struct {
Group string `json:"group"`
Version string `json:"version"`
Kind string `json:"kind"`
Namespaced bool `json:"namespaced"`
Verbs []string `json:"verbs"`
}
APIResource describes a single API resource kind discovered on the server.
func FlattenResources ¶
func FlattenResources(lists []*metav1.APIResourceList) []APIResource
FlattenResources is the exported form of flattenResources. It lets the fake subpackage and external test doubles reuse the same flattening logic.
type AccessReview ¶
type AccessReview struct {
CanView bool `json:"can_view"`
CanCreate bool `json:"can_create"`
CanUpdate bool `json:"can_update"`
CanDelete bool `json:"can_delete"`
// ReadOnly is true when CanCreate/CanUpdate/CanDelete are all false but
// CanView is true. Hub labels such clusters as read-only.
ReadOnly bool `json:"read_only"`
}
AccessReview summarizes the credential's namespace permissions. It tells Hub whether a cluster is writable (can onboard namespaces) or read-only.
type AddToScheme ¶
AddToScheme registers the default Kubernetes API groups a kubernetesx client understands out of the box. Callers may extend the scheme via WithScheme.
type ApplyOptions ¶
type ApplyOptions struct {
// FieldManager is the SSA field owner. Each AISphere resource class
// (namespace, sandbox, workload, network) should use its own manager so
// that the API server tracks field ownership per concern.
FieldManager string
// ForceOwnership, when true, re-acquires fields owned by other managers
// during apply. Use only for fields AISphere exclusively owns; never
// set it to silently overwrite a collaborator (HPA, operator, …).
ForceOwnership bool
// DryRun, when true, validates the apply without persisting. The API
// server still reports conflicts.
DryRun bool
}
ApplyOptions controls Server-Side Apply behavior. Zero-value FieldManager falls back to Config.FieldManager (and then DefaultFieldManager).
func (ApplyOptions) ToPatchOptions ¶
func (o ApplyOptions) ToPatchOptions(defaultFieldManager string) []ctrlclient.PatchOption
ToPatchOptions is the exported form of toPatchOptions. It is exposed so that the fake subpackage and external test doubles can build SSA patch options without re-implementing the default-field-manager fallback.
type Capabilities ¶
type Capabilities struct {
ServerVersion VersionInfo `json:"server_version"`
APIs []APIResource `json:"apis"`
}
Capabilities is the aggregated discovery result returned by Discover. It is the data Hub uses to decide whether a cluster can host a given CRD or run a given workload kind.
func (Capabilities) HasAPI ¶
func (c Capabilities) HasAPI(group, version, kind string) bool
HasAPI reports whether capabilities advertise a resource matching the supplied group/version/kind. Used by probe to gate the Namespace access review on clusters that may not expose core/v1, and by callers to check whether a cluster can host a given CRD or workload kind.
type Client ¶
type Client interface {
Get(ctx context.Context, key ctrlclient.ObjectKey, obj ctrlclient.Object, opts ...ctrlclient.GetOption) error
List(ctx context.Context, list ctrlclient.ObjectList, opts ...ctrlclient.ListOption) error
Create(ctx context.Context, obj ctrlclient.Object, opts ...ctrlclient.CreateOption) error
Update(ctx context.Context, obj ctrlclient.Object, opts ...ctrlclient.UpdateOption) error
Patch(ctx context.Context, obj ctrlclient.Object, patch ctrlclient.Patch, opts ...ctrlclient.PatchOption) error
Delete(ctx context.Context, obj ctrlclient.Object, opts ...ctrlclient.DeleteOption) error
// Apply runs SSA on a typed object. Field ownership conflicts surface as
// KUBERNETES_FIELD_CONFLICT via NormalizeError; they are never silently
// overwritten.
Apply(ctx context.Context, obj ctrlclient.Object, opts ApplyOptions) error
// ApplyUnstructured runs SSA on an *unstructured.Unstructured, used for
// third-party CRDs that have no generated Go type.
ApplyUnstructured(ctx context.Context, obj ctrlclient.Object, opts ApplyOptions) error
ServerVersion(ctx context.Context) (VersionInfo, error)
Discover(ctx context.Context) (Capabilities, error)
Probe(ctx context.Context, req ProbeRequest) (ProbeResult, error)
Scheme() *runtime.Scheme
RESTConfig() *rest.Config
Dynamic() dynamic.Interface
Discovery() discovery.DiscoveryInterface
}
Client is the application-oriented Kubernetes client used by Hub. It wraps controller-runtime's client with Server-Side Apply, discovery, probe, and stable error normalization, so that Hub request handlers never touch client-go types directly.
The CRUD methods mirror controller-runtime's client.Client signatures so that typed objects and unstructured objects both work without translation. Apply and ApplyUnstructured add SSA on top. ServerVersion/Discover/Probe cover cluster onboarding and periodic health checks.
RESTConfig, Dynamic, and Discovery are escape hatches: ONLY Hub Data Adapter code may call them. Hub Biz layer must not depend on these.
func New ¶
New builds a kubernetesx.Client from cfg and opts. The construction order:
- Validate cfg.
- Resolve *rest.Config from Host / Kubeconfig / in-cluster, applying QPS, Burst, Timeout, UserAgent, and InsecureSkipVerify.
- Build the runtime.Scheme (WithScheme or DefaultScheme, plus any WithAddToScheme additions).
- Build the controller-runtime client, dynamic.Interface, and discovery client from the *rest.Config (or use injected test doubles).
- Register metrics.
New never contacts the API server; reachability is checked separately via Probe. This keeps client construction fast and side-effect-free.
type Config ¶
type Config struct {
// Host is the API server URL, e.g. "https://10.0.0.1:6443".
// Required when Kubeconfig is empty and the process is not in-cluster.
Host string `json:"host"`
// ServerName overrides the TLS hostname used to verify the API server
// certificate. Use it when Host is an IP address but the server
// certificate is signed for a DNS name (e.g. Hub reaches a remote
// cluster via a public IP while the cluster cert SAN only lists the
// in-cluster domain). Setting ServerName preserves CA verification —
// it does NOT disable TLS. Empty means the hostname is derived from
// Host. Ignored when Kubeconfig is supplied unless the caller also
// sets it explicitly after MergeCredential.
ServerName string `json:"server_name"`
// Kubeconfig is the raw kubeconfig YAML. When non-empty it takes priority
// over Host for constructing *rest.Config. The selected context is
// controlled by Context.
Kubeconfig []byte `json:"kubeconfig,omitempty"`
// Context selects the kubeconfig context to use. Empty means the
// current-context of the kubeconfig.
Context string `json:"context"`
// Token is the bearer token used when authenticating with a
// CredentialKindServiceAccount credential. Populated by MergeCredential;
// ignored when Kubeconfig is non-empty. Like Kubeconfig, this field
// carries secret material and must not be logged.
Token string `json:"token,omitempty"`
// CACert is the PEM-encoded CA certificate used to verify the API server
// TLS certificate when authenticating with a service-account credential.
// Populated by MergeCredential; ignored when Kubeconfig is non-empty.
CACert []byte `json:"ca_cert,omitempty"`
// QPS is the API server request rate limit. Zero leaves the client-go
// default (5). Hub typically sets 50–100.
QPS float32 `json:"qps"`
// Burst is the burst budget above QPS. Zero leaves the client-go
// default (10).
Burst int `json:"burst"`
// Timeout is the per-request timeout applied to the REST client. Zero
// means no explicit timeout (rely on context deadline).
Timeout time.Duration `json:"timeout_ns"`
// UserAgent is sent as the User-Agent header. Empty defaults to
// "aisphere-kubernetesx".
UserAgent string `json:"user_agent"`
// FieldManager is the default SSA field manager used by Apply when
// ApplyOptions.FieldManager is empty. Defaults to "aisphere-hub".
FieldManager string `json:"field_manager"`
// InsecureSkipVerify disables TLS certificate verification. This is
// dangerous and intended only for local dev clusters. Production must
// leave it false.
InsecureSkipVerify bool `json:"insecure_skip_verify"`
// Logger is the component logger. If nil, kubernetesx uses
// logx.DefaultLogger().
Logger logx.Logger `json:"-" yaml:"-"`
// Metrics is the optional metrics manager for kubernetesx operations.
// A nil Manager is a valid no-op.
Metrics metricsx.Manager `json:"-" yaml:"-"`
// MetricsEnabled controls whether kubernetesx operations record metrics.
MetricsEnabled bool `json:"metrics_enabled" yaml:"metrics_enabled"`
}
Config holds the connection configuration for a Kubernetes API server.
A Config is built either from an explicit Host (plus optional Credential merged via MergeCredential), from a kubeconfig byte blob, or — when both are empty — from the in-cluster service account. Hub callers normally construct Config from a stored Credential and never expose kubeconfig to end users.
func (Config) MergeCredential ¶
func (c Config) MergeCredential(cred Credential) (Config, error)
MergeCredential merges a Credential into the receiver, returning a new Config. The Credential's Kubeconfig, Context, Token, and CACert override the matching Config fields. This is the canonical way Hub builds a per-cluster Config from a stored Credential.
MergeCredential does not validate the credential; callers should call cred.Validate() first.
func (Config) Normalized ¶
Normalized returns a copy of c with default FieldManager / UserAgent filled in. It does not mutate c.
func (Config) Validate ¶
Validate returns ErrConfigInvalid if required fields are missing or inconsistent. A Config is valid when at least one of Host, Kubeconfig, or the in-cluster path is available; the in-cluster path is checked lazily at construction time, so Validate only rejects the impossible case where no connection source can be selected.
type Credential ¶
type Credential struct {
// Kind selects the authentication strategy. Required.
Kind CredentialKind `json:"kind"`
// Kubeconfig is the raw kubeconfig YAML. Required when Kind ==
// KUBECONFIG. Ignored otherwise.
Kubeconfig []byte `json:"kubeconfig,omitempty"`
// Context selects the kubeconfig context. Optional; empty means the
// kubeconfig current-context.
Context string `json:"context"`
// Host is the API server URL. Required when Kind == SERVICE_ACCOUNT.
Host string `json:"host"`
// Token is the bearer token. Required when Kind == SERVICE_ACCOUNT.
Token string `json:"token"`
// CACert is the PEM-encoded CA certificate that signs the API server
// cert. Optional; when empty the system roots are used (or
// InsecureSkipVerify from Config).
CACert []byte `json:"ca_cert"`
}
Credential is the input form Hub sends to kubernetesx when building a per-cluster Client. It carries secret material and must never be logged or serialized into API responses.
func (Credential) Validate ¶
func (c Credential) Validate() error
Validate enforces the security invariants in design §5:
- KUBECONFIG must parse and must not use an exec plugin or reference external files (certificate paths outside the kubeconfig blob are rejected because Hub does not ship those files to its environment);
- SERVICE_ACCOUNT must have Host and Token;
- IN_CLUSTER requires nothing (resolved at runtime from the pod).
Validate does not touch the API server; it only sanity-checks the input.
type CredentialKind ¶
type CredentialKind string
CredentialKind identifies how a cluster is authenticated.
const ( // CredentialKindKubeconfig authenticates via a kubeconfig byte blob. // The kubeconfig must not use an exec plugin or reference external // files; see Validate. CredentialKindKubeconfig CredentialKind = "KUBECONFIG" // CredentialKindInCluster authenticates via the in-cluster service // account mounted in the running pod. Only valid when Hub itself runs // on the target cluster. CredentialKindInCluster CredentialKind = "IN_CLUSTER" // CredentialKindServiceAccount authenticates with a long-lived // service-account token and an explicit Host and optional CA cert. CredentialKindServiceAccount CredentialKind = "SERVICE_ACCOUNT" )
type Option ¶
type Option func(*options)
Option configures a kubernetesx Client at construction time.
func WithAddToScheme ¶
func WithAddToScheme(add AddToScheme) Option
WithAddToScheme registers an additional scheme (e.g. a third-party CRD type) onto the client's scheme. Multiple WithAddToScheme options are applied in order.
func WithDiscoveryClient ¶
func WithDiscoveryClient(d discovery.DiscoveryInterface) Option
WithDiscoveryClient injects a pre-built discovery client. Intended for tests.
func WithDynamicClient ¶
WithDynamicClient injects a pre-built dynamic.Interface. Intended for tests.
func WithLogger ¶
WithLogger overrides the Config.Logger used by the client. If not set, the factory falls back to Config.Logger (and then logx.DefaultLogger()).
func WithMetrics ¶
WithMetrics overrides the Config.Metrics manager used by the client.
func WithMetricsEnabled ¶
WithMetricsEnabled overrides Config.MetricsEnabled.
func WithRESTConfig ¶
WithRESTConfig injects a pre-built *rest.Config, bypassing kubeconfig/Host resolution. Intended for tests and envtest.
func WithScheme ¶
WithScheme sets the base runtime.Scheme used by the client. When supplied it replaces DefaultScheme; otherwise DefaultScheme is used. Additional third-party schemes can still be layered via WithAddToScheme.
type ProbeRequest ¶
type ProbeRequest struct {
// SkipAccessReview, when true, skips the create/update/delete
// SelfSubjectAccessReview calls. Use this for cheap reachability checks
// where the access matrix is already known.
SkipAccessReview bool
// Namespace is the namespace used as the subject of the access review.
// Empty means a cluster-scoped review (namespace create/delete are
// cluster-scoped; update is reviewed against this namespace when set).
Namespace string
}
ProbeRequest controls what Probe validates. The zero value requests the full probe: reachability, server version, cluster UID, API list, and namespace access review. Fields can narrow the work for periodic re-probes.
type ProbeResult ¶
type ProbeResult struct {
Reachable bool `json:"reachable"`
ServerVersion VersionInfo `json:"server_version"`
ClusterUID string `json:"cluster_uid"`
APIs []APIResource `json:"apis"`
NamespaceAccess AccessReview `json:"namespace_access"`
Latency time.Duration `json:"latency_ns"`
Warnings []string `json:"warnings"`
}
ProbeResult is the full output of a probe. Latency covers the whole probe round-trip, not individual sub-calls.
type VersionInfo ¶
type VersionInfo struct {
Major string `json:"major"`
Minor string `json:"minor"`
GitVersion string `json:"git_version"`
Platform string `json:"platform"`
}
VersionInfo is the stable, reduced view of a Kubernetes server version. It drops build/git fields that callers should not depend on and keeps the Major/Minor/GitVersion/Platform used by probe and capability checks.
func FromVersionInfo ¶
func FromVersionInfo(v *version.Info) VersionInfo
FromVersionInfo converts a k8s.io/apimachinery version.Info into the stable kubernetesx.VersionInfo.