aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/play/kube.go48
-rw-r--r--docs/source/markdown/podman-play-kube.1.md16
-rw-r--r--pkg/api/handlers/libpod/play.go44
-rw-r--r--pkg/api/server/register_play.go15
-rw-r--r--pkg/bindings/play/play.go29
-rw-r--r--pkg/bindings/play/types.go4
-rw-r--r--pkg/domain/entities/engine_container.go1
-rw-r--r--pkg/domain/entities/play.go13
-rw-r--r--pkg/domain/infra/abi/play.go70
-rw-r--r--pkg/domain/infra/tunnel/play.go4
-rw-r--r--test/e2e/play_kube_test.go64
11 files changed, 302 insertions, 6 deletions
diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go
index 2eebd9f86..9308371d2 100644
--- a/cmd/podman/play/kube.go
+++ b/cmd/podman/play/kube.go
@@ -86,6 +86,9 @@ func init() {
flags.StringVar(&kubeOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
_ = kubeCmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault)
+ downFlagName := "down"
+ flags.BoolVar(&kubeOptions.Down, downFlagName, false, "Stop pods defined in the YAML file")
+
if !registry.IsRemote() {
certDirFlagName := "cert-dir"
flags.StringVar(&kubeOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys")
@@ -144,12 +147,55 @@ func kube(cmd *cobra.Command, args []string) error {
}
kubeOptions.StaticMACs = append(kubeOptions.StaticMACs, m)
}
+ if kubeOptions.Down {
+ return teardown(yamlfile)
+ }
+ return playkube(yamlfile)
+}
- report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), yamlfile, kubeOptions.PlayKubeOptions)
+func teardown(yamlfile string) error {
+ var (
+ podStopErrors utils.OutputErrors
+ podRmErrors utils.OutputErrors
+ )
+ options := new(entities.PlayKubeDownOptions)
+ reports, err := registry.ContainerEngine().PlayKubeDown(registry.GetContext(), yamlfile, *options)
if err != nil {
return err
}
+ // Output stopped pods
+ fmt.Println("Pods stopped:")
+ for _, stopped := range reports.StopReport {
+ if len(stopped.Errs) == 0 {
+ fmt.Println(stopped.Id)
+ } else {
+ podStopErrors = append(podStopErrors, stopped.Errs...)
+ }
+ }
+ // Dump any stop errors
+ lastStopError := podStopErrors.PrintErrors()
+ if lastStopError != nil {
+ fmt.Fprintf(os.Stderr, "Error: %s\n", lastStopError)
+ }
+
+ // Output rm'd pods
+ fmt.Println("Pods removed:")
+ for _, removed := range reports.RmReport {
+ if removed.Err == nil {
+ fmt.Println(removed.Id)
+ } else {
+ podRmErrors = append(podRmErrors, removed.Err)
+ }
+ }
+ return podRmErrors.PrintErrors()
+}
+
+func playkube(yamlfile string) error {
+ report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), yamlfile, kubeOptions.PlayKubeOptions)
+ if err != nil {
+ return err
+ }
// Print volumes report
for i, volume := range report.Volumes {
if i == 0 {
diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md
index 268e4bbcb..33f79e7ef 100644
--- a/docs/source/markdown/podman-play-kube.1.md
+++ b/docs/source/markdown/podman-play-kube.1.md
@@ -8,7 +8,7 @@ podman-play-kube - Create containers, pods or volumes based on Kubernetes YAML
## DESCRIPTION
**podman play kube** will read in a structured file of Kubernetes YAML. It will then recreate the containers, pods or volumes described in the YAML. Containers within a pod are then started and the ID of the new Pod or the name of the new Volume is output. If the yaml file is specified as "-" then `podman play kube` will read the YAML file from stdin.
-
+Using the `--down` command line option, it is also capable of tearing down the pods created by a previous run of `podman play kube`.
Ideally the input file would be one created by Podman (see podman-generate-kube(1)). This would guarantee a smooth import and expected results.
Currently, the supported Kubernetes kinds are:
@@ -96,6 +96,11 @@ 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.
+#### **--down**
+
+Tears down the pods that were created by a previous run of `play kube`. The pods are stopped and then
+removed. Any volumes created are left intact.
+
#### **--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.
@@ -146,6 +151,15 @@ Recreate the pod and containers as described in a file `demo.yml` sent to stdin
```
$ cat demo.yml | podman play kube -
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
+
+```
+Teardown the pod and containers as described in a file `demo.yml`
+```
+$ podman play kube --down demo.yml
+Pods stopped:
+52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
+Pods removed:
+52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
```
Provide `configmap-foo.yml` and `configmap-bar.yml` as sources for environment variables within the containers.
diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go
index 90332924c..4f79d5f20 100644
--- a/pkg/api/handlers/libpod/play.go
+++ b/pkg/api/handlers/libpod/play.go
@@ -15,6 +15,7 @@ import (
"github.com/containers/podman/v3/pkg/domain/infra/abi"
"github.com/gorilla/schema"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
func PlayKube(w http.ResponseWriter, r *http.Request) {
@@ -66,9 +67,15 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
return
}
- defer os.Remove(tmpfile.Name())
+ defer func() {
+ if err := os.Remove(tmpfile.Name()); err != nil {
+ logrus.Warn(err)
+ }
+ }()
if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
- tmpfile.Close()
+ if err := tmpfile.Close(); err != nil {
+ logrus.Warn(err)
+ }
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
return
}
@@ -105,12 +112,43 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
if _, found := r.URL.Query()["start"]; found {
options.Start = types.NewOptionalBool(query.Start)
}
-
report, err := containerEngine.PlayKube(r.Context(), tmpfile.Name(), options)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error playing YAML file"))
return
}
+ utils.WriteResponse(w, http.StatusOK, report)
+}
+func PlayKubeDown(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ defer func() {
+ if err := os.Remove(tmpfile.Name()); err != nil {
+ logrus.Warn(err)
+ }
+ }()
+ if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
+ if err := tmpfile.Close(); err != nil {
+ logrus.Warn(err)
+ }
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
+ return
+ }
+ if err := tmpfile.Close(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error closing temporary file"))
+ return
+ }
+ containerEngine := abi.ContainerEngine{Libpod: runtime}
+ options := new(entities.PlayKubeDownOptions)
+ report, err := containerEngine.PlayKubeDown(r.Context(), tmpfile.Name(), *options)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error tearing down YAML file"))
+ return
+ }
utils.WriteResponse(w, http.StatusOK, report)
}
diff --git a/pkg/api/server/register_play.go b/pkg/api/server/register_play.go
index c51301aa8..915d0d02e 100644
--- a/pkg/api/server/register_play.go
+++ b/pkg/api/server/register_play.go
@@ -59,5 +59,20 @@ func (s *APIServer) registerPlayHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/play/kube"), s.APIHandler(libpod.PlayKube)).Methods(http.MethodPost)
+ // swagger:operation DELETE /libpod/play/kube libpod PlayKubeDownLibpod
+ // ---
+ // tags:
+ // - containers
+ // - pods
+ // summary: Remove pods from play kube
+ // description: Tears down pods defined in a YAML file
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/DocsLibpodPlayKubeResponse"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/play/kube"), s.APIHandler(libpod.PlayKubeDown)).Methods(http.MethodDelete)
return nil
}
diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go
index 4b735c821..a45508fed 100644
--- a/pkg/bindings/play/play.go
+++ b/pkg/bindings/play/play.go
@@ -6,6 +6,8 @@ import (
"os"
"strconv"
+ "github.com/sirupsen/logrus"
+
"github.com/containers/podman/v3/pkg/auth"
"github.com/containers/podman/v3/pkg/bindings"
"github.com/containers/podman/v3/pkg/domain/entities"
@@ -54,3 +56,30 @@ func Kube(ctx context.Context, path string, options *KubeOptions) (*entities.Pla
return &report, nil
}
+
+func KubeDown(ctx context.Context, path string) (*entities.PlayKubeReport, error) {
+ var report entities.PlayKubeReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err := f.Close(); err != nil {
+ logrus.Warn(err)
+ }
+ }()
+ response, err := conn.DoRequest(f, http.MethodDelete, "/play/kube", nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ if err := response.Process(&report); err != nil {
+ return nil, err
+ }
+
+ return &report, nil
+}
diff --git a/pkg/bindings/play/types.go b/pkg/bindings/play/types.go
index 52a72c7b6..787069169 100644
--- a/pkg/bindings/play/types.go
+++ b/pkg/bindings/play/types.go
@@ -1,6 +1,8 @@
package play
-import "net"
+import (
+ "net"
+)
//go:generate go run ../generator/generator.go KubeOptions
// KubeOptions are optional options for replaying kube YAML files
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 5d3c9480e..5acf7211c 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -67,6 +67,7 @@ type ContainerEngine interface {
NetworkReload(ctx context.Context, names []string, options NetworkReloadOptions) ([]*NetworkReloadReport, error)
NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error)
PlayKube(ctx context.Context, path string, opts PlayKubeOptions) (*PlayKubeReport, error)
+ PlayKubeDown(ctx context.Context, path string, opts PlayKubeDownOptions) (*PlayKubeReport, error)
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
PodExists(ctx context.Context, nameOrID string) (*BoolReport, error)
PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error)
diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go
index 01de73ebe..77329e328 100644
--- a/pkg/domain/entities/play.go
+++ b/pkg/domain/entities/play.go
@@ -14,6 +14,9 @@ type PlayKubeOptions struct {
Build bool
// CertDir - to a directory containing TLS certifications and keys.
CertDir string
+ // Down indicates whether to bring contents of a yaml file "down"
+ // as in stop
+ Down bool
// Username for authenticating against the registry.
Username string
// Password for authenticating against the registry.
@@ -67,4 +70,14 @@ type PlayKubeReport struct {
Pods []PlayKubePod
// Volumes - volumes created by play kube.
Volumes []PlayKubeVolume
+ PlayKubeTeardown
+}
+
+// PlayKubeDownOptions are options for tearing down pods
+type PlayKubeDownOptions struct{}
+
+// PlayKubeDownReport contains the results of tearing down play kube
+type PlayKubeTeardown struct {
+ StopReport []*PodStopReport
+ RmReport []*PodRmReport
}
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index 6224feff5..f22b2dbbb 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -586,3 +586,73 @@ func getBuildFile(imageName string, cwd string) (string, error) {
}
return "", err
}
+
+func (ic *ContainerEngine) PlayKubeDown(ctx context.Context, path string, _ entities.PlayKubeDownOptions) (*entities.PlayKubeReport, error) {
+ var (
+ podNames []string
+ )
+ reports := new(entities.PlayKubeReport)
+
+ // read yaml document
+ content, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ // split yaml document
+ documentList, err := splitMultiDocYAML(content)
+ if err != nil {
+ return nil, err
+ }
+
+ // sort kube kinds
+ documentList, err = sortKubeKinds(documentList)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to sort kube kinds in %q", path)
+ }
+
+ for _, document := range documentList {
+ kind, err := getKubeKind(document)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to read %q as kube YAML", path)
+ }
+
+ switch kind {
+ case "Pod":
+ var podYAML v1.Pod
+ if err := yaml.Unmarshal(document, &podYAML); err != nil {
+ return nil, errors.Wrapf(err, "unable to read YAML %q as Kube Pod", path)
+ }
+ podNames = append(podNames, podYAML.ObjectMeta.Name)
+ case "Deployment":
+ var deploymentYAML v1apps.Deployment
+
+ if err := yaml.Unmarshal(document, &deploymentYAML); err != nil {
+ return nil, errors.Wrapf(err, "unable to read YAML %q as Kube Deployment", path)
+ }
+ var numReplicas int32 = 1
+ deploymentName := deploymentYAML.ObjectMeta.Name
+ if deploymentYAML.Spec.Replicas != nil {
+ numReplicas = *deploymentYAML.Spec.Replicas
+ }
+ for i := 0; i < int(numReplicas); i++ {
+ podName := fmt.Sprintf("%s-pod-%d", deploymentName, i)
+ podNames = append(podNames, podName)
+ }
+ default:
+ continue
+ }
+ }
+
+ // Add the reports
+ reports.StopReport, err = ic.PodStop(ctx, podNames, entities.PodStopOptions{})
+ if err != nil {
+ return nil, err
+ }
+
+ reports.RmReport, err = ic.PodRm(ctx, podNames, entities.PodRmOptions{})
+ if err != nil {
+ return nil, err
+ }
+ return reports, nil
+}
diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go
index e66ff0308..e39751a18 100644
--- a/pkg/domain/infra/tunnel/play.go
+++ b/pkg/domain/infra/tunnel/play.go
@@ -22,3 +22,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, opts entit
}
return play.Kube(ic.ClientCtx, path, options)
}
+
+func (ic *ContainerEngine) PlayKubeDown(ctx context.Context, path string, _ entities.PlayKubeDownOptions) (*entities.PlayKubeReport, error) {
+ return play.KubeDown(ic.ClientCtx, path)
+}
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index eec4b43a5..ab496f0eb 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -2527,4 +2527,68 @@ invalid kube kind
Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).To(ContainSubstring(`map[]`))
})
+
+ It("podman play kube teardown", func() {
+ pod := getPod()
+ err := generateKubeYaml("pod", pod, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube).Should(Exit(0))
+
+ ls := podmanTest.Podman([]string{"pod", "ps", "--format", "'{{.ID}}'"})
+ ls.WaitWithDefaultTimeout()
+ Expect(ls).Should(Exit(0))
+ Expect(len(ls.OutputToStringArray())).To(Equal(1))
+
+ // teardown
+ teardown := podmanTest.Podman([]string{"play", "kube", "--down", kubeYaml})
+ teardown.WaitWithDefaultTimeout()
+ Expect(teardown).Should(Exit(0))
+
+ checkls := podmanTest.Podman([]string{"pod", "ps", "--format", "'{{.ID}}'"})
+ checkls.WaitWithDefaultTimeout()
+ Expect(checkls).Should(Exit(0))
+ Expect(len(checkls.OutputToStringArray())).To(Equal(0))
+ })
+
+ It("podman play kube teardown pod does not exist", func() {
+ // teardown
+ teardown := podmanTest.Podman([]string{"play", "kube", "--down", kubeYaml})
+ teardown.WaitWithDefaultTimeout()
+ Expect(teardown).Should(Exit(125))
+ })
+
+ It("podman play kube teardown with volume", func() {
+
+ volName := RandomString(12)
+ volDevice := "tmpfs"
+ volType := "tmpfs"
+ volOpts := "nodev,noexec"
+
+ pvc := getPVC(withPVCName(volName),
+ withPVCAnnotations(util.VolumeDeviceAnnotation, volDevice),
+ withPVCAnnotations(util.VolumeTypeAnnotation, volType),
+ withPVCAnnotations(util.VolumeMountOptsAnnotation, volOpts))
+ err = generateKubeYaml("persistentVolumeClaim", pvc, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube).Should(Exit(0))
+
+ exists := podmanTest.Podman([]string{"volume", "exists", volName})
+ exists.WaitWithDefaultTimeout()
+ Expect(exists).To(Exit(0))
+
+ teardown := podmanTest.Podman([]string{"play", "kube", "--down", kubeYaml})
+ teardown.WaitWithDefaultTimeout()
+ Expect(teardown).To(Exit(0))
+
+ // volume should not be deleted on teardown
+ exists = podmanTest.Podman([]string{"volume", "exists", volName})
+ exists.WaitWithDefaultTimeout()
+ Expect(exists).To(Exit(0))
+ })
})