diff options
35 files changed, 418 insertions, 74 deletions
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index d110fb1b5..6086df297 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -21,7 +21,7 @@ var ( // ChangeCmds is the list of valid Change commands to passed to the Commit call ChangeCmds = []string{"CMD", "ENTRYPOINT", "ENV", "EXPOSE", "LABEL", "ONBUILD", "STOPSIGNAL", "USER", "VOLUME", "WORKDIR"} // LogLevels supported by podman - LogLevels = []string{"debug", "info", "warn", "warning", "error", "fatal", "panic"} + LogLevels = []string{"trace", "debug", "info", "warn", "warning", "error", "fatal", "panic"} ) type completeType int @@ -1009,7 +1009,7 @@ func AutocompleteEventBackend(cmd *cobra.Command, args []string, toComplete stri } // AutocompleteLogLevel - Autocomplete log level options. -// -> "debug", "info", "warn", "error", "fatal", "panic" +// -> "trace", "debug", "info", "warn", "error", "fatal", "panic" func AutocompleteLogLevel(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return LogLevels, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index 6c03fc3c6..f1f7e8b2e 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -102,7 +102,7 @@ func save(cmd *cobra.Command, args []string) (finalErr error) { tags []string succeeded = false ) - if cmd.Flag("compress").Changed && (saveOpts.Format != define.OCIManifestDir && saveOpts.Format != define.V2s2ManifestDir && saveOpts.Format == "") { + if cmd.Flag("compress").Changed && (saveOpts.Format != define.OCIManifestDir && saveOpts.Format != define.V2s2ManifestDir) { return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") } if len(saveOpts.Output) == 0 { diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index ddba5dc0f..30d6d86f0 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -65,6 +65,10 @@ func init() { flags.StringVar(&kubeOptions.Network, networkFlagName, "", "Connect pod to CNI network(s)") _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag) + staticIPFlagName := "ip" + flags.IPSliceVar(&kubeOptions.StaticIPs, staticIPFlagName, nil, "Static IP addresses to assign to the pods") + _ = kubeCmd.RegisterFlagCompletionFunc(staticIPFlagName, completion.AutocompleteNone) + logDriverFlagName := "log-driver" flags.StringVar(&kubeOptions.LogDriver, logDriverFlagName, "", "Logging driver for the container") _ = kubeCmd.RegisterFlagCompletionFunc(logDriverFlagName, common.AutocompleteLogDriver) diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 4527c2646..9e5d2a236 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -416,7 +416,11 @@ func formatError(err error) string { strings.TrimSuffix(err.Error(), ": "+define.ErrOCIRuntime.Error()), ) } else { - message = "Error: " + err.Error() + if logrus.IsLevelEnabled(logrus.TraceLevel) { + message = fmt.Sprintf("Error: %+v", err) + } else { + message = fmt.Sprintf("Error: %v", err) + } } return message } diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 78ff664f7..02b73bdb8 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -12,9 +12,9 @@ %global debug_package %{nil} %endif -# %if ! 0% {?gobuild:1} -%define gobuild(o:) go build -tags="$BUILDTAGS" -ldflags "${LDFLAGS:-} -B 0x$(head -c20 /dev/urandom|od -An -tx1|tr -d ' \\n')" -a -v -x %{?**}; -#% endif +%if ! 0%{?gobuild:1} +%define gobuild(o:) go build -buildmode pie -compiler gc -tags="rpm_crashtraceback ${BUILDTAGS:-}" -ldflags "${LDFLAGS:-} -B 0x$(head -c20 /dev/urandom|od -An -tx1|tr -d ' \n') -extldflags '-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld '" -a -v -x %{?**}; +%endif # podman hack directory %define hackdir %{_builddir}/%{repo}-%{shortcommit0} @@ -30,12 +30,6 @@ %global commit0 #COMMIT# %global shortcommit0 %(c=%{commit0}; echo ${c:0:8}) -# People want conmon packaged with the copr rpm -%global import_path_conmon github.com/containers/conmon -%global git_conmon https://%{import_path_conmon} -%global commit_conmon 41877362fc4685d55e0473d2e4a1cbe5e1debee0 -%global shortcommit_conmon %(c=%{commit_conmon}; echo ${c:0:7}) - Name: podman %if 0%{?fedora} Epoch: 99 @@ -74,7 +68,8 @@ BuildRequires: libselinux-devel BuildRequires: pkgconfig BuildRequires: make BuildRequires: systemd-devel -Requires: skopeo-containers +Requires: containers-common +Requires: conmon Requires: containernetworking-plugins >= 0.6.0-3 Requires: iptables %if 0%{?rhel} < 8 || 0%{?centos} < 8 @@ -371,6 +366,26 @@ BuildArch: noarch Man pages for the %{name} commands %endif +%if 0%{?fedora} && ! 0%{?centos} +%package tests +Summary: Tests for %{name} + +Requires: %{name} = %{epoch}:%{version}-%{release} +Requires: bats +Requires: jq +Requires: skopeo +Requires: nmap-ncat +Requires: httpd-tools +Requires: openssl +Requires: socat +Requires: buildah + +%description tests +%{summary} + +This package contains system tests for %{name} +%endif + %prep %autosetup -Sgit -n %{repo}-%{shortcommit0} @@ -416,18 +431,6 @@ BUILDTAGS=$BUILDTAGS make binaries docs %else BUILDTAGS=$BUILDTAGS make binaries %endif -# build conmon -pushd conmon - -mkdir _output -pushd _output -mkdir -p src/%{provider}.%{provider_tld}/{containers,opencontainers} -ln -s $(dirs +1 -l) src/%{import_path_conmon} -popd - -export BUILDTAGS="selinux seccomp systemd $(%{hackdir}/hack/btrfs_installed_tag.sh) $(%{hackdir}/hack/btrfs_tag.sh)" -BUILDTAGS=$BUILDTAGS make -popd %install install -dp %{buildroot}%{_unitdir} @@ -444,10 +447,6 @@ PODMAN_VERSION=%{version} %{__make} PREFIX=%{buildroot}%{_prefix} ETCDIR=%{build mv pkg/hooks/README.md pkg/hooks/README-hooks.md -# install conmon -install -dp %{buildroot}%{_libexecdir}/%{name} -install -p -m 755 conmon/bin/conmon %{buildroot}%{_libexecdir}/%{name} - # source codes for building projects %if 0%{?with_devel} install -d -p %{buildroot}/%{gopath}/src/%{import_path}/ @@ -467,6 +466,11 @@ for file in $(find . \( -iname "*.go" -or -iname "*.s" \) \! -iname "*_test.go" done %endif +%if 0%{?fedora} && ! 0%{?centos} +install -d -p %{buildroot}/%{_datadir}/%{name}/test/system +cp -pav test/system %{buildroot}/%{_datadir}/%{name}/test/ +%endif + # testing files for this project %if 0%{?with_unit_test} && 0%{?with_devel} install -d -p %{buildroot}/%{gopath}/src/%{import_path}/ @@ -522,7 +526,6 @@ export GOPATH=%{buildroot}/%{gopath}:$(pwd)/vendor:%{gopath} %{_datadir}/bash-completion/completions/* %{_datadir}/zsh/site-functions/* %{_datadir}/fish/vendor_completions.d/* -%{_libexecdir}/%{name}/conmon %config(noreplace) %{_sysconfdir}/cni/net.d/87-%{name}-bridge.conflist %{_unitdir}/podman-auto-update.service %{_unitdir}/podman-auto-update.timer @@ -558,6 +561,13 @@ export GOPATH=%{buildroot}/%{gopath}:$(pwd)/vendor:%{gopath} %{_mandir}/man5/*.5* %endif +%if 0%{?fedora} && ! 0%{?centos} +%files tests +%license LICENSE +%{_datadir}/%{name}/test +%dir %{_datadir}/%{name}/test/system +%endif + %changelog * Sat Aug 4 2018 Dan Walsh <dwalsh@redhat.com> - 0.8.1-1.git6b4ab2a - Bump to v0.8.1 diff --git a/docs/source/Introduction.rst b/docs/source/Introduction.rst index 9fdce6962..3fa86f868 100644 --- a/docs/source/Introduction.rst +++ b/docs/source/Introduction.rst @@ -32,7 +32,7 @@ There’s an old saying that “nobody runs an operating system just to run an o Sometimes we can find a publicly available container image for the exact workload we’re looking for and it will already be packaged exactly how we want. But, more often than not, there’s something that we want to add, remove, or customize. It could be as simple as a configuration setting for security or performance, or as complex as adding a complex workload. Either way, containers make it fairly easy to make the changes we need. -Container Images aren’t actually images, they’re repositories often made up of multiple layers. These layers can easily be added, saved, and shared with others by using a Containerfile (Dockerfile). This single file often contains all the instructions needed to build the new and can easily be shared with others publicly using tools like GitHub. +Container Images aren’t actually images, they’re repositories often made up of multiple layers. These layers can easily be added, saved, and shared with others by using a Containerfile (Dockerfile). This single file often contains all the instructions needed to build a new container image and can easily be shared with others publicly using tools like GitHub. Here's an example of how to build a Nginx web server on top of a Debian base image using the Dockerfile maintained by Nginx and published in GitHub:: diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md index a91c7b6a6..876bfe412 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -692,6 +692,7 @@ Set the architecture variant of the image to be pulled. * [rw|ro] * [z|Z|O] + * [U] * [`[r]shared`|`[r]slave`|`[r]private`] The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR` @@ -707,6 +708,18 @@ You can add the `:ro` or `:rw` suffix to a volume to mount it read-only or read-write mode, respectively. By default, the volumes are mounted read-write. See examples. + `Chowning Volume Mounts` + +By default, Podman does not change the owner and group of source volume +directories mounted. When running using user namespaces, the UID and GID inside +the namespace may correspond to another UID and GID on the host. + +The `:U` suffix tells Podman to use the correct host UID and GID based on the +UID and GID within the namespace, to change recursively the owner and group of +the source volume. + +**Warning** use with caution since this will modify the host filesystem. + `Labeling Volume Mounts` Labeling systems like SELinux require that proper labels are placed on volume diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index fcd5f3e3f..ae6dfe03b 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -1075,9 +1075,14 @@ See examples. `Chowning Volume Mounts` -By default, Podman does not change the owner and group of source volume directories mounted into containers. If a container is created in a new user namespace, the UID and GID in the container may correspond to another UID and GID on the host. - -The `:U` suffix tells Podman to use the correct host UID and GID based on the UID and GID within the container, to change recursively the owner and group of the source volume. +By default, Podman does not change the owner and group of source volume +directories mounted into containers. If a container is created in a new user +namespace, the UID and GID in the container may correspond to another UID and +GID on the host. + +The `:U` suffix tells Podman to use the correct host UID and GID based on the +UID and GID within the container, to change recursively the owner and group of +the source volume. **Warning** use with caution since this will modify the host filesystem. diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md index 91899a8bd..1074c27f8 100644 --- a/docs/source/markdown/podman-play-kube.1.md +++ b/docs/source/markdown/podman-play-kube.1.md @@ -62,6 +62,10 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. +#### **\-\-ip**=*IP address* + +Assign a static ip address to the pod. This option can be specified several times when play kube creates more than one pod. + #### **\-\-log-driver**=driver Set logging driver for all created containers. diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 3fad9bf64..40b271828 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -1155,9 +1155,14 @@ read-write mode, respectively. By default, the volumes are mounted read-write. `Chowning Volume Mounts` -By default, Podman does not change the owner and group of source volume directories mounted into containers. If a container is created in a new user namespace, the UID and GID in the container may correspond to another UID and GID on the host. - -The `:U` suffix tells Podman to use the correct host UID and GID based on the UID and GID within the container, to change recursively the owner and group of the source volume. +By default, Podman does not change the owner and group of source volume +directories mounted into containers. If a container is created in a new user +namespace, the UID and GID in the container may correspond to another UID and +GID on the host. + +The `:U` suffix tells Podman to use the correct host UID and GID based on the +UID and GID within the container, to change recursively the owner and group of +the source volume. **Warning** use with caution since this will modify the host filesystem. @@ -51,7 +51,7 @@ require ( github.com/opencontainers/selinux v1.8.0 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 - github.com/rootless-containers/rootlesskit v0.14.1 + github.com/rootless-containers/rootlesskit v0.14.2 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.1.3 github.com/spf13/pflag v1.0.5 @@ -713,8 +713,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rootless-containers/rootlesskit v0.14.1 h1:lvyG8XLYOiHPoSjWekgqY4MkNZJ+218KBOLfw9kB3Hk= -github.com/rootless-containers/rootlesskit v0.14.1/go.mod h1:nV3TpRISvwhZQSwo0nmQQnxjCxXr3mvrMi0oASLvzcg= +github.com/rootless-containers/rootlesskit v0.14.2 h1:jmsSyNyRG0QdWc3usppt5jEy5qOheeUsIINcymPrOFg= +github.com/rootless-containers/rootlesskit v0.14.2/go.mod h1:nV3TpRISvwhZQSwo0nmQQnxjCxXr3mvrMi0oASLvzcg= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 12727901a..0e41fde44 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -134,10 +134,11 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ( } nameOrID := img.ID() s, err := img.Size(ctx) - imgSize := *s + imgSize := uint64(0) if err != nil { logrus.Warnf("Failed to collect image size for: %s, %s", nameOrID, err) - imgSize = 0 + } else { + imgSize = *s } if err := img.Remove(ctx, false); err != nil { if errors.Cause(err) == storage.ErrImageUsedByContainer { diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index e0c79e5a7..ec40fdd2d 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -464,15 +464,16 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { var ( imageID string - failed bool + success bool ) runCtx, cancel := context.WithCancel(context.Background()) go func() { defer cancel() imageID, _, err = runtime.Build(r.Context(), buildOptions, query.Dockerfile) - if err != nil { - failed = true + if err == nil { + success = true + } else { stderr.Write([]byte(err.Error() + "\n")) } }() @@ -534,7 +535,8 @@ loop: } flush() case <-runCtx.Done(): - if !failed { + flush() + if success { if !utils.IsLibpodRequest(r) { m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID) if err := enc.Encode(m); err != nil { diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go index eba5386b6..96f572a8b 100644 --- a/pkg/api/handlers/libpod/play.go +++ b/pkg/api/handlers/libpod/play.go @@ -3,6 +3,7 @@ package libpod import ( "io" "io/ioutil" + "net" "net/http" "os" @@ -20,10 +21,11 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - Network string `schema:"network"` - TLSVerify bool `schema:"tlsVerify"` - LogDriver string `schema:"logDriver"` - Start bool `schema:"start"` + Network string `schema:"network"` + TLSVerify bool `schema:"tlsVerify"` + LogDriver string `schema:"logDriver"` + Start bool `schema:"start"` + StaticIPs []string `schema:"staticIPs"` }{ TLSVerify: true, Start: true, @@ -35,6 +37,17 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { return } + staticIPs := make([]net.IP, 0, len(query.StaticIPs)) + for _, ipString := range query.StaticIPs { + ip := net.ParseIP(ipString) + if ip == nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Errorf("Invalid IP address %s", ipString)) + return + } + staticIPs = append(staticIPs, ip) + } + // Fetch the K8s YAML file from the body, and copy it to a temp file. tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml") if err != nil { @@ -71,6 +84,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { Network: query.Network, Quiet: true, LogDriver: query.LogDriver, + StaticIPs: staticIPs, } if _, found := r.URL.Query()["tlsVerify"]; found { options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) diff --git a/pkg/api/server/register_play.go b/pkg/api/server/register_play.go index d21029db5..da37abb70 100644 --- a/pkg/api/server/register_play.go +++ b/pkg/api/server/register_play.go @@ -34,6 +34,12 @@ func (s *APIServer) registerPlayHandlers(r *mux.Router) error { // type: boolean // default: true // description: Start the pod after creating it. + // - in: query + // name: staticIPs + // type: array + // description: Static IPs used for the pods. + // items: + // type: string // - in: body // name: request // description: Kubernetes YAML file. diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index 34d6cee05..c0e5706a5 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -340,6 +340,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO re := regexp.MustCompile(`[0-9a-f]{12}`) var id string + var mErr error for { var s struct { Stream string `json:"stream,omitempty"` @@ -347,11 +348,21 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } if err := dec.Decode(&s); err != nil { if errors.Is(err, io.EOF) { - return &entities.BuildReport{ID: id}, nil + if mErr == nil && id == "" { + mErr = errors.New("stream dropped, unexpected failure") + } + break } s.Error = err.Error() + "\n" } + select { + case <-response.Request.Context().Done(): + return &entities.BuildReport{ID: id}, mErr + default: + // non-blocking select + } + switch { case s.Stream != "": stdout.Write([]byte(s.Stream)) @@ -359,11 +370,12 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO id = strings.TrimSuffix(s.Stream, "\n") } case s.Error != "": - return nil, errors.New(s.Error) + mErr = errors.New(s.Error) default: return &entities.BuildReport{ID: id}, errors.New("failed to parse build results stream, unexpected input") } } + return &entities.BuildReport{ID: id}, mErr } func nTar(excludes []string, sources ...string) (io.ReadCloser, error) { diff --git a/pkg/bindings/play/types.go b/pkg/bindings/play/types.go index 5fb9a4d41..6598ec3c2 100644 --- a/pkg/bindings/play/types.go +++ b/pkg/bindings/play/types.go @@ -1,5 +1,7 @@ package play +import "net" + //go:generate go run ../generator/generator.go KubeOptions // KubeOptions are optional options for replaying kube YAML files type KubeOptions struct { @@ -23,6 +25,8 @@ type KubeOptions struct { // SeccompProfileRoot - path to a directory containing seccomp // profiles. SeccompProfileRoot *string + // StaticIPs - Static IP address used by the pod(s). + StaticIPs *[]net.IP // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. ConfigMaps *[]string // LogDriver for the container. For example: journald diff --git a/pkg/bindings/play/types_kube_options.go b/pkg/bindings/play/types_kube_options.go index 78396a090..a1786f553 100644 --- a/pkg/bindings/play/types_kube_options.go +++ b/pkg/bindings/play/types_kube_options.go @@ -1,6 +1,7 @@ package play import ( + "net" "net/url" "github.com/containers/podman/v3/pkg/bindings/internal/util" @@ -164,6 +165,22 @@ func (o *KubeOptions) GetSeccompProfileRoot() string { return *o.SeccompProfileRoot } +// WithStaticIPs +func (o *KubeOptions) WithStaticIPs(value []net.IP) *KubeOptions { + v := &value + o.StaticIPs = v + return o +} + +// GetStaticIPs +func (o *KubeOptions) GetStaticIPs() []net.IP { + var staticIPs []net.IP + if o.StaticIPs == nil { + return staticIPs + } + return *o.StaticIPs +} + // WithConfigMaps func (o *KubeOptions) WithConfigMaps(value []string) *KubeOptions { v := &value diff --git a/pkg/bindings/test/networks_test.go b/pkg/bindings/test/networks_test.go index df7d7cd1c..b53fc4bd3 100644 --- a/pkg/bindings/test/networks_test.go +++ b/pkg/bindings/test/networks_test.go @@ -2,10 +2,12 @@ package test_bindings import ( "context" + "fmt" "net/http" "time" "github.com/containers/podman/v3/pkg/bindings" + "github.com/containers/podman/v3/pkg/bindings/containers" "github.com/containers/podman/v3/pkg/bindings/network" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -116,4 +118,88 @@ var _ = Describe("Podman networks", func() { Expect(err).To(BeNil()) Expect(data[0]["name"]).To(Equal(name)) }) + + It("list networks", func() { + // create a bunch of named networks and make verify with list + netNames := []string{"homer", "bart", "lisa", "maggie", "marge"} + for i := 0; i < 5; i++ { + opts := network.CreateOptions{ + Name: &netNames[i], + } + _, err = network.Create(connText, &opts) + Expect(err).To(BeNil()) + } + list, err := network.List(connText, nil) + Expect(err).To(BeNil()) + Expect(len(list)).To(BeNumerically(">=", 5)) + for _, n := range list { + if n.Name != "podman" { + Expect(StringInSlice(n.Name, netNames)).To(BeTrue()) + } + } + + // list with bad filter should be 500 + filters := make(map[string][]string) + filters["foobar"] = []string{"1234"} + options := new(network.ListOptions).WithFilters(filters) + _, err = network.List(connText, options) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + + // filter list with success + filters = make(map[string][]string) + filters["name"] = []string{"homer"} + options = new(network.ListOptions).WithFilters(filters) + list, err = network.List(connText, options) + Expect(err).To(BeNil()) + Expect(len(list)).To(BeNumerically("==", 1)) + Expect(list[0].Name).To(Equal("homer")) + }) + + It("remove network", func() { + // removing a noName network should result in 404 + _, err := network.Remove(connText, "noName", nil) + code, err := bindings.CheckResponseCode(err) + Expect(err).To(BeNil()) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + + // Removing an unused network should work + name := "unused" + opts := network.CreateOptions{ + Name: &name, + } + _, err = network.Create(connText, &opts) + Expect(err).To(BeNil()) + report, err := network.Remove(connText, name, nil) + Expect(err).To(BeNil()) + Expect(report[0].Name).To(Equal(name)) + + // Removing a network that is being used without force should be 500 + name = "used" + opts = network.CreateOptions{ + Name: &name, + } + _, err = network.Create(connText, &opts) + Expect(err).To(BeNil()) + + // Start container and wait + container := "ntest" + session := bt.runPodman([]string{"run", "-dt", fmt.Sprintf("--network=%s", name), "--name", container, alpine.name, "top"}) + session.Wait(45) + Expect(session.ExitCode()).To(BeZero()) + + _, err = network.Remove(connText, name, nil) + code, err = bindings.CheckResponseCode(err) + Expect(err).To(BeNil()) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + + // Removing with a network in use with force should work with a stopped container + err = containers.Stop(connText, container, new(containers.StopOptions).WithTimeout(0)) + Expect(err).To(BeNil()) + options := new(network.RemoveOptions).WithForce(true) + report, err = network.Remove(connText, name, options) + Expect(err).To(BeNil()) + Expect(report[0].Name).To(Equal(name)) + }) }) diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go index 91f6444cc..14bda114e 100644 --- a/pkg/bindings/test/volumes_test.go +++ b/pkg/bindings/test/volumes_test.go @@ -83,7 +83,8 @@ var _ = Describe("Podman volumes", func() { It("remove volume", func() { // removing a bogus volume should result in 404 err := volumes.Remove(connText, "foobar", nil) - code, _ := bindings.CheckResponseCode(err) + code, err := bindings.CheckResponseCode(err) + Expect(err).To(BeNil()) Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Removing an unused volume should work @@ -97,9 +98,12 @@ var _ = Describe("Podman volumes", func() { Expect(err).To(BeNil()) session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/foobar", vol.Name), "--name", "vtest", alpine.name, "top"}) session.Wait(45) + Expect(session.ExitCode()).To(BeZero()) + err = volumes.Remove(connText, vol.Name, nil) Expect(err).ToNot(BeNil()) - code, _ = bindings.CheckResponseCode(err) + code, err = bindings.CheckResponseCode(err) + Expect(err).To(BeNil()) Expect(code).To(BeNumerically("==", http.StatusConflict)) // Removing with a volume in use with force should work with a stopped container diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go index cd8bb9506..c69bb0867 100644 --- a/pkg/domain/entities/play.go +++ b/pkg/domain/entities/play.go @@ -1,6 +1,10 @@ package entities -import "github.com/containers/image/v5/types" +import ( + "net" + + "github.com/containers/image/v5/types" +) // PlayKubeOptions controls playing kube YAML files. type PlayKubeOptions struct { @@ -24,6 +28,8 @@ type PlayKubeOptions struct { // SeccompProfileRoot - path to a directory containing seccomp // profiles. SeccompProfileRoot string + // StaticIPs - Static IP address used by the pod(s). + StaticIPs []net.IP // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. ConfigMaps []string // LogDriver for the container. For example: journald diff --git a/pkg/domain/entities/types/auth.go b/pkg/domain/entities/types/auth.go index ddf15bb18..7f2480173 100644 --- a/pkg/domain/entities/types/auth.go +++ b/pkg/domain/entities/types/auth.go @@ -1,4 +1,5 @@ -package types // import "github.com/docker/docker/api/types" +// copied from github.com/docker/docker/api/types +package types // AuthConfig contains authorization information for connecting to a Registry type AuthConfig struct { diff --git a/pkg/domain/entities/types/types.go b/pkg/domain/entities/types/types.go index 77834c0cb..7dc785078 100644 --- a/pkg/domain/entities/types/types.go +++ b/pkg/domain/entities/types/types.go @@ -1,4 +1,5 @@ -package types // import "github.com/docker/docker/api/types" +// copied from github.com/docker/docker/api/types +package types // ComponentVersion describes the version information for a specific component. type ComponentVersion struct { diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 52f759f13..4a13a8029 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -16,6 +16,7 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/specgen/generate" "github.com/containers/podman/v3/pkg/specgen/generate/kube" "github.com/containers/podman/v3/pkg/util" @@ -50,6 +51,8 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en return nil, errors.Wrapf(err, "unable to sort kube kinds in %q", path) } + ipIndex := 0 + // create pod on each document if it is a pod or deployment // any other kube kind will be skipped for _, document := range documentList { @@ -70,7 +73,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en podTemplateSpec.ObjectMeta = podYAML.ObjectMeta podTemplateSpec.Spec = podYAML.Spec - r, err := ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options) + r, err := ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options, &ipIndex) if err != nil { return nil, err } @@ -84,7 +87,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en return nil, errors.Wrapf(err, "unable to read YAML %q as Kube Deployment", path) } - r, err := ic.playKubeDeployment(ctx, &deploymentYAML, options) + r, err := ic.playKubeDeployment(ctx, &deploymentYAML, options, &ipIndex) if err != nil { return nil, err } @@ -118,7 +121,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en return report, nil } -func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAML *v1apps.Deployment, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { +func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAML *v1apps.Deployment, options entities.PlayKubeOptions, ipIndex *int) (*entities.PlayKubeReport, error) { var ( deploymentName string podSpec v1.PodTemplateSpec @@ -140,7 +143,7 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM // create "replicas" number of pods for i = 0; i < numReplicas; i++ { podName := fmt.Sprintf("%s-pod-%d", deploymentName, i) - podReport, err := ic.playKubePod(ctx, podName, &podSpec, options) + podReport, err := ic.playKubePod(ctx, podName, &podSpec, options, ipIndex) if err != nil { return nil, errors.Wrapf(err, "error encountered while bringing up pod %s", podName) } @@ -149,7 +152,7 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM return &report, nil } -func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { +func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions, ipIndex *int) (*entities.PlayKubeReport, error) { var ( registryCreds *types.DockerAuthConfig writer io.Writer @@ -190,9 +193,17 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY // networks. networks := strings.Split(options.Network, ",") logrus.Debugf("Pod joining CNI networks: %v", networks) + p.NetNS.NSMode = specgen.Bridge p.CNINetworks = append(p.CNINetworks, networks...) } } + if len(options.StaticIPs) > *ipIndex { + p.StaticIP = &options.StaticIPs[*ipIndex] + *ipIndex++ + } else if len(options.StaticIPs) > 0 { + // only warn if the user has set at least one ip ip + logrus.Warn("No more static ips left using a random one") + } // Create the Pod pod, err := generate.MakePod(p, ic.Libpod) @@ -300,6 +311,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY RestartPolicy: ctrRestartPolicy, NetNSIsHost: p.NetNS.IsHost(), SecretsManager: secretsManager, + LogDriver: options.LogDriver, } specGen, err := kube.ToSpecGen(ctx, &specgenOpts) if err != nil { diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go index 9f9076114..e52e1a1f7 100644 --- a/pkg/domain/infra/tunnel/play.go +++ b/pkg/domain/infra/tunnel/play.go @@ -11,7 +11,7 @@ import ( func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, opts entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { options := new(play.KubeOptions).WithAuthfile(opts.Authfile).WithUsername(opts.Username).WithPassword(opts.Password) options.WithCertDir(opts.CertDir).WithQuiet(opts.Quiet).WithSignaturePolicy(opts.SignaturePolicy).WithConfigMaps(opts.ConfigMaps) - options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot) + options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot).WithStaticIPs(opts.StaticIPs) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { options.WithSkipTLSVerify(s == types.OptionalBoolTrue) diff --git a/pkg/errorhandling/errorhandling.go b/pkg/errorhandling/errorhandling.go index b1923be98..9dc545ebb 100644 --- a/pkg/errorhandling/errorhandling.go +++ b/pkg/errorhandling/errorhandling.go @@ -24,6 +24,9 @@ func JoinErrors(errs []error) error { if finalErr == nil { return finalErr } + if len(multiE.WrappedErrors()) == 1 && logrus.IsLevelEnabled(logrus.TraceLevel) { + return multiE.WrappedErrors()[0] + } return errors.New(strings.TrimSpace(finalErr.Error())) } diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 31ed3fd7c..7aeec9d41 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -98,6 +98,8 @@ type CtrSpecGenOptions struct { NetNSIsHost bool // SecretManager to access the secrets SecretsManager *secrets.SecretsManager + // LogDriver which should be used for the container + LogDriver string } func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGenerator, error) { @@ -115,6 +117,10 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener s.Pod = opts.PodID + s.LogConfiguration = &specgen.LogConfig{ + Driver: opts.LogDriver, + } + setupSecurityContext(s, opts.Container) // Since we prefix the container name with pod name to work-around the uniqueness requirement, diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go index e135f4728..8066834f7 100644 --- a/pkg/specgen/generate/storage.go +++ b/pkg/specgen/generate/storage.go @@ -57,10 +57,13 @@ func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Ru } for _, m := range s.Mounts { - if _, ok := unifiedMounts[m.Destination]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified mounts - multiple mounts at %q", m.Destination) + // Ensure that mount dest is clean, so that it can be + // compared against named volumes and avoid duplicate mounts. + cleanDestination := filepath.Clean(m.Destination) + if _, ok := unifiedMounts[cleanDestination]; ok { + return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified mounts - multiple mounts at %q", cleanDestination) } - unifiedMounts[m.Destination] = m + unifiedMounts[cleanDestination] = m } for _, m := range commonMounts { diff --git a/test/compose/test-compose b/test/compose/test-compose index c4c484190..46ca80321 100755 --- a/test/compose/test-compose +++ b/test/compose/test-compose @@ -173,15 +173,16 @@ function test_port() { if [ $curl_rc -ne 0 ]; then _show_ok 0 "$testname - curl (port $port) failed with status $curl_rc" - # FIXME: is this useful? What else can we do to diagnose? - echo "# docker-compose logs:" - docker-compose logs echo "# podman ps -a:" $PODMAN_BIN --root $WORKDIR/root --runroot $WORKDIR/runroot ps -a if type -p ss; then echo "# ss -tulpn:" ss -tulpn + echo "# podman unshare --rootless-cni ss -tulpn:" + $PODMAN_BIN --root $WORKDIR/root --runroot $WORKDIR/runroot unshare --rootless-cni ss -tulpn fi + echo "# cat $WORKDIR/server.log:" + cat $WORKDIR/server.log return fi @@ -212,6 +213,7 @@ function start_service() { cp /etc/cni/net.d/*podman*conflist $WORKDIR/cni/ $PODMAN_BIN \ + --log-level debug \ --root $WORKDIR/root \ --runroot $WORKDIR/runroot \ --cgroup-manager=systemd \ diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 41afd9f75..f89da4c05 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -12,6 +12,7 @@ import ( "github.com/containers/podman/v3/pkg/util" . "github.com/containers/podman/v3/test/utils" + "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/opencontainers/selinux/go-selinux" @@ -1716,6 +1717,38 @@ spec: } }) + It("podman play kube --ip", func() { + var i, numReplicas int32 + numReplicas = 3 + deployment := getDeployment(withReplicas(numReplicas)) + err := generateKubeYaml("deployment", deployment, kubeYaml) + Expect(err).To(BeNil()) + + net := "playkube" + stringid.GenerateNonCryptoID() + session := podmanTest.Podman([]string{"network", "create", "--subnet", "10.25.31.0/24", net}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeCNINetwork(net) + Expect(session.ExitCode()).To(BeZero()) + + ips := []string{"10.25.31.5", "10.25.31.10", "10.25.31.15"} + playArgs := []string{"play", "kube", "--network", net} + for _, ip := range ips { + playArgs = append(playArgs, "--ip", ip) + } + + kube := podmanTest.Podman(append(playArgs, kubeYaml)) + kube.WaitWithDefaultTimeout() + Expect(kube.ExitCode()).To(Equal(0)) + + podNames := getPodNamesInDeployment(deployment) + for i = 0; i < numReplicas; i++ { + inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&podNames[i]), "--format", "{{ .NetworkSettings.Networks." + net + ".IPAddress }}"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + Expect(inspect.OutputToString()).To(Equal(ips[i])) + } + }) + It("podman play kube test with network portbindings", func() { ip := "127.0.0.100" port := "5000" @@ -1861,6 +1894,54 @@ spec: Expect(inspect.OutputToString()).To(ContainSubstring(correct)) }) + It("podman play kube test duplicate volume destination between host path and image volumes", func() { + // Create host test directory and file + testdir := "testdir" + testfile := "testfile" + + hostPathDir := filepath.Join(tempdir, testdir) + err := os.Mkdir(hostPathDir, 0755) + Expect(err).To(BeNil()) + + hostPathDirFile := filepath.Join(hostPathDir, testfile) + f, err := os.Create(hostPathDirFile) + Expect(err).To(BeNil()) + f.Close() + + // Create container image with named volume + containerfile := fmt.Sprintf(` +FROM %s +VOLUME %s`, ALPINE, hostPathDir+"/") + + image := "podman-kube-test:podman" + podmanTest.BuildImage(containerfile, image, "false") + + // Create and play kube pod + ctr := getCtr(withVolumeMount(hostPathDir+"/", false), withImage(image)) + pod := getPod(withCtr(ctr), withVolume(getHostPathVolume("Directory", hostPathDir+"/"))) + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "ls", hostPathDir + "/" + testfile}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod)}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + + // If two volumes are specified and share the same destination, + // only one will be mounted. Host path volumes take precedence. + ctrJSON := inspect.InspectContainerToJSON() + Expect(len(ctrJSON[0].Mounts)).To(Equal(1)) + Expect(ctrJSON[0].Mounts[0].Type).To(Equal("bind")) + + }) + It("podman play kube test with PersistentVolumeClaim volume", func() { volumeName := "namedVolume" @@ -1970,7 +2051,6 @@ MemoryReservation: {{ .HostConfig.MemoryReservation }}`}) }) It("podman play kube applies log driver to containers", func() { - Skip("need to verify images have correct packages for journald") pod := getPod() err := generateKubeYaml("pod", pod, kubeYaml) Expect(err).To(BeNil()) diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go index f80e7f506..69184649f 100644 --- a/test/e2e/save_test.go +++ b/test/e2e/save_test.go @@ -111,6 +111,24 @@ var _ = Describe("Podman save", func() { Expect(save.ExitCode()).To(Equal(0)) }) + It("podman save to directory with --compress but not use docker-dir and oci-dir", func() { + if rootless.IsRootless() && podmanTest.RemoteTest { + Skip("Requires a fix in containers image for chown/lchown") + } + outdir := filepath.Join(podmanTest.TempDir, "save") + + save := podmanTest.Podman([]string{"save", "--compress", "--format", "docker-archive", "-o", outdir, ALPINE}) + save.WaitWithDefaultTimeout() + // should not be 0 + Expect(save.ExitCode()).ToNot(Equal(0)) + + save = podmanTest.Podman([]string{"save", "--compress", "--format", "oci-archive", "-o", outdir, ALPINE}) + save.WaitWithDefaultTimeout() + // should not be 0 + Expect(save.ExitCode()).ToNot(Equal(0)) + + }) + It("podman save bad filename", func() { outdir := filepath.Join(podmanTest.TempDir, "save:colon") diff --git a/test/system/001-basic.bats b/test/system/001-basic.bats index 081bb1171..35107f0a0 100644 --- a/test/system/001-basic.bats +++ b/test/system/001-basic.bats @@ -111,4 +111,17 @@ function setup() { is "$output" "you found me" "sample invocation of 'jq'" } +@test "podman --log-level recognizes log levels" { + run_podman 1 --log-level=telepathic info + is "$output" 'Log Level "telepathic" is not supported.*' + run_podman --log-level=trace info + run_podman --log-level=debug info + run_podman --log-level=info info + run_podman --log-level=warn info + run_podman --log-level=warning info + run_podman --log-level=error info + run_podman --log-level=fatal info + run_podman --log-level=panic info +} + # vim: filetype=sh diff --git a/test/system/070-build.bats b/test/system/070-build.bats index 5a887c71e..d4017ae01 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -691,8 +691,16 @@ RUN echo $random_string EOF run_podman 125 build -t build_test --pull-never $tmpdir - is "$output" ".* pull policy is .never. but .* could not be found locally" \ - "--pull-never fails with expected error message" + # FIXME: this is just ridiculous. Even after #10030 and #10034, Ubuntu + # remote *STILL* flakes this test! It fails with the correct exit status, + # but the error output is 'Error: stream dropped, unexpected failure' + # Let's just stop checking on podman-remote. As long as it exits 125, + # we're happy. + if ! is_remote; then + is "$output" \ + ".* pull policy is .never. but .* could not be found locally" \ + "--pull-never fails with expected error message" + fi } @test "podman build --logfile test" { diff --git a/vendor/modules.txt b/vendor/modules.txt index 1bf189b86..431a21c8a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -542,7 +542,7 @@ github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util # github.com/rivo/uniseg v0.2.0 github.com/rivo/uniseg -# github.com/rootless-containers/rootlesskit v0.14.1 +# github.com/rootless-containers/rootlesskit v0.14.2 github.com/rootless-containers/rootlesskit/pkg/api github.com/rootless-containers/rootlesskit/pkg/msgutil github.com/rootless-containers/rootlesskit/pkg/port |