blob: a11cfac3ba5468a52b0f8efaaa8ddae4c6b9bfbd [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 Lekveishvili23ef7f82023-05-26 11:57:48 +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
133 }
134 }
135 return nil
136}
137
138func installMetallbNamespace() error {
139 fmt.Println("Installing metallb namespace")
140 // config, err := createActionConfig("default")
141 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400142 if err != nil {
143 return err
144 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400145 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "namespace"))
giolekva8aa73e82022-07-09 11:34:39 +0400146 if err != nil {
147 return err
148 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400149 values := map[string]interface{}{
150 // "namespace": "pcloud-metallb",
151 "namespace": "metallb-system",
152 "labels": []string{
153 "pod-security.kubernetes.io/audit: privileged",
154 "pod-security.kubernetes.io/enforce: privileged",
155 "pod-security.kubernetes.io/warn: privileged",
156 },
157 }
158 installer := action.NewInstall(config)
159 installer.Namespace = "pcloud"
160 installer.ReleaseName = "metallb-ns"
161 installer.Wait = true
162 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
163 return err
164 }
165 return nil
166}
167
168func installMetallb() error {
169 fmt.Println("Installing metallb")
170 // config, err := createActionConfig("default")
171 config, err := createActionConfig("metallb-system")
giolekva8aa73e82022-07-09 11:34:39 +0400172 if err != nil {
173 return err
174 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400175 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "metallb"))
giolekva8aa73e82022-07-09 11:34:39 +0400176 if err != nil {
177 return err
178 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400179 values := map[string]interface{}{ // TODO(giolekva): add loadBalancerClass?
180 "controller": map[string]interface{}{
181 "image": map[string]interface{}{
182 "repository": "quay.io/metallb/controller",
183 "tag": "v0.13.9",
184 "pullPolicy": "IfNotPresent",
185 },
186 "logLevel": "info",
187 },
188 "speaker": map[string]interface{}{
189 "image": map[string]interface{}{
190 "repository": "quay.io/metallb/speaker",
191 "tag": "v0.13.9",
192 "pullPolicy": "IfNotPresent",
193 },
194 "logLevel": "info",
195 },
196 }
197 installer := action.NewInstall(config)
198 installer.Namespace = "metallb-system" // "pcloud-metallb"
199 installer.CreateNamespace = true
200 installer.ReleaseName = "metallb"
201 installer.IncludeCRDs = true
202 // installer.Wait = true
203 installer.Timeout = 20 * time.Minute
204 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400205 return err
206 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400207 return nil
208}
209
210func installMetallbConfig() error {
211 fmt.Println("Installing metallb-config")
212 // config, err := createActionConfig("default")
213 config, err := createActionConfig("metallb-system")
214 if err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400215 return err
216 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400217 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "metallb-config"))
218 if err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400219 return err
220 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400221 values := map[string]interface{}{
222 "from": "192.168.0.210",
223 "to": "192.168.0.240",
224 }
225 installer := action.NewInstall(config)
226 installer.Namespace = "metallb-system" // "pcloud-metallb"
227 installer.CreateNamespace = true
228 installer.ReleaseName = "metallb-cfg"
229 installer.Wait = true
230 installer.Timeout = 20 * time.Minute
231 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
232 return err
233 }
234 return nil
235}
236
237func installLonghorn() error {
238 fmt.Println("Installing Longhorn")
239 config, err := createActionConfig("pcloud")
240 if err != nil {
241 return err
242 }
243 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "longhorn"))
244 if err != nil {
245 return err
246 }
247 values := map[string]interface{}{
248 "defaultSettings": map[string]interface{}{
249 "defaultDataPath": bootstrapFlags.storageDir,
250 },
251 "persistence": map[string]interface{}{
252 "defaultClassReplicaCount": bootstrapFlags.volumeDefaultReplicaCount,
253 },
254 "service": map[string]interface{}{
255 "ui": map[string]interface{}{
256 "type": "LoadBalancer",
257 },
258 },
259 "ingress": map[string]interface{}{
260 "enabled": false,
261 },
262 }
263 installer := action.NewInstall(config)
264 installer.Namespace = "longhorn-system"
265 installer.CreateNamespace = true
266 installer.ReleaseName = "longhorn"
267 installer.Wait = true
268 installer.Timeout = 20 * time.Minute
269 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +0400270 return err
271 }
272 return nil
273}
274
275func installSoftServe(pubKey, privKey, adminKey string) error {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400276 fmt.Println("Installing SoftServe")
277 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400278 if err != nil {
279 return err
280 }
281 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "soft-serve"))
282 if err != nil {
283 return err
284 }
285 values := map[string]interface{}{
286 "privateKey": privKey,
287 "publicKey": pubKey,
288 "adminKey": adminKey,
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400289 "reservedIP": bootstrapFlags.softServeIP,
giolekva8aa73e82022-07-09 11:34:39 +0400290 }
291 installer := action.NewInstall(config)
292 installer.Namespace = "pcloud"
293 installer.CreateNamespace = true
294 installer.ReleaseName = "soft-serve"
295 installer.Wait = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400296 installer.Timeout = 20 * time.Minute
giolekva8aa73e82022-07-09 11:34:39 +0400297 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
298 return err
299 }
300 return nil
301}
302
303func installFlux(repoAddr, repoHost, repoHostPubKey, privateKey string) error {
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400304 config, err := createActionConfig("pcloud")
giolekva8aa73e82022-07-09 11:34:39 +0400305 if err != nil {
306 return err
307 }
308 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "flux-bootstrap"))
309 if err != nil {
310 return err
311 }
312 values := map[string]interface{}{
313 "repositoryAddress": repoAddr,
314 "repositoryHost": repoHost,
315 "repositoryHostPublicKey": repoHostPubKey,
316 "privateKey": privateKey,
317 }
318 installer := action.NewInstall(config)
319 installer.Namespace = "pcloud"
320 installer.CreateNamespace = true
321 installer.ReleaseName = "flux"
322 installer.Wait = true
323 installer.WaitForJobs = true
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400324 installer.Timeout = 20 * time.Minute
giolekva8aa73e82022-07-09 11:34:39 +0400325 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
326 return err
327 }
328 return nil
329}
330
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400331func installIngressPublic() error {
332 config, err := createActionConfig("pcloud")
333 if err != nil {
334 return err
335 }
336 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "ingress-nginx"))
337 if err != nil {
338 return err
339 }
340 values := map[string]interface{}{
341 "fullnameOverride": "pcloud-ingress-public",
342 "controller": map[string]interface{}{
343 "service": map[string]interface{}{
344 "type": "LoadBalancer",
345 },
346 "ingressClassByName": true,
347 "ingressClassResource": map[string]interface{}{
348 "name": "pcloud-ingress-public",
349 "enabled": true,
350 "default": false,
351 "controllerValue": "k8s.io/pcloud-ingress-public",
352 },
353 "config": map[string]interface{}{
354 "proxy-body-size": "100M",
355 },
356 },
357 }
358 installer := action.NewInstall(config)
359 installer.Namespace = "pcloud-ingress-public"
360 installer.CreateNamespace = true
361 installer.ReleaseName = "ingress-public"
362 installer.Wait = true
363 installer.WaitForJobs = true
364 installer.Timeout = 20 * time.Minute
365 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
366 return err
367 }
368 return nil
369}
370
371func installCertManager() error {
372 config, err := createActionConfig("pcloud-cert-manager")
373 if err != nil {
374 return err
375 }
376 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager"))
377 if err != nil {
378 return err
379 }
380 values := map[string]interface{}{
381 "fullnameOverride": "pcloud-cert-manager",
382 "installCRDs": true,
383 "image": map[string]interface{}{
384 "tag": "v1.11.1",
385 "pullPolicy": "IfNotPresent",
386 },
387 }
388 installer := action.NewInstall(config)
389 installer.Namespace = "pcloud-cert-manager"
390 installer.CreateNamespace = true
391 installer.ReleaseName = "cert-manager"
392 installer.Wait = true
393 installer.WaitForJobs = true
394 installer.Timeout = 20 * time.Minute
395 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
396 return err
397 }
398 return nil
399}
400
401func installCertManagerWebhookGandi() error {
402 config, err := createActionConfig("pcloud-cert-manager")
403 if err != nil {
404 return err
405 }
406 chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager-webhook-gandi"))
407 if err != nil {
408 return err
409 }
410 values := map[string]interface{}{
411 "fullnameOverride": "pcloud-cert-manager-webhook-gandi",
412 "certManager": map[string]interface{}{
413 "namespace": "pcloud-cert-manager",
414 "serviceAccountName": "pcloud-cert-manager",
415 },
416 "image": map[string]interface{}{
417 "repository": "giolekva/cert-manager-webhook-gandi",
418 "tag": "v0.2.0",
419 "pullPolicy": "IfNotPresent",
420 },
421 "logLevel": 2,
422 }
423 installer := action.NewInstall(config)
424 installer.Namespace = "pcloud-cert-manager"
425 installer.CreateNamespace = false
426 installer.ReleaseName = "cert-manager-webhook-gandi"
427 installer.Wait = true
428 installer.WaitForJobs = true
429 installer.Timeout = 20 * time.Minute
430 if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
431 return err
432 }
433 return nil
434}
435
436func createActionConfig(namespace string) (*action.Configuration, error) {
giolekva8aa73e82022-07-09 11:34:39 +0400437 config := new(action.Configuration)
438 if err := config.Init(
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400439 kube.GetConfig(rootFlags.kubeConfig, "", namespace),
440 namespace,
giolekva8aa73e82022-07-09 11:34:39 +0400441 "",
442 func(fmtString string, args ...interface{}) {
443 fmt.Printf(fmtString, args...)
444 fmt.Println()
445 },
446 ); err != nil {
447 return nil, err
448 }
449 return config, nil
450}
451
giolekva8aa73e82022-07-09 11:34:39 +0400452func readAdminKeys() ([]byte, []byte, error) {
453 pubKey, err := os.ReadFile(bootstrapFlags.adminPubKey)
454 if err != nil {
455 return nil, nil, err
456 }
457 privKey, err := os.ReadFile(bootstrapFlags.adminPrivKey)
458 if err != nil {
459 return nil, nil, err
460 }
461 return pubKey, privKey, nil
462}