Skip to content

Enable setting DNS options through global nerdctl config #4378

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion cmd/nerdctl/container/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ func createAction(cmd *cobra.Command, args []string) error {
}
defer cancel()

netFlags, err := loadNetworkFlags(cmd)
netFlags, err := loadNetworkFlags(cmd, createOpt.GOptions)
if err != nil {
return fmt.Errorf("failed to load networking flags: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/nerdctl/container/container_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ func runAction(cmd *cobra.Command, args []string) error {
return errors.New("flags -d and -a cannot be specified together")
}

netFlags, err := loadNetworkFlags(cmd)
netFlags, err := loadNetworkFlags(cmd, createOpt.GOptions)
if err != nil {
return fmt.Errorf("failed to load networking flags: %w", err)
}
Expand Down
57 changes: 41 additions & 16 deletions cmd/nerdctl/container/container_run_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"github.com/containerd/nerdctl/v2/pkg/strutil"
)

func loadNetworkFlags(cmd *cobra.Command) (types.NetworkOptions, error) {
func loadNetworkFlags(cmd *cobra.Command, globalOpts types.GlobalCommandOptions) (types.NetworkOptions, error) {
netOpts := types.NetworkOptions{}

// --net/--network=<net name> ...
Expand Down Expand Up @@ -101,33 +101,58 @@ func loadNetworkFlags(cmd *cobra.Command) (types.NetworkOptions, error) {
netOpts.Domainname = domainname

// --dns=<DNS host> ...
dnsSlice, err := cmd.Flags().GetStringSlice("dns")
if err != nil {
return netOpts, err
// Use command flags if set, otherwise use global config is set
var dnsSlice []string
if cmd.Flags().Changed("dns") {
var err error
dnsSlice, err = cmd.Flags().GetStringSlice("dns")
if err != nil {
return netOpts, err
}
} else {
dnsSlice = globalOpts.DNS
}
netOpts.DNSServers = strutil.DedupeStrSlice(dnsSlice)

// --dns-search=<domain name> ...
dnsSearchSlice, err := cmd.Flags().GetStringSlice("dns-search")
if err != nil {
return netOpts, err
// Use command flags if set, otherwise use global config is set
var dnsSearchSlice []string
if cmd.Flags().Changed("dns-search") {
var err error
dnsSearchSlice, err = cmd.Flags().GetStringSlice("dns-search")
if err != nil {
return netOpts, err
}
} else {
dnsSearchSlice = globalOpts.DNSSearch
}
netOpts.DNSSearchDomains = strutil.DedupeStrSlice(dnsSearchSlice)

// --dns-opt/--dns-option=<resolv.conf line> ...
// Use command flags if set, otherwise use global config if set
dnsOptions := []string{}

dnsOptFlags, err := cmd.Flags().GetStringSlice("dns-opt")
if err != nil {
return netOpts, err
}
dnsOptions = append(dnsOptions, dnsOptFlags...)
// Check if either dns-opt or dns-option flags were set
dnsOptChanged := cmd.Flags().Changed("dns-opt")
dnsOptionChanged := cmd.Flags().Changed("dns-option")

dnsOptionFlags, err := cmd.Flags().GetStringSlice("dns-option")
if err != nil {
return netOpts, err
if dnsOptChanged || dnsOptionChanged {
// Use command flags
dnsOptFlags, err := cmd.Flags().GetStringSlice("dns-opt")
if err != nil {
return netOpts, err
}
dnsOptions = append(dnsOptions, dnsOptFlags...)

dnsOptionFlags, err := cmd.Flags().GetStringSlice("dns-option")
if err != nil {
return netOpts, err
}
dnsOptions = append(dnsOptions, dnsOptionFlags...)
} else {
// Use global config defaults
dnsOptions = append(dnsOptions, globalOpts.DNSOpts...)
}
dnsOptions = append(dnsOptions, dnsOptionFlags...)

netOpts.DNSResolvConfOptions = strutil.DedupeStrSlice(dnsOptions)

Expand Down
84 changes: 84 additions & 0 deletions cmd/nerdctl/container/container_run_network_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -996,3 +996,87 @@ func TestHostNetworkDnsConfigs(t *testing.T) {
}
testCase.Run(t)
}

func TestDNSWithGlobalConfig(t *testing.T) {
var configContent test.ConfigValue = `debug = false
debug_full = false
dns = ["10.10.10.10", "20.20.20.20"]
dns_opts = ["ndots:2", "timeout:5"]
dns_search = ["example.com", "test.local"]`

nerdtest.Setup()

testCase := &test.Case{
Config: test.WithConfig(nerdtest.NerdctlToml, configContent),
// NERDCTL_TOML not supported in Docker
Require: require.Not(nerdtest.Docker),
SubTests: []*test.Case{
{
Description: "Global DNS settings are used when command line options are not provided",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
nerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))
helpers.T().Log("NERDCTL_TOML file content:\n%s", nerdctlTomlContent)
cmd := helpers.Command("run", "--rm", testutil.CommonImage, "cat", "/etc/resolv.conf")
return cmd
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(
expect.Contains("nameserver 10.10.10.10"),
expect.Contains("nameserver 20.20.20.20"),
expect.Contains("search example.com test.local"),
expect.Contains("options ndots:2 timeout:5"),
)),
},
{
Description: "Command line DNS options override global config",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
nerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))
helpers.T().Log("NERDCTL_TOML file content:\n%s", nerdctlTomlContent)
cmd := helpers.Command("run", "--rm",
"--dns", "9.9.9.9",
"--dns-search", "override.com",
"--dns-opt", "ndots:3",
testutil.CommonImage, "cat", "/etc/resolv.conf")
return cmd
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(
expect.Contains("nameserver 9.9.9.9"),
expect.Contains("search override.com"),
expect.Contains("options ndots:3"),
)),
},
{
Description: "Global DNS settings should also apply when using host network",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
nerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))
helpers.T().Log("NERDCTL_TOML file content:\n%s", nerdctlTomlContent)
cmd := helpers.Command("run", "--rm", "--network", "host",
testutil.CommonImage, "cat", "/etc/resolv.conf")
return cmd
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(
expect.Contains("nameserver 10.10.10.10"),
expect.Contains("nameserver 20.20.20.20"),
expect.Contains("search example.com test.local"),
expect.Contains("options ndots:2 timeout:5"),
)),
},
{
Description: "Global DNS settings should also apply when using none network",
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
nerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))
helpers.T().Log("NERDCTL_TOML file content:\n%s", nerdctlTomlContent)
cmd := helpers.Command("run", "--rm", "--network", "none",
testutil.CommonImage, "cat", "/etc/resolv.conf")
return cmd
},
Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(
expect.Contains("nameserver 10.10.10.10"),
expect.Contains("nameserver 20.20.20.20"),
expect.Contains("search example.com test.local"),
expect.Contains("options ndots:2 timeout:5"),
)),
},
},
}
testCase.Run(t)
}
7 changes: 7 additions & 0 deletions cmd/nerdctl/helpers/cobra.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,10 @@ func AddPersistentBoolFlag(cmd *cobra.Command, name string, aliases, nonPersiste
}
}
}

// HiddenPersistentStringArrayFlag creates a persistent string slice flag and hides it.
// Used mainly to pass global config values to individual commands.
func HiddenPersistentStringArrayFlag(cmd *cobra.Command, name string, value []string, usage string) {
cmd.PersistentFlags().StringSlice(name, value, usage)
cmd.PersistentFlags().MarkHidden(name)
}
15 changes: 15 additions & 0 deletions cmd/nerdctl/helpers/flagutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,18 @@ func ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error)
if err != nil {
return types.GlobalCommandOptions{}, err
}
dns, err := cmd.Flags().GetStringSlice("global-dns")
if err != nil {
return types.GlobalCommandOptions{}, err
}
dnsOpts, err := cmd.Flags().GetStringSlice("global-dns-opts")
if err != nil {
return types.GlobalCommandOptions{}, err
}
dnsSearch, err := cmd.Flags().GetStringSlice("global-dns-search")
if err != nil {
return types.GlobalCommandOptions{}, err
}

// Point to dataRoot for filesystem-helpers implementing rollback / backups.
err = pkg.InitFS(dataRoot)
Expand All @@ -169,6 +181,9 @@ func ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error)
BridgeIP: bridgeIP,
KubeHideDupe: kubeHideDupe,
CDISpecDirs: cdiSpecDirs,
DNS: dns,
DNSOpts: dnsOpts,
DNSSearch: dnsSearch,
}, nil
}

Expand Down
3 changes: 3 additions & 0 deletions cmd/nerdctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ func initRootCmdFlags(rootCmd *cobra.Command, tomlPath string) (*pflag.FlagSet,
rootCmd.PersistentFlags().Bool("kube-hide-dupe", cfg.KubeHideDupe, "Deduplicate images for Kubernetes with namespace k8s.io")
rootCmd.PersistentFlags().StringSlice("cdi-spec-dirs", cfg.CDISpecDirs, "The directories to search for CDI spec files. Defaults to /etc/cdi,/var/run/cdi")
rootCmd.PersistentFlags().String("userns-remap", cfg.UsernsRemap, "Support idmapping for creating and running containers. This options is only supported on linux. If `host` is passed, no idmapping is done. if a user name is passed, it does idmapping based on the uidmap and gidmap ranges specified in /etc/subuid and /etc/subgid respectively")
helpers.HiddenPersistentStringArrayFlag(rootCmd, "global-dns", cfg.DNS, "Global DNS servers for containers")
helpers.HiddenPersistentStringArrayFlag(rootCmd, "global-dns-opts", cfg.DNSOpts, "Global DNS options for containers")
helpers.HiddenPersistentStringArrayFlag(rootCmd, "global-dns-search", cfg.DNSSearch, "Global DNS search domains for containers")
return aliasToBeInherited, nil
}

Expand Down
9 changes: 7 additions & 2 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ cgroup_manager = "cgroupfs"
hosts_dir = ["/etc/containerd/certs.d", "/etc/docker/certs.d"]
experimental = true
userns_remap = ""
dns = ["8.8.8.8", "1.1.1.1"]
dns_opts = ["ndots:1", "timeout:2"]
dns_search = ["example.com", "example.org"]
```

## Properties

| TOML property | CLI flag | Env var | Description | Availability \*1 |
| TOML property | CLI flag | Env var | Description | Availability |
|---------------------|------------------------------------|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|
| `debug` | `--debug` | | Debug mode | Since 0.16.0 |
| `debug_full` | `--debug-full` | | Debug mode (with full output) | Since 0.16.0 |
Expand All @@ -50,14 +53,16 @@ userns_remap = ""
| `kube_hide_dupe` | `--kube-hide-dupe` | | Deduplicate images for Kubernetes with namespace k8s.io, no more redundant <none> ones are displayed | Since 2.0.3 |
| `cdi_spec_dirs` | `--cdi-spec-dirs` | | The folders to use when searching for CDI ([container-device-interface](https://github.com/cncf-tags/container-device-interface)) specifications. | Since 2.1.0 |
| `userns_remap` | `--userns-remap` | | Support idmapping of containers. This options is only supported on rootful linux. If `host` is passed, no idmapping is done. if a user name is passed, it does idmapping based on the uidmap and gidmap ranges specified in /etc/subuid and /etc/subgid respectively. | Since 2.1.0 |
| `dns` | | | Set global DNS servers for containers | Since 2.1.3 |
| `dns_opts` | | | Set global DNS options for containers | Since 2.1.3 |
| `dns_search` | | | Set global DNS search domains for containers | Since 2.1.3 |

The properties are parsed in the following precedence:
1. CLI flag
2. Env var
3. TOML property
4. Built-in default value (Run `nerdctl --help` to see the default values)

\*1: Availability of the TOML properties

## See also
- [`registry.md`](registry.md)
Expand Down
11 changes: 8 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ type Config struct {
HostGatewayIP string `toml:"host_gateway_ip"`
BridgeIP string `toml:"bridge_ip, omitempty"`
KubeHideDupe bool `toml:"kube_hide_dupe"`
// CDISpecDirs is a list of directories in which CDI specifications can be found.
CDISpecDirs []string `toml:"cdi_spec_dirs,omitempty"`
UsernsRemap string `toml:"userns_remap, omitempty"`
CDISpecDirs []string `toml:"cdi_spec_dirs,omitempty"` // CDISpecDirs is a list of directories in which CDI specifications can be found.
UsernsRemap string `toml:"userns_remap, omitempty"`
DNS []string `toml:"dns,omitempty"`
DNSOpts []string `toml:"dns_opts,omitempty"`
DNSSearch []string `toml:"dns_search,omitempty"`
}

// New creates a default Config object statically,
Expand All @@ -66,5 +68,8 @@ func New() *Config {
KubeHideDupe: false,
CDISpecDirs: ncdefaults.CDISpecDirs(),
UsernsRemap: "",
DNS: []string{},
DNSOpts: []string{},
DNSSearch: []string{},
}
}
Loading