diff --git a/builder/build/postbuild.go b/builder/build/postbuild.go index cdb99e3..d6c3b33 100644 --- a/builder/build/postbuild.go +++ b/builder/build/postbuild.go @@ -86,7 +86,7 @@ func (b *Build) RunPostbuild() error { if b.System() == BuildpackBuildSystemKeyword { entrypoint = []string{"/cnb/lifecycle/launcher"} } - err = b.containers.RunContainer(containerID, b.CodebuildBuildId, &container.Config{ + err = b.containers.RunContainer(containerID, b.CodebuildBuildId, nil, &container.Config{ Image: imageName, Cmd: []string{"/bin/sh", "-c", testScript}, Entrypoint: entrypoint, diff --git a/builder/build/prebuild.go b/builder/build/prebuild.go index 748491a..9c4050e 100644 --- a/builder/build/prebuild.go +++ b/builder/build/prebuild.go @@ -348,6 +348,11 @@ func (b *Build) StartAddons() (map[string]string, error) { addons := removeDuplicateStr(b.AppJSON.GetTestAddons()) redisImage := "redis:alpine" postgresImage := "postgres:alpine" + // Container names must be unique on the CodeBuild Docker daemon (which is + // reused across builds), so prefix them with the build ID. The friendly + // name ("redis"/"db") is kept as a network alias so other containers can + // still reach the addon by that hostname. + namePrefix := strings.ReplaceAll(b.CodebuildBuildId, ":", "-") // use a switch statement to iterate over addons for _, addon := range addons { switch addon { @@ -355,7 +360,7 @@ func (b *Build) StartAddons() (map[string]string, error) { if err = b.containers.PullImage(redisImage); err != nil { return nil, err } - if err = b.containers.RunContainer("redis", b.CodebuildBuildId, &container.Config{Image: redisImage}); err != nil { + if err = b.containers.RunContainer(fmt.Sprintf("%s-redis", namePrefix), b.CodebuildBuildId, []string{"redis"}, &container.Config{Image: redisImage}); err != nil { return nil, err } envOverides["REDIS_URL"] = "redis://redis:6379" @@ -363,7 +368,7 @@ func (b *Build) StartAddons() (map[string]string, error) { if err = b.containers.PullImage(postgresImage); err != nil { return nil, err } - if err = b.containers.RunContainer("db", b.CodebuildBuildId, &container.Config{Image: postgresImage}); err != nil { + if err = b.containers.RunContainer(fmt.Sprintf("%s-db", namePrefix), b.CodebuildBuildId, []string{"db"}, &container.Config{Image: postgresImage}); err != nil { return nil, err } envOverides["DATABASE_URL"] = "postgres://postgres:postgres@db:5432/postgres" diff --git a/builder/build/prebuild_test.go b/builder/build/prebuild_test.go index dd31efa..9b81cd6 100644 --- a/builder/build/prebuild_test.go +++ b/builder/build/prebuild_test.go @@ -165,8 +165,8 @@ func (c *MockContainers) CreateContainer(s1 string, cfg *container.Config) (*str return args.Get(0).(*string), args.Error(1) } -func (c *MockContainers) RunContainer(s1 string, s2 string, cfg *container.Config) error { - args := c.Called(s1, s2, cfg) +func (c *MockContainers) RunContainer(s1 string, s2 string, aliases []string, cfg *container.Config) error { + args := c.Called(s1, s2, aliases, cfg) return args.Error(0) } @@ -578,7 +578,7 @@ func TestStartAddons(t *testing.T) { ).Return(nil) mockedContainers.On( "RunContainer", - "redis", CodebuildBuildId, &container.Config{Image: "redis:alpine"}, + fmt.Sprintf("%s-redis", CodebuildBuildId), CodebuildBuildId, []string{"redis"}, &container.Config{Image: "redis:alpine"}, ).Return(nil) mockedContainers.On( "PullImage", @@ -586,7 +586,7 @@ func TestStartAddons(t *testing.T) { ).Return(nil) mockedContainers.On( "RunContainer", - "db", CodebuildBuildId, &container.Config{Image: "postgres:alpine"}, + fmt.Sprintf("%s-db", CodebuildBuildId), CodebuildBuildId, []string{"db"}, &container.Config{Image: "postgres:alpine"}, ).Return(nil) b := Build{ CodebuildBuildId: CodebuildBuildId, diff --git a/builder/containers/containers.go b/builder/containers/containers.go index 7c31c88..1a871ae 100644 --- a/builder/containers/containers.go +++ b/builder/containers/containers.go @@ -48,7 +48,7 @@ type ContainersI interface { BuildImage(string, *BuildConfig) error CreateContainer(string, *container.Config) (*string, error) DeleteContainer(string) error - RunContainer(string, string, *container.Config) error + RunContainer(string, string, []string, *container.Config) error GetContainerFile(string, string) (io.ReadCloser, error) WaitForExit(string) (int, error) AttachLogs(string, io.Writer, io.Writer) error @@ -141,13 +141,21 @@ func (c *Containers) CreateContainer(name string, config *container.Config) (*st return &resp.ID, nil } -func (c *Containers) RunContainer(name string, networkID string, config *container.Config) error { - c.Log().Debug().Str("image", config.Image).Str("container", name).Msg("starting container") +// RunContainer creates and starts a container connected to networkID. The +// container is created with the (unique) name so it never collides on a +// reused Docker daemon. Any aliases are registered as network-scoped +// hostnames so other containers can reach it by a friendly name (e.g. "db"). +func (c *Containers) RunContainer(name string, networkID string, aliases []string, config *container.Config) error { + c.Log().Debug().Str("image", config.Image).Str("container", name).Strs("aliases", aliases).Msg("starting container") containerID, err := c.CreateContainer(name, config) if err != nil { return err } - err = c.cli.NetworkConnect(c.ctx, networkID, *containerID, nil) + var endpoint *network.EndpointSettings + if len(aliases) > 0 { + endpoint = &network.EndpointSettings{Aliases: aliases} + } + err = c.cli.NetworkConnect(c.ctx, networkID, *containerID, endpoint) if err != nil { return err }