Skip to content

Commit

Permalink
Merge pull request #50 from buildpacks/ephemeral-network
Browse files Browse the repository at this point in the history
Launch build containers in a separate ephemeral Docker bridge network
  • Loading branch information
natalieparellano authored Jul 11, 2024
2 parents 72ffc75 + 98ffc46 commit c1a1382
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 2 deletions.
2 changes: 2 additions & 0 deletions internal/build/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type DockerClient interface {
ContainerInspect(ctx context.Context, container string) (types.ContainerJSON, error)
ContainerRemove(ctx context.Context, container string, options containertypes.RemoveOptions) error
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error
NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
NetworkRemove(ctx context.Context, network string) error
}

var _ DockerClient = dockerClient.CommonAPIClient(nil)
25 changes: 25 additions & 0 deletions internal/build/lifecycle_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/auth"
"github.com/buildpacks/lifecycle/platform/files"
"github.com/docker/docker/api/types"
"github.com/google/go-containerregistry/pkg/name"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
Expand Down Expand Up @@ -165,6 +166,7 @@ func (l *LifecycleExecution) PrevImageName() string {

func (l *LifecycleExecution) Run(ctx context.Context, phaseFactoryCreator PhaseFactoryCreator) error {
phaseFactory := phaseFactoryCreator(l)

var buildCache Cache
if l.opts.CacheImage != "" || (l.opts.Cache.Build.Format == cache.CacheImage) {
cacheImageName := l.opts.CacheImage
Expand Down Expand Up @@ -196,6 +198,29 @@ func (l *LifecycleExecution) Run(ctx context.Context, phaseFactoryCreator PhaseF

launchCache := cache.NewVolumeCache(l.opts.Image, l.opts.Cache.Launch, "launch", l.docker)

if l.opts.Network == "" {
// start an ephemeral bridge network
driver := "bridge"
if l.os == "windows" {
driver = "nat"
}
networkName := fmt.Sprintf("pack.local/network/%x", randString(10))
resp, err := l.docker.NetworkCreate(ctx, networkName, types.NetworkCreate{
Driver: driver,
})
if err != nil {
return fmt.Errorf("failed to create ephemeral %s network: %w", driver, err)
}
defer func() {
_ = l.docker.NetworkRemove(ctx, networkName)
}()
l.logger.Debugf("Created ephemeral bridge network %s with ID %s", networkName, resp.ID)
if resp.Warning != "" {
l.logger.Warn(resp.Warning)
}
l.opts.Network = networkName
}

if !l.opts.UseCreator {
if l.platformAPI.LessThan("0.7") {
l.logger.Info(style.Step("DETECTING"))
Expand Down
65 changes: 63 additions & 2 deletions internal/build/lifecycle_execution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
ifakes "github.com/buildpacks/imgutil/fakes"
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/platform/files"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/google/go-containerregistry/pkg/authn"
Expand Down Expand Up @@ -275,7 +276,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
fakeBuilder *fakes.FakeBuilder
outBuf bytes.Buffer
logger *logging.LogWithWriters
docker *client.Client
docker *fakeDockerClient
fakeTermui *fakes.FakeTermui
)

Expand All @@ -289,7 +290,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
fakeBuilder, err = fakes.NewFakeBuilder(fakes.WithSupportedPlatformAPIs([]*api.Version{api.MustParse("0.3")}))
h.AssertNil(t, err)
logger = logging.NewLogWithWriters(&outBuf, &outBuf)
docker, err = client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38"))
docker = &fakeDockerClient{}
h.AssertNil(t, err)
fakePhaseFactory = fakes.NewFakePhaseFactory()
})
Expand Down Expand Up @@ -780,6 +781,46 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
})
})

when("network is not provided", func() {
it("creates an ephemeral bridge network", func() {
beforeNetworks := func() int {
networks, err := docker.NetworkList(context.Background(), types.NetworkListOptions{})
h.AssertNil(t, err)
return len(networks)
}()

opts := build.LifecycleOptions{
Image: imageName,
Builder: fakeBuilder,
Termui: fakeTermui,
}

lifecycle, err := build.NewLifecycleExecution(logger, docker, "some-temp-dir", opts)
h.AssertNil(t, err)

err = lifecycle.Run(context.Background(), func(execution *build.LifecycleExecution) build.PhaseFactory {
return fakePhaseFactory
})
h.AssertNil(t, err)

for _, entry := range fakePhaseFactory.NewCalledWithProvider {
h.AssertContains(t, string(entry.HostConfig().NetworkMode), "pack.local/network/")
h.AssertEq(t, entry.HostConfig().NetworkMode.IsDefault(), false)
h.AssertEq(t, entry.HostConfig().NetworkMode.IsHost(), false)
h.AssertEq(t, entry.HostConfig().NetworkMode.IsNone(), false)
h.AssertEq(t, entry.HostConfig().NetworkMode.IsPrivate(), true)
h.AssertEq(t, entry.HostConfig().NetworkMode.IsUserDefined(), true)
}

afterNetworks := func() int {
networks, err := docker.NetworkList(context.Background(), types.NetworkListOptions{})
h.AssertNil(t, err)
return len(networks)
}()
h.AssertEq(t, beforeNetworks, afterNetworks)
})
})

when("Error cases", func() {
when("passed invalid", func() {
it("fails for cache-image", func() {
Expand Down Expand Up @@ -2657,6 +2698,26 @@ func (f *fakeImageFetcher) fetchRunImage(name string) error {
return nil
}

type fakeDockerClient struct {
nNetworks int
build.DockerClient
}

func (f *fakeDockerClient) NetworkList(ctx context.Context, opts types.NetworkListOptions) ([]types.NetworkResource, error) {
ret := make([]types.NetworkResource, f.nNetworks)
return ret, nil
}

func (f *fakeDockerClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
f.nNetworks++
return types.NetworkCreateResponse{}, nil
}

func (f *fakeDockerClient) NetworkRemove(ctx context.Context, network string) error {
f.nNetworks--
return nil
}

func newTestLifecycleExecErr(t *testing.T, logVerbose bool, tmpDir string, ops ...func(*build.LifecycleOptions)) (*build.LifecycleExecution, error) {
docker, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38"))
h.AssertNil(t, err)
Expand Down
2 changes: 2 additions & 0 deletions pkg/client/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ type DockerClient interface {
ContainerWait(ctx context.Context, container string, condition containertypes.WaitCondition) (<-chan containertypes.WaitResponse, <-chan error)
ContainerAttach(ctx context.Context, container string, options containertypes.AttachOptions) (types.HijackedResponse, error)
ContainerStart(ctx context.Context, container string, options containertypes.StartOptions) error
NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
NetworkRemove(ctx context.Context, network string) error
}

0 comments on commit c1a1382

Please sign in to comment.