Add -container-image flag (#1112)
This commit is contained in:
1 parent
ecc9d6d649
commit
9ceeb3bd65
10 files changed
+138
-27
No files matched your search
@@ -21,10 +21,15 @@ type Options struct {
|
||||
withSkipScopesCreation bool
|
||||
withSkipHostResourcesCreation bool
|
||||
withSkipTargetCreation bool
|
||||
withContainerImage string
|
||||
withDialect string
|
||||
}
|
||||
|
||||
func getDefaultOptions() Options {
|
||||
return Options{}
|
||||
return Options{
|
||||
withContainerImage: "postgres",
|
||||
withDialect: "postgres",
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoTokenScope tells the client not to set a scope for the client from a
|
||||
@@ -83,3 +88,17 @@ func WithSkipTargetCreation() Option {
|
||||
o.withSkipTargetCreation = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithContainerImage tells the command which container image
|
||||
// to start a dev database with
|
||||
func WithContainerImage(name string) Option {
|
||||
return func(o *Options) {
|
||||
o.withContainerImage = name
|
||||
}
|
||||
}
|
||||
|
||||
func withDialect(dialect string) Option {
|
||||
return func(o *Options) {
|
||||
o.withDialect = dialect
|
||||
}
|
||||
}
|
||||
@@ -462,16 +462,25 @@ func (b *Server) ConnectToDatabase(dialect string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Server) CreateDevDatabase(ctx context.Context, dialect string, opt ...Option) error {
|
||||
opts := getOpts(opt...)
|
||||
func (b *Server) CreateDevDatabase(ctx context.Context, opt ...Option) error {
|
||||
|
||||
var container, url string
|
||||
var container, url, dialect string
|
||||
var err error
|
||||
var c func() error
|
||||
|
||||
opts := getOpts(opt...)
|
||||
|
||||
//We should only get back postgres for now, but laying the foundation for non-postgres
|
||||
switch opts.withDialect {
|
||||
case "":
|
||||
b.Logger.Error("unsupported dialect. wanted: postgres, got: %v", opts.withDialect)
|
||||
default:
|
||||
dialect = opts.withDialect
|
||||
}
|
||||
|
||||
switch b.DatabaseUrl {
|
||||
case "":
|
||||
c, url, container, err = docker.StartDbInDocker(dialect)
|
||||
c, url, container, err = docker.StartDbInDocker(dialect, docker.WithContainerImage(opts.withContainerImage))
|
||||
// In case of an error, run the cleanup function. If we pass all errors, c should be set to a noop
|
||||
// function before returning from this method
|
||||
defer func() {
|
||||
@@ -502,7 +511,6 @@ func (b *Server) CreateDevDatabase(ctx context.Context, dialect string, opt ...O
|
||||
|
||||
b.DevDatabaseCleanupFunc = c
|
||||
b.DatabaseUrl = url
|
||||
|
||||
default:
|
||||
// Let migrate store manage the dirty bit since dev DBs should be ephemeral anyways.
|
||||
if _, err := schema.MigrateStore(ctx, dialect, b.DatabaseUrl); err != nil {
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/hashicorp/boundary/internal/cmd/config"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/docker"
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
@@ -68,6 +67,7 @@ type Command struct {
|
||||
flagPassthroughDirectory string
|
||||
flagRecoveryKey string
|
||||
flagDatabaseUrl string
|
||||
flagContainerImage string
|
||||
flagDisableDatabaseDestruction bool
|
||||
}
|
||||
|
||||
@@ -247,6 +247,11 @@ func (c *Command) Flags() *base.FlagSets {
|
||||
Target: &c.flagDatabaseUrl,
|
||||
Usage: `If set, specifies the URL used to connect to the database for initialization (otherwise a Docker container will be started). This can refer to a file on disk (file://) from which a URL will be read; an env var (env://) from which the URL will be read; or a direct database URL.`,
|
||||
})
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: "container-image",
|
||||
Target: &c.flagContainerImage,
|
||||
Usage: `Specifies a container image to be utilized. Must be in <repo>:<tag> format`,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
@@ -271,8 +276,6 @@ func (c *Command) Run(args []string) int {
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
// childShutdownCh := make(chan struct{})
|
||||
|
||||
c.Config, err = config.DevCombined()
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Errorf("Error creating controller dev config: %w", err).Error())
|
||||
@@ -433,20 +436,20 @@ func (c *Command) Run(args []string) int {
|
||||
}
|
||||
}()
|
||||
|
||||
var opts []base.Option
|
||||
switch c.flagDatabaseUrl {
|
||||
case "":
|
||||
var opts []base.Option
|
||||
if c.flagDisableDatabaseDestruction {
|
||||
opts = append(opts, base.WithSkipDatabaseDestruction())
|
||||
}
|
||||
if err := c.CreateDevDatabase(c.Context, "postgres", opts...); err != nil {
|
||||
if err == docker.ErrDockerUnsupported {
|
||||
c.UI.Error("Automatically starting a Docker container running Postgres is not currently supported on this platform. Please use -database-url to pass in a URL (or an env var or file reference to a URL) for connecting to an existing empty database.")
|
||||
return base.CommandCliError
|
||||
}
|
||||
c.UI.Error(fmt.Errorf("Error creating dev database container: %w", err).Error())
|
||||
if c.flagContainerImage != "" {
|
||||
opts = append(opts, base.WithContainerImage(c.flagContainerImage))
|
||||
}
|
||||
if err := c.CreateDevDatabase(c.Context, opts...); err != nil {
|
||||
c.UI.Error(fmt.Errorf("Error creating dev database container %w", err).Error())
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
if !c.flagDisableDatabaseDestruction {
|
||||
c.ShutdownFuncs = append(c.ShutdownFuncs, c.DestroyDevDatabase)
|
||||
}
|
||||
@@ -456,7 +459,7 @@ func (c *Command) Run(args []string) int {
|
||||
c.UI.Error(fmt.Errorf("Error parsing database url: %w", err).Error())
|
||||
return base.CommandUserError
|
||||
}
|
||||
if err := c.CreateDevDatabase(c.Context, "postgres"); err != nil {
|
||||
if err := c.CreateDevDatabase(c.Context, opts...); err != nil {
|
||||
c.UI.Error(fmt.Errorf("Error connecting to database: %w", err).Error())
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ func testServerCommand(t *testing.T, controllerKey string) *Command {
|
||||
require.NoError(err)
|
||||
require.NoError(cmd.SetupKMSes(cmd.UI, parsedKmsConfig))
|
||||
|
||||
err = cmd.CreateDevDatabase(cmd.Context, "postgres")
|
||||
opts := base.WithContainerImage("postgres")
|
||||
err = cmd.CreateDevDatabase(cmd.Context, opts)
|
||||
if err != nil {
|
||||
if cmd.DevDatabaseCleanupFunc != nil {
|
||||
require.NoError(cmd.DevDatabaseCleanupFunc())
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
func TestNewManager(t *testing.T) {
|
||||
dialect := "postgres"
|
||||
|
||||
c, u, _, err := docker.StartDbInDocker(dialect)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
@@ -37,6 +38,7 @@ func TestNewManager(t *testing.T) {
|
||||
|
||||
func TestCurrentState(t *testing.T) {
|
||||
dialect := "postgres"
|
||||
|
||||
c, u, _, err := docker.StartDbInDocker(dialect)
|
||||
t.Cleanup(func() {
|
||||
if err := c(); err != nil {
|
||||
@@ -78,6 +80,7 @@ func TestCurrentState(t *testing.T) {
|
||||
|
||||
func TestRollForward(t *testing.T) {
|
||||
dialect := "postgres"
|
||||
|
||||
c, u, _, err := docker.StartDbInDocker(dialect)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
@@ -140,6 +143,7 @@ func TestRollForward_NotFromFresh(t *testing.T) {
|
||||
|
||||
func TestRunMigration_canceledContext(t *testing.T) {
|
||||
dialect := "postgres"
|
||||
|
||||
c, u, _, err := docker.StartDbInDocker(dialect)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
@@ -191,6 +195,7 @@ func TestRollForward_BadSQL(t *testing.T) {
|
||||
func TestManager_ExclusiveLock(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
dialect := "postgres"
|
||||
|
||||
c, u, _, err := docker.StartDbInDocker(dialect)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
@@ -214,7 +219,9 @@ func TestManager_ExclusiveLock(t *testing.T) {
|
||||
|
||||
func TestManager_SharedLock(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
c, u, _, err := docker.StartDbInDocker("postgres")
|
||||
dialect := "postgres"
|
||||
|
||||
c, u, _, err := docker.StartDbInDocker(dialect)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, c())
|
||||
@@ -241,7 +248,9 @@ func TestManager_SharedLock(t *testing.T) {
|
||||
func Test_GetMigrationLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
c, u, _, err := docker.StartDbInDocker("postgres")
|
||||
dialect := "postgres"
|
||||
|
||||
c, u, _, err := docker.StartDbInDocker(dialect)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, c())
|
||||
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
StartDbInDocker func(string) (func() error, string, string, error) = startDbInDockerUnsupported
|
||||
StartDbInDocker func(dialect string, opt ...Option) (func() error, string, string, error) = startDbInDockerUnsupported
|
||||
|
||||
ErrDockerUnsupported = errors.New("docker is not currently supported on this platform")
|
||||
|
||||
mx = sync.Mutex{}
|
||||
)
|
||||
|
||||
func startDbInDockerUnsupported(dialect string) (cleanup func() error, retURL, container string, err error) {
|
||||
func startDbInDockerUnsupported(dialect string, opt ...Option) (cleanup func() error, retURL, container string, err error) {
|
||||
return nil, "", "", ErrDockerUnsupported
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package docker
|
||||
|
||||
// GetOpts - iterate the inbound Options and return a struct.
|
||||
func GetOpts(opt ...Option) Options {
|
||||
opts := getDefaultOptions()
|
||||
for _, o := range opt {
|
||||
o(&opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// Option - how Options are passed as arguments.
|
||||
type Option func(*Options)
|
||||
|
||||
// Options - how Options are represented.
|
||||
type Options struct {
|
||||
withContainerImage string
|
||||
}
|
||||
|
||||
func getDefaultOptions() Options {
|
||||
return Options{}
|
||||
}
|
||||
|
||||
// WithContainerImage tells the command which container image
|
||||
// to start a dev database with
|
||||
func WithContainerImage(image string) Option {
|
||||
return func(o *Options) {
|
||||
o.withContainerImage = image
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ func init() {
|
||||
StartDbInDocker = startDbInDockerSupported
|
||||
}
|
||||
|
||||
func startDbInDockerSupported(dialect string) (cleanup func() error, retURL, container string, err error) {
|
||||
func startDbInDockerSupported(dialect string, opt ...Option) (cleanup func() error, retURL, container string, err error) {
|
||||
// TODO: Debug what part of this method is actually causing race condition issues with our test and fix.
|
||||
mx.Lock()
|
||||
defer mx.Unlock()
|
||||
@@ -25,15 +25,30 @@ func startDbInDockerSupported(dialect string) (cleanup func() error, retURL, con
|
||||
}
|
||||
|
||||
var resource *dockertest.Resource
|
||||
var url string
|
||||
var url, tag, repository string
|
||||
|
||||
opts := GetOpts(opt...)
|
||||
if opts.withContainerImage != "" {
|
||||
repository, tag, err = splitImage(opts)
|
||||
if err != nil {
|
||||
return func() error { return nil }, "", "", fmt.Errorf("error parsing reference: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch dialect {
|
||||
case "postgres":
|
||||
switch {
|
||||
case os.Getenv("BOUNDARY_TESTING_PG_URL") != "":
|
||||
url = os.Getenv("BOUNDARY_TESTING_PG_URL")
|
||||
return func() error { return nil }, url, "", nil
|
||||
case repository != "":
|
||||
resource, err = pool.Run(repository, tag, []string{"POSTGRES_PASSWORD=password", "POSTGRES_DB=boundary"})
|
||||
url = "postgres://postgres:password@localhost:%s?sslmode=disable"
|
||||
if err == nil {
|
||||
url = fmt.Sprintf("postgres://postgres:password@%s/boundary?sslmode=disable", resource.GetHostPort("5432/tcp"))
|
||||
}
|
||||
default:
|
||||
resource, err = pool.Run("postgres", "11", []string{"POSTGRES_PASSWORD=password", "POSTGRES_DB=boundary"})
|
||||
resource, err = pool.Run(dialect, tag, []string{"POSTGRES_PASSWORD=password", "POSTGRES_DB=boundary"})
|
||||
url = "postgres://postgres:password@localhost:%s?sslmode=disable"
|
||||
if err == nil {
|
||||
url = fmt.Sprintf("postgres://postgres:password@%s/boundary?sslmode=disable", resource.GetHostPort("5432/tcp"))
|
||||
@@ -81,5 +96,30 @@ func cleanupDockerResource(pool *dockertest.Pool, resource *dockertest.Resource)
|
||||
if strings.Contains(err.Error(), "No such container") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Failed to cleanup local container: %s", err)
|
||||
return fmt.Errorf("failed to cleanup local container: %s", err)
|
||||
}
|
||||
|
||||
// splitImage takes the WithDatabaseImage option and separates
|
||||
// it into repo + tag. If a tag is not found, it sets the tag to latest
|
||||
func splitImage(opts Options) (string, string, error) {
|
||||
|
||||
separated := strings.Split(opts.withContainerImage, ":")
|
||||
separatedlen := len(separated)
|
||||
|
||||
switch separatedlen {
|
||||
case 1:
|
||||
if separated[0] == "postgres" {
|
||||
return separated[0], "11", nil
|
||||
}
|
||||
return "", "", fmt.Errorf("valid reference format is repo:tag, if"+
|
||||
" no tag provided then repo must be postgres, got: %s", opts.withContainerImage)
|
||||
|
||||
case 2:
|
||||
return separated[0], separated[1], nil
|
||||
|
||||
default:
|
||||
return "", "", fmt.Errorf("valid reference format is repo:tag, got: %s", opts.withContainerImage)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -56,6 +56,7 @@ func testId(t *testing.T) string {
|
||||
|
||||
func testInitDbInDocker(t *testing.T) (cleanup func() error, retURL string, err error) {
|
||||
t.Helper()
|
||||
|
||||
cleanup, retURL, _, err = docker.StartDbInDocker("postgres")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -523,7 +523,7 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController {
|
||||
if opts.DisableAuthMethodCreation {
|
||||
createOpts = append(createOpts, base.WithSkipAuthMethodCreation())
|
||||
}
|
||||
if err := tc.b.CreateDevDatabase(ctx, "postgres", createOpts...); err != nil {
|
||||
if err := tc.b.CreateDevDatabase(ctx, createOpts...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user