diff options
Diffstat (limited to 'libpod/adapter')
-rw-r--r-- | libpod/adapter/runtime.go | 157 | ||||
-rw-r--r-- | libpod/adapter/runtime_remote.go | 282 | ||||
-rw-r--r-- | libpod/adapter/volumes_remote.go | 33 |
3 files changed, 450 insertions, 22 deletions
diff --git a/libpod/adapter/runtime.go b/libpod/adapter/runtime.go index 46771b5b6..02ef9af07 100644 --- a/libpod/adapter/runtime.go +++ b/libpod/adapter/runtime.go @@ -4,18 +4,22 @@ package adapter import ( "context" - "github.com/pkg/errors" "io" "io/ioutil" "os" "strconv" + "github.com/containers/buildah" + "github.com/containers/buildah/imagebuildah" + "github.com/containers/buildah/pkg/parse" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" + "github.com/pkg/errors" ) // LocalRuntime describes a typical libpod runtime @@ -34,6 +38,14 @@ type Container struct { *libpod.Container } +// Volume ... +type Volume struct { + *libpod.Volume +} + +// VolumeFilter is for filtering volumes on the client +type VolumeFilter func(*Volume) bool + // GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it func GetRuntime(c *cliconfig.PodmanCommand) (*LocalRuntime, error) { runtime, err := libpodruntime.GetRuntime(c) @@ -155,3 +167,146 @@ func (r *LocalRuntime) Export(name string, path string) error { func (r *LocalRuntime) Import(ctx context.Context, source, reference string, changes []string, history string, quiet bool) (string, error) { return r.Runtime.Import(ctx, source, reference, changes, history, quiet) } + +// CreateVolume is a wrapper to create volumes +func (r *LocalRuntime) CreateVolume(ctx context.Context, c *cliconfig.VolumeCreateValues, labels, opts map[string]string) (string, error) { + var ( + options []libpod.VolumeCreateOption + volName string + ) + + if len(c.InputArgs) > 0 { + volName = c.InputArgs[0] + options = append(options, libpod.WithVolumeName(volName)) + } + + if c.Flag("driver").Changed { + options = append(options, libpod.WithVolumeDriver(c.Driver)) + } + + if len(labels) != 0 { + options = append(options, libpod.WithVolumeLabels(labels)) + } + + if len(options) != 0 { + options = append(options, libpod.WithVolumeOptions(opts)) + } + newVolume, err := r.NewVolume(ctx, options...) + if err != nil { + return "", err + } + return newVolume.Name(), nil +} + +// RemoveVolumes is a wrapper to remove volumes +func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, error) { + return r.Runtime.RemoveVolumes(ctx, c.InputArgs, c.All, c.Force) +} + +// Push is a wrapper to push an image to a registry +func (r *LocalRuntime) Push(ctx context.Context, srcName, destination, manifestMIMEType, authfile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions image.SigningOptions, dockerRegistryOptions *image.DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged) error { + newImage, err := r.ImageRuntime().NewFromLocal(srcName) + if err != nil { + return err + } + return newImage.PushImageToHeuristicDestination(ctx, destination, manifestMIMEType, authfile, signaturePolicyPath, writer, forceCompress, signingOptions, dockerRegistryOptions, nil) +} + +// InspectVolumes returns a slice of volumes based on an arg list or --all +func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeInspectValues) ([]*Volume, error) { + var ( + volumes []*libpod.Volume + err error + ) + + if c.All { + volumes, err = r.GetAllVolumes() + } else { + for _, v := range c.InputArgs { + vol, err := r.GetVolume(v) + if err != nil { + return nil, err + } + volumes = append(volumes, vol) + } + } + if err != nil { + return nil, err + } + return libpodVolumeToVolume(volumes), nil +} + +// Volumes returns a slice of localruntime volumes +func (r *LocalRuntime) Volumes(ctx context.Context) ([]*Volume, error) { + vols, err := r.GetAllVolumes() + if err != nil { + return nil, err + } + return libpodVolumeToVolume(vols), nil +} + +// libpodVolumeToVolume converts a slice of libpod volumes to a slice +// of localruntime volumes (same as libpod) +func libpodVolumeToVolume(volumes []*libpod.Volume) []*Volume { + var vols []*Volume + for _, v := range volumes { + newVol := Volume{ + v, + } + vols = append(vols, &newVol) + } + return vols +} + +// Build is the wrapper to build images +func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) error { + namespaceOptions, networkPolicy, err := parse.NamespaceOptions(c.PodmanCommand.Command) + if err != nil { + return errors.Wrapf(err, "error parsing namespace-related options") + } + usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command) + if err != nil { + return errors.Wrapf(err, "error parsing ID mapping options") + } + namespaceOptions.AddOrReplace(usernsOption...) + + systemContext, err := parse.SystemContextFromOptions(c.PodmanCommand.Command) + if err != nil { + return errors.Wrapf(err, "error building system context") + } + + authfile := c.Authfile + if len(c.Authfile) == 0 { + authfile = os.Getenv("REGISTRY_AUTH_FILE") + } + + systemContext.AuthFilePath = authfile + commonOpts, err := parse.CommonBuildOptions(c.PodmanCommand.Command) + if err != nil { + return err + } + + options.NamespaceOptions = namespaceOptions + options.ConfigureNetwork = networkPolicy + options.IDMappingOptions = idmappingOptions + options.CommonBuildOpts = commonOpts + options.SystemContext = systemContext + + if c.Flag("runtime").Changed { + options.Runtime = r.GetOCIRuntimePath() + } + if c.Quiet { + options.ReportWriter = ioutil.Discard + } + + if rootless.IsRootless() { + options.Isolation = buildah.IsolationOCIRootless + } + + return r.Runtime.Build(ctx, options, dockerfiles...) +} + +// PruneVolumes is a wrapper function for libpod PruneVolumes +func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) { + return r.Runtime.PruneVolumes(ctx) +} diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go index f754aaee6..f63b5875d 100644 --- a/libpod/adapter/runtime_remote.go +++ b/libpod/adapter/runtime_remote.go @@ -7,18 +7,22 @@ import ( "context" "encoding/json" "fmt" - "github.com/pkg/errors" "io" + "io/ioutil" "os" "strings" "time" + "github.com/containers/buildah/imagebuildah" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" + "github.com/containers/storage/pkg/archive" "github.com/opencontainers/go-digest" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/varlink/go/varlink" ) @@ -91,6 +95,18 @@ type remoteContainer struct { state *libpod.ContainerState } +type VolumeFilter func(*Volume) bool + +// Volume is embed for libpod volumes +type Volume struct { + remoteVolume +} + +type remoteVolume struct { + Runtime *LocalRuntime + config *libpod.VolumeConfig +} + // GetImages returns a slice of containerimages over a varlink connection func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) { var newImages []*ContainerImage @@ -112,8 +128,8 @@ func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) { return newImages, nil } -func imageInListToContainerImage(i iopodman.ImageInList, name string, runtime *LocalRuntime) (*ContainerImage, error) { - created, err := splitStringDate(i.Created) +func imageInListToContainerImage(i iopodman.Image, name string, runtime *LocalRuntime) (*ContainerImage, error) { + created, err := time.ParseInLocation(time.RFC3339, i.Created, time.UTC) if err != nil { return nil, err } @@ -147,7 +163,8 @@ func (r *LocalRuntime) NewImageFromLocal(name string) (*ContainerImage, error) { func (r *LocalRuntime) LoadFromArchiveReference(ctx context.Context, srcRef types.ImageReference, signaturePolicyPath string, writer io.Writer) ([]*ContainerImage, error) { // TODO We need to find a way to leak certDir, creds, and the tlsverify into this function, normally this would // come from cli options but we don't want want those in here either. - imageID, err := iopodman.PullImage().Call(r.Conn, srcRef.DockerReference().String(), "", "", signaturePolicyPath, true) + tlsverify := true + imageID, err := iopodman.PullImage().Call(r.Conn, srcRef.DockerReference().String(), "", "", signaturePolicyPath, &tlsverify) if err != nil { return nil, err } @@ -163,15 +180,21 @@ func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authf if label != nil { return nil, errors.New("the remote client function does not support checking a remote image for a label") } - // TODO Creds needs to be figured out here too, like above - tlsBool := dockeroptions.DockerInsecureSkipTLSVerify - // Remember SkipTlsVerify is the opposite of tlsverify - // If tlsBook is true or undefined, we do not skip - SkipTlsVerify := false - if tlsBool == types.OptionalBoolFalse { - SkipTlsVerify = true + var ( + tlsVerify bool + tlsVerifyPtr *bool + ) + if dockeroptions.DockerInsecureSkipTLSVerify == types.OptionalBoolFalse { + tlsVerify = true + tlsVerifyPtr = &tlsVerify + + } + if dockeroptions.DockerInsecureSkipTLSVerify == types.OptionalBoolTrue { + tlsVerify = false + tlsVerifyPtr = &tlsVerify } - imageID, err := iopodman.PullImage().Call(r.Conn, name, dockeroptions.DockerCertPath, "", signaturePolicyPath, SkipTlsVerify) + + imageID, err := iopodman.PullImage().Call(r.Conn, name, dockeroptions.DockerCertPath, "", signaturePolicyPath, tlsVerifyPtr) if err != nil { return nil, err } @@ -182,12 +205,6 @@ func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authf return newImage, nil } -func splitStringDate(d string) (time.Time, error) { - fields := strings.Fields(d) - t := fmt.Sprintf("%sT%sZ", fields[0], fields[1]) - return time.ParseInLocation(time.RFC3339Nano, t, time.UTC) -} - // IsParent goes through the layers in the store and checks if i.TopLayer is // the parent of any other layer in store. Double check that image with that // layer exists as well. @@ -251,7 +268,7 @@ func (ci *ContainerImage) History(ctx context.Context) ([]*image.History, error) return nil, err } for _, h := range reply { - created, err := splitStringDate(h.Created) + created, err := time.ParseInLocation(time.RFC3339, h.Created, time.UTC) if err != nil { return nil, err } @@ -369,6 +386,108 @@ func (r *LocalRuntime) Export(name string, path string) error { // Import implements the remote calls required to import a container image to the store func (r *LocalRuntime) Import(ctx context.Context, source, reference string, changes []string, history string, quiet bool) (string, error) { // First we send the file to the host + tempFile, err := r.SendFileOverVarlink(source) + if err != nil { + return "", err + } + return iopodman.ImportImage().Call(r.Conn, strings.TrimRight(tempFile, ":"), reference, history, changes, true) +} + +func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) error { + buildOptions := iopodman.BuildOptions{ + AddHosts: options.CommonBuildOpts.AddHost, + CgroupParent: options.CommonBuildOpts.CgroupParent, + CpuPeriod: int64(options.CommonBuildOpts.CPUPeriod), + CpuQuota: options.CommonBuildOpts.CPUQuota, + CpuShares: int64(options.CommonBuildOpts.CPUShares), + CpusetCpus: options.CommonBuildOpts.CPUSetMems, + CpusetMems: options.CommonBuildOpts.CPUSetMems, + Memory: options.CommonBuildOpts.Memory, + MemorySwap: options.CommonBuildOpts.MemorySwap, + ShmSize: options.CommonBuildOpts.ShmSize, + Ulimit: options.CommonBuildOpts.Ulimit, + Volume: options.CommonBuildOpts.Volumes, + } + + buildinfo := iopodman.BuildInfo{ + AdditionalTags: options.AdditionalTags, + Annotations: options.Annotations, + BuildArgs: options.Args, + BuildOptions: buildOptions, + CniConfigDir: options.CNIConfigDir, + CniPluginDir: options.CNIPluginPath, + Compression: string(options.Compression), + DefaultsMountFilePath: options.DefaultMountsFilePath, + Dockerfiles: dockerfiles, + //Err: string(options.Err), + ForceRmIntermediateCtrs: options.ForceRmIntermediateCtrs, + Iidfile: options.IIDFile, + Label: options.Labels, + Layers: options.Layers, + Nocache: options.NoCache, + //Out: + Output: options.Output, + OutputFormat: options.OutputFormat, + PullPolicy: options.PullPolicy.String(), + Quiet: options.Quiet, + RemoteIntermediateCtrs: options.RemoveIntermediateCtrs, + //ReportWriter: + RuntimeArgs: options.RuntimeArgs, + SignaturePolicyPath: options.SignaturePolicyPath, + Squash: options.Squash, + } + // tar the file + logrus.Debugf("creating tarball of context dir %s", options.ContextDirectory) + input, err := archive.Tar(options.ContextDirectory, archive.Uncompressed) + if err != nil { + return errors.Wrapf(err, "unable to create tarball of context dir %s", options.ContextDirectory) + } + + // Write the tarball to the fs + // TODO we might considering sending this without writing to the fs for the sake of performance + // under given conditions like memory availability. + outputFile, err := ioutil.TempFile("", "varlink_tar_send") + if err != nil { + return err + } + defer outputFile.Close() + logrus.Debugf("writing context dir tarball to %s", outputFile.Name()) + + _, err = io.Copy(outputFile, input) + if err != nil { + return err + } + + logrus.Debugf("completed writing context dir tarball %s", outputFile.Name()) + // Send the context dir tarball over varlink. + tempFile, err := r.SendFileOverVarlink(outputFile.Name()) + if err != nil { + return err + } + buildinfo.ContextDir = strings.Replace(tempFile, ":", "", -1) + + reply, err := iopodman.BuildImage().Send(r.Conn, varlink.More, buildinfo) + if err != nil { + return err + } + + for { + responses, flags, err := reply() + if err != nil { + return err + } + for _, line := range responses.Logs { + fmt.Print(line) + } + if flags&varlink.Continues == 0 { + break + } + } + return err +} + +// SendFileOverVarlink sends a file over varlink in an upgraded connection +func (r *LocalRuntime) SendFileOverVarlink(source string) (string, error) { fs, err := os.Open(source) if err != nil { return "", err @@ -378,6 +497,7 @@ func (r *LocalRuntime) Import(ctx context.Context, source, reference string, cha if err != nil { return "", err } + logrus.Debugf("sending %s over varlink connection", source) reply, err := iopodman.SendFile().Send(r.Conn, varlink.Upgrade, "", int64(fileInfo.Size())) if err != nil { return "", err @@ -392,6 +512,7 @@ func (r *LocalRuntime) Import(ctx context.Context, source, reference string, cha if err != nil { return "", err } + logrus.Debugf("file transfer complete for %s", source) r.Conn.Writer.Flush() // All was sent, wait for the ACK from the server @@ -405,7 +526,8 @@ func (r *LocalRuntime) Import(ctx context.Context, source, reference string, cha return "", err } - return iopodman.ImportImage().Call(r.Conn, strings.TrimRight(tempFile, ":"), reference, history, changes, true) + + return tempFile, nil } // GetAllVolumes retrieves all the volumes @@ -429,6 +551,124 @@ func (r *LocalRuntime) GetContainers(filters ...libpod.ContainerFilter) ([]*libp // RemoveContainer removes the given container // If force is specified, the container will be stopped first // Otherwise, RemoveContainer will return an error if the container is running -func (r *LocalRuntime) RemoveContainer(ctx context.Context, c *libpod.Container, force bool) error { +func (r *LocalRuntime) RemoveContainer(ctx context.Context, c *libpod.Container, force, volumes bool) error { return libpod.ErrNotImplemented } + +// CreateVolume creates a volume over a varlink connection for the remote client +func (r *LocalRuntime) CreateVolume(ctx context.Context, c *cliconfig.VolumeCreateValues, labels, opts map[string]string) (string, error) { + cvOpts := iopodman.VolumeCreateOpts{ + Options: opts, + Labels: labels, + } + if len(c.InputArgs) > 0 { + cvOpts.VolumeName = c.InputArgs[0] + } + + if c.Flag("driver").Changed { + cvOpts.Driver = c.Driver + } + + return iopodman.VolumeCreate().Call(r.Conn, cvOpts) +} + +// RemoveVolumes removes volumes over a varlink connection for the remote client +func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, error) { + rmOpts := iopodman.VolumeRemoveOpts{ + All: c.All, + Force: c.Force, + Volumes: c.InputArgs, + } + return iopodman.VolumeRemove().Call(r.Conn, rmOpts) +} + +func (r *LocalRuntime) Push(ctx context.Context, srcName, destination, manifestMIMEType, authfile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions image.SigningOptions, dockerRegistryOptions *image.DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged) error { + + var ( + tls *bool + tlsVerify bool + ) + if dockerRegistryOptions.DockerInsecureSkipTLSVerify == types.OptionalBoolTrue { + tlsVerify = false + tls = &tlsVerify + } + if dockerRegistryOptions.DockerInsecureSkipTLSVerify == types.OptionalBoolFalse { + tlsVerify = true + tls = &tlsVerify + } + + reply, err := iopodman.PushImage().Send(r.Conn, varlink.More, srcName, destination, tls, signaturePolicyPath, "", dockerRegistryOptions.DockerCertPath, forceCompress, manifestMIMEType, signingOptions.RemoveSignatures, signingOptions.SignBy) + if err != nil { + return err + } + for { + responses, flags, err := reply() + if err != nil { + return err + } + for _, line := range responses.Logs { + fmt.Print(line) + } + if flags&varlink.Continues == 0 { + break + } + } + + return err +} + +// InspectVolumes returns a slice of volumes based on an arg list or --all +func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeInspectValues) ([]*Volume, error) { + reply, err := iopodman.GetVolumes().Call(r.Conn, c.InputArgs, c.All) + if err != nil { + return nil, err + } + return varlinkVolumeToVolume(r, reply), nil +} + +//Volumes returns a slice of adapter.volumes based on information about libpod +// volumes over a varlink connection +func (r *LocalRuntime) Volumes(ctx context.Context) ([]*Volume, error) { + reply, err := iopodman.GetVolumes().Call(r.Conn, []string{}, true) + if err != nil { + return nil, err + } + return varlinkVolumeToVolume(r, reply), nil +} + +func varlinkVolumeToVolume(r *LocalRuntime, volumes []iopodman.Volume) []*Volume { + var vols []*Volume + for _, v := range volumes { + volumeConfig := libpod.VolumeConfig{ + Name: v.Name, + Labels: v.Labels, + MountPoint: v.MountPoint, + Driver: v.Driver, + Options: v.Options, + Scope: v.Scope, + } + n := remoteVolume{ + Runtime: r, + config: &volumeConfig, + } + newVol := Volume{ + n, + } + vols = append(vols, &newVol) + } + return vols +} + +// PruneVolumes removes all unused volumes from the remote system +func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) { + var errs []error + prunedNames, prunedErrors, err := iopodman.VolumesPrune().Call(r.Conn) + if err != nil { + return []string{}, []error{err} + } + // We need to transform the string results of the error into actual error types + for _, e := range prunedErrors { + errs = append(errs, errors.New(e)) + } + return prunedNames, errs +} diff --git a/libpod/adapter/volumes_remote.go b/libpod/adapter/volumes_remote.go new file mode 100644 index 000000000..beacd943a --- /dev/null +++ b/libpod/adapter/volumes_remote.go @@ -0,0 +1,33 @@ +// +build remoteclient + +package adapter + +// Name returns the name of the volume +func (v *Volume) Name() string { + return v.config.Name +} + +//Labels returns the labels for a volume +func (v *Volume) Labels() map[string]string { + return v.config.Labels +} + +// Driver returns the driver for the volume +func (v *Volume) Driver() string { + return v.config.Driver +} + +// Options returns the options a volume was created with +func (v *Volume) Options() map[string]string { + return v.config.Options +} + +// MountPath returns the path the volume is mounted to +func (v *Volume) MountPoint() string { + return v.config.MountPoint +} + +// Scope returns the scope for an adapter.volume +func (v *Volume) Scope() string { + return v.config.Scope +} |