Skip to content

Commit

Permalink
Merge pull request #17950 from umohnani8/deployments
Browse files Browse the repository at this point in the history
Support Deployment generation with kube generate
  • Loading branch information
openshift-merge-robot authored Apr 3, 2023
2 parents facb5b0 + 4f90194 commit 9893345
Show file tree
Hide file tree
Showing 17 changed files with 643 additions and 35 deletions.
20 changes: 20 additions & 0 deletions cmd/podman/kube/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"fmt"
"io"
"os"
"strings"

"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/generate"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -68,10 +70,28 @@ func generateFlags(cmd *cobra.Command) {
flags.StringVarP(&generateFile, filenameFlagName, "f", "", "Write output to the specified path")
_ = cmd.RegisterFlagCompletionFunc(filenameFlagName, completion.AutocompleteDefault)

// TODO: default should be configurable in containers.conf
typeFlagName := "type"
flags.StringVarP(&generateOptions.Type, typeFlagName, "t", define.K8sKindPod, "Generate YAML for the given Kubernetes kind")
_ = cmd.RegisterFlagCompletionFunc(typeFlagName, completion.AutocompleteNone)

replicasFlagName := "replicas"
flags.Int32VarP(&generateOptions.Replicas, replicasFlagName, "r", 1, "Set the replicas number for Deployment kind")
_ = cmd.RegisterFlagCompletionFunc(replicasFlagName, completion.AutocompleteNone)

flags.SetNormalizeFunc(utils.AliasFlags)
}

func generateKube(cmd *cobra.Command, args []string) error {
typeVal, err := cmd.Flags().GetString("type")
if err != nil {
return err
}
typeVal = strings.ToLower(typeVal)
if typeVal != define.K8sKindPod && typeVal != define.K8sKindDeployment {
return fmt.Errorf("invalid type given, only supported types are pod and deployment")
}

report, err := registry.ContainerEngine().GenerateKube(registry.GetContext(), args, generateOptions)
if err != nil {
return err
Expand Down
48 changes: 47 additions & 1 deletion docs/source/markdown/podman-kube-generate.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ podman-kube-generate - Generate Kubernetes YAML based on containers, pods or vol

## DESCRIPTION
**podman kube generate** will generate Kubernetes YAML (v1 specification) from Podman containers, pods or volumes. Regardless of whether
the input is for containers or pods, Podman will always generate the specification as a Pod. The input may be in the form
the input is for containers or pods, Podman will generate the specification as a Pod by default. The input may be in the form
of one or more containers, pods or volumes names or IDs.

`Podman Containers or Pods`
Expand All @@ -34,10 +34,19 @@ Note that the generated Kubernetes YAML file can be used to re-run the deploymen

Output to the given file, instead of STDOUT. If the file already exists, `kube generate` will refuse to replace it and return an error.

#### **--replicas**, **-r**=*replica count*

The value to set `replicas` to when generating a **Deployment** kind.
Note: this can only be set with the option `--type=deployment`.

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

#### **--type**, **-t**=*pod | deployment*

The Kubernetes kind to generate in the YAML file. Currently, the only supported Kubernetes specifications are `Pod` and `Deployment`. By default, the `Pod` specification will be generated.

## EXAMPLES

Create Kubernetes Pod YAML for a container called `some-mariadb`.
Expand Down Expand Up @@ -80,6 +89,43 @@ spec:
tty: true
```

Create Kubernetes Deployment YAML with 3 replicas for a container called `dep-ctr`
```
$ podman kube generate --type deployment --replicas 3 dep-ct
r
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-4.5.0-dev
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: "2023-03-27T20:45:08Z"
labels:
app: dep-ctr-pod
name: dep-ctr-pod-deployment
spec:
replicas: 3
selector:
matchLabels:
app: dep-ctr-pod
template:
metadata:
annotations:
io.podman.annotations.ulimit: nofile=524288:524288,nproc=127332:127332
creationTimestamp: "2023-03-27T20:45:08Z"
labels:
app: dep-ctr-pod
name: dep-ctr-pod
spec:
containers:
- command:
- top
image: docker.io/library/alpine:latest
name: dep-ctr
```


Create Kubernetes Pod YAML for a container with the directory `/home/user/my-data` on the host bind-mounted in the container to `/volume`.
```
$ podman kube generate my-container-with-bind-mounted-data
Expand Down
8 changes: 8 additions & 0 deletions libpod/define/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,11 @@ const (
// ContainerInitPath is the default path of the mounted container init.
ContainerInitPath = "/run/podman-init"
)

// Kubernetes Kinds
const (
// A Pod kube yaml spec
K8sKindPod = "pod"
// A Deployment kube yaml spec
K8sKindDeployment = "deployment"
)
95 changes: 93 additions & 2 deletions libpod/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
cutil "github.com/containers/common/pkg/util"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/annotations"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/env"
v1 "github.com/containers/podman/v4/pkg/k8s.io/api/core/v1"
"github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/api/resource"
Expand Down Expand Up @@ -107,8 +108,8 @@ func (p *Pod) GenerateForKube(ctx context.Context, getService bool) (*v1.Pod, []
pod.Spec.RestartPolicy = v1.RestartPolicyOnFailure
case define.RestartPolicyNo:
pod.Spec.RestartPolicy = v1.RestartPolicyNever
default: // some pod create from cmdline, such as "", so set it to Never
pod.Spec.RestartPolicy = v1.RestartPolicyNever
default: // some pod create from cmdline, such as "", so set it to "" as k8s automatically defaults to always
pod.Spec.RestartPolicy = ""
}
break
}
Expand All @@ -131,6 +132,65 @@ func (p *Pod) getInfraContainer() (*Container, error) {
return p.runtime.GetContainer(infraID)
}

// GenerateForKubeDeployment returns a YAMLDeployment from a YAMLPod that is then used to create a kubernetes Deployment
// kind YAML.
func GenerateForKubeDeployment(ctx context.Context, pod *YAMLPod, options entities.GenerateKubeOptions) (*YAMLDeployment, error) {
// Restart policy for Deployments can only be set to Always
if options.Type == define.K8sKindDeployment && !(pod.Spec.RestartPolicy == "" || pod.Spec.RestartPolicy == define.RestartPolicyAlways) {
return nil, fmt.Errorf("k8s Deployments can only have restartPolicy set to Always")
}

// Create label map that will be added to podSpec and Deployment metadata
// The matching label lets the deployment know which pods to manage
appKey := "app"
matchLabels := map[string]string{appKey: pod.Name}
// Add the key:value (app:pod-name) to the podSpec labels
if pod.Labels == nil {
pod.Labels = matchLabels
} else {
pod.Labels[appKey] = pod.Name
}

depSpec := YAMLDeploymentSpec{
DeploymentSpec: v1.DeploymentSpec{
Selector: &v12.LabelSelector{
MatchLabels: matchLabels,
},
},
Template: &YAMLPodTemplateSpec{
PodTemplateSpec: v1.PodTemplateSpec{
ObjectMeta: pod.ObjectMeta,
},
Spec: pod.Spec,
},
}

// Add replicas count if user adds replica number with --replicas flag and is greater than 1
// If replicas is set to 1, no need to add it to the generated yaml as k8s automatically defaults
// to that. Podman as sets replicas to 1 by default.
if options.Replicas > 1 {
depSpec.Replicas = &options.Replicas
}

// Create the Deployment opbject
dep := YAMLDeployment{
Deployment: v1.Deployment{
ObjectMeta: v12.ObjectMeta{
Name: pod.Name + "-deployment",
CreationTimestamp: pod.CreationTimestamp,
Labels: pod.Labels,
},
TypeMeta: v12.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
},
Spec: &depSpec,
}

return &dep, nil
}

// GenerateForKube generates a v1.PersistentVolumeClaim from a libpod volume.
func (v *Volume) GenerateForKube() *v1.PersistentVolumeClaim {
annotations := make(map[string]string)
Expand Down Expand Up @@ -195,6 +255,37 @@ type YAMLPod struct {
Status *v1.PodStatus `json:"status,omitempty"`
}

// YAMLPodTemplateSpec represents the same k8s API core PodTemplateStruct with a
// small change and that is having Spec as a pointer to YAMLPodSpec.
// Because Go doesn't omit empty struct and we want to omit any empty structs in the
// Pod yaml. This is used when generating a Deployment kind.
type YAMLPodTemplateSpec struct {
v1.PodTemplateSpec
Spec *YAMLPodSpec `json:"spec,omitempty"`
}

// YAMLDeploymentSpec represents the same k8s API core DeploymentSpec with a small
// change and that is having Template as a pointer to YAMLPodTemplateSpec and Strategy
// as a pointer to k8s API core DeploymentStrategy.
// Because Go doesn't omit empty struct and we want to omit Strategy and any fields in the Pod YAML
// if it's empty.
type YAMLDeploymentSpec struct {
v1.DeploymentSpec
Template *YAMLPodTemplateSpec `json:"template,omitempty"`
Strategy *v1.DeploymentStrategy `json:"strategy,omitempty"`
}

// YAMLDeployment represents the same k8s API core Deployment with a small change
// and that is having Spec as a pointer to YAMLDeploymentSpec and Status as a pointer to
// k8s API core DeploymentStatus.
// Because Go doesn't omit empty struct and we want to omit Status and any fields in the DeploymentSpec
// if it's empty.
type YAMLDeployment struct {
v1.Deployment
Spec *YAMLDeploymentSpec `json:"spec,omitempty"`
Status *v1.DeploymentStatus `json:"status,omitempty"`
}

// YAMLService represents the same k8s API core Service struct with a small
// change and that is having Status as a pointer to k8s API core ServiceStatus.
// Because Go doesn't omit empty struct and we want to omit Status in YAML
Expand Down
11 changes: 8 additions & 3 deletions pkg/api/handlers/libpod/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"

"github.com/containers/podman/v4/libpod"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/api/handlers/utils"
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/containers/podman/v4/pkg/domain/entities"
Expand Down Expand Up @@ -89,10 +90,14 @@ func GenerateKube(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
Names []string `schema:"names"`
Service bool `schema:"service"`
Names []string `schema:"names"`
Service bool `schema:"service"`
Type string `schema:"type"`
Replicas int32 `schema:"replicas"`
}{
// Defaults would go here.
Type: define.K8sKindPod,
Replicas: 1,
}

if err := decoder.Decode(&query, r.URL.Query()); err != nil {
Expand All @@ -101,7 +106,7 @@ func GenerateKube(w http.ResponseWriter, r *http.Request) {
}

containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.GenerateKubeOptions{Service: query.Service}
options := entities.GenerateKubeOptions{Service: query.Service, Type: query.Type, Replicas: query.Replicas}
report, err := containerEngine.GenerateKube(r.Context(), query.Names, options)
if err != nil {
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("generating YAML: %w", err))
Expand Down
11 changes: 11 additions & 0 deletions pkg/api/server/register_kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,17 @@ func (s *APIServer) registerKubeHandlers(r *mux.Router) error {
// type: boolean
// default: false
// description: Generate YAML for a Kubernetes service object.
// - in: query
// name: type
// type: string
// default: pod
// description: Generate YAML for the given Kubernetes kind.
// - in: query
// name: replicas
// type: integer
// format: int32
// default: 0
// description: Set the replica number for Deployment kind.
// produces:
// - text/vnd.yaml
// - application/json
Expand Down
4 changes: 4 additions & 0 deletions pkg/bindings/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"net/http"
"strconv"

"github.com/containers/podman/v4/pkg/bindings"
"github.com/containers/podman/v4/pkg/domain/entities"
Expand Down Expand Up @@ -54,6 +55,9 @@ func Kube(ctx context.Context, nameOrIDs []string, options *KubeOptions) (*entit
for _, name := range nameOrIDs {
params.Add("names", name)
}
if options.Replicas != nil {
params.Set("replicas", strconv.Itoa(int(*options.Replicas)))
}
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/generate/kube", params, nil)
if err != nil {
return nil, err
Expand Down
4 changes: 4 additions & 0 deletions pkg/bindings/generate/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ package generate
type KubeOptions struct {
// Service - generate YAML for a Kubernetes _service_ object.
Service *bool
// Type - the k8s kind to be generated i.e Pod or Deployment
Type *string
// Replicas - the value to set in the replicas field for a Deployment
Replicas *int32
}

// SystemdOptions are optional options for generating systemd files
Expand Down
30 changes: 30 additions & 0 deletions pkg/bindings/generate/types_kube_options.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pkg/domain/entities/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ type GenerateSystemdReport struct {
type GenerateKubeOptions struct {
// Service - generate YAML for a Kubernetes _service_ object.
Service bool
// Type - the k8s kind to be generated i.e Pod or Deployment
Type string
// Replicas - the value to set in the replicas field for a Deployment
Replicas int32
}

type KubeGenerateOptions = GenerateKubeOptions
Expand Down
Loading

0 comments on commit 9893345

Please sign in to comment.