blob: e5f3f2b1d9459d13f9e3a968622ae3c025afa393 [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
giolekva8aa73e82022-07-09 11:34:39 +040015 "github.com/spf13/cobra"
16 "helm.sh/helm/v3/pkg/action"
17 "helm.sh/helm/v3/pkg/chart/loader"
18 "helm.sh/helm/v3/pkg/kube"
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040019
20 "github.com/giolekva/pcloud/core/installer"
21 "github.com/giolekva/pcloud/core/installer/soft"
giolekva8aa73e82022-07-09 11:34:39 +040022)
23
24var bootstrapFlags struct {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040025 chartsDir string
26 adminPubKey string
27 adminPrivKey string
28 storageDir string
29 volumeDefaultReplicaCount int
30 softServeIP string
giolekva8aa73e82022-07-09 11:34:39 +040031}
32
33func bootstrapCmd() *cobra.Command {
34 cmd := &cobra.Command{
35 Use: "bootstrap",
36 RunE: bootstrapCmdRun,
37 }
38 cmd.Flags().StringVar(
39 &bootstrapFlags.chartsDir,
40 "charts-dir",
41 "",
42 "",
43 )
44 cmd.Flags().StringVar(
45 &bootstrapFlags.adminPubKey,
46 "admin-pub-key",
47 "",
48 "",
49 )
50 cmd.Flags().StringVar(
51 &bootstrapFlags.adminPrivKey,
52 "admin-priv-key",
53 "",
54 "",
55 )
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040056 cmd.Flags().StringVar(
57 &bootstrapFlags.storageDir,
58 "storage-dir",
59 "",
60 "",
61 )
62 cmd.Flags().IntVar(
63 &bootstrapFlags.volumeDefaultReplicaCount,
64 "volume-default-replica-count",
65 3,
66 "",
67 )
68 cmd.Flags().StringVar(
69 &bootstrapFlags.softServeIP,
70 "soft-serve-ip",
71 "",
72 "",
73 )
giolekva8aa73e82022-07-09 11:34:39 +040074 return cmd
75}
76
77func bootstrapCmdRun(cmd *cobra.Command, args []string) error {
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +040078 adminPubKey, adminPrivKey, err := readAdminKeys()
79 if err != nil {
80 return err
81 }
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +040082 softServePub, softServePriv, err := installer.GenerateSSHKeys()
83 if err != nil {
84 return err
85 }
86 if err := installMetallbNamespace(); err != nil {
87 return err
88 }
89 if err := installMetallb(); err != nil {
90 return err
91 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040092 time.Sleep(1 * time.Minute)
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +040093 if err := installMetallbConfig(); err != nil {
94 return err
95 }
96 if err := installLonghorn(); err != nil {
97 return err
98 }
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +040099 time.Sleep(2 * time.Minute)
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +0400100 if err := installSoftServe(softServePub, softServePriv, string(adminPubKey)); err != nil {
101 return err
102 }
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +0400103 time.Sleep(2 * time.Minute)
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400104 ss, err := soft.NewClient(bootstrapFlags.softServeIP, 22, adminPrivKey, log.Default())
105 if err != nil {
106 return err
107 }
108 fluxPub, fluxPriv, err := installer.GenerateSSHKeys()
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +0400109 if err != nil {
110 return err
111 }
112 if err := ss.AddUser("flux", fluxPub); err != nil {
113 return err
114 }
115 if err := ss.MakeUserAdmin("flux"); err != nil {
116 return err
117 }
118 fmt.Println("Creating /pcloud repo")
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +0400119 if err := ss.AddRepository("pcloud", "# PCloud Systems"); err != nil {
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +0400120 return err
121 }
122 fmt.Println("Installing Flux")
123 if err := installFlux("ssh://soft-serve.pcloud.svc.cluster.local:22/pcloud", "soft-serve.pcloud.svc.cluster.local", softServePub, fluxPriv); err != nil {
124 return err
125 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400126 pcloudRepo, err := ss.GetRepo("pcloud") // TODO(giolekva): configurable
127 if err != nil {
128 return err
129 }
130 if err := configurePCloudRepo(installer.NewRepoIO(pcloudRepo, ss.Signer)); err != nil {
131 return err
132 }
Giorgi Lekveishvili677b4572023-05-26 15:02:37 +0400133 // TODO(giolekva): everything below must be installed using Flux
134 if err := installIngressPublic(); err != nil {
135 return err
136 }
137 if err := installCertManager(); err != nil {
138 return err
139 }
140 if err := installCertManagerWebhookGandi(); err != nil {
141 return err
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400142 }
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400143 // TODO(giolekva): ideally should be installed automatically if any of the user installed apps requires it
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +0400144 if err := installSmbDriver(); err != nil {
145 return err
146 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400147 return nil
148}
149
150func installMetallbNamespace() error {
151 fmt.Println("Installing metallb namespace")
152 // config, err := createActionConfig("default")
153 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400154 if err != nil {
155 return err
156 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400157 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "namespace"))
giolekva8aa73e82022-07-09 11:34:39 +0400158 if err != nil {
159 return err
160 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400161 values := map[string]interface{}{
162 // "namespace": "pcloud-metallb",
163 "namespace": "metallb-system",
164 "labels": []string{
165 "pod-security.kubernetes.io/audit: privileged",
166 "pod-security.kubernetes.io/enforce: privileged",
167 "pod-security.kubernetes.io/warn: privileged",
168 },
169 }
170 installer := action.NewInstall(config)
171 installer.Namespace = "pcloud"
172 installer.ReleaseName = "metallb-ns"
173 installer.Wait = true
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400174 installer.WaitForJobs = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400175 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
176 return err
177 }
178 return nil
179}
180
181func installMetallb() error {
182 fmt.Println("Installing metallb")
183 // config, err := createActionConfig("default")
184 config, err := createActionConfig("metallb-system")
giolekva8aa73e82022-07-09 11:34:39 +0400185 if err != nil {
186 return err
187 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400188 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "metallb"))
giolekva8aa73e82022-07-09 11:34:39 +0400189 if err != nil {
190 return err
191 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400192 values := map[string]interface{}{ // TODO(giolekva): add loadBalancerClass?
193 "controller": map[string]interface{}{
194 "image": map[string]interface{}{
195 "repository": "quay.io/metallb/controller",
196 "tag": "v0.13.9",
197 "pullPolicy": "IfNotPresent",
198 },
199 "logLevel": "info",
200 },
201 "speaker": map[string]interface{}{
202 "image": map[string]interface{}{
203 "repository": "quay.io/metallb/speaker",
204 "tag": "v0.13.9",
205 "pullPolicy": "IfNotPresent",
206 },
207 "logLevel": "info",
208 },
209 }
210 installer := action.NewInstall(config)
211 installer.Namespace = "metallb-system" // "pcloud-metallb"
212 installer.CreateNamespace = true
213 installer.ReleaseName = "metallb"
214 installer.IncludeCRDs = true
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400215 installer.Wait = true
216 installer.WaitForJobs = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400217 installer.Timeout = 20 * time.Minute
218 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400219 return err
220 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400221 return nil
222}
223
224func installMetallbConfig() error {
225 fmt.Println("Installing metallb-config")
226 // config, err := createActionConfig("default")
227 config, err := createActionConfig("metallb-system")
228 if err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400229 return err
230 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400231 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "metallb-config"))
232 if err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400233 return err
234 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400235 values := map[string]interface{}{
236 "from": "192.168.0.210",
237 "to": "192.168.0.240",
238 }
239 installer := action.NewInstall(config)
240 installer.Namespace = "metallb-system" // "pcloud-metallb"
241 installer.CreateNamespace = true
242 installer.ReleaseName = "metallb-cfg"
243 installer.Wait = true
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400244 installer.WaitForJobs = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400245 installer.Timeout = 20 * time.Minute
246 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
247 return err
248 }
249 return nil
250}
251
252func installLonghorn() error {
253 fmt.Println("Installing Longhorn")
254 config, err := createActionConfig("pcloud")
255 if err != nil {
256 return err
257 }
258 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "longhorn"))
259 if err != nil {
260 return err
261 }
262 values := map[string]interface{}{
263 "defaultSettings": map[string]interface{}{
264 "defaultDataPath": bootstrapFlags.storageDir,
265 },
266 "persistence": map[string]interface{}{
267 "defaultClassReplicaCount": bootstrapFlags.volumeDefaultReplicaCount,
268 },
269 "service": map[string]interface{}{
270 "ui": map[string]interface{}{
271 "type": "LoadBalancer",
272 },
273 },
274 "ingress": map[string]interface{}{
275 "enabled": false,
276 },
277 }
278 installer := action.NewInstall(config)
279 installer.Namespace = "longhorn-system"
280 installer.CreateNamespace = true
281 installer.ReleaseName = "longhorn"
282 installer.Wait = true
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400283 installer.WaitForJobs = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400284 installer.Timeout = 20 * time.Minute
285 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400286 return err
287 }
288 return nil
289}
290
291func installSoftServe(pubKey, privKey, adminKey string) error {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400292 fmt.Println("Installing SoftServe")
293 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400294 if err != nil {
295 return err
296 }
297 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "soft-serve"))
298 if err != nil {
299 return err
300 }
301 values := map[string]interface{}{
302 "privateKey": privKey,
303 "publicKey": pubKey,
304 "adminKey": adminKey,
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400305 "reservedIP": bootstrapFlags.softServeIP,
giolekva8aa73e82022-07-09 11:34:39 +0400306 }
307 installer := action.NewInstall(config)
308 installer.Namespace = "pcloud"
309 installer.CreateNamespace = true
310 installer.ReleaseName = "soft-serve"
311 installer.Wait = true
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400312 installer.WaitForJobs = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400313 installer.Timeout = 20 * time.Minute
giolekva8aa73e82022-07-09 11:34:39 +0400314 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
315 return err
316 }
317 return nil
318}
319
320func installFlux(repoAddr, repoHost, repoHostPubKey, privateKey string) error {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400321 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400322 if err != nil {
323 return err
324 }
325 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "flux-bootstrap"))
326 if err != nil {
327 return err
328 }
329 values := map[string]interface{}{
330 "repositoryAddress": repoAddr,
331 "repositoryHost": repoHost,
332 "repositoryHostPublicKey": repoHostPubKey,
333 "privateKey": privateKey,
334 }
335 installer := action.NewInstall(config)
336 installer.Namespace = "pcloud"
337 installer.CreateNamespace = true
338 installer.ReleaseName = "flux"
339 installer.Wait = true
340 installer.WaitForJobs = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400341 installer.Timeout = 20 * time.Minute
giolekva8aa73e82022-07-09 11:34:39 +0400342 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
343 return err
344 }
345 return nil
346}
347
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400348func installIngressPublic() error {
349 config, err := createActionConfig("pcloud")
350 if err != nil {
351 return err
352 }
353 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "ingress-nginx"))
354 if err != nil {
355 return err
356 }
357 values := map[string]interface{}{
358 "fullnameOverride": "pcloud-ingress-public",
359 "controller": map[string]interface{}{
360 "service": map[string]interface{}{
361 "type": "LoadBalancer",
362 },
363 "ingressClassByName": true,
364 "ingressClassResource": map[string]interface{}{
365 "name": "pcloud-ingress-public",
366 "enabled": true,
367 "default": false,
368 "controllerValue": "k8s.io/pcloud-ingress-public",
369 },
370 "config": map[string]interface{}{
371 "proxy-body-size": "100M",
372 },
373 },
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +0400374 "udp": map[string]interface{}{
375 "6881": "lekva-app-qbittorrent/torrent:6881",
376 },
377 "tcp": map[string]interface{}{
378 "6881": "lekva-app-qbittorrent/torrent:6881",
379 },
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400380 }
381 installer := action.NewInstall(config)
382 installer.Namespace = "pcloud-ingress-public"
383 installer.CreateNamespace = true
384 installer.ReleaseName = "ingress-public"
385 installer.Wait = true
386 installer.WaitForJobs = true
387 installer.Timeout = 20 * time.Minute
388 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
389 return err
390 }
391 return nil
392}
393
394func installCertManager() error {
395 config, err := createActionConfig("pcloud-cert-manager")
396 if err != nil {
397 return err
398 }
399 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager"))
400 if err != nil {
401 return err
402 }
403 values := map[string]interface{}{
404 "fullnameOverride": "pcloud-cert-manager",
405 "installCRDs": true,
406 "image": map[string]interface{}{
407 "tag": "v1.11.1",
408 "pullPolicy": "IfNotPresent",
409 },
410 }
411 installer := action.NewInstall(config)
412 installer.Namespace = "pcloud-cert-manager"
413 installer.CreateNamespace = true
414 installer.ReleaseName = "cert-manager"
415 installer.Wait = true
416 installer.WaitForJobs = true
417 installer.Timeout = 20 * time.Minute
418 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
419 return err
420 }
421 return nil
422}
423
424func installCertManagerWebhookGandi() error {
425 config, err := createActionConfig("pcloud-cert-manager")
426 if err != nil {
427 return err
428 }
429 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager-webhook-gandi"))
430 if err != nil {
431 return err
432 }
433 values := map[string]interface{}{
434 "fullnameOverride": "pcloud-cert-manager-webhook-gandi",
435 "certManager": map[string]interface{}{
436 "namespace": "pcloud-cert-manager",
437 "serviceAccountName": "pcloud-cert-manager",
438 },
439 "image": map[string]interface{}{
440 "repository": "giolekva/cert-manager-webhook-gandi",
441 "tag": "v0.2.0",
442 "pullPolicy": "IfNotPresent",
443 },
444 "logLevel": 2,
445 }
446 installer := action.NewInstall(config)
447 installer.Namespace = "pcloud-cert-manager"
448 installer.CreateNamespace = false
449 installer.ReleaseName = "cert-manager-webhook-gandi"
450 installer.Wait = true
451 installer.WaitForJobs = true
452 installer.Timeout = 20 * time.Minute
453 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
454 return err
455 }
456 return nil
457}
458
Giorgi Lekveishvilid6e80cc2023-06-09 17:38:49 +0400459func installSmbDriver() error {
460 config, err := createActionConfig("pcloud-csi-driver-smb")
461 if err != nil {
462 return err
463 }
464 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "csi-driver-smb"))
465 if err != nil {
466 return err
467 }
468 values := map[string]interface{}{}
469 installer := action.NewInstall(config)
470 installer.Namespace = "pcloud-csi-driver-smb"
471 installer.CreateNamespace = true
472 installer.ReleaseName = "csi-driver-smb"
473 installer.Wait = true
474 installer.WaitForJobs = true
475 installer.Timeout = 20 * time.Minute
476 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
477 return err
478 }
479 return nil
480}
481
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400482func configurePCloudRepo(repo installer.RepoIO) error {
483 kust := installer.NewKustomization()
484 kust.AddResources("pcloud-flux", "environments")
485 if err := repo.WriteKustomization("kustomization.yaml", kust); err != nil {
486 return err
487 }
488 if err := repo.WriteKustomization("environments/kustomization.yaml", installer.NewKustomization()); err != nil {
489 return err
490 }
491 return repo.CommitAndPush("initialize pcloud directory structure, environments with kustomization.yaml-s")
492}
493
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400494func createActionConfig(namespace string) (*action.Configuration, error) {
giolekva8aa73e82022-07-09 11:34:39 +0400495 config := new(action.Configuration)
496 if err := config.Init(
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400497 kube.GetConfig(rootFlags.kubeConfig, "", namespace),
498 namespace,
giolekva8aa73e82022-07-09 11:34:39 +0400499 "",
500 func(fmtString string, args ...interface{}) {
501 fmt.Printf(fmtString, args...)
502 fmt.Println()
503 },
504 ); err != nil {
505 return nil, err
506 }
507 return config, nil
508}
509
giolekva8aa73e82022-07-09 11:34:39 +0400510func readAdminKeys() ([]byte, []byte, error) {
511 pubKey, err := os.ReadFile(bootstrapFlags.adminPubKey)
512 if err != nil {
513 return nil, nil, err
514 }
515 privKey, err := os.ReadFile(bootstrapFlags.adminPrivKey)
516 if err != nil {
517 return nil, nil, err
518 }
519 return pubKey, privKey, nil
520}