Skip to content

Commit c15e845

Browse files
Merge pull request #27292 from Honny1/pr-multi-file-support-kube
Add multi-file support to `podman kube play/down`
2 parents 0be09e1 + 9bda788 commit c15e845

File tree

5 files changed

+277
-35
lines changed

5 files changed

+277
-35
lines changed

cmd/podman/kube/down.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package kube
22

33
import (
4-
"github.com/containers/podman/v5/cmd/podman/common"
54
"github.com/containers/podman/v5/cmd/podman/registry"
65
"github.com/containers/podman/v5/cmd/podman/utils"
76
"github.com/containers/podman/v5/pkg/domain/entities"
87
"github.com/spf13/cobra"
8+
"go.podman.io/common/pkg/completion"
99
)
1010

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

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

5050
func down(_ *cobra.Command, args []string) error {
51-
reader, err := readerFromArg(args[0])
51+
reader, err := readerFromArgs(args)
5252
if err != nil {
5353
return err
5454
}

cmd/podman/kube/play.go

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ type playKubeOptionsWrapper struct {
4242
macs []string
4343
}
4444

45+
const yamlFileSeparator = "\n---\n"
46+
4547
var (
4648
// https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/
4749
defaultSeccompRoot = "/var/lib/kubelet/seccomp"
@@ -51,12 +53,12 @@ var (
5153
Creates pods or volumes based on the Kubernetes kind described in the YAML. Supported kinds are Pods, Deployments, DaemonSets, Jobs, and PersistentVolumeClaims.`
5254

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

6769
var (
6870
playKubeCmd = &cobra.Command{
69-
Use: "kube [options] KUBEFILE|-",
71+
Use: "kube [options] [KUBEFILE [KUBEFILE...]]|-",
7072
Short: "Play a pod or volume based on Kubernetes YAML",
7173
Long: playDescription,
7274
Hidden: true,
7375
RunE: playKube,
74-
Args: cobra.ExactArgs(1),
75-
ValidArgsFunction: common.AutocompleteDefaultOneArg,
76+
Args: cobra.MinimumNArgs(1),
77+
ValidArgsFunction: completion.AutocompleteDefault,
7678
Example: `podman play kube nginx.yml
7779
cat nginx.yml | podman play kube -
7880
podman play kube --creds user:password --seccomp-profile-root /custom/path apache.yml
@@ -279,7 +281,7 @@ func play(cmd *cobra.Command, args []string) error {
279281
return errors.New("--force may be specified only with --down")
280282
}
281283

282-
reader, err := readerFromArg(args[0])
284+
reader, err := readerFromArgs(args)
283285
if err != nil {
284286
return err
285287
}
@@ -309,7 +311,7 @@ func play(cmd *cobra.Command, args []string) error {
309311
playOptions.ServiceContainer = true
310312

311313
// Read the kube yaml file again so that a reader can be passed down to the teardown function
312-
teardownReader, err = readerFromArg(args[0])
314+
teardownReader, err = readerFromArgs(args)
313315
if err != nil {
314316
return err
315317
}
@@ -367,31 +369,54 @@ func playKube(cmd *cobra.Command, args []string) error {
367369
return play(cmd, args)
368370
}
369371

370-
func readerFromArg(fileName string) (*bytes.Reader, error) {
371-
var reader io.Reader
372-
switch {
373-
case fileName == "-": // Read from stdin
374-
reader = os.Stdin
375-
case parse.ValidWebURL(fileName) == nil:
376-
response, err := http.Get(fileName)
372+
func readerFromArgs(args []string) (*bytes.Reader, error) {
373+
return readerFromArgsWithStdin(args, os.Stdin)
374+
}
375+
376+
func readerFromArgsWithStdin(args []string, stdin io.Reader) (*bytes.Reader, error) {
377+
// if user tried to pipe, shortcut the reading
378+
if len(args) == 1 && args[0] == "-" {
379+
data, err := io.ReadAll(stdin)
377380
if err != nil {
378381
return nil, err
379382
}
380-
defer response.Body.Close()
381-
reader = response.Body
382-
default:
383-
f, err := os.Open(fileName)
383+
return bytes.NewReader(data), nil
384+
}
385+
386+
var combined bytes.Buffer
387+
388+
for i, arg := range args {
389+
reader, err := readerFromArg(arg)
390+
if err != nil {
391+
return nil, err
392+
}
393+
394+
_, err = io.Copy(&combined, reader)
395+
reader.Close()
384396
if err != nil {
385397
return nil, err
386398
}
387-
defer f.Close()
388-
reader = f
399+
400+
if i < len(args)-1 {
401+
// separate multiple files with YAML document separator
402+
combined.WriteString(yamlFileSeparator)
403+
}
389404
}
390-
data, err := io.ReadAll(reader)
391-
if err != nil {
392-
return nil, err
405+
406+
return bytes.NewReader(combined.Bytes()), nil
407+
}
408+
409+
func readerFromArg(fileOrURL string) (io.ReadCloser, error) {
410+
switch {
411+
case parse.ValidWebURL(fileOrURL) == nil:
412+
response, err := http.Get(fileOrURL)
413+
if err != nil {
414+
return nil, err
415+
}
416+
return response.Body, nil
417+
default:
418+
return os.Open(fileOrURL)
393419
}
394-
return bytes.NewReader(data), nil
395420
}
396421

397422
func teardown(body io.Reader, options entities.PlayKubeDownOptions) error {

cmd/podman/kube/play_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package kube
2+
3+
import (
4+
"io"
5+
"os"
6+
"strings"
7+
"testing"
8+
)
9+
10+
var configMapYAML = strings.Join([]string{
11+
"apiVersion: v1",
12+
"kind: ConfigMap",
13+
"metadata:",
14+
" name: my-config",
15+
"data:",
16+
" key: value",
17+
}, "\n")
18+
19+
var podYAML = strings.Join([]string{
20+
"apiVersion: v1",
21+
"kind: Pod",
22+
"metadata:",
23+
" name: my-pod",
24+
}, "\n")
25+
26+
var serviceYAML = strings.Join([]string{
27+
"apiVersion: v1",
28+
"kind: Service",
29+
"metadata:",
30+
" name: my-service",
31+
}, "\n")
32+
33+
var secretYAML = strings.Join([]string{
34+
"apiVersion: v1",
35+
"kind: Secret",
36+
"metadata:",
37+
" name: my-secret",
38+
}, "\n")
39+
40+
var namespaceYAML = strings.Join([]string{
41+
"apiVersion: v1",
42+
"kind: Namespace",
43+
"metadata:",
44+
" name: my-namespace",
45+
}, "\n")
46+
47+
// createTempFile writes content to a temp file and returns its path.
48+
func createTempFile(t *testing.T, content string) string {
49+
t.Helper()
50+
51+
tmp, err := os.CreateTemp(t.TempDir(), "testfile-*.yaml")
52+
if err != nil {
53+
t.Fatalf("failed to create temp file: %v", err)
54+
}
55+
56+
if _, err := tmp.WriteString(content); err != nil {
57+
t.Fatalf("failed to write to temp file: %v", err)
58+
}
59+
60+
if err := tmp.Close(); err != nil {
61+
t.Fatalf("failed to close temp file: %v", err)
62+
}
63+
64+
return tmp.Name()
65+
}
66+
67+
func TestReaderFromArgs(t *testing.T) {
68+
tests := []struct {
69+
name string
70+
files []string // file contents
71+
expected string // expected concatenated output
72+
}{
73+
{
74+
name: "single file",
75+
files: []string{configMapYAML},
76+
expected: configMapYAML,
77+
},
78+
{
79+
name: "two files",
80+
files: []string{
81+
podYAML,
82+
serviceYAML,
83+
},
84+
expected: podYAML + "\n---\n" + serviceYAML,
85+
},
86+
{
87+
name: "empty file and normal file",
88+
files: []string{
89+
"",
90+
secretYAML,
91+
},
92+
expected: "---\n" + secretYAML,
93+
},
94+
{
95+
name: "files with only whitespace",
96+
files: []string{
97+
"\n \n",
98+
namespaceYAML,
99+
},
100+
expected: "---\n" + namespaceYAML,
101+
},
102+
}
103+
104+
for _, tt := range tests {
105+
t.Run(tt.name, func(t *testing.T) {
106+
var paths []string
107+
for _, content := range tt.files {
108+
path := createTempFile(t, content)
109+
defer os.Remove(path)
110+
paths = append(paths, path)
111+
}
112+
113+
reader, err := readerFromArgsWithStdin(paths, nil)
114+
if err != nil {
115+
t.Fatalf("readerFromArgsWithStdin failed: %v", err)
116+
}
117+
118+
output, err := io.ReadAll(reader)
119+
if err != nil {
120+
t.Fatalf("failed to read result: %v", err)
121+
}
122+
123+
got := strings.TrimSpace(string(output))
124+
want := strings.TrimSpace(tt.expected)
125+
126+
if got != want {
127+
t.Errorf("unexpected output:\n--- got ---\n%s\n--- want ---\n%s", got, want)
128+
}
129+
})
130+
}
131+
}
132+
133+
func TestReaderFromArgs_Stdin(t *testing.T) {
134+
stdinReader := strings.NewReader(namespaceYAML)
135+
136+
reader, err := readerFromArgsWithStdin([]string{"-"}, stdinReader)
137+
if err != nil {
138+
t.Fatalf("readerFromArgsWithStdin failed: %v", err)
139+
}
140+
141+
data, err := io.ReadAll(reader)
142+
if err != nil {
143+
t.Fatalf("failed to read from stdin: %v", err)
144+
}
145+
146+
if got := string(data); got != namespaceYAML {
147+
t.Errorf("unexpected stdin result:\n--- got ---\n%s\n--- want ---\n%s", got, namespaceYAML)
148+
}
149+
}

docs/source/markdown/podman-kube-down.1.md

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
podman-kube-down - Remove containers and pods based on Kubernetes YAML
55

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

99
## DESCRIPTION
10-
**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
11-
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
12-
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.
13-
`podman kube down` tears down the pods and containers created by `podman kube play` via the same Kubernetes YAML from the URL. However,
10+
**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
11+
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
12+
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.
13+
`podman kube down` tears down the pods and containers created by `podman kube play` via the same Kubernetes YAML from the URLs. However,
1414
`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
1515
`podman kube play`.
1616

17+
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`.
18+
1719
## OPTIONS
1820

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

72+
Remove the pods and containers that were created from multiple YAML files
73+
```
74+
$ podman kube down pod.yml service.yml configmap.yml
75+
Pods stopped:
76+
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
77+
Pods removed:
78+
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
79+
```
80+
81+
Remove the pods and containers that were created from multiple URLs
82+
```
83+
$ podman kube down https://example.com/pod.yml https://example.com/service.yml https://example.com/configmap.yml
84+
Pods stopped:
85+
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
86+
Pods removed:
87+
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
88+
```
89+
90+
Remove the pods and containers that were created from a combination of local files and URLs
91+
```
92+
$ podman kube down local-pod.yml https://example.com/service.yml local-configmap.yml
93+
Pods stopped:
94+
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
95+
Pods removed:
96+
52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6
97+
```
98+
7099
## SEE ALSO
71100
**[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)**

0 commit comments

Comments
 (0)