mirror of
https://github.com/containers/podman.git
synced 2025-05-21 00:56:36 +08:00
Merge pull request #17950 from umohnani8/deployments
Support Deployment generation with kube generate
This commit is contained in:
@ -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"
|
||||
)
|
||||
@ -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
|
||||
|
@ -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`
|
||||
@ -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`.
|
||||
@ -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
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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"
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
@ -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
|
||||
|
@ -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"
|
||||
@ -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 {
|
||||
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -31,3 +31,33 @@ func (o *KubeOptions) GetService() bool {
|
||||
}
|
||||
return *o.Service
|
||||
}
|
||||
|
||||
// WithType set field Type to given value
|
||||
func (o *KubeOptions) WithType(value string) *KubeOptions {
|
||||
o.Type = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetType returns value of field Type
|
||||
func (o *KubeOptions) GetType() string {
|
||||
if o.Type == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Type
|
||||
}
|
||||
|
||||
// WithReplicas set field Replicas to given value
|
||||
func (o *KubeOptions) WithReplicas(value int32) *KubeOptions {
|
||||
o.Replicas = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetReplicas returns value of field Replicas
|
||||
func (o *KubeOptions) GetReplicas() int32 {
|
||||
if o.Replicas == nil {
|
||||
var z int32
|
||||
return z
|
||||
}
|
||||
return *o.Replicas
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -103,13 +103,20 @@ func (ic *ContainerEngine) GenerateSpec(ctx context.Context, opts *entities.Gene
|
||||
|
||||
func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
|
||||
var (
|
||||
pods []*libpod.Pod
|
||||
ctrs []*libpod.Container
|
||||
vols []*libpod.Volume
|
||||
podContent [][]byte
|
||||
content [][]byte
|
||||
pods []*libpod.Pod
|
||||
ctrs []*libpod.Container
|
||||
vols []*libpod.Volume
|
||||
typeContent [][]byte
|
||||
content [][]byte
|
||||
)
|
||||
|
||||
if options.Replicas > 1 && options.Type != define.K8sKindDeployment {
|
||||
return nil, fmt.Errorf("--replicas can only be set when --type is set to deployment")
|
||||
}
|
||||
if options.Replicas < 1 {
|
||||
return nil, fmt.Errorf("--replicas has to be greater than or equal to 1. By default, --replicas is set to 1")
|
||||
}
|
||||
|
||||
defaultKubeNS := true
|
||||
// Lookup for podman objects.
|
||||
for _, nameOrID := range nameOrIDs {
|
||||
@ -187,12 +194,12 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string,
|
||||
|
||||
// Generate kube pods and services from pods.
|
||||
if len(pods) >= 1 {
|
||||
pos, svcs, err := getKubePods(ctx, pods, options.Service)
|
||||
out, svcs, err := getKubePods(ctx, pods, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
podContent = append(podContent, pos...)
|
||||
typeContent = append(typeContent, out...)
|
||||
if options.Service {
|
||||
content = append(content, svcs...)
|
||||
}
|
||||
@ -212,12 +219,29 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string,
|
||||
`
|
||||
content = append(content, []byte(warning))
|
||||
}
|
||||
b, err := generateKubeYAML(libpod.ConvertV1PodToYAMLPod(po))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
// Create a pod or deployment kind depending on what Type was requested by the user
|
||||
switch options.Type {
|
||||
case define.K8sKindDeployment:
|
||||
dep, err := libpod.GenerateForKubeDeployment(ctx, libpod.ConvertV1PodToYAMLPod(po), options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := generateKubeYAML(dep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typeContent = append(typeContent, b)
|
||||
case define.K8sKindPod:
|
||||
b, err := generateKubeYAML(libpod.ConvertV1PodToYAMLPod(po))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typeContent = append(typeContent, b)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid generation type - only pods and deployments are currently supported")
|
||||
}
|
||||
|
||||
podContent = append(podContent, b)
|
||||
if options.Service {
|
||||
svc, err := libpod.GenerateKubeServiceFromV1Pod(po, []k8sAPI.ServicePort{})
|
||||
if err != nil {
|
||||
@ -231,8 +255,8 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string,
|
||||
}
|
||||
}
|
||||
|
||||
// Content order is based on helm install order (secret, persistentVolumeClaim, service, pod).
|
||||
content = append(content, podContent...)
|
||||
// Content order is based on helm install order (secret, persistentVolumeClaim, service, pod/deployment).
|
||||
content = append(content, typeContent...)
|
||||
|
||||
// Generate kube YAML file from all kube kinds.
|
||||
k, err := generateKubeOutput(content)
|
||||
@ -243,24 +267,39 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string,
|
||||
return &entities.GenerateKubeReport{Reader: bytes.NewReader(k)}, nil
|
||||
}
|
||||
|
||||
// getKubePods returns kube pod and service YAML files from podman pods.
|
||||
func getKubePods(ctx context.Context, pods []*libpod.Pod, getService bool) ([][]byte, [][]byte, error) {
|
||||
pos := [][]byte{}
|
||||
// getKubePods returns kube pod or deployment and service YAML files from podman pods.
|
||||
func getKubePods(ctx context.Context, pods []*libpod.Pod, options entities.GenerateKubeOptions) ([][]byte, [][]byte, error) {
|
||||
out := [][]byte{}
|
||||
svcs := [][]byte{}
|
||||
|
||||
for _, p := range pods {
|
||||
po, sp, err := p.GenerateForKube(ctx, getService)
|
||||
po, sp, err := p.GenerateForKube(ctx, options.Service)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
b, err := generateKubeYAML(po)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
switch options.Type {
|
||||
case define.K8sKindDeployment:
|
||||
dep, err := libpod.GenerateForKubeDeployment(ctx, libpod.ConvertV1PodToYAMLPod(po), options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
b, err := generateKubeYAML(dep)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
out = append(out, b)
|
||||
case define.K8sKindPod:
|
||||
b, err := generateKubeYAML(libpod.ConvertV1PodToYAMLPod(po))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
out = append(out, b)
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("invalid generation type - only pods and deployments are currently supported")
|
||||
}
|
||||
pos = append(pos, b)
|
||||
|
||||
if getService {
|
||||
if options.Service {
|
||||
svc, err := libpod.GenerateKubeServiceFromV1Pod(po, sp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -273,7 +312,7 @@ func getKubePods(ctx context.Context, pods []*libpod.Pod, getService bool) ([][]
|
||||
}
|
||||
}
|
||||
|
||||
return pos, svcs, nil
|
||||
return out, svcs, nil
|
||||
}
|
||||
|
||||
// getKubePVCs returns kube persistent volume claim YAML files from podman volumes.
|
||||
|
@ -46,7 +46,7 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string,
|
||||
//
|
||||
// Note: Caller is responsible for closing returned Reader
|
||||
func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string, opts entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
|
||||
options := new(generate.KubeOptions).WithService(opts.Service)
|
||||
options := new(generate.KubeOptions).WithService(opts.Service).WithType(opts.Type).WithReplicas(opts.Replicas)
|
||||
return generate.Kube(ic.ClientCtx, nameOrIDs, options)
|
||||
}
|
||||
|
||||
|
@ -4738,3 +4738,220 @@ const (
|
||||
// and an error will be returned by the exec plugin runner.
|
||||
AlwaysExecInteractiveMode ExecInteractiveMode = "Always"
|
||||
)
|
||||
|
||||
// +genclient
|
||||
// +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/api/autoscaling/v1.Scale
|
||||
// +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/api/autoscaling/v1.Scale,result=k8s.io/api/autoscaling/v1.Scale
|
||||
// +genclient:method=ApplyScale,verb=apply,subresource=scale,input=k8s.io/api/autoscaling/v1.Scale,result=k8s.io/api/autoscaling/v1.Scale
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Deployment enables declarative updates for Pods and ReplicaSets.
|
||||
type Deployment struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard object's metadata.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// Specification of the desired behavior of the Deployment.
|
||||
// +optional
|
||||
Spec DeploymentSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
|
||||
|
||||
// Most recently observed status of the Deployment.
|
||||
// +optional
|
||||
Status DeploymentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
|
||||
}
|
||||
|
||||
// DeploymentSpec is the specification of the desired behavior of the Deployment.
|
||||
type DeploymentSpec struct {
|
||||
// Number of desired pods. This is a pointer to distinguish between explicit
|
||||
// zero and not specified. Defaults to 1.
|
||||
// +optional
|
||||
Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"`
|
||||
|
||||
// Label selector for pods. Existing ReplicaSets whose pods are
|
||||
// selected by this will be the ones affected by this deployment.
|
||||
// It must match the pod template's labels.
|
||||
Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,2,opt,name=selector"`
|
||||
|
||||
// Template describes the pods that will be created.
|
||||
// The only allowed template.spec.restartPolicy value is "Always".
|
||||
Template PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"`
|
||||
|
||||
// The deployment strategy to use to replace existing pods with new ones.
|
||||
// +optional
|
||||
// +patchStrategy=retainKeys
|
||||
Strategy DeploymentStrategy `json:"strategy,omitempty" patchStrategy:"retainKeys" protobuf:"bytes,4,opt,name=strategy"`
|
||||
|
||||
// Minimum number of seconds for which a newly created pod should be ready
|
||||
// without any of its container crashing, for it to be considered available.
|
||||
// Defaults to 0 (pod will be considered available as soon as it is ready)
|
||||
// +optional
|
||||
MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,5,opt,name=minReadySeconds"`
|
||||
|
||||
// The number of old ReplicaSets to retain to allow rollback.
|
||||
// This is a pointer to distinguish between explicit zero and not specified.
|
||||
// Defaults to 10.
|
||||
// +optional
|
||||
RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,6,opt,name=revisionHistoryLimit"`
|
||||
|
||||
// Indicates that the deployment is paused.
|
||||
// +optional
|
||||
Paused bool `json:"paused,omitempty" protobuf:"varint,7,opt,name=paused"`
|
||||
|
||||
// The maximum time in seconds for a deployment to make progress before it
|
||||
// is considered to be failed. The deployment controller will continue to
|
||||
// process failed deployments and a condition with a ProgressDeadlineExceeded
|
||||
// reason will be surfaced in the deployment status. Note that progress will
|
||||
// not be estimated during the time a deployment is paused. Defaults to 600s.
|
||||
ProgressDeadlineSeconds *int32 `json:"progressDeadlineSeconds,omitempty" protobuf:"varint,9,opt,name=progressDeadlineSeconds"`
|
||||
}
|
||||
|
||||
const (
|
||||
// DefaultDeploymentUniqueLabelKey is the default key of the selector that is added
|
||||
// to existing ReplicaSets (and label key that is added to its pods) to prevent the existing ReplicaSets
|
||||
// to select new pods (and old pods being select by new ReplicaSet).
|
||||
DefaultDeploymentUniqueLabelKey string = "pod-template-hash"
|
||||
)
|
||||
|
||||
// DeploymentStrategy describes how to replace existing pods with new ones.
|
||||
type DeploymentStrategy struct {
|
||||
// Type of deployment. Can be "Recreate" or "RollingUpdate". Default is RollingUpdate.
|
||||
// +optional
|
||||
Type DeploymentStrategyType `json:"type,omitempty" protobuf:"bytes,1,opt,name=type,casttype=DeploymentStrategyType"`
|
||||
|
||||
// Rolling update config params. Present only if DeploymentStrategyType =
|
||||
// RollingUpdate.
|
||||
//---
|
||||
// TODO: Update this to follow our convention for oneOf, whatever we decide it
|
||||
// to be.
|
||||
// +optional
|
||||
RollingUpdate *RollingUpdateDeployment `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"`
|
||||
}
|
||||
|
||||
// +enum
|
||||
type DeploymentStrategyType string
|
||||
|
||||
const (
|
||||
// Kill all existing pods before creating new ones.
|
||||
RecreateDeploymentStrategyType DeploymentStrategyType = "Recreate"
|
||||
|
||||
// Replace the old ReplicaSets by new one using rolling update i.e gradually scale down the old ReplicaSets and scale up the new one.
|
||||
RollingUpdateDeploymentStrategyType DeploymentStrategyType = "RollingUpdate"
|
||||
)
|
||||
|
||||
// Spec to control the desired behavior of rolling update.
|
||||
type RollingUpdateDeployment struct {
|
||||
// The maximum number of pods that can be unavailable during the update.
|
||||
// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
|
||||
// Absolute number is calculated from percentage by rounding down.
|
||||
// This can not be 0 if MaxSurge is 0.
|
||||
// Defaults to 25%.
|
||||
// Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods
|
||||
// immediately when the rolling update starts. Once new pods are ready, old ReplicaSet
|
||||
// can be scaled down further, followed by scaling up the new ReplicaSet, ensuring
|
||||
// that the total number of pods available at all times during the update is at
|
||||
// least 70% of desired pods.
|
||||
// +optional
|
||||
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,1,opt,name=maxUnavailable"`
|
||||
|
||||
// The maximum number of pods that can be scheduled above the desired number of
|
||||
// pods.
|
||||
// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
|
||||
// This can not be 0 if MaxUnavailable is 0.
|
||||
// Absolute number is calculated from percentage by rounding up.
|
||||
// Defaults to 25%.
|
||||
// Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when
|
||||
// the rolling update starts, such that the total number of old and new pods do not exceed
|
||||
// 130% of desired pods. Once old pods have been killed,
|
||||
// new ReplicaSet can be scaled up further, ensuring that total number of pods running
|
||||
// at any time during the update is at most 130% of desired pods.
|
||||
// +optional
|
||||
MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty" protobuf:"bytes,2,opt,name=maxSurge"`
|
||||
}
|
||||
|
||||
// DeploymentStatus is the most recently observed status of the Deployment.
|
||||
type DeploymentStatus struct {
|
||||
// The generation observed by the deployment controller.
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"`
|
||||
|
||||
// Total number of non-terminated pods targeted by this deployment (their labels match the selector).
|
||||
// +optional
|
||||
Replicas int32 `json:"replicas,omitempty" protobuf:"varint,2,opt,name=replicas"`
|
||||
|
||||
// Total number of non-terminated pods targeted by this deployment that have the desired template spec.
|
||||
// +optional
|
||||
UpdatedReplicas int32 `json:"updatedReplicas,omitempty" protobuf:"varint,3,opt,name=updatedReplicas"`
|
||||
|
||||
// readyReplicas is the number of pods targeted by this Deployment with a Ready Condition.
|
||||
// +optional
|
||||
ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,7,opt,name=readyReplicas"`
|
||||
|
||||
// Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.
|
||||
// +optional
|
||||
AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,4,opt,name=availableReplicas"`
|
||||
|
||||
// Total number of unavailable pods targeted by this deployment. This is the total number of
|
||||
// pods that are still required for the deployment to have 100% available capacity. They may
|
||||
// either be pods that are running but not yet available or pods that still have not been created.
|
||||
// +optional
|
||||
UnavailableReplicas int32 `json:"unavailableReplicas,omitempty" protobuf:"varint,5,opt,name=unavailableReplicas"`
|
||||
|
||||
// Represents the latest available observations of a deployment's current state.
|
||||
// +patchMergeKey=type
|
||||
// +patchStrategy=merge
|
||||
Conditions []DeploymentCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,6,rep,name=conditions"`
|
||||
|
||||
// Count of hash collisions for the Deployment. The Deployment controller uses this
|
||||
// field as a collision avoidance mechanism when it needs to create the name for the
|
||||
// newest ReplicaSet.
|
||||
// +optional
|
||||
CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,8,opt,name=collisionCount"`
|
||||
}
|
||||
|
||||
type DeploymentConditionType string
|
||||
|
||||
// These are valid conditions of a deployment.
|
||||
const (
|
||||
// Available means the deployment is available, ie. at least the minimum available
|
||||
// replicas required are up and running for at least minReadySeconds.
|
||||
DeploymentAvailable DeploymentConditionType = "Available"
|
||||
// Progressing means the deployment is progressing. Progress for a deployment is
|
||||
// considered when a new replica set is created or adopted, and when new pods scale
|
||||
// up or old pods scale down. Progress is not estimated for paused deployments or
|
||||
// when progressDeadlineSeconds is not specified.
|
||||
DeploymentProgressing DeploymentConditionType = "Progressing"
|
||||
// ReplicaFailure is added in a deployment when one of its pods fails to be created
|
||||
// or deleted.
|
||||
DeploymentReplicaFailure DeploymentConditionType = "ReplicaFailure"
|
||||
)
|
||||
|
||||
// DeploymentCondition describes the state of a deployment at a certain point.
|
||||
type DeploymentCondition struct {
|
||||
// Type of deployment condition.
|
||||
Type DeploymentConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=DeploymentConditionType"`
|
||||
// Status of the condition, one of True, False, Unknown.
|
||||
Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"`
|
||||
// The last time this condition was updated.
|
||||
LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty" protobuf:"bytes,6,opt,name=lastUpdateTime"`
|
||||
// Last time the condition transitioned from one status to another.
|
||||
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,7,opt,name=lastTransitionTime"`
|
||||
// The reason for the condition's last transition.
|
||||
Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"`
|
||||
// A human readable message indicating details about the transition.
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// DeploymentList is a list of Deployments.
|
||||
type DeploymentList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard list metadata.
|
||||
// +optional
|
||||
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// Items is the list of Deployments.
|
||||
Items []Deployment `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
||||
|
@ -29,6 +29,12 @@ like "$output" ".*metadata:.*" "Check generated kube yaml(service=true) - metada
|
||||
like "$output" ".*spec:.*" "Check generated kube yaml(service=true) - spec"
|
||||
like "$output" ".*kind:\\sService.*" "Check generated kube yaml(service=true) - kind: Service"
|
||||
|
||||
t GET "libpod/generate/kube?type=deployment&names=$cid" 200
|
||||
like "$output" ".*apiVersion:.*" "Check generated kube yaml - apiVersion"
|
||||
like "$output" ".*kind:\\sDeployment.*" "Check generated kube yaml - kind: Deployment"
|
||||
like "$output" ".*metadata:.*" "Check generated kube yaml - metadata"
|
||||
like "$output" ".*spec:.*" "Check generated kube yaml - spec"
|
||||
|
||||
TMPD=$(mktemp -d podman-apiv2-test-kube.XXXXXX)
|
||||
YAML="${TMPD}/kube.yaml"
|
||||
echo "$output" > $YAML
|
||||
|
@ -531,7 +531,7 @@ var _ = Describe("Podman kube generate", func() {
|
||||
It("podman generate kube on pod with restartPolicy", func() {
|
||||
// podName, set, expect
|
||||
testSli := [][]string{
|
||||
{"testPod1", "", "Never"}, // some pod create from cmdline, so set it to Never
|
||||
{"testPod1", "", ""}, // some pod create from cmdline, so set it to an empty string and let k8s default it to Always
|
||||
{"testPod2", "always", "Always"},
|
||||
{"testPod3", "on-failure", "OnFailure"},
|
||||
{"testPod4", "no", "Never"},
|
||||
@ -1402,4 +1402,87 @@ USER test1`
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring("1231"))
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring("3123"))
|
||||
})
|
||||
|
||||
It("podman generate kube on pod with --type=deployment", func() {
|
||||
podName := "test-pod"
|
||||
session := podmanTest.Podman([]string{"pod", "create", podName})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"create", "--pod", podName, ALPINE, "top"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
session = podmanTest.Podman([]string{"create", "--pod", podName, ALPINE, "sleep", "100"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
kube := podmanTest.Podman([]string{"generate", "kube", "--type", "deployment", podName})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(Exit(0))
|
||||
|
||||
dep := new(v1.Deployment)
|
||||
err := yaml.Unmarshal(kube.Out.Contents(), dep)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(dep.Name).To(Equal(podName + "-deployment"))
|
||||
Expect(dep.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app", podName))
|
||||
Expect(dep.Spec.Template.Name).To(Equal(podName))
|
||||
|
||||
numContainers := 0
|
||||
for range dep.Spec.Template.Spec.Containers {
|
||||
numContainers++
|
||||
}
|
||||
Expect(numContainers).To(Equal(2))
|
||||
})
|
||||
|
||||
It("podman generate kube on ctr with --type=deployment and --replicas=3", func() {
|
||||
ctrName := "test-ctr"
|
||||
session := podmanTest.Podman([]string{"create", "--name", ctrName, ALPINE, "top"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
kube := podmanTest.Podman([]string{"generate", "kube", "--type", "deployment", "--replicas", "3", ctrName})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(Exit(0))
|
||||
|
||||
dep := new(v1.Deployment)
|
||||
err := yaml.Unmarshal(kube.Out.Contents(), dep)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(dep.Name).To(Equal(ctrName + "-pod-deployment"))
|
||||
Expect(dep.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app", ctrName+"-pod"))
|
||||
Expect(dep.Spec.Template.Name).To(Equal(ctrName + "-pod"))
|
||||
Expect(int(*dep.Spec.Replicas)).To(Equal(3))
|
||||
|
||||
numContainers := 0
|
||||
for range dep.Spec.Template.Spec.Containers {
|
||||
numContainers++
|
||||
}
|
||||
Expect(numContainers).To(Equal(1))
|
||||
})
|
||||
|
||||
It("podman generate kube on ctr with --type=pod and --replicas=3 should fail", func() {
|
||||
ctrName := "test-ctr"
|
||||
session := podmanTest.Podman([]string{"create", "--name", ctrName, ALPINE, "top"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
kube := podmanTest.Podman([]string{"generate", "kube", "--type", "pod", "--replicas", "3", ctrName})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(Exit(125))
|
||||
})
|
||||
|
||||
It("podman generate kube on pod with --type=deployment and --restart=no should fail", func() {
|
||||
// TODO: When we add --restart for pods, fix this test to reflect that
|
||||
podName := "test-pod"
|
||||
session := podmanTest.Podman([]string{"pod", "create", podName})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"create", "--pod", podName, "--restart", "no", ALPINE, "top"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
kube := podmanTest.Podman([]string{"generate", "kube", "--type", "deployment", podName})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(Exit(125))
|
||||
})
|
||||
})
|
||||
|
@ -162,3 +162,44 @@ load helpers.bash
|
||||
run minikube kubectl delete namespace $project
|
||||
assert $status -eq 0 "delete namespace $project"
|
||||
}
|
||||
|
||||
@test "minikube - deploy generated container yaml to minikube --type=deployment" {
|
||||
cname="test-ctr"
|
||||
fname="/tmp/minikube_deploy_$(random_string 6).yaml"
|
||||
run_podman container create --name $cname $IMAGE top
|
||||
run_podman kube generate --type deployment -f $fname $cname
|
||||
|
||||
# deploy to the minikube cluster
|
||||
project="dep-ctr-ns"
|
||||
run minikube kubectl create namespace $project
|
||||
assert "$status" -eq 0 "create new namespace $project"
|
||||
run minikube kubectl -- apply -f $fname
|
||||
assert "$status" -eq 0 "deploy $fname to the cluster"
|
||||
assert "$output" == "deployment.apps/$cname-pod-deployment created"
|
||||
wait_for_pods_to_start
|
||||
run minikube kubectl delete namespace $project
|
||||
assert $status -eq 0 "delete namespace $project"
|
||||
}
|
||||
|
||||
@test "minikube - deploy generated pod yaml to minikube --type=deployment" {
|
||||
pname="test-pod"
|
||||
cname1="test-ctr1"
|
||||
cname2="test-ctr2"
|
||||
fname="/tmp/minikube_deploy_$(random_string 6).yaml"
|
||||
|
||||
run_podman pod create --name $pname --publish 9999:8888
|
||||
run_podman container create --name $cname1 --pod $pname $IMAGE sleep 1000
|
||||
run_podman container create --name $cname2 --pod $pname $IMAGE sleep 2000
|
||||
run_podman kube generate --type deployment -f $fname $pname
|
||||
|
||||
# deploy to the minikube cluster
|
||||
project="dep-pod-ns"
|
||||
run minikube kubectl create namespace $project
|
||||
assert "$status" -eq 0 "create new namespace $project"
|
||||
run minikube kubectl -- apply -f $fname
|
||||
assert "$status" -eq 0 "deploy $fname to the cluster"
|
||||
assert "$output" == "deployment.apps/$pname-deployment created"
|
||||
wait_for_pods_to_start
|
||||
run minikube kubectl delete namespace $project
|
||||
assert $status -eq 0 "delete namespace $project"
|
||||
}
|
||||
|
@ -106,24 +106,23 @@ metadata.labels.app | = | ${pname}
|
||||
metadata.name | = | ${pname}
|
||||
|
||||
spec.hostname | = | $pname
|
||||
spec.restartPolicy | = | Never
|
||||
|
||||
spec.containers[0].command | = | [\"top\"]
|
||||
spec.containers[0].image | = | $IMAGE
|
||||
spec.containers[0].name | = | $cname1
|
||||
spec.containers[0].ports[0].containerPort | = | 8888
|
||||
spec.containers[0].ports[0].hostPort | = | 9999
|
||||
spec.containers[0].resources | = | {}
|
||||
spec.containers[0].resources | = | null
|
||||
|
||||
spec.containers[1].command | = | [\"bottom\"]
|
||||
spec.containers[1].image | = | $IMAGE
|
||||
spec.containers[1].name | = | $cname2
|
||||
spec.containers[1].ports | = | null
|
||||
spec.containers[1].resources | = | {}
|
||||
spec.containers[1].resources | = | null
|
||||
|
||||
spec.containers[0].securityContext.capabilities | = | $capabilities
|
||||
|
||||
status | = | {}
|
||||
status | = | null
|
||||
"
|
||||
|
||||
while read key op expect; do
|
||||
|
Reference in New Issue
Block a user