From 9919117b9bedf39fbb6ba77414f696d0f6649abd Mon Sep 17 00:00:00 2001 From: Nikolay Edigaryev Date: Thu, 27 Mar 2025 18:48:04 +0400 Subject: [PATCH] orchard controller run: create a default bootstrap context (#291) * orchard controller run: create a default bootstrap context * Dockerfile: correct AS casing * Fix typo in BootstrapContextName --- Dockerfile | 2 +- internal/command/controller/bootstrap.go | 30 ++++++++---- internal/command/controller/run.go | 59 +++++++++++++++++++++++- internal/controller/controller.go | 6 +-- 4 files changed, 83 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8c9bb94..3fd1aef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:latest as builder +FROM golang:latest AS builder # Install GoReleaser Pro RUN echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | tee /etc/apt/sources.list.d/goreleaser.list diff --git a/internal/command/controller/bootstrap.go b/internal/command/controller/bootstrap.go index fee4a1f..c6c03e7 100644 --- a/internal/command/controller/bootstrap.go +++ b/internal/command/controller/bootstrap.go @@ -7,27 +7,39 @@ import ( "github.com/cirruslabs/orchard/internal/controller" v1 "github.com/cirruslabs/orchard/pkg/resource/v1" "github.com/pterm/pterm" + "github.com/samber/lo" "github.com/sethvargo/go-password/password" "os" "strings" ) +const BootstrapContextName = "bootstrap-context" const BootstrapAdminName = "bootstrap-admin" -func Bootstrap(controllerInstance *controller.Controller, controllerCert tls.Certificate) error { +func Bootstrap(controllerInstance *controller.Controller, controllerCert tls.Certificate) (string, string, error) { // Determine if we need to do anything at all orchardBootstrapAdminToken, orchardBootstrapAdminTokenPresent := os.LookupEnv("ORCHARD_BOOTSTRAP_ADMIN_TOKEN") - numServiceAccounts, err := controllerInstance.NumServiceAccounts() + serviceAccounts, err := controllerInstance.ServiceAccounts() if err != nil { - return fmt.Errorf("failed to retrieve the number of service accounts: %w", err) + return "", "", fmt.Errorf("failed to retrieve the number of service accounts: %w", err) } - if numServiceAccounts != 0 && !orchardBootstrapAdminTokenPresent { + if len(serviceAccounts) != 0 && !orchardBootstrapAdminTokenPresent { // No bootstrap is needed because there are service accounts // present in the database (so it's not the first start) // and no bootstrap admin token change is requested - return nil + // + // However, if the BootstrapAdminName service account still exists, + // return its credentials. We'll use them for updating the + // BootstrapContextName context. + if serviceAccount, ok := lo.Find(serviceAccounts, func(serviceAccount *v1.ServiceAccount) bool { + return serviceAccount.Name == BootstrapAdminName + }); ok { + return serviceAccount.Name, serviceAccount.Token, nil + } + + return "", "", nil } // Generate a bootstrap admin token if not present in the environment variable @@ -48,14 +60,14 @@ func Bootstrap(controllerInstance *controller.Controller, controllerCert tls.Cer }, password.Symbols), }) if err != nil { - return fmt.Errorf("failed to generate bootstrap admin token: "+ + return "", "", fmt.Errorf("failed to generate bootstrap admin token: "+ "failed to initialize password generator: %w", err) } orchardBootstrapAdminToken, err = passwordGenerator.Generate(32, 10, 10, false, false) if err != nil { - return fmt.Errorf("failed to generate bootstrap admin token: %w", err) + return "", "", fmt.Errorf("failed to generate bootstrap admin token: %w", err) } } @@ -67,7 +79,7 @@ func Bootstrap(controllerInstance *controller.Controller, controllerCert tls.Cer Token: orchardBootstrapAdminToken, Roles: v1.AllServiceAccountRoles(), }); err != nil { - return err + return "", "", err } // Report bootstrapping result to the user @@ -98,5 +110,5 @@ func Bootstrap(controllerInstance *controller.Controller, controllerCert tls.Cer pterm.Info.Print(messages...) - return nil + return BootstrapAdminName, orchardBootstrapAdminToken, nil } diff --git a/internal/command/controller/run.go b/internal/command/controller/run.go index 7e67a7d..ce8fbdf 100644 --- a/internal/command/controller/run.go +++ b/internal/command/controller/run.go @@ -2,8 +2,10 @@ package controller import ( "crypto/tls" + "encoding/pem" "errors" "fmt" + configpkg "github.com/cirruslabs/orchard/internal/config" "github.com/cirruslabs/orchard/internal/controller" "github.com/cirruslabs/orchard/internal/netconstants" "github.com/gin-gonic/gin" @@ -148,9 +150,64 @@ func runController(cmd *cobra.Command, args []string) (err error) { return err } - if err := Bootstrap(controllerInstance, controllerCert); err != nil { + // Return bootstrap service account credentials, optionally creating it + serviceAccountName, serviceAccountToken, err := Bootstrap(controllerInstance, controllerCert) + if err != nil { return err } + // If bootstrap service account still exists, update a context for it, + // optionally making this context the default one + if serviceAccountName != "" && serviceAccountToken != "" { + if err := createBootstrapContext(controllerInstance.Address(), controllerCert, + serviceAccountName, serviceAccountToken); err != nil { + return err + } + } + return controllerInstance.Run(cmd.Context()) } + +func createBootstrapContext( + controllerAddress string, + controllerCert tls.Certificate, + serviceAccountName string, + serviceAccountToken string, +) error { + configHandle, err := configpkg.NewHandle() + if err != nil { + return err + } + + context := configpkg.Context{ + URL: controllerAddress, + ServiceAccountName: serviceAccountName, + ServiceAccountToken: serviceAccountToken, + } + + if !noTLS { + certificatePEMBytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: controllerCert.Certificate[0], + }) + + context.Certificate = certificatePEMBytes + } + + if err := configHandle.CreateContext(BootstrapContextName, context, true); err != nil { + return err + } + + config, err := configHandle.Config() + if err != nil { + return err + } + + if config.DefaultContext == "" { + if err := configHandle.SetDefaultContext(BootstrapContextName); err != nil { + return err + } + } + + return nil +} diff --git a/internal/controller/controller.go b/internal/controller/controller.go index f8e6dd0..e1fbf42 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -188,7 +188,7 @@ func New(opts ...Option) (*Controller, error) { return controller, nil } -func (controller *Controller) NumServiceAccounts() (int, error) { +func (controller *Controller) ServiceAccounts() ([]*v1.ServiceAccount, error) { var serviceAccounts []*v1.ServiceAccount var err error @@ -197,10 +197,10 @@ func (controller *Controller) NumServiceAccounts() (int, error) { return err }); err != nil { - return 0, fmt.Errorf("failed to retrieve a list of service accounts: %w", err) + return nil, fmt.Errorf("failed to retrieve a list of service accounts: %w", err) } - return len(serviceAccounts), nil + return serviceAccounts, nil } func (controller *Controller) EnsureServiceAccount(serviceAccount *v1.ServiceAccount) error {