diff options
135 files changed, 4054 insertions, 1354 deletions
diff --git a/.golangci.yml b/.golangci.yml index 8af8aea91..b04683b7b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,10 +1,5 @@ --- run: - build-tags: - - apparmor - - seccomp - - selinux - - ABISupport concurrency: 6 deadline: 5m skip-dirs-use-default: true @@ -12,6 +7,8 @@ run: - contrib - dependencies - test + - pkg/varlink + - pkg/varlinkapi skip-files: - iopodman.go - swagger.go @@ -31,3 +28,7 @@ linters: - misspell - prealloc - unparam +linters-settings: + errcheck: + check-blank: false + ignore: encoding/json:^Unmarshal,fmt:.* @@ -159,7 +159,7 @@ endif .PHONY: golangci-lint golangci-lint: .gopathok varlink_generate .install.golangci-lint - $(GOBIN)/golangci-lint run + hack/golangci-lint.sh run .PHONY: gofmt gofmt: ## Verify the source code gofmt @@ -637,10 +637,9 @@ build-no-cgo: .PHONY: vendor vendor: - export GO111MODULE=on \ - $(GO) mod tidy && \ - $(GO) mod vendor && \ - $(GO) mod verify + GO111MODULE=on $(GO) mod tidy + GO111MODULE=on $(GO) mod vendor + GO111MODULE=on $(GO) mod verify .PHONY: vendor-in-container vendor-in-container: @@ -5,7 +5,7 @@ Libpod provides a library for applications looking to use the Container Pod concept, popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)`. Podman manages pods, containers, container images, and container volumes. -* [Latest Version: 1.8.1](https://github.com/containers/libpod/releases/latest) +* [Latest Version: 1.8.2](https://github.com/containers/libpod/releases/latest) * [Continuous Integration:](contrib/cirrus/README.md) [![Build Status](https://api.cirrus-ci.com/github/containers/libpod.svg)](https://cirrus-ci.com/github/containers/libpod/master) * [GoDoc: ![GoDoc](https://godoc.org/github.com/containers/libpod/libpod?status.svg)](https://godoc.org/github.com/containers/libpod/libpod) * Automated continuous release downloads (including remote-client): diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6578f40fd..aef66545f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,7 +1,39 @@ # Release Notes -## 1.8.2 +## 1.9.0 +### Features +- Experimental support has been added for `podman run --userns=auto`, which automatically allocates a unique UID and GID range for the new container's user namespace +- The `podman play kube` command now has a `--network` flag to place the created pod in one or more CNI networks +- The `podman commit` command now supports an `--iidfile` flag to write the ID of the committed image to a file +- Initial support for the new `containers.conf` configuration file has been added. `containers.conf` allows for much more detailed configuration of some Podman functionality + +### Changes +- There has been a major cleanup of the `podman info` command resulting in breaking changes. Many fields have been renamed to better suit usage with APIv2 +- All uses of the `--timeout` flag have been switched to prefer the alternative `--time`. The `--timeout` flag will continue to work, but man pages and `--help` will use the `--time` flag instead + +### Bugfixes +- Fixed a bug where some volume mounts from the host would sometimes not properly determine the flags they should use when mounting +- Fixed a bug where Podman was not propagating `$PATH` to Conmon and the OCI runtime, causing issues for some OCI runtimes that required it +- Fixed a bug where rootless Podman would print error messages about missing support for systemd cgroups when run in a container with no cgroup support ([#5488](https://github.com/containers/libpod/issues/5488)) +- Fixed a bug where `podman play kube` would not properly handle container-only port mappings ([#5610](https://github.com/containers/libpod/issues/5610)) +- Fixed a bug where the `podman container prune` command was not pruning containers in the `created` and `configured` states +- Fixed a bug where Podman was not properly removing CNI IP address allocations after a reboot ([#5433](https://github.com/containers/libpod/issues/5433)) +- Fixed a bug where Podman was not properly applying the default Seccomp profile when `--security-opt` was not given at the command line +### HTTP API +- Many Libpod API endpoints have been added, including `Changes`, `Checkpoint`, `Init`, and `Restore` +- Resolved issues where the `podman system service` command would time out and exit while there were still active connections +- Stability overall has greatly improved as we prepare the API for a beta release soon with Podman 2.0 + +### Misc +- The default infra image for pods has been upgraded to `k8s.gcr.io/pause:3.2` (from 3.1) to address a bug in the architecture metadata for non-AMD64 images +- The `slirp4netns` networking utility in rootless Podman now uses Seccomp filtering where available for improved security +- Updated Buildah to v1.14.8 +- Updated containers/storage to v1.18.2 +- Updated containers/image to v5.4.3 +- Updated containers/common to v0.8.1 + +## 1.8.2 ### Features - Initial support for automatically updating containers managed via Systemd unit files has been merged. This allows containers to automatically upgrade if a newer version of their image becomes available diff --git a/changelog.txt b/changelog.txt index 1e08a8419..95f8d5b47 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,190 @@ +- Changelog for v1.9.0 (2020-04-15) + * podmanV2: fix nil deref + * v2specgen prune libpod + * More system test fixes on regressions + * Add support for the global flags and config files + * Bump to v1.9.0-dev + +- Changelog for v1.9.0-rc2 (2020-04-14) + * Update release notes for v1.9.0-RC2 + * v2podman ps revert structure changes + * podmanv2 mount and umount + * Fix invalid container path comparison for pid cgroup + * v2podman add container init + * Need to set security options even if user does not specify options + * podmanv2 version format variable name change + * Fixes for load and other system tests + * Improve APIv2 support for Attach + * Refactor service idle support + * podmanv2 history and image remove templates + * Bump to v1.9.0-dev + * rootless: use snprintf + * podmanV2: implement search + +- Changelog for v1.9.0-rc1 (2020-04-13) + * build(deps): bump github.com/containers/buildah from 1.14.7 to 1.14.8 + * Update release notes for v1.9.0-RC1 + * v2podman container cleanup + * podmanV2: implement logs + * test: enable preserve fds test for crun + * test: fix exec preserve-fds test + * Set exit codes on errors. + * Run (make vendor) + * Fix (make vendor) + * update the latest version to 1.8.2 + * add tests for kill and exists + * v2podman ps alter formats + * run/create were processing options after the image name + * V2 podman system service + * man page: add note about issue with SELinux + * Bump Buildah to v1.14.7 + * Bump containers/image to v5.4.3 + * V2 podman diff(changes) support + * podman info needs to be run within the user namespace + * podmanv2 images user format + * podmanv2 info + * vendor c/image v5.4.2 + * Do not error on pids.current stats if ctr.path is empty + * fix rootless login/logout tests + * v2podman run + * refactor info + * podmanv2 ps + * userns: support --userns=auto + * podmanv2 start + * build(deps): bump github.com/containers/common from 0.8.0 to 0.8.1 + * build(deps): bump github.com/containers/storage from 1.18.1 to 1.18.2 + * build(deps): bump github.com/opencontainers/selinux from 1.4.0 to 1.5.0 + * v2podman attach and exec + * v2podman container create + * Cleanup whether to enter user namespace for rootless commands + * podmanv2 save image + * podmanv2 version + * checkpoint: handle XDG_RUNTIME_DIR + * checkpoint: change runtime checkpoint support test + * Pass path environment down to the OCI runtime + * podmanv2 checkpoint and restore + * Bump github.com/containers/common from 0.6.1 to 0.8.0 + * test/e2e/run_volume_test: use unique mount point + * test/e2e/run_volume_test.go: mv dockerfile decl + * test/e2e/run_volume_test: only create dir once + * Fix environment handling from containers.conf + * podmanV2: implement push + * pkg/spec.InitFSMounts: optimize + * utils: delete dead code + * attach: skip shutdown on errors + * attach: fix hang if control path is deleted + * pkg/spec.InitFSMounts: fix mount opts in place + * podmanv2 export + * podmanv2 import + * podmanv2-retry - new helper for testing v2 + * podmanv2 load + * podmanv2 pod inspect + * V2 podman inspect + * Fix repos for CentOS 7 RPM build + * podman v2 image tag and untag + * podmanv2 pod ps + * Touch up mailing list address in README.md + * add systemd build tag to podman builds + * Bump github.com/rootless-containers/rootlesskit from 0.9.2 to 0.9.3 + * Switch to using --time as opposed to --timeout to better match Docker. + * podmanV2: implement pull + * pkg/spec/initFSMounts: fix + * Cirrus: Remove darwin/windows builds in gate-job + * Cirrus: Update VM Images + * Cirrus: Minor docs update + * Revert "Default CPUShares in Inspect are 1024" + * fix more swagger inconsistencies + * V2 Move varlink home + * Bump github.com/containers/conmon + * Bump github.com/spf13/cobra from 0.0.6 to 0.0.7 + * rootless: make cgroup ownership detection not fatal + * podmanv2 enable healthcheck run + * Update vendor of boltdb and containers/image + * swagger: top: remove "Docker" from the identifiers + * podmanv2: implement pod top + * v2 api: implement pods top endpoint + * podmanv2 commit + * Bump to buildah v1.14.5 + * Add support for containers.conf + * API v2 tests: usability improvements + * Sanitize port parsing for pods in play kube + * podmanv2 pod create using podspecgen + * use `pause:3.2` image for infra containers + * Add support for specifying CNI networks in podman play kube + * Fix typo in pod create + * podmanV2: implement top + * Fix Markdown typo in podman-create.1.md + * V2 podman image prune + * Support label filters for podman pod ps. + * podmanv2 container inspect + * podmanv2 pod subcommands + * Add bindings for Container Exec Create + Inspect + * apiv2 add default network in specgen + * slirp: enable seccomp filter + * V2 podman image rm | podman rmi [IMAGE] + * V2 podman image + * podmanv2 add pre-run to each commmand + * Ensure that exec sends resize events + * enable linting on v2 + * Bump github.com/rootless-containers/rootlesskit from 0.8.0 to 0.9.2 + * Bump github.com/containers/storage from 1.16.5 to 1.16.6 + * V2 podman images/image list + * podmanv2 volumes + * Combine GlobalFlags and EngineFlags into EngineOptions + * Complete podmanV2 history command + * rootlessport: use x/sys/unix instead of syscall + * podmanv2 exit code + * Bump github.com/sirupsen/logrus from 1.4.2 to 1.5.0 + * Correctly document libpod commit endpoint + * Implement APIv2 Exec Create and Inspect Endpoints + * apiv2 container commit for libpod + * Add image signing with GPG tutorial + * podmanv2 add core container commands + * Improved readability in image json output + * podmanv2 volume create + * Add stubs for cmd/podman in non-Linux local mode + * Make libpod/lock/shm completely Linux-only + * Add stubs for pkg/adapter/terminal_linux.go + * Add a stub for libpod.Container.Top + * Make cmd/podman/shared.GenerateCommand tests Linux-only + * Fix the libpod.LabelVolumePath stub + * Only run TestGetImageConfigStopSignal on Linux + * Fix the pkg/specgen/SpecGenerator.getSeccompConfig stub + * podmanv2 pod exists + * when removing networks for tests, force should be used + * Add basic structure of a spec generator for pods + * [CI:DOCS]fix type issue in pod binding test + * podmanv2 enable remote wait + * fix remote connection use of context + * use boolreport for containerexists response + * podmanv2 container exists|wait + * Add APIV2 service files + * Attempt manual removal of CNI IP allocations on refresh + * Implemented --iidfile for podman commit + * Add guildline for writing podman V2 CLI commands + * Use creds form PullImage remote + * Fix docker man page links + * Bump to v1.8.3-dev + * [CI:DOCS]remove podmanv2 binary + * Cirrus: Update VM images + * Cirrus-CI: Fix source path of vendor task + * Cirrus: Enable future installing buildah packages + * Cirrus: Include packages for buildah CI + * Cirrus: Update Ubuntu base images + * Cirrus: Use opensuse open build Ubuntu packages + * Update release notes for v1.8.2 final release + * rootlessport: handle SIGPIPE + * apiv2 add bindings for logs|events + * Bump github.com/containers/common from 0.5.0 to 0.6.1 + * Add inspect for exec sessions + * Add structure for new exec session tracking to DB + * Populate ExecSession with all required fields + * Fix path of tmp_dir + * Cirrus: Disable non-docs release processing + * container prune remove state created, configured + * Cirrus: Log libseccomp package version + * docs: mention that "podman version" prints out Remote API Version + - Changelog for v1.8.2 (2020-03-19) * fix reported compat issues * Don't include SUBDIR in windows.zip diff --git a/cmd/podman/commands_remoteclient.go b/cmd/podman/commands_remoteclient.go index ef523ffb1..11baeb4ae 100644 --- a/cmd/podman/commands_remoteclient.go +++ b/cmd/podman/commands_remoteclient.go @@ -14,7 +14,7 @@ func getMainCommands() []*cobra.Command { } // commands that only the remoteclient implements -func getAppCommands() []*cobra.Command { +func getAppCommands() []*cobra.Command { // nolint:varcheck,deadcode,unused return []*cobra.Command{} } @@ -29,7 +29,7 @@ func getContainerSubCommands() []*cobra.Command { } // commands that only the remoteclient implements -func getGenerateSubCommands() []*cobra.Command { +func getGenerateSubCommands() []*cobra.Command { // nolint:varcheck,deadcode,unused return []*cobra.Command{} } @@ -126,7 +126,7 @@ func getDefaultPidsDescription() string { return "Tune container pids limit (set 0 for unlimited, -1 for server defaults)" } -func getDefaultShareNetwork() string { +func getDefaultShareNetwork() string { // nolint:varcheck,deadcode,unused return "" } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 5134448da..4435b036e 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -24,8 +24,8 @@ import ( var ( exitCode = define.ExecErrorCodeGeneric Ctx context.Context - span opentracing.Span - closer io.Closer + span opentracing.Span // nolint:varcheck,deadcode,unused + closer io.Closer // nolint:varcheck,deadcode,unused ) // Commands that the remote and local client have diff --git a/cmd/podman/service.go b/cmd/podman/service.go index bcb37eac5..0280b01a4 100644 --- a/cmd/podman/service.go +++ b/cmd/podman/service.go @@ -91,7 +91,7 @@ func resolveApiURI(c *cliconfig.ServiceValues) (string, error) { if len(c.InputArgs) > 0 { apiURI = c.InputArgs[0] - } else if ok := systemd.SocketActivated(); ok { + } else if ok := systemd.SocketActivated(); ok { // nolint: gocritic apiURI = "" } else if rootless.IsRootless() { xdg, err := util.GetRuntimeDir() @@ -155,13 +155,11 @@ func runREST(r *libpod.Runtime, uri string, timeout time.Duration) error { } }() - err = server.Serve() - logrus.Debugf("%d/%d Active connections/Total connections\n", server.ActiveConnections, server.TotalConnections) - return err + return server.Serve() } func runVarlink(r *libpod.Runtime, uri string, timeout time.Duration, c *cliconfig.ServiceValues) error { - var varlinkInterfaces = []*iopodman.VarlinkInterface{varlinkapi.New(&c.PodmanCommand, r)} + var varlinkInterfaces = []*iopodman.VarlinkInterface{varlinkapi.New(c.PodmanCommand.Command, r)} service, err := varlink.NewService( "Atomic", "podman", @@ -182,7 +180,7 @@ func runVarlink(r *libpod.Runtime, uri string, timeout time.Duration, c *cliconf if err = service.Listen(uri, timeout); err != nil { switch err.(type) { case varlink.ServiceTimeoutError: - logrus.Infof("varlink service expired (use --timeout to increase session time beyond %d ms, 0 means never timeout)", timeout.String()) + logrus.Infof("varlink service expired (use --timeout to increase session time beyond %s ms, 0 means never timeout)", timeout.String()) return nil default: return errors.Wrapf(err, "unable to start varlink service") diff --git a/cmd/podman/service_dummy.go b/cmd/podman/service_dummy.go index a774c34de..a726f21c1 100644 --- a/cmd/podman/service_dummy.go +++ b/cmd/podman/service_dummy.go @@ -5,6 +5,7 @@ package main import "github.com/spf13/cobra" var ( + // nolint:varcheck,deadcode,unused _serviceCommand = &cobra.Command{ Use: "", } diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index 68a36d967..94b1e63dc 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -783,10 +783,12 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod. Sysctl: sysctl, } + var securityOpt []string if c.Changed("security-opt") { - if err := secConfig.SetSecurityOpts(runtime, c.StringArray("security-opt")); err != nil { - return nil, err - } + securityOpt = c.StringArray("security-opt") + } + if err := secConfig.SetSecurityOpts(runtime, securityOpt); err != nil { + return nil, err } // SECCOMP diff --git a/cmd/podman/varlink.go b/cmd/podman/varlink.go index 20334ec96..be882d497 100644 --- a/cmd/podman/varlink.go +++ b/cmd/podman/varlink.go @@ -85,7 +85,7 @@ func varlinkCmd(c *cliconfig.VarlinkValues) error { } defer runtime.DeferredShutdown(false) - var varlinkInterfaces = []*iopodman.VarlinkInterface{varlinkapi.New(&c.PodmanCommand, runtime)} + var varlinkInterfaces = []*iopodman.VarlinkInterface{varlinkapi.New(c.PodmanCommand.Command, runtime)} // Register varlink service. The metadata can be retrieved with: // $ varlink info [varlink address URI] service, err := varlink.NewService( diff --git a/cmd/podman/varlink_dummy.go b/cmd/podman/varlink_dummy.go index 430511d72..0c7981f1a 100644 --- a/cmd/podman/varlink_dummy.go +++ b/cmd/podman/varlink_dummy.go @@ -5,6 +5,7 @@ package main import "github.com/spf13/cobra" var ( + // nolint:varcheck,deadcode,unused _varlinkCommand = &cobra.Command{ Use: "", } diff --git a/cmd/podmanV2/Makefile b/cmd/podmanV2/Makefile index b847a9385..01d551212 100644 --- a/cmd/podmanV2/Makefile +++ b/cmd/podmanV2/Makefile @@ -1,2 +1,10 @@ -all: - CGO_ENABLED=1 GO111MODULE=off go build -tags 'ABISupport systemd seccomp' +all: podman podman-remote + +podman: + CGO_ENABLED=1 GO111MODULE=off go build -tags 'ABISupport systemd varlink seccomp' + +podman-remote: + CGO_ENABLED=1 GO111MODULE=off go build -tags '!ABISupport systemd seccomp' -o podmanV2-remote + +clean: + rm podmanV2 podmanV2-remote diff --git a/cmd/podmanV2/containers/cleanup.go b/cmd/podmanV2/containers/cleanup.go new file mode 100644 index 000000000..3f45db160 --- /dev/null +++ b/cmd/podmanV2/containers/cleanup.go @@ -0,0 +1,75 @@ +package containers + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podmanV2/parse" + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/cmd/podmanV2/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + cleanupDescription = ` + podman container cleanup + + Cleans up mount points and network stacks on one or more containers from the host. The container name or ID can be used. This command is used internally when running containers, but can also be used if container cleanup has failed when a container exits. +` + cleanupCommand = &cobra.Command{ + Use: "cleanup [flags] CONTAINER [CONTAINER...]", + Short: "Cleanup network and mountpoints of one or more containers", + Long: cleanupDescription, + RunE: cleanup, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman container cleanup --latest + podman container cleanup ctrID1 ctrID2 ctrID3 + podman container cleanup --all`, + } +) + +var ( + cleanupOptions entities.ContainerCleanupOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Parent: containerCmd, + Command: cleanupCommand, + }) + flags := cleanupCommand.Flags() + flags.BoolVarP(&cleanupOptions.All, "all", "a", false, "Cleans up all containers") + flags.BoolVarP(&cleanupOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&cleanupOptions.Remove, "rm", false, "After cleanup, remove the container entirely") + flags.BoolVar(&cleanupOptions.RemoveImage, "rmi", false, "After cleanup, remove the image entirely") + +} + +func cleanup(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + ) + responses, err := registry.ContainerEngine().ContainerCleanup(registry.GetContext(), args, cleanupOptions) + if err != nil { + return err + } + for _, r := range responses { + if r.CleanErr == nil && r.RmErr == nil && r.RmiErr == nil { + fmt.Println(r.Id) + continue + } + if r.RmErr != nil { + errs = append(errs, r.RmErr) + } + if r.RmiErr != nil { + errs = append(errs, r.RmiErr) + } + if r.CleanErr != nil { + errs = append(errs, r.CleanErr) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podmanV2/containers/init.go b/cmd/podmanV2/containers/init.go new file mode 100644 index 000000000..dd1e1d21b --- /dev/null +++ b/cmd/podmanV2/containers/init.go @@ -0,0 +1,60 @@ +package containers + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podmanV2/parse" + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/cmd/podmanV2/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + initDescription = `Initialize one or more containers, creating the OCI spec and mounts for inspection. Container names or IDs can be used.` + + initCommand = &cobra.Command{ + Use: "init [flags] CONTAINER [CONTAINER...]", + Short: "Initialize one or more containers", + Long: initDescription, + PreRunE: preRunE, + RunE: initContainer, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman init --latest + podman init 3c45ef19d893 + podman init test1`, + } +) + +var ( + initOptions entities.ContainerInitOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: initCommand, + }) + flags := initCommand.Flags() + flags.BoolVarP(&initOptions.All, "all", "a", false, "Initialize all containers") + flags.BoolVarP(&initOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + _ = flags.MarkHidden("latest") +} + +func initContainer(cmd *cobra.Command, args []string) error { + var errs utils.OutputErrors + report, err := registry.ContainerEngine().ContainerInit(registry.GetContext(), args, initOptions) + if err != nil { + return err + } + for _, r := range report { + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podmanV2/containers/kill.go b/cmd/podmanV2/containers/kill.go index 6e6debfec..3155d1f34 100644 --- a/cmd/podmanV2/containers/kill.go +++ b/cmd/podmanV2/containers/kill.go @@ -2,6 +2,7 @@ package containers import ( "context" + "errors" "fmt" "github.com/containers/libpod/cmd/podmanV2/parse" @@ -54,9 +55,13 @@ func kill(cmd *cobra.Command, args []string) error { ) // Check if the signalString provided by the user is valid // Invalid signals will return err - if _, err = signal.ParseSignalNameOrNumber(killOptions.Signal); err != nil { + sig, err := signal.ParseSignalNameOrNumber(killOptions.Signal) + if err != nil { return err } + if sig < 1 || sig > 64 { + return errors.New("valid signals are 1 through 64") + } responses, err := registry.ContainerEngine().ContainerKill(context.Background(), args, killOptions) if err != nil { return err diff --git a/cmd/podmanV2/containers/logs.go b/cmd/podmanV2/containers/logs.go new file mode 100644 index 000000000..d1a179495 --- /dev/null +++ b/cmd/podmanV2/containers/logs.go @@ -0,0 +1,108 @@ +package containers + +import ( + "os" + + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// logsOptionsWrapper wraps entities.LogsOptions and prevents leaking +// CLI-only fields into the API types. +type logsOptionsWrapper struct { + entities.ContainerLogsOptions + + SinceRaw string +} + +var ( + logsOptions logsOptionsWrapper + logsDescription = `Retrieves logs for one or more containers. + + This does not guarantee execution order when combined with podman run (i.e., your run may not have generated any logs at the time you execute podman logs). +` + logsCommand = &cobra.Command{ + Use: "logs [flags] CONTAINER [CONTAINER...]", + Short: "Fetch the logs of one or more container", + Long: logsDescription, + RunE: logs, + PreRunE: preRunE, + Example: `podman logs ctrID + podman logs --names ctrID1 ctrID2 + podman logs --tail 2 mywebserver + podman logs --follow=true --since 10m ctrID + podman logs mywebserver mydbserver`, + } + + containerLogsCommand = &cobra.Command{ + Use: logsCommand.Use, + Short: logsCommand.Short, + Long: logsCommand.Long, + PreRunE: logsCommand.PreRunE, + RunE: logsCommand.RunE, + Example: `podman container logs ctrID + podman container logs --names ctrID1 ctrID2 + podman container logs --tail 2 mywebserver + podman container logs --follow=true --since 10m ctrID + podman container logs mywebserver mydbserver`, + } +) + +func init() { + // logs + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: logsCommand, + }) + + logsCommand.SetHelpTemplate(registry.HelpTemplate()) + logsCommand.SetUsageTemplate(registry.UsageTemplate()) + + flags := logsCommand.Flags() + logsFlags(flags) + + // container logs + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: containerLogsCommand, + Parent: containerCmd, + }) + + containerLogsFlags := containerLogsCommand.Flags() + logsFlags(containerLogsFlags) +} + +func logsFlags(flags *pflag.FlagSet) { + flags.BoolVar(&logsOptions.Details, "details", false, "Show extra details provided to the logs") + flags.BoolVarP(&logsOptions.Follow, "follow", "f", false, "Follow log output. The default is false") + flags.BoolVarP(&logsOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.StringVar(&logsOptions.SinceRaw, "since", "", "Show logs since TIMESTAMP") + flags.Int64Var(&logsOptions.Tail, "tail", -1, "Output the specified number of LINES at the end of the logs. Defaults to -1, which prints all lines") + flags.BoolVarP(&logsOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log") + flags.BoolVarP(&logsOptions.Names, "names", "n", false, "Output the container name in the log") + flags.SetInterspersed(false) + _ = flags.MarkHidden("details") +} + +func logs(cmd *cobra.Command, args []string) error { + if len(args) > 0 && logsOptions.Latest { + return errors.New("no containers can be specified when using 'latest'") + } + if !logsOptions.Latest && len(args) < 1 { + return errors.New("specify at least one container name or ID to log") + } + if logsOptions.SinceRaw != "" { + // parse time, error out if something is wrong + since, err := util.ParseInputTime(logsOptions.SinceRaw) + if err != nil { + return errors.Wrapf(err, "error parsing --since %q", logsOptions.SinceRaw) + } + logsOptions.Since = since + } + logsOptions.Writer = os.Stdout + return registry.ContainerEngine().ContainerLogs(registry.GetContext(), args, logsOptions.ContainerLogsOptions) +} diff --git a/cmd/podmanV2/containers/mount.go b/cmd/podmanV2/containers/mount.go new file mode 100644 index 000000000..4f7b95d98 --- /dev/null +++ b/cmd/podmanV2/containers/mount.go @@ -0,0 +1,124 @@ +package containers + +import ( + "encoding/json" + "fmt" + "os" + "text/tabwriter" + "text/template" + + "github.com/containers/libpod/cmd/podmanV2/parse" + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/cmd/podmanV2/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + mountDescription = `podman mount + Lists all mounted containers mount points if no container is specified + + podman mount CONTAINER-NAME-OR-ID + Mounts the specified container and outputs the mountpoint +` + + mountCommand = &cobra.Command{ + Use: "mount [flags] [CONTAINER]", + Short: "Mount a working container's root filesystem", + Long: mountDescription, + PreRunE: preRunE, + RunE: mount, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, true, false) + }, + Annotations: map[string]string{ + registry.RootRequired: "true", + }, + } +) + +var ( + mountOpts entities.ContainerMountOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: mountCommand, + }) + flags := mountCommand.Flags() + flags.BoolVarP(&mountOpts.All, "all", "a", false, "Mount all containers") + flags.StringVar(&mountOpts.Format, "format", "", "Change the output format to Go template") + flags.BoolVarP(&mountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&mountOpts.NoTruncate, "notruncate", false, "Do not truncate output") +} + +func mount(cmd *cobra.Command, args []string) error { + var ( + errs utils.OutputErrors + mrs []mountReporter + ) + reports, err := registry.ContainerEngine().ContainerMount(registry.GetContext(), args, mountOpts) + if err != nil { + return err + } + if len(args) > 0 || mountOpts.Latest || mountOpts.All { + for _, r := range reports { + if r.Err == nil { + fmt.Println(r.Path) + continue + } + errs = append(errs, r.Err) + } + return errs.PrintErrors() + } + if mountOpts.Format == "json" { + return printJSON(reports) + } + for _, r := range reports { + mrs = append(mrs, mountReporter{r}) + } + row := "{{.ID}} {{.Path}}" + format := "{{range . }}" + row + "{{end}}" + tmpl, err := template.New("mounts").Parse(format) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer w.Flush() + return tmpl.Execute(w, mrs) +} + +func printJSON(reports []*entities.ContainerMountReport) error { + type jreport struct { + ID string `json:"id"` + Names []string + Mountpoint string `json:"mountpoint"` + } + var jreports []jreport + + for _, r := range reports { + jreports = append(jreports, jreport{ + ID: r.Id, + Names: []string{r.Name}, + Mountpoint: r.Path, + }) + } + b, err := json.MarshalIndent(jreports, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} + +type mountReporter struct { + *entities.ContainerMountReport +} + +func (m mountReporter) ID() string { + if mountOpts.NoTruncate { + return m.Id + } + return m.Id[0:12] +} diff --git a/cmd/podmanV2/containers/ps.go b/cmd/podmanV2/containers/ps.go index 2397eb8c0..8ebbf6ebf 100644 --- a/cmd/podmanV2/containers/ps.go +++ b/cmd/podmanV2/containers/ps.go @@ -13,9 +13,7 @@ import ( tm "github.com/buger/goterm" "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podmanV2/registry" - "github.com/containers/libpod/cmd/podmanV2/report" "github.com/containers/libpod/pkg/domain/entities" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" @@ -44,9 +42,6 @@ var ( filters []string noTrunc bool defaultHeaders string = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES" - -// CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - ) func init() { @@ -143,7 +138,7 @@ func getResponses() ([]entities.ListContainer, error) { } func ps(cmd *cobra.Command, args []string) error { - // []string to map[string][]string + var responses []psReporter for _, f := range filters { split := strings.SplitN(f, "=", 2) if len(split) == 1 { @@ -151,22 +146,27 @@ func ps(cmd *cobra.Command, args []string) error { } listOpts.Filters[split[0]] = append(listOpts.Filters[split[0]], split[1]) } - responses, err := getResponses() + listContainers, err := getResponses() if err != nil { return err } if len(listOpts.Sort) > 0 { - responses, err = entities.SortPsOutput(listOpts.Sort, responses) + listContainers, err = entities.SortPsOutput(listOpts.Sort, listContainers) if err != nil { return err } } if listOpts.Format == "json" { - return jsonOut(responses) + return jsonOut(listContainers) } if listOpts.Quiet { - return quietOut(responses) + return quietOut(listContainers) + } + + for _, r := range listContainers { + responses = append(responses, psReporter{r}) } + headers, row := createPsOut() if cmd.Flag("format").Changed { row = listOpts.Format @@ -178,18 +178,21 @@ func ps(cmd *cobra.Command, args []string) error { if !listOpts.Quiet && !cmd.Flag("format").Changed { format = headers + format } - funcs := report.AppendFuncMap(psFuncMap) - tmpl, err := template.New("listPods").Funcs(funcs).Parse(format) + tmpl, err := template.New("listContainers").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) if listOpts.Watch > 0 { for { + var responses []psReporter tm.Clear() tm.MoveCursor(1, 1) tm.Flush() - responses, err := getResponses() + listContainers, err := getResponses() + for _, r := range listContainers { + responses = append(responses, psReporter{r}) + } if err != nil { return err } @@ -217,30 +220,21 @@ func createPsOut() (string, string) { var row string if listOpts.Namespace { headers := "CONTAINER ID\tNAMES\tPID\tCGROUPNS\tIPC\tMNT\tNET\tPIDN\tUSERNS\tUTS\n" - row := "{{.ID}}\t{{names .Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}\n" + row := "{{.ID}}\t{{.Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}\n" return headers, row } headers := defaultHeaders - if noTrunc { - row += "{{.ID}}" - } else { - row += "{{slice .ID 0 12}}" - } - row += "\t{{.Image}}\t{{cmd .Command}}\t{{humanDuration .Created}}\t{{state .}}\t{{ports .Ports}}\t{{names .Names}}" + row += "{{.ID}}" + row += "\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.State}}\t{{.Ports}}\t{{.Names}}" if listOpts.Pod { headers += "\tPOD ID\tPODNAME" - if noTrunc { - row += "\t{{.Pod}}" - } else { - row += "\t{{slice .Pod 0 12}}" - } - row += "\t{{.PodName}}" + row += "\t{{.Pod}}\t{{.PodName}}" } if listOpts.Size { headers += "\tSIZE" - row += "\t{{consize .Size}}" + row += "\t{{.Size}}" } if !strings.HasSuffix(headers, "\n") { headers += "\n" @@ -251,40 +245,81 @@ func createPsOut() (string, string) { return headers, row } -var psFuncMap = template.FuncMap{ - "cmd": func(conCommand []string) string { - return strings.Join(conCommand, " ") - }, - "state": func(con entities.ListContainer) string { - var state string - switch con.State { - case "running": - t := units.HumanDuration(time.Since(time.Unix(con.StartedAt, 0))) - state = "Up " + t + " ago" - case "configured": - state = "Created" - case "exited": - t := units.HumanDuration(time.Since(time.Unix(con.ExitedAt, 0))) - state = fmt.Sprintf("Exited (%d) %s ago", con.ExitCode, t) - default: - state = con.State - } - return state - }, - "ports": func(ports []ocicni.PortMapping) string { - if len(ports) == 0 { - return "" - } - return portsToString(ports) - }, - "names": func(names []string) string { - return names[0] - }, - "consize": func(csize shared.ContainerSize) string { - virt := units.HumanSizeWithPrecision(float64(csize.RootFsSize), 3) - s := units.HumanSizeWithPrecision(float64(csize.RwSize), 3) - return fmt.Sprintf("%s (virtual %s)", s, virt) - }, +type psReporter struct { + entities.ListContainer +} + +// ID returns the ID of the container +func (l psReporter) ID() string { + if !noTrunc { + return l.ListContainer.ID[0:12] + } + return l.ListContainer.ID +} + +// Pod returns the ID of the pod the container +// belongs to and appropriately truncates the ID +func (l psReporter) Pod() string { + if !noTrunc && len(l.ListContainer.Pod) > 0 { + return l.ListContainer.Pod[0:12] + } + return l.ListContainer.Pod +} + +// State returns the container state in human duration +func (l psReporter) State() string { + var state string + switch l.ListContainer.State { + case "running": + t := units.HumanDuration(time.Since(time.Unix(l.StartedAt, 0))) + state = "Up " + t + " ago" + case "configured": + state = "Created" + case "exited", "stopped": + t := units.HumanDuration(time.Since(time.Unix(l.ExitedAt, 0))) + state = fmt.Sprintf("Exited (%d) %s ago", l.ExitCode, t) + default: + state = l.ListContainer.State + } + return state +} + +// Command returns the container command in string format +func (l psReporter) Command() string { + return strings.Join(l.ListContainer.Command, " ") +} + +// Size returns the rootfs and virtual sizes in human duration in +// and output form (string) suitable for ps +func (l psReporter) Size() string { + virt := units.HumanSizeWithPrecision(float64(l.ListContainer.Size.RootFsSize), 3) + s := units.HumanSizeWithPrecision(float64(l.ListContainer.Size.RwSize), 3) + return fmt.Sprintf("%s (virtual %s)", s, virt) +} + +// Names returns the container name in string format +func (l psReporter) Names() string { + return l.ListContainer.Names[0] +} + +// Ports converts from Portmappings to the string form +// required by ps +func (l psReporter) Ports() string { + if len(l.ListContainer.Ports) < 1 { + return "" + } + return portsToString(l.ListContainer.Ports) +} + +// CreatedAt returns the container creation time in string format. podman +// and docker both return a timestamped value for createdat +func (l psReporter) CreatedAt() string { + return time.Unix(l.Created, 0).String() +} + +// CreateHuman allows us to output the created time in human readable format +func (l psReporter) CreatedHuman() string { + return units.HumanDuration(time.Since(time.Unix(l.Created, 0))) + " ago" } // portsToString converts the ports used to a string of the from "port1, port2" diff --git a/cmd/podmanV2/containers/run.go b/cmd/podmanV2/containers/run.go index 5c0069afc..0bf0f90f8 100644 --- a/cmd/podmanV2/containers/run.go +++ b/cmd/podmanV2/containers/run.go @@ -5,15 +5,14 @@ import ( "os" "strings" - "github.com/sirupsen/logrus" - - "github.com/containers/libpod/pkg/domain/entities" - + "github.com/containers/common/pkg/config" "github.com/containers/libpod/cmd/podmanV2/common" "github.com/containers/libpod/cmd/podmanV2/registry" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/specgen" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -75,6 +74,30 @@ func run(cmd *cobra.Command, args []string) error { return err } + ie, err := registry.NewImageEngine(cmd, args) + if err != nil { + return err + } + br, err := ie.Exists(registry.GetContext(), args[0]) + if err != nil { + return err + } + pullPolicy, err := config.ValidatePullPolicy(cliVals.Pull) + if err != nil { + return err + } + if !br.Value || pullPolicy == config.PullImageAlways { + if pullPolicy == config.PullImageNever { + return errors.New("unable to find a name and tag match for busybox in repotags: no such image") + } + _, pullErr := ie.Pull(registry.GetContext(), args[0], entities.ImagePullOptions{ + Authfile: cliVals.Authfile, + Quiet: cliVals.Quiet, + }) + if pullErr != nil { + return pullErr + } + } // If -i is not set, clear stdin if !cliVals.Interactive { runOpts.InputStream = nil @@ -109,15 +132,18 @@ func run(cmd *cobra.Command, args []string) error { } runOpts.Spec = s report, err := registry.ContainerEngine().ContainerRun(registry.GetContext(), runOpts) + // report.ExitCode is set by ContainerRun even it it returns an error + if report != nil { + registry.SetExitCode(report.ExitCode) + } if err != nil { return err } if cliVals.Detach { fmt.Println(report.Id) } - registry.SetExitCode(report.ExitCode) if runRmi { - _, err := registry.ImageEngine().Delete(registry.GetContext(), []string{report.Id}, entities.ImageDeleteOptions{}) + _, err := registry.ImageEngine().Delete(registry.GetContext(), []string{args[0]}, entities.ImageDeleteOptions{}) if err != nil { logrus.Errorf("%s", errors.Wrapf(err, "failed removing image")) } diff --git a/cmd/podmanV2/containers/stop.go b/cmd/podmanV2/containers/stop.go index d6f31352f..53ec2934d 100644 --- a/cmd/podmanV2/containers/stop.go +++ b/cmd/podmanV2/containers/stop.go @@ -46,7 +46,7 @@ func init() { flags.StringArrayVarP(&stopOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") flags.BoolVarP(&stopOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.UintVarP(&stopTimeout, "time", "t", defaultContainerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") - if registry.EngineOptions.EngineMode == entities.ABIMode { + if registry.PodmanOptions.EngineMode == entities.ABIMode { _ = flags.MarkHidden("latest") _ = flags.MarkHidden("cidfile") _ = flags.MarkHidden("ignore") diff --git a/cmd/podmanV2/containers/unmount.go b/cmd/podmanV2/containers/unmount.go new file mode 100644 index 000000000..2a6ef14b0 --- /dev/null +++ b/cmd/podmanV2/containers/unmount.go @@ -0,0 +1,65 @@ +package containers + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podmanV2/parse" + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/cmd/podmanV2/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + description = `Container storage increments a mount counter each time a container is mounted. + + When a container is unmounted, the mount counter is decremented. The container's root filesystem is physically unmounted only when the mount counter reaches zero indicating no other processes are using the mount. + + An unmount can be forced with the --force flag. +` + umountCommand = &cobra.Command{ + Use: "umount [flags] CONTAINER [CONTAINER...]", + Aliases: []string{"unmount"}, + Short: "Unmounts working container's root filesystem", + Long: description, + RunE: unmount, + PreRunE: preRunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) + }, + Example: `podman umount ctrID + podman umount ctrID1 ctrID2 ctrID3 + podman umount --all`, + } +) + +var ( + unmountOpts entities.ContainerUnmountOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: umountCommand, + }) + flags := umountCommand.Flags() + flags.BoolVarP(&unmountOpts.All, "all", "a", false, "Umount all of the currently mounted containers") + flags.BoolVarP(&unmountOpts.Force, "force", "f", false, "Force the complete umount all of the currently mounted containers") + flags.BoolVarP(&unmountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") +} + +func unmount(cmd *cobra.Command, args []string) error { + var errs utils.OutputErrors + reports, err := registry.ContainerEngine().ContainerUnmount(registry.GetContext(), args, unmountOpts) + if err != nil { + return err + } + for _, r := range reports { + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podmanV2/containers/wait.go b/cmd/podmanV2/containers/wait.go index bf3c86200..3d11c581e 100644 --- a/cmd/podmanV2/containers/wait.go +++ b/cmd/podmanV2/containers/wait.go @@ -5,7 +5,6 @@ import ( "fmt" "time" - "github.com/containers/libpod/cmd/podmanV2/parse" "github.com/containers/libpod/cmd/podmanV2/registry" "github.com/containers/libpod/cmd/podmanV2/utils" "github.com/containers/libpod/libpod/define" @@ -23,9 +22,7 @@ var ( Long: waitDescription, RunE: wait, PersistentPreRunE: preRunE, - Args: func(cmd *cobra.Command, args []string) error { - return parse.CheckAllLatestAndCIDFile(cmd, args, false, false) - }, + Args: registry.IdOrLatestArgs, Example: `podman wait --latest podman wait --interval 5000 ctrID podman wait ctrID1 ctrID2`, @@ -47,7 +44,7 @@ func init() { flags.DurationVarP(&waitOptions.Interval, "interval", "i", time.Duration(250), "Milliseconds to wait before polling for completion") flags.BoolVarP(&waitOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.StringVar(&waitCondition, "condition", "stopped", "Condition to wait on") - if registry.EngineOptions.EngineMode == entities.ABIMode { + if registry.PodmanOptions.EngineMode == entities.ABIMode { // TODO: This is the same as V1. We could skip creating the flag altogether in V2... _ = flags.MarkHidden("latest") } @@ -73,7 +70,7 @@ func wait(cmd *cobra.Command, args []string) error { } for _, r := range responses { if r.Error == nil { - fmt.Println(r.Id) + fmt.Println(r.ExitCode) } else { errs = append(errs, r.Error) } diff --git a/cmd/podmanV2/images/history.go b/cmd/podmanV2/images/history.go index f6f15e2f2..e3bb7a051 100644 --- a/cmd/podmanV2/images/history.go +++ b/cmd/podmanV2/images/history.go @@ -8,10 +8,11 @@ import ( "text/tabwriter" "text/template" "time" + "unicode" "github.com/containers/libpod/cmd/podmanV2/registry" - "github.com/containers/libpod/cmd/podmanV2/report" "github.com/containers/libpod/pkg/domain/entities" + "github.com/docker/go-units" jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -52,7 +53,7 @@ func init() { flags := historyCmd.Flags() flags.StringVar(&opts.format, "format", "", "Change the output to JSON or a Go template") - flags.BoolVarP(&opts.human, "human", "H", false, "Display sizes and dates in human readable format") + flags.BoolVarP(&opts.human, "human", "H", true, "Display sizes and dates in human readable format") flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output") flags.BoolVar(&opts.noTrunc, "notruncate", false, "Do not truncate the output") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Display the numeric IDs only") @@ -78,7 +79,7 @@ func history(cmd *cobra.Command, args []string) error { layers := make([]layer, len(results.Layers)) for i, l := range results.Layers { layers[i].ImageHistoryLayer = l - layers[i].Created = time.Unix(l.Created, 0).Format(time.RFC3339) + layers[i].Created = l.Created.Format(time.RFC3339) } json := jsoniter.ConfigCompatibleWithStandardLibrary enc := json.NewEncoder(os.Stdout) @@ -86,10 +87,13 @@ func history(cmd *cobra.Command, args []string) error { } return err } - + var hr []historyreporter + for _, l := range results.Layers { + hr = append(hr, historyreporter{l}) + } // Defaults hdr := "ID\tCREATED\tCREATED BY\tSIZE\tCOMMENT\n" - row := "{{slice .ID 0 12}}\t{{humanDuration .Created}}\t{{ellipsis .CreatedBy 45}}\t{{.Size}}\t{{.Comment}}\n" + row := "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" if len(opts.format) > 0 { hdr = "" @@ -100,9 +104,9 @@ func history(cmd *cobra.Command, args []string) error { } else { switch { case opts.human: - row = "{{slice .ID 0 12}}\t{{humanDuration .Created}}\t{{ellipsis .CreatedBy 45}}\t{{humanSize .Size}}\t{{.Comment}}\n" + row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" case opts.noTrunc: - row = "{{.ID}}\t{{humanDuration .Created}}\t{{.CreatedBy}}\t{{humanSize .Size}}\t{{.Comment}}\n" + row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" case opts.quiet: hdr = "" row = "{{.ID}}\n" @@ -110,14 +114,43 @@ func history(cmd *cobra.Command, args []string) error { } format := hdr + "{{range . }}" + row + "{{end}}" - tmpl := template.Must(template.New("report").Funcs(report.PodmanTemplateFuncs()).Parse(format)) + tmpl := template.Must(template.New("report").Parse(format)) w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) - - _, _ = w.Write(report.ReportHeader("id", "created", "created by", "size", "comment")) - err = tmpl.Execute(w, results.Layers) + err = tmpl.Execute(w, hr) if err != nil { fmt.Fprintln(os.Stderr, errors.Wrapf(err, "Failed to print report")) } w.Flush() return nil } + +type historyreporter struct { + entities.ImageHistoryLayer +} + +func (h historyreporter) Created() string { + if opts.human { + return units.HumanDuration(time.Since(h.ImageHistoryLayer.Created)) + " ago" + } + return h.ImageHistoryLayer.Created.Format(time.RFC3339) +} + +func (h historyreporter) Size() string { + s := units.HumanSizeWithPrecision(float64(h.ImageHistoryLayer.Size), 3) + i := strings.LastIndexFunc(s, unicode.IsNumber) + return s[:i+1] + " " + s[i+1:] +} + +func (h historyreporter) CreatedBy() string { + if len(h.ImageHistoryLayer.CreatedBy) > 45 { + return h.ImageHistoryLayer.CreatedBy[:45-3] + "..." + } + return h.ImageHistoryLayer.CreatedBy +} + +func (h historyreporter) ID() string { + if !opts.noTrunc && len(h.ImageHistoryLayer.ID) >= 12 { + return h.ImageHistoryLayer.ID[0:12] + } + return h.ImageHistoryLayer.ID +} diff --git a/cmd/podmanV2/images/inspect.go b/cmd/podmanV2/images/inspect.go index d7f6b0ee1..2ee2d86ee 100644 --- a/cmd/podmanV2/images/inspect.go +++ b/cmd/podmanV2/images/inspect.go @@ -67,7 +67,6 @@ func inspect(cmd *cobra.Command, args []string) error { } return nil } - row := inspectFormat(inspectOpts.Format) format := "{{range . }}" + row + "{{end}}" tmpl, err := template.New("inspect").Parse(format) @@ -77,7 +76,7 @@ func inspect(cmd *cobra.Command, args []string) error { w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer func() { _ = w.Flush() }() - err = tmpl.Execute(w, results) + err = tmpl.Execute(w, results.Images) if err != nil { return err } diff --git a/cmd/podmanV2/images/list.go b/cmd/podmanV2/images/list.go index 2d6cb3596..d594a4323 100644 --- a/cmd/podmanV2/images/list.go +++ b/cmd/podmanV2/images/list.go @@ -9,10 +9,11 @@ import ( "text/tabwriter" "text/template" "time" + "unicode" "github.com/containers/libpod/cmd/podmanV2/registry" - "github.com/containers/libpod/cmd/podmanV2/report" "github.com/containers/libpod/pkg/domain/entities" + "github.com/docker/go-units" jsoniter "github.com/json-iterator/go" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -130,16 +131,13 @@ func writeJSON(imageS []*entities.ImageSummary) error { } func writeTemplate(imageS []*entities.ImageSummary, err error) error { - type image struct { - entities.ImageSummary - Repository string `json:"repository,omitempty"` - Tag string `json:"tag,omitempty"` - } - - imgs := make([]image, 0, len(imageS)) + var ( + hdr, row string + ) + imgs := make([]imageReporter, 0, len(imageS)) for _, e := range imageS { for _, tag := range e.RepoTags { - var h image + var h imageReporter h.ImageSummary = *e h.Repository, h.Tag = tokenRepoTag(tag) imgs = append(imgs, h) @@ -148,11 +146,16 @@ func writeTemplate(imageS []*entities.ImageSummary, err error) error { listFlag.readOnly = true } } - - hdr, row := imageListFormat(listFlag) + if len(listFlag.format) < 1 { + hdr, row = imageListFormat(listFlag) + } else { + row = listFlag.format + if !strings.HasSuffix(row, "\n") { + row += "\n" + } + } format := hdr + "{{range . }}" + row + "{{end}}" - - tmpl := template.Must(template.New("list").Funcs(report.PodmanTemplateFuncs()).Parse(format)) + tmpl := template.Must(template.New("list").Parse(format)) w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() return tmpl.Execute(w, imgs) @@ -200,7 +203,7 @@ func sortFunc(key string, data []*entities.ImageSummary) func(i, j int) bool { func imageListFormat(flags listFlagType) (string, string) { if flags.quiet { - return "", "{{slice .ID 0 12}}\n" + return "", "{{.ID}}\n" } // Defaults @@ -216,15 +219,15 @@ func imageListFormat(flags listFlagType) (string, string) { if flags.noTrunc { row += "\tsha256:{{.ID}}" } else { - row += "\t{{slice .ID 0 12}}" + row += "\t{{.ID}}" } hdr += "\tCREATED\tSIZE" - row += "\t{{humanDuration .Created}}\t{{humanSize .Size}}" + row += "\t{{.Created}}\t{{.Size}}" if flags.history { hdr += "\tHISTORY" - row += "\t{{if .History}}{{join .History \", \"}}{{else}}<none>{{end}}" + row += "\t{{if .History}}{{.History}}{{else}}<none>{{end}}" } if flags.readOnly { @@ -241,3 +244,30 @@ func imageListFormat(flags listFlagType) (string, string) { row += "\n" return hdr, row } + +type imageReporter struct { + Repository string `json:"repository,omitempty"` + Tag string `json:"tag,omitempty"` + entities.ImageSummary +} + +func (i imageReporter) ID() string { + if !listFlag.noTrunc && len(i.ImageSummary.ID) >= 12 { + return i.ImageSummary.ID[0:12] + } + return i.ImageSummary.ID +} + +func (i imageReporter) Created() string { + return units.HumanDuration(time.Since(time.Unix(i.ImageSummary.Created, 0))) + " ago" +} + +func (i imageReporter) Size() string { + s := units.HumanSizeWithPrecision(float64(i.ImageSummary.Size), 3) + j := strings.LastIndexFunc(s, unicode.IsNumber) + return s[:j+1] + " " + s[j+1:] +} + +func (i imageReporter) History() string { + return strings.Join(i.ImageSummary.History, ", ") +} diff --git a/cmd/podmanV2/images/load.go b/cmd/podmanV2/images/load.go index f60dc4908..315dbed3a 100644 --- a/cmd/podmanV2/images/load.go +++ b/cmd/podmanV2/images/load.go @@ -3,11 +3,18 @@ package images import ( "context" "fmt" + "io" + "io/ioutil" + "os" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/libpod/cmd/podmanV2/parse" "github.com/containers/libpod/cmd/podmanV2/registry" - "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" ) var ( @@ -46,11 +53,40 @@ func init() { func load(cmd *cobra.Command, args []string) error { if len(args) > 0 { - repo, err := image.NormalizedTag(args[0]) + ref, err := reference.Parse(args[0]) if err != nil { return err } - loadOpts.Name = repo.Name() + if t, ok := ref.(reference.Tagged); ok { + loadOpts.Tag = t.Tag() + } else { + loadOpts.Tag = "latest" + } + if r, ok := ref.(reference.Named); ok { + fmt.Println(r.Name()) + loadOpts.Name = r.Name() + } + } + if len(loadOpts.Input) > 0 { + if err := parse.ValidateFileName(loadOpts.Input); err != nil { + return err + } + } else { + if terminal.IsTerminal(int(os.Stdin.Fd())) { + return errors.Errorf("cannot read from terminal. Use command-line redirection or the --input flag.") + } + outFile, err := ioutil.TempFile(util.Tmpdir(), "podman") + if err != nil { + return errors.Errorf("error creating file %v", err) + } + defer os.Remove(outFile.Name()) + defer outFile.Close() + + _, err = io.Copy(outFile, os.Stdin) + if err != nil { + return errors.Errorf("error copying file %v", err) + } + loadOpts.Input = outFile.Name() } response, err := registry.ImageEngine().Load(context.Background(), loadOpts) if err != nil { diff --git a/cmd/podmanV2/images/rm.go b/cmd/podmanV2/images/rm.go index bb5880de3..6784182d9 100644 --- a/cmd/podmanV2/images/rm.go +++ b/cmd/podmanV2/images/rm.go @@ -8,6 +8,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -33,11 +34,13 @@ func init() { Parent: imageCmd, }) - flags := rmCmd.Flags() + imageRemoveFlagSet(rmCmd.Flags()) +} + +func imageRemoveFlagSet(flags *pflag.FlagSet) { flags.BoolVarP(&imageOpts.All, "all", "a", false, "Remove all images") flags.BoolVarP(&imageOpts.Force, "force", "f", false, "Force Removal of the image") } - func rm(cmd *cobra.Command, args []string) error { if len(args) < 1 && !imageOpts.All { @@ -46,7 +49,6 @@ func rm(cmd *cobra.Command, args []string) error { if len(args) > 0 && imageOpts.All { return errors.Errorf("when using the --all switch, you may not pass any images names or IDs") } - report, err := registry.ImageEngine().Delete(registry.GetContext(), args, imageOpts) if err != nil { switch { diff --git a/cmd/podmanV2/images/rmi.go b/cmd/podmanV2/images/rmi.go index 7f9297bc9..973763966 100644 --- a/cmd/podmanV2/images/rmi.go +++ b/cmd/podmanV2/images/rmi.go @@ -27,4 +27,5 @@ func init() { }) rmiCmd.SetHelpTemplate(registry.HelpTemplate()) rmiCmd.SetUsageTemplate(registry.UsageTemplate()) + imageRemoveFlagSet(rmiCmd.Flags()) } diff --git a/cmd/podmanV2/images/search.go b/cmd/podmanV2/images/search.go new file mode 100644 index 000000000..2ab9735ec --- /dev/null +++ b/cmd/podmanV2/images/search.go @@ -0,0 +1,157 @@ +package images + +import ( + "reflect" + "strings" + + buildahcli "github.com/containers/buildah/pkg/cli" + "github.com/containers/buildah/pkg/formats" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util/camelcase" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// searchOptionsWrapper wraps entities.ImagePullOptions and prevents leaking +// CLI-only fields into the API types. +type searchOptionsWrapper struct { + entities.ImageSearchOptions + // CLI only flags + TLSVerifyCLI bool // Used to convert to an optional bool later + Format string // For go templating +} + +var ( + searchOptions = searchOptionsWrapper{} + searchDescription = `Search registries for a given image. Can search all the default registries or a specific registry. + + Users can limit the number of results, and filter the output based on certain conditions.` + + // Command: podman search + searchCmd = &cobra.Command{ + Use: "search [flags] TERM", + Short: "Search registry for image", + Long: searchDescription, + PreRunE: preRunE, + RunE: imageSearch, + Args: cobra.ExactArgs(1), + Example: `podman search --filter=is-official --limit 3 alpine + podman search registry.fedoraproject.org/ # only works with v2 registries + podman search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, + } + + // Command: podman image search + imageSearchCmd = &cobra.Command{ + Use: searchCmd.Use, + Short: searchCmd.Short, + Long: searchCmd.Long, + PreRunE: searchCmd.PreRunE, + RunE: searchCmd.RunE, + Args: searchCmd.Args, + Example: `podman image search --filter=is-official --limit 3 alpine + podman image search registry.fedoraproject.org/ # only works with v2 registries + podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`, + } +) + +func init() { + // search + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: searchCmd, + }) + + searchCmd.SetHelpTemplate(registry.HelpTemplate()) + searchCmd.SetUsageTemplate(registry.UsageTemplate()) + flags := searchCmd.Flags() + searchFlags(flags) + + // images search + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageSearchCmd, + Parent: imageCmd, + }) + + imageSearchFlags := imageSearchCmd.Flags() + searchFlags(imageSearchFlags) +} + +// searchFlags set the flags for the pull command. +func searchFlags(flags *pflag.FlagSet) { + flags.StringSliceVarP(&searchOptions.Filters, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") + flags.StringVar(&searchOptions.Format, "format", "", "Change the output format to a Go template") + flags.IntVar(&searchOptions.Limit, "limit", 0, "Limit the number of results") + flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output") + flags.StringVar(&searchOptions.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.BoolVar(&searchOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + if registry.IsRemote() { + _ = flags.MarkHidden("authfile") + _ = flags.MarkHidden("tls-verify") + } +} + +// imageSearch implements the command for searching images. +func imageSearch(cmd *cobra.Command, args []string) error { + searchTerm := "" + switch len(args) { + case 1: + searchTerm = args[0] + default: + return errors.Errorf("search requires exactly one argument") + } + + sarchOptsAPI := searchOptions.ImageSearchOptions + // TLS verification in c/image is controlled via a `types.OptionalBool` + // which allows for distinguishing among set-true, set-false, unspecified + // which is important to implement a sane way of dealing with defaults of + // boolean CLI flags. + if cmd.Flags().Changed("tls-verify") { + sarchOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI) + } + + searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, sarchOptsAPI) + if err != nil { + return err + } + + format := genSearchFormat(searchOptions.Format) + if len(searchReport) == 0 { + return nil + } + out := formats.StdoutTemplateArray{Output: searchToGeneric(searchReport), Template: format, Fields: searchHeaderMap()} + return out.Out() +} + +// searchHeaderMap returns the headers of a SearchResult. +func searchHeaderMap() map[string]string { + s := new(entities.ImageSearchReport) + v := reflect.Indirect(reflect.ValueOf(s)) + values := make(map[string]string, v.NumField()) + + for i := 0; i < v.NumField(); i++ { + key := v.Type().Field(i).Name + value := key + values[key] = strings.ToUpper(strings.Join(camelcase.Split(value), " ")) + } + return values +} + +func genSearchFormat(format string) string { + if format != "" { + // "\t" from the command line is not being recognized as a tab + // replacing the string "\t" to a tab character if the user passes in "\t" + return strings.Replace(format, `\t`, "\t", -1) + } + return "table {{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\t" +} + +func searchToGeneric(params []entities.ImageSearchReport) (genericParams []interface{}) { + for _, v := range params { + genericParams = append(genericParams, interface{}(v)) + } + return genericParams +} diff --git a/cmd/podmanV2/images/tag.go b/cmd/podmanV2/images/tag.go index f66fe7857..f8799d4a7 100644 --- a/cmd/podmanV2/images/tag.go +++ b/cmd/podmanV2/images/tag.go @@ -9,11 +9,12 @@ import ( var ( tagDescription = "Adds one or more additional names to locally-stored image." tagCommand = &cobra.Command{ - Use: "tag [flags] IMAGE TARGET_NAME [TARGET_NAME...]", - Short: "Add an additional name to a local image", - Long: tagDescription, - RunE: tag, - Args: cobra.MinimumNArgs(2), + Use: "tag [flags] IMAGE TARGET_NAME [TARGET_NAME...]", + Short: "Add an additional name to a local image", + Long: tagDescription, + RunE: tag, + PreRunE: preRunE, + Args: cobra.MinimumNArgs(2), Example: `podman tag 0e3bbc2 fedora:latest podman tag imageID:latest myNewImage:newTag podman tag httpd myregistryhost:5000/fedora/httpd:v2`, diff --git a/cmd/podmanV2/main.go b/cmd/podmanV2/main.go index fe3cd9f16..cfe20d1c1 100644 --- a/cmd/podmanV2/main.go +++ b/cmd/podmanV2/main.go @@ -3,8 +3,6 @@ package main import ( "os" "reflect" - "runtime" - "strings" _ "github.com/containers/libpod/cmd/podmanV2/containers" _ "github.com/containers/libpod/cmd/podmanV2/healthcheck" @@ -14,36 +12,13 @@ import ( "github.com/containers/libpod/cmd/podmanV2/registry" _ "github.com/containers/libpod/cmd/podmanV2/system" _ "github.com/containers/libpod/cmd/podmanV2/volumes" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/storage/pkg/reexec" - "github.com/sirupsen/logrus" ) func init() { - if err := libpod.SetXdgDirs(); err != nil { - logrus.Errorf(err.Error()) - os.Exit(1) - } - - switch runtime.GOOS { - case "darwin": - fallthrough - case "windows": - registry.EngineOptions.EngineMode = entities.TunnelMode - case "linux": - registry.EngineOptions.EngineMode = entities.ABIMode - default: - logrus.Errorf("%s is not a supported OS", runtime.GOOS) - os.Exit(1) - } - - // TODO: Is there a Cobra way to "peek" at os.Args? - for _, v := range os.Args { - if strings.HasPrefix(v, "--remote") { - registry.EngineOptions.EngineMode = entities.TunnelMode - } - } + // This is the bootstrap configuration, if user gives + // CLI flags parts of this configuration may be overwritten + registry.PodmanOptions = registry.NewPodmanConfig() } func main() { @@ -53,7 +28,7 @@ func main() { return } for _, c := range registry.Commands { - if Contains(registry.EngineOptions.EngineMode, c.Mode) { + if Contains(registry.PodmanOptions.EngineMode, c.Mode) { parent := rootCmd if c.Parent != nil { parent = c.Parent diff --git a/cmd/podmanV2/pods/create.go b/cmd/podmanV2/pods/create.go index ab8957ee3..2aaf0cd2c 100644 --- a/cmd/podmanV2/pods/create.go +++ b/cmd/podmanV2/pods/create.go @@ -123,6 +123,21 @@ func create(cmd *cobra.Command, args []string) error { } } + if !createOptions.Infra { + if cmd.Flag("infra-command").Changed { + return errors.New("cannot set infra-command without an infra container") + } + createOptions.InfraCommand = "" + if cmd.Flag("infra-image").Changed { + return errors.New("cannot set infra-image without an infra container") + } + createOptions.InfraImage = "" + if cmd.Flag("share").Changed { + return errors.New("cannot set share namespaces without an infra container") + } + createOptions.Share = nil + } + response, err := registry.ContainerEngine().PodCreate(context.Background(), createOptions) if err != nil { return err diff --git a/cmd/podmanV2/pods/pod.go b/cmd/podmanV2/pods/pod.go index 3766893bb..81c0d33e1 100644 --- a/cmd/podmanV2/pods/pod.go +++ b/cmd/podmanV2/pods/pod.go @@ -1,9 +1,6 @@ package pods import ( - "strings" - "text/template" - "github.com/containers/libpod/cmd/podmanV2/registry" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" @@ -21,33 +18,6 @@ var ( } ) -var podFuncMap = template.FuncMap{ - "numCons": func(cons []*entities.ListPodContainer) int { - return len(cons) - }, - "podcids": func(cons []*entities.ListPodContainer) string { - var ctrids []string - for _, c := range cons { - ctrids = append(ctrids, c.Id[:12]) - } - return strings.Join(ctrids, ",") - }, - "podconnames": func(cons []*entities.ListPodContainer) string { - var ctrNames []string - for _, c := range cons { - ctrNames = append(ctrNames, c.Names[:12]) - } - return strings.Join(ctrNames, ",") - }, - "podconstatuses": func(cons []*entities.ListPodContainer) string { - var statuses []string - for _, c := range cons { - statuses = append(statuses, c.Status) - } - return strings.Join(statuses, ",") - }, -} - func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, diff --git a/cmd/podmanV2/pods/ps.go b/cmd/podmanV2/pods/ps.go index 9875263ac..3dac607df 100644 --- a/cmd/podmanV2/pods/ps.go +++ b/cmd/podmanV2/pods/ps.go @@ -9,9 +9,11 @@ import ( "strings" "text/tabwriter" "text/template" + "time" + + "github.com/docker/go-units" "github.com/containers/libpod/cmd/podmanV2/registry" - "github.com/containers/libpod/cmd/podmanV2/report" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -65,6 +67,7 @@ func pods(cmd *cobra.Command, args []string) error { var ( w io.Writer = os.Stdout row string + lpr []ListPodReporter ) if cmd.Flag("filter").Changed { for _, f := range strings.Split(inputFilters, ",") { @@ -88,6 +91,10 @@ func pods(cmd *cobra.Command, args []string) error { fmt.Println(string(b)) return nil } + + for _, r := range responses { + lpr = append(lpr, ListPodReporter{r}) + } headers, row := createPodPsOut() if psInput.Quiet { if noTrunc { @@ -106,15 +113,14 @@ func pods(cmd *cobra.Command, args []string) error { if !psInput.Quiet && !cmd.Flag("format").Changed { format = headers + format } - funcs := report.AppendFuncMap(podFuncMap) - tmpl, err := template.New("listPods").Funcs(funcs).Parse(format) + tmpl, err := template.New("listPods").Parse(format) if err != nil { return err } if !psInput.Quiet { w = tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) } - if err := tmpl.Execute(w, responses); err != nil { + if err := tmpl.Execute(w, lpr); err != nil { return err } if flusher, ok := w.(interface{ Flush() error }); ok { @@ -132,20 +138,19 @@ func createPodPsOut() (string, string) { row += "{{slice .Id 0 12}}" } - row += "\t{{.Name}}\t{{.Status}}\t{{humanDurationFromTime .Created}}" + row += "\t{{.Name}}\t{{.Status}}\t{{.Created}}" - //rowFormat string = "{{slice .Id 0 12}}\t{{.Name}}\t{{.Status}}\t{{humanDurationFromTime .Created}}" if psInput.CtrIds { headers += "\tIDS" - row += "\t{{podcids .Containers}}" + row += "\t{{.ContainerIds}}" } if psInput.CtrNames { headers += "\tNAMES" - row += "\t{{podconnames .Containers}}" + row += "\t{{.ContainerNames}}" } if psInput.CtrStatus { headers += "\tSTATUS" - row += "\t{{podconstatuses .Containers}}" + row += "\t{{.ContainerStatuses}}" } if psInput.Namespace { headers += "\tCGROUP\tNAMESPACES" @@ -153,7 +158,7 @@ func createPodPsOut() (string, string) { } if !psInput.CtrStatus && !psInput.CtrNames && !psInput.CtrIds { headers += "\t# OF CONTAINERS" - row += "\t{{numCons .Containers}}" + row += "\t{{.NumberOfContainers}}" } headers += "\tINFRA ID\n" @@ -164,3 +169,61 @@ func createPodPsOut() (string, string) { } return headers, row } + +// ListPodReporter is a struct for pod ps output +type ListPodReporter struct { + *entities.ListPodsReport +} + +// Created returns a human readable created time/date +func (l ListPodReporter) Created() string { + return units.HumanDuration(time.Since(l.ListPodsReport.Created)) + " ago" +} + +// NumberofContainers returns an int representation for +// the number of containers belonging to the pod +func (l ListPodReporter) NumberOfContainers() int { + return len(l.Containers) +} + +// Added for backwards compatibility with podmanv1 +func (l ListPodReporter) InfraID() string { + return l.InfraId() +} + +// InfraId returns the infra container id for the pod +// depending on trunc +func (l ListPodReporter) InfraId() string { + if noTrunc { + return l.ListPodsReport.InfraId + } + return l.ListPodsReport.InfraId[0:12] +} + +func (l ListPodReporter) ContainerIds() string { + var ctrids []string + for _, c := range l.Containers { + id := c.Id + if !noTrunc { + id = id[0:12] + } + ctrids = append(ctrids, id) + } + return strings.Join(ctrids, ",") +} + +func (l ListPodReporter) ContainerNames() string { + var ctrNames []string + for _, c := range l.Containers { + ctrNames = append(ctrNames, c.Names) + } + return strings.Join(ctrNames, ",") +} + +func (l ListPodReporter) ContainerStatuses() string { + var statuses []string + for _, c := range l.Containers { + statuses = append(statuses, c.Status) + } + return strings.Join(statuses, ",") +} diff --git a/cmd/podmanV2/registry/config.go b/cmd/podmanV2/registry/config.go new file mode 100644 index 000000000..e68009a50 --- /dev/null +++ b/cmd/podmanV2/registry/config.go @@ -0,0 +1,59 @@ +package registry + +import ( + "os" + "runtime" + "strings" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/sirupsen/logrus" +) + +const ( + RootRequired = "RootRequired" +) + +var ( + PodmanOptions entities.PodmanConfig +) + +// NewPodmanConfig creates a PodmanConfig from the environment +func NewPodmanConfig() entities.PodmanConfig { + if err := libpod.SetXdgDirs(); err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) + } + + var mode entities.EngineMode + switch runtime.GOOS { + case "darwin": + fallthrough + case "windows": + mode = entities.TunnelMode + case "linux": + mode = entities.ABIMode + default: + logrus.Errorf("%s is not a supported OS", runtime.GOOS) + os.Exit(1) + } + + // cobra.Execute() may not be called yet, so we peek at os.Args. + for _, v := range os.Args { + // Prefix checking works because of how default EngineMode's + // have been defined. + if strings.HasPrefix(v, "--remote=") { + mode = entities.TunnelMode + } + } + + // FIXME: for rootless, where to get the path + // TODO: + cfg, err := config.NewConfig("") + if err != nil { + logrus.Error("Failed to obtain podman configuration") + os.Exit(1) + } + return entities.PodmanConfig{Config: cfg, EngineMode: mode} +} diff --git a/cmd/podmanV2/registry/registry.go b/cmd/podmanV2/registry/registry.go index 8ff44041f..5ef6a10d8 100644 --- a/cmd/podmanV2/registry/registry.go +++ b/cmd/podmanV2/registry/registry.go @@ -9,7 +9,11 @@ import ( "github.com/spf13/cobra" ) -type CobraFuncs func(cmd *cobra.Command, args []string) error +// DefaultAPIAddress is the default address of the REST socket +const DefaultAPIAddress = "unix:/run/podman/podman.sock" + +// DefaultVarlinkAddress is the default address of the varlink socket +const DefaultVarlinkAddress = "unix:/run/podman/io.podman" type CliCommand struct { Mode []entities.EngineMode @@ -25,8 +29,9 @@ var ( exitCode = ExecErrorCodeGeneric imageEngine entities.ImageEngine - Commands []CliCommand - EngineOptions entities.EngineOptions + // Commands holds the cobra.Commands to present to the user, including + // parent if not a child of "root" + Commands []CliCommand ) func SetExitCode(code int) { @@ -79,8 +84,8 @@ func ImageEngine() entities.ImageEngine { // NewImageEngine is a wrapper for building an ImageEngine to be used for PreRunE functions func NewImageEngine(cmd *cobra.Command, args []string) (entities.ImageEngine, error) { if imageEngine == nil { - EngineOptions.FlagSet = cmd.Flags() - engine, err := infra.NewImageEngine(EngineOptions) + PodmanOptions.FlagSet = cmd.Flags() + engine, err := infra.NewImageEngine(PodmanOptions) if err != nil { return nil, err } @@ -96,8 +101,8 @@ func ContainerEngine() entities.ContainerEngine { // NewContainerEngine is a wrapper for building an ContainerEngine to be used for PreRunE functions func NewContainerEngine(cmd *cobra.Command, args []string) (entities.ContainerEngine, error) { if containerEngine == nil { - EngineOptions.FlagSet = cmd.Flags() - engine, err := infra.NewContainerEngine(EngineOptions) + PodmanOptions.FlagSet = cmd.Flags() + engine, err := infra.NewContainerEngine(PodmanOptions) if err != nil { return nil, err } @@ -121,24 +126,17 @@ func IdOrLatestArgs(cmd *cobra.Command, args []string) error { return nil } -type podmanContextKey string - -var podmanFactsKey = podmanContextKey("engineOptions") - -func NewOptions(ctx context.Context, facts *entities.EngineOptions) context.Context { - return context.WithValue(ctx, podmanFactsKey, facts) -} - -func Options(cmd *cobra.Command) (*entities.EngineOptions, error) { - if f, ok := cmd.Context().Value(podmanFactsKey).(*entities.EngineOptions); ok { - return f, errors.New("Command Context ") - } - return nil, nil -} - func GetContext() context.Context { if cliCtx == nil { - cliCtx = context.TODO() + cliCtx = context.Background() } return cliCtx } + +type ContextOptionsKey string + +const PodmanOptionsKey ContextOptionsKey = "PodmanOptions" + +func GetContextWithOptions() context.Context { + return context.WithValue(GetContext(), PodmanOptionsKey, PodmanOptions) +} diff --git a/cmd/podmanV2/registry/remote.go b/cmd/podmanV2/registry/remote.go index 32a231ac4..5378701e7 100644 --- a/cmd/podmanV2/registry/remote.go +++ b/cmd/podmanV2/registry/remote.go @@ -5,5 +5,5 @@ import ( ) func IsRemote() bool { - return EngineOptions.EngineMode == entities.TunnelMode + return PodmanOptions.EngineMode == entities.TunnelMode } diff --git a/cmd/podmanV2/report/templates.go b/cmd/podmanV2/report/templates.go deleted file mode 100644 index e46048e97..000000000 --- a/cmd/podmanV2/report/templates.go +++ /dev/null @@ -1,73 +0,0 @@ -package report - -import ( - "strings" - "text/template" - "time" - "unicode" - - "github.com/docker/go-units" -) - -var defaultFuncMap = template.FuncMap{ - "ellipsis": func(s string, length int) string { - if len(s) > length { - return s[:length-3] + "..." - } - return s - }, - "humanDuration": func(t int64) string { - return units.HumanDuration(time.Since(time.Unix(t, 0))) + " ago" - }, - "humanDurationFromTime": func(t time.Time) string { - return units.HumanDuration(time.Since(t)) + " ago" - }, - "humanSize": func(sz int64) string { - s := units.HumanSizeWithPrecision(float64(sz), 3) - i := strings.LastIndexFunc(s, unicode.IsNumber) - return s[:i+1] + " " + s[i+1:] - }, - "join": strings.Join, - "lower": strings.ToLower, - "rfc3339": func(t int64) string { - return time.Unix(t, 0).Format(time.RFC3339) - }, - "replace": strings.Replace, - "split": strings.Split, - "title": strings.Title, - "upper": strings.ToUpper, - // TODO: Remove after Go 1.14 port - "slice": func(s string, i, j int) string { - if i > j || len(s) < i { - return s - } - if len(s) < j { - return s[i:] - } - return s[i:j] - }, -} - -func ReportHeader(columns ...string) []byte { - hdr := make([]string, len(columns)) - for i, h := range columns { - hdr[i] = strings.ToUpper(h) - } - return []byte(strings.Join(hdr, "\t") + "\n") -} - -func AppendFuncMap(funcMap template.FuncMap) template.FuncMap { - merged := PodmanTemplateFuncs() - for k, v := range funcMap { - merged[k] = v - } - return merged -} - -func PodmanTemplateFuncs() template.FuncMap { - merged := make(template.FuncMap) - for k, v := range defaultFuncMap { - merged[k] = v - } - return merged -} diff --git a/cmd/podmanV2/root.go b/cmd/podmanV2/root.go index 6fc12f57e..0639257ea 100644 --- a/cmd/podmanV2/root.go +++ b/cmd/podmanV2/root.go @@ -1,29 +1,37 @@ package main import ( + "context" "fmt" "log/syslog" "os" "path" + "runtime/pprof" "github.com/containers/libpod/cmd/podmanV2/registry" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/tracing" "github.com/containers/libpod/version" + "github.com/opentracing/opentracing-go" + "github.com/pkg/errors" "github.com/sirupsen/logrus" logrusSyslog "github.com/sirupsen/logrus/hooks/syslog" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( rootCmd = &cobra.Command{ - Use: path.Base(os.Args[0]), - Long: "Manage pods, containers and images", - SilenceUsage: true, - SilenceErrors: true, - TraverseChildren: true, - PersistentPreRunE: preRunE, - RunE: registry.SubCommandExists, - Version: version.Version, + Use: path.Base(os.Args[0]), + Long: "Manage pods, containers and images", + SilenceUsage: true, + SilenceErrors: true, + TraverseChildren: true, + PersistentPreRunE: preRunE, + RunE: registry.SubCommandExists, + PersistentPostRunE: postRunE, + Version: version.Version, } logLevels = entities.NewStringSet("debug", "info", "warn", "error", "fatal", "panic") @@ -32,30 +40,73 @@ var ( ) func init() { - // Override default --help information of `--version` global flag} - var dummyVersion bool - // TODO had to disable shorthand -v for version due to -v rm with volume - rootCmd.PersistentFlags().BoolVar(&dummyVersion, "version", false, "Version of Podman") - rootCmd.PersistentFlags().StringVarP(®istry.EngineOptions.Uri, "remote", "r", "", "URL to access Podman service") - rootCmd.PersistentFlags().StringSliceVar(®istry.EngineOptions.Identities, "identity", []string{}, "path to SSH identity file") - rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "error", fmt.Sprintf("Log messages above specified level (%s)", logLevels.String())) - rootCmd.PersistentFlags().BoolVar(&useSyslog, "syslog", false, "Output logging information to syslog as well as the console (default false)") - cobra.OnInitialize( - logging, + rootlessHook, + loggingHook, syslogHook, ) + + rootFlags(registry.PodmanOptions, rootCmd.PersistentFlags()) +} + +func Execute() { + if err := rootCmd.ExecuteContext(registry.GetContextWithOptions()); err != nil { + logrus.Error(err) + } else if registry.GetExitCode() == registry.ExecErrorCodeGeneric { + // The exitCode modified from registry.ExecErrorCodeGeneric, + // indicates an application + // running inside of a container failed, as opposed to the + // podman command failed. Must exit with that exit code + // otherwise command exited correctly. + registry.SetExitCode(0) + } + os.Exit(registry.GetExitCode()) } -func preRunE(cmd *cobra.Command, args []string) error { +func preRunE(cmd *cobra.Command, _ []string) error { + // Update PodmanOptions now that we "know" more + // TODO: pass in path overriding configuration file + registry.PodmanOptions = registry.NewPodmanConfig() + cmd.SetHelpTemplate(registry.HelpTemplate()) cmd.SetUsageTemplate(registry.UsageTemplate()) + + if cmd.Flag("cpu-profile").Changed { + f, err := os.Create(registry.PodmanOptions.CpuProfile) + if err != nil { + return errors.Wrapf(err, "unable to create cpu profiling file %s", + registry.PodmanOptions.CpuProfile) + } + if err := pprof.StartCPUProfile(f); err != nil { + return err + } + } + + if cmd.Flag("trace").Changed { + tracer, closer := tracing.Init("podman") + opentracing.SetGlobalTracer(tracer) + registry.PodmanOptions.SpanCloser = closer + + registry.PodmanOptions.Span = tracer.StartSpan("before-context") + registry.PodmanOptions.SpanCtx = opentracing.ContextWithSpan(context.Background(), registry.PodmanOptions.Span) + } return nil } -func logging() { +func postRunE(cmd *cobra.Command, args []string) error { + if cmd.Flag("cpu-profile").Changed { + pprof.StopCPUProfile() + } + if cmd.Flag("trace").Changed { + registry.PodmanOptions.Span.Finish() + registry.PodmanOptions.SpanCloser.Close() + } + return nil +} + +func loggingHook() { if !logLevels.Contains(logLevel) { - fmt.Fprintf(os.Stderr, "Log Level \"%s\" is not supported, choose from: %s\n", logLevel, logLevels.String()) + logrus.Errorf("Log Level \"%s\" is not supported, choose from: %s", logLevel, logLevels.String()) os.Exit(1) } @@ -83,17 +134,68 @@ func syslogHook() { } } -func Execute() { - o := registry.NewOptions(rootCmd.Context(), ®istry.EngineOptions) - if err := rootCmd.ExecuteContext(o); err != nil { - fmt.Fprintln(os.Stderr, "Error:", err.Error()) - } else if registry.GetExitCode() == registry.ExecErrorCodeGeneric { - // The exitCode modified from registry.ExecErrorCodeGeneric, - // indicates an application - // running inside of a container failed, as opposed to the - // podman command failed. Must exit with that exit code - // otherwise command exited correctly. - registry.SetExitCode(0) +func rootlessHook() { + if rootless.IsRootless() { + logrus.Error("rootless mode is currently not supported. Support will return ASAP.") } - os.Exit(registry.GetExitCode()) + // ce, err := registry.NewContainerEngine(rootCmd, []string{}) + // if err != nil { + // logrus.WithError(err).Fatal("failed to obtain container engine") + // } + // ce.SetupRootLess(rootCmd) +} + +func rootFlags(opts entities.PodmanConfig, flags *pflag.FlagSet) { + // V2 flags + flags.StringVarP(&opts.Uri, "remote", "r", "", "URL to access Podman service") + flags.StringSliceVar(&opts.Identities, "identity", []string{}, "path to SSH identity file") + + // Override default --help information of `--version` global flag + // TODO: restore -v option for version without breaking -v for volumes + var dummyVersion bool + flags.BoolVar(&dummyVersion, "version", false, "Version of Podman") + + cfg := opts.Config + flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, opts.CGroupUsage) + flags.StringVar(&opts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results") + flags.StringVar(&opts.ConmonPath, "conmon", "", "Path of the conmon binary") + flags.StringVar(&cfg.Engine.NetworkCmdPath, "network-cmd-path", cfg.Engine.NetworkCmdPath, "Path to the command for configuring the network") + flags.StringVar(&cfg.Network.NetworkConfigDir, "cni-config-dir", cfg.Network.NetworkConfigDir, "Path of the configuration directory for CNI networks") + flags.StringVar(&cfg.Containers.DefaultMountsFile, "default-mounts-file", cfg.Containers.DefaultMountsFile, "Path to default mounts file") + flags.StringVar(&cfg.Engine.EventsLogger, "events-backend", cfg.Engine.EventsLogger, `Events backend to use ("file"|"journald"|"none")`) + flags.StringSliceVar(&cfg.Engine.HooksDir, "hooks-dir", cfg.Engine.HooksDir, "Set the OCI hooks directory path (may be set multiple times)") + flags.IntVar(&opts.MaxWorks, "max-workers", 0, "The maximum number of workers for parallel operations") + flags.StringVar(&cfg.Engine.Namespace, "namespace", cfg.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system") + flags.StringVar(&cfg.Engine.StaticDir, "root", "", "Path to the root directory in which data, including images, is stored") + flags.StringVar(&opts.Runroot, "runroot", "", "Path to the 'run directory' where all state information is stored") + flags.StringVar(&opts.RuntimePath, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") + // -s is deprecated due to conflict with -s on subcommands + flags.StringVar(&opts.StorageDriver, "storage-driver", "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") + flags.StringArrayVar(&opts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") + + flags.StringVar(&opts.Engine.TmpDir, "tmpdir", "", "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n") + flags.BoolVar(&opts.Trace, "trace", false, "Enable opentracing output (default false)") + + // Override default --help information of `--help` global flag + var dummyHelp bool + flags.BoolVar(&dummyHelp, "help", false, "Help for podman") + flags.StringVar(&logLevel, "log-level", logLevel, fmt.Sprintf("Log messages above specified level (%s)", logLevels.String())) + + // Hide these flags for both ABI and Tunneling + for _, f := range []string{ + "cpu-profile", + "default-mounts-file", + "max-workers", + "trace", + } { + if err := flags.MarkHidden(f); err != nil { + logrus.Warnf("unable to mark %s flag as hidden", f) + } + } + + // Only create these flags for ABI connections + if !registry.IsRemote() { + flags.BoolVar(&useSyslog, "syslog", false, "Output logging information to syslog as well as the console (default false)") + } + } diff --git a/cmd/podmanV2/system/events.go b/cmd/podmanV2/system/events.go new file mode 100644 index 000000000..9fd27e2c1 --- /dev/null +++ b/cmd/podmanV2/system/events.go @@ -0,0 +1,104 @@ +package system + +import ( + "bufio" + "context" + "html/template" + "os" + + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + eventsDescription = "Monitor podman events" + eventsCommand = &cobra.Command{ + Use: "events", + Args: cobra.NoArgs, + Short: "Show podman events", + Long: eventsDescription, + PersistentPreRunE: preRunE, + RunE: eventsCmd, + Example: `podman events + podman events --filter event=create + podman events --since 1h30s`, + } +) + +var ( + eventOptions entities.EventsOptions + eventFormat string +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: eventsCommand, + }) + flags := eventsCommand.Flags() + flags.StringArrayVar(&eventOptions.Filter, "filter", []string{}, "filter output") + flags.StringVar(&eventFormat, "format", "", "format the output using a Go template") + flags.BoolVar(&eventOptions.Stream, "stream", true, "stream new events; for testing only") + flags.StringVar(&eventOptions.Since, "since", "", "show all events created since timestamp") + flags.StringVar(&eventOptions.Until, "until", "", "show all events until timestamp") + _ = flags.MarkHidden("stream") +} + +func eventsCmd(cmd *cobra.Command, args []string) error { + var ( + err error + eventsError error + tmpl *template.Template + ) + if eventFormat != formats.JSONString { + tmpl, err = template.New("events").Parse(eventFormat) + if err != nil { + return err + } + } + if len(eventOptions.Since) > 0 || len(eventOptions.Until) > 0 { + eventOptions.FromStart = true + } + eventChannel := make(chan *events.Event) + eventOptions.EventChan = eventChannel + + go func() { + eventsError = registry.ContainerEngine().Events(context.Background(), eventOptions) + }() + if eventsError != nil { + return eventsError + } + + w := bufio.NewWriter(os.Stdout) + for event := range eventChannel { + switch { + case eventFormat == formats.JSONString: + jsonStr, err := event.ToJSONString() + if err != nil { + return errors.Wrapf(err, "unable to format json") + } + if _, err := w.Write([]byte(jsonStr)); err != nil { + return err + } + case len(eventFormat) > 0: + if err := tmpl.Execute(w, event); err != nil { + return err + } + default: + if _, err := w.Write([]byte(event.ToHumanReadable())); err != nil { + return err + } + } + if _, err := w.Write([]byte("\n")); err != nil { + return err + } + if err := w.Flush(); err != nil { + return err + } + } + return nil +} diff --git a/cmd/podmanV2/system/service.go b/cmd/podmanV2/system/service.go new file mode 100644 index 000000000..5573f7039 --- /dev/null +++ b/cmd/podmanV2/system/service.go @@ -0,0 +1,124 @@ +package system + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/systemd" + "github.com/containers/libpod/pkg/util" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + srvDescription = `Run an API service + +Enable a listening service for API access to Podman commands. +` + + srvCmd = &cobra.Command{ + Use: "service [flags] [URI]", + Args: cobra.MaximumNArgs(1), + Short: "Run API service", + Long: srvDescription, + RunE: service, + Example: `podman system service --time=0 unix:///tmp/podman.sock + podman system service --varlink --time=0 unix:///tmp/podman.sock`, + } + + srvArgs = struct { + Timeout int64 + Varlink bool + }{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: srvCmd, + Parent: systemCmd, + }) + + flags := srvCmd.Flags() + flags.Int64VarP(&srvArgs.Timeout, "time", "t", 5, "Time until the service session expires in seconds. Use 0 to disable the timeout") + flags.Int64Var(&srvArgs.Timeout, "timeout", 5, "Time until the service session expires in seconds. Use 0 to disable the timeout") + flags.BoolVar(&srvArgs.Varlink, "varlink", false, "Use legacy varlink service instead of REST") + + _ = flags.MarkDeprecated("varlink", "valink API is deprecated.") +} + +func service(cmd *cobra.Command, args []string) error { + apiURI, err := resolveApiURI(args) + if err != nil { + return err + } + logrus.Infof("using API endpoint: \"%s\"", apiURI) + + opts := entities.ServiceOptions{ + URI: apiURI, + Timeout: time.Duration(srvArgs.Timeout) * time.Second, + Command: cmd, + } + + if srvArgs.Varlink { + return registry.ContainerEngine().VarlinkService(registry.GetContext(), opts) + } + + logrus.Warn("This function is EXPERIMENTAL") + fmt.Fprintf(os.Stderr, "This function is EXPERIMENTAL.\n") + return registry.ContainerEngine().RestService(registry.GetContext(), opts) +} + +func resolveApiURI(_url []string) (string, error) { + + // When determining _*THE*_ listening endpoint -- + // 1) User input wins always + // 2) systemd socket activation + // 3) rootless honors XDG_RUNTIME_DIR + // 4) if varlink -- adapter.DefaultVarlinkAddress + // 5) lastly adapter.DefaultAPIAddress + + if _url == nil { + if v, found := os.LookupEnv("PODMAN_SOCKET"); found { + _url = []string{v} + } + } + + switch { + case len(_url) > 0: + return _url[0], nil + case systemd.SocketActivated(): + logrus.Info("using systemd socket activation to determine API endpoint") + return "", nil + case rootless.IsRootless(): + xdg, err := util.GetRuntimeDir() + if err != nil { + return "", err + } + + socketName := "podman.sock" + if srvArgs.Varlink { + socketName = "io.podman" + } + socketDir := filepath.Join(xdg, "podman", socketName) + if _, err := os.Stat(filepath.Dir(socketDir)); err != nil { + if os.IsNotExist(err) { + if err := os.Mkdir(filepath.Dir(socketDir), 0755); err != nil { + return "", err + } + } else { + return "", err + } + } + return "unix:" + socketDir, nil + case srvArgs.Varlink: + return registry.DefaultVarlinkAddress, nil + default: + return registry.DefaultAPIAddress, nil + } +} diff --git a/cmd/podmanV2/system/system.go b/cmd/podmanV2/system/system.go index 4e805c7bd..b44632187 100644 --- a/cmd/podmanV2/system/system.go +++ b/cmd/podmanV2/system/system.go @@ -8,7 +8,7 @@ import ( var ( // Command: podman _system_ - cmd = &cobra.Command{ + systemCmd = &cobra.Command{ Use: "system", Short: "Manage podman", Long: "Manage podman", @@ -21,10 +21,10 @@ var ( func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: cmd, + Command: systemCmd, }) - cmd.SetHelpTemplate(registry.HelpTemplate()) - cmd.SetUsageTemplate(registry.UsageTemplate()) + systemCmd.SetHelpTemplate(registry.HelpTemplate()) + systemCmd.SetUsageTemplate(registry.UsageTemplate()) } func preRunE(cmd *cobra.Command, args []string) error { diff --git a/cmd/podmanV2/system/varlink.go b/cmd/podmanV2/system/varlink.go new file mode 100644 index 000000000..da9af6fe4 --- /dev/null +++ b/cmd/podmanV2/system/varlink.go @@ -0,0 +1,56 @@ +package system + +import ( + "time" + + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + varlinkDescription = `Run varlink interface. Podman varlink listens on the specified unix domain socket for incoming connects. + + Tools speaking varlink protocol can remotely manage pods, containers and images. +` + varlinkCmd = &cobra.Command{ + Use: "varlink [flags] [URI]", + Args: cobra.MinimumNArgs(1), + Short: "Run varlink interface", + Long: varlinkDescription, + PreRunE: preRunE, + RunE: varlinkE, + Example: `podman varlink unix:/run/podman/io.podman + podman varlink --timeout 5000 unix:/run/podman/io.podman`, + } + varlinkArgs = struct { + Timeout int64 + }{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: varlinkCmd, + }) + varlinkCmd.SetHelpTemplate(registry.HelpTemplate()) + varlinkCmd.SetUsageTemplate(registry.UsageTemplate()) + + flags := varlinkCmd.Flags() + flags.Int64VarP(&varlinkArgs.Timeout, "time", "t", 1000, "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout") + flags.Int64Var(&varlinkArgs.Timeout, "timeout", 1000, "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout") + +} + +func varlinkE(cmd *cobra.Command, args []string) error { + uri := registry.DefaultVarlinkAddress + if len(args) > 0 { + uri = args[0] + } + opts := entities.ServiceOptions{ + URI: uri, + Timeout: time.Duration(varlinkArgs.Timeout) * time.Second, + Command: cmd, + } + return registry.ContainerEngine().VarlinkService(registry.GetContext(), opts) +} diff --git a/cmd/podmanV2/system/version.go b/cmd/podmanV2/system/version.go index e8002056b..8d6e8b7a8 100644 --- a/cmd/podmanV2/system/version.go +++ b/cmd/podmanV2/system/version.go @@ -24,7 +24,7 @@ var ( RunE: version, PersistentPreRunE: preRunE, } - format string + versionFormat string ) type versionStruct struct { @@ -38,7 +38,7 @@ func init() { Command: versionCommand, }) flags := versionCommand.Flags() - flags.StringVarP(&format, "format", "f", "", "Change the output format to JSON or a Go template") + flags.StringVarP(&versionFormat, "format", "f", "", "Change the output format to JSON or a Go template") } func version(cmd *cobra.Command, args []string) error { @@ -62,7 +62,7 @@ func version(cmd *cobra.Command, args []string) error { v.Server = v.Client //} - versionOutputFormat := format + versionOutputFormat := versionFormat if versionOutputFormat != "" { if strings.Join(strings.Fields(versionOutputFormat), "") == "{{json.}}" { versionOutputFormat = formats.JSONString diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 9641a52e6..afc50f854 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -48,7 +48,7 @@ Epoch: 99 %else Epoch: 0 %endif -Version: 1.8.3 +Version: 2.0.0 Release: #COMMITDATE#.git%{shortcommit0}%{?dist} Summary: Manage Pods, Containers and Container Images License: ASL 2.0 @@ -9,7 +9,7 @@ require ( github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect github.com/containernetworking/cni v0.7.2-0.20200304161608-4fae32b84921 github.com/containernetworking/plugins v0.8.5 - github.com/containers/buildah v1.14.7 + github.com/containers/buildah v1.14.8 github.com/containers/common v0.8.1 github.com/containers/conmon v2.0.14+incompatible github.com/containers/image/v5 v5.4.3 @@ -55,7 +55,6 @@ require ( github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 github.com/uber/jaeger-client-go v2.22.1+incompatible github.com/uber/jaeger-lib v2.2.0+incompatible // indirect - github.com/urfave/cli v1.22.1 // indirect github.com/varlink/go v0.0.0-20190502142041-0f1d566d194b github.com/vishvananda/netlink v1.1.0 go.etcd.io/bbolt v1.3.4 @@ -1,5 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg= github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= @@ -7,7 +8,6 @@ github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/zstd v1.4.0/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -42,6 +42,7 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b h1:T4nWG1TXIxeor8mAu5bFguPJgSIGhZqv/f0z55KCrJM= github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= @@ -62,46 +63,20 @@ github.com/containernetworking/cni v0.7.2-0.20200304161608-4fae32b84921 h1:eUMd8 github.com/containernetworking/cni v0.7.2-0.20200304161608-4fae32b84921/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/plugins v0.8.5 h1:pCvEMrFf7yzJI8+/D/7jkvE96KD52b7/Eu+jpahihy8= github.com/containernetworking/plugins v0.8.5/go.mod h1:UZ2539umj8djuRQmBxuazHeJbYrLV8BSBejkk+she6o= -github.com/containers/buildah v1.14.5 h1:0Q+UgkIG4gAgAEZCu+0Syu/fSKsM1EsrctwV8G299jo= -github.com/containers/buildah v1.14.5/go.mod h1:2rfICEnpTtrMhWF6FZLnAL1Bh7SNmjhiKrjuIo0ZuN8= -github.com/containers/buildah v1.14.6-0.20200402210551-e9a6703edee2 h1:9WchHVTk/FuAHHMuClpAZqk8dxOsPi6i6Yw5ocLbZxk= -github.com/containers/buildah v1.14.6-0.20200402210551-e9a6703edee2/go.mod h1:auylD7PH2uPpE+a/FmgZmP/uC30pIbR3cNYMPSNHxXg= -github.com/containers/buildah v1.14.7 h1:q3uSCb+HWZLF5oLPYJ3wcI8b25/Gy2nqrMuvtjuSiUA= -github.com/containers/buildah v1.14.7/go.mod h1:ytEjHJQnRXC1ygXMyc0FqYkjcoCydqBQkOdxbH563QU= -github.com/containers/common v0.6.1 h1:z9VeVXYeOnNV99uNLp7zoE5KO1n0hqz1mdm5a6AiIrA= -github.com/containers/common v0.6.1/go.mod h1:m62kenckrWi5rZx32kaLje2Og0hpf6NsaTBn6+b+Oys= -github.com/containers/common v0.7.0/go.mod h1:UmhIdvSkhTR0hWR01AnuZGNufm80+A0s8isb05eTmz0= -github.com/containers/common v0.8.0 h1:C+wjkcmR4gooeKCXZpyjsHSFARm5AZRegflGz0x0MMw= -github.com/containers/common v0.8.0/go.mod h1:QJTx9+SvhHKP6e+p7Nxqc8oNnS5rSf0KVhxudIbDslU= +github.com/containers/buildah v1.14.8 h1:JbMI0QSOmyZ30Mr2633uCXAj+Fajgh/EFS9xX/Y14oQ= +github.com/containers/buildah v1.14.8/go.mod h1:ytEjHJQnRXC1ygXMyc0FqYkjcoCydqBQkOdxbH563QU= github.com/containers/common v0.8.1 h1:1IUwAtZ4mC7GYRr4AC23cHf2oXCuoLzTUoSzIkSgnYw= github.com/containers/common v0.8.1/go.mod h1:VxDJbaA1k6N1TNv9Rt6bQEF4hyKVHNfOfGA5L91ADEs= -github.com/containers/common v1.0.0 h1:sZB48LzGP4bP1CmrkQIFUzdUVBysqRv3kWVk4+qbaVA= -github.com/containers/common v1.0.0/go.mod h1:m62kenckrWi5rZx32kaLje2Og0hpf6NsaTBn6+b+Oys= github.com/containers/conmon v2.0.14+incompatible h1:knU1O1QxXy5YxtjMQVKEyCajROaehizK9FHaICl+P5Y= github.com/containers/conmon v2.0.14+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= -github.com/containers/image/v5 v5.2.1/go.mod h1:TfhmLwH+v1/HBVPIWH7diLs8XwcOkP3c7t7JFgqaUEc= -github.com/containers/image/v5 v5.3.1 h1:AL0pR0d1ho3kLUAuBr+wnFlXuD3ChzKVljk0M8JBJHQ= -github.com/containers/image/v5 v5.3.1/go.mod h1:JnCfhbTIL9IxPPZm1JoQwiE0S9KET46M4OZySJsLylk= -github.com/containers/image/v5 v5.4.2 h1:o4q0PyOXbe5+I+qrPMU/SYDxsmCgY9GnttXR7jPk+kk= -github.com/containers/image/v5 v5.4.2/go.mod h1:UsU+3n/uhw0ddok1V5BF6gbrLeAA15snhskzEXDzIyk= github.com/containers/image/v5 v5.4.3 h1:zn2HR7uu4hpvT5QQHgjqonOzKDuM1I1UHUEmzZT5sbs= github.com/containers/image/v5 v5.4.3/go.mod h1:pN0tvp3YbDd7BWavK2aE0mvJUqVd2HmhPjekyWSFm0U= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= -github.com/containers/ocicrypt v0.0.0-20190930154801-b87a4a69c741 h1:8tQkOcednLJtUcZgK7sPglscXtxvMOnFOa6wd09VWLM= -github.com/containers/ocicrypt v0.0.0-20190930154801-b87a4a69c741/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.0.2 h1:Q0/IPs8ohfbXNxEfyJ2pFVmvJu5BhqJUAmc6ES9NKbo= github.com/containers/ocicrypt v1.0.2/go.mod h1:nsOhbP19flrX6rE7ieGFvBlr7modwmNjsqWarIUce4M= github.com/containers/psgo v1.4.0 h1:D8B4fZCCZhYgc8hDyMPCiShOinmOB1TP1qe46sSC19k= github.com/containers/psgo v1.4.0/go.mod h1:ENXXLQ5E1At4K0EUsGogXBJi/C28gwqkONWeLPI9fJ8= -github.com/containers/storage v1.15.8/go.mod h1:zhvjIIl/fR6wt/lgqQAC+xanHQ+8gUQ0GBVeXYN81qI= -github.com/containers/storage v1.16.0/go.mod h1:nqN09JSi1/RSI1UAUwDYXPRiGSlq5FPbNkN/xb0TfG0= -github.com/containers/storage v1.16.5/go.mod h1:SdysZeLKJOvfHYysUWg9OZUC3gdZWi5b2b7NC18VpPE= -github.com/containers/storage v1.16.6 h1:G/thPW/LVRwJpQvve1V4DQXVZpxzSltC2fzc3yTEdi8= -github.com/containers/storage v1.16.6/go.mod h1:Fws4I+U+C4DmJxDbBs1z9SKk50DzN4LtA+g1b+FmkTY= -github.com/containers/storage v1.18.0/go.mod h1:gbFeFybWhlVCk3buJ0sovNKs8MzWEBTrk8/sbJw8irQ= -github.com/containers/storage v1.18.1 h1:W134oYa8ALd78yo6DKiDp6n7EWXrc+fCnYmJi6o49vo= -github.com/containers/storage v1.18.1/go.mod h1:6NtCgnUeYsRlyZyrZ5qKkXYC560GRgvA7YrKRSAYSlo= github.com/containers/storage v1.18.2 h1:4cgFbrrgr9nR9xCeOmfpyxk1MtXYZGr7XGPJfAVkGmc= github.com/containers/storage v1.18.2/go.mod h1:WTBMf+a9ZZ/LbmEVeLHH2TX4CikWbO1Bt+/m58ZHVPg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -132,17 +107,13 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/docker/distribution v0.0.0-20170817175659-5f6282db7d65/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v0.0.0-20171019062838-86f080cff091/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v0.0.0-20180522102801-da99009bbb11/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20191101170500-ac7306503d23/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f h1:Sm8iD2lifO31DwXfkGzq8VgA7rwxPjRsYmeo0K/dF9Y= github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/go-connections v0.0.0-20180212134524-7beb39f0b969/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= @@ -151,9 +122,11 @@ github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libnetwork v0.8.0-dev.2.0.20190625141545-5a177b73e316 h1:moehPjPiGUaWdwgOl92xRyFHJyaqXDHcCyW9M6nmCK4= github.com/docker/libnetwork v0.8.0-dev.2.0.20190625141545-5a177b73e316/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -168,7 +141,6 @@ github.com/fsouza/go-dockerclient v1.6.3/go.mod h1:OiSy/IhZIF+zheikZkXK7LVpGzxWc github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v0.0.0-20161207003320-04f313413ffd/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -200,6 +172,7 @@ github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09Vjb github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -226,14 +199,11 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v0.0.0-20170217192616-94e7d24fd285/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -247,12 +217,11 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -277,13 +246,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/klauspost/pgzip v1.2.2/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.3 h1:Ce2to9wvs/cuJ2b86/CKQoTYr9VHfpanYosZ0UBJqdw= github.com/klauspost/pgzip v1.2.3/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -293,22 +257,24 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mistifyio/go-zfs v2.1.1+incompatible h1:gAMO1HM9xBRONLHHYnu5iFsOJUiJdNZo6oqSENd4eW8= github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= github.com/moby/vpnkit v0.3.1-0.20200304131818-6bc1679a048d/go.mod h1:KyjUrL9cb6ZSNNAUwZfqRjhwwgJ3BJN+kXh0t43WTUQ= @@ -365,24 +331,19 @@ github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7/go.m github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/runtime-tools v0.9.0 h1:FYgwVsKRI/H9hU32MJ/4MLOzXWodKK5zsQavY8NPMkU= github.com/opencontainers/runtime-tools v0.9.0/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.3.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= -github.com/opencontainers/selinux v1.3.2/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/opencontainers/selinux v1.4.0 h1:cpiX/2wWIju/6My60T6/z9CxNG7c8xTQyEmA9fChpUo= github.com/opencontainers/selinux v1.4.0/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= -github.com/opencontainers/selinux v1.5.0 h1:giFN+hbiSqvKWPyagmNk9sABaH7VUZ/+XS7tInqDQ6c= -github.com/opencontainers/selinux v1.5.0/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/opencontainers/selinux v1.5.1 h1:jskKwSMFYqyTrHEuJgQoUlTcId0av64S6EWObrIfn5Y= github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/openshift/api v0.0.0-20200106203948-7ab22a2c8316 h1:enQG2QUGwug4fR1yM6hL0Fjzx6Km/exZY6RbSPwMu3o= github.com/openshift/api v0.0.0-20200106203948-7ab22a2c8316/go.mod h1:dv+J0b/HWai0QnMVb37/H0v36klkLBi2TNpPeWDxX10= -github.com/openshift/imagebuilder v1.1.3 h1:8TiphsD2wboU7tygtGZ5ZBfCP9FH2ZtvEAli67V2PJ4= -github.com/openshift/imagebuilder v1.1.3/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo= github.com/openshift/imagebuilder v1.1.4 h1:LUg8aTjyXMtlDx6IbtvaqofFGZ6aYqe+VIeATE735LM= github.com/openshift/imagebuilder v1.1.4/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw= github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -418,8 +379,6 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rootless-containers/rootlesskit v0.9.2 h1:avrVoGuC8xdrUEwVuxGncEc46bMixvGfjyolMI4H3/U= -github.com/rootless-containers/rootlesskit v0.9.2/go.mod h1:QNzDKFGrnpXx3z7zQRu3nvK6lo9zyaR7O+WvLy6Azu4= github.com/rootless-containers/rootlesskit v0.9.3 h1:hrkZzBZT5vEnhAso6H1jHAcc4DT8h6/hp2z4yL0xu/8= github.com/rootless-containers/rootlesskit v0.9.3/go.mod h1:fx5DhInDgnR0Upj+2cOVacKuZJYSNKV5P/bCwGa+quQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -439,18 +398,20 @@ github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs= -github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -474,19 +435,14 @@ github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMW github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.1.2-0.20200306124602-d648edd48d89/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/varlink/go v0.0.0-20190502142041-0f1d566d194b h1:hdDRrn9OP/roL8a/e/5Zu85ldrcdndu9IeBj2OEvQm0= github.com/varlink/go v0.0.0-20190502142041-0f1d566d194b/go.mod h1:YHaw8N660ESgMgLOZfLQqT1htFItynAUxMesFBho52s= github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02dE= github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g= -github.com/vbauerster/mpb/v4 v4.11.2/go.mod h1:jIuIRCltGJUnm6DCyPVkwjlLUk4nHTH+m4eD14CdFF0= -github.com/vbauerster/mpb/v4 v4.12.2 h1:TsBs1nWRYF0m8cUH13pxNhOUqY6yKcOr2PeSYxp2L3I= -github.com/vbauerster/mpb/v4 v4.12.2/go.mod h1:LVRGvMch8T4HQO3eg2pFPsACH9kO/O6fT/7vhGje3QE= github.com/vbauerster/mpb/v5 v5.0.3 h1:Ldt/azOkbThTk2loi6FrBd/3fhxGFQ24MxFAS88PoNY= github.com/vbauerster/mpb/v5 v5.0.3/go.mod h1:h3YxU5CSr8rZP4Q3xZPVB3jJLhWPou63lHEdr9ytH4Y= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= @@ -501,14 +457,11 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b/go.mod h1:N2 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xeipuuv/gojsonschema v0.0.0-20190816131739-be0936907f66 h1:F6RPtD6im1kY4bmLByRlOLOZwsPP7mw7cxR1v2CotL0= -github.com/xeipuuv/gojsonschema v0.0.0-20190816131739-be0936907f66/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= @@ -523,8 +476,6 @@ golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -591,12 +542,10 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -681,7 +630,6 @@ k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEj k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.17.4 h1:UzM+38cPUJnzqSQ+E1PY4YxMHIzQyCg29LOoGfo79Zw= k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= -k8s.io/client-go v0.0.0-20170217214107-bcde30fb7eae/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g= k8s.io/client-go v0.0.0-20190620085101-78d2af792bab/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k= k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= diff --git a/hack/golangci-lint.sh b/hack/golangci-lint.sh new file mode 100755 index 000000000..385b21f39 --- /dev/null +++ b/hack/golangci-lint.sh @@ -0,0 +1,15 @@ +#!/bin/bash -e + +# Need to run linter twice to cover all the build tags code paths + +declare -A BUILD_TAGS +BUILD_TAGS[default]="apparmor,seccomp,selinux" +BUILD_TAGS[abi]="${BUILD_TAGS[default]},ABISupport,varlink,!remoteclient" +BUILD_TAGS[tunnel]="${BUILD_TAGS[default]},!ABISupport,!varlink,remoteclient" + +[[ $1 == run ]] && shift + +for i in tunnel abi; do + echo Build Tags: ${BUILD_TAGS[$i]} + golangci-lint run --build-tags=${BUILD_TAGS[$i]} "$@" +done diff --git a/libpod/container_api.go b/libpod/container_api.go index 55c79fa74..b31079b26 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -6,10 +6,13 @@ import ( "io/ioutil" "net" "os" + "strings" + "sync" "time" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/libpod/logs" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -244,15 +247,28 @@ func (c *Container) Attach(streams *define.AttachStreams, keys string, resize <- // forwarded to the client. // This function returns when the attach finishes. It does not hold the lock for // the duration of its runtime, only using it at the beginning to verify state. -func (c *Container) HTTPAttach(httpCon net.Conn, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool) error { +// The streamLogs parameter indicates that all the container's logs until present +// will be streamed at the beginning of the attach. +// The streamAttach parameter indicates that the attach itself will be streamed +// over the socket; if this is not set, but streamLogs is, only the logs will be +// sent. +// At least one of streamAttach and streamLogs must be set. +func (c *Container) HTTPAttach(httpCon net.Conn, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool, streamAttach, streamLogs bool) (deferredErr error) { + isTerminal := false + if c.config.Spec.Process != nil { + isTerminal = c.config.Spec.Process.Terminal + } + // Ensure our contract of writing errors to and closing the HTTP conn is + // honored. + defer func() { + hijackWriteErrorAndClose(deferredErr, c.ID(), isTerminal, httpCon, httpBuf) + }() + if !c.batched { c.lock.Lock() if err := c.syncContainer(); err != nil { c.lock.Unlock() - // Write any errors to the HTTP buffer before we close. - hijackWriteErrorAndClose(err, c.ID(), httpCon, httpBuf) - return err } // We are NOT holding the lock for the duration of the function. @@ -260,16 +276,80 @@ func (c *Container) HTTPAttach(httpCon net.Conn, httpBuf *bufio.ReadWriter, stre } if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { - toReturn := errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers") - - // Write any errors to the HTTP buffer before we close. - hijackWriteErrorAndClose(toReturn, c.ID(), httpCon, httpBuf) + return errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers") + } - return toReturn + if !streamAttach && !streamLogs { + return errors.Wrapf(define.ErrInvalidArg, "must specify at least one of stream or logs") } logrus.Infof("Performing HTTP Hijack attach to container %s", c.ID()) + if streamLogs { + // Get all logs for the container + logChan := make(chan *logs.LogLine) + logOpts := new(logs.LogOptions) + logOpts.Tail = -1 + logOpts.WaitGroup = new(sync.WaitGroup) + errChan := make(chan error) + go func() { + var err error + // In non-terminal mode we need to prepend with the + // stream header. + logrus.Debugf("Writing logs for container %s to HTTP attach", c.ID()) + for logLine := range logChan { + if !isTerminal { + device := logLine.Device + var header []byte + headerLen := uint32(len(logLine.Msg)) + + switch strings.ToLower(device) { + case "stdin": + header = makeHTTPAttachHeader(0, headerLen) + case "stdout": + header = makeHTTPAttachHeader(1, headerLen) + case "stderr": + header = makeHTTPAttachHeader(2, headerLen) + default: + logrus.Errorf("Unknown device for log line: %s", device) + header = makeHTTPAttachHeader(1, headerLen) + } + _, err = httpBuf.Write(header) + if err != nil { + break + } + } + _, err = httpBuf.Write([]byte(logLine.Msg)) + if err != nil { + break + } + _, err = httpBuf.Write([]byte("\n")) + if err != nil { + break + } + err = httpBuf.Flush() + if err != nil { + break + } + } + errChan <- err + }() + go func() { + logOpts.WaitGroup.Wait() + close(logChan) + }() + if err := c.ReadLog(logOpts, logChan); err != nil { + return err + } + logrus.Debugf("Done reading logs for container %s", c.ID()) + if err := <-errChan; err != nil { + return err + } + } + if !streamAttach { + return nil + } + c.newContainerEvent(events.Attach) return c.ociRuntime.HTTPAttach(c, httpCon, httpBuf, streams, detachKeys, cancel) } diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index c20e3f0b4..18b438792 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -5,7 +5,6 @@ package libpod import ( "bufio" "bytes" - "encoding/binary" "fmt" "io" "io/ioutil" @@ -480,8 +479,7 @@ func (r *ConmonOCIRuntime) UnpauseContainer(ctr *Container) error { } // HTTPAttach performs an attach for the HTTP API. -// This will consume, and automatically close, the hijacked HTTP session. -// It is not necessary to close it independently. +// The caller must handle closing the HTTP connection after this returns. // The cancel channel is not closed; it is up to the caller to do so after // this function returns. // If this is a container with a terminal, we will stream raw. If it is not, we @@ -492,13 +490,7 @@ func (r *ConmonOCIRuntime) HTTPAttach(ctr *Container, httpConn net.Conn, httpBuf isTerminal = ctr.config.Spec.Process.Terminal } - // Ensure that our contract of closing the HTTP connection is honored. - defer hijackWriteErrorAndClose(deferredErr, ctr.ID(), httpConn, httpBuf) - if streams != nil { - if isTerminal { - return errors.Wrapf(define.ErrInvalidArg, "cannot specify which streams to attach as container %s has a terminal", ctr.ID()) - } if !streams.Stdin && !streams.Stdout && !streams.Stderr { return errors.Wrapf(define.ErrInvalidArg, "must specify at least one stream to attach to") } @@ -547,8 +539,16 @@ func (r *ConmonOCIRuntime) HTTPAttach(ctr *Container, httpConn net.Conn, httpBuf go func() { var err error if isTerminal { + // Hack: return immediately if attachStdout not set to + // emulate Docker. + // Basically, when terminal is set, STDERR goes nowhere. + // Everything does over STDOUT. + // Therefore, if not attaching STDOUT - we'll never copy + // anything from here. logrus.Debugf("Performing terminal HTTP attach for container %s", ctr.ID()) - err = httpAttachTerminalCopy(conn, httpBuf, ctr.ID()) + if attachStdout { + err = httpAttachTerminalCopy(conn, httpBuf, ctr.ID()) + } } else { logrus.Debugf("Performing non-terminal HTTP attach for container %s", ctr.ID()) err = httpAttachNonTerminalCopy(conn, httpBuf, ctr.ID(), attachStdin, attachStdout, attachStderr) @@ -1725,13 +1725,16 @@ func httpAttachNonTerminalCopy(container *net.UnixConn, http *bufio.ReadWriter, for { numR, err := container.Read(buf) if numR > 0 { - headerBuf := []byte{0, 0, 0, 0} + var headerBuf []byte + // Subtract 1 because we strip the first byte (used for + // multiplexing by Conmon). + headerLen := uint32(numR - 1) // Practically speaking, we could make this buf[0] - 1, // but we need to validate it anyways... switch buf[0] { case AttachPipeStdin: - headerBuf[0] = 0 + headerBuf = makeHTTPAttachHeader(0, headerLen) if !stdin { continue } @@ -1739,24 +1742,17 @@ func httpAttachNonTerminalCopy(container *net.UnixConn, http *bufio.ReadWriter, if !stdout { continue } - headerBuf[0] = 1 + headerBuf = makeHTTPAttachHeader(1, headerLen) case AttachPipeStderr: if !stderr { continue } - headerBuf[0] = 2 + headerBuf = makeHTTPAttachHeader(2, headerLen) default: logrus.Errorf("Received unexpected attach type %+d, discarding %d bytes", buf[0], numR) continue } - // Get big-endian length and append. - // Subtract 1 because we strip the first byte (used for - // multiplexing by Conmon). - lenBuf := []byte{0, 0, 0, 0} - binary.BigEndian.PutUint32(lenBuf, uint32(numR-1)) - headerBuf = append(headerBuf, lenBuf...) - numH, err2 := http.Write(headerBuf) if err2 != nil { if err != nil { diff --git a/libpod/util.go b/libpod/util.go index e9d234bbe..6457dac1c 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -2,6 +2,7 @@ package libpod import ( "bufio" + "encoding/binary" "fmt" "io" "os" @@ -239,11 +240,22 @@ func checkDependencyContainer(depCtr, ctr *Container) error { // hijackWriteErrorAndClose writes an error to a hijacked HTTP session and // closes it. Intended to HTTPAttach function. // If error is nil, it will not be written; we'll only close the connection. -func hijackWriteErrorAndClose(toWrite error, cid string, httpCon io.Closer, httpBuf *bufio.ReadWriter) { +func hijackWriteErrorAndClose(toWrite error, cid string, terminal bool, httpCon io.Closer, httpBuf *bufio.ReadWriter) { if toWrite != nil { - if _, err := httpBuf.Write([]byte(toWrite.Error())); err != nil { - logrus.Errorf("Error writing error %q to container %s HTTP attach connection: %v", toWrite, cid, err) - } else if err := httpBuf.Flush(); err != nil { + errString := []byte(fmt.Sprintf("%v\n", toWrite)) + if !terminal { + // We need a header. + header := makeHTTPAttachHeader(2, uint32(len(errString))) + if _, err := httpBuf.Write(header); err != nil { + logrus.Errorf("Error writing header for container %s attach connection error: %v", cid, err) + } + // TODO: May want to return immediately here to avoid + // writing garbage to the socket? + } + if _, err := httpBuf.Write(errString); err != nil { + logrus.Errorf("Error writing error to container %s HTTP attach connection: %v", cid, err) + } + if err := httpBuf.Flush(); err != nil { logrus.Errorf("Error flushing HTTP buffer for container %s HTTP attach connection: %v", cid, err) } } @@ -252,3 +264,14 @@ func hijackWriteErrorAndClose(toWrite error, cid string, httpCon io.Closer, http logrus.Errorf("Error closing container %s HTTP attach connection: %v", cid, err) } } + +// makeHTTPAttachHeader makes an 8-byte HTTP header for a buffer of the given +// length and stream. Accepts an integer indicating which stream we are sending +// to (STDIN = 0, STDOUT = 1, STDERR = 2). +func makeHTTPAttachHeader(stream byte, length uint32) []byte { + headerBuf := []byte{stream, 0, 0, 0} + lenBuf := []byte{0, 0, 0, 0} + binary.BigEndian.PutUint32(lenBuf, length) + headerBuf = append(headerBuf, lenBuf...) + return headerBuf +} diff --git a/pkg/adapter/client.go b/pkg/adapter/client.go index 5774ebe72..a1b2bd507 100644 --- a/pkg/adapter/client.go +++ b/pkg/adapter/client.go @@ -12,7 +12,7 @@ import ( "github.com/varlink/go/varlink" ) -var remoteEndpoint *Endpoint +var remoteEndpoint *Endpoint // nolint: deadcode,unused func (r RemoteRuntime) RemoteEndpoint() (remoteEndpoint *Endpoint, err error) { remoteConfigConnections, err := remoteclientconfig.ReadRemoteConfig(r.config) @@ -26,8 +26,8 @@ func (r RemoteRuntime) RemoteEndpoint() (remoteEndpoint *Endpoint, err error) { remoteEndpoint, err = newBridgeConnection(bridge, nil, r.cmd.LogLevel) // if an environment variable for podman_varlink_address is defined, // we used that as passed - } else if address := os.Getenv("PODMAN_VARLINK_ADDRESS"); address != "" { - logrus.Debug("creating a varlink address based on env variable: %s", address) + } else if address := os.Getenv("PODMAN_VARLINK_ADDRESS"); address != "" { // nolint:gocritic + logrus.Debugf("creating a varlink address based on env variable: %s", address) remoteEndpoint, err = newSocketConnection(address) // if the user provides a remote host, we use it to configure a bridge connection } else if len(r.cmd.RemoteHost) > 0 { @@ -35,7 +35,7 @@ func (r RemoteRuntime) RemoteEndpoint() (remoteEndpoint *Endpoint, err error) { if len(r.cmd.RemoteUserName) < 1 { return nil, errors.New("you must provide a username when providing a remote host name") } - rc := remoteclientconfig.RemoteConnection{r.cmd.RemoteHost, r.cmd.RemoteUserName, false, r.cmd.Port, r.cmd.IdentityFile, r.cmd.IgnoreHosts} + rc := remoteclientconfig.RemoteConnection{r.cmd.RemoteHost, r.cmd.RemoteUserName, false, r.cmd.Port, r.cmd.IdentityFile, r.cmd.IgnoreHosts} // nolint: govet remoteEndpoint, err = newBridgeConnection("", &rc, r.cmd.LogLevel) // if the user has a config file with connections in it } else if len(remoteConfigConnections.Connections) > 0 { @@ -59,7 +59,7 @@ func (r RemoteRuntime) RemoteEndpoint() (remoteEndpoint *Endpoint, err error) { logrus.Debug("creating a varlink address based default root address") remoteEndpoint, err = newSocketConnection(DefaultVarlinkAddress) } - return + return // nolint: nakedret } // Connect provides a varlink connection diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index fc8b524d6..777605896 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -341,7 +341,7 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa failures[ctr] = errors.Wrapf(err, "Failed to evict container: %q", id) continue } - ok = append(ok, string(id)) + ok = append(ok, id) } return ok, failures, nil } @@ -432,7 +432,7 @@ func BatchContainerOp(ctr *Container, opts shared.PsOptions) (shared.BatchContai // Log one or more containers over a varlink connection func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *logs.LogOptions) error { // GetContainersLogs - reply, err := iopodman.GetContainersLogs().Send(r.Conn, uint64(varlink.More), c.InputArgs, c.Follow, c.Latest, options.Since.Format(time.RFC3339Nano), int64(c.Tail), c.Timestamps) + reply, err := iopodman.GetContainersLogs().Send(r.Conn, uint64(varlink.More), c.InputArgs, c.Follow, c.Latest, options.Since.Format(time.RFC3339Nano), c.Tail, c.Timestamps) if err != nil { return errors.Wrapf(err, "failed to get container logs") } @@ -753,15 +753,15 @@ func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid s return nil, nil, err } defer cancel() - defer restoreTerminal(oldTermState) + defer restoreTerminal(oldTermState) // nolint: errcheck logrus.SetFormatter(&RawTtyFormatter{}) - term.SetRawTerminal(os.Stdin.Fd()) + term.SetRawTerminal(os.Stdin.Fd()) // nolint: errcheck } reply, err := iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, detachKeys, start) if err != nil { - restoreTerminal(oldTermState) + restoreTerminal(oldTermState) // nolint: errcheck return nil, nil, err } @@ -769,7 +769,7 @@ func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid s _, err = reply() if err != nil { - restoreTerminal(oldTermState) + restoreTerminal(oldTermState) // nolint: errcheck return nil, nil, err } @@ -857,7 +857,7 @@ func (r *LocalRuntime) Restart(ctx context.Context, c *cliconfig.RestartValues) useTimeout := c.Flag("timeout").Changed || c.Flag("time").Changed inputTimeout := c.Timeout - if c.Latest { + if c.Latest { // nolint: gocritic lastCtr, err := r.GetLatestContainer() if err != nil { return nil, nil, errors.Wrapf(err, "unable to get latest container") @@ -1042,10 +1042,10 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal return ec, err } defer cancel() - defer restoreTerminal(oldTermState) + defer restoreTerminal(oldTermState) // nolint: errcheck logrus.SetFormatter(&RawTtyFormatter{}) - term.SetRawTerminal(os.Stdin.Fd()) + term.SetRawTerminal(os.Stdin.Fd()) // nolint: errcheck } opts := iopodman.ExecOpts{ @@ -1082,7 +1082,7 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal return ec, err } -func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, stdin *os.File, stdout *os.File, oldTermState *term.State, resize chan remotecommand.TerminalSize, ecChan chan int) chan error { +func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, stdin *os.File, stdout *os.File, oldTermState *term.State, resize chan remotecommand.TerminalSize, ecChan chan int) chan error { // nolint: interfacer errChan := make(chan error, 1) // These are the special writers that encode input from the client. varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin) @@ -1092,7 +1092,7 @@ func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, std go func() { // Read from the wire and direct to stdout or stderr err := virtwriter.Reader(reader, stdout, os.Stderr, nil, nil, ecChan) - defer restoreTerminal(oldTermState) + defer restoreTerminal(oldTermState) // nolint: errcheck sendGenericError(ecChan) errChan <- err }() @@ -1101,13 +1101,13 @@ func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, std for termResize := range resize { b, err := json.Marshal(termResize) if err != nil { - defer restoreTerminal(oldTermState) + defer restoreTerminal(oldTermState) // nolint: errcheck,staticcheck sendGenericError(ecChan) errChan <- err } _, err = varlinkResizeWriter.Write(b) if err != nil { - defer restoreTerminal(oldTermState) + defer restoreTerminal(oldTermState) // nolint: errcheck,staticcheck sendGenericError(ecChan) errChan <- err } @@ -1117,7 +1117,7 @@ func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, std // Takes stdinput and sends it over the wire after being encoded go func() { if _, err := io.Copy(varlinkStdinWriter, stdin); err != nil { - defer restoreTerminal(oldTermState) + defer restoreTerminal(oldTermState) // nolint: errcheck sendGenericError(ecChan) errChan <- err } diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go index ebd10a92a..4c6eea9a7 100644 --- a/pkg/adapter/pods_remote.go +++ b/pkg/adapter/pods_remote.go @@ -515,10 +515,10 @@ func (p *Pod) GetPodStats(previousContainerStats map[string]*libpod.ContainerSta newStats := varlinkapi.ContainerStatsToLibpodContainerStats(stats) // If the container wasn't running, don't include it // but also suppress the error - if err != nil && errors.Cause(err) != define.ErrCtrStateInvalid { + if err != nil && errors.Cause(err) != define.ErrCtrStateInvalid { // nolint: govet return nil, err } - if err == nil { + if err == nil { // nolint: govet newContainerStats[c.ID()] = &newStats } } diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index a4ac660ea..c511b70f1 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -180,7 +180,7 @@ type Pod struct { type remotepod struct { config *libpod.PodConfig state *libpod.PodInspectState - containers []libpod.PodContainerInfo + containers []libpod.PodContainerInfo // nolint: structcheck Runtime *LocalRuntime } @@ -627,7 +627,7 @@ func (r *LocalRuntime) SendFileOverVarlink(source string) (string, error) { return "", err } logrus.Debugf("sending %s over varlink connection", source) - reply, err := iopodman.SendFile().Send(r.Conn, varlink.Upgrade, "", int64(fileInfo.Size())) + reply, err := iopodman.SendFile().Send(r.Conn, varlink.Upgrade, "", fileInfo.Size()) if err != nil { return "", err } @@ -754,9 +754,7 @@ func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeIn volumes = append(volumes, vol.Name()) } } else { - for _, arg := range c.InputArgs { - volumes = append(volumes, arg) - } + volumes = append(volumes, c.InputArgs...) } for _, vol := range volumes { @@ -855,7 +853,7 @@ func (r *LocalRuntime) SaveImage(ctx context.Context, c *cliconfig.SaveValues) e } } - if err != nil { + if err != nil { // nolint: govet return err } @@ -927,7 +925,7 @@ func IsImageNotFound(err error) bool { if errors.Cause(err) == image.ErrNoSuchImage { return true } - switch err.(type) { + switch err.(type) { // nolint: gocritic case *iopodman.ImageNotFound: return true } @@ -991,7 +989,7 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { Time: eTime, Type: eType, } - if c.Format == formats.JSONString { + if c.Format == formats.JSONString { // nolint: gocritic jsonStr, err := event.ToJSONString() if err != nil { return errors.Wrapf(err, "unable to format json") @@ -1008,6 +1006,7 @@ func (r *LocalRuntime) Events(c *cliconfig.EventValues) error { return err } } + if _, err := w.Write([]byte("\n")); err != nil { return err } @@ -1040,7 +1039,7 @@ func stringToChangeType(change string) archive.ChangeType { return archive.ChangeAdd case "D": return archive.ChangeDelete - default: + default: // nolint: gocritic,stylecheck logrus.Errorf("'%s' is unknown archive type", change) fallthrough case "C": diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index c53af0f26..3f6aca502 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -261,6 +261,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { var until time.Time if _, found := r.URL.Query()["until"]; found { + // FIXME: until != since but the logs backend does not yet support until. since, err = util.ParseInputTime(query.Until) if err != nil { utils.BadRequest(w, "until", query.Until, err) diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go index da7b5bb0c..80ad52aee 100644 --- a/pkg/api/handlers/compat/containers_attach.go +++ b/pkg/api/handlers/compat/containers_attach.go @@ -1,6 +1,7 @@ package compat import ( + "fmt" "net/http" "github.com/containers/libpod/libpod" @@ -23,7 +24,9 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { Stdin bool `schema:"stdin"` Stdout bool `schema:"stdout"` Stderr bool `schema:"stderr"` - }{} + }{ + Stream: true, + } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, "Error parsing parameters", http.StatusBadRequest, err) return @@ -61,16 +64,9 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { return } - // TODO: Investigate supporting these. - // Logs replays container logs over the attach socket. - // Stream seems to break things up somehow? Not 100% clear. - if query.Logs { - utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("the logs parameter to attach is not presently supported")) - return - } - // We only support stream=true or unset - if _, found := r.URL.Query()["stream"]; found && query.Stream { - utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("the stream parameter to attach is not presently supported")) + // At least one of these must be set + if !query.Stream && !query.Logs { + utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("at least one of Logs or Stream must be set")) return } @@ -86,7 +82,13 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) { + // For Docker compatibility, we need to re-initialize containers in these states. + if state == define.ContainerStateConfigured || state == define.ContainerStateExited { + if err := ctr.Init(r.Context()); err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID())) + return + } + } else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) { utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers")) return } @@ -98,20 +100,23 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusSwitchingProtocols) - connection, buffer, err := hijacker.Hijack() if err != nil { utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection")) return } + // This header string sourced from Docker: + // https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go + // Using literally to ensure compatability with existing clients. + fmt.Fprintf(connection, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") + logrus.Debugf("Hijack for attach of container %s successful", ctr.ID()) // Perform HTTP attach. // HTTPAttach will handle everything about the connection from here on // (including closing it and writing errors to it). - if err := ctr.HTTPAttach(connection, buffer, streams, detachKeys, nil); err != nil { + if err := ctr.HTTPAttach(connection, buffer, streams, detachKeys, nil, query.Stream, query.Logs); err != nil { // We can't really do anything about errors anymore. HTTPAttach // should be writing them to the connection. logrus.Errorf("Error attaching to container %s: %v", ctr.ID(), err) diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go index 0f72ef328..8ef32716d 100644 --- a/pkg/api/handlers/compat/events.go +++ b/pkg/api/handlers/compat/events.go @@ -1,7 +1,6 @@ package compat import ( - "encoding/json" "fmt" "net/http" @@ -10,6 +9,7 @@ import ( "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/gorilla/schema" + jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -48,14 +48,27 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { }() if eventsError != nil { utils.InternalServerError(w, eventsError) + close(eventChannel) return } - coder := json.NewEncoder(w) - coder.SetEscapeHTML(true) + // If client disappears we need to stop listening for events + go func(done <-chan struct{}) { + <-done + close(eventChannel) + }(r.Context().Done()) + // Headers need to be written out before turning Writer() over to json encoder w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + + json := jsoniter.ConfigCompatibleWithStandardLibrary + coder := json.NewEncoder(w) + coder.SetEscapeHTML(true) + for event := range eventChannel { e := handlers.EventToApiEvent(event) if err := coder.Encode(e); err != nil { diff --git a/pkg/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go index 7283b22c4..8da685527 100644 --- a/pkg/api/handlers/compat/images_search.go +++ b/pkg/api/handlers/compat/images_search.go @@ -57,6 +57,7 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { Filter: filter, Limit: query.Limit, } + results, err := image.SearchImages(query.Term, options) if err != nil { utils.BadRequest(w, "term", query.Term, err) diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 5cbfb11eb..086bef847 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -285,3 +285,23 @@ func Restore(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusOK, entities.RestoreReport{Id: ctr.ID()}) } + +func InitContainer(w http.ResponseWriter, r *http.Request) { + name := utils.GetName(r) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + ctr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + err = ctr.Init(r.Context()) + if errors.Cause(err) == define.ErrCtrStateInvalid { + utils.Error(w, "container already initialized", http.StatusNotModified, err) + return + } + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusNoContent, "") +} diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 850de4598..a42d06205 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -645,3 +645,56 @@ func UntagImage(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusCreated, "") } + +func SearchImages(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Term string `json:"term"` + Limit int `json:"limit"` + Filters []string `json:"filters"` + TLSVerify bool `json:"tlsVerify"` + }{ + // This is where you can override the golang default value for one of fields + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + options := image.SearchOptions{ + Limit: query.Limit, + } + if _, found := r.URL.Query()["tlsVerify"]; found { + options.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) + } + + if _, found := r.URL.Query()["filters"]; found { + filter, err := image.ParseSearchFilter(query.Filters) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse filters parameter for %s", r.URL.String())) + return + } + options.Filter = *filter + } + + searchResults, err := image.SearchImages(query.Term, options) + if err != nil { + utils.BadRequest(w, "term", query.Term, err) + return + } + // Convert from image.SearchResults to entities.ImageSearchReport. We don't + // want to leak any low-level packages into the remote client, which + // requires converting. + reports := make([]entities.ImageSearchReport, len(searchResults)) + for i := range searchResults { + reports[i].Index = searchResults[i].Index + reports[i].Name = searchResults[i].Name + reports[i].Description = searchResults[i].Index + reports[i].Stars = searchResults[i].Stars + reports[i].Official = searchResults[i].Official + reports[i].Automated = searchResults[i].Automated + } + + utils.WriteResponse(w, http.StatusOK, reports) +} diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index a890169a1..81cab1ede 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -12,6 +12,7 @@ import ( "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/specgen" + "github.com/containers/libpod/pkg/specgen/generate" "github.com/containers/libpod/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -27,7 +28,7 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Failed to decode specgen", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen")) return } - pod, err := psg.MakePod(runtime) + pod, err := generate.MakePod(&psg, runtime) if err != nil { http_code := http.StatusInternalServerError if errors.Cause(err) == define.ErrPodExists { diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index f1c932ebc..0fe6ae6a7 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -180,6 +180,31 @@ type ExecCreateResponse struct { docker.IDResponse } +func (e *Event) ToLibpodEvent() *events.Event { + exitCode, err := strconv.Atoi(e.Actor.Attributes["containerExitCode"]) + if err != nil { + return nil + } + status, err := events.StringToStatus(e.Action) + if err != nil { + return nil + } + t, err := events.StringToType(e.Type) + if err != nil { + return nil + } + lp := events.Event{ + ContainerExitCode: exitCode, + ID: e.Actor.ID, + Image: e.Actor.Attributes["image"], + Name: e.Actor.Attributes["name"], + Status: status, + Time: time.Unix(e.Time, e.TimeNano), + Type: t, + } + return &lp +} + func EventToApiEvent(e *events.Event) *Event { return &Event{dockerEvents.Message{ Type: e.Type.String(), diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go index 30a1680c9..7a7db12f3 100644 --- a/pkg/api/server/handler_api.go +++ b/pkg/api/server/handler_api.go @@ -19,7 +19,7 @@ func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc { if err != nil { buf := make([]byte, 1<<20) n := runtime.Stack(buf, true) - log.Warnf("Recovering from podman handler panic: %v, %s", err, buf[:n]) + log.Warnf("Recovering from API handler panic: %v, %s", err, buf[:n]) // Try to inform client things went south... won't work if handler already started writing response body utils.InternalServerError(w, fmt.Errorf("%v", err)) } @@ -27,12 +27,7 @@ func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc { // Wrapper to hide some boiler plate fn := func(w http.ResponseWriter, r *http.Request) { - // Connection counting, ugh. Needed to support the sliding window for idle checking. - s.ConnectionCh <- EnterHandler - defer func() { s.ConnectionCh <- ExitHandler }() - - log.Debugf("APIHandler -- Method: %s URL: %s (conn %d/%d)", - r.Method, r.URL.String(), s.ActiveConnections, s.TotalConnections) + log.Debugf("APIHandler -- Method: %s URL: %s", r.Method, r.URL.String()) if err := r.ParseForm(); err != nil { log.Infof("Failed Request: unable to parse form: %q", err) diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 150cca872..8b9a9e312 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -517,13 +517,13 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // name: logs // required: false // type: boolean - // description: Not yet supported + // description: Stream all logs from the container across the connection. Happens before streaming attach (if requested). At least one of logs or stream must be set // - in: query // name: stream // required: false // type: boolean // default: true - // description: If passed, must be set to true; stream=false is not yet supported + // description: Attach to the container. If unset, and logs is set, only the container's logs will be sent. At least one of stream or logs must be set // - in: query // name: stdout // required: false @@ -1194,13 +1194,13 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // name: logs // required: false // type: boolean - // description: Not yet supported + // description: Stream all logs from the container across the connection. Happens before streaming attach (if requested). At least one of logs or stream must be set // - in: query // name: stream // required: false // type: boolean // default: true - // description: If passed, must be set to true; stream=false is not yet supported + // description: Attach to the container. If unset, and logs is set, only the container's logs will be sent. At least one of stream or logs must be set // - in: query // name: stdout // required: false @@ -1377,7 +1377,6 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/containers/{name}/restore"), s.APIHandler(libpod.Restore)).Methods(http.MethodPost) - // swagger:operation GET /containers/{name}/changes libpod libpodChangesContainer // swagger:operation GET /libpod/containers/{name}/changes compat changesContainer // --- @@ -1411,6 +1410,29 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { r.HandleFunc(VersionedPath("/containers/{name}/changes"), s.APIHandler(compat.Changes)) r.HandleFunc("/containers/{name}/changes", s.APIHandler(compat.Changes)) r.HandleFunc(VersionedPath("/libpod/containers/{name}/changes"), s.APIHandler(compat.Changes)) - + // swagger:operation POST /libpod/containers/{name}/init libpod libpodInitContainer + // --- + // tags: + // - containers + // summary: Initialize a container + // description: Performs all tasks necessary for initializing the container but does not start the container. + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // 204: + // description: no error + // 304: + // description: container already initialized + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name}/init"), s.APIHandler(libpod.InitContainer)).Methods(http.MethodPost) return nil } diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 77560e789..7dd887037 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -919,7 +919,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/DocsSearchResponse" // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(compat.SearchImages)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(libpod.SearchImages)).Methods(http.MethodGet) // swagger:operation DELETE /libpod/images/{name:.*} libpod libpodRemoveImage // --- // tags: diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 59f1f95cb..5f1a86183 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -2,11 +2,14 @@ package server import ( "context" + "log" "net" "net/http" "os" "os/signal" + "runtime" "strings" + "sync" "syscall" "time" @@ -20,26 +23,19 @@ import ( ) type APIServer struct { - http.Server // The HTTP work happens here - *schema.Decoder // Decoder for Query parameters to structs - context.Context // Context to carry objects to handlers - *libpod.Runtime // Where the real work happens - net.Listener // mux for routing HTTP API calls to libpod routines - context.CancelFunc // Stop APIServer - *time.Timer // Hold timer for sliding window - time.Duration // Duration of client access sliding window - ActiveConnections uint64 // Number of handlers holding a connection - TotalConnections uint64 // Number of connections handled - ConnectionCh chan int // Channel for signalling handler enter/exit + http.Server // The HTTP work happens here + *schema.Decoder // Decoder for Query parameters to structs + context.Context // Context to carry objects to handlers + *libpod.Runtime // Where the real work happens + net.Listener // mux for routing HTTP API calls to libpod routines + context.CancelFunc // Stop APIServer + idleTracker *IdleTracker // Track connections to support idle shutdown } // Number of seconds to wait for next request, if exceeded shutdown server const ( DefaultServiceDuration = 300 * time.Second UnlimitedServiceDuration = 0 * time.Second - EnterHandler = 1 - ExitHandler = -1 - NOOPHandler = 0 ) // NewServer will create and configure a new API server with all defaults @@ -56,7 +52,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li // If listener not provided try socket activation protocol if listener == nil { if _, found := os.LookupEnv("LISTEN_FDS"); !found { - return nil, errors.Errorf("Cannot create Server, no listener provided and socket activation protocol is not active.") + return nil, errors.Errorf("Cannot create API Server, no listener provided and socket activation protocol is not active.") } listeners, err := activation.Listeners() @@ -70,17 +66,20 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li } router := mux.NewRouter().UseEncodedPath() + idle := NewIdleTracker(duration) + server := APIServer{ Server: http.Server{ Handler: router, ReadHeaderTimeout: 20 * time.Second, IdleTimeout: duration, + ConnState: idle.ConnState, + ErrorLog: log.New(logrus.StandardLogger().Out, "", 0), }, - Decoder: handlers.NewAPIDecoder(), - Runtime: runtime, - Listener: *listener, - Duration: duration, - ConnectionCh: make(chan int), + Decoder: handlers.NewAPIDecoder(), + idleTracker: idle, + Listener: *listener, + Runtime: runtime, } router.NotFoundHandler = http.HandlerFunc( @@ -120,11 +119,11 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li router.Walk(func(route *mux.Route, r *mux.Router, ancestors []*mux.Route) error { // nolint path, err := route.GetPathTemplate() if err != nil { - path = "" + path = "<N/A>" } methods, err := route.GetMethods() if err != nil { - methods = []string{} + methods = []string{"<N/A>"} } logrus.Debugf("Methods: %s Path: %s", strings.Join(methods, ", "), path) return nil @@ -136,24 +135,20 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li // Serve starts responding to HTTP requests func (s *APIServer) Serve() error { - // This is initialized here as Timer is not needed until Serve'ing - if s.Duration > 0 { - s.Timer = time.AfterFunc(s.Duration, func() { - s.ConnectionCh <- NOOPHandler - }) - go s.ReadChannelWithTimeout() - } else { - go s.ReadChannelNoTimeout() - } - sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) errChan := make(chan error, 1) go func() { + <-s.idleTracker.Done() + logrus.Debugf("API Server idle for %v", s.idleTracker.Duration) + _ = s.Shutdown() + }() + + go func() { err := s.Server.Serve(s.Listener) if err != nil && err != http.ErrServerClosed { - errChan <- errors.Wrap(err, "Failed to start APIServer") + errChan <- errors.Wrap(err, "failed to start API server") return } errChan <- nil @@ -169,72 +164,30 @@ func (s *APIServer) Serve() error { return nil } -func (s *APIServer) ReadChannelWithTimeout() { - // stalker to count the connections. Should the timer expire it will shutdown the service. - for delta := range s.ConnectionCh { - switch delta { - case EnterHandler: - s.Timer.Stop() - s.ActiveConnections += 1 - s.TotalConnections += 1 - case ExitHandler: - s.Timer.Stop() - s.ActiveConnections -= 1 - if s.ActiveConnections == 0 { - // Server will be shutdown iff the timer expires before being reset or stopped - s.Timer = time.AfterFunc(s.Duration, func() { - if err := s.Shutdown(); err != nil { - logrus.Errorf("Failed to shutdown APIServer: %v", err) - os.Exit(1) - } - }) - } else { - s.Timer.Reset(s.Duration) - } - case NOOPHandler: - // push the check out another duration... - s.Timer.Reset(s.Duration) - default: - logrus.Warnf("ConnectionCh received unsupported input %d", delta) - } - } -} - -func (s *APIServer) ReadChannelNoTimeout() { - // stalker to count the connections. - for delta := range s.ConnectionCh { - switch delta { - case EnterHandler: - s.ActiveConnections += 1 - s.TotalConnections += 1 - case ExitHandler: - s.ActiveConnections -= 1 - case NOOPHandler: - default: - logrus.Warnf("ConnectionCh received unsupported input %d", delta) - } - } -} - // Shutdown is a clean shutdown waiting on existing clients func (s *APIServer) Shutdown() error { + if logrus.IsLevelEnabled(logrus.DebugLevel) { + _, file, line, _ := runtime.Caller(1) + logrus.Debugf("APIServer.Shutdown by %s:%d, %d/%d connection(s)", + file, line, s.idleTracker.ActiveConnections(), s.idleTracker.TotalConnections()) + } + // Duration == 0 flags no auto-shutdown of the server - if s.Duration == 0 { + if s.idleTracker.Duration == 0 { logrus.Debug("APIServer.Shutdown ignored as Duration == 0") return nil } - logrus.Debugf("APIServer.Shutdown called %v, conn %d/%d", time.Now(), s.ActiveConnections, s.TotalConnections) - // Gracefully shutdown server - ctx, cancel := context.WithTimeout(context.Background(), s.Duration) + // Gracefully shutdown server, duration of wait same as idle window + ctx, cancel := context.WithTimeout(context.Background(), s.idleTracker.Duration) defer cancel() - go func() { err := s.Server.Shutdown(ctx) if err != nil && err != context.Canceled && err != http.ErrServerClosed { logrus.Errorf("Failed to cleanly shutdown APIServer: %s", err.Error()) } }() + <-ctx.Done() return nil } @@ -242,3 +195,55 @@ func (s *APIServer) Shutdown() error { func (s *APIServer) Close() error { return s.Server.Close() } + +type IdleTracker struct { + active map[net.Conn]struct{} + total int + mux sync.Mutex + timer *time.Timer + Duration time.Duration +} + +func NewIdleTracker(idle time.Duration) *IdleTracker { + return &IdleTracker{ + active: make(map[net.Conn]struct{}), + Duration: idle, + timer: time.NewTimer(idle), + } +} + +func (t *IdleTracker) ConnState(conn net.Conn, state http.ConnState) { + t.mux.Lock() + defer t.mux.Unlock() + + oldActive := len(t.active) + logrus.Debugf("IdleTracker %p:%v %d/%d connection(s)", conn, state, t.ActiveConnections(), t.TotalConnections()) + switch state { + case http.StateNew, http.StateActive, http.StateHijacked: + t.active[conn] = struct{}{} + // stop the timer if we transitioned from idle + if oldActive == 0 { + t.timer.Stop() + } + t.total += 1 + case http.StateIdle, http.StateClosed: + delete(t.active, conn) + // Restart the timer if we've become idle + if oldActive > 0 && len(t.active) == 0 { + t.timer.Stop() + t.timer.Reset(t.Duration) + } + } +} + +func (t *IdleTracker) ActiveConnections() int { + return len(t.active) +} + +func (t *IdleTracker) TotalConnections() int { + return t.total +} + +func (t *IdleTracker) Done() <-chan time.Time { + return t.timer.C +} diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index a188d73a0..963f0ec57 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -12,6 +12,7 @@ import ( "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" ) // List obtains a list of containers in local storage. All parameters to this method are optional. @@ -316,3 +317,21 @@ func Export(ctx context.Context, nameOrID string, w io.Writer) error { } return response.Process(nil) } + +// ContainerInit takes a created container and executes all of the +// preparations to run the container except it will not start +// or attach to the container +func ContainerInit(ctx context.Context, nameOrID string) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/init", nil, nameOrID) + if err != nil { + return err + } + if response.StatusCode == http.StatusNotModified { + return errors.Wrapf(define.ErrCtrStateInvalid, "container %s has already been created in runtime", nameOrID) + } + return response.Process(nil) +} diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 1b3df609b..3550c3968 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -2,7 +2,6 @@ package images import ( "context" - "errors" "fmt" "io" "net/http" @@ -13,6 +12,7 @@ import ( "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" ) // Exists a lightweight way to determine if an image exists in local storage. It returns a @@ -308,3 +308,34 @@ func Push(ctx context.Context, source string, destination string, options entiti _, err = conn.DoRequest(nil, http.MethodPost, path, params) return err } + +// Search is the binding for libpod's v2 endpoints for Search images. +func Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + params.Set("term", term) + params.Set("limit", strconv.Itoa(opts.Limit)) + for _, f := range opts.Filters { + params.Set("filters", f) + } + + if opts.TLSVerify != types.OptionalBoolUndefined { + val := bool(opts.TLSVerify == types.OptionalBoolTrue) + params.Set("tlsVerify", strconv.FormatBool(val)) + } + + response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params) + if err != nil { + return nil, err + } + + results := []entities.ImageSearchReport{} + if err := response.Process(&results); err != nil { + return nil, err + } + + return results, nil +} diff --git a/pkg/bindings/images/search.go b/pkg/bindings/images/search.go deleted file mode 100644 index 183ff3d77..000000000 --- a/pkg/bindings/images/search.go +++ /dev/null @@ -1,41 +0,0 @@ -package images - -import ( - "context" - "net/http" - "net/url" - "strconv" - - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/bindings" -) - -// Search looks for the given image (term) in container image registries. The optional limit parameter sets -// a maximum number of results returned. The optional filters parameter allow for more specific image -// searches. -func Search(ctx context.Context, term string, limit *int, filters map[string][]string) ([]image.SearchResult, error) { - var ( - searchResults []image.SearchResult - ) - conn, err := bindings.GetClient(ctx) - if err != nil { - return nil, err - } - params := url.Values{} - params.Set("term", term) - if limit != nil { - params.Set("limit", strconv.Itoa(*limit)) - } - if filters != nil { - stringFilter, err := bindings.FiltersToString(filters) - if err != nil { - return nil, err - } - params.Set("filters", stringFilter) - } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params) - if err != nil { - return searchResults, nil - } - return searchResults, response.Process(&searchResults) -} diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index a31181958..0b1b9ecdd 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -410,4 +410,125 @@ var _ = Describe("Podman containers ", func() { _, err = containers.Top(bt.conn, cid, []string{"Me,Neither"}) Expect(err).To(BeNil()) }) + + It("podman bogus container does not exist in local storage", func() { + // Bogus container existence check should fail + containerExists, err := containers.Exists(bt.conn, "foobar") + Expect(err).To(BeNil()) + Expect(containerExists).To(BeFalse()) + }) + + It("podman container exists in local storage by name", func() { + // Container existence check by name should work + var name = "top" + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + containerExists, err := containers.Exists(bt.conn, name) + Expect(err).To(BeNil()) + Expect(containerExists).To(BeTrue()) + }) + + It("podman container exists in local storage by ID", func() { + // Container existence check by ID should work + var name = "top" + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + containerExists, err := containers.Exists(bt.conn, cid) + Expect(err).To(BeNil()) + Expect(containerExists).To(BeTrue()) + }) + + It("podman container exists in local storage by short ID", func() { + // Container existence check by short ID should work + var name = "top" + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + containerExists, err := containers.Exists(bt.conn, cid[0:12]) + Expect(err).To(BeNil()) + Expect(containerExists).To(BeTrue()) + }) + + It("podman kill bogus container", func() { + // Killing bogus container should return 404 + err := containers.Kill(bt.conn, "foobar", "SIGTERM") + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + }) + + It("podman kill a running container by name with SIGINT", func() { + // Killing a running container should work + var name = "top" + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + err = containers.Kill(bt.conn, name, "SIGINT") + Expect(err).To(BeNil()) + _, err = containers.Exists(bt.conn, name) + Expect(err).To(BeNil()) + }) + + It("podman kill a running container by ID with SIGTERM", func() { + // Killing a running container by ID should work + var name = "top" + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + err = containers.Kill(bt.conn, cid, "SIGTERM") + Expect(err).To(BeNil()) + _, err = containers.Exists(bt.conn, cid) + Expect(err).To(BeNil()) + }) + + It("podman kill a running container by ID with SIGKILL", func() { + // Killing a running container by ID with TERM should work + var name = "top" + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + err = containers.Kill(bt.conn, cid, "SIGKILL") + Expect(err).To(BeNil()) + }) + + It("podman kill a running container by bogus signal", func() { + //Killing a running container by bogus signal should fail + var name = "top" + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + err = containers.Kill(bt.conn, cid, "foobar") + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + }) + + It("podman kill latest container with SIGTERM", func() { + // Killing latest container should work + var name1 = "first" + var name2 = "second" + var latestContainers = 1 + _, err := bt.RunTopContainer(&name1, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + _, err = bt.RunTopContainer(&name2, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + containerLatestList, err := containers.List(bt.conn, nil, nil, &latestContainers, nil, nil, nil) + Expect(err).To(BeNil()) + err = containers.Kill(bt.conn, containerLatestList[0].Names[0], "SIGTERM") + Expect(err).To(BeNil()) + }) + + It("container init on a bogus container", func() { + err := containers.ContainerInit(bt.conn, "doesnotexist") + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + }) + + It("container init", func() { + s := specgen.NewSpecGenerator(alpine.name) + ctr, err := containers.CreateWithSpec(bt.conn, s) + Expect(err).To(BeNil()) + err = containers.ContainerInit(bt.conn, ctr.ID) + Expect(err).To(BeNil()) + // trying to init again should be an error + err = containers.ContainerInit(bt.conn, ctr.ID) + Expect(err).ToNot(BeNil()) + }) + }) diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 992720196..58210efd0 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -314,11 +314,11 @@ var _ = Describe("Podman images", func() { }) It("Search for an image", func() { - imgs, err := images.Search(bt.conn, "alpine", nil, nil) + reports, err := images.Search(bt.conn, "alpine", entities.ImageSearchOptions{}) Expect(err).To(BeNil()) - Expect(len(imgs)).To(BeNumerically(">", 1)) + Expect(len(reports)).To(BeNumerically(">", 1)) var foundAlpine bool - for _, i := range imgs { + for _, i := range reports { if i.Name == "docker.io/library/alpine" { foundAlpine = true break @@ -327,23 +327,20 @@ var _ = Describe("Podman images", func() { Expect(foundAlpine).To(BeTrue()) // Search for alpine with a limit of 10 - ten := 10 - imgs, err = images.Search(bt.conn, "docker.io/alpine", &ten, nil) + reports, err = images.Search(bt.conn, "docker.io/alpine", entities.ImageSearchOptions{Limit: 10}) Expect(err).To(BeNil()) - Expect(len(imgs)).To(BeNumerically("<=", 10)) + Expect(len(reports)).To(BeNumerically("<=", 10)) // Search for alpine with stars greater than 100 - filters := make(map[string][]string) - filters["stars"] = []string{"100"} - imgs, err = images.Search(bt.conn, "docker.io/alpine", nil, filters) + reports, err = images.Search(bt.conn, "docker.io/alpine", entities.ImageSearchOptions{Filters: []string{"stars=100"}}) Expect(err).To(BeNil()) - for _, i := range imgs { + for _, i := range reports { Expect(i.Stars).To(BeNumerically(">=", 100)) } // Search with a fqdn - imgs, err = images.Search(bt.conn, "quay.io/libpod/alpine_nginx", nil, nil) - Expect(len(imgs)).To(BeNumerically(">=", 1)) + reports, err = images.Search(bt.conn, "quay.io/libpod/alpine_nginx", entities.ImageSearchOptions{}) + Expect(len(reports)).To(BeNumerically(">=", 1)) }) It("Prune images", func() { diff --git a/pkg/cgroups/pids.go b/pkg/cgroups/pids.go index 92f82553b..b2bfebe4d 100644 --- a/pkg/cgroups/pids.go +++ b/pkg/cgroups/pids.go @@ -44,7 +44,7 @@ func (c *pidHandler) Destroy(ctr *CgroupControl) error { // Stat fills a metrics structure with usage stats for the controller func (c *pidHandler) Stat(ctr *CgroupControl, m *Metrics) error { - if ctr.path != "" { + if ctr.path == "" { // nothing we can do to retrieve the pids.current path return nil } diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 5d302058b..f21af9ce4 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -172,6 +172,26 @@ type AttachOptions struct { Stderr *os.File } +// ContainerLogsOptions describes the options to extract container logs. +type ContainerLogsOptions struct { + // Show extra details provided to the logs. + Details bool + // Follow the log output. + Follow bool + // Display logs for the latest container only. Ignored on the remote client. + Latest bool + // Show container names in the output. + Names bool + // Show logs since this timestamp. + Since time.Time + // Number of lines to display at the end of the output. + Tail int64 + // Show timestamps in the logs. + Timestamps bool + // Write the logs to Writer. + Writer io.Writer +} + // ExecOptions describes the cli values to exec into // a container type ExecOptions struct { @@ -245,3 +265,65 @@ type ContainerRunReport struct { ExitCode int Id string } + +// ContainerCleanupOptions are the CLI values for the +// cleanup command +type ContainerCleanupOptions struct { + All bool + Latest bool + Remove bool + RemoveImage bool +} + +// ContainerCleanupReport describes the response from a +// container cleanup +type ContainerCleanupReport struct { + CleanErr error + Id string + RmErr error + RmiErr error +} + +// ContainerInitOptions describes input options +// for the container init cli +type ContainerInitOptions struct { + All bool + Latest bool +} + +// ContainerInitReport describes the results of a +// container init +type ContainerInitReport struct { + Err error + Id string +} + +//ContainerMountOptions describes the input values for mounting containers +// in the CLI +type ContainerMountOptions struct { + All bool + Format string + Latest bool + NoTruncate bool +} + +// ContainerUnmountOptions are the options from the cli for unmounting +type ContainerUnmountOptions struct { + All bool + Force bool + Latest bool +} + +// ContainerMountReport describes the response from container mount +type ContainerMountReport struct { + Err error + Id string + Name string + Path string +} + +// ContainerUnmountReport describes the response from umounting a container +type ContainerUnmountReport struct { + Err error + Id string +} diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go index c14348529..3b971a1e8 100644 --- a/pkg/domain/entities/engine.go +++ b/pkg/domain/entities/engine.go @@ -1,13 +1,23 @@ package entities import ( - "os/user" - "path/filepath" + "context" + "fmt" + "io" + "os" + "github.com/containers/buildah/pkg/parse" "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/sysinfo" + "github.com/containers/libpod/pkg/apparmor" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/rootless" + "github.com/opencontainers/selinux/go-selinux" + "github.com/opentracing/opentracing-go" "github.com/spf13/pflag" ) +// EngineMode is the connection type podman is using to access libpod type EngineMode string const ( @@ -15,78 +25,243 @@ const ( TunnelMode = EngineMode("tunnel") ) +// Convert EngineMode to String func (m EngineMode) String() string { return string(m) } -type EngineOptions struct { - Uri string - Identities []string - FlagSet *pflag.FlagSet - EngineMode EngineMode - - CGroupManager string - CniConfigDir string - ConmonPath string - DefaultMountsFile string - EventsBackend string - HooksDir []string - MaxWorks int - Namespace string - Root string - Runroot string - Runtime string - StorageDriver string - StorageOpts []string - Syslog bool - Trace bool - NetworkCmdPath string - - Config string - CpuProfile string - LogLevel string - TmpDir string - - RemoteUserName string - RemoteHost string - VarlinkAddress string - ConnectionName string - RemoteConfigFilePath string - Port int - IdentityFile string - IgnoreHosts bool -} - -func NewEngineOptions() (EngineOptions, error) { - u, _ := user.Current() - return EngineOptions{ - CGroupManager: config.SystemdCgroupsManager, - CniConfigDir: "", - Config: "", - ConmonPath: filepath.Join("usr", "bin", "conmon"), - ConnectionName: "", - CpuProfile: "", - DefaultMountsFile: "", - EventsBackend: "", - HooksDir: nil, - IdentityFile: "", - IgnoreHosts: false, - LogLevel: "", - MaxWorks: 0, - Namespace: "", - NetworkCmdPath: "", - Port: 0, - RemoteConfigFilePath: "", - RemoteHost: "", - RemoteUserName: "", - Root: "", - Runroot: filepath.Join("run", "user", u.Uid), - Runtime: "", - StorageDriver: "overlayfs", - StorageOpts: nil, - Syslog: false, - TmpDir: filepath.Join("run", "user", u.Uid, "libpod", "tmp"), - Trace: false, - VarlinkAddress: "", - }, nil +// PodmanConfig combines the defaults and settings from the file system with the +// flags given in os.Args. Some runtime state is also stored here. +type PodmanConfig struct { + *config.Config + *pflag.FlagSet + + CGroupUsage string // rootless code determines Usage message + ConmonPath string // --conmon flag will set Engine.ConmonPath + CpuProfile string // Hidden: Should CPU profile be taken + EngineMode EngineMode // ABI or Tunneling mode + Identities []string // ssh identities for connecting to server + MaxWorks int // maximum number of parallel threads + RuntimePath string // --runtime flag will set Engine.RuntimePath + SpanCloser io.Closer // Close() for tracing object + SpanCtx context.Context // context to use when tracing + Span opentracing.Span // tracing object + Syslog bool // write to StdOut and Syslog, not supported when tunneling + Trace bool // Hidden: Trace execution + Uri string // URI to API Service + + Runroot string + StorageDriver string + StorageOpts []string +} + +// DefaultSecurityOptions: getter for security options from configuration +func (c PodmanConfig) DefaultSecurityOptions() []string { + securityOpts := []string{} + if c.Containers.SeccompProfile != "" && c.Containers.SeccompProfile != parse.SeccompDefaultPath { + securityOpts = append(securityOpts, fmt.Sprintf("seccomp=%s", c.Containers.SeccompProfile)) + } + if apparmor.IsEnabled() && c.Containers.ApparmorProfile != "" { + securityOpts = append(securityOpts, fmt.Sprintf("apparmor=%s", c.Containers.ApparmorProfile)) + } + if selinux.GetEnabled() && !c.Containers.EnableLabeling { + securityOpts = append(securityOpts, fmt.Sprintf("label=%s", selinux.DisableSecOpt()[0])) + } + return securityOpts +} + +// DefaultSysctls +func (c PodmanConfig) DefaultSysctls() []string { + return c.Containers.DefaultSysctls +} + +func (c PodmanConfig) DefaultVolumes() []string { + return c.Containers.Volumes +} + +func (c PodmanConfig) DefaultDevices() []string { + return c.Containers.Devices +} + +func (c PodmanConfig) DefaultDNSServers() []string { + return c.Containers.DNSServers +} + +func (c PodmanConfig) DefaultDNSSearches() []string { + return c.Containers.DNSSearches +} + +func (c PodmanConfig) DefaultDNSOptions() []string { + return c.Containers.DNSOptions +} + +func (c PodmanConfig) DefaultEnv() []string { + return c.Containers.Env +} + +func (c PodmanConfig) DefaultInitPath() string { + return c.Containers.InitPath +} + +func (c PodmanConfig) DefaultIPCNS() string { + return c.Containers.IPCNS } + +func (c PodmanConfig) DefaultPidNS() string { + return c.Containers.PidNS +} + +func (c PodmanConfig) DefaultNetNS() string { + if c.Containers.NetNS == "private" && rootless.IsRootless() { + return "slirp4netns" + } + return c.Containers.NetNS +} + +func (c PodmanConfig) DefaultCgroupNS() string { + return c.Containers.CgroupNS +} + +func (c PodmanConfig) DefaultUTSNS() string { + return c.Containers.UTSNS +} + +func (c PodmanConfig) DefaultShmSize() string { + return c.Containers.ShmSize +} + +func (c PodmanConfig) DefaultUlimits() []string { + return c.Containers.DefaultUlimits +} + +func (c PodmanConfig) DefaultUserNS() string { + if v, found := os.LookupEnv("PODMAN_USERNS"); found { + return v + } + return c.Containers.UserNS +} + +func (c PodmanConfig) DefaultPidsLimit() int64 { + if rootless.IsRootless() { + cgroup2, _ := cgroups.IsCgroup2UnifiedMode() + if cgroup2 { + return c.Containers.PidsLimit + } + } + return sysinfo.GetDefaultPidsLimit() +} + +func (c PodmanConfig) DefaultPidsDescription() string { + return "Tune container pids limit (set 0 for unlimited)" +} + +func (c PodmanConfig) DefaultDetachKeys() string { + return c.Engine.DetachKeys +} + +// TODO: Remove in rootless support PR +// // EngineOptions holds the environment for running the engines +// type EngineOptions struct { +// // Introduced with V2 +// Uri string +// Identities []string +// FlagSet *pflag.FlagSet +// EngineMode EngineMode +// CGroupUsage string +// +// // Introduced with V1 +// CGroupManager string // config.EngineConfig +// CniConfigDir string // config.NetworkConfig.NetworkConfigDir +// ConmonPath string // config.EngineConfig +// DefaultMountsFile string // config.ContainersConfig +// EventsBackend string // config.EngineConfig.EventsLogger +// HooksDir []string // config.EngineConfig +// MaxWorks int +// Namespace string // config.EngineConfig +// Root string // +// Runroot string // config.EngineConfig.StorageConfigRunRootSet?? +// Runtime string // config.EngineConfig.OCIRuntime +// StorageDriver string // config.EngineConfig.StorageConfigGraphDriverNameSet?? +// StorageOpts []string +// Syslog bool +// Trace bool +// NetworkCmdPath string // config.EngineConfig +// +// Config string +// CpuProfile string +// LogLevel string +// TmpDir string // config.EngineConfig +// +// RemoteUserName string // deprecated +// RemoteHost string // deprecated +// VarlinkAddress string // deprecated +// ConnectionName string +// RemoteConfigFilePath string +// Port int // deprecated +// IdentityFile string // deprecated +// IgnoreHosts bool +// } +// +// func NewEngineOptions(opts EngineOptions) (EngineOptions, error) { +// ctnrCfg, err := config.Default() +// if err != nil { +// logrus.Error(err) +// os.Exit(1) +// } +// +// cgroupManager := ctnrCfg.Engine.CgroupManager +// cgroupUsage := `Cgroup manager to use ("cgroupfs"|"systemd")` +// cgroupv2, _ := cgroups.IsCgroup2UnifiedMode() +// cniPluginDir := ctnrCfg.Network.CNIPluginDirs[0] +// +// cfg, err := config.NewConfig("") +// if err != nil { +// logrus.Errorf("Error loading container config %v\n", err) +// os.Exit(1) +// } +// cfg.CheckCgroupsAndAdjustConfig() +// +// if rootless.IsRootless() { +// if !cgroupv2 { +// cgroupManager = "" +// cgroupUsage = "Cgroup manager is not supported in rootless mode" +// } +// cniPluginDir = "" +// } +// +// return EngineOptions{ +// CGroupManager: cgroupManager, +// CGroupUsage: cgroupUsage, +// CniConfigDir: cniPluginDir, +// Config: opts.Config, // TODO: deprecate +// ConmonPath: opts.ConmonPath, +// ConnectionName: opts.ConnectionName, +// CpuProfile: opts.CpuProfile, +// DefaultMountsFile: ctnrCfg.Containers.DefaultMountsFile, +// EngineMode: opts.EngineMode, +// EventsBackend: ctnrCfg.Engine.EventsLogger, +// FlagSet: opts.FlagSet, // TODO: deprecate +// HooksDir: append(ctnrCfg.Engine.HooksDir[:0:0], ctnrCfg.Engine.HooksDir...), +// Identities: append(opts.Identities[:0:0], opts.Identities...), +// IdentityFile: opts.IdentityFile, // TODO: deprecate +// IgnoreHosts: opts.IgnoreHosts, +// LogLevel: opts.LogLevel, +// MaxWorks: opts.MaxWorks, +// Namespace: ctnrCfg.Engine.Namespace, +// NetworkCmdPath: ctnrCfg.Engine.NetworkCmdPath, +// Port: opts.Port, +// RemoteConfigFilePath: opts.RemoteConfigFilePath, +// RemoteHost: opts.RemoteHost, // TODO: deprecate +// RemoteUserName: opts.RemoteUserName, // TODO: deprecate +// Root: opts.Root, +// Runroot: opts.Runroot, +// Runtime: opts.Runtime, +// StorageDriver: opts.StorageDriver, +// StorageOpts: append(opts.StorageOpts[:0:0], opts.StorageOpts...), +// Syslog: opts.Syslog, +// TmpDir: opts.TmpDir, +// Trace: opts.Trace, +// Uri: opts.Uri, +// VarlinkAddress: opts.VarlinkAddress, +// }, nil +// } diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 24b7a9acc..5fdb9a8a6 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -3,33 +3,42 @@ package entities import ( "context" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/specgen" ) type ContainerEngine interface { + Config(ctx context.Context) (*config.Config, error) ContainerAttach(ctx context.Context, nameOrId string, options AttachOptions) error ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error) + ContainerCleanup(ctx context.Context, namesOrIds []string, options ContainerCleanupOptions) ([]*ContainerCleanupReport, error) ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error) ContainerDiff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error) ContainerExec(ctx context.Context, nameOrId string, options ExecOptions) (int, error) ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error) ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error + ContainerInit(ctx context.Context, namesOrIds []string, options ContainerInitOptions) ([]*ContainerInitReport, error) ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error) ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error) ContainerList(ctx context.Context, options ContainerListOptions) ([]ListContainer, error) + ContainerMount(ctx context.Context, nameOrIds []string, options ContainerMountOptions) ([]*ContainerMountReport, error) ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) + ContainerLogs(ctx context.Context, containers []string, options ContainerLogsOptions) error ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error) ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error) ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error) - ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error) ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, error) + ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error) ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error) ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error) + ContainerUnmount(ctx context.Context, nameOrIds []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error) ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) + Events(ctx context.Context, opts EventsOptions) error HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error) + Info(ctx context.Context) (*define.Info, error) PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error) PodExists(ctx context.Context, nameOrId string) (*BoolReport, error) PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error) @@ -42,11 +51,11 @@ type ContainerEngine interface { PodStop(ctx context.Context, namesOrIds []string, options PodStopOptions) ([]*PodStopReport, error) PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error) PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error) + RestService(ctx context.Context, opts ServiceOptions) error + VarlinkService(ctx context.Context, opts ServiceOptions) error VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error) VolumeInspect(ctx context.Context, namesOrIds []string, opts VolumeInspectOptions) ([]*VolumeInspectReport, error) VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error) VolumePrune(ctx context.Context, opts VolumePruneOptions) ([]*VolumePruneReport, error) VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error) - - Info(ctx context.Context) (*define.Info, error) } diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index 16b96e9ef..3a051ab9b 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -2,9 +2,12 @@ package entities import ( "context" + + "github.com/containers/common/pkg/config" ) type ImageEngine interface { + Config(ctx context.Context) (*config.Config, error) Delete(ctx context.Context, nameOrId []string, opts ImageDeleteOptions) (*ImageDeleteReport, error) Diff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error) Exists(ctx context.Context, nameOrId string) (*BoolReport, error) @@ -19,4 +22,5 @@ type ImageEngine interface { Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error Untag(ctx context.Context, nameOrId string, tags []string, options ImageUntagOptions) error + Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error) } diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index bc8a34c13..78ebb8805 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -2,6 +2,7 @@ package entities import ( "net/url" + "time" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" @@ -99,12 +100,12 @@ type ImageDeleteReport struct { type ImageHistoryOptions struct{} type ImageHistoryLayer struct { - ID string `json:"Id"` - Created int64 `json:",omitempty"` - CreatedBy string `json:",omitempty"` - Tags []string `json:",omitempty"` - Size int64 `json:",omitempty"` - Comment string `json:",omitempty"` + ID string `json:"id"` + Created time.Time `json:"created,omitempty"` + CreatedBy string `json:",omitempty"` + Tags []string `json:"tags,omitempty"` + Size int64 `json:"size"` + Comment string `json:"comment,omitempty"` } type ImageHistoryReport struct { @@ -181,6 +182,37 @@ type ImagePushOptions struct { TLSVerify types.OptionalBool } +// ImageSearchOptions are the arguments for searching images. +type ImageSearchOptions struct { + // Authfile is the path to the authentication file. Ignored for remote + // calls. + Authfile string + // Filters for the search results. + Filters []string + // Limit the number of results. + Limit int + // NoTrunc will not truncate the output. + NoTrunc bool + // TLSVerify to enable/disable HTTPS and certificate verification. + TLSVerify types.OptionalBool +} + +// ImageSearchReport is the response from searching images. +type ImageSearchReport struct { + // Index is the image index (e.g., "docker.io" or "quay.io") + Index string + // Name is the canoncical name of the image (e.g., "docker.io/library/alpine"). + Name string + // Description of the image. + Description string + // Stars is the number of stars of the image. + Stars int + // Official indicates if it's an official image. + Official string + // Automated indicates if the image was created by an automated build. + Automated string +} + type ImageListOptions struct { All bool `json:"all" schema:"all"` Filter []string `json:"Filter,omitempty"` diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index cd2e79961..9ca8ff43c 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -1,6 +1,7 @@ package entities import ( + "strings" "time" "github.com/containers/libpod/libpod" @@ -121,7 +122,9 @@ func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) { s.Hostname = p.Hostname s.Labels = p.Labels s.NoInfra = !p.Infra - s.InfraCommand = []string{p.InfraCommand} + if len(p.InfraCommand) > 0 { + s.InfraCommand = strings.Split(p.InfraCommand, " ") + } s.InfraImage = p.InfraImage s.SharedNamespaces = p.Share diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go new file mode 100644 index 000000000..3ddc04293 --- /dev/null +++ b/pkg/domain/entities/system.go @@ -0,0 +1,14 @@ +package entities + +import ( + "time" + + "github.com/spf13/cobra" +) + +// ServiceOptions provides the input for starting an API Service +type ServiceOptions struct { + URI string // Path to unix domain socket service should listen on + Timeout time.Duration // duration of inactivity the service should wait before shutting down + Command *cobra.Command // CLI command provided. Used in V1 code +} diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index 7e35957f4..91ae00764 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -3,6 +3,7 @@ package entities import ( "net" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/specgen" "github.com/containers/storage/pkg/archive" "github.com/cri-o/ocicni/pkg/ocicni" @@ -62,3 +63,12 @@ type DiffOptions struct { type DiffReport struct { Changes []archive.Change } + +type EventsOptions struct { + FromStart bool + EventChan chan *events.Event + Filter []string + Stream bool + Since string + Until string +} diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index ccbe6d4fd..f464df3ac 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -4,23 +4,30 @@ package abi import ( "context" + "fmt" "io/ioutil" + "os" "strconv" "strings" + "sync" "github.com/containers/buildah" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/manifest" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/libpod/logs" "github.com/containers/libpod/pkg/checkpoint" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/infra/abi/terminal" "github.com/containers/libpod/pkg/ps" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/signal" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/specgen/generate" + "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -709,3 +716,186 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } return &report, nil } + +func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error { + if options.Writer == nil { + return errors.New("no io.Writer set for container logs") + } + + var wg sync.WaitGroup + + ctrs, err := getContainersByContext(false, options.Latest, containers, ic.Libpod) + if err != nil { + return err + } + + logOpts := &logs.LogOptions{ + Multi: len(ctrs) > 1, + Details: options.Details, + Follow: options.Follow, + Since: options.Since, + Tail: options.Tail, + Timestamps: options.Timestamps, + UseName: options.Names, + WaitGroup: &wg, + } + + chSize := len(ctrs) * int(options.Tail) + if chSize <= 0 { + chSize = 1 + } + logChannel := make(chan *logs.LogLine, chSize) + + if err := ic.Libpod.Log(ctrs, logOpts, logChannel); err != nil { + return err + } + + go func() { + wg.Wait() + close(logChannel) + }() + + for line := range logChannel { + fmt.Fprintln(options.Writer, line.String(logOpts)) + } + + return nil +} + +func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []string, options entities.ContainerCleanupOptions) ([]*entities.ContainerCleanupReport, error) { + var reports []*entities.ContainerCleanupReport + ctrs, err := getContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, ctr := range ctrs { + var err error + report := entities.ContainerCleanupReport{Id: ctr.ID()} + if options.Remove { + err = ic.Libpod.RemoveContainer(ctx, ctr, false, true) + if err != nil { + report.RmErr = errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID()) + } + } else { + err := ctr.Cleanup(ctx) + if err != nil { + report.CleanErr = errors.Wrapf(err, "failed to cleanup container %v", ctr.ID()) + } + } + + if options.RemoveImage { + _, imageName := ctr.Image() + ctrImage, err := ic.Libpod.ImageRuntime().NewFromLocal(imageName) + if err != nil { + report.RmiErr = err + reports = append(reports, &report) + continue + } + _, err = ic.Libpod.RemoveImage(ctx, ctrImage, false) + report.RmiErr = err + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []string, options entities.ContainerInitOptions) ([]*entities.ContainerInitReport, error) { + var reports []*entities.ContainerInitReport + ctrs, err := getContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, ctr := range ctrs { + report := entities.ContainerInitReport{Id: ctr.ID()} + report.Err = ctr.Init(ctx) + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerMount(ctx context.Context, nameOrIds []string, options entities.ContainerMountOptions) ([]*entities.ContainerMountReport, error) { + if os.Geteuid() != 0 { + if driver := ic.Libpod.StorageConfig().GraphDriverName; driver != "vfs" { + // Do not allow to mount a graphdriver that is not vfs if we are creating the userns as part + // of the mount command. + return nil, fmt.Errorf("cannot mount using driver %s in rootless mode", driver) + } + + became, ret, err := rootless.BecomeRootInUserNS("") + if err != nil { + return nil, err + } + if became { + os.Exit(ret) + } + } + var reports []*entities.ContainerMountReport + ctrs, err := getContainersByContext(options.All, options.Latest, nameOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, ctr := range ctrs { + report := entities.ContainerMountReport{Id: ctr.ID()} + report.Path, report.Err = ctr.Mount() + reports = append(reports, &report) + } + if len(reports) > 0 { + return reports, nil + } + + // No containers were passed, so we send back what is mounted + ctrs, err = getContainersByContext(true, false, []string{}, ic.Libpod) + if err != nil { + return nil, err + } + for _, ctr := range ctrs { + mounted, path, err := ctr.Mounted() + if err != nil { + return nil, err + } + + if mounted { + reports = append(reports, &entities.ContainerMountReport{ + Id: ctr.ID(), + Name: ctr.Name(), + Path: path, + }) + } + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIds []string, options entities.ContainerUnmountOptions) ([]*entities.ContainerUnmountReport, error) { + var reports []*entities.ContainerUnmountReport + ctrs, err := getContainersByContext(options.All, options.Latest, nameOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, ctr := range ctrs { + state, err := ctr.State() + if err != nil { + logrus.Debugf("Error umounting container %s state: %s", ctr.ID(), err.Error()) + continue + } + if state == define.ContainerStateRunning { + logrus.Debugf("Error umounting container %s, is running", ctr.ID()) + continue + } + + report := entities.ContainerUnmountReport{Id: ctr.ID()} + if err := ctr.Unmount(options.Force); err != nil { + if options.All && errors.Cause(err) == storage.ErrLayerNotMounted { + logrus.Debugf("Error umounting container %s, storage.ErrLayerNotMounted", ctr.ID()) + continue + } + report.Err = errors.Wrapf(err, "error unmounting container %s", ctr.ID()) + } + reports = append(reports, &report) + } + return reports, nil +} + +// GetConfig returns a copy of the configuration used by the runtime +func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) { + return ic.Libpod.GetConfig() +} diff --git a/pkg/domain/infra/abi/events.go b/pkg/domain/infra/abi/events.go new file mode 100644 index 000000000..9540a5b96 --- /dev/null +++ b/pkg/domain/infra/abi/events.go @@ -0,0 +1,18 @@ +//+build ABISupport + +package abi + +import ( + "context" + + "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/sirupsen/logrus" +) + +func (ic *ContainerEngine) Events(ctx context.Context, opts entities.EventsOptions) error { + readOpts := events.ReadOptions{FromStart: opts.FromStart, Stream: opts.Stream, Filters: opts.Filter, EventChannel: opts.EventChan, Since: opts.Since, Until: opts.Until} + err := ic.Libpod.Events(readOpts) + logrus.Error(err) + return err +} diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 2edef2723..9467c14d4 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -9,12 +9,14 @@ import ( "os" "strings" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker" dockerarchive "github.com/containers/image/v5/docker/archive" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" libpodImage "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/domain/entities" @@ -27,10 +29,11 @@ import ( ) func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) { - if _, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId); err != nil { - return &entities.BoolReport{}, nil + _, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) + if err != nil && errors.Cause(err) != define.ErrNoSuchImage { + return nil, err } - return &entities.BoolReport{Value: true}, nil + return &entities.BoolReport{Value: err == nil}, nil } func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { @@ -43,7 +46,9 @@ func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entit if err != nil { return &report, errors.Wrapf(err, "unable to query local images") } - + if len(targets) == 0 { + return &report, nil + } if len(targets) > 0 && len(targets) == len(previousTargets) { return &report, errors.New("unable to delete all images; re-run the rmi command again.") } @@ -140,7 +145,7 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entiti func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer { l := entities.ImageHistoryLayer{} l.ID = layer.ID - l.Created = layer.Created.Unix() + l.Created = *layer.Created l.CreatedBy = layer.CreatedBy copy(l.Tags, layer.Tags) l.Size = layer.Size @@ -392,8 +397,10 @@ func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) if err != nil { return nil, errors.Wrap(err, "image loaded but no additional tags were created") } - if err := newImage.TagImage(opts.Name); err != nil { - return nil, errors.Wrapf(err, "error adding %q to image %q", opts.Name, newImage.InputName) + if len(opts.Name) > 0 { + if err := newImage.TagImage(fmt.Sprintf("%s:%s", opts.Name, opts.Tag)); err != nil { + return nil, errors.Wrapf(err, "error adding %q to image %q", opts.Name, newImage.InputName) + } } return &entities.ImageLoadReport{Name: name}, nil } @@ -421,3 +428,43 @@ func (ir *ImageEngine) Diff(_ context.Context, nameOrId string, _ entities.DiffO } return &entities.DiffReport{Changes: changes}, nil } + +func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { + filter, err := image.ParseSearchFilter(opts.Filters) + if err != nil { + return nil, err + } + + searchOpts := image.SearchOptions{ + Authfile: opts.Authfile, + Filter: *filter, + Limit: opts.Limit, + NoTrunc: opts.NoTrunc, + InsecureSkipTLSVerify: opts.TLSVerify, + } + + searchResults, err := image.SearchImages(term, searchOpts) + if err != nil { + return nil, err + } + + // Convert from image.SearchResults to entities.ImageSearchReport. We don't + // want to leak any low-level packages into the remote client, which + // requires converting. + reports := make([]entities.ImageSearchReport, len(searchResults)) + for i := range searchResults { + reports[i].Index = searchResults[i].Index + reports[i].Name = searchResults[i].Name + reports[i].Description = searchResults[i].Index + reports[i].Stars = searchResults[i].Stars + reports[i].Official = searchResults[i].Official + reports[i].Automated = searchResults[i].Automated + } + + return reports, nil +} + +// GetConfig returns a copy of the configuration used by the runtime +func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) { + return ir.Libpod.GetConfig() +} diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index c3e5d59bc..bb637de3e 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -5,13 +5,13 @@ package abi import ( "context" - lpfilters "github.com/containers/libpod/libpod/filters" - "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + lpfilters "github.com/containers/libpod/libpod/filters" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/signal" "github.com/containers/libpod/pkg/specgen" + "github.com/containers/libpod/pkg/specgen/generate" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -246,7 +246,7 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) { podSpec := specgen.NewPodSpecGenerator() opts.ToPodSpecGen(podSpec) - pod, err := podSpec.MakePod(ic.Libpod) + pod, err := generate.MakePod(podSpec, ic.Libpod) if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 8aaa69847..10872144b 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -4,10 +4,241 @@ package abi import ( "context" + "fmt" + "io/ioutil" + "net" + "os" + "strconv" + "strings" + "syscall" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod/define" + api "github.com/containers/libpod/pkg/api/server" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" + iopodman "github.com/containers/libpod/pkg/varlink" + iopodmanAPI "github.com/containers/libpod/pkg/varlinkapi" + "github.com/containers/libpod/utils" + "github.com/containers/libpod/version" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/varlink/go/varlink" ) func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) { return ic.Libpod.Info() } + +func (ic *ContainerEngine) RestService(_ context.Context, opts entities.ServiceOptions) error { + var ( + listener net.Listener + err error + ) + + if opts.URI != "" { + fields := strings.Split(opts.URI, ":") + if len(fields) == 1 { + return errors.Errorf("%s is an invalid socket destination", opts.URI) + } + address := strings.Join(fields[1:], ":") + listener, err = net.Listen(fields[0], address) + if err != nil { + return errors.Wrapf(err, "unable to create socket %s", opts.URI) + } + } + + server, err := api.NewServerWithSettings(ic.Libpod, opts.Timeout, &listener) + if err != nil { + return err + } + defer func() { + if err := server.Shutdown(); err != nil { + logrus.Warnf("Error when stopping API service: %s", err) + } + }() + + err = server.Serve() + _ = listener.Close() + return err +} + +func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error { + var varlinkInterfaces = []*iopodman.VarlinkInterface{ + iopodmanAPI.New(opts.Command, ic.Libpod), + } + + service, err := varlink.NewService( + "Atomic", + "podman", + version.Version, + "https://github.com/containers/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(opts.URI, opts.Timeout); err != nil { + switch err.(type) { + case varlink.ServiceTimeoutError: + logrus.Infof("varlink service expired (use --timeout to increase session time beyond %s ms, 0 means never timeout)", opts.Timeout.String()) + return nil + default: + return errors.Wrapf(err, "unable to start varlink service") + } + } + return nil +} + +func (ic *ContainerEngine) SetupRootless(cmd *cobra.Command) error { + // do it only after podman has already re-execed and running with uid==0. + if os.Geteuid() == 0 { + ownsCgroup, err := cgroups.UserOwnsCurrentSystemdCgroup() + if err != nil { + logrus.Warnf("Failed to detect the owner for the current cgroup: %v", err) + } + if !ownsCgroup { + conf, err := ic.Config(context.Background()) + if err != nil { + return err + } + unitName := fmt.Sprintf("podman-%d.scope", os.Getpid()) + if err := utils.RunUnderSystemdScope(os.Getpid(), "user.slice", unitName); err != nil { + if conf.Engine.CgroupManager == config.SystemdCgroupsManager { + logrus.Warnf("Failed to add podman to systemd sandbox cgroup: %v", err) + } else { + logrus.Debugf("Failed to add podman to systemd sandbox cgroup: %v", err) + } + } + } + } + + if !executeCommandInUserNS(cmd) { + return nil + } + + pausePidPath, err := util.GetRootlessPauseProcessPidPath() + if err != nil { + return errors.Wrapf(err, "could not get pause process pid file path") + } + + became, ret, err := rootless.TryJoinPauseProcess(pausePidPath) + if err != nil { + return err + } + if became { + os.Exit(ret) + } + + // if there is no pid file, try to join existing containers, and create a pause process. + ctrs, err := ic.Libpod.GetRunningContainers() + if err != nil { + logrus.WithError(err).Fatal("") + } + + paths := []string{} + for _, ctr := range ctrs { + paths = append(paths, ctr.Config().ConmonPidFile) + } + + became, ret, err = rootless.TryJoinFromFilePaths(pausePidPath, true, paths) + if err := movePauseProcessToScope(); err != nil { + conf, err := ic.Config(context.Background()) + if err != nil { + return err + } + if conf.Engine.CgroupManager == config.SystemdCgroupsManager { + logrus.Warnf("Failed to add pause process to systemd sandbox cgroup: %v", err) + } else { + logrus.Debugf("Failed to add pause process to systemd sandbox cgroup: %v", err) + } + } + if err != nil { + logrus.WithError(err).Fatal("") + } + if became { + os.Exit(ret) + } + return nil +} + +// Most podman commands when run in rootless mode, need to be executed in the +// users usernamespace. This function is updated with a list of commands that +// should NOT be run within the user namespace. +func executeCommandInUserNS(cmd *cobra.Command) bool { + return os.Geteuid() == 0 + // if os.Geteuid() == 0 { + // return false + // } + // switch cmd { + // case _migrateCommand, + // _mountCommand, + // _renumberCommand, + // _searchCommand, + // _versionCommand: + // return false + // } + // return true +} + +func movePauseProcessToScope() error { + pausePidPath, err := util.GetRootlessPauseProcessPidPath() + if err != nil { + return errors.Wrapf(err, "could not get pause process pid file path") + } + + data, err := ioutil.ReadFile(pausePidPath) + if err != nil { + return errors.Wrapf(err, "cannot read pause pid file") + } + pid, err := strconv.ParseUint(string(data), 10, 0) + if err != nil { + return errors.Wrapf(err, "cannot parse pid file %s", pausePidPath) + } + + return utils.RunUnderSystemdScope(int(pid), "user.slice", "podman-pause.scope") +} + +func setRLimits() error { // nolint:deadcode,unused + rlimits := new(syscall.Rlimit) + rlimits.Cur = 1048576 + rlimits.Max = 1048576 + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + return errors.Wrapf(err, "error getting rlimits") + } + rlimits.Cur = rlimits.Max + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + return errors.Wrapf(err, "error setting new rlimits") + } + } + return nil +} + +func setUMask() { // nolint:deadcode,unused + // Be sure we can create directories with 0755 mode. + syscall.Umask(0022) +} + +// checkInput can be used to verify any of the globalopt values +func checkInput() error { // nolint:deadcode,unused + return nil +} + +// func getCNIPluginsDir() string { +// if rootless.IsRootless() { +// return "" +// } +// +// return registry.PodmanOptions.Network.CNIPluginDirs[0] +// } diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go index f11026571..0dbcf2ad2 100644 --- a/pkg/domain/infra/runtime_abi.go +++ b/pkg/domain/infra/runtime_abi.go @@ -12,7 +12,7 @@ import ( ) // NewContainerEngine factory provides a libpod runtime for container-related operations -func NewContainerEngine(facts entities.EngineOptions) (entities.ContainerEngine, error) { +func NewContainerEngine(facts entities.PodmanConfig) (entities.ContainerEngine, error) { switch facts.EngineMode { case entities.ABIMode: r, err := NewLibpodRuntime(facts.FlagSet, facts) @@ -25,7 +25,7 @@ func NewContainerEngine(facts entities.EngineOptions) (entities.ContainerEngine, } // NewContainerEngine factory provides a libpod runtime for image-related operations -func NewImageEngine(facts entities.EngineOptions) (entities.ImageEngine, error) { +func NewImageEngine(facts entities.PodmanConfig) (entities.ImageEngine, error) { switch facts.EngineMode { case entities.ABIMode: r, err := NewLibpodImageRuntime(facts.FlagSet, facts) diff --git a/pkg/domain/infra/runtime_image_proxy.go b/pkg/domain/infra/runtime_image_proxy.go index befc66b9a..45c5425a3 100644 --- a/pkg/domain/infra/runtime_image_proxy.go +++ b/pkg/domain/infra/runtime_image_proxy.go @@ -12,7 +12,7 @@ import ( // ContainerEngine Image Proxy will be EOL'ed after podmanV2 is separated from libpod repo -func NewLibpodImageRuntime(flags *pflag.FlagSet, opts entities.EngineOptions) (entities.ImageEngine, error) { +func NewLibpodImageRuntime(flags *pflag.FlagSet, opts entities.PodmanConfig) (entities.ImageEngine, error) { r, err := GetRuntime(context.Background(), flags, opts) if err != nil { return nil, err diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index d59759707..9cf374e2e 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -1,3 +1,5 @@ +// build: ABISupport + package infra import ( @@ -22,68 +24,70 @@ type engineOpts struct { migrate bool noStore bool withFDS bool - flags entities.EngineOptions + config entities.PodmanConfig } // GetRuntimeMigrate gets a libpod runtime that will perform a migration of existing containers -func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, ef entities.EngineOptions, newRuntime string) (*libpod.Runtime, error) { +func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig, newRuntime string) (*libpod.Runtime, error) { return getRuntime(ctx, fs, &engineOpts{ name: newRuntime, renumber: false, migrate: true, noStore: false, withFDS: true, - flags: ef, + config: cfg, }) } // GetRuntimeDisableFDs gets a libpod runtime that will disable sd notify -func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, ef entities.EngineOptions) (*libpod.Runtime, error) { +func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig) (*libpod.Runtime, error) { return getRuntime(ctx, fs, &engineOpts{ renumber: false, migrate: false, noStore: false, withFDS: false, - flags: ef, + config: cfg, }) } // GetRuntimeRenumber gets a libpod runtime that will perform a lock renumber -func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, ef entities.EngineOptions) (*libpod.Runtime, error) { +func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig) (*libpod.Runtime, error) { return getRuntime(ctx, fs, &engineOpts{ renumber: true, migrate: false, noStore: false, withFDS: true, - flags: ef, + config: cfg, }) } // GetRuntime generates a new libpod runtime configured by command line options -func GetRuntime(ctx context.Context, flags *flag.FlagSet, ef entities.EngineOptions) (*libpod.Runtime, error) { +func GetRuntime(ctx context.Context, flags *flag.FlagSet, cfg entities.PodmanConfig) (*libpod.Runtime, error) { return getRuntime(ctx, flags, &engineOpts{ renumber: false, migrate: false, noStore: false, withFDS: true, - flags: ef, + config: cfg, }) } // GetRuntimeNoStore generates a new libpod runtime configured by command line options -func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, ef entities.EngineOptions) (*libpod.Runtime, error) { +func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig) (*libpod.Runtime, error) { return getRuntime(ctx, fs, &engineOpts{ renumber: false, migrate: false, noStore: true, withFDS: true, - flags: ef, + config: cfg, }) } func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpod.Runtime, error) { options := []libpod.RuntimeOption{} storageOpts := storage.StoreOptions{} + cfg := opts.config + storageSet := false uidmapFlag := fs.Lookup("uidmap") @@ -109,25 +113,25 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo if fs.Changed("root") { storageSet = true - storageOpts.GraphRoot = opts.flags.Root + storageOpts.GraphRoot = cfg.Engine.StaticDir } if fs.Changed("runroot") { storageSet = true - storageOpts.RunRoot = opts.flags.Runroot + storageOpts.RunRoot = cfg.Runroot } if len(storageOpts.RunRoot) > 50 { return nil, errors.New("the specified runroot is longer than 50 characters") } if fs.Changed("storage-driver") { storageSet = true - storageOpts.GraphDriverName = opts.flags.StorageDriver + storageOpts.GraphDriverName = cfg.StorageDriver // Overriding the default storage driver caused GraphDriverOptions from storage.conf to be ignored storageOpts.GraphDriverOptions = []string{} } // This should always be checked after storage-driver is checked - if len(opts.flags.StorageOpts) > 0 { + if len(cfg.StorageOpts) > 0 { storageSet = true - storageOpts.GraphDriverOptions = opts.flags.StorageOpts + storageOpts.GraphDriverOptions = cfg.StorageOpts } if opts.migrate { options = append(options, libpod.WithMigrate()) @@ -151,30 +155,30 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo // TODO CLI flags for image config? // TODO CLI flag for signature policy? - if len(opts.flags.Namespace) > 0 { - options = append(options, libpod.WithNamespace(opts.flags.Namespace)) + if len(cfg.Engine.Namespace) > 0 { + options = append(options, libpod.WithNamespace(cfg.Engine.Namespace)) } if fs.Changed("runtime") { - options = append(options, libpod.WithOCIRuntime(opts.flags.Runtime)) + options = append(options, libpod.WithOCIRuntime(cfg.RuntimePath)) } if fs.Changed("conmon") { - options = append(options, libpod.WithConmonPath(opts.flags.ConmonPath)) + options = append(options, libpod.WithConmonPath(cfg.ConmonPath)) } if fs.Changed("tmpdir") { - options = append(options, libpod.WithTmpDir(opts.flags.TmpDir)) + options = append(options, libpod.WithTmpDir(cfg.Engine.TmpDir)) } if fs.Changed("network-cmd-path") { - options = append(options, libpod.WithNetworkCmdPath(opts.flags.NetworkCmdPath)) + options = append(options, libpod.WithNetworkCmdPath(cfg.Engine.NetworkCmdPath)) } if fs.Changed("events-backend") { - options = append(options, libpod.WithEventsLogger(opts.flags.EventsBackend)) + options = append(options, libpod.WithEventsLogger(cfg.Engine.EventsLogger)) } if fs.Changed("cgroup-manager") { - options = append(options, libpod.WithCgroupManager(opts.flags.CGroupManager)) + options = append(options, libpod.WithCgroupManager(cfg.Engine.CgroupManager)) } else { unified, err := cgroups.IsCgroup2UnifiedMode() if err != nil { @@ -189,13 +193,13 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo // TODO flag to set libpod tmp dir? if fs.Changed("cni-config-dir") { - options = append(options, libpod.WithCNIConfigDir(opts.flags.CniConfigDir)) + options = append(options, libpod.WithCNIConfigDir(cfg.Network.NetworkConfigDir)) } if fs.Changed("default-mounts-file") { - options = append(options, libpod.WithDefaultMountsFile(opts.flags.DefaultMountsFile)) + options = append(options, libpod.WithDefaultMountsFile(cfg.Containers.DefaultMountsFile)) } if fs.Changed("hooks-dir") { - options = append(options, libpod.WithHooksDir(opts.flags.HooksDir...)) + options = append(options, libpod.WithHooksDir(cfg.Engine.HooksDir...)) } // TODO flag to set CNI plugins dir? diff --git a/pkg/domain/infra/runtime_proxy.go b/pkg/domain/infra/runtime_proxy.go index 2e38c74b9..18f716ea0 100644 --- a/pkg/domain/infra/runtime_proxy.go +++ b/pkg/domain/infra/runtime_proxy.go @@ -12,7 +12,7 @@ import ( // ContainerEngine Proxy will be EOL'ed after podmanV2 is separated from libpod repo -func NewLibpodRuntime(flags *flag.FlagSet, opts entities.EngineOptions) (entities.ContainerEngine, error) { +func NewLibpodRuntime(flags *flag.FlagSet, opts entities.PodmanConfig) (entities.ContainerEngine, error) { r, err := GetRuntime(context.Background(), flags, opts) if err != nil { return nil, err diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go index dc04b4e53..129fdeb2c 100644 --- a/pkg/domain/infra/runtime_tunnel.go +++ b/pkg/domain/infra/runtime_tunnel.go @@ -11,7 +11,7 @@ import ( "github.com/containers/libpod/pkg/domain/infra/tunnel" ) -func NewContainerEngine(facts entities.EngineOptions) (entities.ContainerEngine, error) { +func NewContainerEngine(facts entities.PodmanConfig) (entities.ContainerEngine, error) { switch facts.EngineMode { case entities.ABIMode: return nil, fmt.Errorf("direct runtime not supported") @@ -23,7 +23,7 @@ func NewContainerEngine(facts entities.EngineOptions) (entities.ContainerEngine, } // NewImageEngine factory provides a libpod runtime for image-related operations -func NewImageEngine(facts entities.EngineOptions) (entities.ImageEngine, error) { +func NewImageEngine(facts entities.PodmanConfig) (entities.ImageEngine, error) { switch facts.EngineMode { case entities.ABIMode: return nil, fmt.Errorf("direct image runtime not supported") diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index b22c6e3ba..05b62efcf 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -5,6 +5,7 @@ import ( "io" "os" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/bindings/containers" @@ -305,6 +306,11 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG return &entities.ContainerCreateReport{Id: response.ID}, nil } +func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error { + // The endpoint is not ready yet and requires some more work. + return errors.New("not implemented yet") +} + func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error { return errors.New("not implemented") } @@ -329,3 +335,35 @@ func (ic *ContainerEngine) ContainerDiff(ctx context.Context, nameOrId string, _ changes, err := containers.Diff(ic.ClientCxt, nameOrId) return &entities.DiffReport{Changes: changes}, err } + +func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []string, options entities.ContainerCleanupOptions) ([]*entities.ContainerCleanupReport, error) { + return nil, errors.New("not implemented") +} + +func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []string, options entities.ContainerInitOptions) ([]*entities.ContainerInitReport, error) { + var reports []*entities.ContainerInitReport + ctrs, err := getContainersByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, ctr := range ctrs { + err := containers.ContainerInit(ic.ClientCxt, ctr.ID) + reports = append(reports, &entities.ContainerInitReport{ + Err: err, + Id: ctr.ID, + }) + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerMount(ctx context.Context, nameOrIds []string, options entities.ContainerMountOptions) ([]*entities.ContainerMountReport, error) { + return nil, errors.New("mounting containers is not supported for remote clients") +} + +func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIds []string, options entities.ContainerUnmountOptions) ([]*entities.ContainerUnmountReport, error) { + return nil, errors.New("unmounting containers is not supported for remote clients") +} + +func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) { + return config.Default() +} diff --git a/pkg/domain/infra/tunnel/events.go b/pkg/domain/infra/tunnel/events.go new file mode 100644 index 000000000..46d88341a --- /dev/null +++ b/pkg/domain/infra/tunnel/events.go @@ -0,0 +1,31 @@ +package tunnel + +import ( + "context" + "strings" + + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings/system" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" +) + +func (ic *ContainerEngine) Events(ctx context.Context, opts entities.EventsOptions) error { + filters := make(map[string][]string) + if len(opts.Filter) > 0 { + for _, filter := range opts.Filter { + split := strings.Split(filter, "=") + if len(split) < 2 { + return errors.Errorf("invalid filter %q", filter) + } + filters[split[0]] = append(filters[split[0]], strings.Join(split[1:], "=")) + } + } + binChan := make(chan handlers.Event) + go func() { + for e := range binChan { + opts.EventChan <- e.ToLibpodEvent() + } + }() + return system.Events(ic.ClientCxt, binChan, nil, &opts.Since, &opts.Until, filters) +} diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 66abd7f47..7d40e0327 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" images "github.com/containers/libpod/pkg/bindings/images" "github.com/containers/libpod/pkg/domain/entities" @@ -250,3 +251,11 @@ func (ir *ImageEngine) Diff(ctx context.Context, nameOrId string, _ entities.Dif } return &entities.DiffReport{Changes: changes}, nil } + +func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { + return images.Search(ir.ClientCxt, term, opts) +} + +func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) { + return config.Default() +} diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go index 5d6346234..7c7a55c05 100644 --- a/pkg/domain/infra/tunnel/system.go +++ b/pkg/domain/infra/tunnel/system.go @@ -2,11 +2,21 @@ package tunnel import ( "context" + "errors" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/bindings/system" + "github.com/containers/libpod/pkg/domain/entities" ) func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) { return system.Info(ic.ClientCxt) } + +func (ic *ContainerEngine) RestService(_ context.Context, _ entities.ServiceOptions) error { + panic(errors.New("rest service is not supported when tunneling")) +} + +func (ic *ContainerEngine) VarlinkService(_ context.Context, _ entities.ServiceOptions) error { + panic(errors.New("varlink service is not supported when tunneling")) +} diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 6643bfbbf..da52a7217 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -288,6 +288,7 @@ static void __attribute__((constructor)) init() char *cwd = getcwd (NULL, 0); char uid_fmt[16]; char gid_fmt[16]; + size_t len; if (cwd == NULL) { @@ -295,13 +296,13 @@ static void __attribute__((constructor)) init() _exit (EXIT_FAILURE); } - if (strlen (xdg_runtime_dir) >= PATH_MAX - strlen (suffix)) + len = snprintf (path, PATH_MAX, "%s%s", xdg_runtime_dir, suffix); + if (len >= PATH_MAX) { fprintf (stderr, "invalid value for XDG_RUNTIME_DIR: %s", strerror (ENAMETOOLONG)); exit (EXIT_FAILURE); } - sprintf (path, "%s%s", xdg_runtime_dir, suffix); fd = open (path, O_RDONLY); if (fd < 0) { diff --git a/pkg/signal/signal_linux.go b/pkg/signal/signal_linux.go index 76ab16ec7..e6e0f1ca1 100644 --- a/pkg/signal/signal_linux.go +++ b/pkg/signal/signal_linux.go @@ -129,14 +129,15 @@ func StopCatch(sigc chan os.Signal) { // ParseSignalNameOrNumber translates a string to a valid syscall signal. Input // can be a name or number representation i.e. "KILL" "9" func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) { - s, err := ParseSignal(rawSignal) + basename := strings.TrimPrefix(rawSignal, "-") + s, err := ParseSignal(basename) if err == nil { return s, nil } for k, v := range signalMap { - if k == strings.ToUpper(rawSignal) { + if k == strings.ToUpper(basename) { return v, nil } } - return -1, fmt.Errorf("invalid signal: %s", rawSignal) + return -1, fmt.Errorf("invalid signal: %s", basename) } diff --git a/pkg/specgen/config_linux_nocgo.go b/pkg/specgen/config_linux_nocgo.go deleted file mode 100644 index fc0c58c37..000000000 --- a/pkg/specgen/config_linux_nocgo.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build linux,!cgo - -package specgen - -import ( - spec "github.com/opencontainers/runtime-spec/specs-go" -) - -func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { - return nil, nil -} diff --git a/pkg/specgen/config_linux_cgo.go b/pkg/specgen/generate/config_linux_cgo.go index ef6c6e951..b06ef5c9a 100644 --- a/pkg/specgen/config_linux_cgo.go +++ b/pkg/specgen/generate/config_linux_cgo.go @@ -1,6 +1,6 @@ // +build linux,cgo -package specgen +package generate import ( "context" @@ -8,13 +8,14 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/seccomp" + "github.com/containers/libpod/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" goSeccomp "github.com/seccomp/containers-golang" "github.com/sirupsen/logrus" ) -func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { +func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { var seccompConfig *spec.LinuxSeccomp var err error scp, err := seccomp.LookupPolicy(s.SeccompPolicy) diff --git a/pkg/specgen/generate/config_linux_nocgo.go b/pkg/specgen/generate/config_linux_nocgo.go new file mode 100644 index 000000000..fc8ed206d --- /dev/null +++ b/pkg/specgen/generate/config_linux_nocgo.go @@ -0,0 +1,14 @@ +// +build linux,!cgo + +package generate + +import ( + "errors" + + "github.com/containers/libpod/pkg/specgen" + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +func (s *specgen.SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { + return nil, errors.New("not implemented") +} diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index aad59a861..264e0ff8e 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -40,7 +40,7 @@ func MakeContainer(rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Contai options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName)) - runtimeSpec, err := s.ToOCISpec(rt, newImage) + runtimeSpec, err := SpecGenToOCI(s, rt, newImage) if err != nil { return nil, err } @@ -80,7 +80,15 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib options = append(options, libpod.WithUserVolumes(destinations)) if len(s.Volumes) != 0 { - options = append(options, libpod.WithNamedVolumes(s.Volumes)) + var volumes []*libpod.ContainerNamedVolume + for _, v := range s.Volumes { + volumes = append(volumes, &libpod.ContainerNamedVolume{ + Name: v.Name, + Dest: v.Dest, + Options: v.Options, + }) + } + options = append(options, libpod.WithNamedVolumes(volumes)) } if len(s.Command) != 0 { @@ -115,7 +123,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib options = append(options, libpod.WithPrivileged(s.Privileged)) // Get namespace related options - namespaceOptions, err := s.GenerateNamespaceContainerOpts(rt) + namespaceOptions, err := GenerateNamespaceContainerOpts(s, rt) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go new file mode 100644 index 000000000..cdd7d86da --- /dev/null +++ b/pkg/specgen/generate/namespaces.go @@ -0,0 +1,417 @@ +package generate + +import ( + "os" + + "github.com/containers/common/pkg/capabilities" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/specgen" + "github.com/cri-o/ocicni/pkg/ocicni" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func GenerateNamespaceContainerOpts(s *specgen.SpecGenerator, rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) { + var portBindings []ocicni.PortMapping + options := make([]libpod.CtrCreateOption, 0) + + // Cgroups + switch { + case s.CgroupNS.IsPrivate(): + ns := s.CgroupNS.Value + if _, err := os.Stat(ns); err != nil { + return nil, err + } + case s.CgroupNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.CgroupNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.CgroupNS.Value) + } + options = append(options, libpod.WithCgroupNSFrom(connectedCtr)) + // TODO + //default: + // return nil, errors.New("cgroup name only supports private and container") + } + + if s.CgroupParent != "" { + options = append(options, libpod.WithCgroupParent(s.CgroupParent)) + } + + if s.CgroupsMode != "" { + options = append(options, libpod.WithCgroupsMode(s.CgroupsMode)) + } + + // ipc + switch { + case s.IpcNS.IsHost(): + options = append(options, libpod.WithShmDir("/dev/shm")) + case s.IpcNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.IpcNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.IpcNS.Value) + } + options = append(options, libpod.WithIPCNSFrom(connectedCtr)) + options = append(options, libpod.WithShmDir(connectedCtr.ShmDir())) + } + + // pid + if s.PidNS.IsContainer() { + connectedCtr, err := rt.LookupContainer(s.PidNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.PidNS.Value) + } + options = append(options, libpod.WithPIDNSFrom(connectedCtr)) + } + + // uts + switch { + case s.UtsNS.IsPod(): + connectedPod, err := rt.LookupPod(s.UtsNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "pod %q not found", s.UtsNS.Value) + } + options = append(options, libpod.WithUTSNSFromPod(connectedPod)) + case s.UtsNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.UtsNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.UtsNS.Value) + } + + options = append(options, libpod.WithUTSNSFrom(connectedCtr)) + } + + if s.UseImageHosts { + options = append(options, libpod.WithUseImageHosts()) + } else if len(s.HostAdd) > 0 { + options = append(options, libpod.WithHosts(s.HostAdd)) + } + + // User + + switch { + case s.UserNS.IsPath(): + ns := s.UserNS.Value + if ns == "" { + return nil, errors.Errorf("invalid empty user-defined user namespace") + } + _, err := os.Stat(ns) + if err != nil { + return nil, err + } + if s.IDMappings != nil { + options = append(options, libpod.WithIDMappings(*s.IDMappings)) + } + case s.UserNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.UserNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.UserNS.Value) + } + options = append(options, libpod.WithUserNSFrom(connectedCtr)) + default: + if s.IDMappings != nil { + options = append(options, libpod.WithIDMappings(*s.IDMappings)) + } + } + + options = append(options, libpod.WithUser(s.User)) + options = append(options, libpod.WithGroups(s.Groups)) + + if len(s.PortMappings) > 0 { + portBindings = s.PortMappings + } + + switch { + case s.NetNS.IsPath(): + ns := s.NetNS.Value + if ns == "" { + return nil, errors.Errorf("invalid empty user-defined network namespace") + } + _, err := os.Stat(ns) + if err != nil { + return nil, err + } + case s.NetNS.IsContainer(): + connectedCtr, err := rt.LookupContainer(s.NetNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", s.NetNS.Value) + } + options = append(options, libpod.WithNetNSFrom(connectedCtr)) + case !s.NetNS.IsHost() && s.NetNS.NSMode != specgen.NoNetwork: + postConfigureNetNS := !s.UserNS.IsHost() + options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(s.NetNS.NSMode), s.CNINetworks)) + } + + if len(s.DNSSearch) > 0 { + options = append(options, libpod.WithDNSSearch(s.DNSSearch)) + } + if len(s.DNSServer) > 0 { + // TODO I'm not sure how we are going to handle this given the input + if len(s.DNSServer) == 1 { //&& strings.ToLower(s.DNSServer[0].) == "none" { + options = append(options, libpod.WithUseImageResolvConf()) + } else { + var dnsServers []string + for _, d := range s.DNSServer { + dnsServers = append(dnsServers, d.String()) + } + options = append(options, libpod.WithDNS(dnsServers)) + } + } + if len(s.DNSOption) > 0 { + options = append(options, libpod.WithDNSOption(s.DNSOption)) + } + if s.StaticIP != nil { + options = append(options, libpod.WithStaticIP(*s.StaticIP)) + } + + if s.StaticMAC != nil { + options = append(options, libpod.WithStaticMAC(*s.StaticMAC)) + } + return options, nil +} + +func pidConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { + if s.PidNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value) + } + if s.PidNS.IsHost() { + return g.RemoveLinuxNamespace(string(spec.PIDNamespace)) + } + if s.PidNS.IsContainer() { + logrus.Debugf("using container %s pidmode", s.PidNS.Value) + } + if s.PidNS.IsPod() { + logrus.Debug("using pod pidmode") + } + return nil +} + +func utsConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, runtime *libpod.Runtime) error { + hostname := s.Hostname + var err error + if hostname == "" { + switch { + case s.UtsNS.IsContainer(): + utsCtr, err := runtime.LookupContainer(s.UtsNS.Value) + if err != nil { + return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value) + } + hostname = utsCtr.Hostname() + case s.NetNS.IsHost() || s.UtsNS.IsHost(): + hostname, err = os.Hostname() + if err != nil { + return errors.Wrap(err, "unable to retrieve hostname of the host") + } + default: + logrus.Debug("No hostname set; container's hostname will default to runtime default") + } + } + g.RemoveHostname() + if s.Hostname != "" || !s.UtsNS.IsHost() { + // Set the hostname in the OCI configuration only + // if specified by the user or if we are creating + // a new UTS namespace. + g.SetHostname(hostname) + } + g.AddProcessEnv("HOSTNAME", hostname) + + if s.UtsNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value) + } + if s.UtsNS.IsHost() { + return g.RemoveLinuxNamespace(string(spec.UTSNamespace)) + } + if s.UtsNS.IsContainer() { + logrus.Debugf("using container %s utsmode", s.UtsNS.Value) + } + return nil +} + +func ipcConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { + if s.IpcNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value) + } + if s.IpcNS.IsHost() { + return g.RemoveLinuxNamespace(s.IpcNS.Value) + } + if s.IpcNS.IsContainer() { + logrus.Debugf("Using container %s ipcmode", s.IpcNS.Value) + } + return nil +} + +func cgroupConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { + if s.CgroupNS.IsPath() { + return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value) + } + if s.CgroupNS.IsHost() { + return g.RemoveLinuxNamespace(s.CgroupNS.Value) + } + if s.CgroupNS.IsPrivate() { + return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "") + } + if s.CgroupNS.IsContainer() { + logrus.Debugf("Using container %s cgroup mode", s.CgroupNS.Value) + } + return nil +} + +func networkConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { + switch { + case s.NetNS.IsHost(): + logrus.Debug("Using host netmode") + if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { + return err + } + + case s.NetNS.NSMode == specgen.NoNetwork: + logrus.Debug("Using none netmode") + case s.NetNS.NSMode == specgen.Bridge: + logrus.Debug("Using bridge netmode") + case s.NetNS.IsContainer(): + logrus.Debugf("using container %s netmode", s.NetNS.Value) + case s.NetNS.IsPath(): + logrus.Debug("Using ns netmode") + if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil { + return err + } + case s.NetNS.IsPod(): + logrus.Debug("Using pod netmode, unless pod is not sharing") + case s.NetNS.NSMode == specgen.Slirp: + logrus.Debug("Using slirp4netns netmode") + default: + return errors.Errorf("unknown network mode") + } + + if g.Config.Annotations == nil { + g.Config.Annotations = make(map[string]string) + } + + if s.PublishImagePorts { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue + } else { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse + } + + return nil +} + +func userConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { + if s.UserNS.IsPath() { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); err != nil { + return err + } + // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping + g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) + g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1)) + } + + if s.IDMappings != nil { + if (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) && !s.UserNS.IsHost() { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return err + } + } + for _, uidmap := range s.IDMappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + for _, gidmap := range s.IDMappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } + } + return nil +} + +func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image) error { + // HANDLE CAPABILITIES + // NOTE: Must happen before SECCOMP + if s.Privileged { + g.SetupPrivileged(true) + } + + useNotRoot := func(user string) bool { + if user == "" || user == "root" || user == "0" { + return false + } + return true + } + configSpec := g.Config + var err error + var caplist []string + bounding := configSpec.Process.Capabilities.Bounding + if useNotRoot(s.User) { + configSpec.Process.Capabilities.Bounding = caplist + } + caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop) + if err != nil { + return err + } + + configSpec.Process.Capabilities.Bounding = caplist + configSpec.Process.Capabilities.Permitted = caplist + configSpec.Process.Capabilities.Inheritable = caplist + configSpec.Process.Capabilities.Effective = caplist + configSpec.Process.Capabilities.Ambient = caplist + if useNotRoot(s.User) { + caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop) + if err != nil { + return err + } + } + configSpec.Process.Capabilities.Bounding = caplist + + // HANDLE SECCOMP + if s.SeccompProfilePath != "unconfined" { + seccompConfig, err := getSeccompConfig(s, configSpec, newImage) + if err != nil { + return err + } + configSpec.Linux.Seccomp = seccompConfig + } + + // Clear default Seccomp profile from Generator for privileged containers + if s.SeccompProfilePath == "unconfined" || s.Privileged { + configSpec.Linux.Seccomp = nil + } + + g.SetRootReadonly(s.ReadOnlyFilesystem) + for sysctlKey, sysctlVal := range s.Sysctl { + g.AddLinuxSysctl(sysctlKey, sysctlVal) + } + + return nil +} + +// GetNamespaceOptions transforms a slice of kernel namespaces +// into a slice of pod create options. Currently, not all +// kernel namespaces are supported, and they will be returned in an error +func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) { + var options []libpod.PodCreateOption + var erroredOptions []libpod.PodCreateOption + for _, toShare := range ns { + switch toShare { + case "cgroup": + options = append(options, libpod.WithPodCgroups()) + case "net": + options = append(options, libpod.WithPodNet()) + case "mnt": + return erroredOptions, errors.Errorf("Mount sharing functionality not supported on pod level") + case "pid": + options = append(options, libpod.WithPodPID()) + case "user": + return erroredOptions, errors.Errorf("User sharing functionality not supported on pod level") + case "ipc": + options = append(options, libpod.WithPodIPC()) + case "uts": + options = append(options, libpod.WithPodUTS()) + case "": + case "none": + return erroredOptions, nil + default: + return erroredOptions, errors.Errorf("Invalid kernel namespace to share: %s. Options are: net, pid, ipc, uts or none", toShare) + } + } + return options, nil +} diff --git a/pkg/specgen/oci.go b/pkg/specgen/generate/oci.go index 0756782b4..4bc4d2327 100644 --- a/pkg/specgen/oci.go +++ b/pkg/specgen/generate/oci.go @@ -1,4 +1,4 @@ -package specgen +package generate import ( "strings" @@ -7,11 +7,12 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" createconfig "github.com/containers/libpod/pkg/spec" + "github.com/containers/libpod/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" ) -func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) { +func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) { var ( inUserNS bool ) @@ -183,32 +184,32 @@ func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*s // NAMESPACES - if err := s.pidConfigureGenerator(&g); err != nil { + if err := pidConfigureGenerator(s, &g); err != nil { return nil, err } - if err := s.userConfigureGenerator(&g); err != nil { + if err := userConfigureGenerator(s, &g); err != nil { return nil, err } - if err := s.networkConfigureGenerator(&g); err != nil { + if err := networkConfigureGenerator(s, &g); err != nil { return nil, err } - if err := s.utsConfigureGenerator(&g, rt); err != nil { + if err := utsConfigureGenerator(s, &g, rt); err != nil { return nil, err } - if err := s.ipcConfigureGenerator(&g); err != nil { + if err := ipcConfigureGenerator(s, &g); err != nil { return nil, err } - if err := s.cgroupConfigureGenerator(&g); err != nil { + if err := cgroupConfigureGenerator(s, &g); err != nil { return nil, err } configSpec := g.Config - if err := s.securityConfigureGenerator(&g, newImage); err != nil { + if err := securityConfigureGenerator(s, &g, newImage); err != nil { return nil, err } diff --git a/pkg/specgen/pod_create.go b/pkg/specgen/generate/pod_create.go index 06aa24e22..292f9b155 100644 --- a/pkg/specgen/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -1,31 +1,31 @@ -package specgen +package generate import ( "context" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/specgen" "github.com/sirupsen/logrus" ) -func (p *PodSpecGenerator) MakePod(rt *libpod.Runtime) (*libpod.Pod, error) { - if err := p.validate(); err != nil { +func MakePod(p *specgen.PodSpecGenerator, rt *libpod.Runtime) (*libpod.Pod, error) { + if err := p.Validate(); err != nil { return nil, err } - options, err := p.createPodOptions() + options, err := createPodOptions(p) if err != nil { return nil, err } return rt.NewPod(context.Background(), options...) } -func (p *PodSpecGenerator) createPodOptions() ([]libpod.PodCreateOption, error) { +func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, error) { var ( options []libpod.PodCreateOption ) if !p.NoInfra { options = append(options, libpod.WithInfraContainer()) - nsOptions, err := shared.GetNamespaceOptions(p.SharedNamespaces) + nsOptions, err := GetNamespaceOptions(p.SharedNamespaces) if err != nil { return nil, err } @@ -62,9 +62,9 @@ func (p *PodSpecGenerator) createPodOptions() ([]libpod.PodCreateOption, error) options = append(options, libpod.WithPodUseImageResolvConf()) } switch p.NetNS.NSMode { - case Bridge: + case specgen.Bridge: logrus.Debugf("Pod using default network mode") - case Host: + case specgen.Host: logrus.Debugf("Pod will use host networking") options = append(options, libpod.WithPodHostNetwork()) default: diff --git a/pkg/specgen/storage.go b/pkg/specgen/generate/storage.go index 1b903f608..c9a36ed46 100644 --- a/pkg/specgen/storage.go +++ b/pkg/specgen/generate/storage.go @@ -1,4 +1,4 @@ -package specgen +package generate //nolint @@ -8,9 +8,9 @@ import ( "path/filepath" "strings" - "github.com/containers/libpod/libpod" - "github.com/containers/buildah/pkg/parse" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -38,7 +38,7 @@ var ( // TODO: Named volume options - should we default to rprivate? It bakes into a // bind mount under the hood... // TODO: handle options parsing/processing via containers/storage/pkg/mount -func (s *SpecGenerator) parseVolumes(mounts, volMounts, tmpMounts []string) error { //nolint +func parseVolumes(s *specgen.SpecGenerator, mounts, volMounts, tmpMounts []string) error { //nolint // TODO this needs to come from the image and erquires a runtime diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 2a7bb3495..2ef5bc229 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -1,16 +1,7 @@ package specgen import ( - "os" - - "github.com/containers/common/pkg/capabilities" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/image" - "github.com/cri-o/ocicni/pkg/ocicni" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) type NamespaceMode string @@ -105,373 +96,3 @@ func (n *Namespace) validate() error { } return nil } - -func (s *SpecGenerator) GenerateNamespaceContainerOpts(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) { - var portBindings []ocicni.PortMapping - options := make([]libpod.CtrCreateOption, 0) - - // Cgroups - switch { - case s.CgroupNS.IsPrivate(): - ns := s.CgroupNS.Value - if _, err := os.Stat(ns); err != nil { - return nil, err - } - case s.CgroupNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.CgroupNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.CgroupNS.Value) - } - options = append(options, libpod.WithCgroupNSFrom(connectedCtr)) - // TODO - //default: - // return nil, errors.New("cgroup name only supports private and container") - } - - if s.CgroupParent != "" { - options = append(options, libpod.WithCgroupParent(s.CgroupParent)) - } - - if s.CgroupsMode != "" { - options = append(options, libpod.WithCgroupsMode(s.CgroupsMode)) - } - - // ipc - switch { - case s.IpcNS.IsHost(): - options = append(options, libpod.WithShmDir("/dev/shm")) - case s.IpcNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.IpcNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.IpcNS.Value) - } - options = append(options, libpod.WithIPCNSFrom(connectedCtr)) - options = append(options, libpod.WithShmDir(connectedCtr.ShmDir())) - } - - // pid - if s.PidNS.IsContainer() { - connectedCtr, err := rt.LookupContainer(s.PidNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.PidNS.Value) - } - options = append(options, libpod.WithPIDNSFrom(connectedCtr)) - } - - // uts - switch { - case s.UtsNS.IsPod(): - connectedPod, err := rt.LookupPod(s.UtsNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "pod %q not found", s.UtsNS.Value) - } - options = append(options, libpod.WithUTSNSFromPod(connectedPod)) - case s.UtsNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.UtsNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.UtsNS.Value) - } - - options = append(options, libpod.WithUTSNSFrom(connectedCtr)) - } - - if s.UseImageHosts { - options = append(options, libpod.WithUseImageHosts()) - } else if len(s.HostAdd) > 0 { - options = append(options, libpod.WithHosts(s.HostAdd)) - } - - // User - - switch { - case s.UserNS.IsPath(): - ns := s.UserNS.Value - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined user namespace") - } - _, err := os.Stat(ns) - if err != nil { - return nil, err - } - if s.IDMappings != nil { - options = append(options, libpod.WithIDMappings(*s.IDMappings)) - } - case s.UserNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.UserNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.UserNS.Value) - } - options = append(options, libpod.WithUserNSFrom(connectedCtr)) - default: - if s.IDMappings != nil { - options = append(options, libpod.WithIDMappings(*s.IDMappings)) - } - } - - options = append(options, libpod.WithUser(s.User)) - options = append(options, libpod.WithGroups(s.Groups)) - - if len(s.PortMappings) > 0 { - portBindings = s.PortMappings - } - - switch { - case s.NetNS.IsPath(): - ns := s.NetNS.Value - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined network namespace") - } - _, err := os.Stat(ns) - if err != nil { - return nil, err - } - case s.NetNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.NetNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.NetNS.Value) - } - options = append(options, libpod.WithNetNSFrom(connectedCtr)) - case !s.NetNS.IsHost() && s.NetNS.NSMode != NoNetwork: - postConfigureNetNS := !s.UserNS.IsHost() - options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(s.NetNS.NSMode), s.CNINetworks)) - } - - if len(s.DNSSearch) > 0 { - options = append(options, libpod.WithDNSSearch(s.DNSSearch)) - } - if len(s.DNSServer) > 0 { - // TODO I'm not sure how we are going to handle this given the input - if len(s.DNSServer) == 1 { //&& strings.ToLower(s.DNSServer[0].) == "none" { - options = append(options, libpod.WithUseImageResolvConf()) - } else { - var dnsServers []string - for _, d := range s.DNSServer { - dnsServers = append(dnsServers, d.String()) - } - options = append(options, libpod.WithDNS(dnsServers)) - } - } - if len(s.DNSOption) > 0 { - options = append(options, libpod.WithDNSOption(s.DNSOption)) - } - if s.StaticIP != nil { - options = append(options, libpod.WithStaticIP(*s.StaticIP)) - } - - if s.StaticMAC != nil { - options = append(options, libpod.WithStaticMAC(*s.StaticMAC)) - } - return options, nil -} - -func (s *SpecGenerator) pidConfigureGenerator(g *generate.Generator) error { - if s.PidNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value) - } - if s.PidNS.IsHost() { - return g.RemoveLinuxNamespace(string(spec.PIDNamespace)) - } - if s.PidNS.IsContainer() { - logrus.Debugf("using container %s pidmode", s.PidNS.Value) - } - if s.PidNS.IsPod() { - logrus.Debug("using pod pidmode") - } - return nil -} - -func (s *SpecGenerator) utsConfigureGenerator(g *generate.Generator, runtime *libpod.Runtime) error { - hostname := s.Hostname - var err error - if hostname == "" { - switch { - case s.UtsNS.IsContainer(): - utsCtr, err := runtime.LookupContainer(s.UtsNS.Value) - if err != nil { - return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value) - } - hostname = utsCtr.Hostname() - case s.NetNS.IsHost() || s.UtsNS.IsHost(): - hostname, err = os.Hostname() - if err != nil { - return errors.Wrap(err, "unable to retrieve hostname of the host") - } - default: - logrus.Debug("No hostname set; container's hostname will default to runtime default") - } - } - g.RemoveHostname() - if s.Hostname != "" || !s.UtsNS.IsHost() { - // Set the hostname in the OCI configuration only - // if specified by the user or if we are creating - // a new UTS namespace. - g.SetHostname(hostname) - } - g.AddProcessEnv("HOSTNAME", hostname) - - if s.UtsNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value) - } - if s.UtsNS.IsHost() { - return g.RemoveLinuxNamespace(string(spec.UTSNamespace)) - } - if s.UtsNS.IsContainer() { - logrus.Debugf("using container %s utsmode", s.UtsNS.Value) - } - return nil -} - -func (s *SpecGenerator) ipcConfigureGenerator(g *generate.Generator) error { - if s.IpcNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value) - } - if s.IpcNS.IsHost() { - return g.RemoveLinuxNamespace(s.IpcNS.Value) - } - if s.IpcNS.IsContainer() { - logrus.Debugf("Using container %s ipcmode", s.IpcNS.Value) - } - return nil -} - -func (s *SpecGenerator) cgroupConfigureGenerator(g *generate.Generator) error { - if s.CgroupNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value) - } - if s.CgroupNS.IsHost() { - return g.RemoveLinuxNamespace(s.CgroupNS.Value) - } - if s.CgroupNS.IsPrivate() { - return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "") - } - if s.CgroupNS.IsContainer() { - logrus.Debugf("Using container %s cgroup mode", s.CgroupNS.Value) - } - return nil -} - -func (s *SpecGenerator) networkConfigureGenerator(g *generate.Generator) error { - switch { - case s.NetNS.IsHost(): - logrus.Debug("Using host netmode") - if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { - return err - } - - case s.NetNS.NSMode == NoNetwork: - logrus.Debug("Using none netmode") - case s.NetNS.NSMode == Bridge: - logrus.Debug("Using bridge netmode") - case s.NetNS.IsContainer(): - logrus.Debugf("using container %s netmode", s.NetNS.Value) - case s.NetNS.IsPath(): - logrus.Debug("Using ns netmode") - if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil { - return err - } - case s.NetNS.IsPod(): - logrus.Debug("Using pod netmode, unless pod is not sharing") - case s.NetNS.NSMode == Slirp: - logrus.Debug("Using slirp4netns netmode") - default: - return errors.Errorf("unknown network mode") - } - - if g.Config.Annotations == nil { - g.Config.Annotations = make(map[string]string) - } - - if s.PublishImagePorts { - g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue - } else { - g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse - } - - return nil -} - -func (s *SpecGenerator) userConfigureGenerator(g *generate.Generator) error { - if s.UserNS.IsPath() { - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); err != nil { - return err - } - // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping - g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) - g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1)) - } - - if s.IDMappings != nil { - if (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) && !s.UserNS.IsHost() { - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { - return err - } - } - for _, uidmap := range s.IDMappings.UIDMap { - g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) - } - for _, gidmap := range s.IDMappings.GIDMap { - g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) - } - } - return nil -} - -func (s *SpecGenerator) securityConfigureGenerator(g *generate.Generator, newImage *image.Image) error { - // HANDLE CAPABILITIES - // NOTE: Must happen before SECCOMP - if s.Privileged { - g.SetupPrivileged(true) - } - - useNotRoot := func(user string) bool { - if user == "" || user == "root" || user == "0" { - return false - } - return true - } - configSpec := g.Config - var err error - var caplist []string - bounding := configSpec.Process.Capabilities.Bounding - if useNotRoot(s.User) { - configSpec.Process.Capabilities.Bounding = caplist - } - caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop) - if err != nil { - return err - } - - configSpec.Process.Capabilities.Bounding = caplist - configSpec.Process.Capabilities.Permitted = caplist - configSpec.Process.Capabilities.Inheritable = caplist - configSpec.Process.Capabilities.Effective = caplist - configSpec.Process.Capabilities.Ambient = caplist - if useNotRoot(s.User) { - caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop) - if err != nil { - return err - } - } - configSpec.Process.Capabilities.Bounding = caplist - - // HANDLE SECCOMP - if s.SeccompProfilePath != "unconfined" { - seccompConfig, err := s.getSeccompConfig(configSpec, newImage) - if err != nil { - return err - } - configSpec.Linux.Seccomp = seccompConfig - } - - // Clear default Seccomp profile from Generator for privileged containers - if s.SeccompProfilePath == "unconfined" || s.Privileged { - configSpec.Linux.Seccomp = nil - } - - g.SetRootReadonly(s.ReadOnlyFilesystem) - for sysctlKey, sysctlVal := range s.Sysctl { - g.AddLinuxSysctl(sysctlKey, sysctlVal) - } - - return nil -} diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go index 50309f096..9e9659fa9 100644 --- a/pkg/specgen/pod_validate.go +++ b/pkg/specgen/pod_validate.go @@ -15,7 +15,8 @@ func exclusivePodOptions(opt1, opt2 string) error { return errors.Wrapf(ErrInvalidPodSpecConfig, "%s and %s are mutually exclusive pod options", opt1, opt2) } -func (p *PodSpecGenerator) validate() error { +// Validate verifies the input is valid +func (p *PodSpecGenerator) Validate() error { // PodBasicConfig if p.NoInfra { if len(p.InfraCommand) > 0 { @@ -25,7 +26,7 @@ func (p *PodSpecGenerator) validate() error { return exclusivePodOptions("NoInfra", "InfraImage") } if len(p.SharedNamespaces) > 0 { - return exclusivePodOptions("NoInfo", "SharedNamespaces") + return exclusivePodOptions("NoInfra", "SharedNamespaces") } } diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 2e6dd9c1d..8482ef2c9 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -4,8 +4,6 @@ import ( "net" "syscall" - "github.com/containers/libpod/libpod" - "github.com/containers/image/v5/manifest" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" @@ -174,7 +172,7 @@ type ContainerStorageConfig struct { // These will supersede Image Volumes and VolumesFrom volumes where // there are conflicts. // Optional. - Volumes []*libpod.ContainerNamedVolume `json:"volumes,omitempty"` + Volumes []*Volumes `json:"volumes,omitempty"` // Devices are devices that will be added to the container. // Optional. Devices []spec.LinuxDevice `json:"devices,omitempty"` @@ -403,6 +401,13 @@ type SpecGenerator struct { ContainerHealthCheckConfig } +// Volumes is a temporary struct to hold input from the User +type Volumes struct { + Name string + Dest string + Options []string +} + // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs func NewSpecGenerator(image string) *SpecGenerator { networkConfig := ContainerNetworkConfig{ diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 372c7c53b..1051ed311 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -516,6 +516,8 @@ func ParseInputTime(inputTime string) (time.Time, error) { } // GetGlobalOpts checks all global flags and generates the command string +// FIXME: Port input to config.Config +// TODO: Is there a "better" way to reverse values to flags? This seems brittle. func GetGlobalOpts(c *cliconfig.RunlabelValues) string { globalFlags := map[string]bool{ "cgroup-manager": true, "cni-config-dir": true, "conmon": true, "default-mounts-file": true, diff --git a/pkg/varlinkapi/attach.go b/pkg/varlinkapi/attach.go index 34f351669..db977ee5c 100644 --- a/pkg/varlinkapi/attach.go +++ b/pkg/varlinkapi/attach.go @@ -44,7 +44,7 @@ func setupStreams(call iopodman.VarlinkCall) (*bufio.Reader, *bufio.Writer, *io. } // Attach connects to a containers console -func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys string, start bool) error { +func (i *VarlinkAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys string, start bool) error { var finalErr error resize := make(chan remotecommand.TerminalSize) errChan := make(chan error) diff --git a/pkg/varlinkapi/config.go b/pkg/varlinkapi/config.go index c69dc794a..cc787eca2 100644 --- a/pkg/varlinkapi/config.go +++ b/pkg/varlinkapi/config.go @@ -3,21 +3,20 @@ package varlinkapi import ( - "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" iopodman "github.com/containers/libpod/pkg/varlink" "github.com/spf13/cobra" ) -// LibpodAPI is the basic varlink struct for libpod -type LibpodAPI struct { +// VarlinkAPI is the basic varlink struct for libpod +type VarlinkAPI struct { Cli *cobra.Command iopodman.VarlinkInterface Runtime *libpod.Runtime } // New creates a new varlink client -func New(cli *cliconfig.PodmanCommand, runtime *libpod.Runtime) *iopodman.VarlinkInterface { - lp := LibpodAPI{Cli: cli.Command, Runtime: runtime} +func New(cli *cobra.Command, runtime *libpod.Runtime) *iopodman.VarlinkInterface { + lp := VarlinkAPI{Cli: cli, Runtime: runtime} return iopodman.VarlinkNew(&lp) } diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 2d051470f..66b3e4095 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -30,7 +30,7 @@ import ( ) // ListContainers ... -func (i *LibpodAPI) ListContainers(call iopodman.VarlinkCall) error { +func (i *VarlinkAPI) ListContainers(call iopodman.VarlinkCall) error { var ( listContainers []iopodman.Container ) @@ -54,7 +54,7 @@ func (i *LibpodAPI) ListContainers(call iopodman.VarlinkCall) error { return call.ReplyListContainers(listContainers) } -func (i *LibpodAPI) Ps(call iopodman.VarlinkCall, opts iopodman.PsOpts) error { +func (i *VarlinkAPI) Ps(call iopodman.VarlinkCall, opts iopodman.PsOpts) error { var ( containers []iopodman.PsContainer ) @@ -106,7 +106,7 @@ func (i *LibpodAPI) Ps(call iopodman.VarlinkCall, opts iopodman.PsOpts) error { } // GetContainer ... -func (i *LibpodAPI) GetContainer(call iopodman.VarlinkCall, id string) error { +func (i *VarlinkAPI) GetContainer(call iopodman.VarlinkCall, id string) error { ctr, err := i.Runtime.LookupContainer(id) if err != nil { return call.ReplyContainerNotFound(id, err.Error()) @@ -123,7 +123,7 @@ func (i *LibpodAPI) GetContainer(call iopodman.VarlinkCall, id string) error { } // GetContainersByContext returns a slice of container ids based on all, latest, or a list -func (i *LibpodAPI) GetContainersByContext(call iopodman.VarlinkCall, all, latest bool, input []string) error { +func (i *VarlinkAPI) GetContainersByContext(call iopodman.VarlinkCall, all, latest bool, input []string) error { var ids []string ctrs, err := shortcuts.GetContainersByContext(all, latest, input, i.Runtime) @@ -141,7 +141,7 @@ func (i *LibpodAPI) GetContainersByContext(call iopodman.VarlinkCall, all, lates } // GetContainersByStatus returns a slice of containers filtered by a libpod status -func (i *LibpodAPI) GetContainersByStatus(call iopodman.VarlinkCall, statuses []string) error { +func (i *VarlinkAPI) GetContainersByStatus(call iopodman.VarlinkCall, statuses []string) error { var ( filterFuncs []libpod.ContainerFilter containers []iopodman.Container @@ -172,7 +172,7 @@ func (i *LibpodAPI) GetContainersByStatus(call iopodman.VarlinkCall, statuses [] } // InspectContainer ... -func (i *LibpodAPI) InspectContainer(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) InspectContainer(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -189,7 +189,7 @@ func (i *LibpodAPI) InspectContainer(call iopodman.VarlinkCall, name string) err } // ListContainerProcesses ... -func (i *LibpodAPI) ListContainerProcesses(call iopodman.VarlinkCall, name string, opts []string) error { +func (i *VarlinkAPI) ListContainerProcesses(call iopodman.VarlinkCall, name string, opts []string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -216,7 +216,7 @@ func (i *LibpodAPI) ListContainerProcesses(call iopodman.VarlinkCall, name strin } // GetContainerLogs ... -func (i *LibpodAPI) GetContainerLogs(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) GetContainerLogs(call iopodman.VarlinkCall, name string) error { var logs []string ctr, err := i.Runtime.LookupContainer(name) if err != nil { @@ -277,7 +277,7 @@ func (i *LibpodAPI) GetContainerLogs(call iopodman.VarlinkCall, name string) err } // ListContainerChanges ... -func (i *LibpodAPI) ListContainerChanges(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) ListContainerChanges(call iopodman.VarlinkCall, name string) error { changes, err := i.Runtime.GetDiff("", name) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -297,7 +297,7 @@ func (i *LibpodAPI) ListContainerChanges(call iopodman.VarlinkCall, name string) } // ExportContainer ... -func (i *LibpodAPI) ExportContainer(call iopodman.VarlinkCall, name, outPath string) error { +func (i *VarlinkAPI) ExportContainer(call iopodman.VarlinkCall, name, outPath string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -319,7 +319,7 @@ func (i *LibpodAPI) ExportContainer(call iopodman.VarlinkCall, name, outPath str } // GetContainerStats ... -func (i *LibpodAPI) GetContainerStats(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) GetContainerStats(call iopodman.VarlinkCall, name string) error { if rootless.IsRootless() { cgroupv2, err := cgroups.IsCgroup2UnifiedMode() if err != nil { @@ -359,7 +359,7 @@ func (i *LibpodAPI) GetContainerStats(call iopodman.VarlinkCall, name string) er } // StartContainer ... -func (i *LibpodAPI) StartContainer(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) StartContainer(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -382,7 +382,7 @@ func (i *LibpodAPI) StartContainer(call iopodman.VarlinkCall, name string) error } // InitContainer initializes the container given by Varlink. -func (i *LibpodAPI) InitContainer(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) InitContainer(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -397,7 +397,7 @@ func (i *LibpodAPI) InitContainer(call iopodman.VarlinkCall, name string) error } // StopContainer ... -func (i *LibpodAPI) StopContainer(call iopodman.VarlinkCall, name string, timeout int64) error { +func (i *VarlinkAPI) StopContainer(call iopodman.VarlinkCall, name string, timeout int64) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -415,7 +415,7 @@ func (i *LibpodAPI) StopContainer(call iopodman.VarlinkCall, name string, timeou } // RestartContainer ... -func (i *LibpodAPI) RestartContainer(call iopodman.VarlinkCall, name string, timeout int64) error { +func (i *VarlinkAPI) RestartContainer(call iopodman.VarlinkCall, name string, timeout int64) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -427,7 +427,7 @@ func (i *LibpodAPI) RestartContainer(call iopodman.VarlinkCall, name string, tim } // ContainerExists looks in local storage for the existence of a container -func (i *LibpodAPI) ContainerExists(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) ContainerExists(call iopodman.VarlinkCall, name string) error { _, err := i.Runtime.LookupContainer(name) if errors.Cause(err) == define.ErrNoSuchCtr { return call.ReplyContainerExists(1) @@ -440,7 +440,7 @@ func (i *LibpodAPI) ContainerExists(call iopodman.VarlinkCall, name string) erro // KillContainer kills a running container. If you want to use the default SIGTERM signal, just send a -1 // for the signal arg. -func (i *LibpodAPI) KillContainer(call iopodman.VarlinkCall, name string, signal int64) error { +func (i *VarlinkAPI) KillContainer(call iopodman.VarlinkCall, name string, signal int64) error { killSignal := uint(syscall.SIGTERM) if signal != -1 { killSignal = uint(signal) @@ -456,7 +456,7 @@ func (i *LibpodAPI) KillContainer(call iopodman.VarlinkCall, name string, signal } // PauseContainer ... -func (i *LibpodAPI) PauseContainer(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) PauseContainer(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -468,7 +468,7 @@ func (i *LibpodAPI) PauseContainer(call iopodman.VarlinkCall, name string) error } // UnpauseContainer ... -func (i *LibpodAPI) UnpauseContainer(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) UnpauseContainer(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -480,7 +480,7 @@ func (i *LibpodAPI) UnpauseContainer(call iopodman.VarlinkCall, name string) err } // WaitContainer ... -func (i *LibpodAPI) WaitContainer(call iopodman.VarlinkCall, name string, interval int64) error { +func (i *VarlinkAPI) WaitContainer(call iopodman.VarlinkCall, name string, interval int64) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -493,7 +493,7 @@ func (i *LibpodAPI) WaitContainer(call iopodman.VarlinkCall, name string, interv } // RemoveContainer ... -func (i *LibpodAPI) RemoveContainer(call iopodman.VarlinkCall, name string, force bool, removeVolumes bool) error { +func (i *VarlinkAPI) RemoveContainer(call iopodman.VarlinkCall, name string, force bool, removeVolumes bool) error { ctx := getContext() ctr, err := i.Runtime.LookupContainer(name) if err != nil { @@ -512,7 +512,7 @@ func (i *LibpodAPI) RemoveContainer(call iopodman.VarlinkCall, name string, forc } // EvictContainer ... -func (i *LibpodAPI) EvictContainer(call iopodman.VarlinkCall, name string, removeVolumes bool) error { +func (i *VarlinkAPI) EvictContainer(call iopodman.VarlinkCall, name string, removeVolumes bool) error { ctx := getContext() id, err := i.Runtime.EvictContainer(ctx, name, removeVolumes) if err != nil { @@ -522,7 +522,7 @@ func (i *LibpodAPI) EvictContainer(call iopodman.VarlinkCall, name string, remov } // DeleteStoppedContainers ... -func (i *LibpodAPI) DeleteStoppedContainers(call iopodman.VarlinkCall) error { +func (i *VarlinkAPI) DeleteStoppedContainers(call iopodman.VarlinkCall) error { ctx := getContext() var deletedContainers []string containers, err := i.Runtime.GetAllContainers() @@ -545,7 +545,7 @@ func (i *LibpodAPI) DeleteStoppedContainers(call iopodman.VarlinkCall) error { } // GetAttachSockets ... -func (i *LibpodAPI) GetAttachSockets(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) GetAttachSockets(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -578,7 +578,7 @@ func (i *LibpodAPI) GetAttachSockets(call iopodman.VarlinkCall, name string) err } // ContainerCheckpoint ... -func (i *LibpodAPI) ContainerCheckpoint(call iopodman.VarlinkCall, name string, keep, leaveRunning, tcpEstablished bool) error { +func (i *VarlinkAPI) ContainerCheckpoint(call iopodman.VarlinkCall, name string, keep, leaveRunning, tcpEstablished bool) error { ctx := getContext() ctr, err := i.Runtime.LookupContainer(name) if err != nil { @@ -597,7 +597,7 @@ func (i *LibpodAPI) ContainerCheckpoint(call iopodman.VarlinkCall, name string, } // ContainerRestore ... -func (i *LibpodAPI) ContainerRestore(call iopodman.VarlinkCall, name string, keep, tcpEstablished bool) error { +func (i *VarlinkAPI) ContainerRestore(call iopodman.VarlinkCall, name string, keep, tcpEstablished bool) error { ctx := getContext() ctr, err := i.Runtime.LookupContainer(name) if err != nil { @@ -615,7 +615,7 @@ func (i *LibpodAPI) ContainerRestore(call iopodman.VarlinkCall, name string, kee } // ContainerConfig returns just the container.config struct -func (i *LibpodAPI) ContainerConfig(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) ContainerConfig(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -629,7 +629,7 @@ func (i *LibpodAPI) ContainerConfig(call iopodman.VarlinkCall, name string) erro } // ContainerArtifacts returns an untouched container's artifact in string format -func (i *LibpodAPI) ContainerArtifacts(call iopodman.VarlinkCall, name, artifactName string) error { +func (i *VarlinkAPI) ContainerArtifacts(call iopodman.VarlinkCall, name, artifactName string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -646,7 +646,7 @@ func (i *LibpodAPI) ContainerArtifacts(call iopodman.VarlinkCall, name, artifact } // ContainerInspectData returns the inspect data of a container in string format -func (i *LibpodAPI) ContainerInspectData(call iopodman.VarlinkCall, name string, size bool) error { +func (i *VarlinkAPI) ContainerInspectData(call iopodman.VarlinkCall, name string, size bool) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -664,7 +664,7 @@ func (i *LibpodAPI) ContainerInspectData(call iopodman.VarlinkCall, name string, } // ContainerStateData returns a container's state data in string format -func (i *LibpodAPI) ContainerStateData(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) ContainerStateData(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) @@ -682,7 +682,7 @@ func (i *LibpodAPI) ContainerStateData(call iopodman.VarlinkCall, name string) e // GetContainerStatsWithHistory is a varlink endpoint that returns container stats based on current and // previous statistics -func (i *LibpodAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prevStats iopodman.ContainerStats) error { +func (i *VarlinkAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prevStats iopodman.ContainerStats) error { con, err := i.Runtime.LookupContainer(prevStats.Id) if err != nil { return call.ReplyContainerNotFound(prevStats.Id, err.Error()) @@ -711,7 +711,7 @@ func (i *LibpodAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prev } // Spec ... -func (i *LibpodAPI) Spec(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) Spec(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -727,7 +727,7 @@ func (i *LibpodAPI) Spec(call iopodman.VarlinkCall, name string) error { } // GetContainersLogs is the varlink endpoint to obtain one or more container logs -func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string, follow, latest bool, since string, tail int64, timestamps bool) error { +func (i *VarlinkAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string, follow, latest bool, since string, tail int64, timestamps bool) error { var wg sync.WaitGroup if call.WantsMore() { call.Continues = true @@ -784,7 +784,7 @@ func newPodmanLogLine(line *logs.LogLine) iopodman.LogLine { } // Top displays information about a container's running processes -func (i *LibpodAPI) Top(call iopodman.VarlinkCall, nameOrID string, descriptors []string) error { +func (i *VarlinkAPI) Top(call iopodman.VarlinkCall, nameOrID string, descriptors []string) error { ctr, err := i.Runtime.LookupContainer(nameOrID) if err != nil { return call.ReplyContainerNotFound(ctr.ID(), err.Error()) @@ -797,7 +797,7 @@ func (i *LibpodAPI) Top(call iopodman.VarlinkCall, nameOrID string, descriptors } // ExecContainer is the varlink endpoint to execute a command in a container -func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecOpts) error { +func (i *VarlinkAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecOpts) error { if !call.WantsUpgrade() { return call.ReplyErrorOccurred("client must use upgraded connection to exec") } @@ -901,7 +901,7 @@ func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecO } // HealthCheckRun executes defined container's healthcheck command and returns the container's health status. -func (i *LibpodAPI) HealthCheckRun(call iopodman.VarlinkCall, nameOrID string) error { +func (i *VarlinkAPI) HealthCheckRun(call iopodman.VarlinkCall, nameOrID string) error { hcStatus, err := i.Runtime.HealthCheck(nameOrID) if err != nil && hcStatus != libpod.HealthCheckFailure { return call.ReplyErrorOccurred(err.Error()) diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go index bbd4d59f1..c1c1f6674 100644 --- a/pkg/varlinkapi/containers_create.go +++ b/pkg/varlinkapi/containers_create.go @@ -8,7 +8,7 @@ import ( ) // CreateContainer ... -func (i *LibpodAPI) CreateContainer(call iopodman.VarlinkCall, config iopodman.Create) error { +func (i *VarlinkAPI) CreateContainer(call iopodman.VarlinkCall, config iopodman.Create) error { generic := shared.VarlinkCreateToGeneric(config) ctr, _, err := shared.CreateContainer(getContext(), &generic, i.Runtime) if err != nil { diff --git a/pkg/varlinkapi/events.go b/pkg/varlinkapi/events.go index 4ae2d1cb2..33938f08b 100644 --- a/pkg/varlinkapi/events.go +++ b/pkg/varlinkapi/events.go @@ -3,7 +3,6 @@ package varlinkapi import ( - "fmt" "time" "github.com/containers/libpod/libpod/events" @@ -11,7 +10,7 @@ import ( ) // GetEvents is a remote endpoint to get events from the event log -func (i *LibpodAPI) GetEvents(call iopodman.VarlinkCall, filter []string, since string, until string) error { +func (i *VarlinkAPI) GetEvents(call iopodman.VarlinkCall, filter []string, since string, until string) error { var ( fromStart bool eventsError error @@ -43,9 +42,9 @@ func (i *LibpodAPI) GetEvents(call iopodman.VarlinkCall, filter []string, since Id: event.ID, Image: event.Image, Name: event.Name, - Status: fmt.Sprintf("%s", event.Status), + Status: string(event.Status), Time: event.Time.Format(time.RFC3339Nano), - Type: fmt.Sprintf("%s", event.Type), + Type: string(event.Type), }) if !call.Continues { // For a one-shot on events, we break out here diff --git a/pkg/varlinkapi/generate.go b/pkg/varlinkapi/generate.go index c19c8dede..81a0df68e 100644 --- a/pkg/varlinkapi/generate.go +++ b/pkg/varlinkapi/generate.go @@ -10,7 +10,7 @@ import ( ) // GenerateKube ... -func (i *LibpodAPI) GenerateKube(call iopodman.VarlinkCall, name string, service bool) error { +func (i *VarlinkAPI) GenerateKube(call iopodman.VarlinkCall, name string, service bool) error { pod, serv, err := shared.GenerateKube(name, service, i.Runtime) if err != nil { return call.ReplyErrorOccurred(err.Error()) diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index c3b4bd9ae..49bd0b0cb 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -35,14 +35,14 @@ import ( ) // ListImagesWithFilters returns a list of images that have been filtered -func (i *LibpodAPI) ListImagesWithFilters(call iopodman.VarlinkCall, filters []string) error { +func (i *VarlinkAPI) ListImagesWithFilters(call iopodman.VarlinkCall, filters []string) error { images, err := i.Runtime.ImageRuntime().GetImagesWithFilters(filters) if err != nil { return call.ReplyErrorOccurred(fmt.Sprintf("unable to get list of images %q", err)) } imageList, err := imagesToImageList(images) if err != nil { - return call.ReplyErrorOccurred(fmt.Sprintf("unable to parse response", err)) + return call.ReplyErrorOccurred("unable to parse response " + err.Error()) } return call.ReplyListImagesWithFilters(imageList) } @@ -50,34 +50,34 @@ func (i *LibpodAPI) ListImagesWithFilters(call iopodman.VarlinkCall, filters []s // imagesToImageList converts a slice of Images to an imagelist for varlink responses func imagesToImageList(images []*image.Image) ([]iopodman.Image, error) { var imageList []iopodman.Image - for _, image := range images { - labels, _ := image.Labels(getContext()) - containers, _ := image.Containers() - repoDigests, err := image.RepoDigests() + for _, img := range images { + labels, _ := img.Labels(getContext()) + containers, _ := img.Containers() + repoDigests, err := img.RepoDigests() if err != nil { return nil, err } - size, _ := image.Size(getContext()) - isParent, err := image.IsParent(context.TODO()) + size, _ := img.Size(getContext()) + isParent, err := img.IsParent(context.TODO()) if err != nil { return nil, err } i := iopodman.Image{ - Id: image.ID(), - Digest: string(image.Digest()), - ParentId: image.Parent, - RepoTags: image.Names(), + Id: img.ID(), + Digest: string(img.Digest()), + ParentId: img.Parent, + RepoTags: img.Names(), RepoDigests: repoDigests, - Created: image.Created().Format(time.RFC3339), + Created: img.Created().Format(time.RFC3339), Size: int64(*size), - VirtualSize: image.VirtualSize, + VirtualSize: img.VirtualSize, Containers: int64(len(containers)), Labels: labels, IsParent: isParent, - ReadOnly: image.IsReadOnly(), - History: image.NamesHistory(), + ReadOnly: img.IsReadOnly(), + History: img.NamesHistory(), } imageList = append(imageList, i) } @@ -86,20 +86,20 @@ func imagesToImageList(images []*image.Image) ([]iopodman.Image, error) { // ListImages lists all the images in the store // It requires no inputs. -func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error { +func (i *VarlinkAPI) ListImages(call iopodman.VarlinkCall) error { images, err := i.Runtime.ImageRuntime().GetImages() if err != nil { - return call.ReplyErrorOccurred(fmt.Sprintf("unable to get list of images %q", err)) + return call.ReplyErrorOccurred("unable to get list of images " + err.Error()) } imageList, err := imagesToImageList(images) if err != nil { - return call.ReplyErrorOccurred(fmt.Sprintf("unable to parse response", err)) + return call.ReplyErrorOccurred("unable to parse response " + err.Error()) } return call.ReplyListImages(imageList) } // GetImage returns a single image in the form of a Image -func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error { +func (i *VarlinkAPI) GetImage(call iopodman.VarlinkCall, id string) error { newImage, err := i.Runtime.ImageRuntime().NewFromLocal(id) if err != nil { return call.ReplyImageNotFound(id, err.Error()) @@ -139,7 +139,7 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error { } // BuildImage ... -func (i *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildInfo) error { +func (i *VarlinkAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildInfo) error { var ( namespace []buildah.NamespaceOption imageID string @@ -302,7 +302,7 @@ func (i *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildI // InspectImage returns an image's inspect information as a string that can be serialized. // Requires an image ID or name -func (i *LibpodAPI) InspectImage(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) InspectImage(call iopodman.VarlinkCall, name string) error { newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) if err != nil { return call.ReplyImageNotFound(name, err.Error()) @@ -320,7 +320,7 @@ func (i *LibpodAPI) InspectImage(call iopodman.VarlinkCall, name string) error { // HistoryImage returns the history of the image's layers // Requires an image or name -func (i *LibpodAPI) HistoryImage(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) HistoryImage(call iopodman.VarlinkCall, name string) error { newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) if err != nil { return call.ReplyImageNotFound(name, err.Error()) @@ -345,7 +345,7 @@ func (i *LibpodAPI) HistoryImage(call iopodman.VarlinkCall, name string) error { } // PushImage pushes an local image to registry -func (i *LibpodAPI) PushImage(call iopodman.VarlinkCall, name, tag string, compress bool, format string, removeSignatures bool, signBy string) error { +func (i *VarlinkAPI) PushImage(call iopodman.VarlinkCall, name, tag string, compress bool, format string, removeSignatures bool, signBy string) error { var ( manifestType string ) @@ -437,7 +437,7 @@ func (i *LibpodAPI) PushImage(call iopodman.VarlinkCall, name, tag string, compr } // TagImage accepts an image name and tag as strings and tags an image in the local store. -func (i *LibpodAPI) TagImage(call iopodman.VarlinkCall, name, tag string) error { +func (i *VarlinkAPI) TagImage(call iopodman.VarlinkCall, name, tag string) error { newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) if err != nil { return call.ReplyImageNotFound(name, err.Error()) @@ -449,7 +449,7 @@ func (i *LibpodAPI) TagImage(call iopodman.VarlinkCall, name, tag string) error } // UntagImage accepts an image name and tag as strings and removes the tag from the local store. -func (i *LibpodAPI) UntagImage(call iopodman.VarlinkCall, name, tag string) error { +func (i *VarlinkAPI) UntagImage(call iopodman.VarlinkCall, name, tag string) error { newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) if err != nil { return call.ReplyImageNotFound(name, err.Error()) @@ -462,7 +462,7 @@ func (i *LibpodAPI) UntagImage(call iopodman.VarlinkCall, name, tag string) erro // RemoveImage accepts a image name or ID as a string and force bool to determine if it should // remove the image even if being used by stopped containers -func (i *LibpodAPI) RemoveImage(call iopodman.VarlinkCall, name string, force bool) error { +func (i *VarlinkAPI) RemoveImage(call iopodman.VarlinkCall, name string, force bool) error { ctx := getContext() newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) if err != nil { @@ -477,7 +477,7 @@ func (i *LibpodAPI) RemoveImage(call iopodman.VarlinkCall, name string, force bo // RemoveImageWithResponse accepts an image name and force bool. It returns details about what // was done in removeimageresponse struct. -func (i *LibpodAPI) RemoveImageWithResponse(call iopodman.VarlinkCall, name string, force bool) error { +func (i *VarlinkAPI) RemoveImageWithResponse(call iopodman.VarlinkCall, name string, force bool) error { ir := iopodman.RemoveImageResponse{} ctx := getContext() newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) @@ -495,7 +495,7 @@ func (i *LibpodAPI) RemoveImageWithResponse(call iopodman.VarlinkCall, name stri // SearchImages searches all registries configured in /etc/containers/registries.conf for an image // Requires an image name and a search limit as int -func (i *LibpodAPI) SearchImages(call iopodman.VarlinkCall, query string, limit *int64, filter iopodman.ImageSearchFilter) error { +func (i *VarlinkAPI) SearchImages(call iopodman.VarlinkCall, query string, limit *int64, filter iopodman.ImageSearchFilter) error { // Transform all arguments to proper types first argLimit := 0 argIsOfficial := types.OptionalBoolUndefined @@ -543,7 +543,7 @@ func (i *LibpodAPI) SearchImages(call iopodman.VarlinkCall, query string, limit // DeleteUnusedImages deletes any images that do not have containers associated with it. // TODO Filters are not implemented -func (i *LibpodAPI) DeleteUnusedImages(call iopodman.VarlinkCall) error { +func (i *VarlinkAPI) DeleteUnusedImages(call iopodman.VarlinkCall) error { images, err := i.Runtime.ImageRuntime().GetImages() if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -565,7 +565,7 @@ func (i *LibpodAPI) DeleteUnusedImages(call iopodman.VarlinkCall) error { } // Commit ... -func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, changes []string, author, message string, pause bool, manifestType string) error { +func (i *VarlinkAPI) Commit(call iopodman.VarlinkCall, name, imageName string, changes []string, author, message string, pause bool, manifestType string) error { var ( newImage *image.Image log []string @@ -643,7 +643,7 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch } // ImportImage imports an image from a tarball to the image store -func (i *LibpodAPI) ImportImage(call iopodman.VarlinkCall, source, reference, message string, changes []string, delete bool) error { +func (i *VarlinkAPI) ImportImage(call iopodman.VarlinkCall, source, reference, message string, changes []string, delete bool) error { configChanges, err := util.GetImageConfig(changes) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -670,7 +670,7 @@ func (i *LibpodAPI) ImportImage(call iopodman.VarlinkCall, source, reference, me // ExportImage exports an image to the provided destination // destination must have the transport type!! -func (i *LibpodAPI) ExportImage(call iopodman.VarlinkCall, name, destination string, compress bool, tags []string) error { +func (i *VarlinkAPI) ExportImage(call iopodman.VarlinkCall, name, destination string, compress bool, tags []string) error { newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) if err != nil { return call.ReplyImageNotFound(name, err.Error()) @@ -688,7 +688,7 @@ func (i *LibpodAPI) ExportImage(call iopodman.VarlinkCall, name, destination str } // PullImage pulls an image from a registry to the image store. -func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string, creds iopodman.AuthConfig) error { +func (i *VarlinkAPI) PullImage(call iopodman.VarlinkCall, name string, creds iopodman.AuthConfig) error { var ( imageID string err error @@ -760,7 +760,7 @@ func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string, creds iopo } // ImageExists returns bool as to whether the input image exists in local storage -func (i *LibpodAPI) ImageExists(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) ImageExists(call iopodman.VarlinkCall, name string) error { _, err := i.Runtime.ImageRuntime().NewFromLocal(name) if errors.Cause(err) == image.ErrNoSuchImage { return call.ReplyImageExists(1) @@ -772,7 +772,7 @@ func (i *LibpodAPI) ImageExists(call iopodman.VarlinkCall, name string) error { } // ContainerRunlabel ... -func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.Runlabel) error { +func (i *VarlinkAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.Runlabel) error { ctx := getContext() dockerRegistryOptions := image.DockerRegistryOptions{} stdErr := os.Stderr @@ -798,7 +798,7 @@ func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman. } // ImagesPrune .... -func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool, filter []string) error { +func (i *VarlinkAPI) ImagesPrune(call iopodman.VarlinkCall, all bool, filter []string) error { prunedImages, err := i.Runtime.ImageRuntime().PruneImages(context.TODO(), all, []string{}) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -807,7 +807,7 @@ func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool, filter []st } // ImageSave .... -func (i *LibpodAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.ImageSaveOptions) error { +func (i *VarlinkAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.ImageSaveOptions) error { newImage, err := i.Runtime.ImageRuntime().NewFromLocal(options.Name) if err != nil { if errors.Cause(err) == define.ErrNoSuchImage { @@ -905,7 +905,7 @@ func (i *LibpodAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.ImageS } // LoadImage ... -func (i *LibpodAPI) LoadImage(call iopodman.VarlinkCall, name, inputFile string, deleteInputFile, quiet bool) error { +func (i *VarlinkAPI) LoadImage(call iopodman.VarlinkCall, name, inputFile string, deleteInputFile, quiet bool) error { var ( names string writer io.Writer @@ -974,7 +974,7 @@ func (i *LibpodAPI) LoadImage(call iopodman.VarlinkCall, name, inputFile string, } // Diff ... -func (i *LibpodAPI) Diff(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) Diff(call iopodman.VarlinkCall, name string) error { var response []iopodman.DiffInfo changes, err := i.Runtime.GetDiff("", name) if err != nil { @@ -987,7 +987,7 @@ func (i *LibpodAPI) Diff(call iopodman.VarlinkCall, name string) error { } // GetLayersMapWithImageInfo is a development only endpoint to obtain layer information for an image. -func (i *LibpodAPI) GetLayersMapWithImageInfo(call iopodman.VarlinkCall) error { +func (i *VarlinkAPI) GetLayersMapWithImageInfo(call iopodman.VarlinkCall) error { layerInfo, err := image.GetLayersMapWithImageInfo(i.Runtime.ImageRuntime()) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -1000,7 +1000,7 @@ func (i *LibpodAPI) GetLayersMapWithImageInfo(call iopodman.VarlinkCall) error { } // BuildImageHierarchyMap ... -func (i *LibpodAPI) BuildImageHierarchyMap(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) BuildImageHierarchyMap(call iopodman.VarlinkCall, name string) error { img, err := i.Runtime.ImageRuntime().NewFromLocal(name) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -1024,7 +1024,7 @@ func (i *LibpodAPI) BuildImageHierarchyMap(call iopodman.VarlinkCall, name strin } // ImageTree returns the image tree string for the provided image name or ID -func (i *LibpodAPI) ImageTree(call iopodman.VarlinkCall, nameOrID string, whatRequires bool) error { +func (i *VarlinkAPI) ImageTree(call iopodman.VarlinkCall, nameOrID string, whatRequires bool) error { img, err := i.Runtime.ImageRuntime().NewFromLocal(nameOrID) if err != nil { return call.ReplyErrorOccurred(err.Error()) diff --git a/pkg/varlinkapi/mount.go b/pkg/varlinkapi/mount.go index 2450f6fd9..6e1eed644 100644 --- a/pkg/varlinkapi/mount.go +++ b/pkg/varlinkapi/mount.go @@ -5,7 +5,7 @@ package varlinkapi import iopodman "github.com/containers/libpod/pkg/varlink" // ListContainerMounts ... -func (i *LibpodAPI) ListContainerMounts(call iopodman.VarlinkCall) error { +func (i *VarlinkAPI) ListContainerMounts(call iopodman.VarlinkCall) error { mounts := make(map[string]string) allContainers, err := i.Runtime.GetAllContainers() if err != nil { @@ -24,7 +24,7 @@ func (i *LibpodAPI) ListContainerMounts(call iopodman.VarlinkCall) error { } // MountContainer ... -func (i *LibpodAPI) MountContainer(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) MountContainer(call iopodman.VarlinkCall, name string) error { container, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -37,7 +37,7 @@ func (i *LibpodAPI) MountContainer(call iopodman.VarlinkCall, name string) error } // UnmountContainer ... -func (i *LibpodAPI) UnmountContainer(call iopodman.VarlinkCall, name string, force bool) error { +func (i *VarlinkAPI) UnmountContainer(call iopodman.VarlinkCall, name string, force bool) error { container, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyErrorOccurred(err.Error()) diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index 79ffb6677..94add1b6c 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -14,7 +14,7 @@ import ( ) // CreatePod ... -func (i *LibpodAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCreate) error { +func (i *VarlinkAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCreate) error { var options []libpod.PodCreateOption if create.Infra { options = append(options, libpod.WithInfraContainer()) @@ -61,7 +61,7 @@ func (i *LibpodAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCrea } // ListPods ... -func (i *LibpodAPI) ListPods(call iopodman.VarlinkCall) error { +func (i *VarlinkAPI) ListPods(call iopodman.VarlinkCall) error { var ( listPods []iopodman.ListPodData ) @@ -82,7 +82,7 @@ func (i *LibpodAPI) ListPods(call iopodman.VarlinkCall) error { } // GetPod ... -func (i *LibpodAPI) GetPod(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) GetPod(call iopodman.VarlinkCall, name string) error { pod, err := i.Runtime.LookupPod(name) if err != nil { return call.ReplyPodNotFound(name, err.Error()) @@ -98,7 +98,7 @@ func (i *LibpodAPI) GetPod(call iopodman.VarlinkCall, name string) error { } // GetPodsByStatus returns a slice of pods filtered by a libpod status -func (i *LibpodAPI) GetPodsByStatus(call iopodman.VarlinkCall, statuses []string) error { +func (i *VarlinkAPI) GetPodsByStatus(call iopodman.VarlinkCall, statuses []string) error { filterFuncs := func(p *libpod.Pod) bool { state, _ := shared.GetPodStatus(p) for _, status := range statuses { @@ -120,7 +120,7 @@ func (i *LibpodAPI) GetPodsByStatus(call iopodman.VarlinkCall, statuses []string } // InspectPod ... -func (i *LibpodAPI) InspectPod(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) InspectPod(call iopodman.VarlinkCall, name string) error { pod, err := i.Runtime.LookupPod(name) if err != nil { return call.ReplyPodNotFound(name, err.Error()) @@ -137,7 +137,7 @@ func (i *LibpodAPI) InspectPod(call iopodman.VarlinkCall, name string) error { } // StartPod ... -func (i *LibpodAPI) StartPod(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) StartPod(call iopodman.VarlinkCall, name string) error { pod, err := i.Runtime.LookupPod(name) if err != nil { return call.ReplyPodNotFound(name, err.Error()) @@ -158,7 +158,7 @@ func (i *LibpodAPI) StartPod(call iopodman.VarlinkCall, name string) error { } // StopPod ... -func (i *LibpodAPI) StopPod(call iopodman.VarlinkCall, name string, timeout int64) error { +func (i *VarlinkAPI) StopPod(call iopodman.VarlinkCall, name string, timeout int64) error { pod, err := i.Runtime.LookupPod(name) if err != nil { return call.ReplyPodNotFound(name, err.Error()) @@ -172,7 +172,7 @@ func (i *LibpodAPI) StopPod(call iopodman.VarlinkCall, name string, timeout int6 } // RestartPod ... -func (i *LibpodAPI) RestartPod(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) RestartPod(call iopodman.VarlinkCall, name string) error { pod, err := i.Runtime.LookupPod(name) if err != nil { return call.ReplyPodNotFound(name, err.Error()) @@ -194,7 +194,7 @@ func (i *LibpodAPI) RestartPod(call iopodman.VarlinkCall, name string) error { // KillPod kills the running containers in a pod. If you want to use the default SIGTERM signal, // just send a -1 for the signal arg. -func (i *LibpodAPI) KillPod(call iopodman.VarlinkCall, name string, signal int64) error { +func (i *VarlinkAPI) KillPod(call iopodman.VarlinkCall, name string, signal int64) error { killSignal := uint(syscall.SIGTERM) if signal != -1 { killSignal = uint(signal) @@ -213,7 +213,7 @@ func (i *LibpodAPI) KillPod(call iopodman.VarlinkCall, name string, signal int64 } // PausePod ... -func (i *LibpodAPI) PausePod(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) PausePod(call iopodman.VarlinkCall, name string) error { pod, err := i.Runtime.LookupPod(name) if err != nil { return call.ReplyPodNotFound(name, err.Error()) @@ -227,7 +227,7 @@ func (i *LibpodAPI) PausePod(call iopodman.VarlinkCall, name string) error { } // UnpausePod ... -func (i *LibpodAPI) UnpausePod(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) UnpausePod(call iopodman.VarlinkCall, name string) error { pod, err := i.Runtime.LookupPod(name) if err != nil { return call.ReplyPodNotFound(name, err.Error()) @@ -241,7 +241,7 @@ func (i *LibpodAPI) UnpausePod(call iopodman.VarlinkCall, name string) error { } // RemovePod ... -func (i *LibpodAPI) RemovePod(call iopodman.VarlinkCall, name string, force bool) error { +func (i *VarlinkAPI) RemovePod(call iopodman.VarlinkCall, name string, force bool) error { ctx := getContext() pod, err := i.Runtime.LookupPod(name) if err != nil { @@ -255,7 +255,7 @@ func (i *LibpodAPI) RemovePod(call iopodman.VarlinkCall, name string, force bool } // GetPodStats ... -func (i *LibpodAPI) GetPodStats(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) GetPodStats(call iopodman.VarlinkCall, name string) error { pod, err := i.Runtime.LookupPod(name) if err != nil { return call.ReplyPodNotFound(name, err.Error()) @@ -291,7 +291,7 @@ func (i *LibpodAPI) GetPodStats(call iopodman.VarlinkCall, name string) error { } // GetPodsByContext returns a slice of pod ids based on all, latest, or a list -func (i *LibpodAPI) GetPodsByContext(call iopodman.VarlinkCall, all, latest bool, input []string) error { +func (i *VarlinkAPI) GetPodsByContext(call iopodman.VarlinkCall, all, latest bool, input []string) error { var podids []string pods, err := shortcuts.GetPodsByContext(all, latest, input, i.Runtime) @@ -305,7 +305,7 @@ func (i *LibpodAPI) GetPodsByContext(call iopodman.VarlinkCall, all, latest bool } // PodStateData returns a container's state data in string format -func (i *LibpodAPI) PodStateData(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) PodStateData(call iopodman.VarlinkCall, name string) error { pod, err := i.Runtime.LookupPod(name) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -322,7 +322,7 @@ func (i *LibpodAPI) PodStateData(call iopodman.VarlinkCall, name string) error { } // TopPod provides the top stats for a given or latest pod -func (i *LibpodAPI) TopPod(call iopodman.VarlinkCall, name string, latest bool, descriptors []string) error { +func (i *VarlinkAPI) TopPod(call iopodman.VarlinkCall, name string, latest bool, descriptors []string) error { var ( pod *libpod.Pod err error diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index 7bee643c2..82efe9b5d 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -16,7 +16,7 @@ import ( ) // GetVersion ... -func (i *LibpodAPI) GetVersion(call iopodman.VarlinkCall) error { +func (i *VarlinkAPI) GetVersion(call iopodman.VarlinkCall) error { versionInfo, err := define.GetVersion() if err != nil { return err @@ -33,7 +33,7 @@ func (i *LibpodAPI) GetVersion(call iopodman.VarlinkCall) error { } // GetInfo returns details about the podman host and its stores -func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { +func (i *VarlinkAPI) GetInfo(call iopodman.VarlinkCall) error { versionInfo, err := define.GetVersion() if err != nil { return err @@ -106,7 +106,7 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { } // GetVersion ... -func (i *LibpodAPI) Reset(call iopodman.VarlinkCall) error { +func (i *VarlinkAPI) Reset(call iopodman.VarlinkCall) error { if err := i.Runtime.Reset(context.TODO()); err != nil { logrus.Errorf("Reset Failed: %v", err) if err := call.ReplyErrorOccurred(err.Error()); err != nil { diff --git a/pkg/varlinkapi/transfers.go b/pkg/varlinkapi/transfers.go index 654da276e..9df8ffcdc 100644 --- a/pkg/varlinkapi/transfers.go +++ b/pkg/varlinkapi/transfers.go @@ -4,7 +4,6 @@ package varlinkapi import ( "bufio" - "fmt" "io" "io/ioutil" "os" @@ -14,7 +13,7 @@ import ( ) // SendFile allows a client to send a file to the varlink server -func (i *LibpodAPI) SendFile(call iopodman.VarlinkCall, ftype string, length int64) error { +func (i *VarlinkAPI) SendFile(call iopodman.VarlinkCall, ftype string, length int64) error { if !call.WantsUpgrade() { return call.ReplyErrorOccurred("client must use upgraded connection to send files") } @@ -40,14 +39,14 @@ func (i *LibpodAPI) SendFile(call iopodman.VarlinkCall, ftype string, length int logrus.Debugf("successfully received %s", outputFile.Name()) // Send an ACK to the client - call.Call.Writer.WriteString(fmt.Sprintf("%s:", outputFile.Name())) + call.Call.Writer.WriteString(outputFile.Name()) call.Call.Writer.Flush() return nil } // ReceiveFile allows the varlink server to send a file to a client -func (i *LibpodAPI) ReceiveFile(call iopodman.VarlinkCall, filepath string, delete bool) error { +func (i *VarlinkAPI) ReceiveFile(call iopodman.VarlinkCall, filepath string, delete bool) error { if !call.WantsUpgrade() { return call.ReplyErrorOccurred("client must use upgraded connection to send files") } diff --git a/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go index b0c3608c4..ff72c3869 100644 --- a/pkg/varlinkapi/volumes.go +++ b/pkg/varlinkapi/volumes.go @@ -11,7 +11,7 @@ import ( ) // VolumeCreate creates a libpod volume based on input from a varlink connection -func (i *LibpodAPI) VolumeCreate(call iopodman.VarlinkCall, options iopodman.VolumeCreateOpts) error { +func (i *VarlinkAPI) VolumeCreate(call iopodman.VarlinkCall, options iopodman.VolumeCreateOpts) error { var volumeOptions []libpod.VolumeCreateOption if len(options.VolumeName) > 0 { @@ -38,7 +38,7 @@ func (i *LibpodAPI) VolumeCreate(call iopodman.VarlinkCall, options iopodman.Vol } // VolumeRemove removes volumes by options.All or options.Volumes -func (i *LibpodAPI) VolumeRemove(call iopodman.VarlinkCall, options iopodman.VolumeRemoveOpts) error { +func (i *VarlinkAPI) VolumeRemove(call iopodman.VarlinkCall, options iopodman.VolumeRemoveOpts) error { success, failed, err := shared.SharedRemoveVolumes(getContext(), i.Runtime, options.Volumes, options.All, options.Force) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -52,7 +52,7 @@ func (i *LibpodAPI) VolumeRemove(call iopodman.VarlinkCall, options iopodman.Vol } // GetVolumes returns all the volumes known to the remote system -func (i *LibpodAPI) GetVolumes(call iopodman.VarlinkCall, args []string, all bool) error { +func (i *VarlinkAPI) GetVolumes(call iopodman.VarlinkCall, args []string, all bool) error { var ( err error reply []*libpod.Volume @@ -87,7 +87,7 @@ func (i *LibpodAPI) GetVolumes(call iopodman.VarlinkCall, args []string, all boo } // InspectVolume inspects a single volume, returning its JSON as a string. -func (i *LibpodAPI) InspectVolume(call iopodman.VarlinkCall, name string) error { +func (i *VarlinkAPI) InspectVolume(call iopodman.VarlinkCall, name string) error { vol, err := i.Runtime.LookupVolume(name) if err != nil { return call.ReplyErrorOccurred(err.Error()) @@ -104,7 +104,7 @@ func (i *LibpodAPI) InspectVolume(call iopodman.VarlinkCall, name string) error } // VolumesPrune removes unused images via a varlink call -func (i *LibpodAPI) VolumesPrune(call iopodman.VarlinkCall) error { +func (i *VarlinkAPI) VolumesPrune(call iopodman.VarlinkCall) error { var ( prunedErrors []string prunedNames []string diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 8c4fe9223..d93ee8d3a 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -221,8 +221,8 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { ociRuntime := os.Getenv("OCI_RUNTIME") if ociRuntime == "" { var err error - ociRuntime, err = exec.LookPath("runc") - // If we cannot find the runc binary, setting to something static as we have no way + ociRuntime, err = exec.LookPath("crun") + // If we cannot find the crun binary, setting to something static as we have no way // to return an error. The tests will fail and point out that the runc binary could // not be found nicely. if err != nil { diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index 5d0d6e689..8b95794d2 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -244,15 +244,17 @@ var _ = Describe("Podman exec", func() { }) It("podman exec preserve fds sanity check", func() { - // TODO: add this test once crun adds the --preserve-fds flag for exec - if strings.Contains(podmanTest.OCIRuntime, "crun") { - Skip("Test only works on crun") - } setup := podmanTest.RunTopContainer("test1") setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) - session := podmanTest.Podman([]string{"exec", "--preserve-fds", "1", "test1", "ls"}) + devNull, err := os.Open("/dev/null") + Expect(err).To(BeNil()) + defer devNull.Close() + files := []*os.File{ + devNull, + } + session := podmanTest.PodmanExtraFiles([]string{"exec", "--preserve-fds", "1", "test1", "ls"}, files) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) }) diff --git a/test/e2e/libpod_suite_remoteclient_test.go b/test/e2e/libpod_suite_remoteclient_test.go index c87ff016a..b5da041ab 100644 --- a/test/e2e/libpod_suite_remoteclient_test.go +++ b/test/e2e/libpod_suite_remoteclient_test.go @@ -34,6 +34,12 @@ func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration return &PodmanSessionIntegration{podmanSession} } +// PodmanExtraFiles is the exec call to podman on the filesystem and passes down extra files +func (p *PodmanTestIntegration) PodmanExtraFiles(args []string, extraFiles []*os.File) *PodmanSessionIntegration { + podmanSession := p.PodmanAsUserBase(args, 0, 0, "", nil, false, false, nil, extraFiles) + return &PodmanSessionIntegration{podmanSession} +} + // PodmanNoCache calls podman with out adding the imagecache func (p *PodmanTestIntegration) PodmanNoCache(args []string) *PodmanSessionIntegration { podmanSession := p.PodmanBase(args, false, true) diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index dc5e91c72..29a55980c 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -27,6 +27,12 @@ func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration return &PodmanSessionIntegration{podmanSession} } +// PodmanExtraFiles is the exec call to podman on the filesystem and passes down extra files +func (p *PodmanTestIntegration) PodmanExtraFiles(args []string, extraFiles []*os.File) *PodmanSessionIntegration { + podmanSession := p.PodmanAsUserBase(args, 0, 0, "", nil, false, false, extraFiles) + return &PodmanSessionIntegration{podmanSession} +} + // PodmanNoCache calls the podman command with no configured imagecache func (p *PodmanTestIntegration) PodmanNoCache(args []string) *PodmanSessionIntegration { podmanSession := p.PodmanBase(args, false, true) @@ -42,7 +48,7 @@ func (p *PodmanTestIntegration) PodmanNoEvents(args []string) *PodmanSessionInte // PodmanAsUser is the exec call to podman on the filesystem with the specified uid/gid and environment func (p *PodmanTestIntegration) PodmanAsUser(args []string, uid, gid uint32, cwd string, env []string) *PodmanSessionIntegration { - podmanSession := p.PodmanAsUserBase(args, uid, gid, cwd, env, false, false) + podmanSession := p.PodmanAsUserBase(args, uid, gid, cwd, env, false, false, nil) return &PodmanSessionIntegration{podmanSession} } diff --git a/test/system/120-load.bats b/test/system/120-load.bats index f2dedb73f..15df6adec 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -10,7 +10,7 @@ load helpers # # initialize, read image ID and name get_iid_and_name() { - run_podman images --format '{{.ID}} {{.Repository}}:{{.Tag}}' + run_podman images -a --format '{{.ID}} {{.Repository}}:{{.Tag}}' read iid img_name < <(echo "$output") archive=$PODMAN_TMPDIR/myimage-$(random_string 8).tar @@ -18,7 +18,7 @@ get_iid_and_name() { # Simple verification of image ID and name verify_iid_and_name() { - run_podman images --format '{{.ID}} {{.Repository}}:{{.Tag}}' + run_podman images -a --format '{{.ID}} {{.Repository}}:{{.Tag}}' read new_iid new_img_name < <(echo "$output") # Verify diff --git a/test/utils/podmantest_test.go b/test/utils/podmantest_test.go index 9620898af..387c8747c 100644 --- a/test/utils/podmantest_test.go +++ b/test/utils/podmantest_test.go @@ -23,7 +23,7 @@ var _ = Describe("PodmanTest test", func() { FakeOutputs["check"] = []string{"check"} os.Setenv("HOOK_OPTION", "hook_option") env := os.Environ() - session := podmanTest.PodmanAsUserBase([]string{"check"}, 1000, 1000, "", env, true, false) + session := podmanTest.PodmanAsUserBase([]string{"check"}, 1000, 1000, "", env, true, false, nil) os.Unsetenv("HOOK_OPTION") session.WaitWithDefaultTimeout() Expect(session.Command.Process).ShouldNot(BeNil()) diff --git a/test/utils/utils.go b/test/utils/utils.go index ad78d9792..6ab8604a4 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -65,7 +65,7 @@ func (p *PodmanTest) MakeOptions(args []string, noEvents, noCache bool) []string // PodmanAsUserBase exec podman as user. uid and gid is set for credentials usage. env is used // to record the env for debugging -func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string, env []string, noEvents, noCache bool) *PodmanSession { +func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string, env []string, noEvents, noCache bool, extraFiles []*os.File) *PodmanSession { var command *exec.Cmd podmanOptions := p.MakeOptions(args, noEvents, noCache) podmanBinary := p.PodmanBinary @@ -93,6 +93,8 @@ func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string command.Dir = cwd } + command.ExtraFiles = extraFiles + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) if err != nil { Fail(fmt.Sprintf("unable to run podman command: %s\n%v", strings.Join(podmanOptions, " "), err)) @@ -102,7 +104,7 @@ func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string // PodmanBase exec podman with default env. func (p *PodmanTest) PodmanBase(args []string, noEvents, noCache bool) *PodmanSession { - return p.PodmanAsUserBase(args, 0, 0, "", nil, noEvents, noCache) + return p.PodmanAsUserBase(args, 0, 0, "", nil, noEvents, noCache, nil) } // WaitForContainer waits on a started container diff --git a/vendor/github.com/containers/buildah/CHANGELOG.md b/vendor/github.com/containers/buildah/CHANGELOG.md index 941a35535..ed4ba3967 100644 --- a/vendor/github.com/containers/buildah/CHANGELOG.md +++ b/vendor/github.com/containers/buildah/CHANGELOG.md @@ -2,6 +2,14 @@ # Changelog +## v1.14.8 (2020-04-09) + Run (make vendor) + Run (make -C tests/tools vendor) + Run (go mod tidy) before (go mod vendor) again + Fix (make vendor) + Bump validation + Bump back to v1.15.0-dev + ## v1.14.7 (2020-04-07) Bump github.com/containers/image/v5 from 5.3.1 to 5.4.3 make vendor: run `tidy` after `vendor` diff --git a/vendor/github.com/containers/buildah/Makefile b/vendor/github.com/containers/buildah/Makefile index 371cefa7f..ced48a95a 100644 --- a/vendor/github.com/containers/buildah/Makefile +++ b/vendor/github.com/containers/buildah/Makefile @@ -142,10 +142,9 @@ vendor-in-container: .PHONY: vendor vendor: - export GO111MODULE=on \ - $(GO) mod vendor && \ - $(GO) mod tidy && \ - $(GO) mod verify + GO111MODULE=on $(GO) mod tidy + GO111MODULE=on $(GO) mod vendor + GO111MODULE=on $(GO) mod verify .PHONY: lint lint: install.tools diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index 059f0edd8..6cfbbb73f 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -27,7 +27,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.14.7" + Version = "1.14.8" // The value we use to identify what type of information, currently a // serialized Builder structure, we are using as per-container state. // This should only be changed when we make incompatible changes to diff --git a/vendor/github.com/containers/buildah/changelog.txt b/vendor/github.com/containers/buildah/changelog.txt index ebbfa5a64..4cd9a18df 100644 --- a/vendor/github.com/containers/buildah/changelog.txt +++ b/vendor/github.com/containers/buildah/changelog.txt @@ -1,3 +1,11 @@ +- Changelog for v1.14.8 (2020-04-09) + * Run (make vendor) + * Run (make -C tests/tools vendor) + * Run (go mod tidy) before (go mod vendor) again + * Fix (make vendor) + * Bump validation + * Bump back to v1.15.0-dev + - Changelog for v1.14.7 (2020-04-07) * Bump github.com/containers/image/v5 from 5.3.1 to 5.4.3 * make vendor: run `tidy` after `vendor` diff --git a/vendor/modules.txt b/vendor/modules.txt index 73c9d45ac..535090e81 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -64,7 +64,7 @@ github.com/containernetworking/plugins/pkg/ns github.com/containernetworking/plugins/pkg/utils/hwaddr github.com/containernetworking/plugins/plugins/ipam/host-local/backend github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator -# github.com/containers/buildah v1.14.7 +# github.com/containers/buildah v1.14.8 github.com/containers/buildah github.com/containers/buildah/bind github.com/containers/buildah/chroot diff --git a/version/version.go b/version/version.go index 0d0b0b422..fe602d8e1 100644 --- a/version/version.go +++ b/version/version.go @@ -4,7 +4,7 @@ package version // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -const Version = "1.8.3-dev" +const Version = "2.0.0-dev" // RemoteAPIVersion is the version for the remote // client API. It is used to determine compatibility |