diff options
Diffstat (limited to 'cmd/podman')
-rw-r--r-- | cmd/podman/common.go | 9 | ||||
-rw-r--r-- | cmd/podman/create.go | 110 | ||||
-rw-r--r-- | cmd/podman/create_cli.go | 6 | ||||
-rw-r--r-- | cmd/podman/exec.go | 14 | ||||
-rw-r--r-- | cmd/podman/image.go | 1 | ||||
-rw-r--r-- | cmd/podman/imagefilters/filters.go | 84 | ||||
-rw-r--r-- | cmd/podman/images.go | 56 | ||||
-rw-r--r-- | cmd/podman/images_prune.go | 15 | ||||
-rw-r--r-- | cmd/podman/info.go | 12 | ||||
-rw-r--r-- | cmd/podman/libpodruntime/runtime.go | 6 | ||||
-rw-r--r-- | cmd/podman/login.go | 42 | ||||
-rw-r--r-- | cmd/podman/main.go | 21 | ||||
-rw-r--r-- | cmd/podman/play_kube.go | 2 | ||||
-rw-r--r-- | cmd/podman/shared/container.go | 18 | ||||
-rw-r--r-- | cmd/podman/shared/funcs.go | 4 | ||||
-rw-r--r-- | cmd/podman/shared/prune.go | 24 | ||||
-rw-r--r-- | cmd/podman/sign.go | 198 | ||||
-rw-r--r-- | cmd/podman/start.go | 16 | ||||
-rw-r--r-- | cmd/podman/trust.go | 58 | ||||
-rw-r--r-- | cmd/podman/varlink/io.podman.varlink | 7 |
20 files changed, 457 insertions, 246 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 0fc9a6acc..d934c8699 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -28,6 +28,10 @@ var ( Name: "latest, l", Usage: "act on the latest pod podman is aware of", } + WorkDirFlag = cli.StringFlag{ + Name: "workdir, w", + Usage: "Working directory inside the container", + } ) const ( @@ -522,10 +526,7 @@ var createFlags = []cli.Flag{ Name: "volumes-from", Usage: "Mount volumes from the specified container(s) (default [])", }, - cli.StringFlag{ - Name: "workdir, w", - Usage: "Working `directory inside the container", - }, + WorkDirFlag, } func getFormat(c *cli.Context) (string, error) { diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 395a64b3b..1aa3425a5 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io/ioutil" "os" "path/filepath" "strconv" @@ -15,13 +16,11 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" ann "github.com/containers/libpod/pkg/annotations" - "github.com/containers/libpod/pkg/apparmor" "github.com/containers/libpod/pkg/inspect" ns "github.com/containers/libpod/pkg/namespaces" "github.com/containers/libpod/pkg/rootless" cc "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/util" - libpodVersion "github.com/containers/libpod/version" "github.com/docker/docker/pkg/signal" "github.com/docker/go-connections/nat" "github.com/docker/go-units" @@ -146,7 +145,7 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container return nil, nil, err } - ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx) + ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx, nil) if err != nil { return nil, nil, err } @@ -162,83 +161,9 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container return ctr, createConfig, nil } -// Checks if a user-specified AppArmor profile is loaded, or loads the default profile if -// AppArmor is enabled. -// Any interaction with AppArmor requires root permissions. -func loadAppArmor(config *cc.CreateConfig) error { - if rootless.IsRootless() { - noAAMsg := "AppArmor security is not available in rootless mode" - switch config.ApparmorProfile { - case "": - logrus.Warn(noAAMsg) - case "unconfined": - default: - return fmt.Errorf(noAAMsg) - } - return nil - } - - if config.ApparmorProfile == "" && apparmor.IsEnabled() { - // Unless specified otherwise, make sure that the default AppArmor - // profile is installed. To avoid redundantly loading the profile - // on each invocation, check if it's loaded before installing it. - // Suffix the profile with the current libpod version to allow - // loading the new, potentially updated profile after an update. - profile := fmt.Sprintf("%s-%s", apparmor.DefaultLibpodProfile, libpodVersion.Version) - - loadProfile := func() error { - isLoaded, err := apparmor.IsLoaded(profile) - if err != nil { - return err - } - if !isLoaded { - err = apparmor.InstallDefault(profile) - if err != nil { - return err - } - - } - return nil - } - - if err := loadProfile(); err != nil { - switch err { - case apparmor.ErrApparmorUnsupported: - // do not set the profile when AppArmor isn't supported - logrus.Debugf("AppArmor is not supported: setting empty profile") - default: - return err - } - } else { - logrus.Infof("Sucessfully loaded AppAmor profile '%s'", profile) - config.ApparmorProfile = profile - } - } else if config.ApparmorProfile != "" && config.ApparmorProfile != "unconfined" { - if !apparmor.IsEnabled() { - return fmt.Errorf("Profile specified but AppArmor is disabled on the host") - } - - isLoaded, err := apparmor.IsLoaded(config.ApparmorProfile) - if err != nil { - switch err { - case apparmor.ErrApparmorUnsupported: - return fmt.Errorf("Profile specified but AppArmor is not supported") - default: - return fmt.Errorf("Error checking if AppArmor profile is loaded: %v", err) - } - } - if !isLoaded { - return fmt.Errorf("The specified AppArmor profile '%s' is not loaded", config.ApparmorProfile) - } - } - - return nil -} - func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { var ( labelOpts []string - err error ) if config.PidMode.IsHost() { @@ -283,10 +208,6 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { } } - if err := loadAppArmor(config); err != nil { - return err - } - if config.SeccompProfilePath == "" { if _, err := os.Stat(libpod.SeccompOverridePath); err == nil { config.SeccompProfilePath = libpod.SeccompOverridePath @@ -304,7 +225,7 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { } } config.LabelOpts = labelOpts - return err + return nil } // isPortInPortBindings determines if an exposed host port is in user @@ -501,6 +422,16 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim } if c.IsSet("pod") { if strings.HasPrefix(originalPodName, "new:") { + if rootless.IsRootless() { + // To create a new pod, we must immediately create the userns. + became, ret, err := rootless.BecomeRootInUserNS() + if err != nil { + return nil, err + } + if became { + os.Exit(ret) + } + } // pod does not exist; lets make it var podOptions []libpod.PodCreateOption podOptions = append(podOptions, libpod.WithPodName(podName), libpod.WithInfraContainer(), libpod.WithPodCgroups()) @@ -865,11 +796,15 @@ func joinOrCreateRootlessUserNamespace(createConfig *cc.CreateConfig, runtime *l if s != libpod.ContainerStateRunning && s != libpod.ContainerStatePaused { continue } - pid, err := prevCtr.PID() + data, err := ioutil.ReadFile(prevCtr.Config().ConmonPidFile) if err != nil { - return false, -1, err + return false, -1, errors.Wrapf(err, "cannot read conmon PID file %q", prevCtr.Config().ConmonPidFile) } - return rootless.JoinNS(uint(pid)) + conmonPid, err := strconv.Atoi(string(data)) + if err != nil { + return false, -1, errors.Wrapf(err, "cannot parse PID %q", data) + } + return rootless.JoinDirectUserAndMountNS(uint(conmonPid)) } } @@ -897,17 +832,16 @@ func joinOrCreateRootlessUserNamespace(createConfig *cc.CreateConfig, runtime *l return rootless.BecomeRootInUserNS() } -func createContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context) (*libpod.Container, error) { +func createContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context, pod *libpod.Pod) (*libpod.Container, error) { runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig) if err != nil { return nil, err } - options, err := createConfig.GetContainerCreateOptions(r) + options, err := createConfig.GetContainerCreateOptions(r, pod) if err != nil { return nil, err } - became, ret, err := joinOrCreateRootlessUserNamespace(createConfig, r) if err != nil { return nil, err diff --git a/cmd/podman/create_cli.go b/cmd/podman/create_cli.go index 1a0830f2e..95b9321fd 100644 --- a/cmd/podman/create_cli.go +++ b/cmd/podman/create_cli.go @@ -201,6 +201,9 @@ func parseVolumesFrom(volumesFrom []string) error { } func validateVolumeHostDir(hostDir string) error { + if len(hostDir) == 0 { + return errors.Errorf("host directory cannot be empty") + } if filepath.IsAbs(hostDir) { if _, err := os.Stat(hostDir); err != nil { return errors.Wrapf(err, "error checking path %q", hostDir) @@ -212,6 +215,9 @@ func validateVolumeHostDir(hostDir string) error { } func validateVolumeCtrDir(ctrDir string) error { + if len(ctrDir) == 0 { + return errors.Errorf("container directory cannot be empty") + } if !filepath.IsAbs(ctrDir) { return errors.Errorf("invalid container path, must be an absolute path %q", ctrDir) } diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go index 1dcb88dbd..073e72e64 100644 --- a/cmd/podman/exec.go +++ b/cmd/podman/exec.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os" - "strings" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" @@ -35,6 +34,7 @@ var ( Usage: "Sets the username or UID used and optionally the groupname or GID for the specified command", }, LatestFlag, + WorkDirFlag, } execDescription = ` podman exec @@ -99,15 +99,7 @@ func execCmd(c *cli.Context) error { } // ENVIRONMENT VARIABLES - env := defaultEnvVariables - for _, e := range c.StringSlice("env") { - split := strings.SplitN(e, "=", 2) - if len(split) > 1 { - env[split[0]] = split[1] - } else { - env[split[0]] = "" - } - } + env := map[string]string{} if err := readKVStrings(env, []string{}, c.StringSlice("env")); err != nil { return errors.Wrapf(err, "unable to process environment variables") @@ -117,5 +109,5 @@ func execCmd(c *cli.Context) error { envs = append(envs, fmt.Sprintf("%s=%s", k, v)) } - return ctr.Exec(c.Bool("tty"), c.Bool("privileged"), envs, cmd, c.String("user")) + return ctr.Exec(c.Bool("tty"), c.Bool("privileged"), envs, cmd, c.String("user"), c.String("workdir")) } diff --git a/cmd/podman/image.go b/cmd/podman/image.go index e978b9cf5..557fc1056 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -20,6 +20,7 @@ var ( saveCommand, tagCommand, trustCommand, + signCommand, } imageDescription = "Manage images" diff --git a/cmd/podman/imagefilters/filters.go b/cmd/podman/imagefilters/filters.go new file mode 100644 index 000000000..366510202 --- /dev/null +++ b/cmd/podman/imagefilters/filters.go @@ -0,0 +1,84 @@ +package imagefilters + +import ( + "context" + "strings" + "time" + + "github.com/containers/libpod/libpod/adapter" + "github.com/containers/libpod/pkg/inspect" +) + +// ResultFilter is a mock function for image filtering +type ResultFilter func(*adapter.ContainerImage) bool + +// Filter is a function to determine whether an image is included in +// command output. Images to be outputted are tested using the function. A true +// return will include the image, a false return will exclude it. +type Filter func(*adapter.ContainerImage, *inspect.ImageData) bool + +// CreatedBeforeFilter allows you to filter on images created before +// the given time.Time +func CreatedBeforeFilter(createTime time.Time) ResultFilter { + return func(i *adapter.ContainerImage) bool { + return i.Created().Before(createTime) + } +} + +// CreatedAfterFilter allows you to filter on images created after +// the given time.Time +func CreatedAfterFilter(createTime time.Time) ResultFilter { + return func(i *adapter.ContainerImage) bool { + return i.Created().After(createTime) + } +} + +// DanglingFilter allows you to filter images for dangling images +func DanglingFilter() ResultFilter { + return func(i *adapter.ContainerImage) bool { + return i.Dangling() + } +} + +// LabelFilter allows you to filter by images labels key and/or value +func LabelFilter(ctx context.Context, labelfilter string) ResultFilter { + // We need to handle both label=key and label=key=value + return func(i *adapter.ContainerImage) bool { + var value string + splitFilter := strings.Split(labelfilter, "=") + key := splitFilter[0] + if len(splitFilter) > 1 { + value = splitFilter[1] + } + labels, err := i.Labels(ctx) + if err != nil { + return false + } + if len(strings.TrimSpace(labels[key])) > 0 && len(strings.TrimSpace(value)) == 0 { + return true + } + return labels[key] == value + } +} + +// OutputImageFilter allows you to filter by an a specific image name +func OutputImageFilter(userImage *adapter.ContainerImage) ResultFilter { + return func(i *adapter.ContainerImage) bool { + return userImage.ID() == i.ID() + } +} + +// FilterImages filters images using a set of predefined filter funcs +func FilterImages(images []*adapter.ContainerImage, filters []ResultFilter) []*adapter.ContainerImage { + var filteredImages []*adapter.ContainerImage + for _, image := range images { + include := true + for _, filter := range filters { + include = include && filter(image) + } + if include { + filteredImages = append(filteredImages, image) + } + } + return filteredImages +} diff --git a/cmd/podman/images.go b/cmd/podman/images.go index 522863b1b..8b8ce78bd 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -2,6 +2,8 @@ package main import ( "context" + "github.com/containers/libpod/cmd/podman/imagefilters" + "github.com/containers/libpod/libpod/adapter" "reflect" "sort" "strings" @@ -9,11 +11,9 @@ import ( "unicode" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/docker/go-units" - digest "github.com/opencontainers/go-digest" + "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -145,20 +145,20 @@ var ( func imagesCmd(c *cli.Context) error { var ( - filterFuncs []image.ResultFilter - newImage *image.Image + filterFuncs []imagefilters.ResultFilter + newImage *adapter.ContainerImage ) if err := validateFlags(c, imagesFlags); err != nil { return err } - runtime, err := libpodruntime.GetRuntime(c) + localRuntime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "Could not get runtime") } - defer runtime.Shutdown(false) + defer localRuntime.Runtime.Shutdown(false) if len(c.Args()) == 1 { - newImage, err = runtime.ImageRuntime().NewFromLocal(c.Args().Get(0)) + newImage, err = localRuntime.NewImageFromLocal(c.Args().Get(0)) if err != nil { return err } @@ -171,7 +171,7 @@ func imagesCmd(c *cli.Context) error { ctx := getContext() if len(c.StringSlice("filter")) > 0 || newImage != nil { - filterFuncs, err = CreateFilterFuncs(ctx, runtime, c, newImage) + filterFuncs, err = CreateFilterFuncs(ctx, localRuntime, c, newImage) if err != nil { return err } @@ -195,20 +195,20 @@ func imagesCmd(c *cli.Context) error { children to the image once built. until buildah supports caching builds, it will not generate these intermediate images. */ - images, err := runtime.ImageRuntime().GetImages() + images, err := localRuntime.GetImages() if err != nil { return errors.Wrapf(err, "unable to get images") } - var filteredImages []*image.Image - // filter the images + var filteredImages []*adapter.ContainerImage + //filter the images if len(c.StringSlice("filter")) > 0 || newImage != nil { - filteredImages = image.FilterImages(images, filterFuncs) + filteredImages = imagefilters.FilterImages(images, filterFuncs) } else { filteredImages = images } - return generateImagesOutput(ctx, runtime, filteredImages, opts) + return generateImagesOutput(ctx, filteredImages, opts) } func (i imagesOptions) setOutputFormat() string { @@ -263,7 +263,7 @@ func sortImagesOutput(sortBy string, imagesOutput imagesSorted) imagesSorted { } // getImagesTemplateOutput returns the images information to be printed in human readable format -func getImagesTemplateOutput(ctx context.Context, runtime *libpod.Runtime, images []*image.Image, opts imagesOptions) (imagesOutput imagesSorted) { +func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerImage, opts imagesOptions) (imagesOutput imagesSorted) { for _, img := range images { // If all is false and the image doesn't have a name, check to see if the top layer of the image is a parent // to another image's top layer. If it is, then it is an intermediate image so don't print out if the --all flag @@ -319,7 +319,7 @@ func getImagesTemplateOutput(ctx context.Context, runtime *libpod.Runtime, image } // getImagesJSONOutput returns the images information in its raw form -func getImagesJSONOutput(ctx context.Context, runtime *libpod.Runtime, images []*image.Image) (imagesOutput []imagesJSONParams) { +func getImagesJSONOutput(ctx context.Context, images []*adapter.ContainerImage) (imagesOutput []imagesJSONParams) { for _, img := range images { size, err := img.Size(ctx) if err != nil { @@ -339,7 +339,7 @@ func getImagesJSONOutput(ctx context.Context, runtime *libpod.Runtime, images [] // generateImagesOutput generates the images based on the format provided -func generateImagesOutput(ctx context.Context, runtime *libpod.Runtime, images []*image.Image, opts imagesOptions) error { +func generateImagesOutput(ctx context.Context, images []*adapter.ContainerImage, opts imagesOptions) error { if len(images) == 0 { return nil } @@ -347,10 +347,10 @@ func generateImagesOutput(ctx context.Context, runtime *libpod.Runtime, images [ switch opts.format { case formats.JSONString: - imagesOutput := getImagesJSONOutput(ctx, runtime, images) + imagesOutput := getImagesJSONOutput(ctx, images) out = formats.JSONStructArray{Output: imagesToGeneric([]imagesTemplateParams{}, imagesOutput)} default: - imagesOutput := getImagesTemplateOutput(ctx, runtime, images, opts) + imagesOutput := getImagesTemplateOutput(ctx, images, opts) out = formats.StdoutTemplateArray{Output: imagesToGeneric(imagesOutput, []imagesJSONParams{}), Template: opts.outputformat, Fields: imagesOutput[0].HeaderMap()} } return formats.Writer(out).Out() @@ -375,34 +375,34 @@ func (i *imagesTemplateParams) HeaderMap() map[string]string { // CreateFilterFuncs returns an array of filter functions based on the user inputs // and is later used to filter images for output -func CreateFilterFuncs(ctx context.Context, r *libpod.Runtime, c *cli.Context, img *image.Image) ([]image.ResultFilter, error) { - var filterFuncs []image.ResultFilter +func CreateFilterFuncs(ctx context.Context, r *adapter.LocalRuntime, c *cli.Context, img *adapter.ContainerImage) ([]imagefilters.ResultFilter, error) { + var filterFuncs []imagefilters.ResultFilter for _, filter := range c.StringSlice("filter") { splitFilter := strings.Split(filter, "=") switch splitFilter[0] { case "before": - before, err := r.ImageRuntime().NewFromLocal(splitFilter[1]) + before, err := r.NewImageFromLocal(splitFilter[1]) if err != nil { return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } - filterFuncs = append(filterFuncs, image.CreatedBeforeFilter(before.Created())) + filterFuncs = append(filterFuncs, imagefilters.CreatedBeforeFilter(before.Created())) case "after": - after, err := r.ImageRuntime().NewFromLocal(splitFilter[1]) + after, err := r.NewImageFromLocal(splitFilter[1]) if err != nil { return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } - filterFuncs = append(filterFuncs, image.CreatedAfterFilter(after.Created())) + filterFuncs = append(filterFuncs, imagefilters.CreatedAfterFilter(after.Created())) case "dangling": - filterFuncs = append(filterFuncs, image.DanglingFilter()) + filterFuncs = append(filterFuncs, imagefilters.DanglingFilter()) case "label": labelFilter := strings.Join(splitFilter[1:], "=") - filterFuncs = append(filterFuncs, image.LabelFilter(ctx, labelFilter)) + filterFuncs = append(filterFuncs, imagefilters.LabelFilter(ctx, labelFilter)) default: return nil, errors.Errorf("invalid filter %s ", splitFilter[0]) } } if img != nil { - filterFuncs = append(filterFuncs, image.OutputImageFilter(img)) + filterFuncs = append(filterFuncs, imagefilters.OutputImageFilter(img)) } return filterFuncs, nil } diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go index cb72a498f..06879e02d 100644 --- a/cmd/podman/images_prune.go +++ b/cmd/podman/images_prune.go @@ -1,8 +1,8 @@ package main import ( + "fmt" "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/cmd/podman/shared" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -30,5 +30,16 @@ func pruneImagesCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - return shared.Prune(runtime.ImageRuntime()) + pruneImages, err := runtime.ImageRuntime().GetPruneImages() + if err != nil { + return err + } + + for _, i := range pruneImages { + if err := i.Remove(true); err != nil { + return errors.Wrapf(err, "failed to remove %s", i.ID()) + } + fmt.Println(i.ID()) + } + return nil } diff --git a/cmd/podman/info.go b/cmd/podman/info.go index c0639725e..4b80f94db 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -1,10 +1,10 @@ package main import ( + "github.com/containers/libpod/libpod/adapter" "runtime" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/urfave/cli" @@ -39,18 +39,20 @@ func infoCmd(c *cli.Context) error { } info := map[string]interface{}{} - runtime, err := libpodruntime.GetRuntime(c) + localRuntime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } - defer runtime.Shutdown(false) + defer localRuntime.Runtime.Shutdown(false) - infoArr, err := runtime.Info() + infoArr, err := localRuntime.Runtime.Info() if err != nil { return errors.Wrapf(err, "error getting info") } - if c.Bool("debug") { + // TODO This is no a problem child because we don't know if we should add information + // TODO about the client or the backend. Only do for traditional podman for now. + if !localRuntime.Remote && c.Bool("debug") { debugInfo := debugInfo(c) infoArr = append(infoArr, libpod.InfoData{Type: "debug", Data: debugInfo}) } diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index d7a0dd931..dca2f5022 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -4,17 +4,15 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" - "github.com/containers/storage" "github.com/pkg/errors" "github.com/urfave/cli" ) // GetRuntime generates a new libpod runtime configured by command line options func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { - storageOpts := new(storage.StoreOptions) options := []libpod.RuntimeOption{} - _, volumePath, err := util.GetDefaultStoreOptions() + storageOpts, volumePath, err := util.GetDefaultStoreOptions() if err != nil { return nil, err } @@ -44,7 +42,7 @@ func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { storageOpts.GraphDriverOptions = c.GlobalStringSlice("storage-opt") } - options = append(options, libpod.WithStorageConfig(*storageOpts)) + options = append(options, libpod.WithStorageConfig(storageOpts)) // TODO CLI flags for image config? // TODO CLI flag for signature policy? diff --git a/cmd/podman/login.go b/cmd/podman/login.go index 4452651f8..fc7b39ed8 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -62,12 +62,18 @@ func loginCmd(c *cli.Context) error { return errors.Errorf("too many arguments, login takes only 1 argument") } if len(args) == 0 { - return errors.Errorf("registry must be given") + return errors.Errorf("please specify a registry to login to") } server := registryFromFullName(scrubServer(args[0])) authfile := getAuthFile(c.String("authfile")) sc := common.GetSystemContext("", authfile, false) + if c.IsSet("tls-verify") { + sc.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + } + if c.String("cert-dir") != "" { + sc.DockerCertPath = c.String("cert-dir") + } if c.IsSet("get-login") { user, err := config.GetUserLoggedIn(sc, server) @@ -87,39 +93,25 @@ func loginCmd(c *cli.Context) error { // username of user logged in to server (if one exists) userFromAuthFile, passFromAuthFile, err := config.GetAuthentication(sc, server) if err != nil { - return errors.Wrapf(err, "error getting logged-in user") + return errors.Wrapf(err, "error reading auth file") } ctx := getContext() - - var ( - username string - password string - ) - - if userFromAuthFile != "" { - username = userFromAuthFile - password = passFromAuthFile + // If no username and no password is specified, try to use existing ones. + if c.String("username") == "" && c.String("password") == "" { fmt.Println("Authenticating with existing credentials...") - if err := docker.CheckAuth(ctx, sc, username, password, server); err == nil { + if err := docker.CheckAuth(ctx, sc, userFromAuthFile, passFromAuthFile, server); err == nil { fmt.Println("Existing credentials are valid. Already logged in to", server) return nil } fmt.Println("Existing credentials are invalid, please enter valid username and password") } - username, password, err = getUserAndPass(c.String("username"), c.String("password"), userFromAuthFile) + username, password, err := getUserAndPass(c.String("username"), c.String("password"), userFromAuthFile) if err != nil { return errors.Wrapf(err, "error getting username and password") } - if c.IsSet("tls-verify") { - sc.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) - } - if c.String("cert-dir") != "" { - sc.DockerCertPath = c.String("cert-dir") - } - if err = docker.CheckAuth(ctx, sc, username, password, server); err == nil { // Write the new credentials to the authfile if err = config.SetAuthentication(sc, server, username, password); err != nil { @@ -131,14 +123,15 @@ func loginCmd(c *cli.Context) error { fmt.Println("Login Succeeded!") return nil case docker.ErrUnauthorizedForCredentials: - return errors.Errorf("error logging into %q: invalid username/password\n", server) + return errors.Errorf("error logging into %q: invalid username/password", server) default: return errors.Wrapf(err, "error authenticating creds for %q", server) } } // getUserAndPass gets the username and password from STDIN if not given -// using the -u and -p flags +// using the -u and -p flags. If the username prompt is left empty, the +// displayed userFromAuthFile will be used instead. func getUserAndPass(username, password, userFromAuthFile string) (string, string, error) { var err error reader := bufio.NewReader(os.Stdin) @@ -152,7 +145,10 @@ func getUserAndPass(username, password, userFromAuthFile string) (string, string if err != nil { return "", "", errors.Wrapf(err, "error reading username") } - // If no username provided, use userFromAuthFile instead. + // If the user just hit enter, use the displayed user from the + // the authentication file. This allows to do a lazy + // `$ podman login -p $NEW_PASSWORD` without specifying the + // user. if strings.TrimSpace(username) == "" { username = userFromAuthFile } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 7ef22a93b..604404827 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -148,19 +148,26 @@ func main() { logrus.SetLevel(level) } - // Only if not rootless, set rlimits for open files. - // We open numerous FDs for ports opened - if !rootless.IsRootless() { - rlimits := new(syscall.Rlimit) - rlimits.Cur = 1048576 - rlimits.Max = 1048576 + rlimits := new(syscall.Rlimit) + rlimits.Cur = 1048576 + rlimits.Max = 1048576 + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + return errors.Wrapf(err, "error getting rlimits") + } + rlimits.Cur = rlimits.Max if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { return errors.Wrapf(err, "error setting new rlimits") } - } else { + } + + if rootless.IsRootless() { logrus.Info("running as rootless") } + // Be sure we can create directories with 0755 mode. + syscall.Umask(0022) + if logLevel == "debug" { debug = true diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go index f165c5f0f..2ce2e21bb 100644 --- a/cmd/podman/play_kube.go +++ b/cmd/podman/play_kube.go @@ -154,7 +154,7 @@ func playKubeYAMLCmd(c *cli.Context) error { if err != nil { return err } - ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx) + ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx, pod) if err != nil { return err } diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 30beb4a49..a904ef75a 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/google/shlex" "io" "os" "path/filepath" @@ -51,7 +52,7 @@ type PsOptions struct { // BatchContainerStruct is the return obkect from BatchContainer and contains // container related information type BatchContainerStruct struct { - ConConfig *libpod.Config + ConConfig *libpod.ContainerConfig ConState libpod.ContainerStatus ExitCode int32 Exited bool @@ -328,7 +329,7 @@ func PBatch(containers []*libpod.Container, workers int, opts PsOptions) []PsCon // locks. func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStruct, error) { var ( - conConfig *libpod.Config + conConfig *libpod.ContainerConfig conState libpod.ContainerStatus err error exitCode int32 @@ -640,6 +641,14 @@ func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtim // GenerateRunlabelCommand generates the command that will eventually be execucted by podman func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string) ([]string, []string, error) { + // If no name is provided, we use the image's basename instead + if name == "" { + baseName, err := image.GetImageBaseName(imageName) + if err != nil { + return nil, nil, err + } + name = baseName + } // The user provided extra arguments that need to be tacked onto the label's command if len(extraArgs) > 0 { runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(extraArgs, " ")) @@ -665,7 +674,10 @@ func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]s return "" } newS := os.Expand(strings.Join(cmd, " "), envmapper) - cmd = strings.Split(newS, " ") + cmd, err = shlex.Split(newS) + if err != nil { + return nil, nil, err + } return cmd, env, nil } diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go index 8770b8ec0..70d041fd2 100644 --- a/cmd/podman/shared/funcs.go +++ b/cmd/podman/shared/funcs.go @@ -65,6 +65,8 @@ func GenerateCommand(command, imageName, name string) ([]string, error) { switch arg { case "IMAGE": newArg = imageName + case "$IMAGE": + newArg = imageName case "IMAGE=IMAGE": newArg = fmt.Sprintf("IMAGE=%s", imageName) case "IMAGE=$IMAGE": @@ -75,6 +77,8 @@ func GenerateCommand(command, imageName, name string) ([]string, error) { newArg = fmt.Sprintf("NAME=%s", name) case "NAME=$NAME": newArg = fmt.Sprintf("NAME=%s", name) + case "$NAME": + newArg = name default: newArg = arg } diff --git a/cmd/podman/shared/prune.go b/cmd/podman/shared/prune.go deleted file mode 100644 index 90cfe4475..000000000 --- a/cmd/podman/shared/prune.go +++ /dev/null @@ -1,24 +0,0 @@ -package shared - -import ( - "fmt" - "github.com/pkg/errors" - - "github.com/containers/libpod/libpod/image" -) - -// Prune removes all unnamed and unused images from the local store -func Prune(ir *image.Runtime) error { - pruneImages, err := ir.GetPruneImages() - if err != nil { - return err - } - - for _, i := range pruneImages { - if err := i.Remove(true); err != nil { - return errors.Wrapf(err, "failed to remove %s", i.ID()) - } - fmt.Println(i.ID()) - } - return nil -} diff --git a/cmd/podman/sign.go b/cmd/podman/sign.go new file mode 100644 index 000000000..1d9aecdc9 --- /dev/null +++ b/cmd/podman/sign.go @@ -0,0 +1,198 @@ +package main + +import ( + "io/ioutil" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/containers/image/signature" + "github.com/containers/image/transports" + "github.com/containers/image/transports/alltransports" + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/trust" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + signFlags = []cli.Flag{ + cli.StringFlag{ + Name: "sign-by", + Usage: "Name of the signing key", + }, + cli.StringFlag{ + Name: "directory, d", + Usage: "Define an alternate directory to store signatures", + }, + } + + signDescription = "Create a signature file that can be used later to verify the image" + signCommand = cli.Command{ + Name: "sign", + Usage: "Sign an image", + Description: signDescription, + Flags: sortFlags(signFlags), + Action: signCmd, + ArgsUsage: "IMAGE-NAME [IMAGE-NAME ...]", + OnUsageError: usageErrorHandler, + } +) + +// SignatureStoreDir defines default directory to store signatures +const SignatureStoreDir = "/var/lib/containers/sigstore" + +func signCmd(c *cli.Context) error { + args := c.Args() + if len(args) < 1 { + return errors.Errorf("at least one image name must be specified") + } + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not create runtime") + } + defer runtime.Shutdown(false) + + signby := c.String("sign-by") + if signby == "" { + return errors.Errorf("please provide an identity") + } + + var sigStoreDir string + if c.IsSet("directory") { + sigStoreDir = c.String("directory") + if _, err := os.Stat(sigStoreDir); err != nil { + return errors.Wrapf(err, "invalid directory %s", sigStoreDir) + } + } + + mech, err := signature.NewGPGSigningMechanism() + if err != nil { + return errors.Wrap(err, "error initializing GPG") + } + defer mech.Close() + if err := mech.SupportsSigning(); err != nil { + return errors.Wrap(err, "signing is not supported") + } + + systemRegistriesDirPath := trust.RegistriesDirPath(runtime.SystemContext()) + registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) + if err != nil { + return errors.Wrapf(err, "error reading registry configuration") + } + + for _, signimage := range args { + srcRef, err := alltransports.ParseImageName(signimage) + if err != nil { + return errors.Wrapf(err, "error parsing image name") + } + rawSource, err := srcRef.NewImageSource(getContext(), runtime.SystemContext()) + if err != nil { + return errors.Wrapf(err, "error getting image source") + } + manifest, _, err := rawSource.GetManifest(getContext(), nil) + if err != nil { + return errors.Wrapf(err, "error getting manifest") + } + dockerReference := rawSource.Reference().DockerReference() + if dockerReference == nil { + return errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) + } + + // create the signstore file + newImage, err := runtime.ImageRuntime().New(getContext(), signimage, runtime.GetConfig().SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{SignBy: signby}, false) + if err != nil { + return errors.Wrapf(err, "error pulling image %s", signimage) + } + + registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs) + if registryInfo != nil { + if sigStoreDir == "" { + sigStoreDir = registryInfo.SigStoreStaging + if sigStoreDir == "" { + sigStoreDir = registryInfo.SigStore + } + } + sigStoreDir, err = isValidSigStoreDir(sigStoreDir) + if err != nil { + return errors.Wrapf(err, "invalid signature storage %s", sigStoreDir) + } + } + if sigStoreDir == "" { + sigStoreDir = SignatureStoreDir + } + + repos, err := newImage.RepoDigests() + if err != nil { + return errors.Wrapf(err, "error calculating repo digests for %s", signimage) + } + if len(repos) == 0 { + logrus.Errorf("no repodigests associated with the image %s", signimage) + continue + } + + // create signature + newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, signby) + if err != nil { + return errors.Wrapf(err, "error creating new signature") + } + + trimmedDigest := strings.TrimPrefix(repos[0], strings.Split(repos[0], "/")[0]) + sigStoreDir = filepath.Join(sigStoreDir, strings.Replace(trimmedDigest, ":", "=", 1)) + if err := os.MkdirAll(sigStoreDir, 0751); err != nil { + // The directory is allowed to exist + if !os.IsExist(err) { + logrus.Errorf("error creating directory %s: %s", sigStoreDir, err) + continue + } + } + sigFilename, err := getSigFilename(sigStoreDir) + if err != nil { + logrus.Errorf("error creating sigstore file: %v", err) + continue + } + err = ioutil.WriteFile(filepath.Join(sigStoreDir, sigFilename), newSig, 0644) + if err != nil { + logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String()) + continue + } + } + return nil +} + +func getSigFilename(sigStoreDirPath string) (string, error) { + sigFileSuffix := 1 + sigFiles, err := ioutil.ReadDir(sigStoreDirPath) + if err != nil { + return "", err + } + sigFilenames := make(map[string]bool) + for _, file := range sigFiles { + sigFilenames[file.Name()] = true + } + for { + sigFilename := "signature-" + strconv.Itoa(sigFileSuffix) + if _, exists := sigFilenames[sigFilename]; !exists { + return sigFilename, nil + } + sigFileSuffix++ + } +} + +func isValidSigStoreDir(sigStoreDir string) (string, error) { + writeURIs := map[string]bool{"file": true} + url, err := url.Parse(sigStoreDir) + if err != nil { + return sigStoreDir, errors.Wrapf(err, "invalid directory %s", sigStoreDir) + } + _, exists := writeURIs[url.Scheme] + if !exists { + return sigStoreDir, errors.Errorf("writing to %s is not supported. Use a supported scheme", sigStoreDir) + } + sigStoreDir = url.Path + return sigStoreDir, nil +} diff --git a/cmd/podman/start.go b/cmd/podman/start.go index 8bb386c68..df34deec2 100644 --- a/cmd/podman/start.go +++ b/cmd/podman/start.go @@ -27,9 +27,9 @@ var ( Name: "interactive, i", Usage: "Keep STDIN open even if not attached", }, - cli.BoolFlag{ + cli.BoolTFlag{ Name: "sig-proxy", - Usage: "proxy received signals to the process", + Usage: "proxy received signals to the process (default true if attaching, false otherwise)", }, LatestFlag, } @@ -67,8 +67,14 @@ func startCmd(c *cli.Context) error { return err } - if c.Bool("sig-proxy") && !attach { - return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") + sigProxy := c.BoolT("sig-proxy") + + if sigProxy && !attach { + if c.IsSet("sig-proxy") { + return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") + } else { + sigProxy = false + } } runtime, err := libpodruntime.GetRuntime(c) @@ -111,7 +117,7 @@ func startCmd(c *cli.Context) error { } // attach to the container and also start it not already running - err = startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), !ctrRunning) + err = startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.String("detach-keys"), sigProxy, !ctrRunning) if ctrRunning { return err } diff --git a/cmd/podman/trust.go b/cmd/podman/trust.go index 7c404cd3f..863f36d09 100644 --- a/cmd/podman/trust.go +++ b/cmd/podman/trust.go @@ -13,7 +13,6 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/trust" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -132,7 +131,7 @@ func showTrustCmd(c *cli.Context) error { if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { return errors.Errorf("could not read trust policies") } - policyJSON, err := trust.GetPolicyJSON(policyContentStruct, systemRegistriesDirPath) + policyJSON, showOutputMap, err := trust.GetPolicy(policyContentStruct, systemRegistriesDirPath) if err != nil { return errors.Wrapf(err, "error reading registry config file") } @@ -144,31 +143,12 @@ func showTrustCmd(c *cli.Context) error { } sortedRepos := sortPolicyJSONKey(policyJSON) - type policydefault struct { - Repo string - Trusttype string - GPGid string - Sigstore string - } - var policyoutput []policydefault - for _, repo := range sortedRepos { - repoval := policyJSON[repo] - var defaultstruct policydefault - defaultstruct.Repo = repo - if repoval["type"] != nil { - defaultstruct.Trusttype = trustTypeDescription(repoval["type"].(string)) - } - if repoval["keys"] != nil && len(repoval["keys"].([]string)) > 0 { - defaultstruct.GPGid = trust.GetGPGId(repoval["keys"].([]string)) - } - if repoval["sigstore"] != nil { - defaultstruct.Sigstore = repoval["sigstore"].(string) - } - policyoutput = append(policyoutput, defaultstruct) - } var output []interface{} - for _, ele := range policyoutput { - output = append(output, interface{}(ele)) + for _, reponame := range sortedRepos { + showOutput, exists := showOutputMap[reponame] + if exists { + output = append(output, interface{}(showOutput)) + } } out := formats.StdoutTemplateArray{Output: output, Template: "{{.Repo}}\t{{.Trusttype}}\t{{.GPGid}}\t{{.Sigstore}}"} return formats.Writer(out).Out() @@ -209,8 +189,10 @@ func setTrustCmd(c *cli.Context) error { policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) } var policyContentStruct trust.PolicyContent + policyFileExists := false _, err = os.Stat(policyPath) if !os.IsNotExist(err) { + policyFileExists = true policyContent, err := ioutil.ReadFile(policyPath) if err != nil { return errors.Wrapf(err, "unable to read %s", policyPath) @@ -218,6 +200,9 @@ func setTrustCmd(c *cli.Context) error { if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { return errors.Errorf("could not read trust policies") } + if args[0] != "default" && len(policyContentStruct.Default) == 0 { + return errors.Errorf("Default trust policy must be set.") + } } var newReposContent []trust.RepoContent if len(pubkeysfile) != 0 { @@ -230,15 +215,18 @@ func setTrustCmd(c *cli.Context) error { if args[0] == "default" { policyContentStruct.Default = newReposContent } else { - exists := false + if policyFileExists == false && len(policyContentStruct.Default) == 0 { + return errors.Errorf("Default trust policy must be set to create the policy file.") + } + registryExists := false for transport, transportval := range policyContentStruct.Transports { - _, exists = transportval[args[0]] - if exists { + _, registryExists = transportval[args[0]] + if registryExists { policyContentStruct.Transports[transport][args[0]] = newReposContent break } } - if !exists { + if !registryExists { if policyContentStruct.Transports == nil { policyContentStruct.Transports = make(map[string]trust.RepoMap) } @@ -260,16 +248,6 @@ func setTrustCmd(c *cli.Context) error { return nil } -var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"} - -func trustTypeDescription(trustType string) string { - trustDescription, exist := typeDescription[trustType] - if !exist { - logrus.Warnf("invalid trust type %s", trustType) - } - return trustDescription -} - func sortPolicyJSONKey(m map[string]map[string]interface{}) []string { keys := make([]string, len(m)) i := 0 diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 4e8b69faf..a3e8c050e 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -37,7 +37,8 @@ type ImageInList ( size: int, virtualSize: int, containers: int, - labels: [string]string + labels: [string]string, + isParent: bool ) # ImageHistory describes the returned structure from ImageHistory. @@ -1015,6 +1016,10 @@ method MountContainer(name: string) -> (path: string) # ~~~ method UnmountContainer(name: string, force: bool) -> () +# ImagesPrune removes all unused images from the local store. Upon successful pruning, +# the IDs of the removed images are returned. +method ImagesPrune() -> (pruned: []string) + # This function is not implemented yet. method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) |