2020-08-27 16:38:44 -04:00
// Copyright (c) HashiCorp, Inc.
2020-12-18 12:12:25 -05:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-09-23 01:23:10 -04:00
package server
2020-02-12 13:53:27 -05:00
import (
2021-02-05 12:58:54 -07:00
"context"
2021-07-28 06:31:16 -04:00
stderrors "errors"
2020-02-12 13:53:27 -05:00
"fmt"
2020-12-18 12:12:25 -05:00
"net"
2021-07-28 14:35:58 -07:00
"os"
2020-02-12 13:53:27 -05:00
"runtime"
"strings"
2022-09-19 09:59:18 -04:00
"sync"
atm "sync/atomic"
2021-02-05 12:58:54 -07:00
"time"
2020-02-12 13:53:27 -05:00
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/config"
2022-03-28 17:16:39 +01:00
"github.com/hashicorp/boundary/internal/cmd/ops"
2022-05-09 12:45:10 -07:00
"github.com/hashicorp/boundary/internal/daemon/controller"
"github.com/hashicorp/boundary/internal/daemon/worker"
2021-01-25 14:43:50 -07:00
"github.com/hashicorp/boundary/internal/db"
2021-01-19 10:40:41 -07:00
"github.com/hashicorp/boundary/internal/db/schema"
2021-01-25 14:43:50 -07:00
"github.com/hashicorp/boundary/internal/errors"
2023-09-12 08:15:34 -04:00
"github.com/hashicorp/boundary/internal/event"
2021-01-25 14:43:50 -07:00
"github.com/hashicorp/boundary/internal/kms"
2020-02-12 13:53:27 -05:00
"github.com/hashicorp/go-hclog"
2021-07-14 17:33:23 -07:00
"github.com/hashicorp/go-secure-stdlib/mlock"
2021-07-14 17:50:15 -07:00
"github.com/hashicorp/go-secure-stdlib/parseutil"
2022-06-09 16:39:20 -04:00
"github.com/hashicorp/go-uuid"
2020-02-12 13:53:27 -05:00
"github.com/mitchellh/cli"
"github.com/posener/complete"
2021-02-25 17:57:10 -05:00
"go.uber.org/atomic"
2020-02-12 13:53:27 -05:00
)
2021-01-07 10:02:32 -05:00
var (
_ cli . Command = ( * Command )( nil )
_ cli . CommandAutocomplete = ( * Command )( nil )
)
2020-02-12 13:53:27 -05:00
2023-03-09 20:31:20 -05:00
var extraSelfTerminationConditionFuncs [] func ( * Command , chan struct {})
2023-03-07 16:29:34 -05:00
2020-03-10 15:48:04 -04:00
type Command struct {
* base . Server
2022-03-28 17:16:39 +01:00
opsServer * ops . Server
2020-02-12 13:53:27 -05:00
2021-02-25 17:57:10 -05:00
SighupCh chan struct {}
SigUSR2Ch chan struct {}
2020-02-12 13:53:27 -05:00
2022-09-06 16:01:52 +01:00
Config * config . Config
schemaManager * schema . Manager
controller * controller . Controller
worker * worker . Worker
2020-02-12 13:53:27 -05:00
2024-02-23 12:19:46 -05:00
flagConfig [] string
flagConfigKms string
flagLogLevel string
flagLogFormat string
flagCombineLogs bool
flagSkipPlugins bool
flagSkipAliasTargetCreation bool
flagWorkerDnsServer string
2021-02-25 17:57:10 -05:00
2023-05-17 18:12:19 -04:00
reloadedCh chan struct {} // for tests
startedCh chan struct {} // for tests
presetConfig * atomic . String // for tests
flagWorkerAuthWorkerRotationInterval time . Duration // for tests
flagWorkerAuthCaCertificateLifetime time . Duration // for tests
2024-06-17 12:26:55 -04:00
flagWorkerAuthCaReinitialize bool // for tests
2020-02-12 13:53:27 -05:00
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) Synopsis () string {
2020-09-23 01:23:10 -04:00
return "Start a Boundary server"
2020-02-12 13:53:27 -05:00
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) Help () string {
2020-02-12 13:53:27 -05:00
helpText := `
2020-09-23 01:23:10 -04:00
Usage: boundary server [options]
2020-02-12 13:53:27 -05:00
2020-09-23 01:23:10 -04:00
Start a server (controller, worker, or both) with a configuration file:
2020-02-12 13:53:27 -05:00
2020-09-23 01:23:10 -04:00
$ boundary server -config=/etc/boundary/controller.hcl
2020-02-12 13:53:27 -05:00
For a full list of examples, please see the documentation.
` + c . Flags (). Help ()
return strings . TrimSpace ( helpText )
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) Flags () * base . FlagSets {
2020-09-22 21:37:37 -04:00
set := c . FlagSet ( base . FlagSetNone )
2020-02-12 13:53:27 -05:00
f := set . NewFlagSet ( "Command Options" )
2022-09-29 09:01:33 -04:00
f . StringSliceVar ( & base . StringSliceVar {
2020-02-12 13:53:27 -05:00
Name : "config" ,
Target : & c . flagConfig ,
Completion : complete . PredictOr (
complete . PredictFiles ( "*.hcl" ),
complete . PredictFiles ( "*.json" ),
),
2022-09-29 09:01:33 -04:00
Usage : "Path to the configuration file. Can be specified multiple times for multiple configuration files (only if using HCL format)." ,
2020-08-27 16:38:44 -04:00
})
f . StringVar ( & base . StringVar {
Name : "config-kms" ,
Target : & c . flagConfigKms ,
Completion : complete . PredictOr (
complete . PredictFiles ( "*.hcl" ),
complete . PredictFiles ( "*.json" ),
),
Usage : `Path to a configuration file containing a "kms" block marked for "config" purpose, to perform decryption of the main configuration file. If not set, will look for such a block in the main configuration file, which has some drawbacks; see the help output for "boundary config encrypt -h" for details.` ,
2020-02-12 13:53:27 -05:00
})
f . StringVar ( & base . StringVar {
Name : "log-level" ,
Target : & c . flagLogLevel ,
2020-08-10 17:15:02 -04:00
EnvVar : "BOUNDARY_LOG_LEVEL" ,
2020-02-12 13:53:27 -05:00
Completion : complete . PredictSet ( "trace" , "debug" , "info" , "warn" , "err" ),
2022-07-11 16:12:02 -04:00
Usage : "Log verbosity level, mostly as a fallback for events. Supported values (in order of more detail to less) are " +
2020-02-12 13:53:27 -05:00
"\"trace\", \"debug\", \"info\", \"warn\", and \"err\"." ,
})
f . StringVar ( & base . StringVar {
Name : "log-format" ,
Target : & c . flagLogFormat ,
Completion : complete . PredictSet ( "standard" , "json" ),
2022-07-11 16:12:02 -04:00
Usage : `Log format, mostly as a fallback for events. Supported values are "standard" and "json".` ,
2020-02-12 13:53:27 -05:00
})
2023-05-17 18:12:19 -04:00
f . DurationVar ( & base . DurationVar {
Name : "worker-auth-worker-rotation-interval" ,
Target : & c . flagWorkerAuthWorkerRotationInterval ,
Hidden : true ,
})
f . DurationVar ( & base . DurationVar {
Name : "worker-auth-ca-certificate-lifetime" ,
Target : & c . flagWorkerAuthCaCertificateLifetime ,
Hidden : true ,
})
2024-06-17 12:26:55 -04:00
f . BoolVar ( & base . BoolVar {
Name : "worker-auth-ca-reinitialize" ,
Target : & c . flagWorkerAuthCaReinitialize ,
Hidden : true ,
})
2023-05-17 18:12:19 -04:00
2023-10-20 11:26:58 -07:00
f . BoolVar ( & base . BoolVar {
Name : "skip-plugins" ,
Target : & c . flagSkipPlugins ,
Usage : "Skip loading compiled-in plugins. This does not prevent loopback plugins from being loaded if enabled." ,
Hidden : true ,
})
f . StringVar ( & base . StringVar {
Name : "worker-dns-server" ,
Target : & c . flagWorkerDnsServer ,
Usage : "Use a custom DNS server when workers resolve endpoints." ,
Hidden : true ,
})
2020-02-12 13:53:27 -05:00
return set
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) AutocompleteArgs () complete . Predictor {
2020-02-12 13:53:27 -05:00
return complete . PredictNothing
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) AutocompleteFlags () complete . Flags {
2020-02-12 13:53:27 -05:00
return c . Flags (). Completions ()
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) Run ( args [] string ) int {
2020-02-12 13:53:27 -05:00
c . CombineLogs = c . flagCombineLogs
2022-08-08 09:13:47 -07:00
defer func () {
if err := c . RunShutdownFuncs (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error running shutdown tasks: %w" , err ). Error ())
}
}()
2020-02-12 13:53:27 -05:00
if result := c . ParseFlagsAndConfig ( args ); result > 0 {
return result
}
if err := c . SetupLogging ( c . flagLogLevel , c . flagLogFormat , c . Config . LogLevel , c . Config . LogFormat ); err != nil {
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-02-12 13:53:27 -05:00
}
2022-06-15 20:01:03 -04:00
serverName , err := os . Hostname ()
if err != nil {
c . UI . Error ( fmt . Errorf ( "Unable to determine hostname: %w" , err ). Error ())
return base . CommandCliError
}
var serverTypes [] string
2021-07-25 17:40:54 -04:00
if c . Config . Controller != nil {
2022-06-15 20:01:03 -04:00
serverTypes = append ( serverTypes , "controller" )
2021-07-25 17:40:54 -04:00
}
if c . Config . Worker != nil {
2022-06-15 20:01:03 -04:00
serverTypes = append ( serverTypes , "worker" )
2021-07-25 17:40:54 -04:00
}
2022-06-15 20:01:03 -04:00
serverName = fmt . Sprintf ( "%s/%s" , serverName , strings . Join ( serverTypes , "+" ))
2021-07-25 17:40:54 -04:00
2023-06-06 17:09:08 -04:00
if err := c . SetupEventing ( c . Context ,
c . Logger ,
2022-03-16 17:04:07 -04:00
c . StderrLock ,
2022-06-15 20:01:03 -04:00
serverName ,
2022-03-16 17:25:34 -04:00
base . WithEventerConfig ( c . Config . Eventing ),
base . WithEventGating ( true )); err != nil {
2021-07-08 15:50:25 -04:00
c . UI . Error ( err . Error ())
return base . CommandUserError
}
2023-03-30 16:20:28 -04:00
c . WorkerAuthDebuggingEnabled . Store ( c . Config . EnableWorkerAuthDebugging )
2021-07-08 15:50:25 -04:00
2022-03-16 17:04:07 -04:00
base . StartMemProfiler ( c . Context )
2020-03-19 15:17:40 -04:00
2022-06-11 16:13:15 -04:00
// Note: the checks directly after this must remain where they are because
// they rely on the state of configured KMSes.
2022-03-16 17:04:07 -04:00
if err := c . SetupKMSes ( c . Context , c . UI , c . Config ); err != nil {
2020-02-12 13:53:27 -05:00
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-05-11 13:27:54 -04:00
}
2020-09-23 01:23:10 -04:00
if c . Config . Controller != nil {
if c . RootKms == nil {
c . UI . Error ( "Root KMS not found after parsing KMS blocks" )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-23 01:23:10 -04:00
}
2020-05-11 13:27:54 -04:00
}
2022-06-11 16:13:15 -04:00
if c . Config . Worker != nil {
switch c . WorkerAuthKms {
case nil :
if c . Config . Worker . AuthStoragePath == "" {
c . UI . Error ( "No worker auth KMS specified and no worker auth storage path specified." )
return base . CommandUserError
}
2022-06-15 15:50:36 -04:00
if c . Config . Worker . Name != "" || c . Config . Worker . Description != "" {
2023-03-30 16:20:28 -04:00
c . UI . Error ( "Worker config cannot contain name or description when using activation-token-based worker authentication; it must be set via the API." )
2022-06-15 15:50:36 -04:00
return base . CommandUserError
}
2022-06-11 16:13:15 -04:00
default :
if c . Config . Worker . Name == "" {
c . UI . Error ( "Worker is using KMS auth but has no name set. It must be the unique name of this instance." )
return base . CommandUserError
}
2022-09-02 14:32:41 -04:00
if c . Config . Worker . ControllerGeneratedActivationToken != "" {
c . UI . Error ( "Worker has KMS auth info but also has a controller-generated activation token set, which is incompatible." )
return base . CommandUserError
}
2023-03-30 16:20:28 -04:00
if c . Config . Worker . AuthStoragePath != "" {
c . UI . Error ( "Worker has KMS auth info but also has an auth storage path set, which is incompatible." )
return base . CommandUserError
}
2022-06-11 16:13:15 -04:00
}
2023-01-11 09:07:31 -05:00
if c . Config . Controller != nil {
if c . Config . Worker . Name == c . Config . Controller . Name {
c . UI . Error ( "Controller and worker cannot be configured with the same name." )
return base . CommandUserError
}
}
2022-06-11 16:13:15 -04:00
}
2020-02-12 13:53:27 -05:00
if c . Config . DefaultMaxRequestDuration != 0 {
globals . DefaultMaxRequestDuration = c . Config . DefaultMaxRequestDuration
}
// If mlockall(2) isn't supported, show a warning. We disable this in dev
2020-08-10 17:15:02 -04:00
// because it is quite scary to see when first using Boundary. We also disable
2020-02-12 13:53:27 -05:00
// this if the user has explicitly disabled mlock in configuration.
2020-09-22 21:37:37 -04:00
if ! c . Config . DisableMlock && ! mlock . Supported () {
2020-02-12 13:53:27 -05:00
c . UI . Warn ( base . WrapAtLength (
"WARNING! mlock is not supported on this system! An mlockall(2)-like " +
"syscall to prevent memory from being swapped to disk is not " +
2020-08-10 17:15:02 -04:00
"supported on this system. For better security, only run Boundary on " +
"systems where this call is supported. If you are running Boundary" +
2020-02-12 13:53:27 -05:00
"in a Docker container, provide the IPC_LOCK cap to the container." ))
}
2023-10-20 11:26:58 -07:00
c . SkipPlugins = c . flagSkipPlugins
2024-02-23 12:19:46 -05:00
c . SkipAliasTargetCreation = c . flagSkipAliasTargetCreation
2023-10-20 11:26:58 -07:00
c . WorkerDnsServer = c . flagWorkerDnsServer
2020-03-22 08:27:24 -04:00
// Perform controller-specific listener checks here before setup
2020-09-24 13:14:45 -04:00
var clusterAddr string
2020-09-23 01:23:10 -04:00
var foundApi bool
var foundProxy bool
2020-03-22 08:27:24 -04:00
for _ , lnConfig := range c . Config . Listeners {
switch len ( lnConfig . Purpose ) {
2020-09-23 01:23:10 -04:00
case 0 :
c . UI . Error ( "Listener specified without a purpose" )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-23 01:23:10 -04:00
2020-03-22 08:27:24 -04:00
case 1 :
2020-09-23 01:23:10 -04:00
purpose := lnConfig . Purpose [ 0 ]
switch purpose {
2020-03-22 08:27:24 -04:00
case "cluster" :
2020-09-24 13:14:45 -04:00
clusterAddr = lnConfig . Address
if clusterAddr == "" {
clusterAddr = "127.0.0.1:9201"
2021-02-10 20:22:02 -05:00
lnConfig . Address = clusterAddr
2020-09-24 13:14:45 -04:00
}
2020-03-22 08:27:24 -04:00
case "api" :
2020-09-23 01:23:10 -04:00
foundApi = true
2020-09-03 13:24:34 -04:00
case "proxy" :
2020-09-23 01:23:10 -04:00
foundProxy = true
2021-02-11 20:10:48 -05:00
if lnConfig . Address == "" {
lnConfig . Address = "127.0.0.1:9202"
}
2022-03-28 17:16:39 +01:00
case "ops" :
2020-03-22 08:27:24 -04:00
default :
c . UI . Error ( fmt . Sprintf ( "Unknown listener purpose %q" , lnConfig . Purpose [ 0 ]))
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-03-22 08:27:24 -04:00
}
2020-09-23 01:23:10 -04:00
default :
c . UI . Error ( "Specifying a listener with more than one purpose is not supported" )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-03-22 08:27:24 -04:00
}
}
2020-09-23 01:23:10 -04:00
if c . Config . Controller != nil {
if ! foundApi {
c . UI . Error ( `Config activates controller but no listener with "api" purpose found` )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-23 01:23:10 -04:00
}
2020-09-24 13:14:45 -04:00
if clusterAddr == "" {
2020-09-23 01:23:10 -04:00
c . UI . Error ( `Config activates controller but no listener with "cluster" purpose found` )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-23 01:23:10 -04:00
}
2022-09-12 09:37:13 -04:00
if err := c . Config . SetupControllerPublicClusterAddress ( "" ); err != nil {
2021-02-10 20:22:02 -05:00
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2021-02-10 20:22:02 -05:00
}
2021-02-11 20:10:48 -05:00
c . InfoKeys = append ( c . InfoKeys , "controller public cluster addr" )
c . Info [ "controller public cluster addr" ] = c . Config . Controller . PublicClusterAddr
2020-03-22 08:27:24 -04:00
}
2021-02-10 20:22:02 -05:00
2020-09-23 01:23:10 -04:00
if c . Config . Worker != nil {
if ! foundProxy {
c . UI . Error ( `Config activates worker but no listener with "proxy" purpose found` )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-23 01:23:10 -04:00
}
2021-02-10 20:22:02 -05:00
2021-12-06 10:39:01 +00:00
if err := c . SetupWorkerPublicAddress ( c . Config , "" ); err != nil {
c . UI . Error ( err . Error ())
return base . CommandUserError
2021-02-10 20:22:02 -05:00
}
2021-12-06 10:39:01 +00:00
c . InfoKeys = append ( c . InfoKeys , "worker public proxy addr" )
c . Info [ "worker public proxy addr" ] = c . Config . Worker . PublicAddr
2021-02-10 20:22:02 -05:00
2020-09-24 13:14:45 -04:00
if c . Config . Controller != nil {
2022-09-12 09:37:13 -04:00
if err := c . Config . SetupWorkerInitialUpstreams (); err != nil {
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-24 13:14:45 -04:00
}
2020-12-18 12:12:25 -05:00
}
2022-06-13 15:06:03 -07:00
for _ , upstream := range c . Config . Worker . InitialUpstreams {
host , _ , err := net . SplitHostPort ( upstream )
2020-12-18 12:12:25 -05:00
if err != nil {
2023-01-26 10:35:29 -08:00
if strings . Contains ( err . Error (), globals . MissingPortErrStr ) {
2022-06-13 15:06:03 -07:00
host = upstream
2020-12-18 12:12:25 -05:00
} else {
2022-06-13 15:06:03 -07:00
c . UI . Error ( fmt . Errorf ( "Invalid worker upstream address %q: %w" , upstream , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-12-18 12:12:25 -05:00
}
}
ip := net . ParseIP ( host )
if ip != nil {
var errMsg string
switch {
case ip . IsUnspecified ():
errMsg = "an unspecified"
case ip . IsMulticast ():
errMsg = "a multicast"
}
if errMsg != "" {
2022-06-13 15:06:03 -07:00
c . UI . Error ( fmt . Sprintf ( "Worker upstream address %q is invalid: cannot be %s address" , upstream , errMsg ))
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-12-18 12:12:25 -05:00
}
}
2020-09-24 13:14:45 -04:00
}
2022-06-09 16:04:13 -04:00
2022-06-16 09:21:11 -05:00
if c . Config . HcpbClusterId != "" {
2023-02-01 13:20:07 -05:00
if len ( c . Config . Worker . InitialUpstreams ) > 0 {
c . UI . Error ( fmt . Errorf ( "Initial upstreams and HCPB cluster ID are mutually exclusive fields" ). Error ())
return base . CommandUserError
}
2023-09-12 13:20:49 -04:00
clusterId , err := parseutil . ParsePath ( c . Config . HcpbClusterId )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
c . UI . Error ( fmt . Errorf ( "Failed to parse HCP Boundary cluster ID %q: %w" , clusterId , err ). Error ())
return base . CommandUserError
}
2023-02-01 13:20:07 -05:00
if strings . HasPrefix ( clusterId , "int-" ) {
clusterId = strings . TrimPrefix ( clusterId , "int-" )
2023-11-01 13:41:27 -07:00
} else if strings . HasPrefix ( clusterId , "dev-" ) {
clusterId = strings . TrimPrefix ( clusterId , "dev-" )
2023-02-01 13:20:07 -05:00
}
2023-09-12 13:20:49 -04:00
_ , err = uuid . ParseUUID ( clusterId )
2022-06-09 16:04:13 -04:00
if err != nil {
2023-02-01 13:20:07 -05:00
c . UI . Error ( fmt . Errorf ( "Invalid HCP Boundary cluster ID %q: %w" , clusterId , err ). Error ())
2022-06-09 16:04:13 -04:00
return base . CommandUserError
}
}
2020-09-23 01:23:10 -04:00
}
2022-03-28 17:16:39 +01:00
if err := c . SetupListeners ( c . UI , c . Config . SharedConfig , [] string { "api" , "cluster" , "proxy" , "ops" }); err != nil {
2020-02-12 13:53:27 -05:00
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-02-12 13:53:27 -05:00
}
2020-10-30 16:54:53 -04:00
if c . Config . Controller != nil {
2020-12-18 12:12:25 -05:00
for _ , ln := range c . Config . Listeners {
for _ , purpose := range ln . Purpose {
if purpose != "cluster" {
continue
}
host , _ , err := net . SplitHostPort ( ln . Address )
if err != nil {
2023-01-26 10:35:29 -08:00
if strings . Contains ( err . Error (), globals . MissingPortErrStr ) {
2020-12-18 12:12:25 -05:00
host = ln . Address
} else {
c . UI . Error ( fmt . Errorf ( "Invalid cluster listener address %q: %w" , ln . Address , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-12-18 12:12:25 -05:00
}
}
ip := net . ParseIP ( host )
if ip != nil {
if ip . IsUnspecified () && c . Config . Controller . PublicClusterAddr == "" {
c . UI . Error ( fmt . Sprintf ( "When %q listener has an unspecified address, %q must be set" , "cluster" , "public_cluster_addr" ))
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-12-18 12:12:25 -05:00
}
}
}
}
2020-10-30 16:54:53 -04:00
}
2020-09-23 01:23:10 -04:00
2020-02-12 13:53:27 -05:00
// Write out the PID to the file now that server has successfully started
if err := c . StorePidFile ( c . Config . PidFile ); err != nil {
2020-03-20 12:51:39 -04:00
c . UI . Error ( fmt . Errorf ( "Error storing PID: %w" , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-02-12 13:53:27 -05:00
}
2020-09-23 01:23:10 -04:00
if c . Config . Controller != nil {
if c . Config . Controller . Database == nil || c . Config . Controller . Database . Url == "" {
c . UI . Error ( `"url" not specified in "controller.database" config block"` )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-23 01:23:10 -04:00
}
2021-01-25 14:43:50 -07:00
var err error
2021-07-14 17:50:15 -07:00
c . DatabaseUrl , err = parseutil . ParsePath ( c . Config . Controller . Database . Url )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
2020-09-23 01:23:10 -04:00
c . UI . Error ( fmt . Errorf ( "Error parsing database url: %w" , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-23 01:23:10 -04:00
}
2021-05-03 23:42:18 +01:00
c . DatabaseMaxOpenConnections = c . Config . Controller . Database . MaxOpenConnections
2022-07-05 15:25:55 -04:00
c . DatabaseMaxIdleConnections = c . Config . Controller . Database . MaxIdleConnections
c . DatabaseConnMaxIdleTimeDuration = c . Config . Controller . Database . ConnMaxIdleTimeDuration
2021-05-03 23:42:18 +01:00
2022-09-06 16:01:52 +01:00
if err := c . OpenAndSetServerDatabase ( c . Context , "postgres" ); err != nil {
2020-09-23 01:23:10 -04:00
c . UI . Error ( fmt . Errorf ( "Error connecting to database: %w" , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandCliError
2020-09-23 01:23:10 -04:00
}
2021-01-19 10:40:41 -07:00
2022-09-12 10:30:52 -04:00
sm , err := acquireSchemaManager ( c . Context , c . Server . Database , c . Config . Controller . Database . SkipSharedLockAcquisition )
2021-01-19 10:40:41 -07:00
if err != nil {
2022-09-06 16:01:52 +01:00
c . UI . Error ( fmt . Errorf ( "Failed to acquire database shared lock: %w" , err ). Error ())
2022-09-06 15:51:30 +01:00
return base . CommandCliError
2022-09-07 16:28:07 -04:00
}
2022-09-06 16:01:52 +01:00
c . schemaManager = sm
2022-09-06 15:51:30 +01:00
defer func () {
2022-09-06 16:01:52 +01:00
if c . schemaManager == nil {
c . UI . Error ( "no schema manager to unlock database with" )
return
}
2022-09-06 15:51:30 +01:00
// The base context has already been canceled so we shouldn't use it here.
// 1 second is chosen so the shutdown is still responsive and this is a mostly
// non critical step since the lock should be released when the session with the
// database is closed.
ctx , cancel := context . WithTimeout ( context . Background (), 1 * time . Second )
defer cancel ()
2022-09-06 16:01:52 +01:00
err := c . schemaManager . Close ( ctx )
if err != nil {
2022-09-06 15:51:30 +01:00
c . UI . Error ( fmt . Errorf ( "Unable to release shared lock to the database: %w" , err ). Error ())
}
}()
2022-09-06 16:01:52 +01:00
err = verifyDatabaseState ( c . Context , c . Server . Database , c . schemaManager )
2021-01-19 10:40:41 -07:00
if err != nil {
2022-09-06 16:01:52 +01:00
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandCliError
2021-01-25 14:43:50 -07:00
}
2022-09-12 10:30:52 -04:00
if c . Config . Controller . Database . SkipSharedLockAcquisition {
if err := c . schemaManager . Close ( c . Context ); err != nil {
c . UI . Error ( fmt . Errorf ( "Unable to release shared lock to the database: %w" , err ). Error ())
return base . CommandCliError
}
}
2020-02-12 13:53:27 -05:00
}
2024-04-12 11:28:40 -04:00
// append storage-enabled plugins
2023-04-08 16:15:14 -07:00
c . EnabledPlugins = append ( c . EnabledPlugins , base . EnabledPluginAws )
2024-04-12 11:28:40 -04:00
if base . MinioEnabled {
c . EnabledPlugins = append ( c . EnabledPlugins , base . EnabledPluginMinio )
}
2020-09-23 01:23:10 -04:00
if c . Config . Controller != nil {
2024-04-12 11:28:40 -04:00
// append host-only plugins
2023-04-08 16:15:14 -07:00
c . EnabledPlugins = append ( c . EnabledPlugins , base . EnabledPluginHostAzure )
2022-03-16 17:04:07 -04:00
if err := c . StartController ( c . Context ); err != nil {
2020-09-23 01:23:10 -04:00
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandCliError
2020-09-23 01:23:10 -04:00
}
}
if c . Config . Worker != nil {
if err := c . StartWorker (); err != nil {
c . UI . Error ( err . Error ())
2021-08-02 09:35:57 -07:00
if c . controller != nil {
2022-03-10 12:54:10 +00:00
if err := c . controller . Shutdown (); err != nil {
2021-08-02 09:35:57 -07:00
c . UI . Error ( fmt . Errorf ( "Error with controller shutdown: %w" , err ). Error ())
}
}
2021-03-04 20:05:17 -05:00
return base . CommandCliError
2020-09-23 01:23:10 -04:00
}
2022-06-05 22:50:44 -04:00
2022-06-15 22:39:52 -04:00
if c . worker . WorkerAuthRegistrationRequest != "" {
2022-06-03 17:47:44 -04:00
c . InfoKeys = append ( c . InfoKeys , "worker auth registration request" )
2022-06-05 22:50:44 -04:00
c . Info [ "worker auth registration request" ] = c . worker . WorkerAuthRegistrationRequest
2022-06-03 17:47:44 -04:00
c . InfoKeys = append ( c . InfoKeys , "worker auth current key id" )
2022-06-07 16:43:07 -04:00
c . Info [ "worker auth current key id" ] = c . worker . WorkerAuthCurrentKeyId . Load ()
2022-06-08 12:18:00 -04:00
// Write the WorkerAuth request to a file
2023-03-30 16:20:28 -04:00
if err := c . StoreWorkerAuthReq ( c . worker . WorkerAuthRegistrationRequest , c . Config . Worker . AuthStoragePath ); err != nil {
2022-06-08 12:18:00 -04:00
// Shutdown on failure
retErr := fmt . Errorf ( "Error storing worker auth request: %w" , err )
if err := c . worker . Shutdown (); err != nil {
c . UI . Error ( retErr . Error ())
retErr = fmt . Errorf ( "Error shutting down worker: %w" , err )
}
c . UI . Error ( retErr . Error ())
2022-07-22 19:39:34 -04:00
if c . controller != nil {
if err := c . controller . Shutdown (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error with controller shutdown: %w" , err ). Error ())
}
2022-06-08 12:18:00 -04:00
}
return base . CommandCliError
}
2022-04-27 16:25:05 -04:00
}
}
c . PrintInfo ( c . UI )
if err := c . ReleaseLogGate (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error releasing event gate: %w" , err ). Error ())
return base . CommandCliError
2020-03-22 20:17:23 -04:00
}
2023-06-06 17:09:08 -04:00
opsServer , err := ops . NewServer ( c . Context , c . Logger , c . controller , c . worker , c . Listeners ... )
2022-08-02 17:18:09 -07:00
if err != nil {
c . UI . Error ( err . Error ())
return base . CommandCliError
2022-03-28 17:16:39 +01:00
}
2022-08-02 17:18:09 -07:00
c . opsServer = opsServer
c . opsServer . Start ()
2022-03-28 17:16:39 +01:00
2021-02-25 17:57:10 -05:00
// Inform any tests that the server is ready
if c . startedCh != nil {
close ( c . startedCh )
}
2020-03-22 20:17:23 -04:00
return c . WaitForInterrupt ()
2020-02-12 13:53:27 -05:00
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) ParseFlagsAndConfig ( args [] string ) int {
2020-02-12 13:53:27 -05:00
var err error
f := c . Flags ()
if err = f . Parse ( args ); err != nil {
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-02-12 13:53:27 -05:00
}
2021-02-25 17:57:10 -05:00
if len ( c . flagConfig ) == 0 && c . presetConfig == nil {
2020-09-22 21:37:37 -04:00
c . UI . Error ( "Must specify a config file using -config" )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-22 21:37:37 -04:00
}
2020-03-22 08:27:24 -04:00
2022-03-16 17:04:07 -04:00
cfg , out := c . reloadConfig ()
if out > 0 {
return out
}
2022-07-22 19:39:34 -04:00
if extraConfigValidationFunc != nil {
if err := extraConfigValidationFunc ( cfg ); err != nil {
c . UI . Error ( err . Error ())
return base . CommandUserError
}
2022-03-16 17:04:07 -04:00
}
2022-07-22 19:39:34 -04:00
c . Config = cfg
2022-03-16 17:04:07 -04:00
return base . CommandSuccess
}
func ( c * Command ) reloadConfig () ( * config . Config , int ) {
const op = "server.(Command).reloadConfig"
var err error
var cfg * config . Config
2021-02-25 17:57:10 -05:00
switch {
case c . presetConfig != nil :
2022-03-16 17:04:07 -04:00
cfg , err = config . Parse ( c . presetConfig . Load ())
2022-09-29 09:01:33 -04:00
if err != nil {
event . WriteError ( c . Context , op , err , event . WithInfoMsg ( "could not parse presetConfig" , "config" , c . presetConfig ))
return nil , base . CommandUserError
}
2021-02-25 17:57:10 -05:00
default :
2022-09-29 09:01:33 -04:00
cfg , err = config . Load ( c . Context , c . flagConfig , c . flagConfigKms )
if err != nil {
c . UI . Error ( "Error parsing config: " + err . Error ())
return nil , base . CommandUserError
2021-02-25 17:57:10 -05:00
}
}
2023-03-30 16:20:28 -04:00
2022-03-16 17:04:07 -04:00
return cfg , 0
2020-02-12 13:53:27 -05:00
}
2021-07-29 18:52:56 -06:00
func ( c * Command ) StartController ( ctx context . Context ) error {
2020-03-19 15:17:40 -04:00
conf := & controller . Config {
2020-03-10 15:48:04 -04:00
RawConfig : c . Config ,
Server : c . Server ,
2023-05-17 18:12:19 -04:00
TestOverrideWorkerAuthCaCertificateLifetime : c . flagWorkerAuthCaCertificateLifetime ,
2024-06-17 12:26:55 -04:00
TestWorkerAuthCaReinitialize : c . flagWorkerAuthCaReinitialize ,
2020-02-12 13:53:27 -05:00
}
2020-03-22 20:17:23 -04:00
var err error
2021-07-29 18:52:56 -06:00
c . controller , err = controller . New ( ctx , conf )
2020-02-12 13:53:27 -05:00
if err != nil {
2020-03-22 20:17:23 -04:00
return fmt . Errorf ( "Error initializing controller: %w" , err )
2020-02-12 13:53:27 -05:00
}
2020-03-22 20:17:23 -04:00
if err := c . controller . Start (); err != nil {
retErr := fmt . Errorf ( "Error starting controller: %w" , err )
2022-03-10 12:54:10 +00:00
if err := c . controller . Shutdown (); err != nil {
2020-03-22 20:17:23 -04:00
c . UI . Error ( retErr . Error ())
2020-09-23 01:23:10 -04:00
retErr = fmt . Errorf ( "Error shutting down controller: %w" , err )
}
return retErr
}
return nil
}
func ( c * Command ) StartWorker () error {
conf := & worker . Config {
RawConfig : c . Config ,
Server : c . Server ,
}
var err error
2023-04-08 16:15:14 -07:00
c . worker , err = worker . New ( c . Context , conf )
2020-09-23 01:23:10 -04:00
if err != nil {
return fmt . Errorf ( "Error initializing worker: %w" , err )
}
2023-05-17 18:12:19 -04:00
c . worker . TestOverrideAuthRotationPeriod = c . flagWorkerAuthWorkerRotationInterval
2020-09-23 01:23:10 -04:00
if err := c . worker . Start (); err != nil {
retErr := fmt . Errorf ( "Error starting worker: %w" , err )
2022-03-09 20:21:36 +00:00
if err := c . worker . Shutdown (); err != nil {
2020-09-23 01:23:10 -04:00
c . UI . Error ( retErr . Error ())
retErr = fmt . Errorf ( "Error shutting down worker: %w" , err )
2020-02-12 13:53:27 -05:00
}
2020-03-22 20:17:23 -04:00
return retErr
2020-02-12 13:53:27 -05:00
}
2023-03-30 16:20:28 -04:00
if c . WorkerAuthKms == nil {
2022-06-15 22:39:52 -04:00
if c . worker . WorkerAuthStorage == nil {
2023-03-30 16:20:28 -04:00
return fmt . Errorf ( "Chosen worker authentication method requires storage and no worker auth storage was configured" )
2022-06-15 22:39:52 -04:00
}
c . InfoKeys = append ( c . InfoKeys , "worker auth storage path" )
2023-03-30 16:20:28 -04:00
c . Info [ "worker auth storage path" ] = c . Config . Worker . AuthStoragePath
2022-06-15 22:39:52 -04:00
}
2020-03-22 20:17:23 -04:00
return nil
}
func ( c * Command ) WaitForInterrupt () int {
2021-07-28 06:31:16 -04:00
const op = "server.(Command).WaitForInterrupt"
2020-02-12 13:53:27 -05:00
2022-09-19 09:59:18 -04:00
var shutdownCompleted atm . Bool
shutdownTriggerCount := 0
2022-08-03 19:29:17 -04:00
2022-09-19 09:59:18 -04:00
var workerShutdownOnce sync . Once
workerShutdownFunc := func () {
if err := c . worker . Shutdown (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error shutting down worker: %w" , err ). Error ())
2022-08-03 19:29:17 -04:00
}
2022-09-19 09:59:18 -04:00
}
workerGracefulShutdownFunc := func () {
if err := c . worker . GracefulShutdown (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error shutting down worker gracefully: %w" , err ). Error ())
2022-08-03 19:29:17 -04:00
}
2022-09-19 09:59:18 -04:00
workerShutdownOnce . Do ( workerShutdownFunc )
}
var controllerOnce sync . Once
controllerShutdownFunc := func () {
if err := c . controller . Shutdown (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error shutting down controller: %w" , err ). Error ())
2022-08-03 19:29:17 -04:00
}
if c . opsServer != nil {
err := c . opsServer . Shutdown ()
if err != nil {
2022-09-19 09:59:18 -04:00
c . UI . Error ( fmt . Errorf ( "Failed to shutdown ops listeners: %w" , err ). Error ())
2022-03-28 17:16:39 +01:00
}
2022-08-03 19:29:17 -04:00
}
2022-09-19 09:59:18 -04:00
}
runShutdownLogic := func () {
switch {
case shutdownTriggerCount == 1 :
c . ContextCancel ()
go func () {
if c . Config . Controller != nil && c . opsServer != nil {
c . opsServer . WaitIfHealthExists ( c . Config . Controller . GracefulShutdownWaitDuration , c . UI )
}
if c . Config . Worker != nil {
c . UI . Output ( "==> Boundary server graceful shutdown triggered, interrupt again to enter shutdown" )
workerGracefulShutdownFunc ()
} else {
c . UI . Output ( "==> Boundary server shutdown triggered, interrupt again to force" )
}
2022-03-28 17:16:39 +01:00
2022-09-19 09:59:18 -04:00
if c . Config . Controller != nil {
controllerOnce . Do ( controllerShutdownFunc )
}
shutdownCompleted . Store ( true )
}()
case shutdownTriggerCount == 2 && c . Config . Worker != nil :
go func () {
if c . Config . Worker != nil {
workerShutdownOnce . Do ( workerShutdownFunc )
}
if c . Config . Controller != nil {
controllerOnce . Do ( controllerShutdownFunc )
}
shutdownCompleted . Store ( true )
}()
case shutdownTriggerCount >= 2 :
c . UI . Error ( "Forcing shutdown" )
os . Exit ( base . CommandCliError )
}
2022-08-03 19:29:17 -04:00
}
2023-03-07 16:29:34 -05:00
for _ , f := range extraSelfTerminationConditionFuncs {
2023-03-09 20:31:20 -05:00
f ( c , c . ServerSideShutdownCh )
2023-03-07 16:29:34 -05:00
}
2022-09-19 09:59:18 -04:00
for ! shutdownCompleted . Load () {
2022-08-03 19:29:17 -04:00
select {
case <- c . ServerSideShutdownCh :
c . UI . Output ( "==> Boundary server self-terminating" )
2022-09-19 09:59:18 -04:00
shutdownTriggerCount ++
2022-08-03 19:29:17 -04:00
runShutdownLogic ()
case <- c . ShutdownCh :
2022-09-19 09:59:18 -04:00
shutdownTriggerCount ++
2022-08-03 19:29:17 -04:00
runShutdownLogic ()
2020-02-12 13:53:27 -05:00
case <- c . SighupCh :
2021-01-21 16:27:20 -05:00
c . UI . Output ( "==> Boundary server reload triggered" )
2020-02-12 13:53:27 -05:00
// Check for new log level
var level hclog . Level
var newConf * config . Config
2022-03-16 17:04:07 -04:00
var out int
2020-02-12 13:53:27 -05:00
2022-09-29 09:01:33 -04:00
if len ( c . flagConfig ) == 0 && c . presetConfig == nil {
2020-02-12 13:53:27 -05:00
goto RUNRELOADFUNCS
}
2022-03-16 17:04:07 -04:00
newConf , out = c . reloadConfig ()
if out > 0 {
2020-02-12 13:53:27 -05:00
goto RUNRELOADFUNCS
}
// Ensure at least one config was found.
if newConf == nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( context . TODO (), op , stderrors . New ( "no config found at reload time" ))
2020-02-12 13:53:27 -05:00
goto RUNRELOADFUNCS
}
if newConf . LogLevel != "" {
configLogLevel := strings . ToLower ( strings . TrimSpace ( newConf . LogLevel ))
switch configLogLevel {
case "trace" :
level = hclog . Trace
case "debug" :
level = hclog . Debug
case "notice" , "info" , "" :
level = hclog . Info
case "warn" , "warning" :
level = hclog . Warn
case "err" , "error" :
level = hclog . Error
default :
2021-07-28 06:31:16 -04:00
event . WriteError ( context . TODO (), op , stderrors . New ( "unknown log level found on reload" ), event . WithInfo ( "level" , newConf . LogLevel ))
2020-02-12 13:53:27 -05:00
goto RUNRELOADFUNCS
}
2020-05-11 13:30:38 -04:00
c . Logger . SetLevel ( level )
2020-02-12 13:53:27 -05:00
}
2023-03-30 16:20:28 -04:00
c . WorkerAuthDebuggingEnabled . Store ( newConf . EnableWorkerAuthDebugging )
2020-02-12 13:53:27 -05:00
RUNRELOADFUNCS :
2021-02-25 17:57:10 -05:00
if err := c . Reload ( newConf ); err != nil {
c . UI . Error ( fmt . Errorf ( "Error(s) were encountered during reload: %w" , err ). Error ())
2020-02-12 13:53:27 -05:00
}
case <- c . SigUSR2Ch :
buf := make ([] byte , 32 * 1024 * 1024 )
n := runtime . Stack ( buf [:], true )
2021-07-28 06:31:16 -04:00
event . WriteSysEvent ( context . TODO (), op , "goroutine trace" , "stack" , string ( buf [: n ]))
2022-09-19 09:59:18 -04:00
case <- time . After ( 10 * time . Millisecond ):
2020-02-12 13:53:27 -05:00
}
}
2021-03-04 20:05:17 -05:00
return base . CommandSuccess
2020-02-12 13:53:27 -05:00
}
2021-02-25 17:57:10 -05:00
func ( c * Command ) Reload ( newConf * config . Config ) error {
2020-02-12 13:53:27 -05:00
c . ReloadFuncsLock . RLock ()
defer c . ReloadFuncsLock . RUnlock ()
2023-09-17 19:35:43 -04:00
var reloadErrors error
2020-02-12 13:53:27 -05:00
2021-02-25 17:57:10 -05:00
for _ , relFunc := range c . ReloadFuncs [ "listeners" ] {
if relFunc != nil {
if err := relFunc (); err != nil {
2023-09-17 19:35:43 -04:00
reloadErrors = stderrors . Join ( reloadErrors , fmt . Errorf ( "error encountered reloading listener: %w" , err ))
2020-02-12 13:53:27 -05:00
}
}
}
2022-09-06 17:10:27 +01:00
err := c . reloadControllerDatabase ( newConf )
if err != nil {
2023-09-17 19:35:43 -04:00
reloadErrors = stderrors . Join ( reloadErrors , fmt . Errorf ( "failed to reload controller database: %w" , err ))
2022-09-06 17:10:27 +01:00
}
2023-10-24 14:52:25 +00:00
if err := c . reloadControllerRateLimits ( newConf ); err != nil {
reloadErrors = stderrors . Join ( reloadErrors , fmt . Errorf ( "failed to reload controller api rate limits: %w" , err ))
}
2024-09-04 15:40:44 -04:00
if err := c . reloadControllerTimings ( newConf ); err != nil {
reloadErrors = stderrors . Join ( reloadErrors , fmt . Errorf ( "failed to reload controller timings: %w" , err ))
}
2021-02-25 17:57:10 -05:00
if newConf != nil && c . worker != nil {
2022-09-12 09:37:13 -04:00
workerReloadErr := func () error {
if newConf . Controller != nil {
if err := newConf . SetupControllerPublicClusterAddress ( "" ); err != nil {
return err
}
}
if err := newConf . SetupWorkerInitialUpstreams (); err != nil {
return err
}
c . worker . Reload ( c . Context , newConf )
return nil
}()
if workerReloadErr != nil {
2023-09-17 19:35:43 -04:00
reloadErrors = stderrors . Join ( reloadErrors , fmt . Errorf ( "error encountered reloading worker initial upstreams: %w" , workerReloadErr ))
2022-09-12 09:37:13 -04:00
}
2021-02-25 17:57:10 -05:00
}
2020-02-12 13:53:27 -05:00
// Send a message that we reloaded. This prevents "guessing" sleep times
// in tests.
2021-02-25 17:57:10 -05:00
if c . reloadedCh != nil {
select {
case c . reloadedCh <- struct {}{}:
default :
}
2020-02-12 13:53:27 -05:00
}
2023-09-17 19:35:43 -04:00
return reloadErrors
2020-02-12 13:53:27 -05:00
}
2021-01-25 14:43:50 -07:00
2022-09-06 16:01:52 +01:00
func verifyKmsSetup ( dbase * db . DB ) error {
2021-01-25 14:43:50 -07:00
const op = "server.(Command).verifyKmsExists"
2022-09-06 16:01:52 +01:00
rw := db . New ( dbase )
2021-01-25 14:43:50 -07:00
2022-05-13 13:53:22 -04:00
ctx := context . Background ()
kmsCache , err := kms . New ( ctx , rw , rw )
2021-01-25 14:43:50 -07:00
if err != nil {
2022-08-03 19:29:17 -04:00
return fmt . Errorf ( "%s: error creating kms: %w" , op , err )
2021-01-25 14:43:50 -07:00
}
2022-05-13 13:53:22 -04:00
if err := kmsCache . VerifyGlobalRoot ( ctx ); err != nil {
2021-01-25 14:43:50 -07:00
return err
}
2022-05-13 13:53:22 -04:00
return nil
2021-01-25 14:43:50 -07:00
}
2022-07-22 19:39:34 -04:00
2022-09-06 17:10:27 +01:00
func ( c * Command ) reloadControllerDatabase ( newConfig * config . Config ) error {
if c . Server == nil || c . Server . Database == nil {
return nil
}
if c . controller == nil {
return nil
}
if newConfig == nil || newConfig . Controller == nil || newConfig . Controller . Database == nil {
return nil
}
var err error
newConfig . Controller . Database . Url , err = parseutil . ParsePath ( newConfig . Controller . Database . Url )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
return fmt . Errorf ( "failed to parse db url: %w" , err )
}
if len ( newConfig . Controller . Database . Url ) == 0 || c . DatabaseUrl == newConfig . Controller . Database . Url {
return nil
}
newDb , err := c . Server . OpenDatabase ( c . Context , "postgres" , newConfig . Controller . Database . Url )
if err != nil {
return fmt . Errorf ( "failed to open connection to new database: %w" , err )
}
// Acquire new lock on the new database and verify that it's in a good state to be used.
2022-09-12 10:30:52 -04:00
newDbSchemaManager , err := acquireSchemaManager ( c . Context , newDb , c . Config . Controller . Database . SkipSharedLockAcquisition )
2022-09-06 17:10:27 +01:00
if err != nil {
_ = newDb . Close ( c . Context )
return fmt . Errorf ( "failed to acquire shared lock on new database: %w" , err )
}
err = verifyDatabaseState ( c . Context , newDb , newDbSchemaManager )
if err != nil {
_ = newDbSchemaManager . Close ( c . Context )
_ = newDb . Close ( c . Context )
return fmt . Errorf ( "invalid new database state: %w" , err )
}
2022-09-12 10:30:52 -04:00
if newConfig . Controller . Database . SkipSharedLockAcquisition {
if err := newDbSchemaManager . Close ( c . Context ); err != nil {
return fmt . Errorf ( "unable to release shared lock to the database for new schema manager: %w" , err )
}
}
2022-09-06 17:10:27 +01:00
oldDbSchemaManager := c . schemaManager
// Swap underlying database with new one and update application state.
oldDbCloseFn , err := c . Database . Swap ( c . Context , newDb )
if err != nil {
_ = newDbSchemaManager . Close ( c . Context )
_ = newDb . Close ( c . Context )
return fmt . Errorf ( "failed to swap databases: %w" , err )
}
c . schemaManager = newDbSchemaManager
c . Server . DatabaseUrl = newConfig . Controller . Database . Url
c . Config . Controller . Database . Url = newConfig . Controller . Database . Url
// Release old database shared lock and close old database object.
_ = oldDbSchemaManager . Close ( c . Context )
oldDbCloseFn ( c . Context )
return nil
}
2023-10-24 14:52:25 +00:00
func ( c * Command ) reloadControllerRateLimits ( newConfig * config . Config ) error {
if c . controller == nil || newConfig == nil || newConfig . Controller == nil {
return nil
}
2023-12-01 22:14:49 +00:00
return c . controller . ReloadRateLimiter ( newConfig )
2023-10-24 14:52:25 +00:00
}
2024-09-04 15:40:44 -04:00
func ( c * Command ) reloadControllerTimings ( newConfig * config . Config ) error {
if c . controller == nil || newConfig == nil || newConfig . Controller == nil {
return nil
}
return c . controller . ReloadTimings ( newConfig )
}
2022-09-12 10:30:52 -04:00
// acquireSchemaManager returns a schema manager and generally acquires a shared lock on
2022-09-06 16:01:52 +01:00
// the database. This is done as a mechanism to disallow running migration commands
// while the database is in use.
2022-09-12 10:30:52 -04:00
func acquireSchemaManager ( ctx context . Context , db * db . DB , skipSharedLock bool ) ( * schema . Manager , error ) {
2022-09-06 16:01:52 +01:00
if db == nil {
return nil , fmt . Errorf ( "nil database" )
}
underlyingDb , err := db . SqlDB ( ctx )
if err != nil {
return nil , fmt . Errorf ( "failed to obtain sql db: %w" , err )
}
manager , err := schema . NewManager ( ctx , "postgres" , underlyingDb )
if err != nil {
return nil , fmt . Errorf ( "failed to create new schema manager: %w" , err )
}
// This is an advisory locks on the DB which is released when the db session ends.
2022-09-12 10:30:52 -04:00
if ! skipSharedLock {
err = manager . SharedLock ( ctx )
if err != nil {
return nil , fmt . Errorf ( "failed to gain shared access to the database: %w" , err )
}
2022-09-06 16:01:52 +01:00
}
return manager , nil
}
// verifyDatabaseState checks that the migrations and kms setup for the given database are correctly setup.
func verifyDatabaseState ( ctx context . Context , db * db . DB , schemaManager * schema . Manager ) error {
if db == nil {
return fmt . Errorf ( "nil database" )
}
if schemaManager == nil {
return fmt . Errorf ( "nil schema manager" )
}
s , err := schemaManager . CurrentState ( ctx )
if err != nil {
return fmt . Errorf ( "failed to get current schema state: %w" , err )
}
if ! s . Initialized {
return fmt . Errorf ( "The database has not been initialized. Please ensure your database user " +
"has access to all Boundary tables, or run 'boundary database init' if you haven't initialized " +
"your database for Boundary." )
}
if ! s . MigrationsApplied () {
for _ , e := range s . Editions {
if e . DatabaseSchemaState == schema . Ahead {
return fmt . Errorf ( "Newer schema version (%s %d) " +
"than this binary expects. Please use a newer version of the boundary " +
"binary." , e . Name , e . DatabaseSchemaVersion )
}
}
return fmt . Errorf ( "Database schema must be updated to use this version. " +
"Run 'boundary database migrate' to update the database. " +
"NOTE: Boundary does not currently support live migration; " +
"Ensure all controllers are shut down before running the migration command." )
}
err = verifyKmsSetup ( db )
if err != nil {
return fmt . Errorf ( "Database is in a bad state. Please revert the database " +
"into the last known good state. (Failed to verify kms setup: %w)" , err )
}
return nil
}
2022-07-22 19:39:34 -04:00
var extraConfigValidationFunc = func ( cfg * config . Config ) error {
if cfg . Controller == nil && cfg . Worker == nil {
return stderrors . New ( "Neither worker nor controller specified in configuration file." )
}
if cfg . Controller != nil && cfg . Controller . Name == "" {
return stderrors . New ( "Controller has no name set. It must be the unique name of this instance." )
}
return nil
}