diff options
38 files changed, 637 insertions, 204 deletions
diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 718717009..d8fdd556f 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -12,8 +12,6 @@ func getAppCommands() []cli.Command { createCommand, diffCommand, execCommand, - exportCommand, - importCommand, killCommand, kubeCommand, loadCommand, diff --git a/cmd/podman/container.go b/cmd/podman/container.go index acbcbb644..29300a6a4 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -8,6 +8,7 @@ import ( var ( containerSubCommands = []cli.Command{ + exportCommand, inspectCommand, } containerDescription = "Manage containers" diff --git a/cmd/podman/create.go b/cmd/podman/create.go index c56efa153..2d85abd35 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -134,7 +134,7 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container writer = os.Stderr } - newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false) + newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false, nil) if err != nil { return nil, nil, err } diff --git a/cmd/podman/export.go b/cmd/podman/export.go index c0e63bd2a..eaa4e38a2 100644 --- a/cmd/podman/export.go +++ b/cmd/podman/export.go @@ -1,12 +1,9 @@ package main import ( - "io/ioutil" "os" - "strconv" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -43,7 +40,7 @@ func exportCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -58,52 +55,18 @@ func exportCmd(c *cli.Context) error { } output := c.String("output") + if runtime.Remote && (output == "/dev/stdout" || len(output) == 0) { + return errors.New("remote client usage must specify an output file (-o)") + } if output == "/dev/stdout" { file := os.Stdout if logrus.IsTerminal(file) { return errors.Errorf("refusing to export to terminal. Use -o flag or redirect") } } + if err := validateFileName(output); err != nil { return err } - - ctr, err := runtime.LookupContainer(args[0]) - if err != nil { - return errors.Wrapf(err, "error looking up container %q", args[0]) - } - - 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(output) + return runtime.Export(args[0], c.String("output")) } diff --git a/cmd/podman/image.go b/cmd/podman/image.go index a51a90b0e..6b451a1ca 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -8,6 +8,7 @@ import ( var ( imageSubCommands = []cli.Command{ + importCommand, historyCommand, imageExistsCommand, inspectCommand, diff --git a/cmd/podman/import.go b/cmd/podman/import.go index 144354fa6..661bd5a65 100644 --- a/cmd/podman/import.go +++ b/cmd/podman/import.go @@ -2,16 +2,8 @@ package main import ( "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/util" - "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -51,7 +43,7 @@ func importCmd(c *cli.Context) error { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -60,7 +52,6 @@ func importCmd(c *cli.Context) error { var ( source string reference string - writer io.Writer ) args := c.Args() @@ -80,67 +71,13 @@ func importCmd(c *cli.Context) error { return err } - changes := v1.ImageConfig{} - if c.IsSet("change") || c.IsSet("c") { - changes, err = util.GetImageConfig(c.StringSlice("change")) - if err != nil { - return errors.Wrapf(err, "error adding config changes to image %q", source) - } + quiet := c.Bool("quiet") + if runtime.Remote { + quiet = false } - - history := []v1.History{ - {Comment: c.String("message")}, - } - - config := v1.Image{ - Config: changes, - History: history, - } - - writer = nil - if !c.Bool("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 := runtime.ImageRuntime().Import(getContext(), source, reference, writer, image.SigningOptions{}, config) + iid, err := runtime.Import(getContext(), source, reference, c.StringSlice("change"), c.String("message"), quiet) if err == nil { - fmt.Println(newImage.ID()) + fmt.Println(iid) } return err } - -// 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/cmd/podman/main.go b/cmd/podman/main.go index c10590006..1ca8882eb 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -88,9 +88,11 @@ func main() { app.Commands = []cli.Command{ containerCommand, + exportCommand, historyCommand, imageCommand, imagesCommand, + importCommand, infoCommand, inspectCommand, pullCommand, diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go index 2ce2e21bb..4753dd0a6 100644 --- a/cmd/podman/play_kube.go +++ b/cmd/podman/play_kube.go @@ -146,7 +146,7 @@ func playKubeYAMLCmd(c *cli.Context) error { } for _, container := range podYAML.Spec.Containers { - newImage, err := runtime.ImageRuntime().New(ctx, container.Image, c.String("signature-policy"), c.String("authfile"), writer, &dockerRegistryOptions, image2.SigningOptions{}, false) + newImage, err := runtime.ImageRuntime().New(ctx, container.Image, c.String("signature-policy"), c.String("authfile"), writer, &dockerRegistryOptions, image2.SigningOptions{}, false, nil) if err != nil { return err } diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go index 2a78d0c54..f70e5cded 100644 --- a/cmd/podman/pull.go +++ b/cmd/podman/pull.go @@ -123,7 +123,7 @@ func pullCmd(c *cli.Context) error { imgID = newImage[0].ID() } else { authfile := getAuthFile(c.String("authfile")) - newImage, err := runtime.New(getContext(), image, c.String("signature-policy"), authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true) + newImage, err := runtime.New(getContext(), image, c.String("signature-policy"), authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true, nil) if err != nil { return errors.Wrapf(err, "error pulling image %q", image) } diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go index 48a296260..b16a93fd9 100644 --- a/cmd/podman/runlabel.go +++ b/cmd/podman/runlabel.go @@ -166,7 +166,7 @@ func runlabelCmd(c *cli.Context) error { return err } if runLabel == "" { - return nil + return errors.Errorf("%s does not have a label of %s", runlabelImage, label) } cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.String("name"), opts, extraArgs) diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 9040c4a5c..f84fb8261 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -609,7 +609,7 @@ func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtim registryCreds = creds } dockerRegistryOptions.DockerRegistryCreds = registryCreds - newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, signaturePolicyPath, authfile, output, &dockerRegistryOptions, image.SigningOptions{}, false) + newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, signaturePolicyPath, authfile, output, &dockerRegistryOptions, image.SigningOptions{}, false, &label) } else { newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) } diff --git a/cmd/podman/sign.go b/cmd/podman/sign.go index 1d9aecdc9..22aa07230 100644 --- a/cmd/podman/sign.go +++ b/cmd/podman/sign.go @@ -104,7 +104,7 @@ func signCmd(c *cli.Context) error { } // create the signstore file - newImage, err := runtime.ImageRuntime().New(getContext(), signimage, runtime.GetConfig().SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{SignBy: signby}, false) + newImage, err := runtime.ImageRuntime().New(getContext(), signimage, runtime.GetConfig().SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{SignBy: signby}, false, nil) if err != nil { return errors.Wrapf(err, "error pulling image %s", signimage) } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 8b02057a1..101232b0c 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -688,7 +688,7 @@ method Commit(name: string, image_name: string, changes: []string, author: strin # ImportImage imports an image from a source (like tarball) into local storage. The image can have additional # descriptions added to it using the message and changes options. See also [ExportImage](ExportImage). -method ImportImage(source: string, reference: string, message: string, changes: []string) -> (image: string) +method ImportImage(source: string, reference: string, message: string, changes: []string, delete: bool) -> (image: string) # ExportImage takes the name or ID of an image and exports it to a destination like a tarball. There is also # a booleon option to force compression. It also takes in a string array of tags to be able to save multiple @@ -1050,6 +1050,9 @@ method ContainerInspectData(name: string) -> (config: string) # development of Podman only and generally should not be used. method ContainerStateData(name: string) -> (config: string) +method SendFile(type: string, length: int) -> (file_handle: string) +method ReceiveFile(path: string, delete: bool) -> (len: int) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (name: string) diff --git a/contrib/perftest/main.go b/contrib/perftest/main.go index 0d0947890..6a6725ab9 100644 --- a/contrib/perftest/main.go +++ b/contrib/perftest/main.go @@ -103,7 +103,7 @@ func main() { } fmt.Printf("image %s not found locally, fetching from remote registry..\n", *testImageName) - testImage, err = client.ImageRuntime().New(ctx, *testImageName, "", "", writer, &dockerRegistryOptions, image2.SigningOptions{}, false) + testImage, err = client.ImageRuntime().New(ctx, *testImageName, "", "", writer, &dockerRegistryOptions, image2.SigningOptions{}, false, nil) if err != nil { logrus.Fatal(err) } diff --git a/docs/podman-container-runlabel.1.md b/docs/podman-container-runlabel.1.md index 6f7b4dae8..c5d7a278f 100644 --- a/docs/podman-container-runlabel.1.md +++ b/docs/podman-container-runlabel.1.md @@ -26,6 +26,9 @@ If the container image has a LABEL INSTALL instruction like the following: `podman container runlabel` will set the following environment variables for use in the command: +If the container image does not have the desired label, an error message will be displayed along with a non-zero +return code. + Note: Podman will always ensure that `podman` is the first argument of the command being executed. **LABEL** 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 } diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index a01e3cc2b..737e2dd96 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "os" "syscall" "time" @@ -194,15 +195,25 @@ func (i *LibpodAPI) ListContainerChanges(call iopodman.VarlinkCall, name string) } // ExportContainer ... -func (i *LibpodAPI) ExportContainer(call iopodman.VarlinkCall, name, path string) error { +func (i *LibpodAPI) ExportContainer(call iopodman.VarlinkCall, name, outPath string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name) } - if err := ctr.Export(path); err != nil { + outputFile, err := ioutil.TempFile("", "varlink_recv") + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + defer outputFile.Close() + if outPath == "" { + outPath = outputFile.Name() + } + if err := ctr.Export(outPath); err != nil { return call.ReplyErrorOccurred(err.Error()) } - return call.ReplyExportContainer(path) + return call.ReplyExportContainer(outPath) + } // GetContainerStats ... diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go index d72eaeb18..cc707b11f 100644 --- a/pkg/varlinkapi/containers_create.go +++ b/pkg/varlinkapi/containers_create.go @@ -25,7 +25,7 @@ func (i *LibpodAPI) CreateContainer(call iopodman.VarlinkCall, config iopodman.C rtc := i.Runtime.GetConfig() ctx := getContext() - newImage, err := i.Runtime.ImageRuntime().New(ctx, config.Image, rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false) + newImage, err := i.Runtime.ImageRuntime().New(ctx, config.Image, rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, nil) if err != nil { return call.ReplyErrorOccurred(err.Error()) } diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index d6a9b7301..5e0889645 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -500,7 +500,7 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch } // ImportImage imports an image from a tarball to the image store -func (i *LibpodAPI) ImportImage(call iopodman.VarlinkCall, source, reference, message string, changes []string) error { +func (i *LibpodAPI) ImportImage(call iopodman.VarlinkCall, source, reference, message string, changes []string, delete bool) error { configChanges, err := util.GetImageConfig(changes) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -516,6 +516,12 @@ func (i *LibpodAPI) ImportImage(call iopodman.VarlinkCall, source, reference, me if err != nil { return call.ReplyErrorOccurred(err.Error()) } + if delete { + if err := os.Remove(source); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + } + return call.ReplyImportImage(newImage.ID()) } @@ -573,7 +579,7 @@ func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string, certDir, c } imageID = newImage[0].ID() } else { - newImage, err := i.Runtime.ImageRuntime().New(getContext(), name, signaturePolicy, "", nil, &dockerRegistryOptions, so, false) + newImage, err := i.Runtime.ImageRuntime().New(getContext(), name, signaturePolicy, "", nil, &dockerRegistryOptions, so, false, nil) if err != nil { return call.ReplyErrorOccurred(fmt.Sprintf("unable to pull %s: %s", name, err.Error())) } @@ -610,15 +616,15 @@ func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman. runLabel, imageName, err := shared.GetRunlabel(input.Label, input.Image, ctx, i.Runtime, input.Pull, input.Creds, dockerRegistryOptions, input.Authfile, input.SignaturePolicyPath, nil) if err != nil { - return err + return call.ReplyErrorOccurred(err.Error()) } if runLabel == "" { - return nil + return call.ReplyErrorOccurred(fmt.Sprintf("%s does not contain the label %s", input.Image, input.Label)) } cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs) if err != nil { - return err + return call.ReplyErrorOccurred(err.Error()) } if err := utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...); err != nil { return call.ReplyErrorOccurred(err.Error()) diff --git a/pkg/varlinkapi/transfers.go b/pkg/varlinkapi/transfers.go new file mode 100644 index 000000000..0cb7e5e2e --- /dev/null +++ b/pkg/varlinkapi/transfers.go @@ -0,0 +1,75 @@ +package varlinkapi + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/containers/libpod/cmd/podman/varlink" +) + +// SendFile allows a client to send a file to the varlink server +func (i *LibpodAPI) SendFile(call iopodman.VarlinkCall, ftype string, length int64) error { + if !call.WantsUpgrade() { + return call.ReplyErrorOccurred("client must use upgraded connection to send files") + } + + outputFile, err := ioutil.TempFile("", "varlink_send") + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + defer outputFile.Close() + + if err = call.ReplySendFile(outputFile.Name()); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + writer := bufio.NewWriter(outputFile) + defer writer.Flush() + + reader := call.Call.Reader + if _, err := io.CopyN(writer, reader, length); err != nil { + return err + } + + // Send an ACK to the client + call.Call.Writer.WriteString(fmt.Sprintf("%s:", outputFile.Name())) + call.Call.Writer.Flush() + return nil + +} + +// ReceiveFile allows the varlink server to send a file to a client +func (i *LibpodAPI) ReceiveFile(call iopodman.VarlinkCall, filepath string, delete bool) error { + if !call.WantsUpgrade() { + return call.ReplyErrorOccurred("client must use upgraded connection to send files") + } + fs, err := os.Open(filepath) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + fileInfo, err := fs.Stat() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + // Send the file length down to client + // Varlink connection upraded + if err = call.ReplyReceiveFile(fileInfo.Size()); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + reader := bufio.NewReader(fs) + _, err = reader.WriteTo(call.Writer) + if err != nil { + return err + } + if delete { + if err := os.Remove(filepath); err != nil { + return err + } + } + return call.Writer.Flush() +} diff --git a/test/e2e/export_test.go b/test/e2e/export_test.go index de3f23667..dba0a2255 100644 --- a/test/e2e/export_test.go +++ b/test/e2e/export_test.go @@ -1,5 +1,3 @@ -// +build !remoteclient - package integration import ( @@ -37,6 +35,7 @@ var _ = Describe("Podman export", func() { }) It("podman export output flag", func() { + SkipIfRemote() _, ec, cid := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go index 9b4f584b0..49b9e13d8 100644 --- a/test/e2e/runlabel_test.go +++ b/test/e2e/runlabel_test.go @@ -68,4 +68,15 @@ var _ = Describe("podman container runlabel", func() { result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) }) + It("podman container runlabel bogus label should result in non-zero exit code", func() { + result := podmanTest.Podman([]string{"container", "runlabel", "RUN", ALPINE}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).ToNot(Equal(0)) + }) + It("podman container runlabel bogus label in remote image should result in non-zero exit", func() { + result := podmanTest.Podman([]string{"container", "runlabel", "RUN", "docker.io/library/ubuntu:latest"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).ToNot(Equal(0)) + + }) }) diff --git a/vendor.conf b/vendor.conf index 4a62c6be7..ce3ca9d44 100644 --- a/vendor.conf +++ b/vendor.conf @@ -91,7 +91,7 @@ k8s.io/apimachinery kubernetes-1.10.13-beta.0 https://github.com/kubernetes/apim k8s.io/client-go kubernetes-1.10.13-beta.0 https://github.com/kubernetes/client-go github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7 github.com/varlink/go 92687ab4eb68d99e43b1f5b93477ad76bb54f811 -github.com/containers/buildah e7ca330f923701dba8859f5c014d0a9a3f7f0a49 +github.com/containers/buildah 2406e4acaa00938b8324a0ef3caadbe5668ec896 # TODO: Gotty has not been updated since 2012. Can we find replacement? github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 # do not go beyond the below commit as the next one requires a more recent diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index 217bcfc79..56ab7aa57 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -1311,13 +1311,17 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (strin // Check if we have a one line Dockerfile making layers irrelevant // or the user told us to ignore layers. - ignoreLayers := (len(stages) < 2 && len(stages[0].Node.Children) < 2) || (!b.layers && !b.noCache) + singleLineDockerfile := (len(stages) < 2 && len(stages[0].Node.Children) < 1) + ignoreLayers := singleLineDockerfile || !b.layers && !b.noCache if ignoreLayers { imgID, ref, err := stageExecutor.Commit(ctx, stages[len(stages)-1].Builder, "") if err != nil { return "", nil, err } + if singleLineDockerfile { + b.log("COMMIT %s", ref) + } imageID = imgID imageRef = ref } @@ -1527,6 +1531,17 @@ func (b *Executor) deleteSuccessfulIntermediateCtrs() error { return lastErr } +func (b *Executor) EnsureContainerPath(path string) error { + _, err := os.Stat(filepath.Join(b.mountPoint, path)) + if err != nil && os.IsNotExist(err) { + err = os.MkdirAll(filepath.Join(b.mountPoint, path), 0755) + } + if err != nil { + return errors.Wrapf(err, "error ensuring container path %q", path) + } + return nil +} + // preprocessDockerfileContents runs CPP(1) in preprocess-only mode on the input // dockerfile content and will use ctxDir as the base include path. // diff --git a/vendor/github.com/varlink/go/varlink/bridge.go b/vendor/github.com/varlink/go/varlink/bridge.go index b20c0925a..0ea5de682 100644 --- a/vendor/github.com/varlink/go/varlink/bridge.go +++ b/vendor/github.com/varlink/go/varlink/bridge.go @@ -5,13 +5,13 @@ package varlink import ( "bufio" "io" - "log" "net" "os/exec" ) type PipeCon struct { net.Conn + cmd *exec.Cmd reader *io.ReadCloser writer *io.WriteCloser } @@ -25,6 +25,8 @@ func (p PipeCon) Close() error { if err2 != nil { return err2 } + p.cmd.Wait() + return nil } @@ -42,18 +44,15 @@ func NewBridge(bridge string) (*Connection, error) { if err != nil { return nil, err } - c.conn = PipeCon{nil, &r, &w} + c.conn = PipeCon{nil, cmd, &r, &w} c.address = "" - c.reader = bufio.NewReader(r) - c.writer = bufio.NewWriter(w) + c.Reader = bufio.NewReader(r) + c.Writer = bufio.NewWriter(w) - go func() { - err := cmd.Run() - if err != nil { - log.Fatal(err) - } - }() + err = cmd.Start() + if err != nil { + return nil, err + } return &c, nil } - diff --git a/vendor/github.com/varlink/go/varlink/bridge_windows.go b/vendor/github.com/varlink/go/varlink/bridge_windows.go index 692367a1a..220ae3156 100644 --- a/vendor/github.com/varlink/go/varlink/bridge_windows.go +++ b/vendor/github.com/varlink/go/varlink/bridge_windows.go @@ -3,13 +3,13 @@ package varlink import ( "bufio" "io" - "log" "net" "os/exec" ) type PipeCon struct { net.Conn + cmd *exec.Cmd reader *io.ReadCloser writer *io.WriteCloser } @@ -23,6 +23,8 @@ func (p PipeCon) Close() error { if err2 != nil { return err2 } + p.cmd.Wait() + return nil } @@ -40,18 +42,15 @@ func NewBridge(bridge string) (*Connection, error) { if err != nil { return nil, err } - c.conn = PipeCon{nil, &r, &w} + c.conn = PipeCon{nil, cmd, &r, &w} c.address = "" c.reader = bufio.NewReader(r) c.writer = bufio.NewWriter(w) - go func() { - err := cmd.Run() - if err != nil { - log.Fatal(err) - } - }() + err = cmd.Start() + if err != nil { + return nil, err + } return &c, nil } - diff --git a/vendor/github.com/varlink/go/varlink/call.go b/vendor/github.com/varlink/go/varlink/call.go index 5e9249c0e..d6e046f1d 100644 --- a/vendor/github.com/varlink/go/varlink/call.go +++ b/vendor/github.com/varlink/go/varlink/call.go @@ -4,6 +4,7 @@ import ( "bufio" "encoding/json" "fmt" + "io" "strings" ) @@ -11,9 +12,11 @@ import ( // client can be terminated by returning an error from the call instead // of sending a reply or error reply. type Call struct { - writer *bufio.Writer + *bufio.Reader + *bufio.Writer in *serviceCall Continues bool + Upgrade bool } // WantsMore indicates if the calling client accepts more than one reply to this method call. @@ -21,6 +24,11 @@ func (c *Call) WantsMore() bool { return c.in.More } +// WantsUpgrade indicates that the calling client wants the connection to be upgraded. +func (c *Call) WantsUpgrade() bool { + return c.in.Upgrade +} + // IsOneway indicate that the calling client does not expect a reply. func (c *Call) IsOneway() bool { return c.in.Oneway @@ -45,11 +53,18 @@ func (c *Call) sendMessage(r *serviceReply) error { } b = append(b, 0) - _, e = c.writer.Write(b) + _, e = c.Writer.Write(b) if e != nil { + if e == io.EOF { + return io.ErrUnexpectedEOF + } return e } - return c.writer.Flush() + e = c.Writer.Flush() + if e == io.EOF { + return io.ErrUnexpectedEOF + } + return e } // Reply sends a reply to this method call. diff --git a/vendor/github.com/varlink/go/varlink/connection.go b/vendor/github.com/varlink/go/varlink/connection.go index ac9542408..596caa825 100644 --- a/vendor/github.com/varlink/go/varlink/connection.go +++ b/vendor/github.com/varlink/go/varlink/connection.go @@ -4,6 +4,7 @@ import ( "bufio" "encoding/json" "fmt" + "io" "net" "strings" ) @@ -15,15 +16,59 @@ const ( More = 1 << iota Oneway = 1 << iota Continues = 1 << iota + Upgrade = 1 << iota ) // Error is a varlink error returned from a method call. type Error struct { - error Name string Parameters interface{} } +func (e *Error) DispatchError() error { + errorRawParameters := e.Parameters.(*json.RawMessage) + + switch e.Name { + case "org.varlink.service.InterfaceNotFound": + var param InterfaceNotFound + if errorRawParameters != nil { + err := json.Unmarshal(*errorRawParameters, ¶m) + if err != nil { + return e + } + } + return ¶m + case "org.varlink.service.MethodNotFound": + var param MethodNotFound + if errorRawParameters != nil { + err := json.Unmarshal(*errorRawParameters, ¶m) + if err != nil { + return e + } + } + return ¶m + case "org.varlink.service.MethodNotImplemented": + var param MethodNotImplemented + if errorRawParameters != nil { + err := json.Unmarshal(*errorRawParameters, ¶m) + if err != nil { + return e + } + } + return ¶m + case "org.varlink.service.InvalidParameter": + var param InvalidParameter + if errorRawParameters != nil { + err := json.Unmarshal(*errorRawParameters, ¶m) + if err != nil { + return e + } + } + return ¶m + } + return e +} + // Error returns the fully-qualified varlink error name. func (e *Error) Error() string { return e.Name @@ -31,10 +76,11 @@ func (e *Error) Error() string { // Connection is a connection from a client to a service. type Connection struct { + io.Closer address string conn net.Conn - reader *bufio.Reader - writer *bufio.Writer + Reader *bufio.Reader + Writer *bufio.Writer } // Send sends a method call. It returns a receive() function which is called to retrieve the method reply. @@ -46,6 +92,7 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) ( Parameters interface{} `json:"parameters,omitempty"` More bool `json:"more,omitempty"` Oneway bool `json:"oneway,omitempty"` + Upgrade bool `json:"upgrade,omitempty"` } if (flags&More != 0) && (flags&Oneway != 0) { @@ -55,11 +102,19 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) ( } } + if (flags&More != 0) && (flags&Upgrade != 0) { + return nil, &Error{ + Name: "org.varlink.InvalidParameter", + Parameters: "more", + } + } + m := call{ Method: method, Parameters: parameters, More: flags&More != 0, Oneway: flags&Oneway != 0, + Upgrade: flags&Upgrade != 0, } b, err := json.Marshal(m) if err != nil { @@ -67,13 +122,19 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) ( } b = append(b, 0) - _, err = c.writer.Write(b) + _, err = c.Writer.Write(b) if err != nil { + if err == io.EOF { + return nil, io.ErrUnexpectedEOF + } return nil, err } - err = c.writer.Flush() + err = c.Writer.Flush() if err != nil { + if err == io.EOF { + return nil, io.ErrUnexpectedEOF + } return nil, err } @@ -84,8 +145,11 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) ( Error string `json:"error"` } - out, err := c.reader.ReadBytes('\x00') + out, err := c.Reader.ReadBytes('\x00') if err != nil { + if err == io.EOF { + return 0, io.ErrUnexpectedEOF + } return 0, err } @@ -96,11 +160,11 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) ( } if m.Error != "" { - err = &Error{ + e := &Error{ Name: m.Error, Parameters: m.Parameters, } - return 0, err + return 0, e.DispatchError() } if m.Parameters != nil { @@ -220,8 +284,8 @@ func NewConnection(address string) (*Connection, error) { } c.address = address - c.reader = bufio.NewReader(c.conn) - c.writer = bufio.NewWriter(c.conn) + c.Reader = bufio.NewReader(c.conn) + c.Writer = bufio.NewWriter(c.conn) return &c, nil } diff --git a/vendor/github.com/varlink/go/varlink/orgvarlinkservice.go b/vendor/github.com/varlink/go/varlink/orgvarlinkservice.go index 39f843c31..600c99d34 100644 --- a/vendor/github.com/varlink/go/varlink/orgvarlinkservice.go +++ b/vendor/github.com/varlink/go/varlink/orgvarlinkservice.go @@ -1,5 +1,42 @@ package varlink +// The requested interface was not found. +type InterfaceNotFound struct { + Interface string `json:"interface"` +} + +func (e InterfaceNotFound) Error() string { + return "org.varlink.service.InterfaceNotFound" +} + +// The requested method was not found +type MethodNotFound struct { + Method string `json:"method"` +} + +func (e MethodNotFound) Error() string { + return "org.varlink.service.MethodNotFound" +} + +// The interface defines the requested method, but the service does not +// implement it. +type MethodNotImplemented struct { + Method string `json:"method"` +} + +func (e MethodNotImplemented) Error() string { + return "org.varlink.service.MethodNotImplemented" +} + +// One of the passed parameters is invalid. +type InvalidParameter struct { + Parameter string `json:"parameter"` +} + +func (e InvalidParameter) Error() string { + return "org.varlink.service.InvalidParameter" +} + func doReplyError(c *Call, name string, parameters interface{}) error { return c.sendMessage(&serviceReply{ Error: name, @@ -9,36 +46,28 @@ func doReplyError(c *Call, name string, parameters interface{}) error { // ReplyInterfaceNotFound sends a org.varlink.service errror reply to this method call func (c *Call) ReplyInterfaceNotFound(interfaceA string) error { - var out struct { - Interface string `json:"interface,omitempty"` - } + var out InterfaceNotFound out.Interface = interfaceA return doReplyError(c, "org.varlink.service.InterfaceNotFound", &out) } // ReplyMethodNotFound sends a org.varlink.service errror reply to this method call func (c *Call) ReplyMethodNotFound(method string) error { - var out struct { - Method string `json:"method,omitempty"` - } + var out MethodNotFound out.Method = method return doReplyError(c, "org.varlink.service.MethodNotFound", &out) } // ReplyMethodNotImplemented sends a org.varlink.service errror reply to this method call func (c *Call) ReplyMethodNotImplemented(method string) error { - var out struct { - Method string `json:"method,omitempty"` - } + var out MethodNotImplemented out.Method = method return doReplyError(c, "org.varlink.service.MethodNotImplemented", &out) } // ReplyInvalidParameter sends a org.varlink.service errror reply to this method call func (c *Call) ReplyInvalidParameter(parameter string) error { - var out struct { - Parameter string `json:"parameter,omitempty"` - } + var out InvalidParameter out.Parameter = parameter return doReplyError(c, "org.varlink.service.InvalidParameter", &out) } diff --git a/vendor/github.com/varlink/go/varlink/service.go b/vendor/github.com/varlink/go/varlink/service.go index cb461f917..abccffe6a 100644 --- a/vendor/github.com/varlink/go/varlink/service.go +++ b/vendor/github.com/varlink/go/varlink/service.go @@ -22,6 +22,7 @@ type serviceCall struct { Parameters *json.RawMessage `json:"parameters,omitempty"` More bool `json:"more,omitempty"` Oneway bool `json:"oneway,omitempty"` + Upgrade bool `json:"upgrade,omitempty"` } type serviceReply struct { @@ -50,7 +51,7 @@ type Service struct { } // ServiceTimoutError helps API users to special-case timeouts. -type ServiceTimeoutError struct {} +type ServiceTimeoutError struct{} func (ServiceTimeoutError) Error() string { return "service timeout" @@ -73,7 +74,7 @@ func (s *Service) getInterfaceDescription(c Call, name string) error { return c.replyGetInterfaceDescription(description) } -func (s *Service) handleMessage(writer *bufio.Writer, request []byte) error { +func (s *Service) handleMessage(reader *bufio.Reader, writer *bufio.Writer, request []byte) error { var in serviceCall err := json.Unmarshal(request, &in) @@ -83,7 +84,8 @@ func (s *Service) handleMessage(writer *bufio.Writer, request []byte) error { } c := Call{ - writer: writer, + Reader: reader, + Writer: writer, in: &in, } @@ -129,7 +131,7 @@ func (s *Service) handleConnection(conn net.Conn, wg *sync.WaitGroup) { break } - err = s.handleMessage(writer, request[:len(request)-1]) + err = s.handleMessage(reader, writer, request[:len(request)-1]) if err != nil { // FIXME: report error //fmt.Fprintf(os.Stderr, "handleMessage: %v", err) @@ -201,11 +203,11 @@ func getListener(protocol string, address string) (net.Listener, error) { func (s *Service) refreshTimeout(timeout time.Duration) error { switch l := s.listener.(type) { case *net.UnixListener: - if err:= l.SetDeadline(time.Now().Add(timeout)); err != nil { + if err := l.SetDeadline(time.Now().Add(timeout)); err != nil { return err } case *net.TCPListener: - if err:= l.SetDeadline(time.Now().Add(timeout)); err != nil { + if err := l.SetDeadline(time.Now().Add(timeout)); err != nil { return err } |