diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/handlers/compat/images_build.go | 10 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/play.go | 22 | ||||
-rw-r--r-- | pkg/api/server/register_play.go | 6 | ||||
-rw-r--r-- | pkg/bindings/images/build.go | 16 | ||||
-rw-r--r-- | pkg/bindings/play/types.go | 4 | ||||
-rw-r--r-- | pkg/bindings/play/types_kube_options.go | 17 | ||||
-rw-r--r-- | pkg/bindings/test/networks_test.go | 86 | ||||
-rw-r--r-- | pkg/bindings/test/volumes_test.go | 8 | ||||
-rw-r--r-- | pkg/domain/entities/play.go | 8 | ||||
-rw-r--r-- | pkg/domain/entities/types/auth.go | 3 | ||||
-rw-r--r-- | pkg/domain/entities/types/types.go | 3 | ||||
-rw-r--r-- | pkg/domain/infra/abi/play.go | 22 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/play.go | 2 | ||||
-rw-r--r-- | pkg/errorhandling/errorhandling.go | 3 | ||||
-rw-r--r-- | pkg/specgen/generate/kube/kube.go | 6 | ||||
-rw-r--r-- | pkg/specgen/generate/storage.go | 9 |
16 files changed, 201 insertions, 24 deletions
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 { |