Skip to content

Commit c1a1382

Browse files
author
Natalie Arellano
authored
Merge pull request #50 from buildpacks/ephemeral-network
Launch build containers in a separate ephemeral Docker bridge network
2 parents 72ffc75 + 98ffc46 commit c1a1382

File tree

4 files changed

+92
-2
lines changed

4 files changed

+92
-2
lines changed

internal/build/docker.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ type DockerClient interface {
2323
ContainerInspect(ctx context.Context, container string) (types.ContainerJSON, error)
2424
ContainerRemove(ctx context.Context, container string, options containertypes.RemoveOptions) error
2525
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error
26+
NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
27+
NetworkRemove(ctx context.Context, network string) error
2628
}
2729

2830
var _ DockerClient = dockerClient.CommonAPIClient(nil)

internal/build/lifecycle_execution.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/buildpacks/lifecycle/api"
1313
"github.com/buildpacks/lifecycle/auth"
1414
"github.com/buildpacks/lifecycle/platform/files"
15+
"github.com/docker/docker/api/types"
1516
"github.com/google/go-containerregistry/pkg/name"
1617
"github.com/pkg/errors"
1718
"golang.org/x/sync/errgroup"
@@ -165,6 +166,7 @@ func (l *LifecycleExecution) PrevImageName() string {
165166

166167
func (l *LifecycleExecution) Run(ctx context.Context, phaseFactoryCreator PhaseFactoryCreator) error {
167168
phaseFactory := phaseFactoryCreator(l)
169+
168170
var buildCache Cache
169171
if l.opts.CacheImage != "" || (l.opts.Cache.Build.Format == cache.CacheImage) {
170172
cacheImageName := l.opts.CacheImage
@@ -196,6 +198,29 @@ func (l *LifecycleExecution) Run(ctx context.Context, phaseFactoryCreator PhaseF
196198

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

201+
if l.opts.Network == "" {
202+
// start an ephemeral bridge network
203+
driver := "bridge"
204+
if l.os == "windows" {
205+
driver = "nat"
206+
}
207+
networkName := fmt.Sprintf("pack.local/network/%x", randString(10))
208+
resp, err := l.docker.NetworkCreate(ctx, networkName, types.NetworkCreate{
209+
Driver: driver,
210+
})
211+
if err != nil {
212+
return fmt.Errorf("failed to create ephemeral %s network: %w", driver, err)
213+
}
214+
defer func() {
215+
_ = l.docker.NetworkRemove(ctx, networkName)
216+
}()
217+
l.logger.Debugf("Created ephemeral bridge network %s with ID %s", networkName, resp.ID)
218+
if resp.Warning != "" {
219+
l.logger.Warn(resp.Warning)
220+
}
221+
l.opts.Network = networkName
222+
}
223+
199224
if !l.opts.UseCreator {
200225
if l.platformAPI.LessThan("0.7") {
201226
l.logger.Info(style.Step("DETECTING"))

internal/build/lifecycle_execution_test.go

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
ifakes "github.com/buildpacks/imgutil/fakes"
1717
"github.com/buildpacks/lifecycle/api"
1818
"github.com/buildpacks/lifecycle/platform/files"
19+
"github.com/docker/docker/api/types"
1920
"github.com/docker/docker/api/types/container"
2021
"github.com/docker/docker/client"
2122
"github.com/google/go-containerregistry/pkg/authn"
@@ -275,7 +276,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
275276
fakeBuilder *fakes.FakeBuilder
276277
outBuf bytes.Buffer
277278
logger *logging.LogWithWriters
278-
docker *client.Client
279+
docker *fakeDockerClient
279280
fakeTermui *fakes.FakeTermui
280281
)
281282

@@ -289,7 +290,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
289290
fakeBuilder, err = fakes.NewFakeBuilder(fakes.WithSupportedPlatformAPIs([]*api.Version{api.MustParse("0.3")}))
290291
h.AssertNil(t, err)
291292
logger = logging.NewLogWithWriters(&outBuf, &outBuf)
292-
docker, err = client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38"))
293+
docker = &fakeDockerClient{}
293294
h.AssertNil(t, err)
294295
fakePhaseFactory = fakes.NewFakePhaseFactory()
295296
})
@@ -780,6 +781,46 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
780781
})
781782
})
782783

784+
when("network is not provided", func() {
785+
it("creates an ephemeral bridge network", func() {
786+
beforeNetworks := func() int {
787+
networks, err := docker.NetworkList(context.Background(), types.NetworkListOptions{})
788+
h.AssertNil(t, err)
789+
return len(networks)
790+
}()
791+
792+
opts := build.LifecycleOptions{
793+
Image: imageName,
794+
Builder: fakeBuilder,
795+
Termui: fakeTermui,
796+
}
797+
798+
lifecycle, err := build.NewLifecycleExecution(logger, docker, "some-temp-dir", opts)
799+
h.AssertNil(t, err)
800+
801+
err = lifecycle.Run(context.Background(), func(execution *build.LifecycleExecution) build.PhaseFactory {
802+
return fakePhaseFactory
803+
})
804+
h.AssertNil(t, err)
805+
806+
for _, entry := range fakePhaseFactory.NewCalledWithProvider {
807+
h.AssertContains(t, string(entry.HostConfig().NetworkMode), "pack.local/network/")
808+
h.AssertEq(t, entry.HostConfig().NetworkMode.IsDefault(), false)
809+
h.AssertEq(t, entry.HostConfig().NetworkMode.IsHost(), false)
810+
h.AssertEq(t, entry.HostConfig().NetworkMode.IsNone(), false)
811+
h.AssertEq(t, entry.HostConfig().NetworkMode.IsPrivate(), true)
812+
h.AssertEq(t, entry.HostConfig().NetworkMode.IsUserDefined(), true)
813+
}
814+
815+
afterNetworks := func() int {
816+
networks, err := docker.NetworkList(context.Background(), types.NetworkListOptions{})
817+
h.AssertNil(t, err)
818+
return len(networks)
819+
}()
820+
h.AssertEq(t, beforeNetworks, afterNetworks)
821+
})
822+
})
823+
783824
when("Error cases", func() {
784825
when("passed invalid", func() {
785826
it("fails for cache-image", func() {
@@ -2657,6 +2698,26 @@ func (f *fakeImageFetcher) fetchRunImage(name string) error {
26572698
return nil
26582699
}
26592700

2701+
type fakeDockerClient struct {
2702+
nNetworks int
2703+
build.DockerClient
2704+
}
2705+
2706+
func (f *fakeDockerClient) NetworkList(ctx context.Context, opts types.NetworkListOptions) ([]types.NetworkResource, error) {
2707+
ret := make([]types.NetworkResource, f.nNetworks)
2708+
return ret, nil
2709+
}
2710+
2711+
func (f *fakeDockerClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
2712+
f.nNetworks++
2713+
return types.NetworkCreateResponse{}, nil
2714+
}
2715+
2716+
func (f *fakeDockerClient) NetworkRemove(ctx context.Context, network string) error {
2717+
f.nNetworks--
2718+
return nil
2719+
}
2720+
26602721
func newTestLifecycleExecErr(t *testing.T, logVerbose bool, tmpDir string, ops ...func(*build.LifecycleOptions)) (*build.LifecycleExecution, error) {
26612722
docker, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38"))
26622723
h.AssertNil(t, err)

pkg/client/docker.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ type DockerClient interface {
3232
ContainerWait(ctx context.Context, container string, condition containertypes.WaitCondition) (<-chan containertypes.WaitResponse, <-chan error)
3333
ContainerAttach(ctx context.Context, container string, options containertypes.AttachOptions) (types.HijackedResponse, error)
3434
ContainerStart(ctx context.Context, container string, options containertypes.StartOptions) error
35+
NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
36+
NetworkRemove(ctx context.Context, network string) error
3537
}

0 commit comments

Comments
 (0)