From 00d38cb37958f3c636aa5837b8f01dfad891a0b5 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Mon, 18 Dec 2017 12:05:06 -0500 Subject: podman create/run need to load information from the image We should be pulling information out of the image to set the defaults to use when setting up the container. Signed-off-by: Daniel J Walsh Closes: #110 Approved by: mheon --- cmd/podman/create.go | 251 +++++++++++++++++++++++++++++++++++--------------- cmd/podman/inspect.go | 3 +- cmd/podman/parse.go | 73 --------------- cmd/podman/run.go | 59 +++--------- cmd/podman/spec.go | 3 - 5 files changed, 192 insertions(+), 197 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 8b64a1cb0..182eb1e56 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/signal" + "github.com/docker/go-connections/nat" "github.com/docker/go-units" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" @@ -80,10 +81,11 @@ type createConfig struct { DNSServers []string //dns Entrypoint string //entrypoint Env map[string]string //env - Expose []string //expose - GroupAdd []uint32 // group-add - Hostname string //hostname + ExposedPorts map[nat.Port]struct{} + GroupAdd []uint32 // group-add + Hostname string //hostname Image string + ImageID string Interactive bool //interactive IpcMode container.IpcMode //ipc IP6Address string //ipv6 @@ -99,7 +101,8 @@ type createConfig struct { NetworkAlias []string //network-alias PidMode container.PidMode //pid NsUser string - Pod string //pod + Pod string //pod + PortBindings nat.PortMap Privileged bool //privileged Publish []string //publish PublishAll bool //publish-all @@ -115,8 +118,7 @@ type createConfig struct { Sysctl map[string]string //sysctl Tmpfs []string // tmpfs Tty bool //tty - User uint32 //user - Group uint32 // group + User string //user UtsMode container.UTSMode //uts Volumes []string //volume WorkDir string //workdir @@ -148,7 +150,6 @@ var createCommand = cli.Command{ func createCmd(c *cli.Context) error { // TODO should allow user to create based off a directory on the host not just image // Need CLI support for this - var imageName string if err := validateFlags(c, createFlags); err != nil { return err } @@ -164,54 +165,19 @@ func createCmd(c *cli.Context) error { return err } - // Deal with the image after all the args have been checked - createImage := runtime.NewImage(createConfig.Image) - createImage.LocalName, _ = createImage.GetLocalImageName() - if createImage.LocalName == "" { - // The image wasnt found by the user input'd name or its fqname - // Pull the image - var writer io.Writer - if !createConfig.Quiet { - writer = os.Stdout - } - createImage.Pull(writer) - } - runtimeSpec, err := createConfigToOCISpec(createConfig) if err != nil { return err } - if createImage.LocalName != "" { - nameIsID, err := runtime.IsImageID(createImage.LocalName) - if err != nil { - return err - } - if nameIsID { - // If the input from the user is an ID, then we need to get the image - // name for cstorage - createImage.LocalName, err = createImage.GetNameByID() - if err != nil { - return err - } - } - imageName = createImage.LocalName - } else { - imageName, err = createImage.GetFQName() - } - if err != nil { - return err - } - imageID, err := createImage.GetImageID() - if err != nil { - return err - } options, err := createConfig.GetContainerCreateOptions() if err != nil { return errors.Wrapf(err, "unable to parse new container options") } // Gather up the options for NewContainer which consist of With... funcs - options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false)) + options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, true)) options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel)) + options = append(options, libpod.WithLabels(createConfig.Labels)) + options = append(options, libpod.WithUser(createConfig.User)) options = append(options, libpod.WithShmDir(createConfig.ShmDir)) ctr, err := runtime.NewContainer(runtimeSpec, options...) if err != nil { @@ -300,13 +266,101 @@ func parseSecurityOpt(config *createConfig, securityOpts []string) error { return err } +func exposedPorts(c *cli.Context, imageExposedPorts map[string]struct{}) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) { + // TODO Handle exposed ports from image + // Currently ignoring imageExposedPorts + + ports, portBindings, err := nat.ParsePortSpecs(c.StringSlice("publish")) + if err != nil { + return nil, nil, err + } + + for _, e := range c.StringSlice("expose") { + // Merge in exposed ports to the map of published ports + if strings.Contains(e, ":") { + return nil, nil, fmt.Errorf("invalid port format for --expose: %s", e) + } + //support two formats for expose, original format /[] or /[] + proto, port := nat.SplitProtoPort(e) + //parse the start and end port and create a sequence of ports to expose + //if expose a port, the start and end port are the same + start, end, err := nat.ParsePortRange(port) + if err != nil { + return nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err) + } + for i := start; i <= end; i++ { + p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) + if err != nil { + return nil, nil, err + } + if _, exists := ports[p]; !exists { + ports[p] = struct{}{} + } + } + } + return ports, portBindings, nil +} + +// imageData pulls down the image if not stored locally and extracts the +// default container runtime data out of it. imageData returns the data +// to the caller. Example Data: Entrypoint, Env, WorkingDir, Labels ... +func imageData(c *cli.Context, runtime *libpod.Runtime, image string) (string, string, *libpod.ImageData, error) { + var err error + // Deal with the image after all the args have been checked + createImage := runtime.NewImage(image) + createImage.LocalName, _ = createImage.GetLocalImageName() + if createImage.LocalName == "" { + // The image wasnt found by the user input'd name or its fqname + // Pull the image + var writer io.Writer + if !c.Bool("quiet") { + writer = os.Stdout + } + createImage.Pull(writer) + } + + var imageName string + if createImage.LocalName != "" { + nameIsID, err := runtime.IsImageID(createImage.LocalName) + if err != nil { + return "", "", nil, err + } + if nameIsID { + // If the input from the user is an ID, then we need to get the image + // name for cstorage + createImage.LocalName, err = createImage.GetNameByID() + if err != nil { + return "", "", nil, err + } + } + imageName = createImage.LocalName + } else { + imageName, err = createImage.GetFQName() + } + if err != nil { + return "", "", nil, err + } + imageID, err := createImage.GetImageID() + if err != nil { + return "", "", nil, err + } + storageImage, err := runtime.GetImage(image) + if err != nil { + return "", "", nil, errors.Wrapf(err, "error getting storage image %q", image) + } + data, err := runtime.GetImageInspectInfo(*storageImage) + if err != nil { + return "", "", nil, errors.Wrapf(err, "error parsing image data %q", image) + } + return imageName, imageID, data, err +} + // Parses CLI options related to container creation into a config which can be // parsed into an OCI runtime spec func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, error) { var command []string var memoryLimit, memoryReservation, memorySwap, memoryKernel int64 var blkioWeight uint16 - var uid, gid uint32 if len(c.Args()) < 1 { return nil, errors.Errorf("image name or ID is required") @@ -317,33 +371,14 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er command = c.Args()[1:] } - // LABEL VARIABLES - labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("labels")) - if err != nil { - return &createConfig{}, errors.Wrapf(err, "unable to process labels") - } - // ENVIRONMENT VARIABLES - env := defaultEnvVariables - if err := readKVStrings(env, c.StringSlice("env-file"), c.StringSlice("env")); err != nil { - return &createConfig{}, errors.Wrapf(err, "unable to process environment variables") - } - sysctl, err := convertStringSliceToMap(c.StringSlice("sysctl"), "=") if err != nil { - return &createConfig{}, errors.Wrapf(err, "sysctl values must be in the form of KEY=VALUE") + return nil, errors.Wrapf(err, "sysctl values must be in the form of KEY=VALUE") } groupAdd, err := stringSlicetoUint32Slice(c.StringSlice("group-add")) if err != nil { - return &createConfig{}, errors.Wrapf(err, "invalid value for groups provided") - } - - if c.String("user") != "" { - // TODO - // We need to mount the imagefs and get the uid/gid - // For now, user zeros - uid = 0 - gid = 0 + return nil, errors.Wrapf(err, "invalid value for groups provided") } if c.String("memory") != "" { @@ -417,14 +452,79 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er } shmDir = ctr.ShmDir() } - stopSignal := syscall.SIGTERM + + imageName, imageID, data, err := imageData(c, runtime, image) + if err != nil { + return nil, err + } + + // USER + user := c.String("user") + if user == "" { + user = data.Config.User + } + + // STOP SIGNAL + stopSignal := syscall.SIGINT + signalString := data.Config.StopSignal if c.IsSet("stop-signal") { - stopSignal, err = signal.ParseSignal(c.String("stop-signal")) + signalString = c.String("stop-signal") + } + if signalString != "" { + stopSignal, err = signal.ParseSignal(signalString) if err != nil { return nil, err } } + // ENVIRONMENT VARIABLES + env := defaultEnvVariables + for _, e := range data.Config.Env { + split := strings.SplitN(e, "=", 2) + if len(split) > 1 { + env[split[0]] = split[1] + } else { + env[split[0]] = "" + } + } + if err := readKVStrings(env, c.StringSlice("env-file"), c.StringSlice("env")); err != nil { + return nil, errors.Wrapf(err, "unable to process environment variables") + } + + // LABEL VARIABLES + labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("labels")) + if err != nil { + return nil, errors.Wrapf(err, "unable to process labels") + } + for key, val := range data.Config.Labels { + if _, ok := labels[key]; !ok { + labels[key] = val + } + } + + // WORKING DIRECTORY + workDir := c.String("workdir") + if workDir == "" { + workDir = data.Config.WorkingDir + } + + // COMMAND + if len(command) == 0 { + command = data.Config.Cmd + } + + // ENTRYPOINT + entrypoint := c.String("entrypoint") + if entrypoint == "" { + entrypoint = strings.Join(data.Config.Entrypoint, " ") + } + + // EXPOSED PORTS + ports, portBindings, err := exposedPorts(c, data.Config.ExposedPorts) + if err != nil { + return nil, err + } + config := &createConfig{ Runtime: runtime, CapAdd: c.StringSlice("cap-add"), @@ -436,12 +536,13 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er DNSOpt: c.StringSlice("dns-opt"), DNSSearch: c.StringSlice("dns-search"), DNSServers: c.StringSlice("dns"), - Entrypoint: c.String("entrypoint"), + Entrypoint: entrypoint, Env: env, - Expose: c.StringSlice("expose"), + ExposedPorts: ports, GroupAdd: groupAdd, Hostname: c.String("hostname"), - Image: image, + Image: imageName, + ImageID: imageID, Interactive: c.Bool("interactive"), IP6Address: c.String("ipv6"), IPAddress: c.String("ip"), @@ -461,6 +562,7 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er Privileged: c.Bool("privileged"), Publish: c.StringSlice("publish"), PublishAll: c.Bool("publish-all"), + PortBindings: portBindings, Quiet: c.Bool("quiet"), ReadOnlyRootfs: c.Bool("read-only"), Resources: createResourceConfig{ @@ -499,10 +601,9 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er Sysctl: sysctl, Tmpfs: c.StringSlice("tmpfs"), Tty: tty, - User: uid, - Group: gid, + User: user, Volumes: c.StringSlice("volume"), - WorkDir: c.String("workdir"), + WorkDir: workDir, } if !config.Privileged { diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 7fd039b75..869d16911 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" + "github.com/docker/go-connections/nat" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/projectatomic/libpod/cmd/podman/formats" @@ -287,7 +288,7 @@ type HostConfig struct { ContainerIDFile string `json:"ContainerIDFile"` LogConfig *LogConfig `json:"LogConfig"` //TODO NetworkMode string `json:"NetworkMode"` - PortBindings map[string]struct{} `json:"PortBindings"` //TODO + PortBindings nat.PortMap `json:"PortBindings"` //TODO AutoRemove bool `json:"AutoRemove"` CapAdd []string `json:"CapAdd"` CapDrop []string `json:"CapDrop"` diff --git a/cmd/podman/parse.go b/cmd/podman/parse.go index 53d49c36c..bb45d08c4 100644 --- a/cmd/podman/parse.go +++ b/cmd/podman/parse.go @@ -11,14 +11,12 @@ import ( "io/ioutil" "net" "os" - "os/user" "path" "regexp" "strconv" "strings" units "github.com/docker/go-units" - specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" ) @@ -713,77 +711,6 @@ func parseStorageOpts(storageOpts []string) (map[string]string, error) { //nolin return m, nil } -// parseUser parses the the uid and gid in the format [:] -// for user flag -// FIXME: Issue from https://github.com/projectatomic/buildah/issues/66 -func parseUser(rootdir, userspec string) (specs.User, error) { //nolint - var gid64 uint64 - var gerr error = user.UnknownGroupError("error looking up group") - - spec := strings.SplitN(userspec, ":", 2) - userspec = spec[0] - groupspec := "" - if userspec == "" { - return specs.User{}, nil - } - if len(spec) > 1 { - groupspec = spec[1] - } - - uid64, uerr := strconv.ParseUint(userspec, 10, 32) - if uerr == nil && groupspec == "" { - // We parsed the user name as a number, and there's no group - // component, so we need to look up the user's primary GID. - var name string - name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64) - if gerr == nil { - userspec = name - } else { - if userrec, err := user.LookupId(userspec); err == nil { - gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32) - userspec = userrec.Name - } - } - } - if uerr != nil { - uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec) - gerr = uerr - } - if uerr != nil { - if userrec, err := user.Lookup(userspec); err == nil { - uid64, uerr = strconv.ParseUint(userrec.Uid, 10, 32) - gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32) - } - } - - if groupspec != "" { - gid64, gerr = strconv.ParseUint(groupspec, 10, 32) - if gerr != nil { - gid64, gerr = lookupGroupInContainer(rootdir, groupspec) - } - if gerr != nil { - if group, err := user.LookupGroup(groupspec); err == nil { - gid64, gerr = strconv.ParseUint(group.Gid, 10, 32) - } - } - } - - if uerr == nil && gerr == nil { - u := specs.User{ - UID: uint32(uid64), - GID: uint32(gid64), - Username: userspec, - } - return u, nil - } - - err := errors.Wrapf(uerr, "error determining run uid") - if uerr == nil { - err = errors.Wrapf(gerr, "error determining run gid") - } - return specs.User{}, err -} - // convertKVStringsToMap converts ["key=value"] to {"key":"value"} func convertKVStringsToMap(values []string) map[string]string { result := make(map[string]string, len(values)) diff --git a/cmd/podman/run.go b/cmd/podman/run.go index bc93459ad..654b7a47e 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -1,9 +1,8 @@ package main import ( + "encoding/json" "fmt" - "io" - "os" "sync" "github.com/pkg/errors" @@ -26,7 +25,6 @@ var runCommand = cli.Command{ } func runCmd(c *cli.Context) error { - var imageName string if err := validateFlags(c, createFlags); err != nil { return err } @@ -41,51 +39,10 @@ func runCmd(c *cli.Context) error { return err } - createImage := runtime.NewImage(createConfig.Image) - createImage.LocalName, _ = createImage.GetLocalImageName() - if createImage.LocalName == "" { - // The image wasnt found by the user input'd name or its fqname - // Pull the image - var writer io.Writer - if !createConfig.Quiet { - writer = os.Stdout - } - createImage.Pull(writer) - } - runtimeSpec, err := createConfigToOCISpec(createConfig) if err != nil { return err } - logrus.Debug("spec is ", runtimeSpec) - - if createImage.LocalName != "" { - nameIsID, err := runtime.IsImageID(createImage.LocalName) - if err != nil { - return err - } - if nameIsID { - // If the input from the user is an ID, then we need to get the image - // name for cstorage - createImage.LocalName, err = createImage.GetNameByID() - if err != nil { - return err - } - } - imageName = createImage.LocalName - } else { - imageName, err = createImage.GetFQName() - } - if err != nil { - return err - } - logrus.Debug("imageName is ", imageName) - - imageID, err := createImage.GetImageID() - if err != nil { - return err - } - logrus.Debug("imageID is ", imageID) options, err := createConfig.GetContainerCreateOptions() if err != nil { @@ -93,8 +50,10 @@ func runCmd(c *cli.Context) error { } // Gather up the options for NewContainer which consist of With... funcs - options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false)) + options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, true)) options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel)) + options = append(options, libpod.WithLabels(createConfig.Labels)) + options = append(options, libpod.WithUser(createConfig.User)) options = append(options, libpod.WithShmDir(createConfig.ShmDir)) ctr, err := runtime.NewContainer(runtimeSpec, options...) if err != nil { @@ -107,6 +66,16 @@ func runCmd(c *cli.Context) error { } logrus.Debugf("container storage created for %q", ctr.ID()) + createConfigJSON, err := json.Marshal(createConfig) + if err != nil { + return err + } + if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil { + return err + } + + logrus.Debug("new container created ", ctr.ID()) + if c.String("cidfile") != "" { libpod.WriteFile(ctr.ID(), c.String("cidfile")) return nil diff --git a/cmd/podman/spec.go b/cmd/podman/spec.go index e000467e2..96eb2f6ee 100644 --- a/cmd/podman/spec.go +++ b/cmd/podman/spec.go @@ -193,9 +193,6 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { g.SetProcessCwd(config.WorkDir) g.SetProcessArgs(config.Command) g.SetProcessTerminal(config.Tty) - // User and Group must go together - g.SetProcessUID(config.User) - g.SetProcessGID(config.Group) for _, gid := range config.GroupAdd { g.AddProcessAdditionalGid(gid) } -- cgit v1.2.3-54-g00ecf