Files
ward/pkg/kubeconfig/kubeconfig.go
James McDonald a0a7932f99
All checks were successful
Release / release (push) Successful in 1m38s
Refactor project layout
2026-04-01 13:16:06 +02:00

166 lines
4.3 KiB
Go

package kubeconfig
import (
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
// KubeConfig is the top-level kubeconfig structure.
type KubeConfig struct {
APIVersion string `yaml:"apiVersion"`
Kind string `yaml:"kind"`
Preferences map[string]any `yaml:"preferences,omitempty"`
Clusters []NamedCluster `yaml:"clusters"`
Users []NamedUser `yaml:"users"`
Contexts []NamedContext `yaml:"contexts"`
CurrentContext string `yaml:"current-context,omitempty"`
}
// NamedCluster is a named cluster entry.
type NamedCluster struct {
Name string `yaml:"name"`
Cluster ClusterData `yaml:"cluster"`
}
// ClusterData holds the cluster connection details.
type ClusterData struct {
Server string `yaml:"server"`
CertificateAuthorityData string `yaml:"certificate-authority-data,omitempty"`
}
// NamedUser is a named user entry.
type NamedUser struct {
Name string `yaml:"name"`
User UserData `yaml:"user"`
}
// UserData holds user credentials.
type UserData struct {
Exec *ExecData `yaml:"exec,omitempty"`
}
// ExecData holds exec plugin configuration.
type ExecData struct {
APIVersion string `yaml:"apiVersion"`
Command string `yaml:"command"`
Args []string `yaml:"args,omitempty"`
InteractiveMode string `yaml:"interactiveMode,omitempty"`
}
// NamedContext is a named context entry.
type NamedContext struct {
Name string `yaml:"name"`
Context ContextData `yaml:"context"`
}
// ContextData holds context details.
type ContextData struct {
Cluster string `yaml:"cluster"`
User string `yaml:"user"`
}
// FilePath returns the path to the active kubeconfig file.
// If KUBECONFIG is set to a colon-separated list, the first entry is returned.
func FilePath() string {
if k := os.Getenv("KUBECONFIG"); k != "" {
if idx := strings.IndexByte(k, os.PathListSeparator); idx >= 0 {
return k[:idx]
}
return k
}
home, _ := os.UserHomeDir()
return filepath.Join(home, ".kube", "config")
}
// RenameContext renames the cluster and context (but not the user) in cfg.
// The bootstrap template uses the cluster name as both the cluster and context
// name; the user name is the actual username and is left unchanged.
func RenameContext(cfg *KubeConfig, newName string) {
oldName := cfg.CurrentContext
if oldName == newName {
return
}
for i := range cfg.Clusters {
if cfg.Clusters[i].Name == oldName {
cfg.Clusters[i].Name = newName
}
}
for i := range cfg.Contexts {
if cfg.Contexts[i].Name == oldName {
cfg.Contexts[i].Name = newName
cfg.Contexts[i].Context.Cluster = newName
}
}
cfg.CurrentContext = newName
}
// Merge merges incoming into the kubeconfig file at FilePath().
// If setContext is true, the current-context is updated to incoming.CurrentContext.
func Merge(incoming *KubeConfig, setContext bool) error {
path := FilePath()
existing := &KubeConfig{APIVersion: "v1", Kind: "Config"}
if data, err := os.ReadFile(path); err == nil {
if err := yaml.Unmarshal(data, existing); err != nil {
return fmt.Errorf("parsing existing kubeconfig: %w", err)
}
}
for _, c := range incoming.Clusters {
existing.Clusters = upsertCluster(existing.Clusters, c)
}
for _, u := range incoming.Users {
existing.Users = upsertUser(existing.Users, u)
}
for _, ctx := range incoming.Contexts {
existing.Contexts = upsertContext(existing.Contexts, ctx)
}
if setContext {
existing.CurrentContext = incoming.CurrentContext
}
data, err := yaml.Marshal(existing)
if err != nil {
return fmt.Errorf("marshaling kubeconfig: %w", err)
}
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
return fmt.Errorf("creating kubeconfig directory: %w", err)
}
return os.WriteFile(path, data, 0600)
}
func upsertCluster(list []NamedCluster, item NamedCluster) []NamedCluster {
for i, c := range list {
if c.Name == item.Name {
list[i] = item
return list
}
}
return append(list, item)
}
func upsertUser(list []NamedUser, item NamedUser) []NamedUser {
for i, u := range list {
if u.Name == item.Name {
list[i] = item
return list
}
}
return append(list, item)
}
func upsertContext(list []NamedContext, item NamedContext) []NamedContext {
for i, c := range list {
if c.Name == item.Name {
list[i] = item
return list
}
}
return append(list, item)
}