Merge pull request #11298 from baude/kubeupdown

teardown play kube
This commit is contained in:
OpenShift Merge Robot 2021-08-26 13:58:44 -04:00 committed by GitHub
commit 94c37d7d47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 306 additions and 7 deletions

View File

@ -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") 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) _ = kubeCmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault)
downFlagName := "down"
flags.BoolVar(&kubeOptions.Down, downFlagName, false, "Stop pods defined in the YAML file")
if !registry.IsRemote() { if !registry.IsRemote() {
certDirFlagName := "cert-dir" certDirFlagName := "cert-dir"
flags.StringVar(&kubeOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys") 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) 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 { if err != nil {
return err 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 // Print volumes report
for i, volume := range report.Volumes { for i, volume := range report.Volumes {
if i == 0 { if i == 0 {

View File

@ -8,7 +8,7 @@ podman-play-kube - Create containers, pods or volumes based on Kubernetes YAML
## DESCRIPTION ## 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. **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. 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: 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 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. 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* #### **--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. 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 - $ cat demo.yml | podman play kube -
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 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. Provide `configmap-foo.yml` and `configmap-bar.yml` as sources for environment variables within the containers.

View File

@ -320,6 +320,9 @@ sub operation_name {
if ($action eq 'df') { if ($action eq 'df') {
$action = 'dataUsage'; $action = 'dataUsage';
} }
elsif ($action eq "delete" && $endpoint eq "/libpod/play/kube") {
$action = "KubeDown"
}
# Grrrrrr, this one is annoying: some operations get an extra 'All' # Grrrrrr, this one is annoying: some operations get an extra 'All'
elsif ($action =~ /^(delete|get|stats)$/ && $endpoint !~ /\{/) { elsif ($action =~ /^(delete|get|stats)$/ && $endpoint !~ /\{/) {
$action .= "All"; $action .= "All";

View File

@ -15,6 +15,7 @@ import (
"github.com/containers/podman/v3/pkg/domain/infra/abi" "github.com/containers/podman/v3/pkg/domain/infra/abi"
"github.com/gorilla/schema" "github.com/gorilla/schema"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
) )
func PlayKube(w http.ResponseWriter, r *http.Request) { 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")) utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
return 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 { 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")) utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
return return
} }
@ -105,12 +112,43 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
if _, found := r.URL.Query()["start"]; found { if _, found := r.URL.Query()["start"]; found {
options.Start = types.NewOptionalBool(query.Start) options.Start = types.NewOptionalBool(query.Start)
} }
report, err := containerEngine.PlayKube(r.Context(), tmpfile.Name(), options) report, err := containerEngine.PlayKube(r.Context(), tmpfile.Name(), options)
if err != nil { if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error playing YAML file")) utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error playing YAML file"))
return 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) utils.WriteResponse(w, http.StatusOK, report)
} }

View File

@ -59,5 +59,20 @@ func (s *APIServer) registerPlayHandlers(r *mux.Router) error {
// 500: // 500:
// $ref: "#/responses/InternalError" // $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/play/kube"), s.APIHandler(libpod.PlayKube)).Methods(http.MethodPost) 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 return nil
} }

View File

@ -6,6 +6,8 @@ import (
"os" "os"
"strconv" "strconv"
"github.com/sirupsen/logrus"
"github.com/containers/podman/v3/pkg/auth" "github.com/containers/podman/v3/pkg/auth"
"github.com/containers/podman/v3/pkg/bindings" "github.com/containers/podman/v3/pkg/bindings"
"github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/entities"
@ -56,3 +58,30 @@ func Kube(ctx context.Context, path string, options *KubeOptions) (*entities.Pla
return &report, nil 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
}

View File

@ -1,6 +1,8 @@
package play package play
import "net" import (
"net"
)
//go:generate go run ../generator/generator.go KubeOptions //go:generate go run ../generator/generator.go KubeOptions
// KubeOptions are optional options for replaying kube YAML files // KubeOptions are optional options for replaying kube YAML files

View File

@ -67,6 +67,7 @@ type ContainerEngine interface {
NetworkReload(ctx context.Context, names []string, options NetworkReloadOptions) ([]*NetworkReloadReport, error) NetworkReload(ctx context.Context, names []string, options NetworkReloadOptions) ([]*NetworkReloadReport, error)
NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error) NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error)
PlayKube(ctx context.Context, path string, opts PlayKubeOptions) (*PlayKubeReport, 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) PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
PodExists(ctx context.Context, nameOrID string) (*BoolReport, error) PodExists(ctx context.Context, nameOrID string) (*BoolReport, error)
PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error) PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error)

View File

@ -14,6 +14,9 @@ type PlayKubeOptions struct {
Build bool Build bool
// CertDir - to a directory containing TLS certifications and keys. // CertDir - to a directory containing TLS certifications and keys.
CertDir string 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 for authenticating against the registry.
Username string Username string
// Password for authenticating against the registry. // Password for authenticating against the registry.
@ -67,4 +70,14 @@ type PlayKubeReport struct {
Pods []PlayKubePod Pods []PlayKubePod
// Volumes - volumes created by play kube. // Volumes - volumes created by play kube.
Volumes []PlayKubeVolume 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
} }

View File

@ -586,3 +586,73 @@ func getBuildFile(imageName string, cwd string) (string, error) {
} }
return "", err 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
}

View File

@ -22,3 +22,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, opts entit
} }
return play.Kube(ic.ClientCtx, path, options) 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)
}

View File

@ -2527,4 +2527,68 @@ invalid kube kind
Expect(inspect).Should(Exit(0)) Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).To(ContainSubstring(`map[]`)) 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))
})
}) })