Add -container-image flag (#1112)

This commit is contained in:
s-christoff authored and GitHub committed 2021-05-03 13:11:28 -05:00
1 parent ecc9d6d649
commit 9ceeb3bd65
10 files changed
+138 -27

No files matched your search

+20 -1
View File
@@ -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
}
}
+13 -5
View File
@@ -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 -11
View File
@@ -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
}
+2 -1
View File
@@ -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())
+11 -2
View File
@@ -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())
+2 -2
View File
@@ -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
}
+30
View File
@@ -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
}
}
+44 -4
View File
@@ -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)
}
}
+1
View File
@@ -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)
+1 -1
View File
@@ -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)
}
}