diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/adapter/client.go | 11 | ||||
-rw-r--r-- | libpod/adapter/runtime.go | 55 | ||||
-rw-r--r-- | libpod/adapter/runtime_remote.go | 86 | ||||
-rw-r--r-- | libpod/image/image.go | 4 | ||||
-rw-r--r-- | libpod/image/image_test.go | 8 | ||||
-rw-r--r-- | libpod/image/pull.go | 44 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 43 | ||||
-rw-r--r-- | libpod/runtime_img.go | 79 | ||||
-rw-r--r-- | libpod/runtime_pod_infra_linux.go | 2 |
9 files changed, 316 insertions, 16 deletions
diff --git a/libpod/adapter/client.go b/libpod/adapter/client.go index b3bb9acae..6512a5952 100644 --- a/libpod/adapter/client.go +++ b/libpod/adapter/client.go @@ -34,3 +34,14 @@ func (r RemoteRuntime) Connect() (*varlink.Connection, error) { } return connection, nil } + +// RefreshConnection is used to replace the current r.Conn after things like +// using an upgraded varlink connection +func (r RemoteRuntime) RefreshConnection() error { + newConn, err := r.Connect() + if err != nil { + return err + } + r.Conn = newConn + return nil +} diff --git a/libpod/adapter/runtime.go b/libpod/adapter/runtime.go index f4961437e..2c408dd2f 100644 --- a/libpod/adapter/runtime.go +++ b/libpod/adapter/runtime.go @@ -4,12 +4,17 @@ package adapter import ( "context" + "github.com/pkg/errors" "io" + "io/ioutil" + "os" + "strconv" "github.com/containers/image/types" "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/urfave/cli" ) @@ -78,8 +83,8 @@ func (r *LocalRuntime) LoadFromArchiveReference(ctx context.Context, srcRef type } // New calls into local storage to look for an image in local storage or to pull it -func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, forcePull bool) (*ContainerImage, error) { - img, err := r.Runtime.ImageRuntime().New(ctx, name, signaturePolicyPath, authfile, writer, dockeroptions, signingoptions, forcePull) +func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, forcePull bool, label *string) (*ContainerImage, error) { + img, err := r.Runtime.ImageRuntime().New(ctx, name, signaturePolicyPath, authfile, writer, dockeroptions, signingoptions, forcePull, label) if err != nil { return nil, err } @@ -104,3 +109,49 @@ func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { return r.ImageRuntime().PruneImages(all) } + +// Export is a wrapper to container export to a tarfile +func (r *LocalRuntime) Export(name string, path string) error { + ctr, err := r.Runtime.LookupContainer(name) + if err != nil { + return errors.Wrapf(err, "error looking up container %q", name) + } + if os.Geteuid() != 0 { + state, err := ctr.State() + if err != nil { + return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) + } + if state == libpod.ContainerStateRunning || state == libpod.ContainerStatePaused { + data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) + if err != nil { + return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) + } + conmonPid, err := strconv.Atoi(string(data)) + if err != nil { + return errors.Wrapf(err, "cannot parse PID %q", data) + } + became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) + if err != nil { + return err + } + if became { + os.Exit(ret) + } + } else { + became, ret, err := rootless.BecomeRootInUserNS() + if err != nil { + return err + } + if became { + os.Exit(ret) + } + } + } + + return ctr.Export(path) +} + +// Import is a wrapper to import a container image +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) +} diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go index f184ce0a9..07c786184 100644 --- a/libpod/adapter/runtime_remote.go +++ b/libpod/adapter/runtime_remote.go @@ -3,10 +3,13 @@ package adapter import ( + "bufio" "context" "encoding/json" "fmt" + "github.com/pkg/errors" "io" + "os" "strings" "time" @@ -156,7 +159,10 @@ func (r *LocalRuntime) LoadFromArchiveReference(ctx context.Context, srcRef type } // New calls into local storage to look for an image in local storage or to pull it -func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, forcePull bool) (*ContainerImage, error) { +func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, forcePull bool, label *string) (*ContainerImage, error) { + 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 @@ -320,6 +326,84 @@ func (r *LocalRuntime) Config(name string) *libpod.ContainerConfig { return &data } + +// PruneImages is the wrapper call for a remote-client to prune images func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { return iopodman.ImagesPrune().Call(r.Conn, all) } + +// Export is a wrapper to container export to a tarfile +func (r *LocalRuntime) Export(name string, path string) error { + tempPath, err := iopodman.ExportContainer().Call(r.Conn, name, "") + if err != nil { + return err + } + + outputFile, err := os.Create(path) + if err != nil { + return err + } + defer outputFile.Close() + + writer := bufio.NewWriter(outputFile) + defer writer.Flush() + + reply, err := iopodman.ReceiveFile().Send(r.Conn, varlink.Upgrade, tempPath, true) + if err != nil { + return err + } + + length, _, err := reply() + if err != nil { + return errors.Wrap(err, "unable to get file length for transfer") + } + + reader := r.Conn.Reader + if _, err := io.CopyN(writer, reader, length); err != nil { + return errors.Wrap(err, "file transer failed") + } + + return nil +} + +// 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 + fs, err := os.Open(source) + if err != nil { + return "", err + } + + fileInfo, err := fs.Stat() + if err != nil { + return "", err + } + reply, err := iopodman.SendFile().Send(r.Conn, varlink.Upgrade, "", int64(fileInfo.Size())) + if err != nil { + return "", err + } + _, _, err = reply() + if err != nil { + return "", err + } + + reader := bufio.NewReader(fs) + _, err = reader.WriteTo(r.Conn.Writer) + if err != nil { + return "", err + } + r.Conn.Writer.Flush() + + // All was sent, wait for the ACK from the server + tempFile, err := r.Conn.Reader.ReadString(':') + if err != nil { + return "", err + } + + // r.Conn is kaput at this point due to the upgrade + if err := r.RemoteRuntime.RefreshConnection(); err != nil { + return "", err + + } + return iopodman.ImportImage().Call(r.Conn, strings.TrimRight(tempFile, ":"), reference, history, changes, true) +} diff --git a/libpod/image/image.go b/libpod/image/image.go index a32336f49..739372e77 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -125,7 +125,7 @@ func (ir *Runtime) NewFromLocal(name string) (*Image, error) { // New creates a new image object where the image could be local // or remote -func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *DockerRegistryOptions, signingoptions SigningOptions, forcePull bool) (*Image, error) { +func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *DockerRegistryOptions, signingoptions SigningOptions, forcePull bool, label *string) (*Image, error) { // We don't know if the image is local or not ... check local first newImage := Image{ InputName: name, @@ -145,7 +145,7 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile if signaturePolicyPath == "" { signaturePolicyPath = ir.SignaturePolicyPath } - imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions) + imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, label) if err != nil { return nil, errors.Wrapf(err, "unable to pull %s", name) } diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index 077ae460e..075ba119d 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -87,9 +87,9 @@ func TestImage_NewFromLocal(t *testing.T) { // Need images to be present for this test ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) - bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, false) + bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, false, nil) assert.NoError(t, err) - bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, false) + bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, false, nil) assert.NoError(t, err) tm, err := makeLocalMatrix(bb, bbglibc) @@ -136,7 +136,7 @@ func TestImage_New(t *testing.T) { // Iterate over the names and delete the image // after the pull for _, img := range names { - newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, false) + newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, false, nil) assert.NoError(t, err) assert.NotEqual(t, newImage.ID(), "") err = newImage.Remove(false) @@ -164,7 +164,7 @@ func TestImage_MatchRepoTag(t *testing.T) { } ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) - newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, false) + newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, false, nil) assert.NoError(t, err) err = newImage.TagImage("foo:latest") assert.NoError(t, err) diff --git a/libpod/image/pull.go b/libpod/image/pull.go index 434b83520..6fef96e37 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "strings" cp "github.com/containers/image/copy" "github.com/containers/image/directory" @@ -192,7 +193,7 @@ func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types. // pullImageFromHeuristicSource pulls an image based on inputName, which is heuristically parsed and may involve configured registries. // Use pullImageFromReference if the source is known precisely. -func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions) ([]string, error) { +func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, label *string) ([]string, error) { var goal *pullGoal sc := GetSystemContext(signaturePolicyPath, authfile, false) srcRef, err := alltransports.ParseImageName(inputName) @@ -208,7 +209,7 @@ func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName s return nil, errors.Wrapf(err, "error determining pull goal for image %q", inputName) } } - return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions) + return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, label) } // pullImageFromReference pulls an image from a types.imageReference. @@ -218,11 +219,11 @@ func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.Imag if err != nil { return nil, errors.Wrapf(err, "error determining pull goal for image %q", transports.ImageName(srcRef)) } - return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions) + return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, nil) } // doPullImage is an internal helper interpreting pullGoal. Almost everyone should call one of the callers of doPullImage instead. -func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions) ([]string, error) { +func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, label *string) ([]string, error) { policyContext, err := getPolicyContext(sc) if err != nil { return nil, err @@ -230,8 +231,12 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa defer policyContext.Destroy() systemRegistriesConfPath := registries.SystemRegistriesConfPath() - var images []string - var pullErrors *multierror.Error + + var ( + images []string + pullErrors *multierror.Error + ) + for _, imageInfo := range goal.refPairs { copyOptions := getCopyOptions(sc, writer, dockerOptions, nil, signingOptions, "", nil) copyOptions.SourceCtx.SystemRegistriesConfPath = systemRegistriesConfPath // FIXME: Set this more globally. Probably no reason not to have it in every types.SystemContext, and to compute the value just once in one place. @@ -239,6 +244,13 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa if writer != nil && (imageInfo.srcRef.Transport().Name() == DockerTransport || imageInfo.srcRef.Transport().Name() == AtomicTransport) { io.WriteString(writer, fmt.Sprintf("Trying to pull %s...", imageInfo.image)) } + // If the label is not nil, check if the label exists and if not, return err + if label != nil { + if err := checkRemoteImageForLabel(ctx, *label, imageInfo, sc); err != nil { + return nil, err + } + } + _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions) if err != nil { pullErrors = multierror.Append(pullErrors, err) @@ -314,3 +326,23 @@ func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullG searchedRegistries: searchRegistries, }, nil } + +// checkRemoteImageForLabel checks if the remote image has a specific label. if the label exists, we +// return nil, else we return an error +func checkRemoteImageForLabel(ctx context.Context, label string, imageInfo pullRefPair, sc *types.SystemContext) error { + labelImage, err := imageInfo.srcRef.NewImage(ctx, sc) + if err != nil { + return err + } + remoteInspect, err := labelImage.Inspect(ctx) + if err != nil { + return err + } + // Labels are case insensitive; so we iterate instead of simple lookup + for k := range remoteInspect.Labels { + if strings.ToLower(label) == strings.ToLower(k) { + return nil + } + } + return errors.Errorf("%s has no label %s", imageInfo.image, label) +} diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 6d5ce5a7e..9afdef7b6 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -2,9 +2,11 @@ package libpod import ( "context" + "io/ioutil" "os" "path" "path/filepath" + "strconv" "strings" "time" @@ -521,3 +523,44 @@ func isNamedVolume(volName string) bool { } return false } + +// Export is the libpod portion of exporting a container to a tar file +func (r *Runtime) Export(name string, path string) error { + ctr, err := r.LookupContainer(name) + if err != nil { + return err + } + if os.Geteuid() != 0 { + state, err := ctr.State() + if err != nil { + return errors.Wrapf(err, "cannot read container state %q", ctr.ID()) + } + if state == ContainerStateRunning || state == ContainerStatePaused { + data, err := ioutil.ReadFile(ctr.Config().ConmonPidFile) + if err != nil { + return errors.Wrapf(err, "cannot read conmon PID file %q", ctr.Config().ConmonPidFile) + } + conmonPid, err := strconv.Atoi(string(data)) + if err != nil { + return errors.Wrapf(err, "cannot parse PID %q", data) + } + became, ret, err := rootless.JoinDirectUserAndMountNS(uint(conmonPid)) + if err != nil { + return err + } + if became { + os.Exit(ret) + } + } else { + became, ret, err := rootless.BecomeRootInUserNS() + if err != nil { + return err + } + if became { + os.Exit(ret) + } + } + } + return ctr.Export(path) + +} diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 66844bb31..c20aa77a3 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -3,9 +3,16 @@ package libpod import ( "context" "fmt" + "github.com/opencontainers/image-spec/specs-go/v1" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" "github.com/containers/buildah/imagebuildah" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/util" "github.com/containers/storage" "github.com/pkg/errors" ) @@ -132,3 +139,75 @@ func (r *Runtime) Build(ctx context.Context, options imagebuildah.BuildOptions, _, _, err := imagebuildah.BuildDockerfiles(ctx, r.store, options, dockerfiles...) return err } + +// Import is called as an intermediary to the image library Import +func (r *Runtime) Import(ctx context.Context, source string, reference string, changes []string, history string, quiet bool) (string, error) { + var ( + writer io.Writer + err error + ) + + ic := v1.ImageConfig{} + if len(changes) > 0 { + ic, err = util.GetImageConfig(changes) + if err != nil { + return "", errors.Wrapf(err, "error adding config changes to image %q", source) + } + } + + hist := []v1.History{ + {Comment: history}, + } + + config := v1.Image{ + Config: ic, + History: hist, + } + + writer = nil + if !quiet { + writer = os.Stderr + } + + // if source is a url, download it and save to a temp file + u, err := url.ParseRequestURI(source) + if err == nil && u.Scheme != "" { + file, err := downloadFromURL(source) + if err != nil { + return "", err + } + defer os.Remove(file) + source = file + } + + newImage, err := r.imageRuntime.Import(ctx, source, reference, writer, image.SigningOptions{}, config) + if err != nil { + return "", err + } + return newImage.ID(), nil +} + +// donwloadFromURL downloads an image in the format "https:/example.com/myimage.tar" +// and temporarily saves in it /var/tmp/importxyz, which is deleted after the image is imported +func downloadFromURL(source string) (string, error) { + fmt.Printf("Downloading from %q\n", source) + + outFile, err := ioutil.TempFile("/var/tmp", "import") + if err != nil { + return "", errors.Wrap(err, "error creating file") + } + defer outFile.Close() + + response, err := http.Get(source) + if err != nil { + return "", errors.Wrapf(err, "error downloading %q", source) + } + defer response.Body.Close() + + _, err = io.Copy(outFile, response.Body) + if err != nil { + return "", errors.Wrapf(err, "error saving %s to %s", source, outFile.Name()) + } + + return outFile.Name(), nil +} diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index 5e1051150..4f221764a 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -67,7 +67,7 @@ func (r *Runtime) createInfraContainer(ctx context.Context, p *Pod) (*Container, return nil, ErrRuntimeStopped } - newImage, err := r.ImageRuntime().New(ctx, r.config.InfraImage, "", "", nil, nil, image.SigningOptions{}, false) + newImage, err := r.ImageRuntime().New(ctx, r.config.InfraImage, "", "", nil, nil, image.SigningOptions{}, false, nil) if err != nil { return nil, err } |