diff --git a/bake/bake.go b/bake/bake.go index ca4cb277d7ca..32ec402bad0b 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -4,6 +4,7 @@ import ( "context" "encoding" "encoding/json" + "fmt" "io" "maps" "os" @@ -726,6 +727,7 @@ type Target struct { Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional" cty:"ulimits"` Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"` Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"` + ExtraHosts map[string]*string `json:"extra-hosts,omitempty" hcl:"extra-hosts,optional" cty:"extra-hosts"` // IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md. // linked is a private field to mark a target used as a linked one @@ -764,6 +766,14 @@ func (t *Target) MarshalJSON() ([]byte, error) { } } + tgt.ExtraHosts = maps.Clone(t.ExtraHosts) + for k, v := range t.ExtraHosts { + if v != nil { + escaped := esc(*v) + tgt.ExtraHosts[k] = &escaped + } + } + return json.Marshal(tgt) } @@ -894,6 +904,15 @@ func (t *Target) Merge(t2 *Target) { if t2.Entitlements != nil { // merge t.Entitlements = append(t.Entitlements, t2.Entitlements...) } + for k, v := range t2.ExtraHosts { + if v == nil { + continue + } + if t.ExtraHosts == nil { + t.ExtraHosts = map[string]*string{} + } + t.ExtraHosts[k] = v + } t.Inherits = append(t.Inherits, t2.Inherits...) } @@ -1082,6 +1101,14 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon return errors.Errorf("invalid value %s for boolean key load", value) } t.Outputs = setLoadOverride(t.Outputs, load) + case "extra-hosts": + if len(keys) != 2 { + return errors.Errorf("invalid format for extra-hosts, expecting extra-hosts.=") + } + if t.ExtraHosts == nil { + t.ExtraHosts = map[string]*string{} + } + t.ExtraHosts[keys[1]] = &value default: return errors.Errorf("unknown key: %s", keys[0]) } @@ -1404,6 +1431,14 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { } } + var extraHosts []string + for k, v := range t.ExtraHosts { + if v == nil { + continue + } + extraHosts = append(extraHosts, fmt.Sprintf("%s=%s", k, *v)) + } + bo := &build.Options{ Inputs: bi, Tags: t.Tags, @@ -1415,6 +1450,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { NetworkMode: networkMode, Linked: t.linked, ShmSize: *shmSize, + ExtraHosts: extraHosts, } platforms, err := platformutil.Parse(t.Platforms) diff --git a/bake/bake_test.go b/bake/bake_test.go index 482b83adda63..ec3b7ec1a40d 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -27,6 +27,9 @@ target "webDEP" { no-cache = true shm-size = "128m" ulimits = ["nofile=1024:1024"] + extra-hosts = { + my_hostname = "8.8.8.8" + } } target "webapp" { @@ -64,6 +67,7 @@ target "webapp" { require.Equal(t, true, *m["webapp"].NoCache) require.Equal(t, "128m", *m["webapp"].ShmSize) require.Equal(t, []string{"nofile=1024:1024"}, m["webapp"].Ulimits) + require.Equal(t, map[string]*string{"my_hostname": ptrstr("8.8.8.8")}, m["webapp"].ExtraHosts) require.Nil(t, m["webapp"].Pull) require.Equal(t, 1, len(g)) diff --git a/bake/compose.go b/bake/compose.go index 1ca046d00454..b913d560d8ee 100644 --- a/bake/compose.go +++ b/bake/compose.go @@ -122,6 +122,16 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf } } + extraHosts := map[string]*string{} + if s.Build.ExtraHosts != nil { + for k, v := range s.Build.ExtraHosts { + for _, ip := range v { + vv := ip + extraHosts[k] = &vv + } + } + } + var ssh []*buildflags.SSH for _, bkey := range s.Build.SSH { sshkey := composeToBuildkitSSH(bkey) @@ -180,6 +190,7 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf Secrets: secrets, ShmSize: shmSize, Ulimits: ulimits, + ExtraHosts: extraHosts, } if err = t.composeExtTarget(s.Build.Extensions); err != nil { return nil, err diff --git a/bake/compose_test.go b/bake/compose_test.go index e19ff1387819..6e08fdc414df 100644 --- a/bake/compose_test.go +++ b/bake/compose_test.go @@ -32,6 +32,9 @@ services: - type=local,src=path/to/cache cache_to: - type=local,dest=path/to/cache + extra_hosts: + - "somehost:162.242.195.82" + - "myhostv6:::1" ssh: - key=/path/to/key - default @@ -76,6 +79,7 @@ secrets: require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"]) require.Equal(t, []string{"type=local,src=path/to/cache"}, stringify(c.Targets[1].CacheFrom)) require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[1].CacheTo)) + require.Equal(t, map[string]*string{"myhostv6": ptrstr("::1"), "somehost": ptrstr("162.242.195.82")}, c.Targets[1].ExtraHosts) require.Equal(t, "none", *c.Targets[1].NetworkMode) require.Equal(t, []string{"default", "key=/path/to/key"}, stringify(c.Targets[1].SSH)) require.Equal(t, []string{ diff --git a/docs/bake-reference.md b/docs/bake-reference.md index 5245f611dc38..22592ec373ba 100644 --- a/docs/bake-reference.md +++ b/docs/bake-reference.md @@ -227,6 +227,8 @@ The following table shows the complete list of attributes that you can assign to | [`description`](#targetdescription) | String | Description of a target | | [`dockerfile-inline`](#targetdockerfile-inline) | String | Inline Dockerfile string | | [`dockerfile`](#targetdockerfile) | String | Dockerfile location | +| [`entitlements`](#targetentitlements) | List | Permissions that the build process requires to run | +| [`extra-hosts`](#targetextra-hosts) | List | Customs host-to-IP mapping | | [`inherits`](#targetinherits) | List | Inherit attributes from other targets | | [`labels`](#targetlabels) | Map | Metadata for images | | [`matrix`](#targetmatrix) | Map | Define a set of variables that forks a target into multiple targets. | @@ -583,6 +585,20 @@ target "integration-tests" { Entitlements are enabled with a two-step process. First, a target must declare the entitlements it requires. Secondly, when invoking the `bake` command, the user must grant the entitlements by passing the `--allow` flag or confirming the entitlements when prompted in an interactive terminal. This is to ensure that the user is aware of the possibly insecure permissions they are granting to the build process. +### `target.extra-hosts` + +Use the `extra-hosts` attribute to define customs host-to-IP mapping for the +target. This has the same effect as passing a [`--add-host`][add-host] flag to +the build command. + +```hcl +target "default" { + extra-hosts = { + my_hostname = "8.8.8.8" + } +} +``` + ### `target.inherits` A target can inherit attributes from other targets. @@ -1422,6 +1438,7 @@ target "webapp-dev" { +[add-host]: https://docs.docker.com/reference/cli/docker/buildx/build/#add-host [attestations]: https://docs.docker.com/build/attestations/ [bake_stdlib]: https://github.com/docker/buildx/blob/master/bake/hclparser/stdlib.go [build-arg]: https://docs.docker.com/reference/cli/docker/image/build/#build-arg diff --git a/docs/reference/buildx_bake.md b/docs/reference/buildx_bake.md index 189866b2bc1f..ab896703a808 100644 --- a/docs/reference/buildx_bake.md +++ b/docs/reference/buildx_bake.md @@ -366,6 +366,7 @@ You can override the following fields: * `context` * `dockerfile` * `entitlements` +* `extra-hosts` * `labels` * `load` * `no-cache` diff --git a/tests/bake.go b/tests/bake.go index ca9902a47bef..71df5e861637 100644 --- a/tests/bake.go +++ b/tests/bake.go @@ -80,6 +80,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){ testBakeCallMetadata, testBakeMultiPlatform, testBakeCheckCallOutput, + testBakeExtraHosts, } func testBakePrint(t *testing.T, sb integration.Sandbox) { @@ -2162,6 +2163,31 @@ target "third" { }) } +func testBakeExtraHosts(t *testing.T, sb integration.Sandbox) { + dockerfile := []byte(` +FROM busybox +RUN cat /etc/hosts | grep myhost | grep 1.2.3.4 + `) + bakefile := []byte(` +target "default" { + extra-hosts = { + myhost = "1.2.3.4" + } +} +`) + dir := tmpdir( + t, + fstest.CreateFile("docker-bake.hcl", bakefile, 0600), + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + + out, err := bakeCmd( + sb, + withDir(dir), + ) + require.NoError(t, err, out) +} + func writeTempPrivateKey(fp string) error { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil {