Skip to content

Commit

Permalink
Add podman kube apply command
Browse files Browse the repository at this point in the history
Add the abilitiy to deploy the generated kube yaml to a
kubernetes cluster with the podman kube apply command.
Add support to directly apply containers, pods, or volumes
by passing in their names or ids to the command.
Use the kubernetes API endpoints and http requests to connect
to the cluster and deploy the various kubernetes object kinds.

Signed-off-by: Urvashi Mohnani <[email protected]>
  • Loading branch information
umohnani8 committed Nov 1, 2022
1 parent fa19f57 commit f6c7432
Show file tree
Hide file tree
Showing 17 changed files with 1,007 additions and 41 deletions.
111 changes: 111 additions & 0 deletions cmd/podman/kube/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package kube

import (
"errors"
"fmt"
"io"
"os"

"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/spf13/cobra"
)

var (
applyOptions = entities.ApplyOptions{}
applyDescription = `Command applies a podman container, pod, volume, or kube yaml to a Kubernetes cluster when a kubeconfig file is given.`

applyCmd = &cobra.Command{
Use: "apply [options] [CONTAINER...|POD...|VOLUME...]",
Short: "Deploy a podman container, pod, volume, or Kubernetes yaml to a Kubernetes cluster",
Long: applyDescription,
RunE: apply,
ValidArgsFunction: common.AutocompleteForKube,
Example: `podman kube apply ctrName volName
podman kube apply --namespace project -f fileName`,
}
)

func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: applyCmd,
Parent: kubeCmd,
})
applyFlags(applyCmd)
}

func applyFlags(cmd *cobra.Command) {
flags := cmd.Flags()
flags.SetNormalizeFunc(utils.AliasFlags)

kubeconfigFlagName := "kubeconfig"
flags.StringVarP(&applyOptions.Kubeconfig, kubeconfigFlagName, "k", os.Getenv("KUBECONFIG"), "Path to the kubeconfig file for the Kubernetes cluster")
_ = cmd.RegisterFlagCompletionFunc(kubeconfigFlagName, completion.AutocompleteDefault)

namespaceFlagName := "ns"
flags.StringVarP(&applyOptions.Namespace, namespaceFlagName, "", "", "The namespace to deploy the workload to on the Kubernetes cluster")
_ = cmd.RegisterFlagCompletionFunc(namespaceFlagName, completion.AutocompleteNone)

caCertFileFlagName := "ca-cert-file"
flags.StringVarP(&applyOptions.CACertFile, caCertFileFlagName, "", "", "Path to the CA cert file for the Kubernetes cluster.")
_ = cmd.RegisterFlagCompletionFunc(caCertFileFlagName, completion.AutocompleteDefault)

fileFlagName := "file"
flags.StringVarP(&applyOptions.File, fileFlagName, "f", "", "Path to the Kubernetes yaml file to deploy.")
_ = cmd.RegisterFlagCompletionFunc(fileFlagName, completion.AutocompleteDefault)

serviceFlagName := "service"
flags.BoolVarP(&applyOptions.Service, serviceFlagName, "s", false, "Create a service object for the container being deployed.")
}

func apply(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("file") && cmd.Flags().Changed("service") {
return errors.New("cannot set --service and --file at the same time")
}

kubeconfig, err := cmd.Flags().GetString("kubeconfig")
if err != nil {
return err
}
if kubeconfig == "" {
return errors.New("kubeconfig not given, unable to connect to cluster")
}

var reader io.Reader
if cmd.Flags().Changed("file") {
yamlFile := applyOptions.File
if yamlFile == "-" {
yamlFile = os.Stdin.Name()
}

f, err := os.Open(yamlFile)
if err != nil {
return err
}
defer f.Close()
reader = f
} else {
generateOptions.Service = applyOptions.Service
report, err := registry.ContainerEngine().GenerateKube(registry.GetContext(), args, generateOptions)
if err != nil {
return err
}
if r, ok := report.Reader.(io.ReadCloser); ok {
defer r.Close()
}
reader = report.Reader
}

fmt.Println("Deploying to cluster...")

if err = registry.ContainerEngine().KubeApply(registry.GetContext(), reader, applyOptions); err != nil {
return err
}

fmt.Println("Successfully deployed workloads to cluster!")

return nil
}
73 changes: 73 additions & 0 deletions docs/source/markdown/podman-kube-apply.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
-% podman-kube-apply(1)
## NAME
podman-kube-apply - Apply Kubernetes YAML based on containers, pods, or volumes to a Kubernetes cluster

## SYNOPSIS
**podman kube apply** [*options*] [*container...* | *pod...* | *volume...*]

## DESCRIPTION
**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.

Note that the Kubernetes YAML file can be used to run the deployment in Podman via podman-play-kube(1).

## OPTIONS

#### **--ca-cert-file**=*ca cert file path | "insecure"*

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.

#### **--file**, **-f**=*kube yaml filepath*

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=-`.

#### **--kubeconfig**, **-k**=*kubeconfig filepath*

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.
Note: A kubeconfig can have multiple cluster configurations, but `kube generate` will always only pick the first cluster configuration in the given kubeconfig.

#### **--ns**=*namespace*

The namespace or project to deploy the workloads of the generated kube yaml to in the Kubernetes cluster.

#### **--service**, **-s**

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.

## EXAMPLES

Apply a podman volume and container to the "default" namespace in a Kubernetes cluster.
```
$ podman kube apply --kubeconfig /tmp/kubeconfig myvol vol-test-1
Deploying to cluster...
Successfully deployed workloads to cluster!
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
vol-test-1-pod 1/1 Running 0 9m
```

Apply a Kubernetes YAML file to the "default" namespace in a Kubernetes cluster.
```
$ podman kube apply --kubeconfig /tmp/kubeconfig -f vol.yaml
Deploying to cluster...
Successfully deployed workloads to cluster!
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
vol-test-2-pod 1/1 Running 0 9m
```

Apply a Kubernetes YAML file to the "test1" namespace in a Kubernetes cluster.
```
$ podman kube apply --kubeconfig /tmp/kubeconfig --ns test1 vol-test-3
Deploying to cluster...
Successfully deployed workloads to cluster!
$ kubectl get pods --namespace test1
NAME READY STATUS RESTARTS AGE
vol-test-3-pod 1/1 Running 0 9m
```

## SEE ALSO
**[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)**

## HISTORY
September 2022, Originally compiled by Urvashi Mohnani (umohnani at redhat dot com)
3 changes: 1 addition & 2 deletions docs/source/markdown/podman-kube-generate.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ Output to the given file, instead of STDOUT. If the file already exists, `kube g

#### **--service**, **-s**

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.
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.

## EXAMPLES

Expand Down
3 changes: 2 additions & 1 deletion docs/source/markdown/podman-kube.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ file input. Containers will be automatically started.

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

## SEE ALSO
**[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(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)**

## HISTORY
December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com)
26 changes: 26 additions & 0 deletions pkg/api/handlers/libpod/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,29 @@ func KubePlayDown(w http.ResponseWriter, r *http.Request) {
func KubeGenerate(w http.ResponseWriter, r *http.Request) {
GenerateKube(w, r)
}

func KubeApply(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
CACertFile string `schema:"caCertFile"`
Kubeconfig string `schema:"kubeconfig"`
Namespace string `schema:"namespace"`
}{
// Defaults would go here.
}

if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
return
}

containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.ApplyOptions{CACertFile: query.CACertFile, Kubeconfig: query.Kubeconfig, Namespace: query.Namespace}
if err := containerEngine.KubeApply(r.Context(), r.Body, options); err != nil {
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error applying YAML to k8s cluster: %w", err))
return
}

utils.WriteResponse(w, http.StatusOK, "Deployed!")
}
44 changes: 44 additions & 0 deletions pkg/api/server/register_kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,49 @@ func (s *APIServer) registerKubeHandlers(r *mux.Router) error {
// $ref: "#/responses/internalError"
r.HandleFunc(VersionedPath("/libpod/generate/kube"), s.APIHandler(libpod.GenerateKube)).Methods(http.MethodGet)
r.HandleFunc(VersionedPath("/libpod/kube/generate"), s.APIHandler(libpod.KubeGenerate)).Methods(http.MethodGet)
// swagger:operation POST /libpod/kube/apply libpod KubeApplyLibpod
// ---
// tags:
// - containers
// - pods
// summary: Apply a podman workload or Kubernetes YAML file.
// description: Deploy a podman container, pod, volume, or Kubernetes yaml to a Kubernetes cluster.
// parameters:
// - in: query
// name: caCertFile
// type: string
// description: Path to the CA cert file for the Kubernetes cluster.
// - in: query
// name: kubeConfig
// type: string
// description: Path to the kubeconfig file for the Kubernetes cluster.
// - in: query
// name: namespace
// type: string
// description: The namespace to deploy the workload to on the Kubernetes cluster.
// - in: query
// name: service
// type: boolean
// description: Create a service object for the container being deployed.
// - in: query
// name: file
// type: string
// description: Path to the Kubernetes yaml file to deploy.
// - in: body
// name: request
// description: Kubernetes YAML file.
// schema:
// type: string
// produces:
// - application/json
// responses:
// 200:
// description: Kubernetes YAML file successfully deployed to cluster
// schema:
// type: string
// format: binary
// 500:
// $ref: "#/responses/internalError"
r.HandleFunc(VersionedPath("/libpod/kube/apply"), s.APIHandler(libpod.KubeApply)).Methods(http.MethodPost)
return nil
}
38 changes: 38 additions & 0 deletions pkg/bindings/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,41 @@ func DownWithBody(ctx context.Context, body io.Reader) (*entities.KubePlayReport
func Generate(ctx context.Context, nameOrIDs []string, options generate.KubeOptions) (*entities.GenerateKubeReport, error) {
return generate.Kube(ctx, nameOrIDs, &options)
}

func Apply(ctx context.Context, path string, options *ApplyOptions) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer func() {
if err := f.Close(); err != nil {
logrus.Warn(err)
}
}()

return ApplyWithBody(ctx, f, options)
}

func ApplyWithBody(ctx context.Context, body io.Reader, options *ApplyOptions) error {
if options == nil {
options = new(ApplyOptions)
}

conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}

params, err := options.ToParams()
if err != nil {
return err
}

response, err := conn.DoRequest(ctx, body, http.MethodPost, "/kube/apply", params, nil)
if err != nil {
return err
}
defer response.Body.Close()

return nil
}
16 changes: 16 additions & 0 deletions pkg/bindings/kube/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,19 @@ type PlayOptions struct {
// Userns - define the user namespace to use.
Userns *string
}

// ApplyOptions are optional options for applying kube YAML files to a k8s cluster
//
//go:generate go run ../generator/generator.go ApplyOptions
type ApplyOptions struct {
// Kubeconfig - path to the cluster's kubeconfig file.
Kubeconfig *string
// Namespace - namespace to deploy the workload in on the cluster.
Namespace *string
// CACertFile - the path to the CA cert file for the Kubernetes cluster.
CACertFile *string
// File - the path to the Kubernetes yaml to deploy.
File *string
// Service - creates a service for the container being deployed.
Service *bool
}
Loading

0 comments on commit f6c7432

Please sign in to comment.