github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/rkt/run.go (about)

     1  // Copyright 2014 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //+build linux
    16  
    17  package main
    18  
    19  import (
    20  	"bufio"
    21  	"errors"
    22  	"fmt"
    23  	"net"
    24  	"os"
    25  	"strconv"
    26  	"strings"
    27  
    28  	"github.com/appc/spec/schema/types"
    29  	cnitypes "github.com/containernetworking/cni/pkg/types"
    30  	"github.com/hashicorp/errwrap"
    31  	"github.com/opencontainers/selinux/go-selinux/label"
    32  	"github.com/rkt/rkt/common"
    33  	"github.com/rkt/rkt/pkg/lock"
    34  	pkgPod "github.com/rkt/rkt/pkg/pod"
    35  	"github.com/rkt/rkt/pkg/user"
    36  	"github.com/rkt/rkt/rkt/image"
    37  	"github.com/rkt/rkt/stage0"
    38  	"github.com/rkt/rkt/store/imagestore"
    39  	"github.com/rkt/rkt/store/treestore"
    40  	"github.com/spf13/cobra"
    41  )
    42  
    43  var (
    44  	cmdRun = &cobra.Command{
    45  		Use:   "run [--volume=name,kind=host,...] [--mount volume=VOL,target=PATH] IMAGE [-- image-args...[---]]...",
    46  		Short: "Run image(s) in a pod in rkt",
    47  		Long: `IMAGE should be a string referencing an image; either a hash, local file on
    48  disk, or URL. They will be checked in that order and the first match will be
    49  used.
    50  
    51  Volumes are made available to the container via --volume. Mounts bind volumes
    52  into each image's root within the container via --mount. --mount is
    53  position-sensitive; occurring before any images applies to all images, occurring
    54  after any images applies only to the nearest preceding image. Per-app mounts
    55  take precedence over global ones if they have the same path.
    56  
    57  An "--" may be used to inhibit rkt run's parsing of subsequent arguments, which
    58  will instead be appended to the preceding image app's exec arguments. End the
    59  image arguments with a lone "---" to resume argument parsing.`,
    60  		Run: ensureSuperuser(runWrapper(runRun)),
    61  	}
    62  	flagPorts        portList
    63  	flagNet          common.NetList
    64  	flagPrivateUsers bool
    65  	flagInheritEnv   bool
    66  	flagExplicitEnv  kvMap
    67  	flagEnvFromFile  envFileMap
    68  	flagInteractive  bool
    69  	flagDNS          flagStringList
    70  	flagDNSSearch    flagStringList
    71  	flagDNSOpt       flagStringList
    72  	flagDNSDomain    string
    73  	flagNoOverlay    bool
    74  	flagStoreOnly    bool
    75  	flagNoStore      bool
    76  	flagPodManifest  string
    77  	flagMDSRegister  bool
    78  	flagUUIDFileSave string
    79  	flagHostname     string
    80  	flagHostsEntries flagStringList
    81  	flagPullPolicy   string
    82  	flagIPCMode      string
    83  )
    84  
    85  func addIsolatorFlags(cmd *cobra.Command, compat bool) {
    86  	cmd.Flags().Var((*appMemoryLimit)(&rktApps), "memory", "memory limit for the preceding image (example: '--memory=16Mi', '--memory=50M', '--memory=1G')")
    87  	cmd.Flags().Var((*appCPULimit)(&rktApps), "cpu", "cpu limit for the preceding image (example: '--cpu=500m')")
    88  	cmd.Flags().Var((*appCPUShares)(&rktApps), "cpu-shares", "cpu-shares assigns the specified CPU time share weight (example: '--cpu-shares=2048')")
    89  	cmd.Flags().Var((*appCapsRetain)(&rktApps), "caps-retain", "capability to retain (example: '--caps-retain=CAP_SYS_ADMIN')")
    90  	cmd.Flags().Var((*appCapsRemove)(&rktApps), "caps-remove", "capability to remove (example: '--caps-remove=CAP_MKNOD')")
    91  	cmd.Flags().Var((*appSeccompFilter)(&rktApps), "seccomp", "seccomp filter override (example: '--seccomp mode=retain,errno=EPERM,chmod,chown')")
    92  	cmd.Flags().Var((*appOOMScoreAdj)(&rktApps), "oom-score-adj", "oom-score-adj isolator override")
    93  
    94  	// For backwards compatibility
    95  	if compat {
    96  		cmd.Flags().Var((*appCapsRetain)(&rktApps), "cap-retain", "capability to retain (example: '--caps-retain=CAP_SYS_ADMIN')")
    97  		cmd.Flags().Var((*appCapsRemove)(&rktApps), "cap-remove", "capability to remove (example: '--caps-remove=CAP_MKNOD')")
    98  		cmd.Flags().MarkDeprecated("cap-retain", "use --caps-retain instead")
    99  		cmd.Flags().MarkDeprecated("cap-remove", "use --caps-remove instead")
   100  	}
   101  }
   102  
   103  func addAppFlags(cmd *cobra.Command) {
   104  	cmd.Flags().Var((*appExec)(&rktApps), "exec", "override the exec command for the preceding image")
   105  	cmd.Flags().Var((*appWorkingDir)(&rktApps), "working-dir", "override the working directory of the preceding image")
   106  	cmd.Flags().Var((*appReadOnlyRootFS)(&rktApps), "readonly-rootfs", "if set, the app's rootfs will be mounted read-only")
   107  	cmd.Flags().Var((*appMount)(&rktApps), "mount", "mount point binding a volume to a path within an app")
   108  	cmd.Flags().Var((*appUser)(&rktApps), "user", "user override for the preceding image (example: '--user=user')")
   109  	cmd.Flags().Var((*appGroup)(&rktApps), "group", "group override for the preceding image (example: '--group=group')")
   110  	cmd.Flags().Var((*appSupplementaryGIDs)(&rktApps), "supplementary-gids", "supplementary group IDs override for the preceding image (examples: '--supplementary-gids=1024,2048'")
   111  	cmd.Flags().Var((*appName)(&rktApps), "name", "set the name of the app (example: '--name=foo'). If not set, then the app name default to the image's name")
   112  	cmd.Flags().Var((*appAnnotation)(&rktApps), "annotation", "set the app's annotations (example: '--annotation=foo=bar')")
   113  	cmd.Flags().Var((*appUserAnnotation)(&rktApps), "user-annotation", "set the app's annotations (example: '--user-annotation=foo=bar')")
   114  	cmd.Flags().Var((*appLabel)(&rktApps), "user-label", "set the app's labels (example: '--user-label=foo=bar')")
   115  	cmd.Flags().Var((*appEnv)(&rktApps), "environment", "set the app's environment variables (example: '--environment=foo=bar')")
   116  	if common.IsExperimentEnabled("attach") {
   117  		cmd.Flags().Var((*appStdin)(&rktApps), "stdin", "stdin mode for the preceding application (example: '--stdin=null')")
   118  		cmd.Flags().MarkHidden("stdin")
   119  		cmd.Flags().Var((*appStdout)(&rktApps), "stdout", "stdout mode for the preceding application (example: '--stdout=log')")
   120  		cmd.Flags().MarkHidden("stdout")
   121  		cmd.Flags().Var((*appStderr)(&rktApps), "stderr", "stderr mode for the preceding application (example: '--stderr=log')")
   122  		cmd.Flags().MarkHidden("stderr")
   123  	}
   124  }
   125  
   126  func init() {
   127  	cmdRkt.AddCommand(cmdRun)
   128  
   129  	/*
   130  	   Careful!
   131  	   Be sure to add common flags to run-prepared as well!
   132  	*/
   133  
   134  	addStage1ImageFlags(cmdRun.Flags())
   135  	cmdRun.Flags().Var(&flagPorts, "port", "ports to expose on the host (requires contained network). Syntax: --port=NAME:[HOSTIP:]HOSTPORT")
   136  	cmdRun.Flags().Var(&flagNet, "net", "configure the pod's networking. Optionally, pass a list of user-configured networks to load and set arguments to pass to each network, respectively. Syntax: --net[=n[:args], ...]")
   137  	cmdRun.Flags().Lookup("net").NoOptDefVal = "default"
   138  	cmdRun.Flags().BoolVar(&flagInheritEnv, "inherit-env", false, "inherit all environment variables not set by apps")
   139  	cmdRun.Flags().BoolVar(&flagNoOverlay, "no-overlay", false, "disable overlay filesystem")
   140  	cmdRun.Flags().BoolVar(&flagPrivateUsers, "private-users", false, "run within user namespaces.")
   141  	cmdRun.Flags().Var(&flagExplicitEnv, "set-env", "environment variable to set for all the apps in the form key=value, this will be overridden by --environment")
   142  	cmdRun.Flags().Var(&flagEnvFromFile, "set-env-file", "path to an environment variables file")
   143  	cmdRun.Flags().BoolVar(&flagInteractive, "interactive", false, "run pod interactively. If true, only one image may be supplied.")
   144  	cmdRun.Flags().Var(&flagDNS, "dns", "name servers to write in /etc/resolv.conf. Pass 'host' to use host's resolv.conf. Pass 'none' to ignore CNI DNS config")
   145  	cmdRun.Flags().Var(&flagDNSSearch, "dns-search", "DNS search domains to write in /etc/resolv.conf")
   146  	cmdRun.Flags().Var(&flagDNSOpt, "dns-opt", "DNS options to write in /etc/resolv.conf")
   147  	cmdRun.Flags().StringVar(&flagDNSDomain, "dns-domain", "", "DNS domain to write in /etc/resolv.conf")
   148  	cmdRun.Flags().Var(&flagHostsEntries, "hosts-entry", "Entries to add to the pod-wide /etc/hosts. Pass 'host' to use the host's /etc/hosts")
   149  	cmdRun.Flags().BoolVar(&flagStoreOnly, "store-only", false, "use only available images in the store (do not discover or download from remote URLs)")
   150  	cmdRun.Flags().MarkDeprecated("store-only", "please use --pull-policy=never")
   151  	cmdRun.Flags().BoolVar(&flagNoStore, "no-store", false, "fetch images ignoring the local store")
   152  	cmdRun.Flags().MarkDeprecated("no-store", "please use --pull-policy=update")
   153  	cmdRun.Flags().StringVar(&flagPullPolicy, "pull-policy", image.PullPolicyNew, "when to pull an image")
   154  	cmdRun.Flags().StringVar(&flagPodManifest, "pod-manifest", "", "the path to the pod manifest. If it's non-empty, then only '--net', '--no-overlay' and '--interactive' will have effect")
   155  	cmdRun.Flags().BoolVar(&flagMDSRegister, "mds-register", false, "register pod with metadata service. needs network connectivity to the host (--net=(default|default-restricted|host)")
   156  	cmdRun.Flags().StringVar(&flagUUIDFileSave, "uuid-file-save", "", "write out pod UUID to specified file")
   157  	cmdRun.Flags().StringVar(&flagHostname, "hostname", "", `pod's hostname. If empty, it will be "rkt-$PODUUID"`)
   158  	cmdRun.Flags().Var((*appsVolume)(&rktApps), "volume", "volumes to make available in the pod")
   159  	cmdRun.Flags().StringVar(&flagIPCMode, "ipc", "", `whether to stay in the host IPC namespace. Syntax: --ipc=[auto|private|parent]`)
   160  
   161  	// per-app flags
   162  	cmdRun.Flags().Var((*appAsc)(&rktApps), "signature", "local signature file to use in validating the preceding image")
   163  	addAppFlags(cmdRun)
   164  	addIsolatorFlags(cmdRun, true)
   165  
   166  	flagPorts = portList{}
   167  	flagDNS = flagStringList{}
   168  	flagDNSSearch = flagStringList{}
   169  	flagDNSOpt = flagStringList{}
   170  	flagHostsEntries = flagStringList{}
   171  
   172  	// Disable interspersed flags to stop parsing after the first non flag
   173  	// argument. All the subsequent parsing will be done by parseApps.
   174  	// This is needed to correctly handle image args
   175  	cmdRun.Flags().SetInterspersed(false)
   176  }
   177  
   178  func runRun(cmd *cobra.Command, args []string) (exit int) {
   179  	privateUsers := user.NewBlankUidRange()
   180  	err := parseApps(&rktApps, args, cmd.Flags(), true)
   181  	if err != nil {
   182  		stderr.PrintE("error parsing app image arguments", err)
   183  		return 254
   184  	}
   185  
   186  	if flagStoreOnly && flagNoStore {
   187  		stderr.Print("both --store-only and --no-store specified")
   188  		return 254
   189  	}
   190  	if flagStoreOnly {
   191  		flagPullPolicy = image.PullPolicyNever
   192  	}
   193  	if flagNoStore {
   194  		flagPullPolicy = image.PullPolicyUpdate
   195  	}
   196  
   197  	if flagPrivateUsers {
   198  		if !common.SupportsUserNS() {
   199  			stderr.Print("--private-users is not supported, kernel compiled without user namespace support")
   200  			return 254
   201  		}
   202  		privateUsers.SetRandomUidRange(user.DefaultRangeCount)
   203  	}
   204  
   205  	if len(flagPorts) > 0 && flagNet.None() {
   206  		stderr.Print("--port flag does not work with 'none' networking")
   207  		return 254
   208  	}
   209  	if len(flagPorts) > 0 && flagNet.Host() {
   210  		stderr.Print("--port flag does not work with 'host' networking")
   211  		return 254
   212  	}
   213  
   214  	if flagMDSRegister && flagNet.None() {
   215  		stderr.Print("--mds-register flag does not work with --net=none. Please use 'host', 'default' or an equivalent network")
   216  		return 254
   217  	}
   218  
   219  	if len(flagPodManifest) > 0 && (rktApps.Count() > 0 ||
   220  		(*appsVolume)(&rktApps).String() != "" || (*appMount)(&rktApps).String() != "" ||
   221  		len(flagPorts) > 0 || flagPullPolicy == image.PullPolicyNever ||
   222  		flagPullPolicy == image.PullPolicyUpdate || flagInheritEnv ||
   223  		!flagExplicitEnv.IsEmpty() || !flagEnvFromFile.IsEmpty()) {
   224  		stderr.Print("conflicting flags set with --pod-manifest (see --help)")
   225  		return 254
   226  	}
   227  
   228  	if flagInteractive && rktApps.Count() > 1 {
   229  		stderr.Print("interactive option only supports one image")
   230  		return 254
   231  	}
   232  
   233  	if rktApps.Count() < 1 && len(flagPodManifest) == 0 {
   234  		stderr.Print("must provide at least one image or specify the pod manifest")
   235  		return 254
   236  	}
   237  
   238  	s, err := imagestore.NewStore(storeDir())
   239  	if err != nil {
   240  		stderr.PrintE("cannot open store", err)
   241  		return 254
   242  	}
   243  
   244  	ts, err := treestore.NewStore(treeStoreDir(), s)
   245  	if err != nil {
   246  		stderr.PrintE("cannot open treestore", err)
   247  		return 254
   248  	}
   249  
   250  	config, err := getConfig()
   251  	if err != nil {
   252  		stderr.PrintE("cannot get configuration", err)
   253  		return 254
   254  	}
   255  
   256  	s1img, err := getStage1Hash(s, ts, config)
   257  	if err != nil {
   258  		stderr.Error(err)
   259  		return 254
   260  	}
   261  
   262  	fn := &image.Finder{
   263  		S:                  s,
   264  		Ts:                 ts,
   265  		Ks:                 getKeystore(),
   266  		Headers:            config.AuthPerHost,
   267  		DockerAuth:         config.DockerCredentialsPerRegistry,
   268  		InsecureFlags:      globalFlags.InsecureFlags,
   269  		Debug:              globalFlags.Debug,
   270  		TrustKeysFromHTTPS: globalFlags.TrustKeysFromHTTPS,
   271  
   272  		PullPolicy: flagPullPolicy,
   273  		WithDeps:   true,
   274  	}
   275  	if err := fn.FindImages(&rktApps); err != nil {
   276  		stderr.Error(err)
   277  		return 254
   278  	}
   279  
   280  	p, err := pkgPod.NewPod(getDataDir())
   281  	if err != nil {
   282  		stderr.PrintE("error creating new pod", err)
   283  		return 254
   284  	}
   285  
   286  	// if requested, write out pod UUID early so "rkt rm" can
   287  	// clean it up even if something goes wrong
   288  	if flagUUIDFileSave != "" {
   289  		if err := pkgPod.WriteUUIDToFile(p.UUID, flagUUIDFileSave); err != nil {
   290  			stderr.PrintE("error saving pod UUID to file", err)
   291  			return 254
   292  		}
   293  	}
   294  
   295  	processLabel, mountLabel, err := label.InitLabels([]string{})
   296  	if err != nil {
   297  		stderr.PrintE("error initialising SELinux", err)
   298  		return 254
   299  	}
   300  	p.MountLabel = mountLabel
   301  
   302  	cfg := stage0.CommonConfig{
   303  		DataDir:      getDataDir(),
   304  		MountLabel:   mountLabel,
   305  		ProcessLabel: processLabel,
   306  		Store:        s,
   307  		TreeStore:    ts,
   308  		Stage1Image:  *s1img,
   309  		UUID:         p.UUID,
   310  		Debug:        globalFlags.Debug,
   311  		Mutable:      false,
   312  	}
   313  
   314  	ovlOk := true
   315  	if err := common.PathSupportsOverlay(getDataDir()); err != nil {
   316  		if oerr, ok := err.(common.ErrOverlayUnsupported); ok {
   317  			stderr.Printf("disabling overlay support: %q", oerr.Error())
   318  			ovlOk = false
   319  		} else {
   320  			stderr.PrintE("error determining overlay support", err)
   321  			return 254
   322  		}
   323  	}
   324  
   325  	useOverlay := !flagNoOverlay && ovlOk
   326  
   327  	pcfg := stage0.PrepareConfig{
   328  		CommonConfig: &cfg,
   329  		UseOverlay:   useOverlay,
   330  		PrivateUsers: privateUsers,
   331  	}
   332  
   333  	if len(flagPodManifest) > 0 {
   334  		pcfg.PodManifest = flagPodManifest
   335  	} else {
   336  		pcfg.Ports = []types.ExposedPort(flagPorts)
   337  		pcfg.InheritEnv = flagInheritEnv
   338  		pcfg.ExplicitEnv = flagExplicitEnv.Strings()
   339  		pcfg.EnvFromFile = flagEnvFromFile.Strings()
   340  		pcfg.Apps = &rktApps
   341  	}
   342  
   343  	if globalFlags.Debug {
   344  		stage0.InitDebug()
   345  	}
   346  
   347  	keyLock, err := lock.SharedKeyLock(lockDir(), common.PrepareLock)
   348  	if err != nil {
   349  		stderr.PrintE("cannot get shared prepare lock", err)
   350  		return 254
   351  	}
   352  	err = stage0.Prepare(pcfg, p.Path(), p.UUID)
   353  	if err != nil {
   354  		stderr.PrintE("error setting up stage0", err)
   355  		keyLock.Close()
   356  		return 254
   357  	}
   358  	keyLock.Close()
   359  
   360  	// get the lock fd for run
   361  	lfd, err := p.Fd()
   362  	if err != nil {
   363  		stderr.PrintE("error getting pod lock fd", err)
   364  		return 254
   365  	}
   366  
   367  	// skip prepared by jumping directly to run, we own this pod
   368  	if err := p.ToRun(); err != nil {
   369  		stderr.PrintE("unable to transition to run", err)
   370  		return 254
   371  	}
   372  
   373  	rktgid, err := common.LookupGid(common.RktGroup)
   374  	if err != nil {
   375  		stderr.Printf("group %q not found, will use default gid when rendering images", common.RktGroup)
   376  		rktgid = -1
   377  	}
   378  
   379  	DNSConfMode, DNSConfig, HostsEntries, err := parseDNSFlags(flagHostsEntries, flagDNS, flagDNSSearch, flagDNSOpt, flagDNSDomain)
   380  	if err != nil {
   381  		stderr.PrintE("error with dns flags", err)
   382  		return 254
   383  	}
   384  
   385  	rcfg := stage0.RunConfig{
   386  		CommonConfig:         &cfg,
   387  		Net:                  flagNet,
   388  		LockFd:               lfd,
   389  		Interactive:          flagInteractive,
   390  		DNSConfMode:          DNSConfMode,
   391  		DNSConfig:            DNSConfig,
   392  		MDSRegister:          flagMDSRegister,
   393  		LocalConfig:          globalFlags.LocalConfigDir,
   394  		RktGid:               rktgid,
   395  		Hostname:             flagHostname,
   396  		InsecureCapabilities: globalFlags.InsecureFlags.SkipCapabilities(),
   397  		InsecurePaths:        globalFlags.InsecureFlags.SkipPaths(),
   398  		InsecureSeccomp:      globalFlags.InsecureFlags.SkipSeccomp(),
   399  		UseOverlay:           useOverlay,
   400  		HostsEntries:         *HostsEntries,
   401  		IPCMode:              flagIPCMode,
   402  	}
   403  
   404  	_, manifest, err := p.PodManifest()
   405  	if err != nil {
   406  		stderr.PrintE("cannot get the pod manifest", err)
   407  		return 254
   408  	}
   409  
   410  	if len(manifest.Apps) == 0 {
   411  		stderr.Print("pod must contain at least one application")
   412  		return 254
   413  	}
   414  	rcfg.Apps = manifest.Apps
   415  	stage0.Run(rcfg, p.Path(), getDataDir()) // execs, never returns
   416  
   417  	return 254
   418  }
   419  
   420  // portList implements the flag.Value interface to contain a set of mappings
   421  // from port name --> host port
   422  type portList []types.ExposedPort
   423  
   424  func (pl *portList) Set(s string) error {
   425  	parts := strings.SplitN(s, ":", 3)
   426  	if len(parts) < 2 {
   427  		return fmt.Errorf("%q is not in name:[ip:]port format", s)
   428  	}
   429  
   430  	name, err := types.NewACName(parts[0])
   431  	if err != nil {
   432  		return errwrap.Wrap(fmt.Errorf("%q is not a valid port name", parts[0]), err)
   433  	}
   434  
   435  	portStr := parts[1]
   436  	var ip net.IP
   437  	if len(parts) == 3 {
   438  		portStr = parts[2]
   439  		ip = net.ParseIP(parts[1])
   440  		if ip == nil {
   441  			return fmt.Errorf("%q is not a valid IP", parts[1])
   442  		}
   443  	}
   444  
   445  	port, err := strconv.ParseUint(portStr, 10, 16)
   446  	if err != nil {
   447  		return fmt.Errorf("%q is not a valid port number", parts[1])
   448  	}
   449  
   450  	p := types.ExposedPort{
   451  		Name:     *name,
   452  		HostPort: uint(port),
   453  		HostIP:   ip,
   454  	}
   455  
   456  	*pl = append(*pl, p)
   457  	return nil
   458  }
   459  
   460  func (pl *portList) String() string {
   461  	var ps []string
   462  	for _, p := range []types.ExposedPort(*pl) {
   463  		ps = append(ps, fmt.Sprintf("%v:%v", p.Name, p.HostPort))
   464  	}
   465  	return strings.Join(ps, " ")
   466  }
   467  
   468  func (pl *portList) Type() string {
   469  	return "portList"
   470  }
   471  
   472  // flagStringList implements the flag.Value interface to contain a set of strings
   473  type flagStringList []string
   474  
   475  func (dns *flagStringList) Set(s string) error {
   476  	*dns = append(*dns, s)
   477  	return nil
   478  }
   479  
   480  func (dns *flagStringList) String() string {
   481  	return strings.Join(*dns, " ")
   482  }
   483  
   484  func (dns *flagStringList) Type() string {
   485  	return "flagStringList"
   486  }
   487  
   488  // kvMap implements the flag.Value interface to contain a set of key=value mappings
   489  type kvMap struct {
   490  	mapping map[string]string
   491  }
   492  
   493  func (e *kvMap) Set(s string) error {
   494  	if e.mapping == nil {
   495  		e.mapping = make(map[string]string)
   496  	}
   497  	pair := strings.SplitN(s, "=", 2)
   498  	if len(pair) != 2 {
   499  		return fmt.Errorf("must be specified as key=value")
   500  	}
   501  	if _, exists := e.mapping[pair[0]]; exists {
   502  		return fmt.Errorf("key %q already set", pair[0])
   503  	}
   504  	e.mapping[pair[0]] = pair[1]
   505  	return nil
   506  }
   507  
   508  func (e *kvMap) IsEmpty() bool {
   509  	return len(e.mapping) == 0
   510  }
   511  
   512  func (e *kvMap) String() string {
   513  	return strings.Join(e.Strings(), "\n")
   514  }
   515  
   516  func (e *kvMap) Strings() []string {
   517  	var env []string
   518  	for n, v := range e.mapping {
   519  		env = append(env, n+"="+v)
   520  	}
   521  	return env
   522  }
   523  
   524  func (e *kvMap) Type() string {
   525  	return "kvMap"
   526  }
   527  
   528  // envFileMap
   529  type envFileMap struct {
   530  	mapping map[string]string
   531  }
   532  
   533  func commentLine(s string) bool {
   534  	return strings.HasPrefix(s, "#") || strings.HasPrefix(s, ";")
   535  }
   536  
   537  func (e *envFileMap) Set(s string) error {
   538  	if e.mapping == nil {
   539  		e.mapping = make(map[string]string)
   540  	}
   541  	file, err := os.Open(s)
   542  	if err != nil {
   543  		return err
   544  	}
   545  	defer file.Close()
   546  
   547  	scanner := bufio.NewScanner(file)
   548  	for scanner.Scan() {
   549  		line := scanner.Text()
   550  		// Skip empty lines
   551  		if len(line) == 0 {
   552  			continue
   553  		}
   554  		// Skip comments
   555  		if commentLine(line) {
   556  			continue
   557  		}
   558  		pair := strings.SplitN(line, "=", 2)
   559  		// Malformed lines
   560  		if len(pair) != 2 || len(pair[0]) == 0 {
   561  			return fmt.Errorf("environment variable must be specified as name=value (file %q)", s)
   562  		}
   563  		if _, exists := e.mapping[pair[0]]; exists {
   564  			return fmt.Errorf("environment variable %q already set (file %q)", pair[0], s)
   565  		}
   566  		e.mapping[pair[0]] = pair[1]
   567  	}
   568  	return nil
   569  }
   570  
   571  func (e *envFileMap) IsEmpty() bool {
   572  	return len(e.mapping) == 0
   573  }
   574  
   575  func (e *envFileMap) String() string {
   576  	return strings.Join(e.Strings(), "\n")
   577  }
   578  
   579  func (e *envFileMap) Strings() []string {
   580  	var env []string
   581  	for n, v := range e.mapping {
   582  		env = append(env, n+"="+v)
   583  	}
   584  	return env
   585  }
   586  
   587  func (e *envFileMap) Type() string {
   588  	return "envFileMap"
   589  }
   590  
   591  /*
   592   * Parse out the --hosts-entries, --dns, --dns-search, and --dns-opt flags
   593   * This includes decoding the "magic" values for hosts-entries and dns.
   594   * Try to detect any obvious insanity, namely invalid IPs or more than one
   595   * magic option
   596   */
   597  func parseDNSFlags(flagHostsEntries, flagDNS, flagDNSSearch, flagDNSOpt []string, flagDNSDomain string) (stage0.DNSConfMode, cnitypes.DNS, *stage0.HostsEntries, error) {
   598  	DNSConfMode := stage0.DNSConfMode{
   599  		Resolv: "default",
   600  		Hosts:  "default",
   601  	}
   602  	DNSConfig := cnitypes.DNS{}
   603  	HostsEntries := make(stage0.HostsEntries)
   604  
   605  	// Loop through --dns and look for magic option
   606  	// Check for obvious insanity - only one magic option allowed
   607  	for _, d := range flagDNS {
   608  		// parse magic values
   609  		if d == "host" || d == "none" {
   610  			if len(flagDNS) > 1 {
   611  				return DNSConfMode, DNSConfig, &HostsEntries,
   612  					fmt.Errorf("no other --dns options allowed when --dns=%s is passed", d)
   613  			}
   614  			DNSConfMode.Resolv = d
   615  			break
   616  
   617  		} else {
   618  			// parse list of IPS
   619  			for _, d := range strings.Split(d, ",") {
   620  				if net.ParseIP(d) == nil {
   621  					return DNSConfMode, DNSConfig, &HostsEntries,
   622  						fmt.Errorf("Invalid IP passed to --dns: %s", d)
   623  				}
   624  				DNSConfig.Nameservers = append(DNSConfig.Nameservers, d)
   625  			}
   626  		}
   627  	}
   628  
   629  	DNSConfig.Search = flagDNSSearch
   630  	DNSConfig.Options = flagDNSOpt
   631  	DNSConfig.Domain = flagDNSDomain
   632  
   633  	if !common.IsDNSZero(&DNSConfig) {
   634  		if DNSConfMode.Resolv == "default" {
   635  			DNSConfMode.Resolv = "stage0"
   636  		}
   637  
   638  		if DNSConfMode.Resolv != "stage0" {
   639  			return DNSConfMode, DNSConfig, &HostsEntries,
   640  				fmt.Errorf("Cannot call --dns-opt, --dns-search, or --dns-domain with --dns=%v", DNSConfMode.Resolv)
   641  		}
   642  	}
   643  
   644  	// Parse out --hosts-entries, also looking for the magic value "host"
   645  	for _, entry := range flagHostsEntries {
   646  		if entry == "host" {
   647  			DNSConfMode.Hosts = "host"
   648  			continue
   649  		}
   650  		for _, entry := range strings.Split(entry, ",") {
   651  			vals := strings.SplitN(entry, "=", 2)
   652  			if len(vals) != 2 {
   653  				return DNSConfMode, DNSConfig, &HostsEntries,
   654  					fmt.Errorf("Did not understand --hosts-entry %s", entry)
   655  			}
   656  			ipStr := vals[0]
   657  			hostname := vals[1]
   658  
   659  			// validate IP address
   660  			ip := net.ParseIP(ipStr)
   661  			if ip == nil {
   662  				return DNSConfMode, DNSConfig, &HostsEntries,
   663  					fmt.Errorf("Invalid IP passed to --hosts-entry: %s", ipStr)
   664  			}
   665  
   666  			_, exists := HostsEntries[ipStr]
   667  			if !exists {
   668  				HostsEntries[ipStr] = []string{hostname}
   669  			} else {
   670  				HostsEntries[ipStr] = append(HostsEntries[ipStr], hostname)
   671  			}
   672  		}
   673  	}
   674  
   675  	if len(HostsEntries) > 0 {
   676  		if DNSConfMode.Hosts == "host" {
   677  			return DNSConfMode, DNSConfig, &HostsEntries,
   678  				errors.New("cannot pass --hosts-entry=host with multiple hosts-entries")
   679  		}
   680  		DNSConfMode.Hosts = "stage0"
   681  	}
   682  
   683  	return DNSConfMode, DNSConfig, &HostsEntries, nil
   684  }