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
package ccmmain
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/johnandersen777/socialweb-computer-kcp/internal/clusterstack"
)
const (
envTenantKubeconfig = "SOCIALWEB_CLOUDCONTROLLERMANAGER_TENANT_KUBECONFIG"
envCluster = "SOCIALWEB_CLOUDCONTROLLERMANAGER_CLUSTER"
envInstancesFile = "SOCIALWEB_CLOUDCONTROLLERMANAGER_INSTANCES_FILE"
envMaxNodes = "SOCIALWEB_CLOUDCONTROLLERMANAGER_MAX_NODES"
envPodsPerNode = "SOCIALWEB_CLOUDCONTROLLERMANAGER_PODS_PER_NODE"
)
const (
DefaultMaxNodes = 4
DefaultPodsPerNode = 8
)
type Config struct {
TenantKubeconfig string
Cluster string
InstancesFile string
MaxNodes int
PodsPerNode int
}
func load(get func(string) string) (Config, error) {
cfg := Config{
TenantKubeconfig: strings.TrimSpace(get(envTenantKubeconfig)),
Cluster: strings.TrimSpace(get(envCluster)),
InstancesFile: strings.TrimSpace(get(envInstancesFile)),
MaxNodes: DefaultMaxNodes,
PodsPerNode: DefaultPodsPerNode,
}
for _, req := range []struct{ name, value string }{
{envTenantKubeconfig, cfg.TenantKubeconfig},
{envCluster, cfg.Cluster},
{envInstancesFile, cfg.InstancesFile},
} {
if req.value == "" {
return Config{}, fmt.Errorf(
"%s is not set. The cloud controller manager is a peer of this cluster's control "+
"plane: it holds a tenant credential for it, it owns a cluster name, and it "+
"reads the instances that back the Nodes in its own state directory", req.name)
}
}
if err := checkTenantKubeconfig(cfg.TenantKubeconfig); err != nil {
return Config{}, err
}
if raw := strings.TrimSpace(get(envMaxNodes)); raw != "" {
n, err := strconv.Atoi(raw)
if err != nil {
return Config{}, fmt.Errorf("%s is %q, which is not a whole number: %w", envMaxNodes, raw, err)
}
if n < 1 {
return Config{}, fmt.Errorf(
"%s is %q. A ceiling below one node cannot hold any work, so a cluster with any "+
"intent could never be satisfied", envMaxNodes, raw)
}
cfg.MaxNodes = n
}
if raw := strings.TrimSpace(get(envPodsPerNode)); raw != "" {
n, err := strconv.Atoi(raw)
if err != nil {
return Config{}, fmt.Errorf("%s is %q, which is not a whole number: %w", envPodsPerNode, raw, err)
}
if n < 1 {
return Config{}, fmt.Errorf(
"%s is %q. The autoscaling signal divides work by this number, so a value below "+
"one is not a smaller node, it is no ratio at all", envPodsPerNode, raw)
}
cfg.PodsPerNode = n
}
return cfg, nil
}
func checkTenantKubeconfig(path string) error {
if filepath.Base(path) == clusterstack.AdminKubeconfigName {
return fmt.Errorf(
"%s is %q, which is k3s's OWN admin kubeconfig, and it is refused. Invariant 20: the "+
"provider's tenant credential is MINTED, never copied -- a client certificate "+
"signed by this cluster's own ClientCA and valid only against this cluster. The "+
"k3s-written admin kubeconfig is shared state on a multi-cluster host and its "+
"server name is unverified against the address a node dials, so the cloud "+
"controller manager must not be given it. Point %s at the minted tenant kubeconfig "+
"produced by internal/clusterprovider/tenantcredential.go",
envTenantKubeconfig, path, envTenantKubeconfig)
}
raw, err := os.ReadFile(path)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf(
"%s is %q, and no such file exists. A controller with no tenant credential cannot "+
"read the Node objects it reconciles", envTenantKubeconfig, path)
}
return fmt.Errorf("%s is %q, which cannot be read: %w", envTenantKubeconfig, path, err)
}
lower := strings.ToLower(string(raw))
for _, forbidden := range forbiddenInTenantKubeconfig {
if strings.Contains(lower, strings.ToLower(forbidden)) {
return fmt.Errorf(
"%s is %q and its document contains %q. The tenant credential is a minted client "+
"certificate, never a static bearer token and never k3s's admin kubeconfig; a "+
"document carrying one of those is refused rather than used",
envTenantKubeconfig, path, forbidden)
}
}
return nil
}
var forbiddenInTenantKubeconfig = []string{"bearer_token", "token:", "admin.kubeconfig"}