From 062ce6079ffbe3f60992f412c76c606ff97044ab Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 11 May 2020 17:03:23 -0400 Subject: [PATCH] Create dev org in database (#41) These changes create a dev org in the database on startup. They also plumb through a dev-org-id flag that allows specifying a chosen org ID in dev mode. The logic will allow us to let people specify a default org ID in the config file too, although currently that's not enabled. Several other improvements were made as well: * Consolidation of some code from across the server commands * Plumbing shutdownch through so we can use it for canceling contexts during startup --- internal/cmd/base/servers.go | 76 ++++++++++++++++++- internal/cmd/commands.go | 12 +-- .../cmd/commands/controller/controller.go | 32 +++++++- internal/cmd/commands/dev/dev.go | 69 +++++++---------- internal/cmd/commands/worker/worker.go | 2 - internal/cmd/config/config.go | 3 +- internal/iam/scope.go | 19 ++++- 7 files changed, 150 insertions(+), 63 deletions(-) diff --git a/internal/cmd/base/servers.go b/internal/cmd/base/servers.go index de5afe435..da191d742 100644 --- a/internal/cmd/base/servers.go +++ b/internal/cmd/base/servers.go @@ -25,7 +25,9 @@ import ( "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/watchtower/globals" "github.com/hashicorp/watchtower/internal/db" + "github.com/hashicorp/watchtower/internal/iam" "github.com/hashicorp/watchtower/version" + "github.com/jinzhu/gorm" "github.com/mitchellh/cli" "golang.org/x/net/http/httpproxy" "google.golang.org/grpc/grpclog" @@ -34,6 +36,8 @@ import ( ) type Server struct { + *Command + InfoKeys []string Info map[string]string @@ -57,12 +61,17 @@ type Server struct { Listeners []*ServerListener + DefaultOrgId string + DevDatabaseUrl string DevDatabaseCleanupFunc func() error + + Database *gorm.DB } -func NewServer() *Server { +func NewServer(cmd *Command) *Server { return &Server{ + Command: cmd, InfoKeys: make([]string, 0, 20), Info: make(map[string]string), SecureRandomReader: rand.Reader, @@ -355,11 +364,11 @@ func (b *Server) RunShutdownFuncs(ui cli.Ui) { } } -func (b *Server) CreateDevDatabase() error { - c, url, err := db.InitDbInDocker("postgres") +func (b *Server) CreateDevDatabase(dialect string) error { + c, url, err := db.InitDbInDocker(dialect) if err != nil { c() - return fmt.Errorf("unable to start dev database: %w", err) + return fmt.Errorf("unable to start dev database with dialect %s: %w", dialect, err) } b.DevDatabaseCleanupFunc = c @@ -367,10 +376,69 @@ func (b *Server) CreateDevDatabase() error { b.InfoKeys = append(b.InfoKeys, "dev database url") b.Info["dev database url"] = b.DevDatabaseUrl + + dbase, err := gorm.Open(dialect, url) + if err != nil { + c() + return fmt.Errorf("unable to create db object with dialect %s: %w", dialect, err) + } + b.Database = dbase + + rw := &db.Db{Tx: b.Database} + repo, err := iam.NewRepository(rw, rw, b.ControllerKMS) + if err != nil { + c() + return fmt.Errorf("unable to create repo for org id: %w", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + <-b.ShutdownCh + cancel() + }() + + var scope *iam.Scope + if b.DefaultOrgId != "" { + scope, err = repo.LookupScope(ctx, iam.WithPublicId(b.DefaultOrgId)) + if err != nil { + c() + return fmt.Errorf("error looking up existing scope with org ID %q: %w", b.DefaultOrgId, err) + } + if scope != nil { + goto INFO + } + } + + scope, err = iam.NewOrganization(iam.WithPublicId(b.DefaultOrgId)) + if err != nil { + c() + return fmt.Errorf("error creating new org scope: %w", err) + } + scope, err = repo.CreateScope(ctx, scope) + if err != nil { + c() + return fmt.Errorf("error persisting new org scope: %w", err) + } + if b.DefaultOrgId != "" { + if scope.GetPublicId() != b.DefaultOrgId { + c() + return fmt.Errorf("expected org ID %q, got %q after persisting", b.DefaultOrgId, scope.GetPublicId()) + } + } else { + b.DefaultOrgId = scope.GetPublicId() + } + +INFO: + b.InfoKeys = append(b.InfoKeys, "dev org id") + b.Info["dev org id"] = b.DefaultOrgId + return nil } func (b *Server) DestroyDevDatabase() error { + if b.Database != nil { + b.Database.Close() + } if b.DevDatabaseCleanupFunc != nil { return b.DevDatabaseCleanupFunc() } diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index 62d81e0d0..7aaee764b 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -37,30 +37,30 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Commands = map[string]cli.CommandFactory{ "controller": func() (cli.Command, error) { return &controller.Command{ - Command: &base.Command{ + Server: base.NewServer(&base.Command{ UI: serverCmdUi, ShutdownCh: MakeShutdownCh(), - }, + }), SighupCh: MakeSighupCh(), SigUSR2Ch: MakeSigUSR2Ch(), }, nil }, "worker": func() (cli.Command, error) { return &worker.Command{ - Command: &base.Command{ + Server: base.NewServer(&base.Command{ UI: serverCmdUi, ShutdownCh: MakeShutdownCh(), - }, + }), SighupCh: MakeSighupCh(), SigUSR2Ch: MakeSigUSR2Ch(), }, nil }, "dev": func() (cli.Command, error) { return &dev.Command{ - Command: &base.Command{ + Server: base.NewServer(&base.Command{ UI: serverCmdUi, ShutdownCh: MakeShutdownCh(), - }, + }), SighupCh: MakeSighupCh(), SigUSR2Ch: MakeSigUSR2Ch(), }, nil diff --git a/internal/cmd/commands/controller/controller.go b/internal/cmd/commands/controller/controller.go index 12e1b97e4..d324cdc64 100644 --- a/internal/cmd/commands/controller/controller.go +++ b/internal/cmd/commands/controller/controller.go @@ -22,7 +22,6 @@ var _ cli.Command = (*Command)(nil) var _ cli.CommandAutocomplete = (*Command)(nil) type Command struct { - *base.Command *base.Server ExtShutdownCh chan struct{} @@ -42,6 +41,7 @@ type Command struct { flagDevAdminPassword string flagDevControllerAPIListenAddr string flagDevControllerClusterListenAddr string + flagDevOrgId string flagCombineLogs bool } @@ -105,6 +105,15 @@ func (c *Command) Flags() *base.FlagSets { "production.", }) + f.StringVar(&base.StringVar{ + Name: "dev-org-id", + Target: &c.flagDevOrgId, + Default: "", + EnvVar: "WATCHTWER_DEV_ORG_ID", + Usage: "Auto-created organization ID. This only applies when running in \"dev\" " + + "mode.", + }) + f.StringVar(&base.StringVar{ Name: "dev-admin-password", Target: &c.flagDevAdminPassword, @@ -147,7 +156,6 @@ func (c *Command) AutocompleteFlags() complete.Flags { } func (c *Command) Run(args []string) int { - c.Server = base.NewServer() c.CombineLogs = c.flagCombineLogs if result := c.ParseFlagsAndConfig(args); result > 0 { @@ -244,7 +252,7 @@ func (c *Command) Run(args []string) int { } if c.flagDev { - if err := c.CreateDevDatabase(); err != nil { + if err := c.CreateDevDatabase("postgres"); err != nil { c.UI.Error(fmt.Errorf("Error creating dev database container: %s", err.Error()).Error()) return 1 } @@ -296,10 +304,14 @@ func (c *Command) ParseFlagsAndConfig(args []string) int { } else { c.Config, err = config.DevController() if err != nil { - c.UI.Error(fmt.Errorf("Error creating dev config: %s", err).Error()) + c.UI.Error(fmt.Errorf("Error creating dev config: %w", err).Error()) return 1 } + if c.flagDevOrgId != "" { + c.Config.DefaultOrgId = c.flagDevOrgId + } + for _, l := range c.Config.Listeners { if len(l.Purpose) != 1 { continue @@ -318,6 +330,18 @@ func (c *Command) ParseFlagsAndConfig(args []string) int { } } + if c.Config.DefaultOrgId != "" { + if !strings.HasPrefix(c.Config.DefaultOrgId, "o_") { + c.UI.Error(fmt.Sprintf("Invalid default org ID, must start with %q", "o_")) + return 1 + } + if len(c.Config.DefaultOrgId) != 12 { + c.UI.Error(fmt.Sprintf("Invalid default org ID, must be 10 base62 characters after %q", "o_")) + return 1 + } + c.DefaultOrgId = c.Config.DefaultOrgId + } + return 0 } diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index 33f6516de..9bc088df0 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -18,7 +18,6 @@ var _ cli.Command = (*Command)(nil) var _ cli.CommandAutocomplete = (*Command)(nil) type Command struct { - *base.Command *base.Server SighupCh chan struct{} @@ -33,6 +32,7 @@ type Command struct { flagLogFormat string flagDev bool flagDevAdminPassword string + flagDevOrgId string flagDevControllerAPIListenAddr string flagDevControllerClusterListenAddr string flagCombineLogs bool @@ -81,6 +81,15 @@ func (c *Command) Flags() *base.FlagSets { f = set.NewFlagSet("Dev Options") + f.StringVar(&base.StringVar{ + Name: "dev-org-id", + Target: &c.flagDevOrgId, + Default: "", + EnvVar: "WATCHTWER_DEV_ORG_ID", + Usage: "Auto-created organization ID. This only applies when running in \"dev\" " + + "mode.", + }) + f.StringVar(&base.StringVar{ Name: "dev-admin-password", Target: &c.flagDevAdminPassword, @@ -123,7 +132,6 @@ func (c *Command) AutocompleteFlags() complete.Flags { } func (c *Command) Run(args []string) int { - c.Server = base.NewServer() c.CombineLogs = c.flagCombineLogs var err error @@ -139,9 +147,12 @@ func (c *Command) Run(args []string) int { devConfig, err := config.DevController() if err != nil { - c.UI.Error(fmt.Errorf("Error creating controller dev config: %s", err).Error()) + c.UI.Error(fmt.Errorf("Error creating controller dev config: %w", err).Error()) return 1 } + if c.flagDevOrgId != "" { + devConfig.DefaultOrgId = c.flagDevOrgId + } for _, l := range devConfig.Listeners { if len(l.Purpose) != 1 { @@ -160,6 +171,18 @@ func (c *Command) Run(args []string) int { } } + if devConfig.DefaultOrgId != "" { + if !strings.HasPrefix(devConfig.DefaultOrgId, "o_") { + c.UI.Error(fmt.Sprintf("Invalid default org ID, must start with %q", "o_")) + return 1 + } + if len(devConfig.DefaultOrgId) != 12 { + c.UI.Error(fmt.Sprintf("Invalid default org ID, must be 10 base62 characters after %q", "o_")) + return 1 + } + c.DefaultOrgId = devConfig.DefaultOrgId + } + if err := c.SetupLogging(c.flagLogLevel, c.flagLogFormat, "", ""); err != nil { c.UI.Error(err.Error()) return 1 @@ -197,45 +220,9 @@ func (c *Command) Run(args []string) int { return 1 } - /* - // If we're in Dev mode, then initialize the core - if c.flagDev && !c.flagDevSkipInit { - init, err := c.enableDev(core, coreConfig) - if err != nil { - c.UI.Error(fmt.Sprintf("Error initializing Dev mode: %w", err)) - return 1 - } - - // Print the big dev mode warning! - c.UI.Warn(base.WrapAtLength( - "WARNING! dev mode is enabled! In this mode, Watchtower runs entirely " + - "in-memory and all state is lost upon shutdown.")) - c.UI.Warn("") - c.UI.Warn("You may need to set the following environment variable:") - c.UI.Warn("") - - endpointURL := "http://" + config.Listeners[0].Config["address"].(string) - if runtime.GOOS == "windows" { - c.UI.Warn("PowerShell:") - c.UI.Warn(fmt.Sprintf(" $env:WATCHTOWER_ADDR=\"%s\"", endpointURL)) - c.UI.Warn("cmd.exe:") - c.UI.Warn(fmt.Sprintf(" set WATCHTOWER_ADDR=%s", endpointURL)) - } else { - c.UI.Warn(fmt.Sprintf(" $ export VAULT_ADDR='%s'", endpointURL)) - } - - c.UI.Warn(fmt.Sprintf("Root Token: %s", init.RootToken)) - - c.UI.Warn("") - c.UI.Warn(base.WrapAtLength( - "Development mode should NOT be used in production installations!")) - c.UI.Warn("") - } - */ - defer c.RunShutdownFuncs(c.UI) - if err := c.CreateDevDatabase(); err != nil { + if err := c.CreateDevDatabase("postgres"); err != nil { c.UI.Error(fmt.Errorf("Error creating dev database container: %w", err).Error()) return 1 } @@ -251,7 +238,6 @@ func (c *Command) Run(args []string) int { c.childSighupCh = append(c.childSighupCh, controllerSighupCh) devController := &controllercmd.Command{ - Command: c.Command, Server: c.Server, ExtShutdownCh: childShutdownCh, SighupCh: controllerSighupCh, @@ -265,7 +251,6 @@ func (c *Command) Run(args []string) int { workerSighupCh := make(chan struct{}) c.childSighupCh = append(c.childSighupCh, workerSighupCh) devWorker := &workercmd.Command{ - Command: c.Command, Server: c.Server, ExtShutdownCh: childShutdownCh, SighupCh: workerSighupCh, diff --git a/internal/cmd/commands/worker/worker.go b/internal/cmd/commands/worker/worker.go index e5cad5089..0cb9de0ac 100644 --- a/internal/cmd/commands/worker/worker.go +++ b/internal/cmd/commands/worker/worker.go @@ -20,7 +20,6 @@ var _ cli.Command = (*Command)(nil) var _ cli.CommandAutocomplete = (*Command)(nil) type Command struct { - *base.Command *base.Server ExtShutdownCh chan struct{} @@ -127,7 +126,6 @@ func (c *Command) AutocompleteFlags() complete.Flags { } func (c *Command) Run(args []string) int { - c.Server = base.NewServer() c.CombineLogs = c.flagCombineLogs if result := c.ParseFlagsAndConfig(args); result > 0 { diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index 7d2942b1f..7ed919b1a 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -63,7 +63,8 @@ listener "tcp" { type Config struct { *configutil.SharedConfig `hcl:"-"` - DevController bool `hcl:"-"` + DevController bool `hcl:"-"` + DefaultOrgId string `hcl:"default_org_id"` } // DevWorker is a Config that is used for dev mode of Watchtower diff --git a/internal/iam/scope.go b/internal/iam/scope.go index 061347067..5d94ad0d1 100644 --- a/internal/iam/scope.go +++ b/internal/iam/scope.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "github.com/hashicorp/watchtower/internal/db" "github.com/hashicorp/watchtower/internal/iam/store" @@ -62,6 +63,7 @@ func NewProject(organizationPublicId string, opt ...Option) (*Scope, error) { // WithScope specifies the Scope's parent func newScope(scopeType ScopeType, opt ...Option) (*Scope, error) { opts := getOpts(opt...) + withPublicId := opts.withPublicId withName := opts.withName withScope := opts.withScope withDescription := opts.withDescription @@ -81,11 +83,20 @@ func newScope(scopeType ScopeType, opt ...Option) (*Scope, error) { if scopeType == OrganizationScope { prefix = "o" } - - publicId, err := db.NewPublicId(prefix) - if err != nil { - return nil, fmt.Errorf("error generating public id %w for new scope", err) + var publicId string + if withPublicId != "" { + if !strings.HasPrefix(withPublicId, prefix+"_") { + return nil, errors.New("passed-in public ID has wrong prefix for type") + } + publicId = withPublicId + } else { + var err error + publicId, err = db.NewPublicId(prefix) + if err != nil { + return nil, fmt.Errorf("error generating public id %w for new scope", err) + } } + s := &Scope{ Scope: &store.Scope{ PublicId: publicId,