diff options
53 files changed, 947 insertions, 218 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 2aae343e8..7400ae0a8 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -644,7 +644,6 @@ special_testing_cross_task: special_testing_bindings_task: - skip: $CI == 'true' depends_on: - "gating" - "varlink_api" @@ -189,7 +189,11 @@ podman: bin/podman .PHONY: bin/podman-remote bin/podman-remote: .gopathok $(SOURCES) go.mod go.sum $(PODMAN_VARLINK_DEPENDENCIES) ## Build with podman on remote environment - $(GO_BUILD) $(BUILDFLAGS) -gcflags '$(GCFLAGS)' -asmflags '$(ASMFLAGS)' -ldflags '$(LDFLAGS_PODMAN)' -tags "!ABISupport $(BUILDTAGS) remoteclient" -o $@ $(PROJECT)/cmd/podman + $(GO_BUILD) $(BUILDFLAGS) -gcflags '$(GCFLAGS)' -asmflags '$(ASMFLAGS)' -ldflags '$(LDFLAGS_PODMAN)' -tags "!ABISupport remoteclient" -o $@ $(PROJECT)/cmd/podman + +.PHONY: bin/podman-remote-static +podman-remote-static: bin/podman-remote-static + CGO_ENABLED=0 $(GO_BUILD) $(BUILDFLAGS) -gcflags '$(GCFLAGS)' -asmflags '$(ASMFLAGS)' -ldflags '$(LDFLAGS_PODMAN_STATIC)' -tags "!ABISupport containers_image_openpgp remoteclient" -o bin/podman-remote-static $(PROJECT)/cmd/podman .PHONY: podman-remote podman-remote: bin/podman-remote @@ -373,7 +377,7 @@ MANPAGES_MD ?= $(wildcard docs/source/markdown/*.md pkg/*/docs/*.md) MANPAGES ?= $(MANPAGES_MD:%.md=%) MANPAGES_DEST ?= $(subst markdown,man, $(subst source,build,$(MANPAGES))) -$(MANPAGES): %: %.md .gopathok +$(MANPAGES): %: %.md .install.md2man docdir @sed -e 's/\((podman.*\.md)\)//' -e 's/\[\(podman.*\)\]/\1/' $< | $(GOMD2MAN) -in /dev/stdin -out $(subst source/markdown,build/man,$@) .PHONY: docs @@ -381,7 +385,7 @@ docdir: mkdir -p docs/build/man .PHONY: docs -docs: .install.md2man docdir $(MANPAGES) ## Generate documentation +docs: $(MANPAGES) ## Generate documentation .PHONE: xref_helpmsgs_manpages xref_helpmsgs_manpages: diff --git a/cmd/podman/auto-update.go b/cmd/podman/auto-update.go new file mode 100644 index 000000000..758cbbc6f --- /dev/null +++ b/cmd/podman/auto-update.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/errorhandling" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + autoUpdateDescription = `Auto update containers according to their auto-update policy. + + Auto-update policies are specified with the "io.containers.autoupdate" label. + Note that this command is experimental.` + autoUpdateCommand = &cobra.Command{ + Use: "auto-update [flags]", + Short: "Auto update containers according to their auto-update policy", + Long: autoUpdateDescription, + RunE: autoUpdate, + Example: `podman auto-update`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: autoUpdateCommand, + }) +} + +func autoUpdate(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + // Backwards compat. System tests expext this error string. + return errors.Errorf("`%s` takes no arguments", cmd.CommandPath()) + } + report, failures := registry.ContainerEngine().AutoUpdate(registry.GetContext()) + if report != nil { + for _, unit := range report.Units { + fmt.Println(unit) + } + } + return errorhandling.JoinErrors(failures) +} diff --git a/cmd/podman/containers/runlabel.go b/cmd/podman/containers/runlabel.go new file mode 100644 index 000000000..11fa362b8 --- /dev/null +++ b/cmd/podman/containers/runlabel.go @@ -0,0 +1,80 @@ +package containers + +import ( + "context" + "os" + + "github.com/containers/common/pkg/auth" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +// runlabelOptionsWrapper allows for combining API-only with CLI-only options +// and to convert between them. +type runlabelOptionsWrapper struct { + entities.ContainerRunlabelOptions + TLSVerifyCLI bool +} + +var ( + runlabelOptions = runlabelOptionsWrapper{} + runlabelDescription = "Executes a command as described by a container image label." + runlabelCommand = &cobra.Command{ + Use: "runlabel [flags] LABEL IMAGE [ARG...]", + Short: "Execute the command described by an image label", + Long: runlabelDescription, + RunE: runlabel, + Args: cobra.MinimumNArgs(2), + Example: `podman container runlabel run imageID + podman container runlabel --pull install imageID arg1 arg2 + podman container runlabel --display run myImage`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: runlabelCommand, + Parent: containerCmd, + }) + + flags := rmCommand.Flags() + flags.StringVar(&runlabelOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&runlabelOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + flags.StringVar(&runlabelOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + flags.BoolVar(&runlabelOptions.Display, "display", false, "Preview the command that the label would run") + flags.StringVarP(&runlabelOptions.Name, "name", "n", "", "Assign a name to the container") + flags.StringVar(&runlabelOptions.Optional1, "opt1", "", "Optional parameter to pass for install") + flags.StringVar(&runlabelOptions.Optional2, "opt2", "", "Optional parameter to pass for install") + flags.StringVar(&runlabelOptions.Optional3, "opt3", "", "Optional parameter to pass for install") + flags.BoolP("pull", "p", false, "Pull the image if it does not exist locally prior to executing the label contents") + flags.BoolVarP(&runlabelOptions.Quiet, "quiet", "q", false, "Suppress output information when installing images") + flags.BoolVar(&runlabelOptions.Replace, "replace", false, "Replace existing container with a new one from the image") + flags.StringVar(&runlabelOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.BoolVar(&runlabelOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + + // Hide the optional flags. + _ = flags.MarkHidden("opt1") + _ = flags.MarkHidden("opt2") + _ = flags.MarkHidden("opt3") + + if err := flags.MarkDeprecated("pull", "podman will pull if not found in local storage"); err != nil { + logrus.Error("unable to mark pull flag deprecated") + } +} + +func runlabel(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("tls-verify") { + runlabelOptions.SkipTLSVerify = types.NewOptionalBool(!runlabelOptions.TLSVerifyCLI) + } + if runlabelOptions.Authfile != "" { + if _, err := os.Stat(runlabelOptions.Authfile); err != nil { + return errors.Wrapf(err, "error getting authfile %s", runlabelOptions.Authfile) + } + } + return registry.ContainerEngine().ContainerRunlabel(context.Background(), args[0], args[1], args[2:], runlabelOptions.ContainerRunlabelOptions) +} diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index 3f9db671f..5b7f52cc7 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -234,7 +234,6 @@ func outputJSON(stats []*containerStats) error { Pids: j.PIDS(), }) } - b, err := json.MarshalIndent(jstats, "", " ") if err != nil { return err diff --git a/cmd/podman/login.go b/cmd/podman/login.go index 8413861f5..92f13d0e7 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -46,7 +46,6 @@ func init() { // Podman flags. flags.BoolVarP(&loginOptions.tlsVerify, "tls-verify", "", false, "Require HTTPS and verify certificates when contacting registries") - flags.BoolVarP(&loginOptions.GetLoginSet, "get-login", "", false, "Return the current login user for the registry") loginOptions.Stdin = os.Stdin loginOptions.Stdout = os.Stdout loginOptions.AcceptUnspecifiedRegistry = true diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go index d0afc21b4..c016de8ae 100644 --- a/cmd/podman/logout.go +++ b/cmd/podman/logout.go @@ -37,7 +37,6 @@ func init() { // Flags from the auth package. flags.AddFlagSet(auth.GetLogoutFlags(&logoutOptions)) - logoutOptions.Stdin = os.Stdin logoutOptions.Stdout = os.Stdout logoutOptions.AcceptUnspecifiedRegistry = true } diff --git a/cmd/podman/manifest/annotate.go b/cmd/podman/manifest/annotate.go index 21d4fb747..82ee1ffda 100644 --- a/cmd/podman/manifest/annotate.go +++ b/cmd/podman/manifest/annotate.go @@ -24,7 +24,7 @@ var ( func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Mode: []entities.EngineMode{entities.ABIMode}, Command: annotateCmd, Parent: manifestCmd, }) diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 0a2016496..f97fa836a 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -17,6 +17,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -59,6 +60,14 @@ func init() { flags.StringVarP(&createOptions.Hostname, "hostname", "", "", "Set a hostname to the pod") flags.StringVar(&podIDFile, "pod-id-file", "", "Write the pod ID to the file") flags.StringVar(&share, "share", createconfig.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") + flags.SetNormalizeFunc(aliasNetworkFlag) +} + +func aliasNetworkFlag(_ *pflag.FlagSet, name string) pflag.NormalizedName { + if name == "net" { + name = "network" + } + return pflag.NormalizedName(name) } func create(cmd *cobra.Command, args []string) error { @@ -105,29 +114,21 @@ func create(cmd *cobra.Command, args []string) error { if err != nil { return err } - netInput, err := cmd.Flags().GetString("network") - if err != nil { - return err - } - n := specgen.Namespace{} - switch netInput { - case "bridge": - n.NSMode = specgen.Bridge - case "host": - n.NSMode = specgen.Host - case "slip4netns": - n.NSMode = specgen.Slirp - default: - if strings.HasPrefix(netInput, "container:") { // nolint - split := strings.Split(netInput, ":") - if len(split) != 2 { - return errors.Errorf("invalid network paramater: %q", netInput) - } - n.NSMode = specgen.FromContainer - n.Value = split[1] - } else if strings.HasPrefix(netInput, "ns:") { - return errors.New("the ns: network option is not supported for pods") - } else { + if cmd.Flag("network").Changed { + netInput, err := cmd.Flags().GetString("network") + if err != nil { + return err + } + n := specgen.Namespace{} + switch netInput { + case "bridge": + n.NSMode = specgen.Bridge + case "host": + n.NSMode = specgen.Host + case "slirp4netns": + n.NSMode = specgen.Slirp + default: + // Container and NS mode are presently unsupported n.NSMode = specgen.Bridge createOptions.Net.CNINetworks = strings.Split(netInput, ",") } diff --git a/cmd/podman/registry/config.go b/cmd/podman/registry/config.go index fc6eb538e..49d5bca74 100644 --- a/cmd/podman/registry/config.go +++ b/cmd/podman/registry/config.go @@ -22,6 +22,7 @@ const ( var ( podmanOptions entities.PodmanConfig podmanSync sync.Once + abiSupport = false ) // PodmanConfig returns an entities.PodmanConfig built up from @@ -39,23 +40,31 @@ func newPodmanConfig() { var mode entities.EngineMode switch runtime.GOOS { - case "darwin": - fallthrough - case "windows": + case "darwin", "windows": mode = entities.TunnelMode case "linux": - mode = entities.ABIMode + // Some linux clients might only be compiled without ABI + // support (e.g., podman-remote). + if abiSupport { + mode = entities.ABIMode + } else { + mode = entities.TunnelMode + } default: fmt.Fprintf(os.Stderr, "%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 + // Check if need to fallback to the tunnel mode if --remote is used. + if abiSupport && mode == entities.ABIMode { + // 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 + break + } } } diff --git a/cmd/podman/registry/config_abi.go b/cmd/podman/registry/config_abi.go new file mode 100644 index 000000000..55430e1bf --- /dev/null +++ b/cmd/podman/registry/config_abi.go @@ -0,0 +1,7 @@ +// +build ABISupport + +package registry + +func init() { + abiSupport = true +} diff --git a/cmd/podman/registry/config_tunnel.go b/cmd/podman/registry/config_tunnel.go new file mode 100644 index 000000000..29e744dac --- /dev/null +++ b/cmd/podman/registry/config_tunnel.go @@ -0,0 +1,7 @@ +// +build !ABISupport + +package registry + +func init() { + abiSupport = false +} diff --git a/cmd/podman/registry/registry.go b/cmd/podman/registry/registry.go index 69e2babfc..71ee2bed0 100644 --- a/cmd/podman/registry/registry.go +++ b/cmd/podman/registry/registry.go @@ -2,14 +2,18 @@ package registry import ( "context" + "path/filepath" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/infra" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) -// DefaultAPIAddress is the default address of the REST socket -const DefaultAPIAddress = "unix:/run/podman/podman.sock" +// DefaultRootAPIAddress is the default address of the REST socket +const DefaultRootAPIAddress = "unix:/run/podman/podman.sock" // DefaultVarlinkAddress is the default address of the varlink socket const DefaultVarlinkAddress = "unix:/run/podman/io.podman" @@ -98,3 +102,15 @@ func GetContextWithOptions() context.Context { func GetContext() context.Context { return Context() } + +func DefaultAPIAddress() string { + if rootless.IsRootless() { + xdg, err := util.GetRuntimeDir() + if err != nil { + logrus.Warnf("Failed to get rootless runtime dir for DefaultAPIAddress: %s", err) + return DefaultRootAPIAddress + } + return "unix:" + filepath.Join(xdg, "podman", "podman.sock") + } + return DefaultRootAPIAddress +} diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 502b6c03c..7d6f6f823 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -208,7 +208,7 @@ func syslogHook() { func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) { // V2 flags - flags.StringVarP(&opts.Uri, "remote", "r", "", "URL to access Podman service") + flags.StringVarP(&opts.Uri, "remote", "r", registry.DefaultAPIAddress(), "URL to access Podman service") flags.StringSliceVar(&opts.Identities, "identity", []string{}, "path to SSH identity file") cfg := opts.Config diff --git a/cmd/podman/system/service.go b/cmd/podman/system/service.go index f4b91dd78..552c72f79 100644 --- a/cmd/podman/system/service.go +++ b/cmd/podman/system/service.go @@ -139,6 +139,6 @@ func resolveApiURI(_url []string) (string, error) { case srvArgs.Varlink: return registry.DefaultVarlinkAddress, nil default: - return registry.DefaultAPIAddress, nil + return registry.DefaultRootAPIAddress, nil } } diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 6bec9625e..756240444 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -87,7 +87,7 @@ case "$CG_FS_TYPE" in # Normally not something to do for stable testing # but crun is new, and late-breaking fixes may be required # on short notice - dnf update -y crun + dnf update -y crun containers-common fi ;; *) diff --git a/docs/source/markdown/podman-network-create.1.md b/docs/source/markdown/podman-network-create.1.md index cbdfee4d0..45d9d9b0b 100644 --- a/docs/source/markdown/podman-network-create.1.md +++ b/docs/source/markdown/podman-network-create.1.md @@ -24,7 +24,7 @@ resolution. **-d**, **--driver** -Driver to manage the network (default "bridge"). Currently on `bridge` is supported. +Driver to manage the network (default "bridge"). Currently only `bridge` is supported. **--gateway** @@ -10,7 +10,7 @@ require ( github.com/containernetworking/cni v0.7.2-0.20200304161608-4fae32b84921 github.com/containernetworking/plugins v0.8.5 github.com/containers/buildah v1.14.9-0.20200501175434-42a48f9373d9 - github.com/containers/common v0.11.0 + github.com/containers/common v0.11.1 github.com/containers/conmon v2.0.14+incompatible github.com/containers/image/v5 v5.4.3 github.com/containers/psgo v1.5.0 @@ -35,7 +35,7 @@ require ( github.com/json-iterator/go v1.1.9 github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618 github.com/onsi/ginkgo v1.12.0 - github.com/onsi/gomega v1.9.0 + github.com/onsi/gomega v1.10.0 github.com/opencontainers/go-digest v1.0.0-rc1 github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 github.com/opencontainers/runc v1.0.0-rc9 @@ -72,8 +72,8 @@ github.com/containers/buildah v1.14.9-0.20200501175434-42a48f9373d9 h1:EGegltin1 github.com/containers/buildah v1.14.9-0.20200501175434-42a48f9373d9/go.mod h1:+2aNsVcd4pVzmVAbOfWN5X+0Lpz2rtICSGXbTSCzdBU= github.com/containers/common v0.10.0 h1:Km1foMJJBIxceA1/UCZcIuwf8sCF71sP5DwE6Oh1BEA= github.com/containers/common v0.10.0/go.mod h1:6A/moCuQITXLqBe5A0WKKTcCfCmEQRbknI05HcPzOL0= -github.com/containers/common v0.11.0 h1:uFSBIl9iqoTIv8icBe9lPrYKkmSiGrAWr0a2PyJLrO4= -github.com/containers/common v0.11.0/go.mod h1:ag8p8Xp2o1wPAPz/+bA7LVQlDavtg3M15RZLBWt/2KE= +github.com/containers/common v0.11.1 h1:i++kltFD92bKfDeE3B+Bpe5jYVTnAibmIUUUnXYKoPo= +github.com/containers/common v0.11.1/go.mod h1:2w3QE6VUmhltGYW4wV00h4okq1Crs7hNI1ZD2I0QRUY= 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.4.3 h1:zn2HR7uu4hpvT5QQHgjqonOzKDuM1I1UHUEmzZT5sbs= @@ -334,6 +334,8 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.0 h1:Gwkk+PTu/nfOwNMtUB/mRUv0X7ewW5dO4AERT1ThVKo= +github.com/onsi/gomega v1.10.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= diff --git a/libpod/runtime.go b/libpod/runtime.go index e71483ef9..c1864683f 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -752,7 +752,7 @@ type DBConfig struct { // mergeDBConfig merges the configuration from the database. func (r *Runtime) mergeDBConfig(dbConfig *DBConfig) error { - c := r.config.Engine + c := &r.config.Engine if !r.storageSet.RunRootSet && dbConfig.StorageTmp != "" { if r.storageConfig.RunRoot != dbConfig.StorageTmp && r.storageConfig.RunRoot != "" { diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index b85169410..3e0ef0325 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -125,23 +125,24 @@ func Push(ctx context.Context, name string, destination *string, all *bool) (str return idr.ID, response.Process(&idr) } +// There is NO annotate endpoint. this binding could never work // Annotate updates the image configuration of a given manifest list -func Annotate(ctx context.Context, name, digest string, options image.ManifestAnnotateOpts) (string, error) { - var idr handlers.IDResponse - conn, err := bindings.GetClient(ctx) - if err != nil { - return "", err - } - params := url.Values{} - params.Set("digest", digest) - optionsString, err := jsoniter.MarshalToString(options) - if err != nil { - return "", err - } - stringReader := strings.NewReader(optionsString) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/annotate", params, name) - if err != nil { - return "", err - } - return idr.ID, response.Process(&idr) -} +//func Annotate(ctx context.Context, name, digest string, options image.ManifestAnnotateOpts) (string, error) { +// var idr handlers.IDResponse +// conn, err := bindings.GetClient(ctx) +// if err != nil { +// return "", err +// } +// params := url.Values{} +// params.Set("digest", digest) +// optionsString, err := jsoniter.MarshalToString(options) +// if err != nil { +// return "", err +// } +// stringReader := strings.NewReader(optionsString) +// response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/annotate", params, name) +// if err != nil { +// return "", err +// } +// return idr.ID, response.Process(&idr) +//} diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index c79d89b73..f40d8ce46 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -531,7 +531,7 @@ var _ = Describe("Podman containers ", func() { Expect(err).ToNot(BeNil()) }) - It("podman prune stoped containers", func() { + It("podman prune stopped containers", func() { // Start and stop a container to enter in exited state. var name = "top" _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) @@ -546,7 +546,7 @@ var _ = Describe("Podman containers ", func() { Expect(len(pruneResponse.ID)).To(Equal(1)) }) - It("podman prune stoped containers with filters", func() { + It("podman prune stopped containers with filters", func() { // Start and stop a container to enter in exited state. var name = "top" _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go index ddb75865c..71d626b7b 100644 --- a/pkg/bindings/test/manifests_test.go +++ b/pkg/bindings/test/manifests_test.go @@ -118,25 +118,27 @@ var _ = Describe("Podman containers ", func() { Expect(len(data.Manifests)).To(BeZero()) }) - It("annotate manifest", func() { - id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil) - Expect(err).To(BeNil()) - opts := image.ManifestAddOpts{Images: []string{"docker.io/library/alpine:latest"}} - - _, err = manifests.Add(bt.conn, id, opts) - Expect(err).To(BeNil()) - data, err := manifests.Inspect(bt.conn, id) - Expect(err).To(BeNil()) - Expect(len(data.Manifests)).To(BeNumerically("==", 1)) - digest := data.Manifests[0].Digest.String() - annoOpts := image.ManifestAnnotateOpts{OS: "foo"} - _, err = manifests.Annotate(bt.conn, id, digest, annoOpts) - Expect(err).To(BeNil()) - list, err := manifests.Inspect(bt.conn, id) - Expect(err).To(BeNil()) - Expect(len(list.Manifests)).To(BeNumerically("==", 1)) - Expect(list.Manifests[0].Platform.OS).To(Equal("foo")) - }) + // There is NO annotate endpoint, this could never work.:w + + //It("annotate manifest", func() { + // id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil) + // Expect(err).To(BeNil()) + // opts := image.ManifestAddOpts{Images: []string{"docker.io/library/alpine:latest"}} + // + // _, err = manifests.Add(bt.conn, id, opts) + // Expect(err).To(BeNil()) + // data, err := manifests.Inspect(bt.conn, id) + // Expect(err).To(BeNil()) + // Expect(len(data.Manifests)).To(BeNumerically("==", 1)) + // digest := data.Manifests[0].Digest.String() + // annoOpts := image.ManifestAnnotateOpts{OS: "foo"} + // _, err = manifests.Annotate(bt.conn, id, digest, annoOpts) + // Expect(err).To(BeNil()) + // list, err := manifests.Inspect(bt.conn, id) + // Expect(err).To(BeNil()) + // Expect(len(list.Manifests)).To(BeNumerically("==", 1)) + // Expect(list.Manifests[0].Platform.OS).To(Equal("foo")) + //}) It("push manifest", func() { Skip("TODO") diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index 8a0b9c7a6..49bbfa246 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -57,6 +57,11 @@ var _ = Describe("Podman pods", func() { podSummary, err := pods.List(bt.conn, nil) Expect(err).To(BeNil()) Expect(len(podSummary)).To(Equal(1)) + + // Start the pod + _, err = pods.Start(bt.conn, newpod) + Expect(err).To(BeNil()) + // Adding an alpine container to the existing pod _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod) Expect(err).To(BeNil()) @@ -83,6 +88,11 @@ var _ = Describe("Podman pods", func() { It("List pods with filters", func() { newpod2 := "newpod2" bt.Podcreate(&newpod2) + + // Start the pod + _, err = pods.Start(bt.conn, newpod) + Expect(err).To(BeNil()) + _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod) Expect(err).To(BeNil()) diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go index 87e6d56dc..62ea32377 100644 --- a/pkg/bindings/test/system_test.go +++ b/pkg/bindings/test/system_test.go @@ -3,7 +3,6 @@ package test_bindings import ( "time" - "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/bindings/pods" @@ -39,8 +38,8 @@ var _ = Describe("Podman system", func() { }) It("podman events", func() { - eChan := make(chan handlers.Event, 1) - var messages []handlers.Event + eChan := make(chan entities.Event, 1) + var messages []entities.Event cancelChan := make(chan bool, 1) go func() { for e := range eChan { diff --git a/pkg/domain/entities/auto-update.go b/pkg/domain/entities/auto-update.go new file mode 100644 index 000000000..aef8fc46b --- /dev/null +++ b/pkg/domain/entities/auto-update.go @@ -0,0 +1,7 @@ +package entities + +// AutoUpdateReport contains the results from running auto-update. +type AutoUpdateReport struct { + // Units - the restarted systemd units during auto-update. + Units []string +} diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 071eff2fc..e5330e1ab 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -6,11 +6,49 @@ import ( "os" "time" + "github.com/containers/image/v5/types" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/specgen" "github.com/cri-o/ocicni/pkg/ocicni" ) +// ContainerRunlabelOptions are the options to execute container-runlabel. +type ContainerRunlabelOptions struct { + // Authfile - path to an authentication file. + Authfile string + // CertDir - path to a directory containing TLS certifications and + // keys. + CertDir string + // Credentials - `user:password` to use when pulling an image. + Credentials string + // Display - do not execute but print the command. + Display bool + // Replace - replace an existing container with a new one from the + // image. + Replace bool + // Name - use this name when executing the runlabel container. + Name string + // Optional1 - fist optional parameter for install. + Optional1 string + // Optional2 - second optional parameter for install. + Optional2 string + // Optional3 - third optional parameter for install. + Optional3 string + // Pull - pull the specified image if it's not in the local storage. + Pull bool + // Quiet - suppress output when pulling images. + Quiet bool + // SignaturePolicy - path to a signature-policy file. + SignaturePolicy string + // SkipTLSVerify - skip HTTPS and certificate verifications when + // contacting registries. + SkipTLSVerify types.OptionalBool +} + +// ContainerRunlabelReport contains the results from executing container-runlabel. +type ContainerRunlabelReport struct { +} + type WaitOptions struct { Condition define.ContainerStatus Interval time.Duration diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 1bfac4514..7c93e6802 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -10,6 +10,7 @@ import ( ) type ContainerEngine interface { + AutoUpdate(ctx context.Context) (*AutoUpdateReport, []error) 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) @@ -34,6 +35,7 @@ type ContainerEngine interface { ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error) ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error) ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, error) + ContainerRunlabel(ctx context.Context, label string, image string, args []string, opts ContainerRunlabelOptions) error ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error) ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) error ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error) diff --git a/pkg/domain/infra/abi/auto-update.go b/pkg/domain/infra/abi/auto-update.go new file mode 100644 index 000000000..aa20664b4 --- /dev/null +++ b/pkg/domain/infra/abi/auto-update.go @@ -0,0 +1,13 @@ +package abi + +import ( + "context" + + "github.com/containers/libpod/pkg/autoupdate" + "github.com/containers/libpod/pkg/domain/entities" +) + +func (ic *ContainerEngine) AutoUpdate(ctx context.Context) (*entities.AutoUpdateReport, []error) { + units, failures := autoupdate.AutoUpdate(ic.Libpod) + return &entities.AutoUpdateReport{Units: units}, failures +} diff --git a/pkg/domain/infra/abi/containers_runlabel.go b/pkg/domain/infra/abi/containers_runlabel.go new file mode 100644 index 000000000..41f4444d5 --- /dev/null +++ b/pkg/domain/infra/abi/containers_runlabel.go @@ -0,0 +1,280 @@ +package abi + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/image/v5/types" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/domain/entities" + envLib "github.com/containers/libpod/pkg/env" + "github.com/containers/libpod/pkg/util" + "github.com/containers/libpod/utils" + "github.com/google/shlex" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, imageRef string, args []string, options entities.ContainerRunlabelOptions) error { + // First, get the image and pull it if needed. + img, err := ic.runlabelImage(ctx, label, imageRef, options) + if err != nil { + return err + } + // Extract the runlabel from the image. + runlabel, err := img.GetLabel(ctx, label) + if err != nil { + return err + } + + cmd, env, err := generateRunlabelCommand(runlabel, img, args, options) + if err != nil { + return err + } + + stdErr := os.Stderr + stdOut := os.Stdout + stdIn := os.Stdin + if options.Quiet { + stdErr = nil + stdOut = nil + stdIn = nil + } + + // If container already exists && --replace given -- Nuke it + if options.Replace { + for i, entry := range cmd { + if entry == "--name" { + name := cmd[i+1] + ctr, err := ic.Libpod.LookupContainer(name) + if err != nil { + if errors.Cause(err) != define.ErrNoSuchCtr { + logrus.Debugf("Error occurred searching for container %s: %s", name, err.Error()) + return err + } + } else { + logrus.Debugf("Runlabel --replace option given. Container %s will be deleted. The new container will be named %s", ctr.ID(), name) + if err := ic.Libpod.RemoveContainer(ctx, ctr, true, false); err != nil { + return err + } + } + break + } + } + } + + return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) +} + +// runlabelImage returns an image based on the specified image AND options. +func (ic *ContainerEngine) runlabelImage(ctx context.Context, label string, imageRef string, options entities.ContainerRunlabelOptions) (*image.Image, error) { + // First, look up the image locally. If we get an error and requested + // to pull, fallthrough and pull it. + img, err := ic.Libpod.ImageRuntime().NewFromLocal(imageRef) + switch { + case err == nil: + return img, nil + case !options.Pull: + return nil, err + default: + // Fallthrough and pull! + } + + // Parse credentials if specified. + var credentials *types.DockerAuthConfig + if options.Credentials != "" { + credentials, err = util.ParseRegistryCreds(options.Credentials) + if err != nil { + return nil, err + } + } + + // Suppress pull progress bars if requested. + pullOutput := os.Stdout + if options.Quiet { + pullOutput = nil // c/image/copy takes care of the rest + } + + // Pull the image. + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerCertPath: options.CertDir, + DockerInsecureSkipTLSVerify: options.SkipTLSVerify, + DockerRegistryCreds: credentials, + } + + return ic.Libpod.ImageRuntime().New(ctx, imageRef, options.SignaturePolicy, options.Authfile, pullOutput, &dockerRegistryOptions, image.SigningOptions{}, &label, util.PullImageMissing) +} + +// generateRunlabelCommand generates the to-be-executed command as a string +// slice along with a base environment. +func generateRunlabelCommand(runlabel string, img *image.Image, args []string, options entities.ContainerRunlabelOptions) ([]string, []string, error) { + var ( + err error + name, imageName string + globalOpts string + cmd, env []string + ) + + // TODO: How do we get global opts as done in v1? + + // Extract the imageName (or ID). + imgNames := img.Names() + if len(imgNames) == 0 { + imageName = img.ID() + } else { + imageName = imgNames[0] + } + + // Use the user-specified name or extract one from the image. + if options.Name != "" { + name = options.Name + } else { + name, err = image.GetImageBaseName(imageName) + if err != nil { + return nil, nil, err + } + } + + // Append the user-specified arguments to the runlabel (command). + if len(args) > 0 { + runlabel = fmt.Sprintf("%s %s", runlabel, strings.Join(args, " ")) + } + + cmd, err = generateCommand(runlabel, imageName, name, globalOpts) + if err != nil { + return nil, nil, err + } + + env = generateRunEnvironment(name, imageName, options) + env = append(env, "PODMAN_RUNLABEL_NESTED=1") + envmap, err := envLib.ParseSlice(env) + if err != nil { + return nil, nil, err + } + + envmapper := func(k string) string { + switch k { + case "OPT1": + return envmap["OPT1"] + case "OPT2": + return envmap["OPT2"] + case "OPT3": + return envmap["OPT3"] + case "PWD": + // I would prefer to use os.getenv but it appears PWD is not in the os env list. + d, err := os.Getwd() + if err != nil { + logrus.Error("unable to determine current working directory") + return "" + } + return d + } + return "" + } + newS := os.Expand(strings.Join(cmd, " "), envmapper) + cmd, err = shlex.Split(newS) + if err != nil { + return nil, nil, err + } + return cmd, env, nil +} + +// generateCommand takes a label (string) and converts it to an executable command +func generateCommand(command, imageName, name, globalOpts string) ([]string, error) { + var ( + newCommand []string + ) + if name == "" { + name = imageName + } + + cmd, err := shlex.Split(command) + if err != nil { + return nil, err + } + + prog, err := substituteCommand(cmd[0]) + if err != nil { + return nil, err + } + newCommand = append(newCommand, prog) + + for _, arg := range cmd[1:] { + var newArg string + switch arg { + case "IMAGE": + newArg = imageName + case "$IMAGE": + newArg = imageName + case "IMAGE=IMAGE": + newArg = fmt.Sprintf("IMAGE=%s", imageName) + case "IMAGE=$IMAGE": + newArg = fmt.Sprintf("IMAGE=%s", imageName) + case "NAME": + newArg = name + case "NAME=NAME": + newArg = fmt.Sprintf("NAME=%s", name) + case "NAME=$NAME": + newArg = fmt.Sprintf("NAME=%s", name) + case "$NAME": + newArg = name + case "$GLOBAL_OPTS": + newArg = globalOpts + default: + newArg = arg + } + newCommand = append(newCommand, newArg) + } + return newCommand, nil +} + +// GenerateRunEnvironment merges the current environment variables with optional +// environment variables provided by the user +func generateRunEnvironment(name, imageName string, options entities.ContainerRunlabelOptions) []string { + newEnv := os.Environ() + if options.Optional1 != "" { + newEnv = append(newEnv, fmt.Sprintf("OPT1=%s", options.Optional1)) + } + if options.Optional2 != "" { + newEnv = append(newEnv, fmt.Sprintf("OPT2=%s", options.Optional2)) + } + if options.Optional3 != "" { + newEnv = append(newEnv, fmt.Sprintf("OPT3=%s", options.Optional3)) + } + return newEnv +} + +func substituteCommand(cmd string) (string, error) { + var ( + newCommand string + ) + + // Replace cmd with "/proc/self/exe" if "podman" or "docker" is being + // used. If "/usr/bin/docker" is provided, we also sub in podman. + // Otherwise, leave the command unchanged. + if cmd == "podman" || filepath.Base(cmd) == "docker" { + newCommand = "/proc/self/exe" + } else { + newCommand = cmd + } + + // If cmd is an absolute or relative path, check if the file exists. + // Throw an error if it doesn't exist. + if strings.Contains(newCommand, "/") || strings.HasPrefix(newCommand, ".") { + res, err := filepath.Abs(newCommand) + if err != nil { + return "", err + } + if _, err := os.Stat(res); !os.IsNotExist(err) { + return res, nil + } else if err != nil { + return "", err + } + } + + return newCommand, nil +} diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 692fcfa0f..24c62465f 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -86,6 +86,7 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) } } } + return nil } pausePidPath, err := util.GetRootlessPauseProcessPidPath() diff --git a/pkg/domain/infra/tunnel/auto-update.go b/pkg/domain/infra/tunnel/auto-update.go new file mode 100644 index 000000000..fac033050 --- /dev/null +++ b/pkg/domain/infra/tunnel/auto-update.go @@ -0,0 +1,12 @@ +package tunnel + +import ( + "context" + + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" +) + +func (ic *ContainerEngine) AutoUpdate(ctx context.Context) (*entities.AutoUpdateReport, []error) { + return nil, []error{errors.New("not implemented")} +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 227b660f7..49a3069d6 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -14,6 +14,10 @@ import ( "github.com/pkg/errors" ) +func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, image string, args []string, options entities.ContainerRunlabelOptions) error { + return errors.New("not implemented") +} + func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { exists, err := containers.Exists(ic.ClientCxt, nameOrId) return &entities.BoolReport{Value: exists}, err diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go index 7d9a0fce1..9c1f5349a 100644 --- a/pkg/domain/infra/tunnel/manifest.go +++ b/pkg/domain/infra/tunnel/manifest.go @@ -64,32 +64,34 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd return listID, nil } +// FIXME There is no endpoint for annotate and therefor this code is currently invalid // ManifestAnnotate updates an entry of the manifest list func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) { - manifestAnnotateOpts := image.ManifestAnnotateOpts{ - Arch: opts.Arch, - Features: opts.Features, - OS: opts.OS, - OSFeatures: opts.OSFeatures, - OSVersion: opts.OSVersion, - Variant: opts.Variant, - } - if len(opts.Annotation) > 0 { - annotations := make(map[string]string) - for _, annotationSpec := range opts.Annotation { - spec := strings.SplitN(annotationSpec, "=", 2) - if len(spec) != 2 { - return "", errors.Errorf("no value given for annotation %q", spec[0]) - } - annotations[spec[0]] = spec[1] - } - manifestAnnotateOpts.Annotation = annotations - } - updatedListID, err := manifests.Annotate(ctx, names[0], names[1], manifestAnnotateOpts) - if err != nil { - return updatedListID, errors.Wrapf(err, "error annotating %s of manifest list %s", names[1], names[0]) - } - return fmt.Sprintf("%s :%s", updatedListID, names[1]), nil + return "", errors.New("not implemented") + // manifestAnnotateOpts := image.ManifestAnnotateOpts{ + // Arch: opts.Arch, + // Features: opts.Features, + // OS: opts.OS, + // OSFeatures: opts.OSFeatures, + // OSVersion: opts.OSVersion, + // Variant: opts.Variant, + // } + // if len(opts.Annotation) > 0 { + // annotations := make(map[string]string) + // for _, annotationSpec := range opts.Annotation { + // spec := strings.SplitN(annotationSpec, "=", 2) + // if len(spec) != 2 { + // return "", errors.Errorf("no value given for annotation %q", spec[0]) + // } + // annotations[spec[0]] = spec[1] + // } + // manifestAnnotateOpts.Annotation = annotations + // } + // updatedListID, err := manifests.Annotate(ctx, names[0], names[1], manifestAnnotateOpts) + // if err != nil { + // return updatedListID, errors.Wrapf(err, "error annotating %s of manifest list %s", names[1], names[0]) + // } + // return fmt.Sprintf("%s :%s", updatedListID, names[1]), nil } // ManifestRemove removes the digest from manifest list diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index e4bd2991a..a217125f4 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -42,11 +42,13 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat if err != nil { return err } - sig, err := signal.ParseSignalNameOrNumber(stopSignal) - if err != nil { - return err + if stopSignal != "" { + sig, err := signal.ParseSignalNameOrNumber(stopSignal) + if err != nil { + return err + } + s.StopSignal = &sig } - s.StopSignal = &sig } rtc, err := r.GetConfig() @@ -78,6 +80,9 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat } // labels from the image that dont exist already + if len(labels) > 0 && s.Labels == nil { + s.Labels = make(map[string]string) + } for k, v := range labels { if _, exists := s.Labels[k]; !exists { s.Labels[k] = v diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go index 98d59549e..08f1c0300 100644 --- a/pkg/specgen/pod_validate.go +++ b/pkg/specgen/pod_validate.go @@ -33,7 +33,7 @@ func (p *PodSpecGenerator) Validate() error { } // PodNetworkConfig - if err := p.NetNS.validate(); err != nil { + if err := validateNetNS(&p.NetNS); err != nil { return err } if p.NoInfra { @@ -85,10 +85,6 @@ func (p *PodSpecGenerator) Validate() error { return exclusivePodOptions("NoManageHosts", "HostAdd") } - if err := p.NetNS.validate(); err != nil { - return err - } - // Set Defaults if p.NetNS.Value == "" { if rootless.IsRootless() { diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 682f3f215..11976233a 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -54,7 +54,7 @@ type PodNetworkConfig struct { // namespace. This network will, by default, be shared with all // containers in the pod. // Cannot be set to FromContainer and FromPod. - // Setting this to anything except "" conflicts with NoInfra=true. + // Setting this to anything except default conflicts with NoInfra=true. // Defaults to Bridge as root and Slirp as rootless. // Mandatory. NetNS Namespace `json:"netns,omitempty"` diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go index 83fdcabc9..41d61e9d9 100644 --- a/test/e2e/runlabel_test.go +++ b/test/e2e/runlabel_test.go @@ -31,7 +31,6 @@ var _ = Describe("podman container runlabel", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go index 762417a17..d3af44891 100644 --- a/test/e2e/stats_test.go +++ b/test/e2e/stats_test.go @@ -5,6 +5,7 @@ package integration import ( "fmt" "os" + "time" "github.com/containers/libpod/pkg/cgroups" . "github.com/containers/libpod/test/utils" @@ -87,13 +88,24 @@ var _ = Describe("Podman stats", func() { }) It("podman stats with json output", func() { + var found bool session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"stats", "--all", "--no-stream", "--format", "json"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - Expect(session.IsJSONOutputValid()).To(BeTrue()) + for i := 0; i < 5; i++ { + ps := podmanTest.Podman([]string{"ps", "-q"}) + ps.WaitWithDefaultTimeout() + if len(ps.OutputToStringArray()) == 1 { + found = true + break + } + time.Sleep(time.Second) + } + Expect(found).To(BeTrue()) + stats := podmanTest.Podman([]string{"stats", "--all", "--no-stream", "--format", "json"}) + stats.WaitWithDefaultTimeout() + Expect(stats.ExitCode()).To(Equal(0)) + Expect(stats.IsJSONOutputValid()).To(BeTrue()) }) It("podman stats on a container with no net ns", func() { diff --git a/test/system/250-generate-systemd.bats b/test/system/250-generate-systemd.bats deleted file mode 100644 index 6155d6ace..000000000 --- a/test/system/250-generate-systemd.bats +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bats -*- bats -*- -# -# Tests generated configurations for systemd. -# - -load helpers - -# Be extra paranoid in naming to avoid collisions. -SERVICE_NAME="podman_test_$(random_string)" -UNIT_DIR="$HOME/.config/systemd/user" -UNIT_FILE="$UNIT_DIR/$SERVICE_NAME.service" - -# FIXME: the must run as root (because of CI). It's also broken... - -function setup() { - skip_if_not_systemd - skip_if_remote - - basic_setup - - if [ ! -d "$UNIT_DIR" ]; then - mkdir -p "$UNIT_DIR" - systemctl --user daemon-reload - fi -} - -function teardown() { - rm -f "$UNIT_FILE" - systemctl --user stop "$SERVICE_NAME" - basic_teardown -} - -@test "podman generate - systemd - basic" { - run_podman create $IMAGE echo "I'm alive!" - cid="$output" - - run_podman generate systemd $cid > "$UNIT_FILE" - - run systemctl --user start "$SERVICE_NAME" - if [ $status -ne 0 ]; then - die "The systemd service $SERVICE_NAME did not start correctly, output: $output" - fi - - run_podman logs $cid - is "$output" "I'm alive!" "Container output" -} - -# vim: filetype=sh diff --git a/test/system/250-systemd.bats b/test/system/250-systemd.bats new file mode 100644 index 000000000..902a7cad8 --- /dev/null +++ b/test/system/250-systemd.bats @@ -0,0 +1,49 @@ +#!/usr/bin/env bats -*- bats -*- +# +# Tests generated configurations for systemd. +# + +load helpers + +SERVICE_NAME="podman_test_$(random_string)" +UNIT_DIR="/usr/lib/systemd/system" +UNIT_FILE="$UNIT_DIR/$SERVICE_NAME.service" + +function setup() { + skip_if_remote + skip_if_rootless "systemd tests are root-only for now" + + basic_setup +} + +function teardown() { + rm -f "$UNIT_FILE" + systemctl daemon-reload + basic_teardown +} + +@test "podman generate - systemd - basic" { + run_podman create --name keepme --detach busybox:latest top + + run_podman generate systemd --new keepme > "$UNIT_FILE" + run_podman rm keepme + + systemctl daemon-reload + + run systemctl start "$SERVICE_NAME" + if [ $status -ne 0 ]; then + die "Error starting systemd unit $SERVICE_NAME, output: $output" + fi + + run systemctl status "$SERVICE_NAME" + if [ $status -ne 0 ]; then + die "Non-zero status of systemd unit $SERVICE_NAME, output: $output" + fi + + run systemctl stop "$SERVICE_NAME" + if [ $status -ne 0 ]; then + die "Error stopping systemd unit $SERVICE_NAME, output: $output" + fi +} + +# vim: filetype=sh diff --git a/test/system/helpers.bash b/test/system/helpers.bash index 51240edc9..7ec2105d1 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -252,17 +252,6 @@ function skip_if_remote() { fi } -######################### -# skip_if_not_systemd # ...with an optional message -######################### -function skip_if_not_systemd() { - if systemctl --user >/dev/null 2>&1; then - return - fi - - skip "${1:-no systemd or daemon does not respond}" -} - ######### # die # Abort with helpful message ######### diff --git a/vendor/github.com/containers/common/pkg/auth/auth.go b/vendor/github.com/containers/common/pkg/auth/auth.go index 4e0400d23..1aa9f8b31 100644 --- a/vendor/github.com/containers/common/pkg/auth/auth.go +++ b/vendor/github.com/containers/common/pkg/auth/auth.go @@ -34,9 +34,32 @@ func CheckAuthFile(authfile string) error { return nil } +// systemContextWithOptions returns a version of sys +// updated with authFile and certDir values (if they are not ""). +// NOTE: this is a shallow copy that can be used and updated, but may share +// data with the original parameter. +func systemContextWithOptions(sys *types.SystemContext, authFile, certDir string) *types.SystemContext { + if sys != nil { + copy := *sys + sys = © + } else { + sys = &types.SystemContext{} + } + + if authFile != "" { + sys.AuthFilePath = authFile + } + if certDir != "" { + sys.DockerCertPath = certDir + } + return sys +} + // Login implements a “log in” command with the provided opts and args // reading the password from opts.Stdin or the options in opts. func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginOptions, args []string) error { + systemContext = systemContextWithOptions(systemContext, opts.AuthFile, opts.CertDir) + var ( server string err error @@ -172,6 +195,11 @@ func getUserAndPass(opts *LoginOptions, password, userFromAuthFile string) (stri // Logout implements a “log out” command with the provided opts and args func Logout(systemContext *types.SystemContext, opts *LogoutOptions, args []string) error { + if err := CheckAuthFile(opts.AuthFile); err != nil { + return err + } + systemContext = systemContextWithOptions(systemContext, opts.AuthFile, "") + var ( server string err error @@ -194,9 +222,6 @@ func Logout(systemContext *types.SystemContext, opts *LogoutOptions, args []stri } server = getRegistryName(args[0]) } - if err := CheckAuthFile(opts.AuthFile); err != nil { - return err - } if opts.All { if err := config.RemoveAllAuthentication(systemContext); err != nil { diff --git a/vendor/github.com/containers/common/pkg/auth/cli.go b/vendor/github.com/containers/common/pkg/auth/cli.go index 3384b0731..ab033681d 100644 --- a/vendor/github.com/containers/common/pkg/auth/cli.go +++ b/vendor/github.com/containers/common/pkg/auth/cli.go @@ -7,16 +7,19 @@ import ( ) // LoginOptions represents common flags in login -// caller should define bool or optionalBool fields for flags --get-login and --tls-verify +// In addition, the caller should probably provide a --tls-verify flag (that affects the provided +// *types.SystemContest) type LoginOptions struct { // CLI flags managed by the FlagSet returned by GetLoginFlags + // Callers that use GetLoginFlags should not need to touch these values at all; callers that use + // other CLI frameworks should set them based on user input. AuthFile string CertDir string Password string Username string StdinPassword bool + GetLoginSet bool // Options caller can set - GetLoginSet bool // set to true if --get-login is explicitly set Stdin io.Reader // set to os.Stdin Stdout io.Writer // set to os.Stdout AcceptUnspecifiedRegistry bool // set to true if allows login with unspecified registry @@ -25,10 +28,11 @@ type LoginOptions struct { // LogoutOptions represents the results for flags in logout type LogoutOptions struct { // CLI flags managed by the FlagSet returned by GetLogoutFlags + // Callers that use GetLogoutFlags should not need to touch these values at all; callers that use + // other CLI frameworks should set them based on user input. AuthFile string All bool // Options caller can set - Stdin io.Reader // set to os.Stdin Stdout io.Writer // set to os.Stdout AcceptUnspecifiedRegistry bool // set to true if allows logout with unspecified registry } @@ -41,6 +45,7 @@ func GetLoginFlags(flags *LoginOptions) *pflag.FlagSet { fs.StringVarP(&flags.Password, "password", "p", "", "Password for registry") fs.StringVarP(&flags.Username, "username", "u", "", "Username for registry") fs.BoolVar(&flags.StdinPassword, "password-stdin", false, "Take the password from stdin") + fs.BoolVar(&flags.GetLoginSet, "get-login", false, "Return the current login user for the registry") return &fs } diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go index 0f17c27c9..611284476 100644 --- a/vendor/github.com/containers/common/pkg/config/config.go +++ b/vendor/github.com/containers/common/pkg/config/config.go @@ -442,16 +442,6 @@ func readConfigFromFile(path string, config *Config) (*Config, error) { if err != nil { return nil, fmt.Errorf("unable to decode configuration %v: %v", path, err) } - if config.Engine.VolumePath != "" { - config.Engine.VolumePathSet = true - } - if config.Engine.StaticDir != "" { - config.Engine.StaticDirSet = true - } - if config.Engine.TmpDir != "" { - config.Engine.TmpDirSet = true - } - return config, err } diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go index ec52ff706..7debd2984 100644 --- a/vendor/github.com/containers/common/pkg/config/default.go +++ b/vendor/github.com/containers/common/pkg/config/default.go @@ -479,6 +479,8 @@ func (c *Config) PidsLimit() int64 { cgroup2, _ := cgroupv2.Enabled() if cgroup2 { return c.Containers.PidsLimit + } else { + return 0 } } return sysinfo.GetDefaultPidsLimit() diff --git a/vendor/github.com/onsi/gomega/.travis.yml b/vendor/github.com/onsi/gomega/.travis.yml index c6391855a..072fdd2db 100644 --- a/vendor/github.com/onsi/gomega/.travis.yml +++ b/vendor/github.com/onsi/gomega/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.12.x - 1.13.x + - 1.14.x - gotip env: diff --git a/vendor/github.com/onsi/gomega/CHANGELOG.md b/vendor/github.com/onsi/gomega/CHANGELOG.md index 3e9b5961b..35adfb8d7 100644 --- a/vendor/github.com/onsi/gomega/CHANGELOG.md +++ b/vendor/github.com/onsi/gomega/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.10.0 + +### Features +- Add HaveHTTPStatusMatcher (#378) [f335c94] +- Changed matcher for content-type in VerifyJSONRepresenting (#377) [6024f5b] +- Make ghttp usable with x-unit style tests (#376) [c0be499] +- Implement PanicWith matcher (#381) [f8032b4] + ## 1.9.0 ### Features diff --git a/vendor/github.com/onsi/gomega/gomega_dsl.go b/vendor/github.com/onsi/gomega/gomega_dsl.go index 0ab35bc7a..65e837e20 100644 --- a/vendor/github.com/onsi/gomega/gomega_dsl.go +++ b/vendor/github.com/onsi/gomega/gomega_dsl.go @@ -24,7 +24,7 @@ import ( "github.com/onsi/gomega/types" ) -const GOMEGA_VERSION = "1.9.0" +const GOMEGA_VERSION = "1.10.0" const nilFailHandlerPanic = `You are trying to make an assertion, but Gomega's fail handler is nil. If you're using Ginkgo then you probably forgot to put your assertion in an It(). @@ -252,7 +252,7 @@ func Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion { return ConsistentlyWithOffset(0, actual, intervals...) } -// ConsistentlyWithOffset operates like Consistnetly but takes an additional +// ConsistentlyWithOffset operates like Consistently but takes an additional // initial argument to indicate an offset in the call stack. This is useful when building helper // functions that contain matchers. To learn more, read about `ExpectWithOffset`. func ConsistentlyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion { @@ -432,3 +432,32 @@ func toDuration(input interface{}) time.Duration { panic(fmt.Sprintf("%v is not a valid interval. Must be time.Duration, parsable duration string or a number.", input)) } + +// Gomega describes the essential Gomega DSL. This interface allows libraries +// to abstract between the standard package-level function implementations +// and alternatives like *WithT. +type Gomega interface { + Expect(actual interface{}, extra ...interface{}) Assertion + Eventually(actual interface{}, intervals ...interface{}) AsyncAssertion + Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion +} + +type globalFailHandlerGomega struct{} + +// DefaultGomega supplies the standard package-level implementation +var Default Gomega = globalFailHandlerGomega{} + +// Expect is used to make assertions. See documentation for Expect. +func (globalFailHandlerGomega) Expect(actual interface{}, extra ...interface{}) Assertion { + return Expect(actual, extra...) +} + +// Eventually is used to make asynchronous assertions. See documentation for Eventually. +func (globalFailHandlerGomega) Eventually(actual interface{}, extra ...interface{}) AsyncAssertion { + return Eventually(actual, extra...) +} + +// Consistently is used to make asynchronous assertions. See documentation for Consistently. +func (globalFailHandlerGomega) Consistently(actual interface{}, extra ...interface{}) AsyncAssertion { + return Consistently(actual, extra...) +} diff --git a/vendor/github.com/onsi/gomega/matchers.go b/vendor/github.com/onsi/gomega/matchers.go index 11f5b1070..16218d4c5 100644 --- a/vendor/github.com/onsi/gomega/matchers.go +++ b/vendor/github.com/onsi/gomega/matchers.go @@ -390,6 +390,16 @@ func Panic() types.GomegaMatcher { return &matchers.PanicMatcher{} } +//PanicWith succeeds if actual is a function that, when invoked, panics with a specific value. +//Actual must be a function that takes no arguments and returns no results. +// +//By default PanicWith uses Equal() to perform the match, however a +//matcher can be passed in instead: +// Expect(fn).Should(PanicWith(MatchRegexp(`.+Foo$`))) +func PanicWith(expected interface{}) types.GomegaMatcher { + return &matchers.PanicMatcher{Expected: expected} +} + //BeAnExistingFile succeeds if a file exists. //Actual must be a string representing the abs path to the file being checked. func BeAnExistingFile() types.GomegaMatcher { @@ -408,6 +418,15 @@ func BeADirectory() types.GomegaMatcher { return &matchers.BeADirectoryMatcher{} } +//HaveHTTPStatus succeeds if the Status or StatusCode field of an HTTP response matches. +//Actual must be either a *http.Response or *httptest.ResponseRecorder. +//Expected must be either an int or a string. +// Expect(resp).Should(HaveHTTPStatus(http.StatusOK)) // asserts that resp.StatusCode == 200 +// Expect(resp).Should(HaveHTTPStatus("404 Not Found")) // asserts that resp.Status == "404 Not Found" +func HaveHTTPStatus(expected interface{}) types.GomegaMatcher { + return &matchers.HaveHTTPStatusMatcher{Expected: expected} +} + //And succeeds only if all of the given matchers succeed. //The matchers are tried in order, and will fail-fast if one doesn't succeed. // Expect("hi").To(And(HaveLen(2), Equal("hi")) diff --git a/vendor/github.com/onsi/gomega/matchers/have_http_status_matcher.go b/vendor/github.com/onsi/gomega/matchers/have_http_status_matcher.go new file mode 100644 index 000000000..3ce4800b7 --- /dev/null +++ b/vendor/github.com/onsi/gomega/matchers/have_http_status_matcher.go @@ -0,0 +1,42 @@ +package matchers + +import ( + "fmt" + "net/http" + "net/http/httptest" + + "github.com/onsi/gomega/format" +) + +type HaveHTTPStatusMatcher struct { + Expected interface{} +} + +func (matcher *HaveHTTPStatusMatcher) Match(actual interface{}) (success bool, err error) { + var resp *http.Response + switch a := actual.(type) { + case *http.Response: + resp = a + case *httptest.ResponseRecorder: + resp = a.Result() + default: + return false, fmt.Errorf("HaveHTTPStatus matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n%s", format.Object(actual, 1)) + } + + switch e := matcher.Expected.(type) { + case int: + return resp.StatusCode == e, nil + case string: + return resp.Status == e, nil + } + + return false, fmt.Errorf("HaveHTTPStatus matcher must be passed an int or a string. Got:\n%s", format.Object(matcher.Expected, 1)) +} + +func (matcher *HaveHTTPStatusMatcher) FailureMessage(actual interface{}) (message string) { + return format.Message(actual, "to have HTTP status", matcher.Expected) +} + +func (matcher *HaveHTTPStatusMatcher) NegatedFailureMessage(actual interface{}) (message string) { + return format.Message(actual, "not to have HTTP status", matcher.Expected) +} diff --git a/vendor/github.com/onsi/gomega/matchers/panic_matcher.go b/vendor/github.com/onsi/gomega/matchers/panic_matcher.go index 640f4db1a..adc8cee63 100644 --- a/vendor/github.com/onsi/gomega/matchers/panic_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/panic_matcher.go @@ -8,7 +8,8 @@ import ( ) type PanicMatcher struct { - object interface{} + Expected interface{} + object interface{} } func (matcher *PanicMatcher) Match(actual interface{}) (success bool, err error) { @@ -28,7 +29,21 @@ func (matcher *PanicMatcher) Match(actual interface{}) (success bool, err error) defer func() { if e := recover(); e != nil { matcher.object = e - success = true + + if matcher.Expected == nil { + success = true + return + } + + valueMatcher, valueIsMatcher := matcher.Expected.(omegaMatcher) + if !valueIsMatcher { + valueMatcher = &EqualMatcher{Expected: matcher.Expected} + } + + success, err = valueMatcher.Match(e) + if err != nil { + err = fmt.Errorf("PanicMatcher's value matcher failed with:\n%s%s", format.Indent, err.Error()) + } } }() @@ -38,9 +53,62 @@ func (matcher *PanicMatcher) Match(actual interface{}) (success bool, err error) } func (matcher *PanicMatcher) FailureMessage(actual interface{}) (message string) { - return format.Message(actual, "to panic") + if matcher.Expected == nil { + // We wanted any panic to occur, but none did. + return format.Message(actual, "to panic") + } + + if matcher.object == nil { + // We wanted a panic with a specific value to occur, but none did. + switch matcher.Expected.(type) { + case omegaMatcher: + return format.Message(actual, "to panic with a value matching", matcher.Expected) + default: + return format.Message(actual, "to panic with", matcher.Expected) + } + } + + // We got a panic, but the value isn't what we expected. + switch matcher.Expected.(type) { + case omegaMatcher: + return format.Message( + actual, + fmt.Sprintf( + "to panic with a value matching\n%s\nbut panicked with\n%s", + format.Object(matcher.Expected, 1), + format.Object(matcher.object, 1), + ), + ) + default: + return format.Message( + actual, + fmt.Sprintf( + "to panic with\n%s\nbut panicked with\n%s", + format.Object(matcher.Expected, 1), + format.Object(matcher.object, 1), + ), + ) + } } func (matcher *PanicMatcher) NegatedFailureMessage(actual interface{}) (message string) { - return format.Message(actual, fmt.Sprintf("not to panic, but panicked with\n%s", format.Object(matcher.object, 1))) + if matcher.Expected == nil { + // We didn't want any panic to occur, but one did. + return format.Message(actual, fmt.Sprintf("not to panic, but panicked with\n%s", format.Object(matcher.object, 1))) + } + + // We wanted a to ensure a panic with a specific value did not occur, but it did. + switch matcher.Expected.(type) { + case omegaMatcher: + return format.Message( + actual, + fmt.Sprintf( + "not to panic with a value matching\n%s\nbut panicked with\n%s", + format.Object(matcher.Expected, 1), + format.Object(matcher.object, 1), + ), + ) + default: + return format.Message(actual, "not to panic with", matcher.Expected) + } } diff --git a/vendor/modules.txt b/vendor/modules.txt index da97b24fe..b638d1ad0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -82,7 +82,7 @@ github.com/containers/buildah/pkg/secrets github.com/containers/buildah/pkg/supplemented github.com/containers/buildah/pkg/umask github.com/containers/buildah/util -# github.com/containers/common v0.11.0 +# github.com/containers/common v0.11.1 github.com/containers/common/pkg/apparmor github.com/containers/common/pkg/auth github.com/containers/common/pkg/capabilities @@ -375,7 +375,7 @@ github.com/onsi/ginkgo/reporters/stenographer github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty github.com/onsi/ginkgo/types -# github.com/onsi/gomega v1.9.0 +# github.com/onsi/gomega v1.10.0 github.com/onsi/gomega github.com/onsi/gomega/format github.com/onsi/gomega/gbytes |