diff options
45 files changed, 3434 insertions, 32 deletions
diff --git a/.gitignore b/.gitignore index cc7e7bd3e..c372735f5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ /test/checkseccomp/checkseccomp /test/copyimg/copyimg /build/ +cmd/podman/ioprojectatomicpodman/ioprojectatomicpodman.go diff --git a/Dockerfile.Fedora b/Dockerfile.Fedora index aa10a054f..1480647c0 100644 --- a/Dockerfile.Fedora +++ b/Dockerfile.Fedora @@ -79,4 +79,6 @@ RUN mkdir -p /etc/containers COPY test/policy.json /etc/containers/policy.json COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml +# Install varlink stuff +RUN pip3 install varlink WORKDIR /go/src/github.com/projectatomic/libpod @@ -15,7 +15,9 @@ MANDIR ?= ${PREFIX}/share/man SHAREDIR_CONTAINERS ?= ${PREFIX}/share/containers ETCDIR ?= ${DESTDIR}/etc ETCDIR_LIBPOD ?= ${ETCDIR}/crio +SYSTEMDDIR ?= ${PREFIX}/lib/systemd/system BUILDTAGS ?= seccomp $(shell hack/btrfs_tag.sh) $(shell hack/libdm_tag.sh) $(shell hack/btrfs_installed_tag.sh) $(shell hack/ostree_tag.sh) $(shell hack/selinux_tag.sh) +PYTHON ?= /usr/bin/python3 BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions OCIUMOUNTINSTALLDIR=$(PREFIX)/share/oci-umount/oci-umount.d @@ -65,7 +67,7 @@ ifeq ("$(wildcard $(GOPKGDIR))","") endif touch "$(GOPATH)/.gopathok" -lint: .gopathok +lint: .gopathok varlink_generate @echo "checking lint" @./.tool/lint @@ -101,6 +103,7 @@ endif rm -f test/copyimg/copyimg rm -f test/checkseccomp/checkseccomp rm -fr build/ + rm -f cmd/podman/ioprojectatomicpodman/ioprojectatomicpodman.go libpodimage: docker build -t ${LIBPOD_IMAGE} . @@ -126,19 +129,20 @@ shell: libpodimage testunit: libpodimage docker run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${LIBPOD_IMAGE} make localunit -localunit: +localunit: varlink_generate $(GO) test -tags "$(BUILDTAGS)" -cover $(PACKAGES) ginkgo: ginkgo -v test/e2e/ -localintegration: test-binaries +localintegration: varlink_generate test-binaries ginkgo -v -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/. + sh test/varlink/run_varlink_tests.sh vagrant-check: BOX=$(BOX) sh ./vagrant.sh -binaries: podman +binaries: varlink_generate podman test-binaries: test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp @@ -163,7 +167,7 @@ changelog: $(shell cat $(TMPFILE) >> changelog.txt) $(shell rm $(TMPFILE)) -install: .gopathok install.bin install.man install.cni +install: .gopathok install.bin install.man install.cni install.systemd install.bin: install ${SELINUXOPT} -D -m 755 bin/podman $(BINDIR)/podman @@ -189,6 +193,10 @@ install.docker: docker-docs install ${SELINUXOPT} -d -m 755 $(MANDIR)/man1 install ${SELINUXOPT} -m 644 docs/docker*.1 -t $(MANDIR)/man1 +install.systemd: + install ${SELINUXOPT} -m 644 contrib/varlink/io.projectatomic.podman.socket ${SYSTEMDDIR}/io.projectatomic.podman.socket + install ${SELINUXOPT} -m 644 contrib/varlink/io.projectatomic.podman.service ${SYSTEMDDIR}/io.projectatomic.podman.service + uninstall: for i in $(filter %.1,$(MANPAGES)); do \ rm -f $(MANDIR)/man1/$$(basename $${i}); \ @@ -229,6 +237,14 @@ install.tools: .install.gitvalidation .install.gometalinter .install.md2man make all install; \ fi +.install.varlink: .gopathok + $(GO) get -u github.com/varlink/go/varlink + $(GO) get -u github.com/varlink/go/cmd/varlink-go-interface-generator + +varlink_generate: .gopathok .install.varlink + rm -f cmd/podman/ioprojectatomicpodman/ioprojectatomicpodman.go + $(GO) generate ./cmd/podman/ioprojectatomicpodman/... + validate: gofmt .gitvalidation .PHONY: \ diff --git a/cmd/podman/info.go b/cmd/podman/info.go index 89f32a258..1c4ba2515 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -79,6 +79,6 @@ func debugInfo(c *cli.Context) map[string]interface{} { info["compiler"] = runtime.Compiler info["go version"] = runtime.Version() info["podman version"] = c.App.Version - info["git commit"] = gitCommit + info["git commit"] = libpod.GitCommit return info } diff --git a/cmd/podman/ioprojectatomicpodman/generate.go b/cmd/podman/ioprojectatomicpodman/generate.go new file mode 100644 index 000000000..b24234f0a --- /dev/null +++ b/cmd/podman/ioprojectatomicpodman/generate.go @@ -0,0 +1,3 @@ +package ioprojectatomicpodman + +//go:generate $GOPATH/bin/varlink-go-interface-generator io.projectatomic.podman.varlink diff --git a/cmd/podman/ioprojectatomicpodman/io.projectatomic.podman.varlink b/cmd/podman/ioprojectatomicpodman/io.projectatomic.podman.varlink new file mode 100644 index 000000000..00a99017c --- /dev/null +++ b/cmd/podman/ioprojectatomicpodman/io.projectatomic.podman.varlink @@ -0,0 +1,65 @@ +# Podman Service Interface +interface io.projectatomic.podman + +type Version ( + version: string, + go_version: string, + git_commit: string, + built: int, + os_arch: string +) + +type NotImplemented ( + comment: string +) + +type StringResponse ( + message: string +) + +# System +method Ping() -> (ping: StringResponse) +method GetVersion() -> (version: Version) + +# Containers +method ListContainers() -> (notimplemented: NotImplemented) +method CreateContainer() -> (notimplemented: NotImplemented) +method InspectContainer() -> (notimplemented: NotImplemented) +method ListContainerProcesses() -> (notimplemented: NotImplemented) +method GetContainerLogs() -> (notimplemented: NotImplemented) +method ListContainerChanges() -> (notimplemented: NotImplemented) +method ExportContainer() -> (notimplemented: NotImplemented) +method GetContainerStats() -> (notimplemented: NotImplemented) +method ResizeContainerTty() -> (notimplemented: NotImplemented) +method StartContainer() -> (notimplemented: NotImplemented) +method StopContainer() -> (notimplemented: NotImplemented) +method RestartContainer() -> (notimplemented: NotImplemented) +method KillContainer() -> (notimplemented: NotImplemented) +method UpdateContainer() -> (notimplemented: NotImplemented) +method RenameContainer() -> (notimplemented: NotImplemented) +method PauseContainer() -> (notimplemented: NotImplemented) +method UnpauseContainer() -> (notimplemented: NotImplemented) +method AttachToContainer() -> (notimplemented: NotImplemented) +method WaitContainer() -> (notimplemented: NotImplemented) +method RemoveContainer() -> (notimplemented: NotImplemented) +method DeleteStoppedContainers() -> (notimplemented: NotImplemented) + +# Images +method ListImages() -> (notimplemented: NotImplemented) +method BuildImage() -> (notimplemented: NotImplemented) +method CreateImage() -> (notimplemented: NotImplemented) +method InspectImage() -> (notimplemented: NotImplemented) +method HistoryImage() -> (notimplemented: NotImplemented) +method PushImage() -> (notimplemented: NotImplemented) +method TagImage() -> (notimplemented: NotImplemented) +method RemoveImage() -> (notimplemented: NotImplemented) +method SearchImage() -> (notimplemented: NotImplemented) +method DeleteUnusedImages() -> (notimplemented: NotImplemented) +method CreateFromContainer() -> (notimplemented: NotImplemented) +method ImportImage() -> (notimplemented: NotImplemented) +method ExportImage() -> (notimplemented: NotImplemented) +method PullImage() -> (notimplemented: NotImplemented) + + +# Something failed +error ActionFailed (reason: string) diff --git a/cmd/podman/main.go b/cmd/podman/main.go index ef11f7905..a283c2622 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -70,6 +70,7 @@ func main() { topCommand, umountCommand, unpauseCommand, + varlinkCommand, versionCommand, waitCommand, } diff --git a/cmd/podman/varlink.go b/cmd/podman/varlink.go new file mode 100644 index 000000000..75ddc6c4d --- /dev/null +++ b/cmd/podman/varlink.go @@ -0,0 +1,60 @@ +package main + +import ( + "github.com/pkg/errors" + "github.com/projectatomic/libpod/cmd/podman/ioprojectatomicpodman" + "github.com/projectatomic/libpod/pkg/varlinkapi" + "github.com/projectatomic/libpod/version" + "github.com/urfave/cli" + "github.com/varlink/go/varlink" +) + +var ( + varlinkDescription = ` + podman varlink + + run varlink interface +` + varlinkFlags = []cli.Flag{} + varlinkCommand = cli.Command{ + Name: "varlink", + Usage: "Run varlink interface", + Description: varlinkDescription, + Flags: varlinkFlags, + Action: varlinkCmd, + ArgsUsage: "VARLINK_URI", + } +) + +func varlinkCmd(c *cli.Context) error { + args := c.Args() + if len(args) < 1 { + return errors.Errorf("you must provide a varlink URI") + } + + var varlinkInterfaces = []*ioprojectatomicpodman.VarlinkInterface{varlinkapi.VarlinkLibpod} + // Register varlink service. The metadata can be retrieved with: + // $ varlink info [varlink address URI] + service, err := varlink.NewService( + "Atomic", + "podman", + version.Version, + "https://github.com/projectatomic/libpod", + ) + if err != nil { + return errors.Wrapf(err, "unable to create new varlink service") + } + + for _, i := range varlinkInterfaces { + if err := service.RegisterInterface(i); err != nil { + return errors.Errorf("unable to register varlink interface %v", i) + } + } + + // Run the varlink server at the given address + if err = service.Listen(args[0], 0); err != nil { + return errors.Errorf("unable to start varlink service") + } + + return nil +} diff --git a/cmd/podman/version.go b/cmd/podman/version.go index be9b406e7..952cf32d3 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -2,41 +2,30 @@ package main import ( "fmt" - "runtime" - "strconv" "time" + "github.com/pkg/errors" + "github.com/projectatomic/libpod/libpod" "github.com/urfave/cli" ) -// Overwritten at build time -var ( - // gitCommit is the commit that the binary is being built from. - // It will be populated by the Makefile. - gitCommit string - // buildInfo is the time at which the binary was built - // It will be populated by the Makefile. - buildInfo string -) - // versionCmd gets and prints version info for version command func versionCmd(c *cli.Context) error { - fmt.Println("Version: ", c.App.Version) - fmt.Println("Go Version: ", runtime.Version()) - if gitCommit != "" { - fmt.Println("Git Commit: ", gitCommit) + output, err := libpod.GetVersion() + if err != nil { + errors.Wrapf(err, "unable to determine version") + } + fmt.Println("Version: ", output.Version) + fmt.Println("Go Version: ", output.GoVersion) + if output.GitCommit != "" { + fmt.Println("Git Commit: ", output.GitCommit) } - if buildInfo != "" { - // Converts unix time from string to int64 - buildTime, err := strconv.ParseInt(buildInfo, 10, 64) - if err != nil { - return err - } - // Prints out the build time in readable format - fmt.Println("Built: ", time.Unix(buildTime, 0).Format(time.ANSIC)) + // Prints out the build time in readable format + if libpod.BuildInfo != "" { + fmt.Println("Built: ", time.Unix(output.Built, 0).Format(time.ANSIC)) } - fmt.Println("OS/Arch: ", runtime.GOOS+"/"+runtime.GOARCH) + fmt.Println("OS/Arch: ", output.OsArch) return nil } diff --git a/commands.md b/commands.md index da6852b01..a55bc3612 100644 --- a/commands.md +++ b/commands.md @@ -42,5 +42,6 @@ | [podman-top(1)](/docs/podman-top.1.md) | Display the running processes of a container |[![...](/docs/play.png)](https://asciinema.org/a/5WCCi1LXwSuRbvaO9cBUYf3fk)| | [podman-umount(1)](/docs/podman-umount.1.md) | Unmount a working container's root filesystem |[![...](/docs/play.png)](https://asciinema.org/a/MZPTWD5CVs3dMREkBxQBY9C5z)| | [podman-unpause(1)](/docs/podman-unpause.1.md) | Unpause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)| +| [podman-varlink(1)](/docs/podman-varlink.1.md) | Run the varlink backend || | [podman-version(1)](/docs/podman-version.1.md) | Display the version information |[![...](/docs/play.png)](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)| | [podman-wait(1)](/docs/podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes |[![...](/docs/play.png)](https://asciinema.org/a/QNPGKdjWuPgI96GcfkycQtah0)| diff --git a/completions/bash/podman b/completions/bash/podman index 3cb48b156..4c9be0ab2 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1514,6 +1514,14 @@ _podman_unpause() { _complete_ "$options_with_args" "$boolean_options" } +_podman_varlink() { + local options_with_args=" + --help -h + " + local boolean_options="" + _complete_ "$options_with_args" "$boolean_options" +} + _podman_wait() { local options_with_args="" local boolean_options=" @@ -1633,6 +1641,7 @@ _podman_podman() { umount unmount unpause + varlink version wait " diff --git a/contrib/varlink/io.projectatomic.podman.service b/contrib/varlink/io.projectatomic.podman.service new file mode 100644 index 000000000..5b5c43134 --- /dev/null +++ b/contrib/varlink/io.projectatomic.podman.service @@ -0,0 +1,12 @@ +[Unit] +Description=Pod Manager +Requires=io.projectatomic.podman.socket +After=io.projectatomic.podman.socket + +[Service] +Type=simple +ExecStart=/usr/bin/podman --varlink=unix:/run/io.projectatomic.podman + +[Install] +WantedBy=multi-user.target +Also=io.projectatomic.podman.socket diff --git a/contrib/varlink/io.projectatomic.podman.socket b/contrib/varlink/io.projectatomic.podman.socket new file mode 100644 index 000000000..d49b458a0 --- /dev/null +++ b/contrib/varlink/io.projectatomic.podman.socket @@ -0,0 +1,8 @@ +[Unit] +Description=Pod Manager Socket + +[Socket] +ListenStream=/run/io.projectatomic.podman + +[Install] +WantedBy=sockets.target diff --git a/docs/podman-varlink.1.md b/docs/podman-varlink.1.md new file mode 100644 index 000000000..af50ad9dc --- /dev/null +++ b/docs/podman-varlink.1.md @@ -0,0 +1,38 @@ +% podman(1) podman-varlink - Waits on a container +% Brent Baude +# podman-varlink "1" "April 2018" "podman" + +## NAME +podman varlink - Runs the varlink backend interface + +## SYNOPSIS +**podman varlink** +[**--help**|**-h**] +VARLINK_URI + +## DESCRIPTION +Starts the varlink service that allows varlink clients to interact with podman. +<!-- +More will go here as the docs and api firm up. +--> + +**podman [GLOBAL OPTIONS] varlink ** + +## GLOBAL OPTIONS + +**--help, -h** + Print usage statement + +## EXAMPLES + + podman varlink unix:/run/io.projectatomic.podman +<!-- + TODO: More examples with TCP can be added when that works + as well. +--> + +## SEE ALSO +podman(1) + +## HISTORY +April 2018, Originally compiled by Brent Baude<bbaude@redhat.com> diff --git a/hack/verify-gofmt.sh b/hack/verify-gofmt.sh index c11ab3adb..af252a13b 100755 --- a/hack/verify-gofmt.sh +++ b/hack/verify-gofmt.sh @@ -10,7 +10,8 @@ find_files() { -wholename '*/vendor/*' \ \) -prune \ \) -name '*.go' \ - -not \( -wholename './_output/*' \) + -not \( -wholename './_output/*' \) \ + -not \( -wholename './cmd/podman/ioprojectatomicpodman/ioprojectatomicpodman.go' \) } FIX=0 GOFMT="gofmt -s" diff --git a/libpod/version.go b/libpod/version.go new file mode 100644 index 000000000..9bc4fe616 --- /dev/null +++ b/libpod/version.go @@ -0,0 +1,50 @@ +package libpod + +import ( + "runtime" + "strconv" + + "github.com/projectatomic/libpod/cmd/podman/ioprojectatomicpodman" + podmanVersion "github.com/projectatomic/libpod/version" +) + +// Overwritten at build time +var ( + // GitCommit is the commit that the binary is being built from. + // It will be populated by the Makefile. + GitCommit string + // BuildInfo is the time at which the binary was built + // It will be populated by the Makefile. + BuildInfo string +) + +//Version is an output struct for varlink +type Version struct { + ioprojectatomicpodman.VarlinkInterface + Version string + GoVersion string + GitCommit string + Built int64 + OsArch string +} + +// GetVersion returns a VersionOutput struct for varlink and podman +func GetVersion() (Version, error) { + var err error + var buildTime int64 + if BuildInfo != "" { + // Converts unix time from string to int64 + buildTime, err = strconv.ParseInt(BuildInfo, 10, 64) + + if err != nil { + return Version{}, err + } + } + return Version{ + Version: podmanVersion.Version, + GoVersion: runtime.Version(), + GitCommit: GitCommit, + Built: buildTime, + OsArch: runtime.GOOS + "/" + runtime.GOARCH, + }, nil +} diff --git a/pkg/varlinkapi/config.go b/pkg/varlinkapi/config.go new file mode 100644 index 000000000..167270f09 --- /dev/null +++ b/pkg/varlinkapi/config.go @@ -0,0 +1,14 @@ +package varlinkapi + +import "github.com/projectatomic/libpod/cmd/podman/ioprojectatomicpodman" + +// LibpodAPI is the basic varlink struct for libpod +type LibpodAPI struct { + ioprojectatomicpodman.VarlinkInterface +} + +var ( + lp = LibpodAPI{} + // VarlinkLibpod instantiation + VarlinkLibpod = ioprojectatomicpodman.VarlinkNew(&lp) +) diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go new file mode 100644 index 000000000..e58cab497 --- /dev/null +++ b/pkg/varlinkapi/containers.go @@ -0,0 +1,111 @@ +package varlinkapi + +import ( + "github.com/projectatomic/libpod/cmd/podman/ioprojectatomicpodman" +) + +// ListContainers ... +func (i *LibpodAPI) ListContainers(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("ListContainers") +} + +// CreateContainer ... +func (i *LibpodAPI) CreateContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("CreateContainer") +} + +// InspectContainer ... +func (i *LibpodAPI) InspectContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("InspectContainer") +} + +// ListContainerProcesses ... +func (i *LibpodAPI) ListContainerProcesses(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("ListContainerProcesses") +} + +// GetContainerLogs ... +func (i *LibpodAPI) GetContainerLogs(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("GetContainerLogs") +} + +// ListContainerChanges ... +func (i *LibpodAPI) ListContainerChanges(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("ListContianerChanges") +} + +// ExportContainer ... +func (i *LibpodAPI) ExportContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("ExportContainer") +} + +// GetContainerStats ... +func (i *LibpodAPI) GetContainerStats(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("GetContainerStates") +} + +// ResizeContainerTty ... +func (i *LibpodAPI) ResizeContainerTty(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("ResizeContainerTty") +} + +// StartContainer ... +func (i *LibpodAPI) StartContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("StartContainer") +} + +// StopContainer ... +func (i *LibpodAPI) StopContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("StopContainer") +} + +// RestartContainer ... +func (i *LibpodAPI) RestartContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("RestartContainer") +} + +// KillContainer ... +func (i *LibpodAPI) KillContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("KillContainer") +} + +// UpdateContainer ... +func (i *LibpodAPI) UpdateContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("UpdateContainer") +} + +// RenameContainer ... +func (i *LibpodAPI) RenameContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("RenameContainer") +} + +// PauseContainer ... +func (i *LibpodAPI) PauseContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("PauseContainer") +} + +// UnpauseContainer ... +func (i *LibpodAPI) UnpauseContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("UnpauseContainer") +} + +// AttachToContainer ... +// TODO: DO we also want a different one for websocket? +func (i *LibpodAPI) AttachToContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("AttachToContainer") +} + +// WaitContainer ... +func (i *LibpodAPI) WaitContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("WaitContainer") +} + +// RemoveContainer ... +func (i *LibpodAPI) RemoveContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("RemoveContainer") +} + +// DeleteStoppedContainers ... +func (i *LibpodAPI) DeleteStoppedContainers(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("DeleteContainer") +} diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go new file mode 100644 index 000000000..1de54e43b --- /dev/null +++ b/pkg/varlinkapi/images.go @@ -0,0 +1,75 @@ +package varlinkapi + +import ( + "github.com/projectatomic/libpod/cmd/podman/ioprojectatomicpodman" +) + +// ListImages ... +func (i *LibpodAPI) ListImages(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("ListImages") +} + +// BuildImage ... +func (i *LibpodAPI) BuildImage(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("BuildImage") +} + +// CreateImage ... +func (i *LibpodAPI) CreateImage(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("CreateImage") +} + +// InspectImage ... +func (i *LibpodAPI) InspectImage(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("InspectImage") +} + +// HistoryImage ... +func (i *LibpodAPI) HistoryImage(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("HistoryImage") +} + +// PushImage ... +func (i *LibpodAPI) PushImage(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("PushImage") +} + +// TagImage ... +func (i *LibpodAPI) TagImage(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("TagImage") +} + +// RemoveImage ... +func (i *LibpodAPI) RemoveImage(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("RemoveImage") +} + +// SearchImage ... +func (i *LibpodAPI) SearchImage(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("SearchImage") +} + +// DeleteUnusedImages ... +func (i *LibpodAPI) DeleteUnusedImages(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("DeleteUnusedImages") +} + +// CreateFromContainer ... +func (i *LibpodAPI) CreateFromContainer(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("CreateFromContainer") +} + +// ImportImage ... +func (i *LibpodAPI) ImportImage(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("ImportImage") +} + +// ExportImage ... +func (i *LibpodAPI) ExportImage(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("ExportImage") +} + +// PullImage ... +func (i *LibpodAPI) PullImage(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyMethodNotImplemented("PullImage") +} diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go new file mode 100644 index 000000000..c343f1245 --- /dev/null +++ b/pkg/varlinkapi/system.go @@ -0,0 +1,30 @@ +package varlinkapi + +import ( + "github.com/projectatomic/libpod/cmd/podman/ioprojectatomicpodman" + "github.com/projectatomic/libpod/libpod" +) + +// GetVersion ... +func (i *LibpodAPI) GetVersion(call ioprojectatomicpodman.VarlinkCall) error { + versionInfo, err := libpod.GetVersion() + if err != nil { + return err + } + + return call.ReplyGetVersion(ioprojectatomicpodman.Version{ + Version: versionInfo.Version, + Go_version: versionInfo.GoVersion, + Git_commit: versionInfo.GitCommit, + Built: versionInfo.Built, + Os_arch: versionInfo.OsArch, + }) +} + +// Ping returns a simple string "OK" response for clients to make sure +// the service is working. +func (i *LibpodAPI) Ping(call ioprojectatomicpodman.VarlinkCall) error { + return call.ReplyPing(ioprojectatomicpodman.StringResponse{ + Message: "OK", + }) +} diff --git a/test/varlink/run_varlink_tests.sh b/test/varlink/run_varlink_tests.sh new file mode 100644 index 000000000..9c247fec2 --- /dev/null +++ b/test/varlink/run_varlink_tests.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +set -x +if [ ! -n "${PYTHON+ }" ]; then + if hash python3 > /dev/null 2>&1 /dev/null; then + PYTHON=$(hash -t python3) + elif type python3 > /dev/null 2>&1; then + PYTHON=$(type python3 | awk '{print $3}') + elif hash python2 > /dev/null 2>&1; then + PYTHON=$(hash -t python2) + elif type python2 > /dev/null 2>&1; then + PYTHON=$(type python2 | awk '{print $3}') + else + PYTHON='/usr/bin/python' + fi +fi + +# Create temporary directory for storage +TMPSTORAGE=`mktemp -d` + +# Need a location to store the podman socket +mkdir /run/podman + +# Run podman in background without systemd for test purposes +bin/podman --storage-driver=vfs --root=${TMPSTORAGE}/crio --runroot=${TMPSTORAGE}/crio-run varlink unix:/run/podman/io.projectatomic.podman& + +# Record podman's pid to be killed later +PODMAN_PID=`echo $!` + +# Run tests +${PYTHON} -m unittest discover -s test/varlink/ + +# Kill podman +kill -9 ${PODMAN_PID} + +# Clean up +rm -fr ${TMPSTORAGE} diff --git a/test/varlink/test_containers.py b/test/varlink/test_containers.py new file mode 100644 index 000000000..651461d55 --- /dev/null +++ b/test/varlink/test_containers.py @@ -0,0 +1,99 @@ +import unittest +from varlink import (Client, VarlinkError) + + +address = "unix:/run/podman/io.projectatomic.podman" +client = Client(address=address) + + +def runErrorTest(tfunc): + try: + tfunc() + except VarlinkError as e: + return e.error() == "org.varlink.service.MethodNotImplemented" + return False + + +class ContainersAPI(unittest.TestCase): + def test_ListContainers(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.ListContainers)) + + def test_CreateContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.CreateContainer)) + + def test_InspecContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.InspectContainer)) + + def test_ListContainerProcesses(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.ListContainerProcesses)) + + def test_GetContainerLogs(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.GetContainerLogs)) + + def test_ListContainerChanges(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.ListContainerChanges)) + + def test_ExportContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.ExportContainer)) + + def test_GetContainerStats(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.GetContainerStats)) + + def test_ResizeContainerTty(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.ResizeContainerTty)) + + def test_StartContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.StartContainer)) + + def test_RestartContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.RestartContainer)) + + def test_KillContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.KillContainer)) + + def test_UpdateContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.UpdateContainer)) + + def test_RenameContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.RenameContainer)) + + def test_PauseContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.PauseContainer)) + + def test_UnpauseContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.UnpauseContainer)) + + def test_AttachToContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.AttachToContainer)) + + def test_WaitContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.WaitContainer)) + + def test_RemoveContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.RemoveContainer)) + + def test_DeleteContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.DeleteContainer)) + +if __name__ == '__main__': + unittest.main() diff --git a/test/varlink/test_images.py b/test/varlink/test_images.py new file mode 100644 index 000000000..ef1ab1088 --- /dev/null +++ b/test/varlink/test_images.py @@ -0,0 +1,71 @@ +import unittest +from varlink import (Client, VarlinkError) + + +address = "unix:/run/podman/io.projectatomic.podman" +client = Client(address=address) + + +def runErrorTest(tfunc): + try: + tfunc() + except VarlinkError as e: + return e.error() == "org.varlink.service.MethodNotImplemented" + return False + + +class ImagesAPI(unittest.TestCase): + def test_ListImages(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.ListImages)) + + def test_BuildImage(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.BuildImage)) + + def test_CreateImage(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.CreateImage)) + + def test_InspectImage(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.InspectImage)) + + def test_HistoryImage(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.HistoryImage)) + + def test_PushImage(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.PushImage)) + + def test_TagImage(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.TagImage)) + + def test_RemoveImage(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.TagImage)) + + def test_SearchImage(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.SearchImage)) + + def test_DeleteUnusedImages(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.DeleteUnusedImages)) + + def test_CreateFromContainer(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.CreateFromContainer)) + + def test_ImportImage(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.ImportImage)) + + def test_ExportImage(self): + podman = client.open("io.projectatomic.podman") + self.assertTrue(runErrorTest(podman.ExportImage)) + +if __name__ == '__main__': + unittest.main() diff --git a/test/varlink/test_system.py b/test/varlink/test_system.py new file mode 100644 index 000000000..6180d2068 --- /dev/null +++ b/test/varlink/test_system.py @@ -0,0 +1,21 @@ +import unittest +from varlink import (Client, VarlinkError) + + +address = "unix:/run/podman/io.projectatomic.podman" +client = Client(address=address) + +class SystemAPI(unittest.TestCase): + def test_ping(self): + podman = client.open("io.projectatomic.podman") + response = podman.Ping() + self.assertEqual("OK", response["ping"]["message"]) + + def test_GetVersion(self): + podman = client.open("io.projectatomic.podman") + response = podman.GetVersion() + for k in ["version", "go_version", "built", "os_arch"]: + self.assertTrue(k in response["version"].keys()) + +if __name__ == '__main__': + unittest.main() diff --git a/varlink_client.py b/varlink_client.py new file mode 100644 index 000000000..7446e3012 --- /dev/null +++ b/varlink_client.py @@ -0,0 +1,9 @@ +from varlink import (Client, VarlinkError) +import json + +address = "unix:/run/podman/io.projectatomic.podman" + +with Client(address=address) as client: + podman = client.open('io.projectatomic.podman') + response = podman.GetVersion() + print(json.dumps(response, indent=4, separators=(',', ': ')))
\ No newline at end of file diff --git a/vendor.conf b/vendor.conf index f7e2b5abb..487431232 100644 --- a/vendor.conf +++ b/vendor.conf @@ -87,3 +87,4 @@ k8s.io/client-go 7cd1d3291b7d9b1e2d54d4b69eb65995eaf8888e https://github.com/kub k8s.io/kube-openapi 275e2ce91dec4c05a4094a7b1daee5560b555ac9 https://github.com/kubernetes/kube-openapi k8s.io/utils 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e https://github.com/kubernetes/utils github.com/mrunalp/fileutils master +github.com/varlink/go master https://github.com/varlink/go diff --git a/vendor/github.com/varlink/go/.gitignore b/vendor/github.com/varlink/go/.gitignore new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/vendor/github.com/varlink/go/.gitignore diff --git a/vendor/github.com/varlink/go/.travis.yml b/vendor/github.com/varlink/go/.travis.yml new file mode 100644 index 000000000..990f12e0a --- /dev/null +++ b/vendor/github.com/varlink/go/.travis.yml @@ -0,0 +1,13 @@ +language: go +sudo: false +go: +- '1.9' +- 1.10.x +install: +- go get golang.org/x/tools/cmd/cover +- go get github.com/mattn/goveralls +script: +- '"$HOME/gopath/bin/goveralls" -show -service=travis-ci -repotoken "$COVERALLS_TOKEN"' +env: + global: + - secure: bjxOSgBfB+YooxNTkIDHAD+/X6g56qBWoYpB1JinuS5kmt3vSjfRSuXui71sGuha7jO2FOJja8HcpjOv3UP+qmmej9276o5VWrjS1AwnI95hSQQ4JHm293Z1QeojjRaxmoKrgn7i82Hn4qNdVLQA142s+SIdqOxtN6LDs7i0Yb4IuXoiMQHbd6kAAL95o9IUFPpYAdsXoQ6xnx+TXNiSwPPeh4m5CNKuTtmGTuMGaj8tXxttFKJhZcRzvOpDuh7luc9PSVnQgYmKE/3S9ehzGV8Lk4T8eC7587DY1GdYQKt1egJSE72L+PVnmoalWROaAGHZvYWsSAeNi1UIvcFwGbXBRpq7kz3DVfIULM8V67UAaF3dGYDN3Ae825mDjN5JDfml17AoEjMjI0LlBImZLX2EWIEN225JIREHdpG9seJkaN1ClcpvEIeYuThF2MiivP1EE8/w8S80yoO5nW76Py/th16OuaEiP9LdLsbXimObUPsS9Sr8qquf/PiVqRMMpVW88oOEG5HVn4Ra5B/xVC6nPEF88tE6p9+7RSz4rOWih8QmW+6SX6eo0BI9di4L779f/WfUrddN0JLIvEnRFZZ+pVF/oo+N2INNeIMsZBvG3FVo+Zxzo6SExXnSSpuf1bp140ZdinUMACq6BqK+9gj1C9vNRmqQJaEefrqutws= diff --git a/vendor/github.com/varlink/go/LICENSE b/vendor/github.com/varlink/go/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/varlink/go/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/varlink/go/Makefile b/vendor/github.com/varlink/go/Makefile new file mode 100644 index 000000000..3f1c0e6d6 --- /dev/null +++ b/vendor/github.com/varlink/go/Makefile @@ -0,0 +1,3 @@ +all: + go test -v ./... +.PHONY: all diff --git a/vendor/github.com/varlink/go/README.md b/vendor/github.com/varlink/go/README.md new file mode 100644 index 000000000..926a82bdd --- /dev/null +++ b/vendor/github.com/varlink/go/README.md @@ -0,0 +1,7 @@ +[![Build Status](https://travis-ci.org/varlink/go.svg?branch=master)](https://travis-ci.org/varlink/go) +[![Go Report Card](https://goreportcard.com/badge/github.com/varlink/go)](https://goreportcard.com/report/github.com/varlink/go) +[![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/varlink/go/varlink) +[![Coverage Status](https://coveralls.io/repos/github/varlink/go/badge.svg?branch=master)](https://coveralls.io/github/varlink/go?branch=master) +[![Release](https://img.shields.io/github/release/golang-standards/project-layout.svg?style=flat-square)](https://github.com/varlink/go/varlink/releases/latest) + +# go/varlink diff --git a/vendor/github.com/varlink/go/cmd/varlink-go-interface-generator/generator_test.go b/vendor/github.com/varlink/go/cmd/varlink-go-interface-generator/generator_test.go new file mode 100644 index 000000000..aa3a8565f --- /dev/null +++ b/vendor/github.com/varlink/go/cmd/varlink-go-interface-generator/generator_test.go @@ -0,0 +1,85 @@ +package main + +import ( + "strings" + "testing" +) + +func expect(t *testing.T, expected string, returned string) { + if strings.Compare(returned, expected) != 0 { + t.Fatalf("Expected(%d): `%s`\nGot(%d): `%s`\n", + len(expected), expected, + len(returned), returned) + } +} + +func TestIDLParser(t *testing.T) { + pkgname, b, err := generateTemplate(` +# Interface to jump a spacecraft to another point in space. The +# FTL Drive is the propulsion system to achieve faster-than-light +# travel through space. A ship making a properly calculated +# jump can arrive safely in planetary orbit, or alongside other +# ships or spaceborne objects. +interface org.example.ftl + +# The current state of the FTL drive and the amount of fuel +# available to jump. +type DriveCondition ( + state: (idle, spooling, busy), + booster: bool, + active_engines: [](id: int, state: bool), + tylium_level: int +) + +# Speed, trajectory and jump duration is calculated prior to +# activating the FTL drive. +type DriveConfiguration ( + speed: int, + trajectory: int, + duration: int +) + +# The galactic coordinates use the Sun as the origin. Galactic +# longitude is measured with primary direction from the Sun to +# the center of the galaxy in the galactic plane, while the +# galactic latitude measures the angle of the object above the +# galactic plane. +type Coordinate ( + longitude: float, + latitude: float, + distance: int +) + +# Monitor the drive. The method will reply with an update whenever +# the drive's state changes +method Monitor() -> (condition: DriveCondition) + +# Calculate the drive's jump parameters from the current +# position to the target position in the galaxy +method CalculateConfiguration( + current: Coordinate, + target: Coordinate +) -> (configuration: DriveConfiguration) + +# Jump to the calculated point in space +method Jump(configuration: DriveConfiguration) -> () + +# There is not enough tylium to jump with the given parameters +error NotEnoughEnergy () + +# The supplied parameters are outside the supported range +error ParameterOutOfRange (field: string) + +# some more coverage +method Foo(interface: string) -> (ret: (go: string, switch: bool, more: (t:bool, f:bool))) + `) + + if err != nil { + t.Fatalf("Error parsing %v", err) + } + expect(t, "orgexampleftl", pkgname) + if len(b) <= 0 { + t.Fatal("No generated go source") + } + // FIXME: compare b.String() against expected output +} diff --git a/vendor/github.com/varlink/go/cmd/varlink-go-interface-generator/main.go b/vendor/github.com/varlink/go/cmd/varlink-go-interface-generator/main.go new file mode 100644 index 000000000..1bcdb78de --- /dev/null +++ b/vendor/github.com/varlink/go/cmd/varlink-go-interface-generator/main.go @@ -0,0 +1,294 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path" + "strings" + + "github.com/varlink/go/varlink/idl" +) + +var goKeywords = map[string]struct{}{ + "break": {}, + "case": {}, + "chan": {}, + "const": {}, + "continue": {}, + "default": {}, + "defer": {}, + "else": {}, + "fallthrough": {}, + "for": {}, + "func": {}, + "go": {}, + "goto": {}, + "if": {}, + "import": {}, + "interface": {}, + "map": {}, + "package": {}, + "range": {}, + "return": {}, + "select": {}, + "struct": {}, + "switch": {}, + "type": {}, + "var": {}, +} + +func sanitizeGoName(name string) string { + if _, ok := goKeywords[name]; !ok { + return name + } + return name + "_" +} + +func writeType(b *bytes.Buffer, t *idl.Type, json bool, ident int) { + switch t.Kind { + case idl.TypeBool: + b.WriteString("bool") + + case idl.TypeInt: + b.WriteString("int64") + + case idl.TypeFloat: + b.WriteString("float64") + + case idl.TypeString, idl.TypeEnum: + b.WriteString("string") + + case idl.TypeArray: + b.WriteString("[]") + writeType(b, t.ElementType, json, ident) + + case idl.TypeMaybe: + b.WriteString("*") + writeType(b, t.ElementType, json, ident) + + case idl.TypeAlias: + b.WriteString(t.Alias) + + case idl.TypeStruct: + b.WriteString("struct{\n") + for _, field := range t.Fields { + for i := 0; i < ident+1; i++ { + b.WriteString("\t") + } + + b.WriteString(strings.Title(field.Name) + " ") + writeType(b, field.Type, json, ident+1) + if json { + b.WriteString(" `json:\"" + field.Name + "\"`") + } + b.WriteString("\n") + } + for i := 0; i < ident; i++ { + b.WriteString("\t") + } + b.WriteString("}") + } +} + +func generateTemplate(description string) (string, []byte, error) { + description = strings.TrimRight(description, "\n") + + midl, err := idl.New(description) + if err != nil { + return "", nil, err + } + + pkgname := strings.Replace(midl.Name, ".", "", -1) + + var b bytes.Buffer + b.WriteString("// Generated with github.com/varlink/go/cmd/varlink-go-interface-generator\n") + b.WriteString("package " + pkgname + "\n\n") + b.WriteString(`import "github.com/varlink/go/varlink"` + "\n\n") + + // Type declarations + for _, a := range midl.Aliases { + b.WriteString("type " + a.Name + " ") + writeType(&b, a.Type, true, 0) + b.WriteString("\n\n") + } + + // Local interface with all methods + b.WriteString("type " + pkgname + "Interface interface {\n") + for _, m := range midl.Methods { + b.WriteString("\t" + m.Name + "(c VarlinkCall") + for _, field := range m.In.Fields { + b.WriteString(", " + strings.Title(field.Name) + " ") + writeType(&b, field.Type, false, 1) + } + b.WriteString(") error\n") + } + b.WriteString("}\n\n") + + // Local object with all methods + b.WriteString("type VarlinkCall struct{ varlink.Call }\n\n") + + // Reply methods for all varlink errors + for _, e := range midl.Errors { + b.WriteString("func (c *VarlinkCall) Reply" + e.Name + "(") + for i, field := range e.Type.Fields { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(sanitizeGoName(field.Name) + " ") + writeType(&b, field.Type, false, 1) + } + b.WriteString(") error {\n") + if len(e.Type.Fields) > 0 { + b.WriteString("\tvar out ") + writeType(&b, e.Type, true, 1) + b.WriteString("\n") + for _, field := range e.Type.Fields { + switch field.Type.Kind { + case idl.TypeStruct, idl.TypeArray: + b.WriteString("\tout." + strings.Title(field.Name) + " = ") + writeType(&b, field.Type, true, 1) + b.WriteString("(" + sanitizeGoName(field.Name) + ")\n") + + default: + b.WriteString("\tout." + strings.Title(field.Name) + " = " + sanitizeGoName(field.Name) + "\n") + } + } + b.WriteString("\treturn c.ReplyError(\"" + midl.Name + "." + e.Name + "\", &out)\n") + } else { + b.WriteString("\treturn c.ReplyError(\"" + midl.Name + "." + e.Name + "\", nil)\n") + } + b.WriteString("}\n\n") + } + + // Reply methods for all varlink methods + for _, m := range midl.Methods { + b.WriteString("func (c *VarlinkCall) Reply" + m.Name + "(") + for i, field := range m.Out.Fields { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(sanitizeGoName(field.Name) + " ") + writeType(&b, field.Type, false, 1) + } + b.WriteString(") error {\n") + if len(m.Out.Fields) > 0 { + b.WriteString("\tvar out ") + writeType(&b, m.Out, true, 1) + b.WriteString("\n") + for _, field := range m.Out.Fields { + switch field.Type.Kind { + case idl.TypeStruct, idl.TypeArray: + b.WriteString("\tout." + strings.Title(field.Name) + " = ") + writeType(&b, field.Type, true, 1) + b.WriteString("(" + sanitizeGoName(field.Name) + ")\n") + + default: + b.WriteString("\tout." + strings.Title(field.Name) + " = " + sanitizeGoName(field.Name) + "\n") + } + } + b.WriteString("\treturn c.Reply(&out)\n") + } else { + b.WriteString("\treturn c.Reply(nil)\n") + } + b.WriteString("}\n\n") + } + + // Dummy methods for all varlink methods + for _, m := range midl.Methods { + b.WriteString("func (s *VarlinkInterface) " + m.Name + "(c VarlinkCall") + for _, field := range m.In.Fields { + b.WriteString(", " + sanitizeGoName(field.Name) + " ") + writeType(&b, field.Type, false, 1) + } + b.WriteString(") error {\n" + + "\treturn c.ReplyMethodNotImplemented(\"" + m.Name + "\")\n" + + "}\n\n") + } + + // Method call dispatcher + b.WriteString("func (s *VarlinkInterface) VarlinkDispatch(call varlink.Call, methodname string) error {\n" + + "\tswitch methodname {\n") + for _, m := range midl.Methods { + b.WriteString("\tcase \"" + m.Name + "\":\n") + if len(m.In.Fields) > 0 { + b.WriteString("\t\tvar in ") + writeType(&b, m.In, true, 2) + b.WriteString("\n") + b.WriteString("\t\terr := call.GetParameters(&in)\n" + + "\t\tif err != nil {\n" + + "\t\t\treturn call.ReplyInvalidParameter(\"parameters\")\n" + + "\t\t}\n") + b.WriteString("\t\treturn s." + pkgname + "Interface." + m.Name + "(VarlinkCall{call}") + if len(m.In.Fields) > 0 { + for _, field := range m.In.Fields { + switch field.Type.Kind { + case idl.TypeStruct, idl.TypeArray: + b.WriteString(", ") + writeType(&b, field.Type, false, 2) + b.WriteString("(in." + strings.Title(field.Name) + ")") + + default: + b.WriteString(", in." + strings.Title(field.Name)) + } + } + } + b.WriteString(")\n") + } else { + b.WriteString("\t\treturn s." + pkgname + "Interface." + m.Name + "(VarlinkCall{call})\n") + } + b.WriteString("\n") + } + b.WriteString("\tdefault:\n" + + "\t\treturn call.ReplyMethodNotFound(methodname)\n" + + "\t}\n" + + "}\n") + + // Varlink interface name + b.WriteString("func (s *VarlinkInterface) VarlinkGetName() string {\n" + + "\treturn `" + midl.Name + "`\n" + "}\n\n") + + // Varlink interface description + b.WriteString("func (s *VarlinkInterface) VarlinkGetDescription() string {\n" + + "\treturn `" + midl.Description + "\n`\n}\n\n") + + b.WriteString("type VarlinkInterface struct {\n" + + "\t" + pkgname + "Interface\n" + + "}\n\n") + + b.WriteString("func VarlinkNew(m " + pkgname + "Interface) *VarlinkInterface {\n" + + "\treturn &VarlinkInterface{m}\n" + + "}\n") + + return pkgname, b.Bytes(), nil +} + +func generateFile(varlinkFile string) { + file, err := ioutil.ReadFile(varlinkFile) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading file '%s': %s\n", varlinkFile, err) + os.Exit(1) + } + + pkgname, b, err := generateTemplate(string(file)) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing file '%s': %s\n", varlinkFile, err) + os.Exit(1) + } + + filename := path.Dir(varlinkFile) + "/" + pkgname + ".go" + err = ioutil.WriteFile(filename, b, 0660) + if err != nil { + fmt.Fprintf(os.Stderr, "Error writing file '%s': %s\n", filename, err) + os.Exit(1) + } +} + +func main() { + if len(os.Args) != 2 { + fmt.Printf("Usage: %s <file>\n", os.Args[0]) + os.Exit(1) + } + generateFile(os.Args[1]) +} diff --git a/vendor/github.com/varlink/go/cmd/varlink-go-type-generator/main.go b/vendor/github.com/varlink/go/cmd/varlink-go-type-generator/main.go new file mode 100644 index 000000000..dfbec5fb2 --- /dev/null +++ b/vendor/github.com/varlink/go/cmd/varlink-go-type-generator/main.go @@ -0,0 +1,142 @@ +package main + +import ( + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "log" + "os" +) + +func GoToVarlinkType(t types.Type) string { + switch u := t.(type) { + case *types.Basic: + if u.Info()&types.IsBoolean != 0 { + return "bool" + } + if u.Info()&types.IsInteger != 0 { + return "int" + } + if u.Info()&types.IsFloat != 0 { + return "float" + } + if u.Info()&types.IsString != 0 { + return "string" + } + return fmt.Sprintf("<<<%s>>>", t.String()) + + case *types.Named: + return u.Obj().Name() + + case *types.Map: + return fmt.Sprintf("<<<%s>>>", u.String()) + + case *types.Interface: + return fmt.Sprintf("<<<%s>>>", u.String()) + + case *types.Pointer: + return fmt.Sprintf("?%s", GoToVarlinkType(u.Elem())) + + case *types.Array: + return fmt.Sprintf("[]%s", GoToVarlinkType(u.Elem())) + + case *types.Slice: + return fmt.Sprintf("[]%s", GoToVarlinkType(u.Elem())) + + default: + return fmt.Sprintf("<<<%T %s>>>", t, u) + } + + return t.String() +} + +func PrintDefsUses(name string, fset *token.FileSet, files []*ast.File) error { + conf := types.Config{ + Importer: importer.Default(), + FakeImportC: true, + } + + info := &types.Info{ + Defs: make(map[*ast.Ident]types.Object), + } + + _, err := conf.Check(name, fset, files, info) + if err != nil { + return err // type error + } + + seen := map[string]interface{}{} + + for id, obj := range info.Defs { + if obj == nil { + continue + } + + if _, ok := seen[id.Name]; ok { + continue + } + + /* + if !obj.Exported() || obj.Pkg().Name() != name { + continue + } + */ + switch f := obj.Type().Underlying().(type) { + case *types.Struct: + if f.NumFields() > 0 { + fmt.Printf("type %s (\n", id.Name) + fmt.Printf("\t%s: %s", + f.Field(0).Name(), GoToVarlinkType(f.Field(0).Type())) + for i := 1; i < f.NumFields(); i++ { + fmt.Printf(",\n\t%s: %s", + f.Field(i).Name(), GoToVarlinkType(f.Field(i).Type())) + } + fmt.Printf("\n)\n\n") + } + } + seen[id.Name] = nil + } + + return nil +} + +func main() { + + path := os.Args[1] + fs := token.NewFileSet() + + if stat, err := os.Stat(path); err == nil && stat.IsDir() { + pkgs, err := parser.ParseDir(fs, path, nil, 0) + if err != nil { + fmt.Printf("parsing dir '%s': %s", path, err) + } + for name, pkg := range pkgs { + log.Println("Found package:", name) + + fset := make([]*ast.File, len(pkg.Files), len(pkg.Files)) + idx := 0 + for _, value := range pkg.Files { + fset[idx] = value + idx++ + } + + if err := PrintDefsUses(name, fs, fset); err != nil { + log.Print(err) // type error + } + } + } else { + + fset, err := parser.ParseFile(fs, path, nil, 0) + + if err != nil { + fmt.Printf("parsing file '%s': %s", path, err) + } + name := fset.Name.String() + if err := PrintDefsUses(name, fs, []*ast.File{fset}); err != nil { + log.Print(err) // type error + } + } +} diff --git a/vendor/github.com/varlink/go/golang-github-varlink-go.spec b/vendor/github.com/varlink/go/golang-github-varlink-go.spec new file mode 100644 index 000000000..85f5d1788 --- /dev/null +++ b/vendor/github.com/varlink/go/golang-github-varlink-go.spec @@ -0,0 +1,44 @@ +%global goipath github.com/varlink/go +Version: 0 +%gometa + +Name: %{goname} +Release: 1%{?dist} +Summary: Go bindings for varlink +License: ASL 2.0 +URL: %{gourl} +Source0: %{gosource} + +%description +Native Go bindings for the varlink protocol. + +%package devel +Summary: %{summary} +BuildArch: noarch + +%description devel +%{summary} + +This package contains library source intended for +building other packages which use import path with +%{gobaseipath} prefix. + +%prep +%forgesetup + +%build +%gobuildroot + +%install +gofiles=$(find . %{gofindfilter} -print) +%goinstall $gofiles + +%check + +%files devel -f devel.file-list +%license LICENSE +%doc README.md + +%changelog +* Tue Mar 20 2018 <info@varlink.org> 0-1 +- Version 0 diff --git a/vendor/github.com/varlink/go/varlink/call.go b/vendor/github.com/varlink/go/varlink/call.go new file mode 100644 index 000000000..c5d7cf7fd --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/call.go @@ -0,0 +1,86 @@ +package varlink + +import ( + "bufio" + "encoding/json" + "fmt" + "strings" +) + +// Call is a method call retrieved by a Service. The connection from the +// 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 + in *serviceCall + Continues bool +} + +// WantsMore indicates if the calling client accepts more than one reply to this method call. +func (c *Call) WantsMore() bool { + return c.in.More +} + +// IsOneShot indicate that the calling client does not expect a reply. +func (c *Call) IsOneShot() bool { + return c.in.OneShot +} + +// GetParameters retrieves the method call parameters. +func (c *Call) GetParameters(p interface{}) error { + if c.in.Parameters == nil { + return fmt.Errorf("empty parameters") + } + return json.Unmarshal(*c.in.Parameters, p) +} + +func (c *Call) sendMessage(r *serviceReply) error { + if c.in.OneShot { + return nil + } + + b, e := json.Marshal(r) + if e != nil { + return e + } + + b = append(b, 0) + _, e = c.writer.Write(b) + if e != nil { + return e + } + return c.writer.Flush() +} + +// Reply sends a reply to this method call. +func (c *Call) Reply(parameters interface{}) error { + if !c.Continues { + return c.sendMessage(&serviceReply{ + Parameters: parameters, + }) + } + + if !c.in.More { + return fmt.Errorf("call did not set more, it does not expect continues") + } + + return c.sendMessage(&serviceReply{ + Continues: true, + Parameters: parameters, + }) +} + +// ReplyError sends an error reply to this method call. +func (c *Call) ReplyError(name string, parameters interface{}) error { + r := strings.LastIndex(name, ".") + if r <= 0 { + return fmt.Errorf("invalid error name") + } + if name[:r] == "org.varlink.service" { + return fmt.Errorf("refused to send org.varlink.service errors") + } + return c.sendMessage(&serviceReply{ + Error: name, + Parameters: parameters, + }) +} diff --git a/vendor/github.com/varlink/go/varlink/connection.go b/vendor/github.com/varlink/go/varlink/connection.go new file mode 100644 index 000000000..2445072b6 --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/connection.go @@ -0,0 +1,197 @@ +package varlink + +import ( + "bufio" + "encoding/json" + "net" + "strings" +) + +// Error is a varlink error returned from a method call. +type Error struct { + Name string + Parameters interface{} +} + +// Error returns the fully-qualified varlink error name. +func (e *Error) Error() string { + return e.Name +} + +// Connection is a connection from a client to a service. +type Connection struct { + address string + conn net.Conn + reader *bufio.Reader + writer *bufio.Writer +} + +// Send sends a method call. +func (c *Connection) Send(method string, parameters interface{}, more bool) error { + type call struct { + Method string `json:"method"` + Parameters interface{} `json:"parameters,omitempty"` + More bool `json:"more,omitempty"` + OneShot bool `json:"oneshot,omitempty"` + } + m := call{ + Method: method, + Parameters: parameters, + More: more, + } + b, err := json.Marshal(m) + if err != nil { + return err + } + + b = append(b, 0) + _, err = c.writer.Write(b) + if err != nil { + return err + } + + return c.writer.Flush() +} + +// Receive receives a method reply. +func (c *Connection) Receive(parameters interface{}, continues *bool, oneshot *bool) error { + type reply struct { + Parameters *json.RawMessage `json:"parameters"` + Continues bool `json:"continues"` + Oneshot bool `json:"oneshot"` + Error string `json:"error"` + } + + out, err := c.reader.ReadBytes('\x00') + if err != nil { + return err + } + + var m reply + err = json.Unmarshal(out[:len(out)-1], &m) + if err != nil { + return err + } + + if m.Error != "" { + return &Error{ + Name: m.Error, + Parameters: m.Parameters, + } + } + + if continues != nil { + *continues = m.Continues + } + if oneshot != nil { + *oneshot = m.Oneshot + } + if parameters != nil && m.Parameters != nil { + return json.Unmarshal(*m.Parameters, parameters) + } + + return nil +} + +// Call sends a method call and returns the result of the call. +func (c *Connection) Call(method string, parameters interface{}, result interface{}) error { + err := c.Send(method, ¶meters, false) + if err != nil { + return err + } + + return c.Receive(result, nil, nil) +} + +// GetInterfaceDescription requests the interface description string from the service. +func (c *Connection) GetInterfaceDescription(name string) (string, error) { + type request struct { + Interface string `json:"interface"` + } + type reply struct { + Description string `json:"description"` + } + + var r reply + err := c.Call("org.varlink.service.GetInterfaceDescription", request{Interface: name}, &r) + if err != nil { + return "", err + } + + return r.Description, nil +} + +// GetInfo requests information about the service. +func (c *Connection) GetInfo(vendor *string, product *string, version *string, url *string, interfaces *[]string) error { + type reply struct { + Vendor string `json:"vendor"` + Product string `json:"product"` + Version string `json:"version"` + URL string `json:"url"` + Interfaces []string `json:"interfaces"` + } + + var r reply + err := c.Call("org.varlink.service.GetInfo", nil, &r) + if err != nil { + return err + } + + if vendor != nil { + *vendor = r.Vendor + } + if product != nil { + *product = r.Product + } + if version != nil { + *version = r.Version + } + if url != nil { + *url = r.URL + } + if interfaces != nil { + *interfaces = r.Interfaces + } + + return nil +} + +// Close terminates the connection. +func (c *Connection) Close() error { + return c.conn.Close() +} + +// NewConnection returns a new connection to the given address. +func NewConnection(address string) (*Connection, error) { + var err error + + words := strings.SplitN(address, ":", 2) + protocol := words[0] + addr := words[1] + + // Ignore parameters after ';' + words = strings.SplitN(addr, ";", 2) + if words != nil { + addr = words[0] + } + + switch protocol { + case "unix": + break + + case "tcp": + break + } + + c := Connection{} + c.conn, err = net.Dial(protocol, addr) + if err != nil { + return nil, err + } + + c.address = address + c.reader = bufio.NewReader(c.conn) + c.writer = bufio.NewWriter(c.conn) + + return &c, nil +} diff --git a/vendor/github.com/varlink/go/varlink/doc.go b/vendor/github.com/varlink/go/varlink/doc.go new file mode 100644 index 000000000..de1ed2380 --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/doc.go @@ -0,0 +1,63 @@ +/* +Package varlink provides varlink client and server implementations. See http://varlink.org +for more information about varlink. + +Example varlink interface definition in a org.example.this.varlink file: + interface org.example.this + + method Ping(in: string) -> (out: string) + +Generated Go module in a orgexamplethis/orgexamplethis.go file. The generated module +provides reply methods for all methods specified in the varlink interface description. +The stub implementations return a MethodNotImplemented error; the service implementation +using this module will override the methods with its own implementation. + // Generated with github.com/varlink/go/cmd/varlink-go-interface-generator + package orgexamplethis + + import "github.com/varlink/go/varlink" + + type orgexamplethisInterface interface { + Ping(c VarlinkCall, in string) error + } + + type VarlinkCall struct{ varlink.Call } + + func (c *VarlinkCall) ReplyPing(out string) error { + var out struct { + Out string `json:"out,omitempty"` + } + out.Out = out + return c.Reply(&out) + } + + func (s *VarlinkInterface) Ping(c VarlinkCall, in string) error { + return c.ReplyMethodNotImplemented("Ping") + } + + [...] + +Service implementing the interface and its method: + import ("orgexamplethis") + + type Data struct { + orgexamplethis.VarlinkInterface + data string + } + + data := Data{data: "test"} + + func (d *Data) Ping(call orgexamplethis.VarlinkCall, ping string) error { + return call.ReplyPing(ping) + } + + service, _ = varlink.NewService( + "Example", + "This", + "1", + "https://example.org/this", + ) + + service.RegisterInterface(orgexamplethis.VarlinkNew(&data)) + err := service.Listen("unix:/run/org.example.this", 0) +*/ +package varlink diff --git a/vendor/github.com/varlink/go/varlink/external_test.go b/vendor/github.com/varlink/go/varlink/external_test.go new file mode 100644 index 000000000..dbe4290e6 --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/external_test.go @@ -0,0 +1,144 @@ +package varlink_test + +// test with no internal access + +import ( + "github.com/varlink/go/varlink" + "os" + "testing" + "time" +) + +type VarlinkInterface struct{} + +func (s *VarlinkInterface) VarlinkDispatch(call varlink.Call, methodname string) error { + return call.ReplyMethodNotImplemented(methodname) +} +func (s *VarlinkInterface) VarlinkGetName() string { + return `org.example.test` +} + +func (s *VarlinkInterface) VarlinkGetDescription() string { + return "#" +} + +type VarlinkInterface2 struct{} + +func (s *VarlinkInterface2) VarlinkDispatch(call varlink.Call, methodname string) error { + return call.ReplyMethodNotImplemented(methodname) +} +func (s *VarlinkInterface2) VarlinkGetName() string { + return `org.example.test2` +} + +func (s *VarlinkInterface2) VarlinkGetDescription() string { + return "#" +} + +func TestRegisterService(t *testing.T) { + newTestInterface := new(VarlinkInterface) + service, err := varlink.NewService( + "Varlink", + "Varlink Test", + "1", + "https://github.com/varlink/go/varlink", + ) + if err != nil { + t.Fatalf("NewService(): %v", err) + } + + if err := service.RegisterInterface(newTestInterface); err != nil { + t.Fatalf("Couldn't register service: %v", err) + } + + if err := service.RegisterInterface(newTestInterface); err == nil { + t.Fatal("Could register service twice") + } + + defer func() { service.Shutdown() }() + + servererror := make(chan error) + + go func() { + servererror <- service.Listen("unix:@varlinkexternal_TestRegisterService", 0) + }() + + time.Sleep(time.Second / 5) + + n := new(VarlinkInterface2) + + if err := service.RegisterInterface(n); err == nil { + t.Fatal("Could register service while running") + } + time.Sleep(time.Second / 5) + service.Shutdown() + + if err := <-servererror; err != nil { + t.Fatalf("service.Listen(): %v", err) + } +} + +func TestUnix(t *testing.T) { + newTestInterface := new(VarlinkInterface) + service, err := varlink.NewService( + "Varlink", + "Varlink Test", + "1", + "https://github.com/varlink/go/varlink", + ) + + if err != nil { + t.Fatalf("NewService(): %v", err) + } + + if err := service.RegisterInterface(newTestInterface); err != nil { + t.Fatalf("RegisterInterface(): %v", err) + } + + servererror := make(chan error) + + go func() { + servererror <- service.Listen("unix:varlinkexternal_TestUnix", 0) + }() + + time.Sleep(time.Second / 5) + service.Shutdown() + + if err := <-servererror; err != nil { + t.Fatalf("service.Listen(): %v", err) + } +} + +func TestListenFDSNotInt(t *testing.T) { + newTestInterface := new(VarlinkInterface) + service, err := varlink.NewService( + "Varlink", + "Varlink Test", + "1", + "https://github.com/varlink/go/varlink", + ) + + if err != nil { + t.Fatalf("NewService(): %v", err) + } + + if err := service.RegisterInterface(newTestInterface); err != nil { + t.Fatalf("Couldn't register service: %v", err) + } + os.Setenv("LISTEN_FDS", "foo") + + servererror := make(chan error) + + go func() { + servererror <- service.Listen("unix:varlinkexternal_TestListenFDSNotInt", 0) + }() + + time.Sleep(time.Second / 5) + service.Shutdown() + + err = <-servererror + + if err != nil { + t.Fatalf("service.Run(): %v", err) + } +} diff --git a/vendor/github.com/varlink/go/varlink/idl/idl.go b/vendor/github.com/varlink/go/varlink/idl/idl.go new file mode 100644 index 000000000..27f21a759 --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/idl/idl.go @@ -0,0 +1,465 @@ +// Package idl provides a varlink interface description parser. +package idl + +import ( + "bytes" + "fmt" + "regexp" +) + +// Valid TypeKind values. +const ( + TypeBool = iota + TypeInt + TypeFloat + TypeString + TypeArray + TypeMaybe + TypeStruct + TypeEnum + TypeAlias +) + +// TypeKind specifies the type of an Type. +type TypeKind uint + +// Type represents a varlink type. Types are method input and output parameters, +// error output parameters, or custom defined types in the interface description. +type Type struct { + Kind TypeKind + ElementType *Type + Alias string + Fields []TypeField +} + +// TypeField is a named member of a TypeStruct. +type TypeField struct { + Name string + Type *Type +} + +// Alias represents a named Type in the interface description. +type Alias struct { + Name string + Doc string + Type *Type +} + +// Method represents a method defined in the interface description. +type Method struct { + Name string + Doc string + In *Type + Out *Type +} + +// Error represents an error defined in the interface description. +type Error struct { + Name string + Type *Type +} + +// IDL represents a parsed varlink interface description with types, methods, errors and +// documentation. +type IDL struct { + Name string + Doc string + Description string + Members []interface{} + Aliases map[string]*Alias + Methods map[string]*Method + Errors map[string]*Error +} + +type parser struct { + input string + position int + lineStart int + lastComment bytes.Buffer +} + +func (p *parser) next() int { + r := -1 + + if p.position < len(p.input) { + r = int(p.input[p.position]) + } + + p.position++ + return r +} + +func (p *parser) backup() { + p.position-- +} + +func (p *parser) advance() bool { + for { + char := p.next() + + if char == '\n' { + p.lineStart = p.position + p.lastComment.Reset() + + } else if char == ' ' || char == '\t' { + // ignore + + } else if char == '#' { + p.next() + start := p.position + for { + c := p.next() + if c < 0 || c == '\n' { + p.backup() + break + } + } + if p.lastComment.Len() > 0 { + p.lastComment.WriteByte('\n') + } + p.lastComment.WriteString(p.input[start:p.position]) + p.next() + + } else { + p.backup() + break + } + } + + return p.position < len(p.input) +} + +func (p *parser) advanceOnLine() { + for { + char := p.next() + if char != ' ' { + p.backup() + return + } + } +} + +func (p *parser) readKeyword() string { + start := p.position + + for { + char := p.next() + if char < 'a' || char > 'z' { + p.backup() + break + } + } + + return p.input[start:p.position] +} + +func (p *parser) readInterfaceName() string { + start := p.position + dnrx := regexp.MustCompile(`^[a-z]+(\.[a-z0-9]+([-][a-z0-9]+)*)+`) + name := dnrx.FindString(p.input[start:]) + if name != "" { + if len(name) > 255 { + return "" + } + p.position += len(name) + return name + } + xdnrx := regexp.MustCompile(`^xn--[a-z0-9]+(\.[a-z0-9]+([-][a-z0-9]+)*)+`) + name = xdnrx.FindString(p.input[start:]) + if name != "" { + if len(name) > 255 { + return "" + } + p.position += len(name) + return name + } + return "" +} + +func (p *parser) readFieldName() string { + start := p.position + + char := p.next() + if char < 'a' || char > 'z' { + p.backup() + return "" + } + + for { + char := p.next() + if (char < 'A' || char > 'Z') && (char < 'a' || char > 'z') && (char < '0' || char > '9') && char != '_' { + p.backup() + break + } + } + + return p.input[start:p.position] +} + +func (p *parser) readTypeName() string { + start := p.position + + for { + char := p.next() + if (char < 'A' || char > 'Z') && (char < 'a' || char > 'z') && (char < '0' || char > '9') { + p.backup() + break + } + } + + return p.input[start:p.position] +} + +func (p *parser) readStructType() *Type { + if p.next() != '(' { + p.backup() + return nil + } + + t := &Type{Kind: TypeStruct} + t.Fields = make([]TypeField, 0) + + char := p.next() + if char != ')' { + p.backup() + + for { + field := TypeField{} + + p.advance() + field.Name = p.readFieldName() + if field.Name == "" { + return nil + } + + p.advance() + + // Enums have no types, they are just a list of names + if p.next() == ':' { + if t.Kind == TypeEnum { + return nil + } + + p.advance() + field.Type = p.readType() + if field.Type == nil { + return nil + } + + } else { + t.Kind = TypeEnum + p.backup() + } + + t.Fields = append(t.Fields, field) + + p.advance() + char = p.next() + if char != ',' { + break + } + } + + if char != ')' { + return nil + } + } + + return t +} + +func (p *parser) readType() *Type { + var t *Type + + switch p.next() { + case '?': + e := p.readType() + if e == nil { + return nil + } + t = &Type{Kind: TypeMaybe, ElementType: e} + + case '[': + if p.next() != ']' { + return nil + } + e := p.readType() + if e == nil { + return nil + } + t = &Type{Kind: TypeArray, ElementType: e} + + default: + p.backup() + if keyword := p.readKeyword(); keyword != "" { + switch keyword { + case "bool": + t = &Type{Kind: TypeBool} + + case "int": + t = &Type{Kind: TypeInt} + + case "float": + t = &Type{Kind: TypeFloat} + + case "string": + t = &Type{Kind: TypeString} + } + + } else if name := p.readTypeName(); name != "" { + t = &Type{Kind: TypeAlias, Alias: name} + + } else if t = p.readStructType(); t == nil { + return nil + } + } + + return t +} + +func (p *parser) readAlias(idl *IDL) (*Alias, error) { + a := &Alias{} + + p.advance() + a.Doc = p.lastComment.String() + a.Name = p.readTypeName() + if a.Name == "" { + return nil, fmt.Errorf("missing type name") + } + + p.advance() + a.Type = p.readType() + if a.Type == nil { + return nil, fmt.Errorf("missing type declaration") + } + + return a, nil +} + +func (p *parser) readMethod(idl *IDL) (*Method, error) { + m := &Method{} + + p.advance() + m.Doc = p.lastComment.String() + m.Name = p.readTypeName() + if m.Name == "" { + return nil, fmt.Errorf("missing method type") + } + + p.advance() + m.In = p.readType() + if m.In == nil { + return nil, fmt.Errorf("missing method input") + } + + p.advance() + one := p.next() + two := p.next() + if (one != '-') || two != '>' { + return nil, fmt.Errorf("missing method '->' operator") + } + + p.advance() + m.Out = p.readType() + if m.Out == nil { + return nil, fmt.Errorf("missing method output") + } + + return m, nil +} + +func (p *parser) readError(idl *IDL) (*Error, error) { + e := &Error{} + + p.advance() + e.Name = p.readTypeName() + if e.Name == "" { + return nil, fmt.Errorf("missing error name") + } + + p.advanceOnLine() + e.Type = p.readType() + + return e, nil +} + +func (p *parser) readIDL() (*IDL, error) { + if keyword := p.readKeyword(); keyword != "interface" { + return nil, fmt.Errorf("missing interface keyword") + } + + idl := &IDL{ + Members: make([]interface{}, 0), + Aliases: make(map[string]*Alias), + Methods: make(map[string]*Method), + Errors: make(map[string]*Error), + } + + p.advance() + idl.Doc = p.lastComment.String() + idl.Name = p.readInterfaceName() + if idl.Name == "" { + return nil, fmt.Errorf("interface name") + } + + for { + if !p.advance() { + break + } + + switch keyword := p.readKeyword(); keyword { + case "type": + a, err := p.readAlias(idl) + if err != nil { + return nil, err + } + + idl.Members = append(idl.Members, a) + idl.Aliases[a.Name] = a + + case "method": + m, err := p.readMethod(idl) + if err != nil { + return nil, err + } + + idl.Members = append(idl.Members, m) + if _, ok := idl.Methods[m.Name]; ok { + return nil, fmt.Errorf("method `%s` already defined", m.Name) + } + idl.Methods[m.Name] = m + + case "error": + e, err := p.readError(idl) + if err != nil { + return nil, err + } + + idl.Members = append(idl.Members, e) + idl.Errors[e.Name] = e + + default: + return nil, fmt.Errorf("unknown keyword '%s'", keyword) + } + } + + return idl, nil +} + +// New parses a varlink interface description. +func New(description string) (*IDL, error) { + p := &parser{input: description} + + p.advance() + idl, err := p.readIDL() + if err != nil { + return nil, err + } + + if len(idl.Methods) == 0 { + return nil, fmt.Errorf("no methods defined") + } + + idl.Description = description + return idl, nil +} diff --git a/vendor/github.com/varlink/go/varlink/idl/idl_test.go b/vendor/github.com/varlink/go/varlink/idl/idl_test.go new file mode 100644 index 000000000..5d83d5890 --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/idl/idl_test.go @@ -0,0 +1,127 @@ +package idl + +import ( + "fmt" + "runtime" + "testing" +) + +/* +func expect(t *testing.T, expected string, returned string) { + if strings.Compare(returned, expected) != 0 { + t.Fatalf("Expected(%d): `%s`\nGot(%d): `%s`\n", + len(expected), expected, + len(returned), returned) + } +} +*/ + +func testParse(t *testing.T, pass bool, description string) { + _, _, line, _ := runtime.Caller(1) + + t.Run(fmt.Sprintf("Line-%d", line), func(t *testing.T) { + midl, err := New(description) + if pass { + if err != nil { + t.Fatalf("generateTemplate(`%s`): %v", description, err) + } + if len(midl.Name) <= 0 { + t.Fatalf("generateTemplate(`%s`): returned no pkgname", description) + } + } + if !pass && (err == nil) { + t.Fatalf("generateTemplate(`%s`): did not fail", description) + } + }) +} + +func TestOneMethod(t *testing.T) { + testParse(t, true, "interface foo.bar\nmethod Foo()->()") +} + +func TestOneMethodNoType(t *testing.T) { + testParse(t, false, "interface foo.bar\nmethod Foo()->(b:)") +} + +func TestDomainNames(t *testing.T) { + testParse(t, true, "interface org.varlink.service\nmethod F()->()") + testParse(t, true, "interface com.example.0example\nmethod F()->()") + testParse(t, true, "interface com.example.example-dash\nmethod F()->()") + testParse(t, true, "interface xn--lgbbat1ad8j.example.algeria\nmethod F()->()") + testParse(t, false, "interface com.-example.leadinghyphen\nmethod F()->()") + testParse(t, false, "interface com.example-.danglinghyphen-\nmethod F()->()") + testParse(t, false, "interface Com.example.uppercase-toplevel\nmethod F()->()") + testParse(t, false, "interface Co9.example.number-toplevel\nmethod F()->()") + testParse(t, false, "interface 1om.example.number-toplevel\nmethod F()->()") + testParse(t, false, "interface com.Example\nmethod F()->()") + var name string + for i := 0; i < 255; i++ { + name += "a" + } + testParse(t, false, "interface com.example.toolong"+name+"\nmethod F()->()") + testParse(t, false, "interface xn--example.toolong"+name+"\nmethod F()->()") +} + +func TestNoMethod(t *testing.T) { + testParse(t, false, ` +interface org.varlink.service + type Interface (name: string, types: []Type, methods: []Method) + type Property (key: string, value: string) +`) +} + +func TestTypeNoArgs(t *testing.T) { + testParse(t, true, "interface foo.bar\n type I ()\nmethod F()->()") +} + +func TestTypeOneArg(t *testing.T) { + testParse(t, true, "interface foo.bar\n type I (b:bool)\nmethod F()->()") +} + +func TestTypeOneArray(t *testing.T) { + testParse(t, true, "interface foo.bar\n type I (b:[]bool)\nmethod F()->()") + testParse(t, false, "interface foo.bar\n type I (b:bool[ ])\nmethod F()->()") + testParse(t, false, "interface foo.bar\n type I (b:bool[1])\nmethod F()->()") + testParse(t, false, "interface foo.bar\n type I (b:bool[ 1 ])\nmethod F()->()") + testParse(t, false, "interface foo.bar\n type I (b:bool[ 1 1 ])\nmethod F()->()") +} + +func TestFieldnames(t *testing.T) { + testParse(t, false, "interface foo.bar\n type I (Test:[]bool)\nmethod F()->()") + testParse(t, false, "interface foo.bar\n type I (_test:[]bool)\nmethod F()->()") + testParse(t, false, "interface foo.bar\n type I (Ă„est:[]bool)\nmethod F()->()") +} +func TestNestedStructs(t *testing.T) { + testParse(t, true, "interface foo.bar\n type I ( b: [](foo: bool, bar: bool, baz: int) )\nmethod F()->()") +} + +func TestEnum(t *testing.T) { + testParse(t, true, "interface foo.bar\n type I (b:(foo, bar, baz))\nmethod F()->()") + testParse(t, false, "interface foo.bar\n type I (foo, bar, baz : bool)\nmethod F()->()") +} + +func TestIncomplete(t *testing.T) { + testParse(t, false, "interfacef foo.bar\nmethod F()->()") + testParse(t, false, "interface foo.bar\nmethod F()->()\ntype I (b: bool") + testParse(t, false, "interface foo.bar\nmethod F()->(") + testParse(t, false, "interface foo.bar\nmethod F(") + testParse(t, false, "interface foo.bar\nmethod ()->()") + testParse(t, false, "interface foo.bar\nmethod F->()\n") + testParse(t, false, "interface foo.bar\nmethod F()->\n") + testParse(t, false, "interface foo.bar\nmethod F()>()\n") + testParse(t, false, "interface foo.bar\nmethod F()->()\ntype (b: bool)") + testParse(t, false, "interface foo.bar\nmethod F()->()\nerror (b: bool)") + testParse(t, false, "interface foo.bar\nmethod F()->()\n dfghdrg") +} + +func TestDuplicate(t *testing.T) { + testParse(t, false, ` +interface foo.example + type Device() + type Device() + type T() + type T() + method F() -> () + method F() -> () +`) +} diff --git a/vendor/github.com/varlink/go/varlink/orgvarlinkservice.go b/vendor/github.com/varlink/go/varlink/orgvarlinkservice.go new file mode 100644 index 000000000..39f843c31 --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/orgvarlinkservice.go @@ -0,0 +1,133 @@ +package varlink + +func doReplyError(c *Call, name string, parameters interface{}) error { + return c.sendMessage(&serviceReply{ + Error: name, + Parameters: parameters, + }) +} + +// 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"` + } + 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"` + } + 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"` + } + 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"` + } + out.Parameter = parameter + return doReplyError(c, "org.varlink.service.InvalidParameter", &out) +} + +func (c *Call) replyGetInfo(vendor string, product string, version string, url string, interfaces []string) error { + var out struct { + Vendor string `json:"vendor,omitempty"` + Product string `json:"product,omitempty"` + Version string `json:"version,omitempty"` + URL string `json:"url,omitempty"` + Interfaces []string `json:"interfaces,omitempty"` + } + out.Vendor = vendor + out.Product = product + out.Version = version + out.URL = url + out.Interfaces = interfaces + return c.Reply(&out) +} + +func (c *Call) replyGetInterfaceDescription(description string) error { + var out struct { + Description string `json:"description,omitempty"` + } + out.Description = description + return c.Reply(&out) +} + +func (s *Service) orgvarlinkserviceDispatch(c Call, methodname string) error { + switch methodname { + case "GetInfo": + return s.getInfo(c) + case "GetInterfaceDescription": + var in struct { + Interface string `json:"interface"` + } + err := c.GetParameters(&in) + if err != nil { + return c.ReplyInvalidParameter("parameters") + } + return s.getInterfaceDescription(c, in.Interface) + + default: + return c.ReplyMethodNotFound(methodname) + } +} + +func (s *orgvarlinkserviceInterface) VarlinkDispatch(call Call, methodname string) error { + return nil +} + +func (s *orgvarlinkserviceInterface) VarlinkGetName() string { + return `org.varlink.service` +} + +func (s *orgvarlinkserviceInterface) VarlinkGetDescription() string { + return `# The Varlink Service Interface is provided by every varlink service. It +# describes the service and the interfaces it implements. +interface org.varlink.service + +# Get a list of all the interfaces a service provides and information +# about the implementation. +method GetInfo() -> ( + vendor: string, + product: string, + version: string, + url: string, + interfaces: []string +) + +# Get the description of an interface that is implemented by this service. +method GetInterfaceDescription(interface: string) -> (description: string) + +# The requested interface was not found. +error InterfaceNotFound (interface: string) + +# The requested method was not found +error MethodNotFound (method: string) + +# The interface defines the requested method, but the service does not +# implement it. +error MethodNotImplemented (method: string) + +# One of the passed parameters is invalid. +error InvalidParameter (parameter: string)` +} + +type orgvarlinkserviceInterface struct{} + +func orgvarlinkserviceNew() *orgvarlinkserviceInterface { + return &orgvarlinkserviceInterface{} +} diff --git a/vendor/github.com/varlink/go/varlink/resolver.go b/vendor/github.com/varlink/go/varlink/resolver.go new file mode 100644 index 000000000..f0f4487d2 --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/resolver.go @@ -0,0 +1,92 @@ +package varlink + +// ResolverAddress is the well-known address of the varlink interface resolver, +// it translates varlink interface names to varlink service addresses. +const ResolverAddress = "unix:/run/org.varlink.resolver" + +// Resolver resolves varlink interface names to varlink addresses +type Resolver struct { + address string + conn *Connection +} + +// Resolve resolves a varlink interface name to a varlink address. +func (r *Resolver) Resolve(iface string) (string, error) { + type request struct { + Interface string `json:"interface"` + } + type reply struct { + Address string `json:"address"` + } + + /* don't ask the resolver for itself */ + if iface == "org.varlink.resolver" { + return r.address, nil + } + + var rep reply + err := r.conn.Call("org.varlink.resolver.Resolve", &request{Interface: iface}, &rep) + if err != nil { + return "", err + } + + return rep.Address, nil +} + +// GetInfo requests information about the resolver. +func (r *Resolver) GetInfo(vendor *string, product *string, version *string, url *string, interfaces *[]string) error { + type reply struct { + Vendor string + Product string + Version string + URL string + Interfaces []string + } + + var rep reply + err := r.conn.Call("org.varlink.resolver.GetInfo", nil, &rep) + if err != nil { + return err + } + + if vendor != nil { + *vendor = rep.Vendor + } + if product != nil { + *product = rep.Product + } + if version != nil { + *version = rep.Version + } + if url != nil { + *url = rep.URL + } + if interfaces != nil { + *interfaces = rep.Interfaces + } + + return nil +} + +// Close terminates the resolver. +func (r *Resolver) Close() error { + return r.conn.Close() +} + +// NewResolver returns a new resolver connected to the given address. +func NewResolver(address string) (*Resolver, error) { + if address == "" { + address = ResolverAddress + } + + c, err := NewConnection(address) + if err != nil { + return nil, err + } + r := Resolver{ + address: address, + conn: c, + } + + return &r, nil +} diff --git a/vendor/github.com/varlink/go/varlink/service.go b/vendor/github.com/varlink/go/varlink/service.go new file mode 100644 index 000000000..eb110e503 --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/service.go @@ -0,0 +1,350 @@ +package varlink + +import ( + "bufio" + "encoding/json" + "fmt" + "net" + "os" + "strconv" + "strings" + "sync" + "syscall" + "time" +) + +type dispatcher interface { + VarlinkDispatch(c Call, methodname string) error + VarlinkGetName() string + VarlinkGetDescription() string +} + +type serviceCall struct { + Method string `json:"method"` + Parameters *json.RawMessage `json:"parameters,omitempty"` + More bool `json:"more,omitempty"` + OneShot bool `json:"oneshot,omitempty"` +} + +type serviceReply struct { + Parameters interface{} `json:"parameters,omitempty"` + Continues bool `json:"continues,omitempty"` + Error string `json:"error,omitempty"` +} + +// Service represents an active varlink service. In addition to the registered custom varlink Interfaces, every service +// implements the org.varlink.service interface which allows clients to retrieve information about the +// running service. +type Service struct { + vendor string + product string + version string + url string + interfaces map[string]dispatcher + names []string + descriptions map[string]string + running bool + listener net.Listener + conncounter int64 + mutex sync.Mutex + protocol string + address string +} + +func (s *Service) getInfo(c Call) error { + return c.replyGetInfo(s.vendor, s.product, s.version, s.url, s.names) +} + +func (s *Service) getInterfaceDescription(c Call, name string) error { + if name == "" { + return c.ReplyInvalidParameter("interface") + } + + description, ok := s.descriptions[name] + if !ok { + return c.ReplyInvalidParameter("interface") + } + + return c.replyGetInterfaceDescription(description) +} + +func (s *Service) handleMessage(writer *bufio.Writer, request []byte) error { + var in serviceCall + + err := json.Unmarshal(request, &in) + + if err != nil { + return err + } + + c := Call{ + writer: writer, + in: &in, + } + + r := strings.LastIndex(in.Method, ".") + if r <= 0 { + return c.ReplyInvalidParameter("method") + } + + interfacename := in.Method[:r] + methodname := in.Method[r+1:] + + if interfacename == "org.varlink.service" { + return s.orgvarlinkserviceDispatch(c, methodname) + } + + // Find the interface and method in our service + iface, ok := s.interfaces[interfacename] + if !ok { + return c.ReplyInterfaceNotFound(interfacename) + } + + return iface.VarlinkDispatch(c, methodname) +} + +func activationListener() net.Listener { + pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) + if err != nil || pid != os.Getpid() { + return nil + } + + nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) + if err != nil || nfds < 1 { + return nil + } + + fd := -1 + + // If more than one file descriptor is passed, find the + // "varlink" tag. The first file descriptor is always 3. + if nfds > 1 { + fdnames, set := os.LookupEnv("LISTEN_FDNAMES") + if !set { + return nil + } + + names := strings.Split(fdnames, ":") + if len(names) != nfds { + return nil + } + + for i, name := range names { + if name == "varlink" { + fd = 3 + i + break + } + } + + if fd < 0 { + return nil + } + + } else { + fd = 3 + } + + syscall.CloseOnExec(fd) + + file := os.NewFile(uintptr(fd), "varlink") + listener, err := net.FileListener(file) + if err != nil { + return nil + } + + os.Unsetenv("LISTEN_PID") + os.Unsetenv("LISTEN_FDS") + os.Unsetenv("LISTEN_FDNAMES") + + return listener +} + +// Shutdown shuts down the listener of a running service. +func (s *Service) Shutdown() { + s.running = false + s.mutex.Lock() + if s.listener != nil { + s.listener.Close() + } + s.mutex.Unlock() +} + +func (s *Service) handleConnection(conn net.Conn, wg *sync.WaitGroup) { + defer func() { s.mutex.Lock(); s.conncounter--; s.mutex.Unlock(); wg.Done() }() + reader := bufio.NewReader(conn) + writer := bufio.NewWriter(conn) + + for { + request, err := reader.ReadBytes('\x00') + if err != nil { + break + } + + err = s.handleMessage(writer, request[:len(request)-1]) + if err != nil { + // FIXME: report error + //fmt.Fprintf(os.Stderr, "handleMessage: %v", err) + break + } + } + + conn.Close() +} + +func (s *Service) teardown() { + s.mutex.Lock() + s.listener = nil + s.running = false + s.protocol = "" + s.address = "" + s.mutex.Unlock() +} + +func (s *Service) parseAddress(address string) error { + words := strings.SplitN(address, ":", 2) + s.protocol = words[0] + s.address = words[1] + + // Ignore parameters after ';' + words = strings.SplitN(s.address, ";", 2) + if words != nil { + s.address = words[0] + } + + switch s.protocol { + case "unix": + break + case "tcp": + break + + default: + return fmt.Errorf("Unknown protocol") + } + + return nil +} + +func getListener(protocol string, address string) (net.Listener, error) { + l := activationListener() + if l == nil { + if protocol == "unix" && address[0] != '@' { + os.Remove(address) + } + + var err error + l, err = net.Listen(protocol, address) + if err != nil { + return nil, err + } + + if protocol == "unix" && address[0] != '@' { + l.(*net.UnixListener).SetUnlinkOnClose(true) + } + } + + return l, nil +} + +func (s *Service) refreshTimeout(timeout time.Duration) error { + switch s.protocol { + case "unix": + if err := s.listener.(*net.UnixListener).SetDeadline(time.Now().Add(timeout)); err != nil { + return err + } + + case "tcp": + if err := s.listener.(*net.TCPListener).SetDeadline(time.Now().Add(timeout)); err != nil { + return err + } + } + + return nil +} + +// Listen starts a Service. +func (s *Service) Listen(address string, timeout time.Duration) error { + var wg sync.WaitGroup + defer func() { s.teardown(); wg.Wait() }() + + s.mutex.Lock() + if s.running { + s.mutex.Unlock() + return fmt.Errorf("Listen(): already running") + } + s.mutex.Unlock() + + s.parseAddress(address) + + l, err := getListener(s.protocol, s.address) + if err != nil { + return err + } + + s.mutex.Lock() + s.listener = l + s.running = true + s.mutex.Unlock() + + for s.running { + if timeout != 0 { + if err := s.refreshTimeout(timeout); err != nil { + return err + } + } + conn, err := l.Accept() + if err != nil { + if err.(net.Error).Timeout() { + s.mutex.Lock() + if s.conncounter == 0 { + s.mutex.Unlock() + return nil + } + s.mutex.Unlock() + continue + } + if !s.running { + return nil + } + return err + } + s.mutex.Lock() + s.conncounter++ + s.mutex.Unlock() + wg.Add(1) + go s.handleConnection(conn, &wg) + } + + return nil +} + +// RegisterInterface registers a varlink.Interface containing struct to the Service +func (s *Service) RegisterInterface(iface dispatcher) error { + name := iface.VarlinkGetName() + if _, ok := s.interfaces[name]; ok { + return fmt.Errorf("interface '%s' already registered", name) + } + + if s.running { + return fmt.Errorf("service is already running") + } + s.interfaces[name] = iface + s.descriptions[name] = iface.VarlinkGetDescription() + s.names = append(s.names, name) + + return nil +} + +// NewService creates a new Service which implements the list of given varlink interfaces. +func NewService(vendor string, product string, version string, url string) (*Service, error) { + s := Service{ + vendor: vendor, + product: product, + version: version, + url: url, + interfaces: make(map[string]dispatcher), + descriptions: make(map[string]string), + } + err := s.RegisterInterface(orgvarlinkserviceNew()) + + return &s, err +} diff --git a/vendor/github.com/varlink/go/varlink/varlink_test.go b/vendor/github.com/varlink/go/varlink/varlink_test.go new file mode 100644 index 000000000..e19e768a2 --- /dev/null +++ b/vendor/github.com/varlink/go/varlink/varlink_test.go @@ -0,0 +1,232 @@ +package varlink + +// tests with access to internals + +import ( + "bufio" + "bytes" + "fmt" + "strings" + "testing" +) + +func expect(t *testing.T, expected string, returned string) { + if strings.Compare(returned, expected) != 0 { + t.Fatalf("Expected(%d): `%s`\nGot(%d): `%s`\n", + len(expected), expected, + len(returned), strings.Replace(returned, "\000", "`+\"\\000\"+`", -1)) + } +} + +func TestService(t *testing.T) { + service, _ := NewService( + "Varlink", + "Varlink Test", + "1", + "https://github.com/varlink/go/varlink", + ) + + t.Run("ZeroMessage", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + if err := service.handleMessage(w, []byte{0}); err == nil { + t.Fatal("HandleMessage returned non-error") + } + }) + + t.Run("InvalidJson", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"foo.GetInterfaceDescription" fdgdfg}`) + if err := service.handleMessage(w, msg); err == nil { + t.Fatal("HandleMessage returned no error on invalid json") + } + }) + + t.Run("WrongInterface", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"foo.GetInterfaceDescription"}`) + if err := service.handleMessage(w, msg); err != nil { + t.Fatal("HandleMessage returned error on wrong interface") + } + expect(t, `{"parameters":{"interface":"foo"},"error":"org.varlink.service.InterfaceNotFound"}`+"\000", + b.String()) + }) + + t.Run("InvalidMethod", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"InvalidMethod"}`) + if err := service.handleMessage(w, msg); err != nil { + t.Fatal("HandleMessage returned error on invalid method") + } + expect(t, `{"parameters":{"parameter":"method"},"error":"org.varlink.service.InvalidParameter"}`+"\000", + b.String()) + }) + + t.Run("WrongMethod", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"org.varlink.service.WrongMethod"}`) + if err := service.handleMessage(w, msg); err != nil { + t.Fatal("HandleMessage returned error on wrong method") + } + expect(t, `{"parameters":{"method":"WrongMethod"},"error":"org.varlink.service.MethodNotFound"}`+"\000", + b.String()) + }) + + t.Run("GetInterfaceDescriptionNullParameters", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"org.varlink.service.GetInterfaceDescription","parameters": null}`) + if err := service.handleMessage(w, msg); err != nil { + t.Fatalf("HandleMessage returned error: %v", err) + } + expect(t, `{"parameters":{"parameter":"parameters"},"error":"org.varlink.service.InvalidParameter"}`+"\000", + b.String()) + }) + + t.Run("GetInterfaceDescriptionNoInterface", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"org.varlink.service.GetInterfaceDescription","parameters":{}}`) + if err := service.handleMessage(w, msg); err != nil { + t.Fatalf("HandleMessage returned error: %v", err) + } + expect(t, `{"parameters":{"parameter":"interface"},"error":"org.varlink.service.InvalidParameter"}`+"\000", + b.String()) + }) + + t.Run("GetInterfaceDescriptionWrongInterface", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"org.varlink.service.GetInterfaceDescription","parameters":{"interface":"foo"}}`) + if err := service.handleMessage(w, msg); err != nil { + t.Fatalf("HandleMessage returned error: %v", err) + } + expect(t, `{"parameters":{"parameter":"interface"},"error":"org.varlink.service.InvalidParameter"}`+"\000", + b.String()) + }) + + t.Run("GetInterfaceDescription", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"org.varlink.service.GetInterfaceDescription","parameters":{"interface":"org.varlink.service"}}`) + if err := service.handleMessage(w, msg); err != nil { + t.Fatalf("HandleMessage returned error: %v", err) + } + expect(t, `{"parameters":{"description":"# The Varlink Service Interface is provided by every varlink service. It\n# describes the service and the interfaces it implements.\ninterface org.varlink.service\n\n# Get a list of all the interfaces a service provides and information\n# about the implementation.\nmethod GetInfo() -\u003e (\n vendor: string,\n product: string,\n version: string,\n url: string,\n interfaces: []string\n)\n\n# Get the description of an interface that is implemented by this service.\nmethod GetInterfaceDescription(interface: string) -\u003e (description: string)\n\n# The requested interface was not found.\nerror InterfaceNotFound (interface: string)\n\n# The requested method was not found\nerror MethodNotFound (method: string)\n\n# The interface defines the requested method, but the service does not\n# implement it.\nerror MethodNotImplemented (method: string)\n\n# One of the passed parameters is invalid.\nerror InvalidParameter (parameter: string)"}}`+"\000", + b.String()) + }) + + t.Run("GetInfo", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"org.varlink.service.GetInfo"}`) + if err := service.handleMessage(w, msg); err != nil { + t.Fatalf("HandleMessage returned error: %v", err) + } + expect(t, `{"parameters":{"vendor":"Varlink","product":"Varlink Test","version":"1","url":"https://github.com/varlink/go/varlink","interfaces":["org.varlink.service"]}}`+"\000", + b.String()) + }) +} + +type VarlinkInterface struct{} + +func (s *VarlinkInterface) VarlinkDispatch(call Call, methodname string) error { + switch methodname { + case "Ping": + if !call.WantsMore() { + return fmt.Errorf("More flag not passed") + } + if call.IsOneShot() { + return fmt.Errorf("OneShot flag set") + } + call.Continues = true + if err := call.Reply(nil); err != nil { + return err + } + if err := call.Reply(nil); err != nil { + return err + } + call.Continues = false + if err := call.Reply(nil); err != nil { + return err + } + return nil + + case "PingError": + return call.ReplyError("org.example.test.PingError", nil) + } + + call.Continues = true + if err := call.Reply(nil); err == nil { + return fmt.Errorf("call.Reply did not fail for Continues/More mismatch") + } + call.Continues = false + + if err := call.ReplyError("WrongName", nil); err == nil { + return fmt.Errorf("call.ReplyError accepted invalid error name") + } + + if err := call.ReplyError("org.varlink.service.MethodNotImplemented", nil); err == nil { + return fmt.Errorf("call.ReplyError accepted org.varlink.service error") + } + + return call.ReplyMethodNotImplemented(methodname) +} +func (s *VarlinkInterface) VarlinkGetName() string { + return `org.example.test` +} + +func (s *VarlinkInterface) VarlinkGetDescription() string { + return "#" +} + +func TestMoreService(t *testing.T) { + newTestInterface := new(VarlinkInterface) + + service, _ := NewService( + "Varlink", + "Varlink Test", + "1", + "https://github.com/varlink/go/varlink", + ) + + if err := service.RegisterInterface(newTestInterface); err != nil { + t.Fatalf("Couldn't register service: %v", err) + } + + t.Run("MethodNotImplemented", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"org.example.test.Pingf"}`) + if err := service.handleMessage(w, msg); err != nil { + t.Fatalf("HandleMessage returned error: %v", err) + } + expect(t, `{"parameters":{"method":"Pingf"},"error":"org.varlink.service.MethodNotImplemented"}`+"\000", + b.String()) + }) + + t.Run("PingError", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"org.example.test.PingError", "more" : true}`) + if err := service.handleMessage(w, msg); err != nil { + t.Fatalf("HandleMessage returned error: %v", err) + } + expect(t, `{"error":"org.example.test.PingError"}`+"\000", + b.String()) + }) + t.Run("MoreTest", func(t *testing.T) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + msg := []byte(`{"method":"org.example.test.Ping", "more" : true}`) + if err := service.handleMessage(w, msg); err != nil { + t.Fatalf("HandleMessage returned error: %v", err) + } + expect(t, `{"continues":true}`+"\000"+`{"continues":true}`+"\000"+`{}`+"\000", + b.String()) + }) +} |