Skip to content

Commit

Permalink
Merge pull request containers#15091 from umohnani8/lift
Browse files Browse the repository at this point in the history
Add podman kube apply command
  • Loading branch information
openshift-merge-robot authored Nov 2, 2022
2 parents 0e367c7 + f6c7432 commit c35ed35
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 c35ed35

Please sign in to comment.