github.com/containerd/nerdctl@v1.7.7/pkg/containerutil/container_network_manager.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package containerutil
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"reflect"
    27  	"runtime"
    28  	"strings"
    29  
    30  	"github.com/containerd/containerd"
    31  	"github.com/containerd/containerd/containers"
    32  	"github.com/containerd/containerd/oci"
    33  	"github.com/containerd/nerdctl/pkg/api/types"
    34  	"github.com/containerd/nerdctl/pkg/clientutil"
    35  	"github.com/containerd/nerdctl/pkg/dnsutil/hostsstore"
    36  	"github.com/containerd/nerdctl/pkg/idutil/containerwalker"
    37  	"github.com/containerd/nerdctl/pkg/labels"
    38  	"github.com/containerd/nerdctl/pkg/mountutil"
    39  	"github.com/containerd/nerdctl/pkg/netutil"
    40  	"github.com/containerd/nerdctl/pkg/netutil/nettype"
    41  	"github.com/containerd/nerdctl/pkg/strutil"
    42  	"github.com/opencontainers/runtime-spec/specs-go"
    43  )
    44  
    45  const (
    46  	UtsNamespaceHost = "host"
    47  )
    48  
    49  func withCustomResolvConf(src string) func(context.Context, oci.Client, *containers.Container, *oci.Spec) error {
    50  	return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
    51  		s.Mounts = append(s.Mounts, specs.Mount{
    52  			Destination: "/etc/resolv.conf",
    53  			Type:        "bind",
    54  			Source:      src,
    55  			Options:     []string{"bind", mountutil.DefaultPropagationMode}, // writable
    56  		})
    57  		return nil
    58  	}
    59  }
    60  
    61  func withCustomEtcHostname(src string) func(context.Context, oci.Client, *containers.Container, *oci.Spec) error {
    62  	return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
    63  		s.Mounts = append(s.Mounts, specs.Mount{
    64  			Destination: "/etc/hostname",
    65  			Type:        "bind",
    66  			Source:      src,
    67  			Options:     []string{"bind", mountutil.DefaultPropagationMode}, // writable
    68  		})
    69  		return nil
    70  	}
    71  }
    72  
    73  func withCustomHosts(src string) func(context.Context, oci.Client, *containers.Container, *oci.Spec) error {
    74  	return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
    75  		s.Mounts = append(s.Mounts, specs.Mount{
    76  			Destination: "/etc/hosts",
    77  			Type:        "bind",
    78  			Source:      src,
    79  			Options:     []string{"bind", mountutil.DefaultPropagationMode}, // writable
    80  		})
    81  		return nil
    82  	}
    83  }
    84  
    85  // NetworkOptionsManager types.NetworkOptionsManager is an interface for reading/setting networking
    86  // options for containers based on the provided command flags.
    87  type NetworkOptionsManager interface {
    88  	// NetworkOptions Returns a copy of the internal types.NetworkOptions.
    89  	NetworkOptions() types.NetworkOptions
    90  
    91  	// VerifyNetworkOptions Verifies that the internal network settings are correct.
    92  	VerifyNetworkOptions(context.Context) error
    93  
    94  	// SetupNetworking Performs setup actions required for the container with the given ID.
    95  	SetupNetworking(context.Context, string) error
    96  
    97  	// CleanupNetworking Performs any required cleanup actions for the given container.
    98  	// Should only be called to revert any setup steps performed in SetupNetworking.
    99  	CleanupNetworking(context.Context, containerd.Container) error
   100  
   101  	// InternalNetworkingOptionLabels Returns the set of NetworkingOptions which should be set as labels on the container.
   102  	//
   103  	// These options can potentially differ from the actual networking options
   104  	// that the NetworkOptionsManager was initially instantiated with.
   105  	// E.g: in container networking mode, the label will be normalized to an ID:
   106  	// `--net=container:myContainer` => `--net=container:<ID of myContainer>`.
   107  	InternalNetworkingOptionLabels(context.Context) (types.NetworkOptions, error)
   108  
   109  	// ContainerNetworkingOpts Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent
   110  	// the network specs which need to be applied to the container with the given ID.
   111  	ContainerNetworkingOpts(context.Context, string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error)
   112  }
   113  
   114  // NewNetworkingOptionsManager Returns a types.NetworkOptionsManager based on the provided command's flags.
   115  func NewNetworkingOptionsManager(globalOptions types.GlobalCommandOptions, netOpts types.NetworkOptions, client *containerd.Client) (NetworkOptionsManager, error) {
   116  	netType, err := nettype.Detect(netOpts.NetworkSlice)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	var manager NetworkOptionsManager
   122  	switch netType {
   123  	case nettype.None:
   124  		manager = &noneNetworkManager{globalOptions, netOpts, client}
   125  	case nettype.Host:
   126  		manager = &hostNetworkManager{globalOptions, netOpts, client}
   127  	case nettype.Container:
   128  		manager = &containerNetworkManager{globalOptions, netOpts, client}
   129  	case nettype.CNI:
   130  		manager = &cniNetworkManager{globalOptions, netOpts, client, cniNetworkManagerPlatform{}}
   131  	default:
   132  		return nil, fmt.Errorf("unexpected container networking type: %q", netType)
   133  	}
   134  
   135  	return manager, nil
   136  }
   137  
   138  // No-op types.NetworkOptionsManager for network-less containers.
   139  type noneNetworkManager struct {
   140  	globalOptions types.GlobalCommandOptions
   141  	netOpts       types.NetworkOptions
   142  	client        *containerd.Client
   143  }
   144  
   145  // NetworkOptions Returns a copy of the internal types.NetworkOptions.
   146  func (m *noneNetworkManager) NetworkOptions() types.NetworkOptions {
   147  	return m.netOpts
   148  }
   149  
   150  // VerifyNetworkOptions Verifies that the internal network settings are correct.
   151  func (m *noneNetworkManager) VerifyNetworkOptions(_ context.Context) error {
   152  	// No options to verify if no network settings are provided.
   153  	return nil
   154  }
   155  
   156  // SetupNetworking Performs setup actions required for the container with the given ID.
   157  func (m *noneNetworkManager) SetupNetworking(_ context.Context, _ string) error {
   158  	return nil
   159  }
   160  
   161  // CleanupNetworking Performs any required cleanup actions for the given container.
   162  // Should only be called to revert any setup steps performed in SetupNetworking.
   163  func (m *noneNetworkManager) CleanupNetworking(_ context.Context, _ containerd.Container) error {
   164  	return nil
   165  }
   166  
   167  // InternalNetworkingOptionLabels Returns the set of NetworkingOptions which should be set as labels on the container.
   168  func (m *noneNetworkManager) InternalNetworkingOptionLabels(_ context.Context) (types.NetworkOptions, error) {
   169  	return m.netOpts, nil
   170  }
   171  
   172  // ContainerNetworkingOpts Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent
   173  // the network specs which need to be applied to the container with the given ID.
   174  func (m *noneNetworkManager) ContainerNetworkingOpts(_ context.Context, _ string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {
   175  	// No options to return if no network settings are provided.
   176  	return []oci.SpecOpts{}, []containerd.NewContainerOpts{}, nil
   177  }
   178  
   179  // types.NetworkOptionsManager implementation for container networking settings.
   180  type containerNetworkManager struct {
   181  	globalOptions types.GlobalCommandOptions
   182  	netOpts       types.NetworkOptions
   183  	client        *containerd.Client
   184  }
   185  
   186  // NetworkOptions Returns a copy of the internal types.NetworkOptions.
   187  func (m *containerNetworkManager) NetworkOptions() types.NetworkOptions {
   188  	return m.netOpts
   189  }
   190  
   191  // VerifyNetworkOptions Verifies that the internal network settings are correct.
   192  func (m *containerNetworkManager) VerifyNetworkOptions(_ context.Context) error {
   193  	// TODO: check host OS, not client-side OS.
   194  	if runtime.GOOS != "linux" {
   195  		return errors.New("container networking mode is currently only supported on Linux")
   196  	}
   197  
   198  	if len(m.netOpts.NetworkSlice) > 1 {
   199  		return errors.New("conflicting options: only one network specification is allowed when using '--network=container:<container>'")
   200  	}
   201  
   202  	nonZeroParams := nonZeroMapValues(map[string]interface{}{
   203  		"--hostname":    m.netOpts.Hostname,
   204  		"--mac-address": m.netOpts.MACAddress,
   205  		// NOTE: an empty slice still counts as a non-zero value so we check its length:
   206  		"-p/--publish": len(m.netOpts.PortMappings) != 0,
   207  		"--dns":        len(m.netOpts.DNSServers) != 0,
   208  		"--add-host":   len(m.netOpts.AddHost) != 0,
   209  	})
   210  
   211  	if len(nonZeroParams) != 0 {
   212  		return fmt.Errorf("conflicting options: the following arguments are not supported when using `--network=container:<container>`: %s", nonZeroParams)
   213  	}
   214  
   215  	return nil
   216  }
   217  
   218  // Returns the relevant paths of the `hostname`, `resolv.conf`, and `hosts` files
   219  // in the datastore of the container with the given ID.
   220  func (m *containerNetworkManager) getContainerNetworkFilePaths(containerID string) (string, string, string, error) {
   221  	dataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address)
   222  	if err != nil {
   223  		return "", "", "", err
   224  	}
   225  	conStateDir, err := ContainerStateDirPath(m.globalOptions.Namespace, dataStore, containerID)
   226  	if err != nil {
   227  		return "", "", "", err
   228  	}
   229  
   230  	hostnamePath := filepath.Join(conStateDir, "hostname")
   231  	resolvConfPath := filepath.Join(conStateDir, "resolv.conf")
   232  	etcHostsPath := hostsstore.HostsPath(dataStore, m.globalOptions.Namespace, containerID)
   233  
   234  	return hostnamePath, resolvConfPath, etcHostsPath, nil
   235  }
   236  
   237  // SetupNetworking Performs setup actions required for the container with the given ID.
   238  func (m *containerNetworkManager) SetupNetworking(_ context.Context, _ string) error {
   239  	// NOTE: container networking simply reuses network config files from the
   240  	// bridged container so there are no setup/teardown steps required.
   241  	return nil
   242  }
   243  
   244  // CleanupNetworking Performs any required cleanup actions for the given container.
   245  // Should only be called to revert any setup steps performed in SetupNetworking.
   246  func (m *containerNetworkManager) CleanupNetworking(_ context.Context, _ containerd.Container) error {
   247  	// NOTE: container networking simply reuses network config files from the
   248  	// bridged container so there are no setup/teardown steps required.
   249  	return nil
   250  }
   251  
   252  // Searches for and returns the networking container for the given network argument.
   253  func (m *containerNetworkManager) getNetworkingContainerForArgument(ctx context.Context, containerNetArg string, client *containerd.Client) (containerd.Container, error) {
   254  	netItems := strings.Split(containerNetArg, ":")
   255  	if len(netItems) < 2 {
   256  		return nil, fmt.Errorf("container networking argument format must be 'container:<id|name>', got: %q", containerNetArg)
   257  	}
   258  	containerName := netItems[1]
   259  
   260  	var foundContainer containerd.Container
   261  	walker := &containerwalker.ContainerWalker{
   262  		Client: client,
   263  		OnFound: func(ctx context.Context, found containerwalker.Found) error {
   264  			if found.MatchCount > 1 {
   265  				return fmt.Errorf("container networking: multiple containers found with prefix: %s", containerName)
   266  			}
   267  			foundContainer = found.Container
   268  			return nil
   269  		},
   270  	}
   271  	n, err := walker.Walk(ctx, containerName)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	if n == 0 {
   276  		return nil, fmt.Errorf("container networking: could not find container: %s", containerName)
   277  	}
   278  
   279  	return foundContainer, nil
   280  }
   281  
   282  // InternalNetworkingOptionLabels Returns the set of NetworkingOptions which should be set as labels on the container.
   283  func (m *containerNetworkManager) InternalNetworkingOptionLabels(ctx context.Context) (types.NetworkOptions, error) {
   284  	opts := m.netOpts
   285  	if m.netOpts.NetworkSlice == nil || len(m.netOpts.NetworkSlice) != 1 {
   286  		return opts, fmt.Errorf("conflicting options: exactly one network specification is allowed when using '--network=container:<container>'")
   287  	}
   288  
   289  	container, err := m.getNetworkingContainerForArgument(ctx, m.netOpts.NetworkSlice[0], m.client)
   290  	if err != nil {
   291  		return opts, err
   292  	}
   293  	containerID := container.ID()
   294  	opts.NetworkSlice = []string{fmt.Sprintf("container:%s", containerID)}
   295  	return opts, nil
   296  }
   297  
   298  // ContainerNetworkingOpts Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent
   299  // the network specs which need to be applied to the container with the given ID.
   300  func (m *containerNetworkManager) ContainerNetworkingOpts(ctx context.Context, _ string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {
   301  	opts := []oci.SpecOpts{}
   302  	cOpts := []containerd.NewContainerOpts{}
   303  
   304  	container, err := m.getNetworkingContainerForArgument(ctx, m.netOpts.NetworkSlice[0], m.client)
   305  	if err != nil {
   306  		return nil, nil, err
   307  	}
   308  	containerID := container.ID()
   309  
   310  	s, err := container.Spec(ctx)
   311  	if err != nil {
   312  		return nil, nil, err
   313  	}
   314  	hostname := s.Hostname
   315  
   316  	netNSPath, err := ContainerNetNSPath(ctx, container)
   317  	if err != nil {
   318  		return nil, nil, err
   319  	}
   320  
   321  	hostnamePath, resolvConfPath, etcHostsPath, err := m.getContainerNetworkFilePaths(containerID)
   322  	if err != nil {
   323  		return nil, nil, err
   324  	}
   325  
   326  	opts = append(opts,
   327  		oci.WithLinuxNamespace(specs.LinuxNamespace{
   328  			Type: specs.NetworkNamespace,
   329  			Path: netNSPath,
   330  		}),
   331  		withCustomResolvConf(resolvConfPath),
   332  		withCustomHosts(etcHostsPath),
   333  		oci.WithHostname(hostname),
   334  		withCustomEtcHostname(hostnamePath),
   335  	)
   336  
   337  	return opts, cOpts, nil
   338  }
   339  
   340  // types.NetworkOptionsManager implementation for host networking settings.
   341  type hostNetworkManager struct {
   342  	globalOptions types.GlobalCommandOptions
   343  	netOpts       types.NetworkOptions
   344  	client        *containerd.Client
   345  }
   346  
   347  // NetworkOptions Returns a copy of the internal types.NetworkOptions.
   348  func (m *hostNetworkManager) NetworkOptions() types.NetworkOptions {
   349  	return m.netOpts
   350  }
   351  
   352  // VerifyNetworkOptions Verifies that the internal network settings are correct.
   353  func (m *hostNetworkManager) VerifyNetworkOptions(_ context.Context) error {
   354  	// TODO: check host OS, not client-side OS.
   355  	if runtime.GOOS == "windows" {
   356  		return errors.New("cannot use host networking on Windows")
   357  	}
   358  
   359  	if m.netOpts.MACAddress != "" {
   360  		return errors.New("conflicting options: mac-address and the network mode")
   361  	}
   362  
   363  	return validateUtsSettings(m.netOpts)
   364  }
   365  
   366  // SetupNetworking Performs setup actions required for the container with the given ID.
   367  func (m *hostNetworkManager) SetupNetworking(_ context.Context, _ string) error {
   368  	// NOTE: there are no setup steps required for host networking.
   369  	return nil
   370  }
   371  
   372  // CleanupNetworking Performs any required cleanup actions for the given container.
   373  // Should only be called to revert any setup steps performed in SetupNetworking.
   374  func (m *hostNetworkManager) CleanupNetworking(_ context.Context, _ containerd.Container) error {
   375  	// NOTE: there are no setup steps required for host networking.
   376  	return nil
   377  }
   378  
   379  // InternalNetworkingOptionLabels Returns the set of NetworkingOptions which should be set as labels on the container.
   380  func (m *hostNetworkManager) InternalNetworkingOptionLabels(_ context.Context) (types.NetworkOptions, error) {
   381  	opts := m.netOpts
   382  	// Cannot have a MAC address in host networking mode.
   383  	opts.MACAddress = ""
   384  	return opts, nil
   385  }
   386  
   387  // withDedupMounts Returns the specOpts if the mountPath is not in existing mounts.
   388  // for https://github.com/containerd/nerdctl/issues/2685
   389  func withDedupMounts(mountPath string, defaultSpec oci.SpecOpts) oci.SpecOpts {
   390  	return func(ctx context.Context, client oci.Client, c *containers.Container, s *oci.Spec) error {
   391  		for _, m := range s.Mounts {
   392  			if m.Destination == mountPath {
   393  				return nil
   394  			}
   395  		}
   396  		return defaultSpec(ctx, client, c, s)
   397  	}
   398  }
   399  
   400  func copyFileContent(src string, dst string) error {
   401  	data, err := os.ReadFile(src)
   402  	if err != nil {
   403  		return err
   404  	}
   405  	err = os.WriteFile(dst, data, 0644)
   406  	if err != nil {
   407  		return err
   408  	}
   409  	return nil
   410  }
   411  
   412  // ContainerNetworkingOpts Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent
   413  // the network specs which need to be applied to the container with the given ID.
   414  func (m *hostNetworkManager) ContainerNetworkingOpts(_ context.Context, containerID string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {
   415  
   416  	cOpts := []containerd.NewContainerOpts{}
   417  
   418  	dataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address)
   419  	if err != nil {
   420  		return nil, nil, err
   421  	}
   422  
   423  	stateDir, err := ContainerStateDirPath(m.globalOptions.Namespace, dataStore, containerID)
   424  	if err != nil {
   425  		return nil, nil, err
   426  	}
   427  
   428  	resolvConfPath := filepath.Join(stateDir, "resolv.conf")
   429  	copyFileContent("/etc/resolv.conf", resolvConfPath)
   430  
   431  	etcHostsPath, err := hostsstore.AllocHostsFile(dataStore, m.globalOptions.Namespace, containerID)
   432  	if err != nil {
   433  		return nil, nil, err
   434  	}
   435  	copyFileContent("/etc/hosts", etcHostsPath)
   436  
   437  	specs := []oci.SpecOpts{
   438  		oci.WithHostNamespace(specs.NetworkNamespace),
   439  		withDedupMounts("/etc/hosts", withCustomHosts(etcHostsPath)),
   440  		withDedupMounts("/etc/resolv.conf", withCustomResolvConf(resolvConfPath)),
   441  	}
   442  
   443  	// `/etc/hostname` does not exist on FreeBSD
   444  	if runtime.GOOS == "linux" && m.netOpts.UTSNamespace != UtsNamespaceHost {
   445  		// If no hostname is set, default to first 12 characters of the container ID.
   446  		hostname := m.netOpts.Hostname
   447  		if hostname == "" {
   448  			hostname = containerID
   449  			if len(hostname) > 12 {
   450  				hostname = hostname[0:12]
   451  			}
   452  		}
   453  		m.netOpts.Hostname = hostname
   454  
   455  		hostnameOpts, err := writeEtcHostnameForContainer(m.globalOptions, m.netOpts.Hostname, containerID)
   456  		if err != nil {
   457  			return nil, nil, err
   458  		}
   459  		if hostnameOpts != nil {
   460  			specs = append(specs, hostnameOpts...)
   461  		}
   462  	}
   463  
   464  	return specs, cOpts, nil
   465  }
   466  
   467  // types.NetworkOptionsManager implementation for CNI networking settings.
   468  // This is a more specialized and OS-dependendant networking model so this
   469  // struct provides different implementations on different platforms.
   470  type cniNetworkManager struct {
   471  	globalOptions types.GlobalCommandOptions
   472  	netOpts       types.NetworkOptions
   473  	client        *containerd.Client
   474  	cniNetworkManagerPlatform
   475  }
   476  
   477  // NetworkOptions Returns a copy of the internal types.NetworkOptions.
   478  func (m *cniNetworkManager) NetworkOptions() types.NetworkOptions {
   479  	return m.netOpts
   480  }
   481  
   482  func validateUtsSettings(netOpts types.NetworkOptions) error {
   483  	utsNamespace := netOpts.UTSNamespace
   484  	if utsNamespace == "" {
   485  		return nil
   486  	}
   487  
   488  	// Docker considers this a validation error so keep compat.
   489  	// https://docs.docker.com/engine/reference/run/#uts-settings---uts
   490  	if utsNamespace == UtsNamespaceHost && netOpts.Hostname != "" {
   491  		return fmt.Errorf("conflicting options: cannot set a --hostname with --uts=host")
   492  	}
   493  
   494  	return nil
   495  }
   496  
   497  // Writes the provided hostname string in a "hostname" file in the Container's
   498  // Nerdctl-managed datastore and returns the oci.SpecOpts required in the container
   499  // spec for the file to be mounted under /etc/hostname in the new container.
   500  // If the hostname is empty, the leading 12 characters of the containerID
   501  func writeEtcHostnameForContainer(globalOptions types.GlobalCommandOptions, hostname string, containerID string) ([]oci.SpecOpts, error) {
   502  	if containerID == "" {
   503  		return nil, fmt.Errorf("container ID is required for setting up hostname file")
   504  	}
   505  
   506  	dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)
   507  	if err != nil {
   508  		return nil, err
   509  	}
   510  
   511  	stateDir, err := ContainerStateDirPath(globalOptions.Namespace, dataStore, containerID)
   512  	if err != nil {
   513  		return nil, err
   514  	}
   515  
   516  	hostnamePath := filepath.Join(stateDir, "hostname")
   517  	if err := os.WriteFile(hostnamePath, []byte(hostname+"\n"), 0644); err != nil {
   518  		return nil, err
   519  	}
   520  
   521  	return []oci.SpecOpts{oci.WithHostname(hostname), withCustomEtcHostname(hostnamePath)}, nil
   522  }
   523  
   524  // Loads all available networks and verifies that every selected network
   525  // from the networkSlice is of a type within supportedTypes.
   526  func verifyNetworkTypes(env *netutil.CNIEnv, networkSlice []string, supportedTypes []string) (map[string]*netutil.NetworkConfig, error) {
   527  	netMap, err := env.NetworkMap()
   528  	if err != nil {
   529  		return nil, err
   530  	}
   531  
   532  	res := make(map[string]*netutil.NetworkConfig, len(networkSlice))
   533  	for _, netstr := range networkSlice {
   534  		netConfig, ok := netMap[netstr]
   535  		if !ok {
   536  			return nil, fmt.Errorf("network %s not found", netstr)
   537  		}
   538  		netType := netConfig.Plugins[0].Network.Type
   539  		if supportedTypes != nil && !strutil.InStringSlice(supportedTypes, netType) {
   540  			return nil, fmt.Errorf("network type %q is not supported for network mapping %q, must be one of: %v", netType, netstr, supportedTypes)
   541  		}
   542  
   543  		res[netstr] = netConfig
   544  	}
   545  
   546  	return res, nil
   547  }
   548  
   549  // NetworkOptionsFromSpec Returns the NetworkOptions used in a container's creation from its spec.Annotations.
   550  func NetworkOptionsFromSpec(spec *specs.Spec) (types.NetworkOptions, error) {
   551  	opts := types.NetworkOptions{}
   552  
   553  	if spec == nil {
   554  		return opts, fmt.Errorf("cannot determine networking options from nil spec")
   555  	}
   556  	if spec.Annotations == nil {
   557  		return opts, fmt.Errorf("cannot determine networking options from nil spec.Annotations")
   558  	}
   559  
   560  	opts.Hostname = spec.Hostname
   561  
   562  	if macAddress, ok := spec.Annotations[labels.MACAddress]; ok {
   563  		opts.MACAddress = macAddress
   564  	}
   565  
   566  	if ipAddress, ok := spec.Annotations[labels.IPAddress]; ok {
   567  		opts.IPAddress = ipAddress
   568  	}
   569  
   570  	var networks []string
   571  	networksJSON := spec.Annotations[labels.Networks]
   572  	if err := json.Unmarshal([]byte(networksJSON), &networks); err != nil {
   573  		return opts, err
   574  	}
   575  	opts.NetworkSlice = networks
   576  
   577  	if portsJSON := spec.Annotations[labels.Ports]; portsJSON != "" {
   578  		if err := json.Unmarshal([]byte(portsJSON), &opts.PortMappings); err != nil {
   579  			return opts, err
   580  		}
   581  	}
   582  
   583  	return opts, nil
   584  }
   585  
   586  // Returns a lslice of keys of the values in the map that are invalid or are a non-zero-value
   587  // for their respective type. (e.g. anything other than a `""` for string type)
   588  // Note that the zero-values for innately pointer-types slices/maps/chans are `nil`,
   589  // and NOT a zero-length container value like `[]Any{}`.
   590  func nonZeroMapValues(values map[string]interface{}) []string {
   591  	nonZero := []string{}
   592  
   593  	for k, v := range values {
   594  		if !reflect.ValueOf(v).IsZero() {
   595  			nonZero = append(nonZero, k)
   596  		}
   597  	}
   598  
   599  	return nonZero
   600  }