Merge pull request #2227 from baude/remoteexport

podman-remote import|export
This commit is contained in:
OpenShift Merge Robot
2019-02-05 19:33:06 +01:00
committed by GitHub
22 changed files with 542 additions and 174 deletions

View File

@ -12,8 +12,6 @@ func getAppCommands() []cli.Command {
createCommand, createCommand,
diffCommand, diffCommand,
execCommand, execCommand,
exportCommand,
importCommand,
killCommand, killCommand,
kubeCommand, kubeCommand,
loadCommand, loadCommand,

View File

@ -8,6 +8,7 @@ import (
var ( var (
containerSubCommands = []cli.Command{ containerSubCommands = []cli.Command{
exportCommand,
inspectCommand, inspectCommand,
} }
containerDescription = "Manage containers" containerDescription = "Manage containers"

View File

@ -1,12 +1,9 @@
package main package main
import ( import (
"io/ioutil"
"os" "os"
"strconv"
"github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod/adapter"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -43,7 +40,7 @@ func exportCmd(c *cli.Context) error {
rootless.SetSkipStorageSetup(true) rootless.SetSkipStorageSetup(true)
} }
runtime, err := libpodruntime.GetRuntime(c) runtime, err := adapter.GetRuntime(c)
if err != nil { if err != nil {
return errors.Wrapf(err, "could not get runtime") return errors.Wrapf(err, "could not get runtime")
} }
@ -58,52 +55,18 @@ func exportCmd(c *cli.Context) error {
} }
output := c.String("output") 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" { if output == "/dev/stdout" {
file := os.Stdout file := os.Stdout
if logrus.IsTerminal(file) { if logrus.IsTerminal(file) {
return errors.Errorf("refusing to export to terminal. Use -o flag or redirect") return errors.Errorf("refusing to export to terminal. Use -o flag or redirect")
} }
} }
if err := validateFileName(output); err != nil { if err := validateFileName(output); err != nil {
return err return err
} }
return runtime.Export(args[0], c.String("output"))
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)
} }

View File

@ -8,6 +8,7 @@ import (
var ( var (
imageSubCommands = []cli.Command{ imageSubCommands = []cli.Command{
importCommand,
historyCommand, historyCommand,
imageExistsCommand, imageExistsCommand,
inspectCommand, inspectCommand,

View File

@ -2,16 +2,8 @@ package main
import ( import (
"fmt" "fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod/adapter"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/util"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -51,7 +43,7 @@ func importCmd(c *cli.Context) error {
return err return err
} }
runtime, err := libpodruntime.GetRuntime(c) runtime, err := adapter.GetRuntime(c)
if err != nil { if err != nil {
return errors.Wrapf(err, "could not get runtime") return errors.Wrapf(err, "could not get runtime")
} }
@ -60,7 +52,6 @@ func importCmd(c *cli.Context) error {
var ( var (
source string source string
reference string reference string
writer io.Writer
) )
args := c.Args() args := c.Args()
@ -80,67 +71,13 @@ func importCmd(c *cli.Context) error {
return err return err
} }
changes := v1.ImageConfig{} quiet := c.Bool("quiet")
if c.IsSet("change") || c.IsSet("c") { if runtime.Remote {
changes, err = util.GetImageConfig(c.StringSlice("change")) quiet = false
if err != nil {
return errors.Wrapf(err, "error adding config changes to image %q", source)
}
} }
iid, err := runtime.Import(getContext(), source, reference, c.StringSlice("change"), c.String("message"), quiet)
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)
if err == nil { if err == nil {
fmt.Println(newImage.ID()) fmt.Println(iid)
} }
return err 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
}

View File

@ -88,9 +88,11 @@ func main() {
app.Commands = []cli.Command{ app.Commands = []cli.Command{
containerCommand, containerCommand,
exportCommand,
historyCommand, historyCommand,
imageCommand, imageCommand,
imagesCommand, imagesCommand,
importCommand,
infoCommand, infoCommand,
inspectCommand, inspectCommand,
pullCommand, pullCommand,

View File

@ -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 # 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). # 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 # 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 # 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. # development of Podman only and generally should not be used.
method ContainerStateData(name: string) -> (config: string) 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. # ImageNotFound means the image could not be found by the provided name or ID in local storage.
error ImageNotFound (name: string) error ImageNotFound (name: string)

View File

@ -34,3 +34,14 @@ func (r RemoteRuntime) Connect() (*varlink.Connection, error) {
} }
return connection, nil 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
}

View File

@ -4,12 +4,17 @@ package adapter
import ( import (
"context" "context"
"github.com/pkg/errors"
"io" "io"
"io/ioutil"
"os"
"strconv"
"github.com/containers/image/types" "github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -104,3 +109,49 @@ func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) {
func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { func (r *LocalRuntime) PruneImages(all bool) ([]string, error) {
return r.ImageRuntime().PruneImages(all) 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)
}

View File

@ -3,11 +3,13 @@
package adapter package adapter
import ( import (
"bufio"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"io" "io"
"os"
"strings" "strings"
"time" "time"
@ -324,6 +326,84 @@ func (r *LocalRuntime) Config(name string) *libpod.ContainerConfig {
return &data return &data
} }
// PruneImages is the wrapper call for a remote-client to prune images
func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { func (r *LocalRuntime) PruneImages(all bool) ([]string, error) {
return iopodman.ImagesPrune().Call(r.Conn, all) 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)
}

View File

@ -2,9 +2,11 @@ package libpod
import ( import (
"context" "context"
"io/ioutil"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"time" "time"
@ -521,3 +523,44 @@ func isNamedVolume(volName string) bool {
} }
return false 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)
}

View File

@ -3,9 +3,16 @@ package libpod
import ( import (
"context" "context"
"fmt" "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/buildah/imagebuildah"
"github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/pkg/errors" "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...) _, _, err := imagebuildah.BuildDockerfiles(ctx, r.store, options, dockerfiles...)
return err 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
}

View File

@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"syscall" "syscall"
"time" "time"
@ -194,15 +195,25 @@ func (i *LibpodAPI) ListContainerChanges(call iopodman.VarlinkCall, name string)
} }
// ExportContainer ... // 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) ctr, err := i.Runtime.LookupContainer(name)
if err != nil { if err != nil {
return call.ReplyContainerNotFound(name) 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()) return call.ReplyErrorOccurred(err.Error())
} }
return call.ReplyExportContainer(path)
defer outputFile.Close()
if outPath == "" {
outPath = outputFile.Name()
}
if err := ctr.Export(outPath); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyExportContainer(outPath)
} }
// GetContainerStats ... // GetContainerStats ...

View File

@ -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 // 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) configChanges, err := util.GetImageConfig(changes)
if err != nil { if err != nil {
return call.ReplyErrorOccurred(err.Error()) return call.ReplyErrorOccurred(err.Error())
@ -516,6 +516,12 @@ func (i *LibpodAPI) ImportImage(call iopodman.VarlinkCall, source, reference, me
if err != nil { if err != nil {
return call.ReplyErrorOccurred(err.Error()) return call.ReplyErrorOccurred(err.Error())
} }
if delete {
if err := os.Remove(source); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
}
return call.ReplyImportImage(newImage.ID()) return call.ReplyImportImage(newImage.ID())
} }

View File

@ -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()
}

View File

@ -1,5 +1,3 @@
// +build !remoteclient
package integration package integration
import ( import (
@ -37,6 +35,7 @@ var _ = Describe("Podman export", func() {
}) })
It("podman export output flag", func() { It("podman export output flag", func() {
SkipIfRemote()
_, ec, cid := podmanTest.RunLsContainer("") _, ec, cid := podmanTest.RunLsContainer("")
Expect(ec).To(Equal(0)) Expect(ec).To(Equal(0))

View File

@ -5,13 +5,13 @@ package varlink
import ( import (
"bufio" "bufio"
"io" "io"
"log"
"net" "net"
"os/exec" "os/exec"
) )
type PipeCon struct { type PipeCon struct {
net.Conn net.Conn
cmd *exec.Cmd
reader *io.ReadCloser reader *io.ReadCloser
writer *io.WriteCloser writer *io.WriteCloser
} }
@ -25,6 +25,8 @@ func (p PipeCon) Close() error {
if err2 != nil { if err2 != nil {
return err2 return err2
} }
p.cmd.Wait()
return nil return nil
} }
@ -42,18 +44,15 @@ func NewBridge(bridge string) (*Connection, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
c.conn = PipeCon{nil, &r, &w} c.conn = PipeCon{nil, cmd, &r, &w}
c.address = "" c.address = ""
c.reader = bufio.NewReader(r) c.Reader = bufio.NewReader(r)
c.writer = bufio.NewWriter(w) c.Writer = bufio.NewWriter(w)
go func() { err = cmd.Start()
err := cmd.Run() if err != nil {
if err != nil { return nil, err
log.Fatal(err) }
}
}()
return &c, nil return &c, nil
} }

View File

@ -3,13 +3,13 @@ package varlink
import ( import (
"bufio" "bufio"
"io" "io"
"log"
"net" "net"
"os/exec" "os/exec"
) )
type PipeCon struct { type PipeCon struct {
net.Conn net.Conn
cmd *exec.Cmd
reader *io.ReadCloser reader *io.ReadCloser
writer *io.WriteCloser writer *io.WriteCloser
} }
@ -23,6 +23,8 @@ func (p PipeCon) Close() error {
if err2 != nil { if err2 != nil {
return err2 return err2
} }
p.cmd.Wait()
return nil return nil
} }
@ -40,18 +42,15 @@ func NewBridge(bridge string) (*Connection, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
c.conn = PipeCon{nil, &r, &w} c.conn = PipeCon{nil, cmd, &r, &w}
c.address = "" c.address = ""
c.reader = bufio.NewReader(r) c.reader = bufio.NewReader(r)
c.writer = bufio.NewWriter(w) c.writer = bufio.NewWriter(w)
go func() { err = cmd.Start()
err := cmd.Run() if err != nil {
if err != nil { return nil, err
log.Fatal(err) }
}
}()
return &c, nil return &c, nil
} }

View File

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"strings" "strings"
) )
@ -11,9 +12,11 @@ import (
// client can be terminated by returning an error from the call instead // client can be terminated by returning an error from the call instead
// of sending a reply or error reply. // of sending a reply or error reply.
type Call struct { type Call struct {
writer *bufio.Writer *bufio.Reader
*bufio.Writer
in *serviceCall in *serviceCall
Continues bool Continues bool
Upgrade bool
} }
// WantsMore indicates if the calling client accepts more than one reply to this method call. // 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 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. // IsOneway indicate that the calling client does not expect a reply.
func (c *Call) IsOneway() bool { func (c *Call) IsOneway() bool {
return c.in.Oneway return c.in.Oneway
@ -45,11 +53,18 @@ func (c *Call) sendMessage(r *serviceReply) error {
} }
b = append(b, 0) b = append(b, 0)
_, e = c.writer.Write(b) _, e = c.Writer.Write(b)
if e != nil { if e != nil {
if e == io.EOF {
return io.ErrUnexpectedEOF
}
return e 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. // Reply sends a reply to this method call.

View File

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net" "net"
"strings" "strings"
) )
@ -15,15 +16,59 @@ const (
More = 1 << iota More = 1 << iota
Oneway = 1 << iota Oneway = 1 << iota
Continues = 1 << iota Continues = 1 << iota
Upgrade = 1 << iota
) )
// Error is a varlink error returned from a method call. // Error is a varlink error returned from a method call.
type Error struct { type Error struct {
error
Name string Name string
Parameters interface{} 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, &param)
if err != nil {
return e
}
}
return &param
case "org.varlink.service.MethodNotFound":
var param MethodNotFound
if errorRawParameters != nil {
err := json.Unmarshal(*errorRawParameters, &param)
if err != nil {
return e
}
}
return &param
case "org.varlink.service.MethodNotImplemented":
var param MethodNotImplemented
if errorRawParameters != nil {
err := json.Unmarshal(*errorRawParameters, &param)
if err != nil {
return e
}
}
return &param
case "org.varlink.service.InvalidParameter":
var param InvalidParameter
if errorRawParameters != nil {
err := json.Unmarshal(*errorRawParameters, &param)
if err != nil {
return e
}
}
return &param
}
return e
}
// Error returns the fully-qualified varlink error name. // Error returns the fully-qualified varlink error name.
func (e *Error) Error() string { func (e *Error) Error() string {
return e.Name return e.Name
@ -31,10 +76,11 @@ func (e *Error) Error() string {
// Connection is a connection from a client to a service. // Connection is a connection from a client to a service.
type Connection struct { type Connection struct {
io.Closer
address string address string
conn net.Conn conn net.Conn
reader *bufio.Reader Reader *bufio.Reader
writer *bufio.Writer Writer *bufio.Writer
} }
// Send sends a method call. It returns a receive() function which is called to retrieve the method reply. // 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"` Parameters interface{} `json:"parameters,omitempty"`
More bool `json:"more,omitempty"` More bool `json:"more,omitempty"`
Oneway bool `json:"oneway,omitempty"` Oneway bool `json:"oneway,omitempty"`
Upgrade bool `json:"upgrade,omitempty"`
} }
if (flags&More != 0) && (flags&Oneway != 0) { 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{ m := call{
Method: method, Method: method,
Parameters: parameters, Parameters: parameters,
More: flags&More != 0, More: flags&More != 0,
Oneway: flags&Oneway != 0, Oneway: flags&Oneway != 0,
Upgrade: flags&Upgrade != 0,
} }
b, err := json.Marshal(m) b, err := json.Marshal(m)
if err != nil { if err != nil {
@ -67,13 +122,19 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) (
} }
b = append(b, 0) b = append(b, 0)
_, err = c.writer.Write(b) _, err = c.Writer.Write(b)
if err != nil { if err != nil {
if err == io.EOF {
return nil, io.ErrUnexpectedEOF
}
return nil, err return nil, err
} }
err = c.writer.Flush() err = c.Writer.Flush()
if err != nil { if err != nil {
if err == io.EOF {
return nil, io.ErrUnexpectedEOF
}
return nil, err return nil, err
} }
@ -84,8 +145,11 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) (
Error string `json:"error"` Error string `json:"error"`
} }
out, err := c.reader.ReadBytes('\x00') out, err := c.Reader.ReadBytes('\x00')
if err != nil { if err != nil {
if err == io.EOF {
return 0, io.ErrUnexpectedEOF
}
return 0, err return 0, err
} }
@ -96,11 +160,11 @@ func (c *Connection) Send(method string, parameters interface{}, flags uint64) (
} }
if m.Error != "" { if m.Error != "" {
err = &Error{ e := &Error{
Name: m.Error, Name: m.Error,
Parameters: m.Parameters, Parameters: m.Parameters,
} }
return 0, err return 0, e.DispatchError()
} }
if m.Parameters != nil { if m.Parameters != nil {
@ -220,8 +284,8 @@ func NewConnection(address string) (*Connection, error) {
} }
c.address = address c.address = address
c.reader = bufio.NewReader(c.conn) c.Reader = bufio.NewReader(c.conn)
c.writer = bufio.NewWriter(c.conn) c.Writer = bufio.NewWriter(c.conn)
return &c, nil return &c, nil
} }

View File

@ -1,5 +1,42 @@
package varlink 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 { func doReplyError(c *Call, name string, parameters interface{}) error {
return c.sendMessage(&serviceReply{ return c.sendMessage(&serviceReply{
Error: name, 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 // ReplyInterfaceNotFound sends a org.varlink.service errror reply to this method call
func (c *Call) ReplyInterfaceNotFound(interfaceA string) error { func (c *Call) ReplyInterfaceNotFound(interfaceA string) error {
var out struct { var out InterfaceNotFound
Interface string `json:"interface,omitempty"`
}
out.Interface = interfaceA out.Interface = interfaceA
return doReplyError(c, "org.varlink.service.InterfaceNotFound", &out) return doReplyError(c, "org.varlink.service.InterfaceNotFound", &out)
} }
// ReplyMethodNotFound sends a org.varlink.service errror reply to this method call // ReplyMethodNotFound sends a org.varlink.service errror reply to this method call
func (c *Call) ReplyMethodNotFound(method string) error { func (c *Call) ReplyMethodNotFound(method string) error {
var out struct { var out MethodNotFound
Method string `json:"method,omitempty"`
}
out.Method = method out.Method = method
return doReplyError(c, "org.varlink.service.MethodNotFound", &out) return doReplyError(c, "org.varlink.service.MethodNotFound", &out)
} }
// ReplyMethodNotImplemented sends a org.varlink.service errror reply to this method call // ReplyMethodNotImplemented sends a org.varlink.service errror reply to this method call
func (c *Call) ReplyMethodNotImplemented(method string) error { func (c *Call) ReplyMethodNotImplemented(method string) error {
var out struct { var out MethodNotImplemented
Method string `json:"method,omitempty"`
}
out.Method = method out.Method = method
return doReplyError(c, "org.varlink.service.MethodNotImplemented", &out) return doReplyError(c, "org.varlink.service.MethodNotImplemented", &out)
} }
// ReplyInvalidParameter sends a org.varlink.service errror reply to this method call // ReplyInvalidParameter sends a org.varlink.service errror reply to this method call
func (c *Call) ReplyInvalidParameter(parameter string) error { func (c *Call) ReplyInvalidParameter(parameter string) error {
var out struct { var out InvalidParameter
Parameter string `json:"parameter,omitempty"`
}
out.Parameter = parameter out.Parameter = parameter
return doReplyError(c, "org.varlink.service.InvalidParameter", &out) return doReplyError(c, "org.varlink.service.InvalidParameter", &out)
} }

View File

@ -22,6 +22,7 @@ type serviceCall struct {
Parameters *json.RawMessage `json:"parameters,omitempty"` Parameters *json.RawMessage `json:"parameters,omitempty"`
More bool `json:"more,omitempty"` More bool `json:"more,omitempty"`
Oneway bool `json:"oneway,omitempty"` Oneway bool `json:"oneway,omitempty"`
Upgrade bool `json:"upgrade,omitempty"`
} }
type serviceReply struct { type serviceReply struct {
@ -50,7 +51,7 @@ type Service struct {
} }
// ServiceTimoutError helps API users to special-case timeouts. // ServiceTimoutError helps API users to special-case timeouts.
type ServiceTimeoutError struct {} type ServiceTimeoutError struct{}
func (ServiceTimeoutError) Error() string { func (ServiceTimeoutError) Error() string {
return "service timeout" return "service timeout"
@ -73,7 +74,7 @@ func (s *Service) getInterfaceDescription(c Call, name string) error {
return c.replyGetInterfaceDescription(description) 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 var in serviceCall
err := json.Unmarshal(request, &in) err := json.Unmarshal(request, &in)
@ -83,7 +84,8 @@ func (s *Service) handleMessage(writer *bufio.Writer, request []byte) error {
} }
c := Call{ c := Call{
writer: writer, Reader: reader,
Writer: writer,
in: &in, in: &in,
} }
@ -129,7 +131,7 @@ func (s *Service) handleConnection(conn net.Conn, wg *sync.WaitGroup) {
break break
} }
err = s.handleMessage(writer, request[:len(request)-1]) err = s.handleMessage(reader, writer, request[:len(request)-1])
if err != nil { if err != nil {
// FIXME: report error // FIXME: report error
//fmt.Fprintf(os.Stderr, "handleMessage: %v", err) //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 { func (s *Service) refreshTimeout(timeout time.Duration) error {
switch l := s.listener.(type) { switch l := s.listener.(type) {
case *net.UnixListener: 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 return err
} }
case *net.TCPListener: 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 return err
} }