kubernetesx

package
v0.4.18 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 26 Imported by: 0

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

View Source
const (
	CodeConfigInvalid     = errorx.Code("KUBERNETES_CONFIG_INVALID")
	CodeCredentialInvalid = errorx.Code("KUBERNETES_CREDENTIAL_INVALID")
	CodeUnauthorized      = errorx.Code("KUBERNETES_UNAUTHORIZED")
	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")
	CodeAPIUnavailable    = errorx.Code("KUBERNETES_API_UNAVAILABLE")
)

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.

View Source
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.

View Source
const DefaultFieldManager = "aisphere-hub"

DefaultFieldManager is the SSA field manager used when Config.FieldManager and ApplyOptions.FieldManager are both empty.

View Source
const DefaultUserAgent = "aisphere-kubernetesx"

DefaultUserAgent is the User-Agent sent when Config.UserAgent is empty.

Variables

View Source
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 is the normalized 401 from the API server.
	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 is a 5xx from the API server (excluding timeout).
	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

func DefaultScheme() *runtime.Scheme

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

func NormalizeError(err error) error

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

func ParseHost(host string) string

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

type AddToScheme = func(*runtime.Scheme) error

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

func New(cfg Config, opts ...Option) (Client, error)

New builds a kubernetesx.Client from cfg and opts. The construction order:

  1. Validate cfg.
  2. Resolve *rest.Config from Host / Kubeconfig / in-cluster, applying QPS, Burst, Timeout, UserAgent, and InsecureSkipVerify.
  3. Build the runtime.Scheme (WithScheme or DefaultScheme, plus any WithAddToScheme additions).
  4. Build the controller-runtime client, dynamic.Interface, and discovery client from the *rest.Config (or use injected test doubles).
  5. 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

func (c Config) Normalized() Config

Normalized returns a copy of c with default FieldManager / UserAgent filled in. It does not mutate c.

func (Config) Validate

func (c Config) Validate() error

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

func WithDynamicClient(d dynamic.Interface) Option

WithDynamicClient injects a pre-built dynamic.Interface. Intended for tests.

func WithLogger

func WithLogger(logger logx.Logger) Option

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

func WithMetrics(m metricsx.Manager) Option

WithMetrics overrides the Config.Metrics manager used by the client.

func WithMetricsEnabled

func WithMetricsEnabled(enabled bool) Option

WithMetricsEnabled overrides Config.MetricsEnabled.

func WithRESTConfig

func WithRESTConfig(cfg *rest.Config) Option

WithRESTConfig injects a pre-built *rest.Config, bypassing kubeconfig/Host resolution. Intended for tests and envtest.

func WithScheme

func WithScheme(scheme *runtime.Scheme) Option

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.

Directories

Path Synopsis
Package fake provides a kubernetesx.Client backed by controller-runtime's fake client for unit tests.
Package fake provides a kubernetesx.Client backed by controller-runtime's fake client for unit tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL