blob: 6f3801a6a277e7da2d9d83353edc9c0a15008eae [file] [log] [blame]
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +04001// TODO
2// * ns pcloud not found
3
giolekva8aa73e82022-07-09 11:34:39 +04004package main
5
6import (
7 "context"
giolekva8aa73e82022-07-09 11:34:39 +04008 _ "embed"
giolekva8aa73e82022-07-09 11:34:39 +04009 "fmt"
giolekva8aa73e82022-07-09 11:34:39 +040010 "log"
11 "os"
12 "path/filepath"
13 "time"
14
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040015 "github.com/giolekva/pcloud/core/installer"
giolekva8aa73e82022-07-09 11:34:39 +040016 "github.com/giolekva/pcloud/core/installer/soft"
17 "github.com/spf13/cobra"
18 "helm.sh/helm/v3/pkg/action"
19 "helm.sh/helm/v3/pkg/chart/loader"
20 "helm.sh/helm/v3/pkg/kube"
21)
22
23var bootstrapFlags struct {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040024 chartsDir string
25 adminPubKey string
26 adminPrivKey string
27 storageDir string
28 volumeDefaultReplicaCount int
29 softServeIP string
giolekva8aa73e82022-07-09 11:34:39 +040030}
31
32func bootstrapCmd() *cobra.Command {
33 cmd := &cobra.Command{
34 Use: "bootstrap",
35 RunE: bootstrapCmdRun,
36 }
37 cmd.Flags().StringVar(
38 &bootstrapFlags.chartsDir,
39 "charts-dir",
40 "",
41 "",
42 )
43 cmd.Flags().StringVar(
44 &bootstrapFlags.adminPubKey,
45 "admin-pub-key",
46 "",
47 "",
48 )
49 cmd.Flags().StringVar(
50 &bootstrapFlags.adminPrivKey,
51 "admin-priv-key",
52 "",
53 "",
54 )
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040055 cmd.Flags().StringVar(
56 &bootstrapFlags.storageDir,
57 "storage-dir",
58 "",
59 "",
60 )
61 cmd.Flags().IntVar(
62 &bootstrapFlags.volumeDefaultReplicaCount,
63 "volume-default-replica-count",
64 3,
65 "",
66 )
67 cmd.Flags().StringVar(
68 &bootstrapFlags.softServeIP,
69 "soft-serve-ip",
70 "",
71 "",
72 )
giolekva8aa73e82022-07-09 11:34:39 +040073 return cmd
74}
75
76func bootstrapCmdRun(cmd *cobra.Command, args []string) error {
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +040077 adminPubKey, adminPrivKey, err := readAdminKeys()
78 if err != nil {
79 return err
80 }
81 fluxPub, fluxPriv, err := installer.GenerateSSHKeys()
82 if err != nil {
83 return err
84 }
85 softServePub, softServePriv, err := installer.GenerateSSHKeys()
86 if err != nil {
87 return err
88 }
89 if err := installMetallbNamespace(); err != nil {
90 return err
91 }
92 if err := installMetallb(); err != nil {
93 return err
94 }
95 time.Sleep(3 * time.Minute)
96 if err := installMetallbConfig(); err != nil {
97 return err
98 }
99 if err := installLonghorn(); err != nil {
100 return err
101 }
102 if err := installSoftServe(softServePub, softServePriv, string(adminPubKey)); err != nil {
103 return err
104 }
105 time.Sleep(30 * time.Second)
106 ss, err := soft.NewClient(bootstrapFlags.softServeIP, 22, adminPrivKey, log.Default())
107 if err != nil {
108 return err
109 }
110 if err := ss.AddUser("flux", fluxPub); err != nil {
111 return err
112 }
113 if err := ss.MakeUserAdmin("flux"); err != nil {
114 return err
115 }
116 fmt.Println("Creating /pcloud repo")
117 if err := ss.AddRepository("pcloud", "# PCloud Systems\n"); err != nil {
118 return err
119 }
120 fmt.Println("Installing Flux")
121 if err := installFlux("ssh://soft-serve.pcloud.svc.cluster.local:22/pcloud", "soft-serve.pcloud.svc.cluster.local", softServePub, fluxPriv); err != nil {
122 return err
123 }
124 // TODO(giolekva): everything below must be installed using Flux
125 if err := installIngressPublic(); err != nil {
126 return err
127 }
128 if err := installCertManager(); err != nil {
129 return err
130 }
131 if err := installCertManagerWebhookGandi(); err != nil {
132 return err
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400133 }
134 return nil
135}
136
137func installMetallbNamespace() error {
138 fmt.Println("Installing metallb namespace")
139 // config, err := createActionConfig("default")
140 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400141 if err != nil {
142 return err
143 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400144 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "namespace"))
giolekva8aa73e82022-07-09 11:34:39 +0400145 if err != nil {
146 return err
147 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400148 values := map[string]interface{}{
149 // "namespace": "pcloud-metallb",
150 "namespace": "metallb-system",
151 "labels": []string{
152 "pod-security.kubernetes.io/audit: privileged",
153 "pod-security.kubernetes.io/enforce: privileged",
154 "pod-security.kubernetes.io/warn: privileged",
155 },
156 }
157 installer := action.NewInstall(config)
158 installer.Namespace = "pcloud"
159 installer.ReleaseName = "metallb-ns"
160 installer.Wait = true
161 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
162 return err
163 }
164 return nil
165}
166
167func installMetallb() error {
168 fmt.Println("Installing metallb")
169 // config, err := createActionConfig("default")
170 config, err := createActionConfig("metallb-system")
giolekva8aa73e82022-07-09 11:34:39 +0400171 if err != nil {
172 return err
173 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400174 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "metallb"))
giolekva8aa73e82022-07-09 11:34:39 +0400175 if err != nil {
176 return err
177 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400178 values := map[string]interface{}{ // TODO(giolekva): add loadBalancerClass?
179 "controller": map[string]interface{}{
180 "image": map[string]interface{}{
181 "repository": "quay.io/metallb/controller",
182 "tag": "v0.13.9",
183 "pullPolicy": "IfNotPresent",
184 },
185 "logLevel": "info",
186 },
187 "speaker": map[string]interface{}{
188 "image": map[string]interface{}{
189 "repository": "quay.io/metallb/speaker",
190 "tag": "v0.13.9",
191 "pullPolicy": "IfNotPresent",
192 },
193 "logLevel": "info",
194 },
195 }
196 installer := action.NewInstall(config)
197 installer.Namespace = "metallb-system" // "pcloud-metallb"
198 installer.CreateNamespace = true
199 installer.ReleaseName = "metallb"
200 installer.IncludeCRDs = true
201 // installer.Wait = true
202 installer.Timeout = 20 * time.Minute
203 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400204 return err
205 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400206 return nil
207}
208
209func installMetallbConfig() error {
210 fmt.Println("Installing metallb-config")
211 // config, err := createActionConfig("default")
212 config, err := createActionConfig("metallb-system")
213 if err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400214 return err
215 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400216 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "metallb-config"))
217 if err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400218 return err
219 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400220 values := map[string]interface{}{
221 "from": "192.168.0.210",
222 "to": "192.168.0.240",
223 }
224 installer := action.NewInstall(config)
225 installer.Namespace = "metallb-system" // "pcloud-metallb"
226 installer.CreateNamespace = true
227 installer.ReleaseName = "metallb-cfg"
228 installer.Wait = true
229 installer.Timeout = 20 * time.Minute
230 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
231 return err
232 }
233 return nil
234}
235
236func installLonghorn() error {
237 fmt.Println("Installing Longhorn")
238 config, err := createActionConfig("pcloud")
239 if err != nil {
240 return err
241 }
242 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "longhorn"))
243 if err != nil {
244 return err
245 }
246 values := map[string]interface{}{
247 "defaultSettings": map[string]interface{}{
248 "defaultDataPath": bootstrapFlags.storageDir,
249 },
250 "persistence": map[string]interface{}{
251 "defaultClassReplicaCount": bootstrapFlags.volumeDefaultReplicaCount,
252 },
253 "service": map[string]interface{}{
254 "ui": map[string]interface{}{
255 "type": "LoadBalancer",
256 },
257 },
258 "ingress": map[string]interface{}{
259 "enabled": false,
260 },
261 }
262 installer := action.NewInstall(config)
263 installer.Namespace = "longhorn-system"
264 installer.CreateNamespace = true
265 installer.ReleaseName = "longhorn"
266 installer.Wait = true
267 installer.Timeout = 20 * time.Minute
268 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400269 return err
270 }
271 return nil
272}
273
274func installSoftServe(pubKey, privKey, adminKey string) error {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400275 fmt.Println("Installing SoftServe")
276 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400277 if err != nil {
278 return err
279 }
280 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "soft-serve"))
281 if err != nil {
282 return err
283 }
284 values := map[string]interface{}{
285 "privateKey": privKey,
286 "publicKey": pubKey,
287 "adminKey": adminKey,
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400288 "reservedIP": bootstrapFlags.softServeIP,
giolekva8aa73e82022-07-09 11:34:39 +0400289 }
290 installer := action.NewInstall(config)
291 installer.Namespace = "pcloud"
292 installer.CreateNamespace = true
293 installer.ReleaseName = "soft-serve"
294 installer.Wait = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400295 installer.Timeout = 20 * time.Minute
giolekva8aa73e82022-07-09 11:34:39 +0400296 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
297 return err
298 }
299 return nil
300}
301
302func installFlux(repoAddr, repoHost, repoHostPubKey, privateKey string) error {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400303 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400304 if err != nil {
305 return err
306 }
307 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "flux-bootstrap"))
308 if err != nil {
309 return err
310 }
311 values := map[string]interface{}{
312 "repositoryAddress": repoAddr,
313 "repositoryHost": repoHost,
314 "repositoryHostPublicKey": repoHostPubKey,
315 "privateKey": privateKey,
316 }
317 installer := action.NewInstall(config)
318 installer.Namespace = "pcloud"
319 installer.CreateNamespace = true
320 installer.ReleaseName = "flux"
321 installer.Wait = true
322 installer.WaitForJobs = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400323 installer.Timeout = 20 * time.Minute
giolekva8aa73e82022-07-09 11:34:39 +0400324 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
325 return err
326 }
327 return nil
328}
329
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400330func installIngressPublic() error {
331 config, err := createActionConfig("pcloud")
332 if err != nil {
333 return err
334 }
335 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "ingress-nginx"))
336 if err != nil {
337 return err
338 }
339 values := map[string]interface{}{
340 "fullnameOverride": "pcloud-ingress-public",
341 "controller": map[string]interface{}{
342 "service": map[string]interface{}{
343 "type": "LoadBalancer",
344 },
345 "ingressClassByName": true,
346 "ingressClassResource": map[string]interface{}{
347 "name": "pcloud-ingress-public",
348 "enabled": true,
349 "default": false,
350 "controllerValue": "k8s.io/pcloud-ingress-public",
351 },
352 "config": map[string]interface{}{
353 "proxy-body-size": "100M",
354 },
355 },
356 }
357 installer := action.NewInstall(config)
358 installer.Namespace = "pcloud-ingress-public"
359 installer.CreateNamespace = true
360 installer.ReleaseName = "ingress-public"
361 installer.Wait = true
362 installer.WaitForJobs = true
363 installer.Timeout = 20 * time.Minute
364 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
365 return err
366 }
367 return nil
368}
369
370func installCertManager() error {
371 config, err := createActionConfig("pcloud-cert-manager")
372 if err != nil {
373 return err
374 }
375 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager"))
376 if err != nil {
377 return err
378 }
379 values := map[string]interface{}{
380 "fullnameOverride": "pcloud-cert-manager",
381 "installCRDs": true,
382 "image": map[string]interface{}{
383 "tag": "v1.11.1",
384 "pullPolicy": "IfNotPresent",
385 },
386 }
387 installer := action.NewInstall(config)
388 installer.Namespace = "pcloud-cert-manager"
389 installer.CreateNamespace = true
390 installer.ReleaseName = "cert-manager"
391 installer.Wait = true
392 installer.WaitForJobs = true
393 installer.Timeout = 20 * time.Minute
394 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
395 return err
396 }
397 return nil
398}
399
400func installCertManagerWebhookGandi() error {
401 config, err := createActionConfig("pcloud-cert-manager")
402 if err != nil {
403 return err
404 }
405 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager-webhook-gandi"))
406 if err != nil {
407 return err
408 }
409 values := map[string]interface{}{
410 "fullnameOverride": "pcloud-cert-manager-webhook-gandi",
411 "certManager": map[string]interface{}{
412 "namespace": "pcloud-cert-manager",
413 "serviceAccountName": "pcloud-cert-manager",
414 },
415 "image": map[string]interface{}{
416 "repository": "giolekva/cert-manager-webhook-gandi",
417 "tag": "v0.2.0",
418 "pullPolicy": "IfNotPresent",
419 },
420 "logLevel": 2,
421 }
422 installer := action.NewInstall(config)
423 installer.Namespace = "pcloud-cert-manager"
424 installer.CreateNamespace = false
425 installer.ReleaseName = "cert-manager-webhook-gandi"
426 installer.Wait = true
427 installer.WaitForJobs = true
428 installer.Timeout = 20 * time.Minute
429 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
430 return err
431 }
432 return nil
433}
434
435func createActionConfig(namespace string) (*action.Configuration, error) {
giolekva8aa73e82022-07-09 11:34:39 +0400436 config := new(action.Configuration)
437 if err := config.Init(
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400438 kube.GetConfig(rootFlags.kubeConfig, "", namespace),
439 namespace,
giolekva8aa73e82022-07-09 11:34:39 +0400440 "",
441 func(fmtString string, args ...interface{}) {
442 fmt.Printf(fmtString, args...)
443 fmt.Println()
444 },
445 ); err != nil {
446 return nil, err
447 }
448 return config, nil
449}
450
giolekva8aa73e82022-07-09 11:34:39 +0400451func readAdminKeys() ([]byte, []byte, error) {
452 pubKey, err := os.ReadFile(bootstrapFlags.adminPubKey)
453 if err != nil {
454 return nil, nil, err
455 }
456 privKey, err := os.ReadFile(bootstrapFlags.adminPrivKey)
457 if err != nil {
458 return nil, nil, err
459 }
460 return pubKey, privKey, nil
461}