From 8a21a9cee83392f61b5431e8c57b755a168864c9 Mon Sep 17 00:00:00 2001 From: Adphi Date: Wed, 21 Jan 2026 17:42:48 +0100 Subject: [PATCH] feat: add `--add-host` flag to customize host-to-IP mapping in /etc/hosts Signed-off-by: Adphi --- README.md | 2 ++ builder.go | 10 ++++++++-- cmd/d2vm/build.go | 1 + cmd/d2vm/convert.go | 1 + cmd/d2vm/flags.go | 23 ++++++++++++++++++++++- convert.go | 2 +- convert_options.go | 7 +++++++ docs/content/reference/d2vm_build.md | 1 + docs/content/reference/d2vm_convert.md | 1 + 9 files changed, 44 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 85cf9d8..cfdc66f 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ Usage: Flags: --append-to-cmdline string Extra kernel cmdline arguments to append to the generated one + --add-host strings Add a custom host-to-IP mapping (host:ip) to the /etc/hosts file in the generated image --boot-fs string Filesystem to use for the boot partition, ext4 or fat32 --boot-size uint Size of the boot partition in MB (default 100) --bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi, defaults to syslinux on amd64 and grub-efi on arm64 @@ -327,6 +328,7 @@ Usage: Flags: --append-to-cmdline string Extra kernel cmdline arguments to append to the generated one + --add-host strings Add a custom host-to-IP mapping (host:ip) to the /etc/hosts file in the generated image --boot-fs string Filesystem to use for the boot partition, ext4 or fat32 --boot-size uint Size of the boot partition in MB (default 100) --bootloader string Bootloader to use: syslinux, grub, grub-bios, grub-efi, defaults to syslinux on amd64 and grub-efi on arm64 diff --git a/builder.go b/builder.go index df1a64b..3ada1dc 100644 --- a/builder.go +++ b/builder.go @@ -87,9 +87,10 @@ type builder struct { hostname string dns []string dnsSearch []string + hosts string } -func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64, osRelease OSRelease, format string, cmdLineExtra string, splitBoot bool, bootFS BootFS, bootSize uint64, luksPassword string, bootLoader string, platform, hostname string, dns, dnsSearch []string) (Builder, error) { +func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64, osRelease OSRelease, format string, cmdLineExtra string, splitBoot bool, bootFS BootFS, bootSize uint64, luksPassword string, bootLoader string, platform, hostname string, dns, dnsSearch []string, extraHosts map[string]string) (Builder, error) { var arch string switch platform { case "linux/amd64": @@ -189,6 +190,10 @@ func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64, if len(dns) == 0 { dns = []string{"8.8.8.8"} } + hosts := hosts + for k, v := range extraHosts { + hosts += fmt.Sprintf("%s %s\n", v, k) + } b := &builder{ osRelease: osRelease, config: config, @@ -208,6 +213,7 @@ func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64, hostname: hostname, dns: dns, dnsSearch: dnsSearch, + hosts: hosts, } if err := b.checkDependencies(); err != nil { return nil, err @@ -428,7 +434,7 @@ func (b *builder) setupRootFS(ctx context.Context) (err error) { if err := b.chWriteFile("/etc/hostname", b.hostname+"\n", perm); err != nil { return err } - if err := b.chWriteFileIfNotExist("/etc/hosts", hosts, perm); err != nil { + if err := b.chWriteFile("/etc/hosts", b.hosts, perm); err != nil { return err } // TODO(adphi): is it the righ fix ? diff --git a/cmd/d2vm/build.go b/cmd/d2vm/build.go index af9f6d2..9ac9443 100644 --- a/cmd/d2vm/build.go +++ b/cmd/d2vm/build.go @@ -113,6 +113,7 @@ var ( d2vm.WithHostname(hostname), d2vm.WithDNS(dns), d2vm.WithDNSSearch(dnsSearch), + d2vm.WithExtraHosts(extraHosts), ); err != nil { return err } diff --git a/cmd/d2vm/convert.go b/cmd/d2vm/convert.go index dd8b543..6027302 100644 --- a/cmd/d2vm/convert.go +++ b/cmd/d2vm/convert.go @@ -94,6 +94,7 @@ var ( d2vm.WithHostname(hostname), d2vm.WithDNS(dns), d2vm.WithDNSSearch(dnsSearch), + d2vm.WithExtraHosts(extraHosts), ); err != nil { return err } diff --git a/cmd/d2vm/flags.go b/cmd/d2vm/flags.go index ea7edb6..db60a13 100644 --- a/cmd/d2vm/flags.go +++ b/cmd/d2vm/flags.go @@ -48,9 +48,12 @@ var ( hostname string dns []string dnsSearch []string + hosts []string + + extraHosts map[string]string ) -func validateFlags() error { +func validateFlags() (err error) { switch platform { case "linux/amd64": if bootloader == "" { @@ -98,6 +101,10 @@ func validateFlags() error { return fmt.Errorf("%s already exists", output) } } + extraHosts, err = validateHosts(hosts...) + if err != nil { + return fmt.Errorf("invalid --add-host value: %w", err) + } return nil } @@ -123,5 +130,19 @@ func buildFlags() *pflag.FlagSet { flags.StringVar(&hostname, "hostname", "localhost", "Hostname to set in the generated image") flags.StringSliceVar(&dns, "dns", []string{}, "DNS servers to set in the generated image") flags.StringSliceVar(&dnsSearch, "dns-search", []string{}, "DNS search domains to set in the generated image") + flags.StringSliceVar(&hosts, "add-host", []string{}, "Add a custom host-to-IP mapping (host:ip) to the /etc/hosts file in the generated image") return flags } + +func validateHosts(vals ...string) (map[string]string, error) { + out := make(map[string]string) + for _, val := range vals { + // allow for IPv6 addresses in extra hosts by only splitting on first ":" + k, v, ok := strings.Cut(val, ":") + if !ok || k == "" { + return nil, fmt.Errorf("bad format for add-host: %q", val) + } + out[k] = v + } + return out, nil +} diff --git a/convert.go b/convert.go index 0d03551..7c4a19a 100644 --- a/convert.go +++ b/convert.go @@ -88,7 +88,7 @@ func Convert(ctx context.Context, img string, opts ...ConvertOption) error { if format == "" { format = "raw" } - b, err := NewBuilder(ctx, tmpPath, imgUUID, "", o.size, r, format, o.cmdLineExtra, o.splitBoot, o.bootFS, o.bootSize, o.luksPassword, o.bootLoader, o.platform, o.hostname, o.dns, o.dnsSearch) + b, err := NewBuilder(ctx, tmpPath, imgUUID, "", o.size, r, format, o.cmdLineExtra, o.splitBoot, o.bootFS, o.bootSize, o.luksPassword, o.bootLoader, o.platform, o.hostname, o.dns, o.dnsSearch, o.hosts) if err != nil { return err } diff --git a/convert_options.go b/convert_options.go index afdc9ba..5c04fa9 100644 --- a/convert_options.go +++ b/convert_options.go @@ -38,6 +38,7 @@ type convertOptions struct { hostname string dns []string dnsSearch []string + hosts map[string]string } func (o *convertOptions) hasGrubBIOS() bool { @@ -149,3 +150,9 @@ func WithDNSSearch(dnsSearch []string) ConvertOption { o.dnsSearch = dnsSearch } } + +func WithExtraHosts(hosts map[string]string) ConvertOption { + return func(o *convertOptions) { + o.hosts = hosts + } +} diff --git a/docs/content/reference/d2vm_build.md b/docs/content/reference/d2vm_build.md index 007214d..bc3f1be 100644 --- a/docs/content/reference/d2vm_build.md +++ b/docs/content/reference/d2vm_build.md @@ -9,6 +9,7 @@ d2vm build [context directory] [flags] ### Options ``` + --add-host strings Add a custom host-to-IP mapping (host:ip) to the /etc/hosts file in the generated image --append-to-cmdline string Extra kernel cmdline arguments to append to the generated one --boot-fs string Filesystem to use for the boot partition, ext4 or fat32 --boot-size uint Size of the boot partition in MB (default 100) diff --git a/docs/content/reference/d2vm_convert.md b/docs/content/reference/d2vm_convert.md index fcd260e..cd97d29 100644 --- a/docs/content/reference/d2vm_convert.md +++ b/docs/content/reference/d2vm_convert.md @@ -9,6 +9,7 @@ d2vm convert [docker image] [flags] ### Options ``` + --add-host strings Add a custom host-to-IP mapping (host:ip) to the /etc/hosts file in the generated image --append-to-cmdline string Extra kernel cmdline arguments to append to the generated one --boot-fs string Filesystem to use for the boot partition, ext4 or fat32 --boot-size uint Size of the boot partition in MB (default 100)