~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
142 lines · 4.9 KB Raw History
package clusterkcp

import (
	"context"
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"

	rbacv1 "k8s.io/api/rbac/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/clusterprovider"
)

const cloudControllerManagerKubeconfigMode = 0o600

var _ clusterprovider.KubeconfigWriter = WriteKubeconfig

func WriteKubeconfig(path string, cfg *clusterprovider.Credential, contextName string, unwritable error) error {
	if cfg == nil {
		return fmt.Errorf("%w: no credential was rendered for %s", unwritable, path)
	}
	if path == "" {
		return fmt.Errorf("%w: the target path is empty", unwritable)
	}
	body, err := clientcmd.Write(clientcmdapi.Config{
		Clusters: map[string]*clientcmdapi.Cluster{contextName: {
			Server:                   cfg.Host,
			CertificateAuthorityData: cfg.CAData,
		}},
		AuthInfos: map[string]*clientcmdapi.AuthInfo{contextName: {
			ClientCertificateData: cfg.CertData,
			ClientKeyData:         cfg.KeyData,
		}},
		Contexts: map[string]*clientcmdapi.Context{contextName: {
			Cluster:  contextName,
			AuthInfo: contextName,
		}},
		CurrentContext: contextName,
	})
	if err != nil {
		return fmt.Errorf("%w: rendering the credential for %s: %v", unwritable, path, err)
	}

	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0o700); err != nil {
		return fmt.Errorf("%w: the directory %s holding %s: %v",
			unwritable, dir, path, err)
	}
	if info, err := os.Stat(path); err == nil {
		current, err := os.ReadFile(path)
		if err != nil {
			return fmt.Errorf("%w: reading %s: %v", unwritable, path, err)
		}
		if string(current) == string(body) && info.Mode().Perm() == cloudControllerManagerKubeconfigMode {
			return nil
		}
	} else if !errors.Is(err, fs.ErrNotExist) {
		return fmt.Errorf("%w: stat %s: %v", unwritable, path, err)
	}

	tmp, err := os.CreateTemp(dir, filepath.Base(path)+".")
	if err != nil {
		return fmt.Errorf("%w: a temporary file beside %s: %v",
			unwritable, path, err)
	}
	defer func() { _ = os.Remove(tmp.Name()) }()
	if err := tmp.Chmod(cloudControllerManagerKubeconfigMode); err != nil {
		_ = tmp.Close()
		return fmt.Errorf("%w: the mode of %s: %v", unwritable, tmp.Name(), err)
	}
	if _, err := tmp.Write(body); err != nil {
		_ = tmp.Close()
		return fmt.Errorf("%w: writing %s: %v", unwritable, tmp.Name(), err)
	}
	if err := tmp.Close(); err != nil {
		return fmt.Errorf("%w: closing %s: %v", unwritable, tmp.Name(), err)
	}
	if err := os.Rename(tmp.Name(), path); err != nil {
		return fmt.Errorf("%w: renaming %s over %s: %v",
			unwritable, tmp.Name(), path, err)
	}
	return nil
}

func GrantCloudControllerManager(ctx context.Context, core kubernetes.Interface, cluster string) error {
	if core == nil {
		return fmt.Errorf("%w (cluster %s)", clusterprovider.ErrNoCloudControllerManagerCredential, cluster)
	}
	role := cloudControllerManagerClusterRole()
	if _, err := core.RbacV1().ClusterRoles().Create(ctx, role, metav1.CreateOptions{FieldManager: fieldManager}); err != nil {
		if !apierrors.IsAlreadyExists(err) {
			return fmt.Errorf("clusterprovider: creating the RBAC ClusterRole %s: %w", role.Name, err)
		}
		if _, err := core.RbacV1().ClusterRoles().Update(ctx, role, metav1.UpdateOptions{FieldManager: fieldManager}); err != nil {
			return fmt.Errorf("clusterprovider: updating the RBAC ClusterRole %s: %w", role.Name, err)
		}
	}
	binding := cloudControllerManagerClusterRoleBinding()
	if _, err := core.RbacV1().ClusterRoleBindings().Create(ctx, binding, metav1.CreateOptions{FieldManager: fieldManager}); err != nil {
		if !apierrors.IsAlreadyExists(err) {
			return fmt.Errorf("clusterprovider: creating the RBAC ClusterRoleBinding %s: %w", binding.Name, err)
		}
		if _, err := core.RbacV1().ClusterRoleBindings().Update(ctx, binding, metav1.UpdateOptions{FieldManager: fieldManager}); err != nil {
			return fmt.Errorf("clusterprovider: updating the RBAC ClusterRoleBinding %s: %w", binding.Name, err)
		}
	}
	return nil
}

func cloudControllerManagerClusterRole() *rbacv1.ClusterRole {
	return &rbacv1.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{Name: clusterprovider.CloudControllerManagerClusterRole},
		Rules: []rbacv1.PolicyRule{{
			APIGroups: []string{""},
			Resources: []string{"nodes"},
			Verbs:     []string{"get", "list", "watch", "patch", "update", "delete"},
		}},
	}
}

func cloudControllerManagerClusterRoleBinding() *rbacv1.ClusterRoleBinding {
	return &rbacv1.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{Name: clusterprovider.CloudControllerManagerClusterRoleBinding},
		RoleRef: rbacv1.RoleRef{
			APIGroup: rbacv1.GroupName,
			Kind:     "ClusterRole",
			Name:     clusterprovider.CloudControllerManagerClusterRole,
		},
		Subjects: []rbacv1.Subject{{
			Kind:     rbacv1.GroupKind,
			APIGroup: rbacv1.GroupName,
			Name:     clusterprovider.CloudControllerManagerGroup,
		}},
	}
}