diff --git a/frontend/dockerfile/builder/build.go b/frontend/dockerfile/builder/build.go index ab7cfe3b..de06462d 100644 --- a/frontend/dockerfile/builder/build.go +++ b/frontend/dockerfile/builder/build.go @@ -4,8 +4,10 @@ import ( "archive/tar" "bytes" "context" + "encoding/csv" "encoding/json" "fmt" + "net" "regexp" "strconv" "strings" @@ -36,6 +38,7 @@ const ( keyTargetPlatform = "platform" keyMultiPlatform = "multi-platform" keyImageResolveMode = "image-resolve-mode" + keyGlobalAddHosts = "add-hosts" ) var httpPrefix = regexp.MustCompile("^https?://") @@ -64,6 +67,11 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) { return nil, err } + extraHosts, err := parseExtraHosts(opts[keyGlobalAddHosts]) + if err != nil { + return nil, errors.Wrap(err, "failed to parse additional hosts") + } + filename := opts[keyFilename] if filename == "" { filename = defaultDockerfileName @@ -250,6 +258,7 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) { BuildPlatforms: buildPlatforms, ImageResolveMode: resolveMode, PrefixPlatform: exportMap, + ExtraHosts: extraHosts, }) if err != nil { @@ -413,3 +422,29 @@ func parseResolveMode(v string) (llb.ResolveMode, error) { return 0, errors.Errorf("invalid image-resolve-mode: %s", v) } } + +func parseExtraHosts(v string) ([]llb.HostIP, error) { + if v == "" { + return nil, nil + } + out := make([]llb.HostIP, 0) + csvReader := csv.NewReader(strings.NewReader(v)) + fields, err := csvReader.Read() + if err != nil { + return nil, err + } + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + if len(parts) != 2 { + return nil, errors.Errorf("invalid key-value pair %s", field) + } + key := strings.ToLower(parts[0]) + val := strings.ToLower(parts[1]) + ip := net.ParseIP(val) + if ip == nil { + return nil, errors.Errorf("failed to parse IP %s", val) + } + out = append(out, llb.HostIP{Host: key, IP: ip}) + } + return out, nil +} diff --git a/frontend/dockerfile/dockerfile2llb/convert.go b/frontend/dockerfile/dockerfile2llb/convert.go index 484b3e42..75be1f33 100644 --- a/frontend/dockerfile/dockerfile2llb/convert.go +++ b/frontend/dockerfile/dockerfile2llb/convert.go @@ -52,6 +52,7 @@ type ConvertOpt struct { TargetPlatform *specs.Platform BuildPlatforms []specs.Platform PrefixPlatform bool + ExtraHosts []llb.HostIP } func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, error) { @@ -294,6 +295,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, cacheIDNamespace: opt.CacheIDNamespace, buildPlatforms: platformOpt.buildPlatforms, targetPlatform: platformOpt.targetPlatform, + extraHosts: opt.ExtraHosts, } if err = dispatchOnBuild(d, d.image.Config.OnBuild, opt); err != nil { @@ -397,6 +399,7 @@ type dispatchOpt struct { cacheIDNamespace string targetPlatform specs.Platform buildPlatforms []specs.Platform + extraHosts []llb.HostIP } func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error { @@ -584,6 +587,9 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE } opt = append(opt, runMounts...) opt = append(opt, llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(dopt.shlex, c.String(), d.state.Run(opt...).Env())), d.prefixPlatform, d.state.GetPlatform()))) + for _, h := range dopt.extraHosts { + opt = append(opt, llb.AddExtraHost(h.Host, h.IP)) + } d.state = d.state.Run(opt...).Root() return commitToHistory(&d.image, "RUN "+runCommandString(args, d.buildArgs), true, &d.state) }