Documentation
¶
Overview ¶
Package k8s provides common Kubernetes utilities for creating clients, configs, and namespace detection that can be shared across packages.
Package k8s provides common Kubernetes client utilities for ToolHive.
This package centralizes Kubernetes client creation, configuration loading, and namespace detection to avoid duplication across the codebase and prevent circular dependencies.
Configuration Loading ¶
The package uses a fallback strategy for loading Kubernetes configuration:
In-cluster configuration (when running inside a Kubernetes pod) - Reads from /var/run/secrets/kubernetes.io/serviceaccount/ - Automatically configured by Kubernetes
Out-of-cluster configuration (when running locally or outside Kubernetes) - Follows standard kubeconfig loading rules: a. KUBECONFIG environment variable (can specify multiple files separated by colons) b. ~/.kube/config file (default location)
Namespace Detection ¶
The GetCurrentNamespace() function detects the current Kubernetes namespace using multiple methods in order of precedence:
Service Account Namespace File - Path: /var/run/secrets/kubernetes.io/serviceaccount/namespace - Available when running inside a Kubernetes pod - Most reliable method for in-cluster deployments
Environment Variable - Variable: POD_NAMESPACE - Commonly set via Kubernetes downward API - Example in pod spec: env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace
Kubeconfig Context - Reads namespace from the current kubectl context - Uses the same kubeconfig loading rules as configuration - Falls back if namespace is not set in context
Default Namespace - Falls back to "default" if all other methods fail
Environment Variables ¶
The package respects the following environment variables:
- KUBECONFIG: Specifies path(s) to kubeconfig files (colon-separated)
- POD_NAMESPACE: Explicitly sets the current namespace (used by GetCurrentNamespace)
Usage Examples ¶
Creating a Kubernetes client:
import "github.com/stacklok/toolhive/pkg/k8s"
// Create client with automatic config detection
clientset, config, err := k8s.NewClient()
if err != nil {
return err
}
// Use the client
pods, err := clientset.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
Creating a client from existing config:
import "github.com/stacklok/toolhive/pkg/k8s"
// Get config separately
config, err := k8s.GetConfig()
if err != nil {
return err
}
// Customize config if needed
config.Timeout = 30 * time.Second
// Create client from config
clientset, err := k8s.NewClientWithConfig(config)
if err != nil {
return err
}
Working with Custom Resource Definitions (CRDs):
import "github.com/stacklok/toolhive/pkg/k8s"
import "k8s.io/apimachinery/pkg/runtime"
import utilruntime "k8s.io/apimachinery/pkg/util/runtime"
import clientgoscheme "k8s.io/client-go/kubernetes/scheme"
// Create a scheme and register your CRD types
scheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(scheme)) // Standard K8s types
utilruntime.Must(mycrdv1alpha1.AddToScheme(scheme)) // Your CRD types
// Create controller-runtime client
k8sClient, err := k8s.NewControllerRuntimeClient(scheme)
if err != nil {
return err
}
// Now you can work with both standard resources and CRDs
var myCustomResource mycrdv1alpha1.MyResource
err = k8sClient.Get(ctx, types.NamespacedName{Name: "example", Namespace: "default"}, &myCustomResource)
Working with dynamic/unstructured resources:
import "github.com/stacklok/toolhive/pkg/k8s"
import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
import "k8s.io/apimachinery/pkg/runtime/schema"
// Create dynamic client
dynamicClient, err := k8s.NewDynamicClient()
if err != nil {
return err
}
// Define the resource you want to work with
gvr := schema.GroupVersionResource{
Group: "example.com",
Version: "v1",
Resource: "myresources",
}
// Get resources
list, err := dynamicClient.Resource(gvr).Namespace("default").List(ctx, metav1.ListOptions{})
Detecting the current namespace:
import "github.com/stacklok/toolhive/pkg/k8s"
// Get current namespace with automatic detection
namespace := k8s.GetCurrentNamespace()
fmt.Printf("Current namespace: %s\n", namespace)
// Use in operations
pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
Checking Kubernetes availability:
import "github.com/stacklok/toolhive/pkg/k8s"
if k8s.IsAvailable() {
fmt.Println("Kubernetes is available")
// Proceed with Kubernetes operations
} else {
fmt.Println("Kubernetes is not available, falling back to local mode")
// Use alternative runtime
}
Client Types ¶
The package provides three specialized client creation functions:
NewClient() - Standard Kubernetes clientset (kubernetes.Interface) - Use for working with built-in Kubernetes resources (Pods, Services, etc.) - Type-safe access to core API groups - Most common choice for basic Kubernetes operations
NewControllerRuntimeClient() - Controller-runtime client (client.Client) - Use when working with Custom Resource Definitions (CRDs) - Requires a runtime.Scheme with registered types - Provides unified access to both standard and custom resources - Ideal for operators, controllers, and CRD-heavy applications
NewDynamicClient() - Dynamic client (dynamic.Interface) - Use for working with arbitrary resources without compile-time types - Works with unstructured.Unstructured objects - Useful for discovery, generic tooling, or when resource types are unknown
Design Considerations ¶
This package is designed to:
- Provide a single source of truth for Kubernetes client creation
- Enable reuse across different packages without circular dependencies
- Support both in-cluster and out-of-cluster deployments
- Support multiple client types for different use cases
- Follow Kubernetes client-go conventions and best practices
- Maintain compatibility with standard Kubernetes tooling (kubectl, etc.)
- Keep the config/scheme layers separate to avoid circular dependencies
Testing ¶
When writing tests that use this package:
- Use fake clientsets from k8s.io/client-go/kubernetes/fake for standard clients
- Use controller-runtime fake client for CRD testing
- Pass fake clients directly to functions that accept the respective interfaces
- Mock config and namespace detection as needed for your test scenarios
Example test setup for standard clients:
import (
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/rest"
)
func TestMyFunction(t *testing.T) {
// Create fake client
fakeClient := fake.NewSimpleClientset()
// Use with functions that accept kubernetes.Interface
result, err := MyFunction(fakeClient)
// assertions...
}
Example test setup for controller-runtime clients:
import (
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
func TestMyControllerFunction(t *testing.T) {
// Create scheme
scheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(mycrdv1alpha1.AddToScheme(scheme))
// Create fake controller-runtime client
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
// Use with functions that accept client.Client
result, err := MyControllerFunction(fakeClient)
// assertions...
}
Index ¶
- func GetConfig() (*rest.Config, error)
- func GetCurrentNamespace() string
- func IsAvailable() bool
- func NewClient() (kubernetes.Interface, *rest.Config, error)
- func NewClientWithConfig(config *rest.Config) (kubernetes.Interface, error)
- func NewControllerRuntimeClient(scheme *runtime.Scheme) (client.Client, error)
- func NewDynamicClient() (dynamic.Interface, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GetConfig ¶
GetConfig returns a Kubernetes REST config with the following fallback strategy:
- In-cluster config (when running inside a Kubernetes pod)
- Out-of-cluster config using standard kubeconfig loading rules: a. KUBECONFIG environment variable (colon-separated paths) b. ~/.kube/config file
This order prioritizes in-cluster config for security and reliability when running as a pod, while supporting local development when running outside the cluster.
The returned config uses secure defaults:
- TLS certificate verification is enabled
- In-cluster: Service account CA cert is used automatically
- Out-of-cluster: certificate-authority-data from kubeconfig is used
- Default QPS: 5 requests/second, Burst: 10 (suitable for most use cases)
For high-volume operations (e.g., operators reconciling many resources), consider increasing QPS and Burst limits:
config, err := k8s.GetConfig()
if err != nil {
return err
}
config.QPS = 50 // Increase from default 5
config.Burst = 100 // Increase from default 10
func GetCurrentNamespace ¶
func GetCurrentNamespace() string
GetCurrentNamespace attempts to determine the current Kubernetes namespace using multiple methods, falling back to "default" if none succeed.
func IsAvailable ¶
func IsAvailable() bool
IsAvailable checks if Kubernetes is available by attempting to create a client and verifying connectivity.
func NewClient ¶
func NewClient() (kubernetes.Interface, *rest.Config, error)
NewClient creates a new standard Kubernetes clientset using the default config loading. It tries in-cluster config first, then falls back to out-of-cluster config. Use this when you only need to work with standard Kubernetes resources.
func NewClientWithConfig ¶
func NewClientWithConfig(config *rest.Config) (kubernetes.Interface, error)
NewClientWithConfig creates a new standard Kubernetes clientset from the provided config. Use this when you have an existing config and only need standard Kubernetes resources.
func NewControllerRuntimeClient ¶
NewControllerRuntimeClient creates a new controller-runtime client with a custom scheme. This is useful for working with Custom Resource Definitions (CRDs) alongside standard resources. The scheme should have all required types registered before calling this function.
Example:
scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(mycrds.AddToScheme(scheme)) k8sClient, err := k8s.NewControllerRuntimeClient(scheme)
func NewDynamicClient ¶
NewDynamicClient creates a new dynamic client for working with arbitrary resources. Use this when you need to work with resources without compile-time type information, such as discovering resources at runtime or working with unstructured data.
Types ¶
This section is empty.