Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions internal/build/fakes/fake_builder.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package fakes

import (
"net"

"github.com/Masterminds/semver"
"github.com/buildpacks/imgutil"
ifakes "github.com/buildpacks/imgutil/fakes"
"github.com/buildpacks/lifecycle/api"
"github.com/moby/moby/api/types/network"

"github.com/buildpacks/pack/internal/build"
"github.com/buildpacks/pack/internal/builder"
Expand Down Expand Up @@ -132,6 +135,17 @@ func WithEnableUsernsHost() func(*build.LifecycleOptions) {
}
}

// WithMacAddress creates a LifecycleOptions option that sets the container MAC address.
func WithMacAddress(macAddress string) func(*build.LifecycleOptions) {
return func(opts *build.LifecycleOptions) {
parsed, err := net.ParseMAC(macAddress)
if err != nil {
panic(err)
}
opts.MacAddress = network.HardwareAddr(parsed)
}
}

// WithExecutionEnvironment creates a LifecycleOptions option that sets the execution environment
func WithExecutionEnvironment(execEnv string) func(*build.LifecycleOptions) {
return func(opts *build.LifecycleOptions) {
Expand Down
2 changes: 2 additions & 0 deletions internal/build/lifecycle_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/buildpacks/lifecycle/platform/files"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/moby/moby/api/types/network"

"github.com/buildpacks/pack/internal/builder"
"github.com/buildpacks/pack/internal/container"
Expand Down Expand Up @@ -95,6 +96,7 @@ type LifecycleOptions struct {
HTTPSProxy string
NoProxy string
Network string
MacAddress network.HardwareAddr
AdditionalTags []string
Volumes []string
InsecureRegistries []string
Expand Down
7 changes: 5 additions & 2 deletions internal/build/phase.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"

dcontainer "github.com/moby/moby/api/types/container"
dnetwork "github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"github.com/pkg/errors"

Expand All @@ -19,6 +20,7 @@ type Phase struct {
handler container.Handler
ctrConf *dcontainer.Config
hostConf *dcontainer.HostConfig
networkConf *dnetwork.NetworkingConfig
ctr client.ContainerCreateResult
uid, gid int
appPath string
Expand All @@ -30,8 +32,9 @@ type Phase struct {
func (p *Phase) Run(ctx context.Context) error {
var err error
p.ctr, err = p.docker.ContainerCreate(ctx, client.ContainerCreateOptions{
Config: p.ctrConf,
HostConfig: p.hostConf,
Config: p.ctrConf,
HostConfig: p.hostConf,
NetworkingConfig: p.networkConf,
})
if err != nil {
return errors.Wrapf(err, "failed to create '%s' container", p.name)
Expand Down
32 changes: 32 additions & 0 deletions internal/build/phase_config_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"

pcontainer "github.com/buildpacks/pack/internal/container"
"github.com/buildpacks/pack/internal/style"
Expand All @@ -25,6 +26,7 @@ type PhaseConfigProviderOperation func(*PhaseConfigProvider)
type PhaseConfigProvider struct {
ctrConf *container.Config
hostConf *container.HostConfig
networkConf *network.NetworkingConfig
name string
os string
containerOps []ContainerOperation
Expand Down Expand Up @@ -72,6 +74,10 @@ func NewPhaseConfigProvider(name string, lifecycleExec *LifecycleExecution, ops
op(provider)
}

if len(lifecycleExec.opts.MacAddress) > 0 {
provider.withMACAddress(lifecycleExec.opts.MacAddress)
}

provider.ctrConf.Entrypoint = []string{""} // override entrypoint in case it is set
provider.ctrConf.Cmd = append([]string{"/cnb/lifecycle/" + name}, provider.ctrConf.Cmd...)

Expand All @@ -86,6 +92,9 @@ func NewPhaseConfigProvider(name string, lifecycleExec *LifecycleExecution, ops
lifecycleExec.logger.Debug("Host Settings:")
lifecycleExec.logger.Debugf(" Binds: %s", style.Symbol(strings.Join(provider.hostConf.Binds, " ")))
lifecycleExec.logger.Debugf(" Network Mode: %s", style.Symbol(string(provider.hostConf.NetworkMode)))
if len(lifecycleExec.opts.MacAddress) > 0 {
lifecycleExec.logger.Debugf(" MAC Address: %s", style.Symbol(lifecycleExec.opts.MacAddress.String()))
}

if lifecycleExec.opts.Interactive {
provider.handler = lifecycleExec.opts.Termui.Handler()
Expand All @@ -106,10 +115,33 @@ func sanitized(origEnv []string) []string {
return sanitizedEnv
}

func (p *PhaseConfigProvider) withMACAddress(macAddress network.HardwareAddr) {
if p.networkConf == nil {
p.networkConf = new(network.NetworkingConfig)
}
if p.networkConf.EndpointsConfig == nil {
p.networkConf.EndpointsConfig = make(map[string]*network.EndpointSettings)
}

networkName := p.hostConf.NetworkMode.NetworkName()
if networkName == "" {
networkName = network.NetworkDefault
}

if p.networkConf.EndpointsConfig[networkName] == nil {
p.networkConf.EndpointsConfig[networkName] = &network.EndpointSettings{}
}
p.networkConf.EndpointsConfig[networkName].MacAddress = macAddress
}

func (p *PhaseConfigProvider) ContainerConfig() *container.Config {
return p.ctrConf
}

func (p *PhaseConfigProvider) NetworkConfig() *network.NetworkingConfig {
return p.networkConf
}

func (p *PhaseConfigProvider) ContainerOps() []ContainerOperation {
return p.containerOps
}
Expand Down
26 changes: 26 additions & 0 deletions internal/build/phase_config_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/buildpacks/lifecycle/api"
"github.com/heroku/color"
dcontainer "github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"github.com/pkg/errors"
"github.com/sclevine/spec"
Expand Down Expand Up @@ -77,6 +78,31 @@ func testPhaseConfigProvider(t *testing.T, when spec.G, it spec.S) {
})
})

when("mac address is set", func() {
it("sets the mac address on the default network endpoint", func() {
expectedMACAddress := "01:23:45:67:89:ab"
lifecycle := newTestLifecycleExec(t, false, "some-temp-dir", fakes.WithMacAddress(expectedMACAddress))

phaseConfigProvider := build.NewPhaseConfigProvider("some-name", lifecycle)

endpoint := phaseConfigProvider.NetworkConfig().EndpointsConfig[network.NetworkDefault]
h.AssertNotNil(t, endpoint)
h.AssertEq(t, endpoint.MacAddress.String(), expectedMACAddress)
})

it("sets the mac address on the selected network endpoint", func() {
expectedMACAddress := "01:23:45:67:89:ab"
expectedNetwork := "some-network"
lifecycle := newTestLifecycleExec(t, false, "some-temp-dir", fakes.WithMacAddress(expectedMACAddress))

phaseConfigProvider := build.NewPhaseConfigProvider("some-name", lifecycle, build.WithNetwork(expectedNetwork))

endpoint := phaseConfigProvider.NetworkConfig().EndpointsConfig[expectedNetwork]
h.AssertNotNil(t, endpoint)
h.AssertEq(t, endpoint.MacAddress.String(), expectedMACAddress)
})
})

when("building for Windows", func() {
it("sets process isolation", func() {
fakeBuilderImage := ifakes.NewImage("fake-builder", "", nil)
Expand Down
1 change: 1 addition & 0 deletions internal/build/phase_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func (m *DefaultPhaseFactory) New(provider *PhaseConfigProvider) RunnerCleaner {
return &Phase{
ctrConf: provider.ContainerConfig(),
hostConf: provider.HostConfig(),
networkConf: provider.NetworkConfig(),
name: provider.Name(),
docker: m.lifecycleExec.docker,
infoWriter: provider.InfoWriter(),
Expand Down
14 changes: 12 additions & 2 deletions internal/commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"fmt"
"net"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -45,6 +46,7 @@ type BuildFlags struct {
Platform string
Policy string
Network string
MacAddress string
DescriptorPath string
DefaultProcessType string
LifecycleImage string
Expand Down Expand Up @@ -195,8 +197,9 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob
Buildpacks: buildpacks,
Extensions: extensions,
ContainerConfig: client.ContainerConfig{
Network: flags.Network,
Volumes: flags.Volumes,
Network: flags.Network,
MacAddress: flags.MacAddress,
Volumes: flags.Volumes,
},
DefaultProcessType: flags.DefaultProcessType,
ProjectDescriptorBaseDir: filepath.Dir(actualDescriptorPath),
Expand Down Expand Up @@ -275,6 +278,7 @@ func buildCommandFlags(cmd *cobra.Command, buildFlags *BuildFlags, cfg config.Co
cmd.Flags().StringArrayVarP(&buildFlags.Env, "env", "e", []string{}, "Build-time environment variable, in the form 'VAR=VALUE' or 'VAR'.\nWhen using latter value-less form, value will be taken from current\n environment at the time this command is executed.\nThis flag may be specified multiple times and will override\n individual values defined by --env-file."+stringArrayHelp("env")+"\nNOTE: These are NOT available at image runtime.")
cmd.Flags().StringArrayVar(&buildFlags.EnvFiles, "env-file", []string{}, "Build-time environment variables file\nOne variable per line, of the form 'VAR=VALUE' or 'VAR'\nWhen using latter value-less form, value will be taken from current\n environment at the time this command is executed\nNOTE: These are NOT available at image runtime.\"")
cmd.Flags().StringVar(&buildFlags.Network, "network", "", "Connect detect and build containers to network")
cmd.Flags().StringVar(&buildFlags.MacAddress, "mac-address", "", "MAC address to set on the build container network endpoint")
cmd.Flags().StringArrayVar(&buildFlags.PreBuildpacks, "pre-buildpack", []string{}, "Buildpacks to prepend to the groups in the builder's order")
cmd.Flags().StringArrayVar(&buildFlags.PostBuildpacks, "post-buildpack", []string{}, "Buildpacks to append to the groups in the builder's order")
cmd.Flags().BoolVar(&buildFlags.Publish, "publish", false, "Publish the application image directly to the container registry specified in <image-name>, instead of the daemon. The run image must also reside in the registry.")
Expand Down Expand Up @@ -338,6 +342,12 @@ func validateBuildFlags(flags *BuildFlags, cfg config.Config, inputImageRef clie
return errors.New("uid flag must be in the range of 0-2147483647")
}

if flags.MacAddress != "" {
if _, err := net.ParseMAC(flags.MacAddress); err != nil {
return errors.Wrapf(err, "invalid MAC address %q", flags.MacAddress)
}
}

if flags.Interactive && !cfg.Experimental {
return client.NewExperimentError("Interactive mode is currently experimental.")
}
Expand Down
26 changes: 26 additions & 0 deletions internal/commands/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,23 @@ func testBuildCommand(t *testing.T, when spec.G, it spec.S) {
})
})

when("a mac address is given", func() {
it("forwards the mac address onto the client", func() {
mockClient.EXPECT().
Build(gomock.Any(), EqBuildOptionsWithMacAddress("01:23:45:67:89:ab")).
Return(nil)

command.SetArgs([]string{"image", "--builder", "my-builder", "--mac-address", "01:23:45:67:89:ab"})
h.AssertNil(t, command.Execute())
})

it("returns an error for invalid mac addresses", func() {
command.SetArgs([]string{"image", "--builder", "my-builder", "--mac-address", "invalid-mac"})

h.AssertError(t, command.Execute(), `invalid MAC address "invalid-mac"`)
})
})

when("--platform", func() {
it("sets platform", func() {
mockClient.EXPECT().
Expand Down Expand Up @@ -1220,6 +1237,15 @@ func EqBuildOptionsWithNetwork(network string) gomock.Matcher {
}
}

func EqBuildOptionsWithMacAddress(macAddress string) gomock.Matcher {
return buildOptionsMatcher{
description: fmt.Sprintf("MacAddress=%s", macAddress),
equals: func(o client.BuildOptions) bool {
return o.ContainerConfig.MacAddress == macAddress
},
}
}

func EqBuildOptionsWithBuilder(builder string) gomock.Matcher {
return buildOptionsMatcher{
description: fmt.Sprintf("Builder=%s", builder),
Expand Down
24 changes: 24 additions & 0 deletions pkg/client/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/json"
"fmt"
"io"
"net"
"os"
"path/filepath"
"sort"
Expand All @@ -24,6 +25,7 @@ import (
"github.com/buildpacks/lifecycle/platform/files"
"github.com/chainguard-dev/kaniko/pkg/util/proc"
"github.com/google/go-containerregistry/pkg/name"
mnetwork "github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"github.com/pkg/errors"
ignore "github.com/sabhiram/go-gitignore"
Expand Down Expand Up @@ -261,6 +263,9 @@ type ContainerConfig struct {
// https://docs.docker.com/network/#network-drivers
Network string

// Configure the MAC address of the build containers' network endpoint.
MacAddress string

// Volumes are accessible during both detect build phases
// should have the form: /path/in/host:/path/in/container.
// For more about volume mounts, and their permissions see:
Expand All @@ -274,6 +279,19 @@ type ContainerConfig struct {
Volumes []string
}

func parseMACAddress(macAddress string) (mnetwork.HardwareAddr, error) {
if macAddress == "" {
return nil, nil
}

parsed, err := net.ParseMAC(macAddress)
if err != nil {
return nil, errors.Wrapf(err, "invalid MAC address %q", macAddress)
}

return mnetwork.HardwareAddr(parsed), nil
}

type LayoutConfig struct {
// Application image reference provided by the user
InputImage InputImageReference
Expand Down Expand Up @@ -609,6 +627,11 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
c.logger.Warn(warning)
}

macAddress, err := parseMACAddress(opts.ContainerConfig.MacAddress)
if err != nil {
return err
}

fileFilter, err := getFileFilter(opts.ProjectDescriptor)
if err != nil {
return err
Expand Down Expand Up @@ -654,6 +677,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
HTTPSProxy: proxyConfig.HTTPSProxy,
NoProxy: proxyConfig.NoProxy,
Network: opts.ContainerConfig.Network,
MacAddress: macAddress,
AdditionalTags: opts.AdditionalTags,
Volumes: processedVolumes,
DefaultProcessType: opts.DefaultProcessType,
Expand Down
25 changes: 25 additions & 0 deletions pkg/client/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2564,6 +2564,31 @@ api = "0.2"
})
})

when("MacAddress option", func() {
it("passes the parsed value through", func() {
h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{
Image: "some/app",
Builder: defaultBuilderName,
ContainerConfig: ContainerConfig{
MacAddress: "01:23:45:67:89:ab",
},
}))
h.AssertEq(t, fakeLifecycle.Opts.MacAddress.String(), "01:23:45:67:89:ab")
})

it("returns an error for invalid values", func() {
err := subject.Build(context.TODO(), BuildOptions{
Image: "some/app",
Builder: defaultBuilderName,
ContainerConfig: ContainerConfig{
MacAddress: "invalid-mac",
},
})

h.AssertError(t, err, `invalid MAC address "invalid-mac"`)
})
})

when("Lifecycle option", func() {
when("Platform API", func() {
for _, supportedPlatformAPI := range []string{"0.3", "0.4"} {
Expand Down
Loading