Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions cmd/podman/kube/down.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package kube

import (
"github.com/containers/podman/v5/cmd/podman/common"
"github.com/containers/podman/v5/cmd/podman/registry"
"github.com/containers/podman/v5/cmd/podman/utils"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/spf13/cobra"
"go.podman.io/common/pkg/completion"
)

type downKubeOptions struct {
Expand All @@ -18,12 +18,12 @@ var (
Removes pods that have been based on the Kubernetes kind described in the YAML.`

downCmd = &cobra.Command{
Use: "down [options] KUBEFILE|-",
Use: "down [options] [KUBEFILE [KUBEFILE...]]|-",
Short: "Remove pods based on Kubernetes YAML",
Long: downDescription,
RunE: down,
Args: cobra.ExactArgs(1),
ValidArgsFunction: common.AutocompleteDefaultOneArg,
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: completion.AutocompleteDefault,
Example: `podman kube down nginx.yml
cat nginx.yml | podman kube down -
podman kube down https://example.com/nginx.yml`,
Expand All @@ -48,7 +48,7 @@ func downFlags(cmd *cobra.Command) {
}

func down(_ *cobra.Command, args []string) error {
reader, err := readerFromArg(args[0])
reader, err := readerFromArgs(args)
if err != nil {
return err
}
Expand Down
75 changes: 50 additions & 25 deletions cmd/podman/kube/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ type playKubeOptionsWrapper struct {
macs []string
}

const yamlFileSeparator = "\n---\n"

var (
// https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/
defaultSeccompRoot = "/var/lib/kubelet/seccomp"
Expand All @@ -51,12 +53,12 @@ var (
Creates pods or volumes based on the Kubernetes kind described in the YAML. Supported kinds are Pods, Deployments, DaemonSets, Jobs, and PersistentVolumeClaims.`

playCmd = &cobra.Command{
Use: "play [options] KUBEFILE|-",
Use: "play [options] [KUBEFILE [KUBEFILE...]]|-",
Short: "Play a pod or volume based on Kubernetes YAML",
Long: playDescription,
RunE: play,
Args: cobra.ExactArgs(1),
ValidArgsFunction: common.AutocompleteDefaultOneArg,
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: completion.AutocompleteDefault,
Example: `podman kube play nginx.yml
cat nginx.yml | podman kube play -
podman kube play --creds user:password --seccomp-profile-root /custom/path apache.yml
Expand All @@ -66,13 +68,13 @@ var (

var (
playKubeCmd = &cobra.Command{
Use: "kube [options] KUBEFILE|-",
Use: "kube [options] [KUBEFILE [KUBEFILE...]]|-",
Short: "Play a pod or volume based on Kubernetes YAML",
Long: playDescription,
Hidden: true,
RunE: playKube,
Args: cobra.ExactArgs(1),
ValidArgsFunction: common.AutocompleteDefaultOneArg,
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: completion.AutocompleteDefault,
Example: `podman play kube nginx.yml
cat nginx.yml | podman play kube -
podman play kube --creds user:password --seccomp-profile-root /custom/path apache.yml
Expand Down Expand Up @@ -276,7 +278,7 @@ func play(cmd *cobra.Command, args []string) error {
return errors.New("--force may be specified only with --down")
}

reader, err := readerFromArg(args[0])
reader, err := readerFromArgs(args)
if err != nil {
return err
}
Expand Down Expand Up @@ -306,7 +308,7 @@ func play(cmd *cobra.Command, args []string) error {
playOptions.ServiceContainer = true

// Read the kube yaml file again so that a reader can be passed down to the teardown function
teardownReader, err = readerFromArg(args[0])
teardownReader, err = readerFromArgs(args)
if err != nil {
return err
}
Expand Down Expand Up @@ -364,31 +366,54 @@ func playKube(cmd *cobra.Command, args []string) error {
return play(cmd, args)
}

func readerFromArg(fileName string) (*bytes.Reader, error) {
var reader io.Reader
switch {
case fileName == "-": // Read from stdin
reader = os.Stdin
case parse.ValidWebURL(fileName) == nil:
response, err := http.Get(fileName)
func readerFromArgs(args []string) (*bytes.Reader, error) {
return readerFromArgsWithStdin(args, os.Stdin)
}

func readerFromArgsWithStdin(args []string, stdin io.Reader) (*bytes.Reader, error) {
// if user tried to pipe, shortcut the reading
if len(args) == 1 && args[0] == "-" {
data, err := io.ReadAll(stdin)
if err != nil {
return nil, err
}
defer response.Body.Close()
reader = response.Body
default:
f, err := os.Open(fileName)
return bytes.NewReader(data), nil
}

var combined bytes.Buffer

for i, arg := range args {
reader, err := readerFromArg(arg)
if err != nil {
return nil, err
}

_, err = io.Copy(&combined, reader)
reader.Close()
if err != nil {
return nil, err
}
defer f.Close()
reader = f

if i < len(args)-1 {
// separate multiple files with YAML document separator
combined.WriteString(yamlFileSeparator)
}
}
data, err := io.ReadAll(reader)
if err != nil {
return nil, err

return bytes.NewReader(combined.Bytes()), nil
}

func readerFromArg(fileOrURL string) (io.ReadCloser, error) {
switch {
case parse.ValidWebURL(fileOrURL) == nil:
response, err := http.Get(fileOrURL)
if err != nil {
return nil, err
}
return response.Body, nil
default:
return os.Open(fileOrURL)
}
return bytes.NewReader(data), nil
}

func teardown(body io.Reader, options entities.PlayKubeDownOptions) error {
Expand Down
149 changes: 149 additions & 0 deletions cmd/podman/kube/play_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package kube

import (
"io"
"os"
"strings"
"testing"
)

var configMapYAML = strings.Join([]string{
"apiVersion: v1",
"kind: ConfigMap",
"metadata:",
" name: my-config",
"data:",
" key: value",
}, "\n")

var podYAML = strings.Join([]string{
"apiVersion: v1",
"kind: Pod",
"metadata:",
" name: my-pod",
}, "\n")

var serviceYAML = strings.Join([]string{
"apiVersion: v1",
"kind: Service",
"metadata:",
" name: my-service",
}, "\n")

var secretYAML = strings.Join([]string{
"apiVersion: v1",
"kind: Secret",
"metadata:",
" name: my-secret",
}, "\n")

var namespaceYAML = strings.Join([]string{
"apiVersion: v1",
"kind: Namespace",
"metadata:",
" name: my-namespace",
}, "\n")
Comment on lines +10 to +45
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is quite ugly, go supports multi line strings just fine with the backtick escapes which would make this more readable, but not worth to push again just for this


// createTempFile writes content to a temp file and returns its path.
func createTempFile(t *testing.T, content string) string {
t.Helper()

tmp, err := os.CreateTemp(t.TempDir(), "testfile-*.yaml")
if err != nil {
t.Fatalf("failed to create temp file: %v", err)
}

if _, err := tmp.WriteString(content); err != nil {
t.Fatalf("failed to write to temp file: %v", err)
}

if err := tmp.Close(); err != nil {
t.Fatalf("failed to close temp file: %v", err)
}

return tmp.Name()
}

func TestReaderFromArgs(t *testing.T) {
tests := []struct {
name string
files []string // file contents
expected string // expected concatenated output
}{
{
name: "single file",
files: []string{configMapYAML},
expected: configMapYAML,
},
{
name: "two files",
files: []string{
podYAML,
serviceYAML,
},
expected: podYAML + "\n---\n" + serviceYAML,
},
{
name: "empty file and normal file",
files: []string{
"",
secretYAML,
},
expected: "---\n" + secretYAML,
},
{
name: "files with only whitespace",
files: []string{
"\n \n",
namespaceYAML,
},
expected: "---\n" + namespaceYAML,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var paths []string
for _, content := range tt.files {
path := createTempFile(t, content)
defer os.Remove(path)
paths = append(paths, path)
}

reader, err := readerFromArgsWithStdin(paths, nil)
if err != nil {
t.Fatalf("readerFromArgsWithStdin failed: %v", err)
}

output, err := io.ReadAll(reader)
if err != nil {
t.Fatalf("failed to read result: %v", err)
}

got := strings.TrimSpace(string(output))
want := strings.TrimSpace(tt.expected)

if got != want {
t.Errorf("unexpected output:\n--- got ---\n%s\n--- want ---\n%s", got, want)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another nit: we already depend on assert so might as well use the nicer test hallper such as assert.Equal(), etc...

}
})
}
}

func TestReaderFromArgs_Stdin(t *testing.T) {
stdinReader := strings.NewReader(namespaceYAML)

reader, err := readerFromArgsWithStdin([]string{"-"}, stdinReader)
if err != nil {
t.Fatalf("readerFromArgsWithStdin failed: %v", err)
}

data, err := io.ReadAll(reader)
if err != nil {
t.Fatalf("failed to read from stdin: %v", err)
}

if got := string(data); got != namespaceYAML {
t.Errorf("unexpected stdin result:\n--- got ---\n%s\n--- want ---\n%s", got, namespaceYAML)
}
}
39 changes: 34 additions & 5 deletions docs/source/markdown/podman-kube-down.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
podman-kube-down - Remove containers and pods based on Kubernetes YAML

## SYNOPSIS
**podman kube down** [*options*] *file.yml|-|https://website.io/file.yml*
**podman kube down** [*options*] *file.yml|-|https://website.io/file.yml* [*file2.yml|https://website.io/file2.yml* ...]

## DESCRIPTION
**podman kube down** reads a specified Kubernetes YAML file, tearing down pods that were created by the `podman kube play` command via the same Kubernetes YAML
file. Any volumes that were created by the previous `podman kube play` command remain intact unless the `--force` options is used. If the YAML file is
specified as `-`, `podman kube down` reads the YAML from stdin. The input can also be a URL that points to a YAML file such as https://podman.io/demo.yml.
`podman kube down` tears down the pods and containers created by `podman kube play` via the same Kubernetes YAML from the URL. However,
**podman kube down** reads one or more specified Kubernetes YAML files, tearing down pods that were created by the `podman kube play` command via the same Kubernetes YAML
files. Any volumes that were created by the previous `podman kube play` command remain intact unless the `--force` options is used. If the YAML file is
specified as `-`, `podman kube down` reads the YAML from stdin. The inputs can also be URLs that point to YAML files such as https://podman.io/demo.yml.
`podman kube down` tears down the pods and containers created by `podman kube play` via the same Kubernetes YAML from the URLs. However,
`podman kube down` does not work with a URL if the YAML file the URL points to has been changed or altered since the creation of the pods and containers using
`podman kube play`.

When multiple YAML files are specified (local files, URLs, or a combination), they are processed sequentially and combined with YAML document separators (`---`), just like with `podman kube play`.

## OPTIONS

#### **--force**
Expand Down Expand Up @@ -67,5 +69,32 @@ Pods removed:
`podman kube down` does not work with a URL if the YAML file the URL points to has been changed
or altered since it was used to create the pods and containers.

Remove the pods and containers that were created from multiple YAML files
```
$ podman kube down pod.yml service.yml configmap.yml
Pods stopped:
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
Pods removed:
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
```

Remove the pods and containers that were created from multiple URLs
```
$ podman kube down https://example.com/pod.yml https://example.com/service.yml https://example.com/configmap.yml
Pods stopped:
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
Pods removed:
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
```

Remove the pods and containers that were created from a combination of local files and URLs
```
$ podman kube down local-pod.yml https://example.com/service.yml local-configmap.yml
Pods stopped:
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
Pods removed:
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
```

## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-kube(1)](podman-kube.1.md)**, **[podman-kube-play(1)](podman-kube-play.1.md)**, **[podman-kube-generate(1)](podman-kube-generate.1.md)**, **[containers-certs.d(5)](https://github.com/containers/image/blob/main/docs/containers-certs.d.5.md)**
Loading