From 9b5ad098418d6a106c8a408cead890c6ed4a5bb0 Mon Sep 17 00:00:00 2001 From: Fedor Korotkov Date: Tue, 21 Mar 2023 15:36:55 -0400 Subject: [PATCH] Consolidate controller bootstrap login in `run` command (#38) --- Dockerfile | 6 +- internal/command/controller/controller.go | 4 +- internal/command/controller/init.go | 87 ++++------------------- internal/command/controller/run.go | 53 ++++++++++---- internal/controller/api.go | 1 - internal/controller/controller.go | 3 +- internal/controller/datadir.go | 9 +++ 7 files changed, 68 insertions(+), 95 deletions(-) diff --git a/Dockerfile b/Dockerfile index a008378..74bb70c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,9 +8,13 @@ RUN goreleaser build --single-target --snapshot --timeout 60m FROM gcr.io/distroless/base LABEL org.opencontainers.image.source=https://github.com/cirruslabs/orchard +ENV GIN_MODE=release ENV ORCHARD_HOME=/data EXPOSE 6120 COPY --from=builder /tmp/orchard/dist/orchard_linux_*/orchard /bin/orchard -CMD ["/bin/orchard"] +ENTRYPOINT ["/bin/orchard"] + +# default arguments to run controller +CMD ["controller", "run"] diff --git a/internal/command/controller/controller.go b/internal/command/controller/controller.go index 61e2051..2b5a958 100644 --- a/internal/command/controller/controller.go +++ b/internal/command/controller/controller.go @@ -12,10 +12,10 @@ var dataDirPath string func NewCommand() *cobra.Command { command := &cobra.Command{ Use: "controller", - Short: "Initialize and run a controller on the local machine", + Short: "Run a controller on the local machine", } - command.AddCommand(newInitCommand(), newRunCommand()) + command.AddCommand(newRunCommand()) orchardHome, err := orchardhome.Path() if err != nil { diff --git a/internal/command/controller/init.go b/internal/command/controller/init.go index 5266893..5e8021b 100644 --- a/internal/command/controller/init.go +++ b/internal/command/controller/init.go @@ -12,8 +12,6 @@ import ( "fmt" "github.com/cirruslabs/orchard/internal/controller" "github.com/cirruslabs/orchard/internal/netconstants" - v1 "github.com/cirruslabs/orchard/pkg/resource/v1" - "github.com/spf13/cobra" "math/big" "time" ) @@ -24,86 +22,25 @@ var controllerCertPath string var controllerKeyPath string var serviceAccountName string var serviceAccountToken string -var force bool - -func newInitCommand() *cobra.Command { - command := &cobra.Command{ - Use: "init", - Short: "Initialize the controller", - RunE: runInit, - } - - command.PersistentFlags().StringVar(&controllerCertPath, "controller-cert", "", - "do not auto-generate the controller certificate, import it from the specified path instead"+ - " (requires --controller-key)") - command.PersistentFlags().StringVar(&controllerKeyPath, "controller-key", "", - "do not auto-generate the controller certificate key, import it from the specified path instead"+ - " (requires --controller-cert)") - command.PersistentFlags().StringVar(&serviceAccountName, "service-account-name", "admin", - "name of the service account with maximum privileges to create") - command.PersistentFlags().StringVar(&serviceAccountToken, "service-account-token", "", - "token to use when creating the service account with maximum privileges") - command.PersistentFlags().BoolVar(&force, "force", false, - "force re-initialization if the controller is already initialized") - - return command -} - -func runInit(cmd *cobra.Command, args []string) (err error) { - if serviceAccountToken == "" { - return fmt.Errorf("%w: --service-account-token is required", ErrInitFailed) - } - - dataDir, err := controller.NewDataDir(dataDirPath) - if err != nil { - return err - } - - initialized, err := dataDir.Initialized() - if err != nil { - return err - } - - if initialized && !force { - return fmt.Errorf("%w: controller is already initialized, preventing overwrite; "+ - "please specify \"--force\" to re-initialize", ErrInitFailed) - } - - var controllerCert tls.Certificate +func FindControllerCertificate(dataDir *controller.DataDir) (controllerCert tls.Certificate, err error) { if controllerCertPath != "" || controllerKeyPath != "" { - if err := checkBothCertAndKeyAreSpecified(); err != nil { - return err + // if external certificate is specified, use it + if err = checkBothCertAndKeyAreSpecified(); err != nil { + return controllerCert, err } - - controllerCert, err = tls.LoadX509KeyPair(controllerCertPath, controllerCertPath) - if err != nil { - return err - } - } else { + return tls.LoadX509KeyPair(controllerCertPath, controllerCertPath) + } else if !dataDir.ControllerCertificateExists() { + // otherwise, generate a self-signed certificate if it's not already present controllerCert, err = GenerateSelfSignedControllerCertificate() if err != nil { - return err + return controllerCert, err + } + if err = dataDir.SetControllerCertificate(controllerCert); err != nil { + return controllerCert, err } } - - if err := dataDir.SetControllerCertificate(controllerCert); err != nil { - return err - } - - // Run the controller to create the service account with maximum privileges - controller, err := controller.New(controller.WithDataDir(dataDir)) - if err != nil { - return err - } - - return controller.EnsureServiceAccount(&v1.ServiceAccount{ - Meta: v1.Meta{ - Name: serviceAccountName, - }, - Token: serviceAccountToken, - Roles: v1.AllServiceAccountRoles(), - }) + return } func checkBothCertAndKeyAreSpecified() error { diff --git a/internal/command/controller/run.go b/internal/command/controller/run.go index 8ed10ad..2446c1a 100644 --- a/internal/command/controller/run.go +++ b/internal/command/controller/run.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/cirruslabs/orchard/internal/controller" "github.com/cirruslabs/orchard/internal/netconstants" + v1 "github.com/cirruslabs/orchard/pkg/resource/v1" "github.com/spf13/cobra" "go.uber.org/zap" "os" @@ -30,6 +31,20 @@ func newRunCommand() *cobra.Command { cmd.PersistentFlags().StringVarP(&address, "listen", "l", fmt.Sprintf(":%s", port), "address to listen on") + // flags for auto-init if necessary + // this simplifies the user experience to run the controller in serverless environments + cmd.PersistentFlags().StringVar(&controllerCertPath, "controller-cert", "", + "use the controller certificate from the specified path instead of the auto-generated one"+ + " (requires --controller-key)") + cmd.PersistentFlags().StringVar(&controllerKeyPath, "controller-key", "", + "use the controller certificate key from the specified path instead of the auto-generated one"+ + " (requires --controller-cert)") + cmd.PersistentFlags().StringVar(&serviceAccountName, "superuser-account-name", "", + "optional name of a service account with maximum privileges to auto-create") + cmd.PersistentFlags().StringVar(&serviceAccountToken, "superuser-account-token", "", + "token to use when creating a service account with maximum privileges "+ + "(required when --admin-account-name is specified)") + return cmd } @@ -51,22 +66,20 @@ func runController(cmd *cobra.Command, args []string) (err error) { return err } - initialized, err := dataDir.Initialized() - if err != nil { - return err + var controllerCert tls.Certificate + if dataDir.ControllerCertificateExists() { + controllerCert, err = dataDir.ControllerCertificate() + if err != nil { + return err + } + } else { + controllerCert, err = FindControllerCertificate(dataDir) + if err != nil { + return err + } } - if !initialized { - return fmt.Errorf("%w: data directory is not initialized, please run \"orchard controller init\" first", - ErrRunFailed) - } - - controllerCert, err := dataDir.ControllerCertificate() - if err != nil { - return err - } - - controller, err := controller.New( + controllerInstance, err := controller.New( controller.WithListenAddr(address), controller.WithDataDir(dataDir), controller.WithLogger(logger), @@ -81,5 +94,15 @@ func runController(cmd *cobra.Command, args []string) (err error) { return err } - return controller.Run(cmd.Context()) + if serviceAccountName != "" { + err = controllerInstance.EnsureServiceAccount(&v1.ServiceAccount{ + Meta: v1.Meta{ + Name: serviceAccountName, + }, + Token: serviceAccountToken, + Roles: v1.AllServiceAccountRoles(), + }) + } + + return controllerInstance.Run(cmd.Context()) } diff --git a/internal/controller/api.go b/internal/controller/api.go index b1ca810..ba5a964 100644 --- a/internal/controller/api.go +++ b/internal/controller/api.go @@ -19,7 +19,6 @@ const ctxServiceAccountKey = "service-account" var ErrUnauthorized = errors.New("unauthorized") func (controller *Controller) initAPI() *gin.Engine { - gin.SetMode(gin.DebugMode) ginEngine := gin.Default() // v1 API diff --git a/internal/controller/controller.go b/internal/controller/controller.go index c9d9b70..66bc048 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -116,7 +116,8 @@ func (controller *Controller) EnsureServiceAccount(serviceAccount *v1.ServiceAcc } if serviceAccount.Token == "" { - serviceAccount.Token = uuid.New().String() + return fmt.Errorf("%w: attempted to create a service account with an empty token", + ErrAdminTaskFailed) } serviceAccount.CreatedAt = time.Now() diff --git a/internal/controller/datadir.go b/internal/controller/datadir.go index 00890ca..d94377f 100644 --- a/internal/controller/datadir.go +++ b/internal/controller/datadir.go @@ -70,6 +70,15 @@ func (dataDir *DataDir) DBPath() string { return filepath.Join(dataDir.path, "db") } +func (dataDir *DataDir) ControllerCertificateExists() bool { + return fileExist(dataDir.ControllerCertificatePath()) && fileExist(dataDir.ControllerKeyPath()) +} + +func fileExist(path string) bool { + _, err := os.Stat(path) + return err == nil +} + func (dataDir *DataDir) ControllerCertificatePath() string { return filepath.Join(dataDir.path, "controller.crt") }