Skip to content

Commit

Permalink
teardown play kube
Browse files Browse the repository at this point in the history
add the ability for play kube to tear down based on the yaml used to
play it.  it is indicated by --down in the play kube command.  volumes
are NOT deleted during the teardown.  pods and their containers are
stopped and removed.

Signed-off-by: Brent Baude <[email protected]>
  • Loading branch information
baude committed Aug 24, 2021
1 parent e9daaf6 commit 1e17692
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 6 deletions.
48 changes: 47 additions & 1 deletion cmd/podman/play/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down
16 changes: 15 additions & 1 deletion docs/source/markdown/podman-play-kube.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
44 changes: 41 additions & 3 deletions pkg/api/handlers/libpod/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
15 changes: 15 additions & 0 deletions pkg/api/server/register_play.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
29 changes: 29 additions & 0 deletions pkg/bindings/play/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
4 changes: 3 additions & 1 deletion pkg/bindings/play/types.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions pkg/domain/entities/engine_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions pkg/domain/entities/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
70 changes: 70 additions & 0 deletions pkg/domain/infra/abi/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions pkg/domain/infra/tunnel/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Loading

0 comments on commit 1e17692

Please sign in to comment.