166 lines
4.3 KiB
Go
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)
|
|
}
|