Skip to content

Commit c35ed35

Browse files
Merge pull request #15091 from umohnani8/lift
Add podman kube apply command
2 parents 0e367c7 + f6c7432 commit c35ed35

File tree

17 files changed

+1007
-41
lines changed

17 files changed

+1007
-41
lines changed

cmd/podman/kube/apply.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package kube
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"os"
8+
9+
"github.com/containers/common/pkg/completion"
10+
"github.com/containers/podman/v4/cmd/podman/common"
11+
"github.com/containers/podman/v4/cmd/podman/registry"
12+
"github.com/containers/podman/v4/cmd/podman/utils"
13+
"github.com/containers/podman/v4/pkg/domain/entities"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
var (
18+
applyOptions = entities.ApplyOptions{}
19+
applyDescription = `Command applies a podman container, pod, volume, or kube yaml to a Kubernetes cluster when a kubeconfig file is given.`
20+
21+
applyCmd = &cobra.Command{
22+
Use: "apply [options] [CONTAINER...|POD...|VOLUME...]",
23+
Short: "Deploy a podman container, pod, volume, or Kubernetes yaml to a Kubernetes cluster",
24+
Long: applyDescription,
25+
RunE: apply,
26+
ValidArgsFunction: common.AutocompleteForKube,
27+
Example: `podman kube apply ctrName volName
28+
podman kube apply --namespace project -f fileName`,
29+
}
30+
)
31+
32+
func init() {
33+
registry.Commands = append(registry.Commands, registry.CliCommand{
34+
Command: applyCmd,
35+
Parent: kubeCmd,
36+
})
37+
applyFlags(applyCmd)
38+
}
39+
40+
func applyFlags(cmd *cobra.Command) {
41+
flags := cmd.Flags()
42+
flags.SetNormalizeFunc(utils.AliasFlags)
43+
44+
kubeconfigFlagName := "kubeconfig"
45+
flags.StringVarP(&applyOptions.Kubeconfig, kubeconfigFlagName, "k", os.Getenv("KUBECONFIG"), "Path to the kubeconfig file for the Kubernetes cluster")
46+
_ = cmd.RegisterFlagCompletionFunc(kubeconfigFlagName, completion.AutocompleteDefault)
47+
48+
namespaceFlagName := "ns"
49+
flags.StringVarP(&applyOptions.Namespace, namespaceFlagName, "", "", "The namespace to deploy the workload to on the Kubernetes cluster")
50+
_ = cmd.RegisterFlagCompletionFunc(namespaceFlagName, completion.AutocompleteNone)
51+
52+
caCertFileFlagName := "ca-cert-file"
53+
flags.StringVarP(&applyOptions.CACertFile, caCertFileFlagName, "", "", "Path to the CA cert file for the Kubernetes cluster.")
54+
_ = cmd.RegisterFlagCompletionFunc(caCertFileFlagName, completion.AutocompleteDefault)
55+
56+
fileFlagName := "file"
57+
flags.StringVarP(&applyOptions.File, fileFlagName, "f", "", "Path to the Kubernetes yaml file to deploy.")
58+
_ = cmd.RegisterFlagCompletionFunc(fileFlagName, completion.AutocompleteDefault)
59+
60+
serviceFlagName := "service"
61+
flags.BoolVarP(&applyOptions.Service, serviceFlagName, "s", false, "Create a service object for the container being deployed.")
62+
}
63+
64+
func apply(cmd *cobra.Command, args []string) error {
65+
if cmd.Flags().Changed("file") && cmd.Flags().Changed("service") {
66+
return errors.New("cannot set --service and --file at the same time")
67+
}
68+
69+
kubeconfig, err := cmd.Flags().GetString("kubeconfig")
70+
if err != nil {
71+
return err
72+
}
73+
if kubeconfig == "" {
74+
return errors.New("kubeconfig not given, unable to connect to cluster")
75+
}
76+
77+
var reader io.Reader
78+
if cmd.Flags().Changed("file") {
79+
yamlFile := applyOptions.File
80+
if yamlFile == "-" {
81+
yamlFile = os.Stdin.Name()
82+
}
83+
84+
f, err := os.Open(yamlFile)
85+
if err != nil {
86+
return err
87+
}
88+
defer f.Close()
89+
reader = f
90+
} else {
91+
generateOptions.Service = applyOptions.Service
92+
report, err := registry.ContainerEngine().GenerateKube(registry.GetContext(), args, generateOptions)
93+
if err != nil {
94+
return err
95+
}
96+
if r, ok := report.Reader.(io.ReadCloser); ok {
97+
defer r.Close()
98+
}
99+
reader = report.Reader
100+
}
101+
102+
fmt.Println("Deploying to cluster...")
103+
104+
if err = registry.ContainerEngine().KubeApply(registry.GetContext(), reader, applyOptions); err != nil {
105+
return err
106+
}
107+
108+
fmt.Println("Successfully deployed workloads to cluster!")
109+
110+
return nil
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
-% podman-kube-apply(1)
2+
## NAME
3+
podman-kube-apply - Apply Kubernetes YAML based on containers, pods, or volumes to a Kubernetes cluster
4+
5+
## SYNOPSIS
6+
**podman kube apply** [*options*] [*container...* | *pod...* | *volume...*]
7+
8+
## DESCRIPTION
9+
**podman kube apply** will deploy a podman container, pod, or volume to a Kubernetes cluster. Use the `--file` flag to deploy a Kubernetes YAML (v1 specification) to a kubernetes cluster as well.
10+
11+
Note that the Kubernetes YAML file can be used to run the deployment in Podman via podman-play-kube(1).
12+
13+
## OPTIONS
14+
15+
#### **--ca-cert-file**=*ca cert file path | "insecure"*
16+
17+
The path to the CA cert file for the Kubernetes cluster. Usually the kubeconfig has the CA cert file data and `generate kube` automatically picks that up if it is available in the kubeconfig. If no CA cert file data is available, set this to `insecure` to bypass the certificate verification.
18+
19+
#### **--file**, **-f**=*kube yaml filepath*
20+
21+
Path to the kubernetes yaml file to deploy onto the kubernetes cluster. This file can be generated using the `podman kube generate` command. The input may be in the form of a yaml file, or stdin. For stdin, use `--file=-`.
22+
23+
#### **--kubeconfig**, **-k**=*kubeconfig filepath*
24+
25+
Path to the kubeconfig file to be used when deploying the generated kube yaml to the Kubernetes cluster. The environment variable `KUBECONFIG` can be used to set the path for the kubeconfig file as well.
26+
Note: A kubeconfig can have multiple cluster configurations, but `kube generate` will always only pick the first cluster configuration in the given kubeconfig.
27+
28+
#### **--ns**=*namespace*
29+
30+
The namespace or project to deploy the workloads of the generated kube yaml to in the Kubernetes cluster.
31+
32+
#### **--service**, **-s**
33+
34+
Used to create a service for the corresponding container or pod being deployed to the cluster. In particular, if the container or pod has portmap bindings, the service specification will include a NodePort declaration to expose the service. A random port is assigned by Podman in the service specification that is deployed to the cluster.
35+
36+
## EXAMPLES
37+
38+
Apply a podman volume and container to the "default" namespace in a Kubernetes cluster.
39+
```
40+
$ podman kube apply --kubeconfig /tmp/kubeconfig myvol vol-test-1
41+
Deploying to cluster...
42+
Successfully deployed workloads to cluster!
43+
$ kubectl get pods
44+
NAME READY STATUS RESTARTS AGE
45+
vol-test-1-pod 1/1 Running 0 9m
46+
```
47+
48+
Apply a Kubernetes YAML file to the "default" namespace in a Kubernetes cluster.
49+
```
50+
$ podman kube apply --kubeconfig /tmp/kubeconfig -f vol.yaml
51+
Deploying to cluster...
52+
Successfully deployed workloads to cluster!
53+
$ kubectl get pods
54+
NAME READY STATUS RESTARTS AGE
55+
vol-test-2-pod 1/1 Running 0 9m
56+
```
57+
58+
Apply a Kubernetes YAML file to the "test1" namespace in a Kubernetes cluster.
59+
```
60+
$ podman kube apply --kubeconfig /tmp/kubeconfig --ns test1 vol-test-3
61+
Deploying to cluster...
62+
Successfully deployed workloads to cluster!
63+
$ kubectl get pods --namespace test1
64+
NAME READY STATUS RESTARTS AGE
65+
vol-test-3-pod 1/1 Running 0 9m
66+
67+
```
68+
69+
## SEE ALSO
70+
**[podman(1)](podman.1.md)**, **[podman-container(1)](podman-container.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **[podman-kube-play(1)](podman-kube-play.1.md)**, **[podman-kube-generate(1)](podman-kube-generate.1.md)**
71+
72+
## HISTORY
73+
September 2022, Originally compiled by Urvashi Mohnani (umohnani at redhat dot com)

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ Output to the given file, instead of STDOUT. If the file already exists, `kube g
3636

3737
#### **--service**, **-s**
3838

39-
Generate a Kubernetes service object in addition to the Pods. Used to generate a Service specification for the corresponding Pod output. In particular, if the object has portmap bindings, the service specification will include a NodePort declaration to expose the service. A
40-
random port is assigned by Podman in the specification.
39+
Generate a Kubernetes service object in addition to the Pods. Used to generate a Service specification for the corresponding Pod output. In particular, if the object has portmap bindings, the service specification will include a NodePort declaration to expose the service. A random port is assigned by Podman in the specification.
4140

4241
## EXAMPLES
4342

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ file input. Containers will be automatically started.
1414

1515
| Command | Man Page | Description |
1616
| ------- | ---------------------------------------------------- | ----------------------------------------------------------------------------- |
17+
| apply | [podman-kube-apply(1)](podman-kube-apply.1.md) | Apply Kubernetes YAML based on containers, pods, or volumes to a Kubernetes cluster |
1718
| down | [podman-kube-down(1)](podman-kube-down.1.md) | Remove containers and pods based on Kubernetes YAML. |
1819
| generate | [podman-kube-generate(1)](podman-kube-generate.1.md) | Generate Kubernetes YAML based on containers, pods or volumes. |
1920
| play | [podman-kube-play(1)](podman-kube-play.1.md) | Create containers, pods and volumes based on Kubernetes YAML. |
2021

2122
## SEE ALSO
22-
**[podman(1)](podman.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **[podman-container(1)](podman-container.1.md)**, **[podman-kube-play(1)](podman-kube-play.1.md)**, **[podman-kube-down(1)](podman-kube-down.1.md)**, **[podman-kube-generate(1)](podman-kube-generate.1.md)**
23+
**[podman(1)](podman.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **[podman-container(1)](podman-container.1.md)**, **[podman-kube-play(1)](podman-kube-play.1.md)**, **[podman-kube-down(1)](podman-kube-down.1.md)**, **[podman-kube-generate(1)](podman-kube-generate.1.md)**, **[podman-kube-apply(1)](podman-kube-apply.1.md)**
2324

2425
## HISTORY
2526
December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com)

pkg/api/handlers/libpod/kube.go

+26
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,29 @@ func KubePlayDown(w http.ResponseWriter, r *http.Request) {
126126
func KubeGenerate(w http.ResponseWriter, r *http.Request) {
127127
GenerateKube(w, r)
128128
}
129+
130+
func KubeApply(w http.ResponseWriter, r *http.Request) {
131+
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
132+
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
133+
query := struct {
134+
CACertFile string `schema:"caCertFile"`
135+
Kubeconfig string `schema:"kubeconfig"`
136+
Namespace string `schema:"namespace"`
137+
}{
138+
// Defaults would go here.
139+
}
140+
141+
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
142+
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
143+
return
144+
}
145+
146+
containerEngine := abi.ContainerEngine{Libpod: runtime}
147+
options := entities.ApplyOptions{CACertFile: query.CACertFile, Kubeconfig: query.Kubeconfig, Namespace: query.Namespace}
148+
if err := containerEngine.KubeApply(r.Context(), r.Body, options); err != nil {
149+
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error applying YAML to k8s cluster: %w", err))
150+
return
151+
}
152+
153+
utils.WriteResponse(w, http.StatusOK, "Deployed!")
154+
}

pkg/api/server/register_kube.go

+44
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,49 @@ func (s *APIServer) registerKubeHandlers(r *mux.Router) error {
111111
// $ref: "#/responses/internalError"
112112
r.HandleFunc(VersionedPath("/libpod/generate/kube"), s.APIHandler(libpod.GenerateKube)).Methods(http.MethodGet)
113113
r.HandleFunc(VersionedPath("/libpod/kube/generate"), s.APIHandler(libpod.KubeGenerate)).Methods(http.MethodGet)
114+
// swagger:operation POST /libpod/kube/apply libpod KubeApplyLibpod
115+
// ---
116+
// tags:
117+
// - containers
118+
// - pods
119+
// summary: Apply a podman workload or Kubernetes YAML file.
120+
// description: Deploy a podman container, pod, volume, or Kubernetes yaml to a Kubernetes cluster.
121+
// parameters:
122+
// - in: query
123+
// name: caCertFile
124+
// type: string
125+
// description: Path to the CA cert file for the Kubernetes cluster.
126+
// - in: query
127+
// name: kubeConfig
128+
// type: string
129+
// description: Path to the kubeconfig file for the Kubernetes cluster.
130+
// - in: query
131+
// name: namespace
132+
// type: string
133+
// description: The namespace to deploy the workload to on the Kubernetes cluster.
134+
// - in: query
135+
// name: service
136+
// type: boolean
137+
// description: Create a service object for the container being deployed.
138+
// - in: query
139+
// name: file
140+
// type: string
141+
// description: Path to the Kubernetes yaml file to deploy.
142+
// - in: body
143+
// name: request
144+
// description: Kubernetes YAML file.
145+
// schema:
146+
// type: string
147+
// produces:
148+
// - application/json
149+
// responses:
150+
// 200:
151+
// description: Kubernetes YAML file successfully deployed to cluster
152+
// schema:
153+
// type: string
154+
// format: binary
155+
// 500:
156+
// $ref: "#/responses/internalError"
157+
r.HandleFunc(VersionedPath("/libpod/kube/apply"), s.APIHandler(libpod.KubeApply)).Methods(http.MethodPost)
114158
return nil
115159
}

pkg/bindings/kube/kube.go

+38
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,41 @@ func DownWithBody(ctx context.Context, body io.Reader) (*entities.KubePlayReport
102102
func Generate(ctx context.Context, nameOrIDs []string, options generate.KubeOptions) (*entities.GenerateKubeReport, error) {
103103
return generate.Kube(ctx, nameOrIDs, &options)
104104
}
105+
106+
func Apply(ctx context.Context, path string, options *ApplyOptions) error {
107+
f, err := os.Open(path)
108+
if err != nil {
109+
return err
110+
}
111+
defer func() {
112+
if err := f.Close(); err != nil {
113+
logrus.Warn(err)
114+
}
115+
}()
116+
117+
return ApplyWithBody(ctx, f, options)
118+
}
119+
120+
func ApplyWithBody(ctx context.Context, body io.Reader, options *ApplyOptions) error {
121+
if options == nil {
122+
options = new(ApplyOptions)
123+
}
124+
125+
conn, err := bindings.GetClient(ctx)
126+
if err != nil {
127+
return err
128+
}
129+
130+
params, err := options.ToParams()
131+
if err != nil {
132+
return err
133+
}
134+
135+
response, err := conn.DoRequest(ctx, body, http.MethodPost, "/kube/apply", params, nil)
136+
if err != nil {
137+
return err
138+
}
139+
defer response.Body.Close()
140+
141+
return nil
142+
}

pkg/bindings/kube/types.go

+16
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,19 @@ type PlayOptions struct {
4747
// Userns - define the user namespace to use.
4848
Userns *string
4949
}
50+
51+
// ApplyOptions are optional options for applying kube YAML files to a k8s cluster
52+
//
53+
//go:generate go run ../generator/generator.go ApplyOptions
54+
type ApplyOptions struct {
55+
// Kubeconfig - path to the cluster's kubeconfig file.
56+
Kubeconfig *string
57+
// Namespace - namespace to deploy the workload in on the cluster.
58+
Namespace *string
59+
// CACertFile - the path to the CA cert file for the Kubernetes cluster.
60+
CACertFile *string
61+
// File - the path to the Kubernetes yaml to deploy.
62+
File *string
63+
// Service - creates a service for the container being deployed.
64+
Service *bool
65+
}

0 commit comments

Comments
 (0)