diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/handlers/libpod/images.go | 2 | ||||
-rw-r--r-- | pkg/domain/entities/engine_image.go | 3 | ||||
-rw-r--r-- | pkg/domain/entities/manifest.go | 15 | ||||
-rw-r--r-- | pkg/domain/infra/abi/images.go | 11 | ||||
-rw-r--r-- | pkg/domain/infra/abi/manifest.go | 101 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/manifest.go | 63 | ||||
-rw-r--r-- | pkg/specgen/generate/storage.go | 814 | ||||
-rw-r--r-- | pkg/specgen/specgen.go | 17 |
8 files changed, 203 insertions, 823 deletions
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 46401e4f2..760ab1b7c 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -443,7 +443,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { nil, util.PullImageAlways) if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "error pulling image %q", query.Reference)) + utils.InternalServerError(w, err) return } res = append(res, handlers.LibpodImagesPullReport{ID: newImage.ID()}) diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index 9ba733162..b118a4104 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -26,4 +26,7 @@ type ImageEngine interface { Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error Tree(ctx context.Context, nameOrId string, options ImageTreeOptions) (*ImageTreeReport, error) Untag(ctx context.Context, nameOrId string, tags []string, options ImageUntagOptions) error + ManifestCreate(ctx context.Context, names, images []string, opts ManifestCreateOptions) (string, error) + ManifestInspect(ctx context.Context, name string) ([]byte, error) + ManifestAdd(ctx context.Context, opts ManifestAddOptions) (string, error) } diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go new file mode 100644 index 000000000..a9c961f9d --- /dev/null +++ b/pkg/domain/entities/manifest.go @@ -0,0 +1,15 @@ +package entities + +type ManifestCreateOptions struct { + All bool `schema:"all"` +} + +type ManifestAddOptions struct { + All bool `json:"all" schema:"all"` + Annotation []string `json:"annotation" schema:"annotation"` + Arch string `json:"arch" schema:"arch"` + Features []string `json:"features" schema:"features"` + Images []string `json:"images" schema:"images"` + OSVersion string `json:"os_version" schema:"os_version"` + Variant string `json:"variant" schema:"variant"` +} diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index bed59b379..64d9c9f12 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -101,7 +101,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti if imageRef.Transport().Name() == dockerarchive.Transport.Name() { newImage, err := ir.Libpod.ImageRuntime().LoadFromArchiveReference(ctx, imageRef, options.SignaturePolicy, writer) if err != nil { - return nil, errors.Wrapf(err, "error pulling image %q", rawImage) + return nil, err } return &entities.ImagePullReport{Images: []string{newImage[0].ID()}}, nil } @@ -125,7 +125,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti if !options.AllTags { newImage, err := ir.Libpod.ImageRuntime().New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) if err != nil { - return nil, errors.Wrapf(err, "error pulling image %q", rawImage) + return nil, err } return &entities.ImagePullReport{Images: []string{newImage.ID()}}, nil } @@ -166,7 +166,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti } if len(tags) != len(foundIDs) { - return nil, errors.Errorf("error pulling image %q", rawImage) + return nil, err } return &entities.ImagePullReport{Images: foundIDs}, nil } @@ -466,7 +466,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie }() // deleteImage is an anonymous function to conveniently delete an image - // withouth having to pass all local data around. + // without having to pass all local data around. deleteImage := func(img *image.Image) error { results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force) switch errors.Cause(err) { @@ -476,6 +476,9 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie inUseErrors = true // Important for exit codes in Podman. return errors.New( fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID())) + case define.ErrImageInUse: + inUseErrors = true + return err default: otherErrors = true // Important for exit codes in Podman. return err diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go new file mode 100644 index 000000000..27d4bf9a5 --- /dev/null +++ b/pkg/domain/infra/abi/manifest.go @@ -0,0 +1,101 @@ +// +build ABISupport + +package abi + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + buildahUtil "github.com/containers/buildah/util" + "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/transports/alltransports" + libpodImage "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + + "github.com/pkg/errors" +) + +// ManifestCreate implements logic for creating manifest lists via ImageEngine +func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) { + fullNames, err := buildahUtil.ExpandNames(names, "", ir.Libpod.SystemContext(), ir.Libpod.GetStore()) + if err != nil { + return "", errors.Wrapf(err, "error encountered while expanding image name %q", names) + } + imageID, err := libpodImage.CreateManifestList(ir.Libpod.ImageRuntime(), *ir.Libpod.SystemContext(), fullNames, images, opts.All) + if err != nil { + return imageID, err + } + return imageID, err +} + +// ManifestInspect returns the content of a manifest list or image +func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) { + dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) + _, err := alltransports.ParseImageName(name) + if err != nil { + _, err = alltransports.ParseImageName(dockerPrefix + name) + if err != nil { + return nil, errors.Errorf("invalid image reference %q", name) + } + } + image, err := ir.Libpod.ImageRuntime().New(ctx, name, "", "", nil, nil, libpodImage.SigningOptions{}, nil, util.PullImageMissing) + if err != nil { + return nil, errors.Wrapf(err, "reading image %q", name) + } + + list, err := image.InspectManifest() + if err != nil { + return nil, errors.Wrapf(err, "loading manifest %q", name) + } + buf, err := json.MarshalIndent(list, "", " ") + if err != nil { + return buf, errors.Wrapf(err, "error rendering manifest for display") + } + return buf, nil +} + +// ManifestAdd adds images to the manifest list +func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) { + imageSpec := opts.Images[0] + listImageSpec := opts.Images[1] + dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) + _, err := alltransports.ParseImageName(imageSpec) + if err != nil { + _, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, imageSpec)) + if err != nil { + return "", errors.Errorf("invalid image reference %q", imageSpec) + } + } + listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(listImageSpec) + if err != nil { + return "", errors.Wrapf(err, "error retriving local image from image name %s", listImageSpec) + } + + manifestAddOpts := libpodImage.ManifestAddOpts{ + All: opts.All, + Arch: opts.Arch, + Features: opts.Features, + Images: opts.Images, + OSVersion: opts.OSVersion, + Variant: opts.Variant, + } + if len(opts.Annotation) != 0 { + annotations := make(map[string]string) + for _, annotationSpec := range opts.Annotation { + spec := strings.SplitN(annotationSpec, "=", 2) + if len(spec) != 2 { + return "", errors.Errorf("no value given for annotation %q", spec[0]) + } + annotations[spec[0]] = spec[1] + } + manifestAddOpts.Annotation = annotations + } + listID, err := listImage.AddManifest(*ir.Libpod.SystemContext(), manifestAddOpts) + if err != nil { + return listID, err + } + return listID, nil +} diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go new file mode 100644 index 000000000..338256530 --- /dev/null +++ b/pkg/domain/infra/tunnel/manifest.go @@ -0,0 +1,63 @@ +package tunnel + +import ( + "context" + "encoding/json" + "strings" + + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/bindings/manifests" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" +) + +// ManifestCreate implements manifest create via ImageEngine +func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) { + imageID, err := manifests.Create(ir.ClientCxt, names, images, &opts.All) + if err != nil { + return imageID, errors.Wrapf(err, "error creating manifest") + } + return imageID, err +} + +// ManifestInspect returns contents of manifest list with given name +func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) { + list, err := manifests.Inspect(ir.ClientCxt, name) + if err != nil { + return nil, errors.Wrapf(err, "error getting content of manifest list or image %s", name) + } + + buf, err := json.MarshalIndent(list, "", " ") + if err != nil { + return buf, errors.Wrapf(err, "error rendering manifest for display") + } + return buf, err +} + +// ManifestAdd adds images to the manifest list +func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) { + manifestAddOpts := image.ManifestAddOpts{ + All: opts.All, + Arch: opts.Arch, + Features: opts.Features, + Images: opts.Images, + OSVersion: opts.OSVersion, + Variant: opts.Variant, + } + if len(opts.Annotation) != 0 { + annotations := make(map[string]string) + for _, annotationSpec := range opts.Annotation { + spec := strings.SplitN(annotationSpec, "=", 2) + if len(spec) != 2 { + return "", errors.Errorf("no value given for annotation %q", spec[0]) + } + annotations[spec[0]] = spec[1] + } + manifestAddOpts.Annotation = annotations + } + listID, err := manifests.Add(ctx, opts.Images[1], manifestAddOpts) + if err != nil { + return listID, errors.Wrapf(err, "error adding to manifest list %s", opts.Images[1]) + } + return listID, nil +} diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go index c9a36ed46..7650e4e9a 100644 --- a/pkg/specgen/generate/storage.go +++ b/pkg/specgen/generate/storage.go @@ -1,22 +1,16 @@ package generate -//nolint - import ( - "fmt" "path" "path/filepath" "strings" - "github.com/containers/buildah/pkg/parse" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) +// TODO unify this in one place - maybe libpod/define const ( // TypeBind is the type for mounting host dir TypeBind = "bind" @@ -26,812 +20,6 @@ const ( TypeTmpfs = "tmpfs" ) -var ( - errDuplicateDest = errors.Errorf("duplicate mount destination") //nolint - optionArgError = errors.Errorf("must provide an argument for option") //nolint - noDestError = errors.Errorf("must set volume destination") //nolint -) - -// Parse all volume-related options in the create config into a set of mounts -// and named volumes to add to the container. -// Handles --volumes-from, --volumes, --tmpfs, --init, and --init-path flags. -// TODO: Named volume options - should we default to rprivate? It bakes into a -// bind mount under the hood... -// TODO: handle options parsing/processing via containers/storage/pkg/mount -func parseVolumes(s *specgen.SpecGenerator, mounts, volMounts, tmpMounts []string) error { //nolint - - // TODO this needs to come from the image and erquires a runtime - - // Add image volumes. - //baseMounts, baseVolumes, err := config.getImageVolumes() - //if err != nil { - // return nil, nil, err - //} - - // Add --volumes-from. - // Overrides image volumes unconditionally. - //vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime) - //if err != nil { - // return nil, nil, err - //} - //for dest, mount := range vFromMounts { - // baseMounts[dest] = mount - //} - //for dest, volume := range vFromVolumes { - // baseVolumes[dest] = volume - //} - - // Next mounts from the --mounts flag. - // Do not override yet. - //unifiedMounts, _, err := getMounts(mounts) - //if err != nil { - // return err - //} - // - //// Next --volumes flag. - //// Do not override yet. - //volumeMounts, _ , err := getVolumeMounts(volMounts) - //if err != nil { - // return err - //} - // - //// Next --tmpfs flag. - //// Do not override yet. - //tmpfsMounts, err := getTmpfsMounts(tmpMounts) - //if err != nil { - // return err - //} - - //// Unify mounts from --mount, --volume, --tmpfs. - //// Also add mounts + volumes directly from createconfig. - //// Start with --volume. - //for dest, mount := range volumeMounts { - // if _, ok := unifiedMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedMounts[dest] = mount - //} - //for dest, volume := range volumeVolumes { - // if _, ok := unifiedVolumes[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedVolumes[dest] = volume - //} - //// Now --tmpfs - //for dest, tmpfs := range tmpfsMounts { - // if _, ok := unifiedMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedMounts[dest] = tmpfs - //} - //// Now spec mounts and volumes - //for _, mount := range config.Mounts { - // dest := mount.Destination - // if _, ok := unifiedMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedMounts[dest] = mount - //} - //for _, volume := range config.NamedVolumes { - // dest := volume.Dest - // if _, ok := unifiedVolumes[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedVolumes[dest] = volume - //} - // - //// If requested, add container init binary - //if config.Init { - // initPath := config.InitPath - // if initPath == "" { - // rtc, err := runtime.GetConfig() - // if err != nil { - // return nil, nil, err - // } - // initPath = rtc.Engine.InitPath - // } - // initMount, err := config.addContainerInitBinary(initPath) - // if err != nil { - // return nil, nil, err - // } - // if _, ok := unifiedMounts[initMount.Destination]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination) - // } - // unifiedMounts[initMount.Destination] = initMount - //} - // - //// Before superseding, we need to find volume mounts which conflict with - //// named volumes, and vice versa. - //// We'll delete the conflicts here as we supersede. - //for dest := range unifiedMounts { - // if _, ok := baseVolumes[dest]; ok { - // delete(baseVolumes, dest) - // } - //} - //for dest := range unifiedVolumes { - // if _, ok := baseMounts[dest]; ok { - // delete(baseMounts, dest) - // } - //} - // - //// Supersede volumes-from/image volumes with unified volumes from above. - //// This is an unconditional replacement. - //for dest, mount := range unifiedMounts { - // baseMounts[dest] = mount - //} - //for dest, volume := range unifiedVolumes { - // baseVolumes[dest] = volume - //} - // - //// If requested, add tmpfs filesystems for read-only containers. - //if config.Security.ReadOnlyRootfs && config.Security.ReadOnlyTmpfs { - // readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"} - // options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} - // for _, dest := range readonlyTmpfs { - // if _, ok := baseMounts[dest]; ok { - // continue - // } - // if _, ok := baseVolumes[dest]; ok { - // continue - // } - // localOpts := options - // if dest == "/run" { - // localOpts = append(localOpts, "noexec", "size=65536k") - // } else { - // localOpts = append(localOpts, "exec") - // } - // baseMounts[dest] = spec.Mount{ - // Destination: dest, - // Type: "tmpfs", - // Source: "tmpfs", - // Options: localOpts, - // } - // } - //} - // - //// Check for conflicts between named volumes and mounts - //for dest := range baseMounts { - // if _, ok := baseVolumes[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) - // } - //} - //for dest := range baseVolumes { - // if _, ok := baseMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) - // } - //} - // - //// Final step: maps to arrays - //finalMounts := make([]spec.Mount, 0, len(baseMounts)) - //for _, mount := range baseMounts { - // if mount.Type == TypeBind { - // absSrc, err := filepath.Abs(mount.Source) - // if err != nil { - // return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) - // } - // mount.Source = absSrc - // } - // finalMounts = append(finalMounts, mount) - //} - //finalVolumes := make([]*define.ContainerNamedVolume, 0, len(baseVolumes)) - //for _, volume := range baseVolumes { - // finalVolumes = append(finalVolumes, volume) - //} - - //return finalMounts, finalVolumes, nil - return nil -} - -// Parse volumes from - a set of containers whose volumes we will mount in. -// Grab the containers, retrieve any user-created spec mounts and all named -// volumes, and return a list of them. -// Conflicts are resolved simply - the last container specified wins. -// Container names may be suffixed by mount options after a colon. -// TODO: We should clean these paths if possible -// TODO deferred baude -func getVolumesFrom() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - // Both of these are maps of mount destination to mount type. - // We ensure that each destination is only mounted to once in this way. - //finalMounts := make(map[string]spec.Mount) - //finalNamedVolumes := make(map[string]*define.ContainerNamedVolume) - // - //for _, vol := range config.VolumesFrom { - // var ( - // options = []string{} - // err error - // splitVol = strings.SplitN(vol, ":", 2) - // ) - // if len(splitVol) == 2 { - // splitOpts := strings.Split(splitVol[1], ",") - // for _, checkOpt := range splitOpts { - // switch checkOpt { - // case "z", "ro", "rw": - // // Do nothing, these are valid options - // default: - // return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z'", splitVol[1]) - // } - // } - // - // if options, err = parse.ValidateVolumeOpts(splitOpts); err != nil { - // return nil, nil, err - // } - // } - // ctr, err := runtime.LookupContainer(splitVol[0]) - // if err != nil { - // return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0]) - // } - // - // logrus.Debugf("Adding volumes from container %s", ctr.ID()) - // - // // Look up the container's user volumes. This gets us the - // // destinations of all mounts the user added to the container. - // userVolumesArr := ctr.UserVolumes() - // - // // We're going to need to access them a lot, so convert to a map - // // to reduce looping. - // // We'll also use the map to indicate if we missed any volumes along the way. - // userVolumes := make(map[string]bool) - // for _, dest := range userVolumesArr { - // userVolumes[dest] = false - // } - // - // // Now we get the container's spec and loop through its volumes - // // and append them in if we can find them. - // spec := ctr.Spec() - // if spec == nil { - // return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID()) - // } - // for _, mnt := range spec.Mounts { - // if mnt.Type != TypeBind { - // continue - // } - // if _, exists := userVolumes[mnt.Destination]; exists { - // userVolumes[mnt.Destination] = true - // - // if len(options) != 0 { - // mnt.Options = options - // } - // - // if _, ok := finalMounts[mnt.Destination]; ok { - // logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID()) - // } - // finalMounts[mnt.Destination] = mnt - // } - // } - // - // // We're done with the spec mounts. Add named volumes. - // // Add these unconditionally - none of them are automatically - // // part of the container, as some spec mounts are. - // namedVolumes := ctr.NamedVolumes() - // for _, namedVol := range namedVolumes { - // if _, exists := userVolumes[namedVol.Dest]; exists { - // userVolumes[namedVol.Dest] = true - // } - // - // if len(options) != 0 { - // namedVol.Options = options - // } - // - // if _, ok := finalMounts[namedVol.Dest]; ok { - // logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID()) - // } - // finalNamedVolumes[namedVol.Dest] = namedVol - // } - // - // // Check if we missed any volumes - // for volDest, found := range userVolumes { - // if !found { - // logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) - // } - // } - //} - // - //return finalMounts, finalNamedVolumes, nil - return nil, nil, nil -} - -// getMounts takes user-provided input from the --mount flag and creates OCI -// spec mounts and Libpod named volumes. -// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... -// podman run --mount type=tmpfs,target=/dev/shm ... -// podman run --mount type=volume,source=test-volume, ... -func getMounts(mounts []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - finalMounts := make(map[string]spec.Mount) - finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume) - - errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]") - - // TODO(vrothberg): the manual parsing can be replaced with a regular expression - // to allow a more robust parsing of the mount format and to give - // precise errors regarding supported format versus supported options. - for _, mount := range mounts { - arr := strings.SplitN(mount, ",", 2) - if len(arr) < 2 { - return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) - } - kv := strings.Split(arr[0], "=") - // TODO: type is not explicitly required in Docker. - // If not specified, it defaults to "volume". - if len(kv) != 2 || kv[0] != "type" { - return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) - } - - tokens := strings.Split(arr[1], ",") - switch kv[1] { - case TypeBind: - mount, err := getBindMount(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) - } - finalMounts[mount.Destination] = mount - case TypeTmpfs: - mount, err := getTmpfsMount(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) - } - finalMounts[mount.Destination] = mount - case "volume": - volume, err := getNamedVolume(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalNamedVolumes[volume.Dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest) - } - finalNamedVolumes[volume.Dest] = volume - default: - return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1]) - } - } - - return finalMounts, finalNamedVolumes, nil -} - -// Parse a single bind mount entry from the --mount flag. -func getBindMount(args []string) (spec.Mount, error) { //nolint - newMount := spec.Mount{ - Type: TypeBind, - } - - var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool - - for _, val := range args { - kv := strings.Split(val, "=") - switch kv[0] { - case "bind-nonrecursive": - newMount.Options = append(newMount.Options, "bind") - case "ro", "rw": - if setRORW { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' or 'rw' options more than once") - } - setRORW = true - // Can be formatted as one of: - // ro - // ro=[true|false] - // rw - // rw=[true|false] - switch len(kv) { - case 1: - newMount.Options = append(newMount.Options, kv[0]) - case 2: - switch strings.ToLower(kv[1]) { - case "true": - newMount.Options = append(newMount.Options, kv[0]) - case "false": - // Set the opposite only for rw - // ro's opposite is the default - if kv[0] == "rw" { - newMount.Options = append(newMount.Options, "ro") - } - default: - return newMount, errors.Wrapf(optionArgError, "%s must be set to true or false, instead received %q", kv[0], kv[1]) - } - default: - return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val) - } - case "nosuid", "suid": - if setSuid { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newMount.Options = append(newMount.Options, kv[0]) - case "nodev", "dev": - if setDev { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newMount.Options = append(newMount.Options, kv[0]) - case "noexec", "exec": - if setExec { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newMount.Options = append(newMount.Options, kv[0]) - case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z": - newMount.Options = append(newMount.Options, kv[0]) - case "bind-propagation": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, kv[1]) - case "src", "source": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeHostDir(kv[1]); err != nil { - return newMount, err - } - newMount.Source = kv[1] - setSource = true - case "target", "dst", "destination": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return newMount, err - } - newMount.Destination = filepath.Clean(kv[1]) - setDest = true - case "relabel": - if setRelabel { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once") - } - setRelabel = true - if len(kv) != 2 { - return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) - } - switch kv[1] { - case "private": - newMount.Options = append(newMount.Options, "z") - case "shared": - newMount.Options = append(newMount.Options, "Z") - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) - } - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) - } - } - - if !setDest { - return newMount, noDestError - } - - if !setSource { - newMount.Source = newMount.Destination - } - - options, err := parse.ValidateVolumeOpts(newMount.Options) - if err != nil { - return newMount, err - } - newMount.Options = options - return newMount, nil -} - -// Parse a single tmpfs mount entry from the --mount flag -func getTmpfsMount(args []string) (spec.Mount, error) { //nolint - newMount := spec.Mount{ - Type: TypeTmpfs, - Source: TypeTmpfs, - } - - var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool - - for _, val := range args { - kv := strings.Split(val, "=") - switch kv[0] { - case "tmpcopyup", "notmpcopyup": - if setTmpcopyup { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once") - } - setTmpcopyup = true - newMount.Options = append(newMount.Options, kv[0]) - case "ro", "rw": - if setRORW { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") - } - setRORW = true - newMount.Options = append(newMount.Options, kv[0]) - case "nosuid", "suid": - if setSuid { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newMount.Options = append(newMount.Options, kv[0]) - case "nodev", "dev": - if setDev { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newMount.Options = append(newMount.Options, kv[0]) - case "noexec", "exec": - if setExec { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newMount.Options = append(newMount.Options, kv[0]) - case "tmpfs-mode": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1])) - case "tmpfs-size": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1])) - case "src", "source": - return newMount, errors.Errorf("source is not supported with tmpfs mounts") - case "target", "dst", "destination": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return newMount, err - } - newMount.Destination = filepath.Clean(kv[1]) - setDest = true - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) - } - } - - if !setDest { - return newMount, noDestError - } - - return newMount, nil -} - -// Parse a single volume mount entry from the --mount flag. -// Note that the volume-label option for named volumes is currently NOT supported. -// TODO: add support for --volume-label -func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) { //nolint - newVolume := new(libpod.ContainerNamedVolume) - - var setSource, setDest, setRORW, setSuid, setDev, setExec bool - - for _, val := range args { - kv := strings.Split(val, "=") - switch kv[0] { - case "ro", "rw": - if setRORW { - return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") - } - setRORW = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "nosuid", "suid": - if setSuid { - return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "nodev", "dev": - if setDev { - return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "noexec", "exec": - if setExec { - return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "volume-label": - return nil, errors.Errorf("the --volume-label option is not presently implemented") - case "src", "source": - if len(kv) == 1 { - return nil, errors.Wrapf(optionArgError, kv[0]) - } - newVolume.Name = kv[1] - setSource = true - case "target", "dst", "destination": - if len(kv) == 1 { - return nil, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return nil, err - } - newVolume.Dest = filepath.Clean(kv[1]) - setDest = true - default: - return nil, errors.Wrapf(util.ErrBadMntOption, kv[0]) - } - } - - if !setSource { - return nil, errors.Errorf("must set source volume") - } - if !setDest { - return nil, noDestError - } - - return newVolume, nil -} - -func getVolumeMounts(vols []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - mounts := make(map[string]spec.Mount) - volumes := make(map[string]*libpod.ContainerNamedVolume) - - volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") - - for _, vol := range vols { - var ( - options []string - src string - dest string - err error - ) - - splitVol := strings.Split(vol, ":") - if len(splitVol) > 3 { - return nil, nil, errors.Wrapf(volumeFormatErr, vol) - } - - src = splitVol[0] - if len(splitVol) == 1 { - // This is an anonymous named volume. Only thing given - // is destination. - // Name/source will be blank, and populated by libpod. - src = "" - dest = splitVol[0] - } else if len(splitVol) > 1 { - dest = splitVol[1] - } - if len(splitVol) > 2 { - if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil { - return nil, nil, err - } - } - - // Do not check source dir for anonymous volumes - if len(splitVol) > 1 { - if err := parse.ValidateVolumeHostDir(src); err != nil { - return nil, nil, err - } - } - if err := parse.ValidateVolumeCtrDir(dest); err != nil { - return nil, nil, err - } - - cleanDest := filepath.Clean(dest) - - if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") { - // This is not a named volume - newMount := spec.Mount{ - Destination: cleanDest, - Type: string(TypeBind), - Source: src, - Options: options, - } - if _, ok := mounts[newMount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination) - } - mounts[newMount.Destination] = newMount - } else { - // This is a named volume - newNamedVol := new(libpod.ContainerNamedVolume) - newNamedVol.Name = src - newNamedVol.Dest = cleanDest - newNamedVol.Options = options - - if _, ok := volumes[newNamedVol.Dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest) - } - volumes[newNamedVol.Dest] = newNamedVol - } - - logrus.Debugf("User mount %s:%s options %v", src, dest, options) - } - - return mounts, volumes, nil -} - -// Get mounts for container's image volumes -// TODO deferred baude -func getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - //mounts := make(map[string]spec.Mount) - //volumes := make(map[string]*define.ContainerNamedVolume) - // - //if config.ImageVolumeType == "ignore" { - // return mounts, volumes, nil - //} - // - //for vol := range config.BuiltinImgVolumes { - // cleanDest := filepath.Clean(vol) - // logrus.Debugf("Adding image volume at %s", cleanDest) - // if config.ImageVolumeType == "tmpfs" { - // // Tmpfs image volumes are handled as mounts - // mount := spec.Mount{ - // Destination: cleanDest, - // Source: TypeTmpfs, - // Type: TypeTmpfs, - // Options: []string{"rprivate", "rw", "nodev", "exec"}, - // } - // mounts[cleanDest] = mount - // } else { - // // Anonymous volumes have no name. - // namedVolume := new(define.ContainerNamedVolume) - // namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"} - // namedVolume.Dest = cleanDest - // volumes[cleanDest] = namedVolume - // } - //} - // - //return mounts, volumes, nil - return nil, nil, nil -} - -// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts -func getTmpfsMounts(mounts []string) (map[string]spec.Mount, error) { //nolint - m := make(map[string]spec.Mount) - for _, i := range mounts { - // Default options if nothing passed - var options []string - spliti := strings.Split(i, ":") - destPath := spliti[0] - if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil { - return nil, err - } - if len(spliti) > 1 { - options = strings.Split(spliti[1], ",") - } - - if _, ok := m[destPath]; ok { - return nil, errors.Wrapf(errDuplicateDest, destPath) - } - - mount := spec.Mount{ - Destination: filepath.Clean(destPath), - Type: string(TypeTmpfs), - Options: options, - Source: string(TypeTmpfs), - } - m[destPath] = mount - } - return m, nil -} - -// AddContainerInitBinary adds the init binary specified by path iff the -// container will run in a private PID namespace that is not shared with the -// host or another pre-existing container, where an init-like process is -// already running. -// -// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command -// to execute the bind-mounted binary as PID 1. -// TODO this needs to be worked on to work in new env -func addContainerInitBinary(path string) (spec.Mount, error) { //nolint - mount := spec.Mount{ - Destination: "/dev/init", - Type: TypeBind, - Source: path, - Options: []string{TypeBind, "ro"}, - } - - //if path == "" { - // return mount, fmt.Errorf("please specify a path to the container-init binary") - //} - //if !config.Pid.PidMode.IsPrivate() { - // return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)") - //} - //if config.Systemd { - // return mount, fmt.Errorf("cannot use container-init binary with systemd") - //} - //if _, err := os.Stat(path); os.IsNotExist(err) { - // return mount, errors.Wrap(err, "container-init binary not found on the host") - //} - //config.Command = append([]string{"/dev/init", "--"}, config.Command...) - return mount, nil -} - // Supersede existing mounts in the spec with new, user-specified mounts. // TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by // one mount, and we already have /tmp/a and /tmp/b, should we remove diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index e102a3234..37f2b3190 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -171,7 +171,7 @@ type ContainerStorageConfig struct { // These will supersede Image Volumes and VolumesFrom volumes where // there are conflicts. // Optional. - Volumes []*Volumes `json:"volumes,omitempty"` + Volumes []*NamedVolume `json:"volumes,omitempty"` // Devices are devices that will be added to the container. // Optional. Devices []spec.LinuxDevice `json:"devices,omitempty"` @@ -387,10 +387,17 @@ type SpecGenerator struct { ContainerHealthCheckConfig } -// Volumes is a temporary struct to hold input from the User -type Volumes struct { - Name string - Dest string +// NamedVolume holds information about a named volume that will be mounted into +// the container. +type NamedVolume struct { + // Name is the name of the named volume to be mounted. May be empty. + // If empty, a new named volume with a pseudorandomly generated name + // will be mounted at the given destination. + Name string + // Destination to mount the named volume within the container. Must be + // an absolute path. Path will be created if it does not exist. + Dest string + // Options are options that the named volume will be mounted with. Options []string } |