1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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,
}},
}
}