summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/images/save.go2
-rw-r--r--cmd/podman/play/kube.go4
-rw-r--r--docs/source/Introduction.rst2
-rw-r--r--docs/source/markdown/podman-play-kube.1.md4
-rw-r--r--libpod/diff.go12
-rw-r--r--libpod/image/prune.go5
-rw-r--r--pkg/api/handlers/libpod/images.go10
-rw-r--r--pkg/api/handlers/libpod/play.go22
-rw-r--r--pkg/api/server/register_play.go6
-rw-r--r--pkg/bindings/play/types.go4
-rw-r--r--pkg/bindings/play/types_kube_options.go17
-rw-r--r--pkg/bindings/test/networks_test.go86
-rw-r--r--pkg/bindings/test/volumes_test.go8
-rw-r--r--pkg/domain/entities/play.go8
-rw-r--r--pkg/domain/infra/abi/play.go21
-rw-r--r--pkg/domain/infra/tunnel/play.go2
-rw-r--r--pkg/specgen/generate/storage.go9
-rw-r--r--test/e2e/play_kube_test.go81
-rw-r--r--test/e2e/save_test.go22
-rw-r--r--test/system/070-build.bats12
20 files changed, 301 insertions, 36 deletions
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/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-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/libpod/diff.go b/libpod/diff.go
index 36d60b838..df1acf4bb 100644
--- a/libpod/diff.go
+++ b/libpod/diff.go
@@ -1,8 +1,6 @@
package libpod
import (
- "io"
-
"github.com/containers/podman/v3/libpod/layers"
"github.com/containers/storage/pkg/archive"
"github.com/pkg/errors"
@@ -46,16 +44,6 @@ func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) {
return rchanges, err
}
-// ApplyDiffTarStream applies the changes stored in 'diff' to the layer 'to'
-func (r *Runtime) ApplyDiffTarStream(to string, diff io.Reader) error {
- toLayer, err := r.getLayerID(to)
- if err != nil {
- return err
- }
- _, err = r.store.ApplyDiff(toLayer, diff)
- return err
-}
-
// GetLayerID gets a full layer id given a full or partial id
// If the id matches a container or image, the id of the top layer is returned
// If the id matches a layer, the top layer id is returned
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/libpod/images.go b/pkg/api/handlers/libpod/images.go
index 158babcdc..92882cc40 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -270,6 +270,16 @@ func ExportImages(w http.ResponseWriter, r *http.Request) {
return
}
+ // if format is dir, server will save to an archive
+ // the client will unArchive after receive the archive file
+ // so must convert is at here
+ switch query.Format {
+ case define.OCIManifestDir:
+ query.Format = define.OCIArchive
+ case define.V2s2ManifestDir:
+ query.Format = define.V2s2Archive
+ }
+
switch query.Format {
case define.V2s2Archive, define.OCIArchive:
tmpfile, err := ioutil.TempFile("", "api.tar")
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/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/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index 6eecef2de..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)
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/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/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index e479b88cc..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"
diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go
index 5ddd5efc8..69184649f 100644
--- a/test/e2e/save_test.go
+++ b/test/e2e/save_test.go
@@ -79,7 +79,7 @@ var _ = Describe("Podman save", func() {
})
It("podman save to directory with oci format", func() {
- if rootless.IsRootless() && podmanTest.RemoteTest {
+ if rootless.IsRootless() {
Skip("Requires a fix in containers image for chown/lchown")
}
outdir := filepath.Join(podmanTest.TempDir, "save")
@@ -90,7 +90,7 @@ var _ = Describe("Podman save", func() {
})
It("podman save to directory with v2s2 docker format", func() {
- if rootless.IsRootless() && podmanTest.RemoteTest {
+ if rootless.IsRootless() {
Skip("Requires a fix in containers image for chown/lchown")
}
outdir := filepath.Join(podmanTest.TempDir, "save")
@@ -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/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" {