1
0
mirror of https://github.com/containers/podman.git synced 2026-02-05 06:45:31 +01:00

Merge pull request #27292 from Honny1/pr-multi-file-support-kube

Add multi-file support to `podman kube play/down`
This commit is contained in:
openshift-merge-bot[bot]
2025-10-22 11:46:42 +00:00
committed by GitHub
5 changed files with 279 additions and 37 deletions

View File

@@ -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 {
@@ -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`,
@@ -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
}

View File

@@ -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"
@@ -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
@@ -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
@@ -279,7 +281,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
}
@@ -309,7 +311,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
}
@@ -367,31 +369,54 @@ func playKube(cmd *cobra.Command, args []string) error {
return play(cmd, args)
}
func readerFromArg(fileName string) (*bytes.Reader, error) {
var reader io.Reader
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
}
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
}
if i < len(args)-1 {
// separate multiple files with YAML document separator
combined.WriteString(yamlFileSeparator)
}
}
return bytes.NewReader(combined.Bytes()), nil
}
func readerFromArg(fileOrURL string) (io.ReadCloser, error) {
switch {
case fileName == "-": // Read from stdin
reader = os.Stdin
case parse.ValidWebURL(fileName) == nil:
response, err := http.Get(fileName)
case parse.ValidWebURL(fileOrURL) == nil:
response, err := http.Get(fileOrURL)
if err != nil {
return nil, err
}
defer response.Body.Close()
reader = response.Body
return response.Body, nil
default:
f, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer f.Close()
reader = f
return os.Open(fileOrURL)
}
data, err := io.ReadAll(reader)
if err != nil {
return nil, err
}
return bytes.NewReader(data), nil
}
func teardown(body io.Reader, options entities.PlayKubeDownOptions) error {

View File

@@ -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")
// 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)
}
})
}
}
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)
}
}