mirror of
https://github.com/containers/podman.git
synced 2025-07-04 18:27:33 +08:00
Add podman kube apply command
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 <umohnani@redhat.com>
This commit is contained in:
111
cmd/podman/kube/apply.go
Normal file
111
cmd/podman/kube/apply.go
Normal file
@ -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
docs/source/markdown/podman-kube-apply.1.md
Normal file
73
docs/source/markdown/podman-kube-apply.1.md
Normal file
@ -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)
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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!")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
93
pkg/bindings/kube/types_apply_options.go
Normal file
93
pkg/bindings/kube/types_apply_options.go
Normal file
@ -0,0 +1,93 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package kube
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/containers/podman/v4/pkg/bindings/internal/util"
|
||||
)
|
||||
|
||||
// Changed returns true if named field has been set
|
||||
func (o *ApplyOptions) Changed(fieldName string) bool {
|
||||
return util.Changed(o, fieldName)
|
||||
}
|
||||
|
||||
// ToParams formats struct fields to be passed to API service
|
||||
func (o *ApplyOptions) ToParams() (url.Values, error) {
|
||||
return util.ToParams(o)
|
||||
}
|
||||
|
||||
// WithKubeconfig set field Kubeconfig to given value
|
||||
func (o *ApplyOptions) WithKubeconfig(value string) *ApplyOptions {
|
||||
o.Kubeconfig = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetKubeconfig returns value of field Kubeconfig
|
||||
func (o *ApplyOptions) GetKubeconfig() string {
|
||||
if o.Kubeconfig == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Kubeconfig
|
||||
}
|
||||
|
||||
// WithNamespace set field Namespace to given value
|
||||
func (o *ApplyOptions) WithNamespace(value string) *ApplyOptions {
|
||||
o.Namespace = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetNamespace returns value of field Namespace
|
||||
func (o *ApplyOptions) GetNamespace() string {
|
||||
if o.Namespace == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Namespace
|
||||
}
|
||||
|
||||
// WithCACertFile set field CACertFile to given value
|
||||
func (o *ApplyOptions) WithCACertFile(value string) *ApplyOptions {
|
||||
o.CACertFile = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetCACertFile returns value of field CACertFile
|
||||
func (o *ApplyOptions) GetCACertFile() string {
|
||||
if o.CACertFile == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.CACertFile
|
||||
}
|
||||
|
||||
// WithFile set field File to given value
|
||||
func (o *ApplyOptions) WithFile(value string) *ApplyOptions {
|
||||
o.File = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetFile returns value of field File
|
||||
func (o *ApplyOptions) GetFile() string {
|
||||
if o.File == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.File
|
||||
}
|
||||
|
||||
// WithService set field Service to given value
|
||||
func (o *ApplyOptions) WithService(value bool) *ApplyOptions {
|
||||
o.Service = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetService returns value of field Service
|
||||
func (o *ApplyOptions) GetService() bool {
|
||||
if o.Service == nil {
|
||||
var z bool
|
||||
return z
|
||||
}
|
||||
return *o.Service
|
||||
}
|
21
pkg/domain/entities/apply.go
Normal file
21
pkg/domain/entities/apply.go
Normal file
@ -0,0 +1,21 @@
|
||||
package entities
|
||||
|
||||
var (
|
||||
TypePVC = "PersistentVolumeClaim"
|
||||
TypePod = "Pod"
|
||||
TypeService = "Service"
|
||||
)
|
||||
|
||||
// ApplyOptions controls the deployment of kube yaml files to a Kubernetes Cluster
|
||||
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
|
||||
}
|
@ -61,6 +61,7 @@ type ContainerEngine interface { //nolint:interfacebloat
|
||||
SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error)
|
||||
HealthCheckRun(ctx context.Context, nameOrID string, options HealthCheckOptions) (*define.HealthCheckResults, error)
|
||||
Info(ctx context.Context) (*define.Info, error)
|
||||
KubeApply(ctx context.Context, body io.Reader, opts ApplyOptions) error
|
||||
NetworkConnect(ctx context.Context, networkname string, options NetworkConnectOptions) error
|
||||
NetworkCreate(ctx context.Context, network types.Network) (*types.Network, error)
|
||||
NetworkDisconnect(ctx context.Context, networkname string, options NetworkDisconnectOptions) error
|
||||
|
194
pkg/domain/infra/abi/apply.go
Normal file
194
pkg/domain/infra/abi/apply.go
Normal file
@ -0,0 +1,194 @@
|
||||
package abi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/podman/v4/pkg/domain/entities"
|
||||
k8sAPI "github.com/containers/podman/v4/pkg/k8s.io/api/core/v1"
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
func (ic *ContainerEngine) KubeApply(ctx context.Context, body io.Reader, options entities.ApplyOptions) error {
|
||||
// Read the yaml file
|
||||
content, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(content) == 0 {
|
||||
return errors.New("yaml file provided is empty, cannot apply to a cluster")
|
||||
}
|
||||
|
||||
// Split the yaml file
|
||||
documentList, err := splitMultiDocYAML(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sort the kube kinds
|
||||
documentList, err = sortKubeKinds(documentList)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to sort kube kinds: %w", err)
|
||||
}
|
||||
|
||||
// Get the namespace to deploy the workload to
|
||||
namespace := options.Namespace
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
|
||||
// Parse the given kubeconfig
|
||||
kconfig, err := getClusterInfo(options.Kubeconfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set up the client to connect to the cluster endpoints
|
||||
client, err := setUpClusterClient(kconfig, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, document := range documentList {
|
||||
kind, err := getKubeKind(document)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read kube YAML: %w", err)
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case entities.TypeService:
|
||||
url := kconfig.Clusters[0].Cluster.Server + "/api/v1/namespaces/" + namespace + "/services"
|
||||
if err := createObject(client, url, document); err != nil {
|
||||
return err
|
||||
}
|
||||
case entities.TypePVC:
|
||||
url := kconfig.Clusters[0].Cluster.Server + "/api/v1/namespaces/" + namespace + "/persistentvolumeclaims"
|
||||
if err := createObject(client, url, document); err != nil {
|
||||
return err
|
||||
}
|
||||
case entities.TypePod:
|
||||
url := kconfig.Clusters[0].Cluster.Server + "/api/v1/namespaces/" + namespace + "/pods"
|
||||
if err := createObject(client, url, document); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported Kubernetes kind found: %q", kind)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setUpClusterClient sets up the client to use when connecting to the cluster. It sets up the CA Certs and
|
||||
// client certs and keys based on the information given in the kubeconfig
|
||||
func setUpClusterClient(kconfig k8sAPI.Config, applyOptions entities.ApplyOptions) (*http.Client, error) {
|
||||
var (
|
||||
clientCert tls.Certificate
|
||||
err error
|
||||
)
|
||||
|
||||
// Load client certificate and key
|
||||
// This information will always be in the kubeconfig
|
||||
if kconfig.AuthInfos[0].AuthInfo.ClientCertificate != "" && kconfig.AuthInfos[0].AuthInfo.ClientKey != "" {
|
||||
clientCert, err = tls.LoadX509KeyPair(kconfig.AuthInfos[0].AuthInfo.ClientCertificate, kconfig.AuthInfos[0].AuthInfo.ClientKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if len(kconfig.AuthInfos[0].AuthInfo.ClientCertificateData) > 0 && len(kconfig.AuthInfos[0].AuthInfo.ClientKeyData) > 0 {
|
||||
clientCert, err = tls.X509KeyPair(kconfig.AuthInfos[0].AuthInfo.ClientCertificateData, kconfig.AuthInfos[0].AuthInfo.ClientKeyData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Load CA cert
|
||||
// The CA cert may not always be in the kubeconfig and could be in a separate file.
|
||||
// The CA cert file can be passed on here by setting the --ca-cert-file flag. If that is not set
|
||||
// check the kubeconfig to see if it has the CA cert data.
|
||||
var caCert []byte
|
||||
insecureSkipVerify := false
|
||||
caCertFile := applyOptions.CACertFile
|
||||
caCertPool := x509.NewCertPool()
|
||||
|
||||
// Be insecure if user sets ca-cert-file flag to insecure
|
||||
if strings.ToLower(caCertFile) == "insecure" {
|
||||
insecureSkipVerify = true
|
||||
} else if caCertFile == "" {
|
||||
caCertFile = kconfig.Clusters[0].Cluster.CertificateAuthority
|
||||
}
|
||||
|
||||
// Get the caCert data if we are running secure
|
||||
if caCertFile != "" && !insecureSkipVerify {
|
||||
caCert, err = os.ReadFile(caCertFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if len(kconfig.Clusters[0].Cluster.CertificateAuthorityData) > 0 && !insecureSkipVerify {
|
||||
caCert = kconfig.Clusters[0].Cluster.CertificateAuthorityData
|
||||
}
|
||||
if len(caCert) > 0 {
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
}
|
||||
|
||||
// Create transport with ca and client certs
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{RootCAs: caCertPool, Certificates: []tls.Certificate{clientCert}, InsecureSkipVerify: insecureSkipVerify},
|
||||
}
|
||||
return &http.Client{Transport: tr}, nil
|
||||
}
|
||||
|
||||
// createObject connects to the given url and creates the yaml given in objectData
|
||||
func createObject(client *http.Client, url string, objectData []byte) error {
|
||||
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(string(objectData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/yaml")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Log the response body as fatal if we get a non-success status code
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusAccepted {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New(string(body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getClusterInfo returns the kubeconfig in struct form so that the server
|
||||
// and certificates data can be accessed and used to connect to the k8s cluster
|
||||
func getClusterInfo(kubeconfig string) (k8sAPI.Config, error) {
|
||||
var config k8sAPI.Config
|
||||
|
||||
configData, err := os.ReadFile(kubeconfig)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
|
||||
// Convert yaml kubeconfig to json so we can unmarshal it
|
||||
jsonData, err := yaml.YAMLToJSON(configData)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(jsonData, &config); err != nil {
|
||||
return config, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
@ -3,8 +3,12 @@ package tunnel
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/podman/v4/pkg/bindings/generate"
|
||||
"github.com/containers/podman/v4/pkg/bindings/kube"
|
||||
"github.com/containers/podman/v4/pkg/bindings/play"
|
||||
"github.com/containers/podman/v4/pkg/domain/entities"
|
||||
)
|
||||
|
||||
@ -49,3 +53,33 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string,
|
||||
func (ic *ContainerEngine) GenerateSpec(ctx context.Context, opts *entities.GenerateSpecOptions) (*entities.GenerateSpecReport, error) {
|
||||
return nil, fmt.Errorf("GenerateSpec is not supported on the remote API")
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, opts entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
|
||||
options := new(kube.PlayOptions).WithAuthfile(opts.Authfile).WithUsername(opts.Username).WithPassword(opts.Password)
|
||||
options.WithCertDir(opts.CertDir).WithQuiet(opts.Quiet).WithSignaturePolicy(opts.SignaturePolicy).WithConfigMaps(opts.ConfigMaps)
|
||||
options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Networks).WithSeccompProfileRoot(opts.SeccompProfileRoot)
|
||||
options.WithStaticIPs(opts.StaticIPs).WithStaticMACs(opts.StaticMACs)
|
||||
if len(opts.LogOptions) > 0 {
|
||||
options.WithLogOptions(opts.LogOptions)
|
||||
}
|
||||
if opts.Annotations != nil {
|
||||
options.WithAnnotations(opts.Annotations)
|
||||
}
|
||||
options.WithNoHosts(opts.NoHosts).WithUserns(opts.Userns)
|
||||
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
|
||||
options.WithSkipTLSVerify(s == types.OptionalBoolTrue)
|
||||
}
|
||||
if start := opts.Start; start != types.OptionalBoolUndefined {
|
||||
options.WithStart(start == types.OptionalBoolTrue)
|
||||
}
|
||||
return play.KubeWithBody(ic.ClientCtx, body, options)
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) PlayKubeDown(ctx context.Context, body io.Reader, _ entities.PlayKubeDownOptions) (*entities.PlayKubeReport, error) {
|
||||
return play.DownWithBody(ic.ClientCtx, body)
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) KubeApply(ctx context.Context, body io.Reader, opts entities.ApplyOptions) error {
|
||||
options := new(kube.ApplyOptions).WithKubeconfig(opts.Kubeconfig).WithCACertFile(opts.CACertFile).WithNamespace(opts.Namespace)
|
||||
return kube.ApplyWithBody(ic.ClientCtx, body, options)
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/podman/v4/pkg/bindings/kube"
|
||||
"github.com/containers/podman/v4/pkg/bindings/play"
|
||||
"github.com/containers/podman/v4/pkg/domain/entities"
|
||||
)
|
||||
|
||||
func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, opts entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
|
||||
options := new(kube.PlayOptions).WithAuthfile(opts.Authfile).WithUsername(opts.Username).WithPassword(opts.Password)
|
||||
options.WithCertDir(opts.CertDir).WithQuiet(opts.Quiet).WithSignaturePolicy(opts.SignaturePolicy).WithConfigMaps(opts.ConfigMaps)
|
||||
options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Networks).WithSeccompProfileRoot(opts.SeccompProfileRoot)
|
||||
options.WithStaticIPs(opts.StaticIPs).WithStaticMACs(opts.StaticMACs)
|
||||
if len(opts.LogOptions) > 0 {
|
||||
options.WithLogOptions(opts.LogOptions)
|
||||
}
|
||||
if opts.Annotations != nil {
|
||||
options.WithAnnotations(opts.Annotations)
|
||||
}
|
||||
options.WithNoHosts(opts.NoHosts).WithUserns(opts.Userns)
|
||||
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
|
||||
options.WithSkipTLSVerify(s == types.OptionalBoolTrue)
|
||||
}
|
||||
if start := opts.Start; start != types.OptionalBoolUndefined {
|
||||
options.WithStart(start == types.OptionalBoolTrue)
|
||||
}
|
||||
return play.KubeWithBody(ic.ClientCtx, body, options)
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) PlayKubeDown(ctx context.Context, body io.Reader, _ entities.PlayKubeDownOptions) (*entities.PlayKubeReport, error) {
|
||||
return play.DownWithBody(ic.ClientCtx, body)
|
||||
}
|
@ -4490,3 +4490,248 @@ type PortStatus struct {
|
||||
// +kubebuilder:validation:MaxLength=316
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// The following has been copied from https://github.com/kubernetes/client-go/blob/master/tools/clientcmd/api/v1/types.go
|
||||
// It holds the struct information for a kubeconfig that let's us unmarshal a given kubeconfig so that we can deploy workloads
|
||||
// to the cluster with podman generate kube.
|
||||
|
||||
// Config holds the information needed to build connect to remote kubernetes clusters as a given user
|
||||
type Config struct {
|
||||
// Legacy field from pkg/api/types.go TypeMeta.
|
||||
// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
Kind string `json:"kind,omitempty"`
|
||||
// Legacy field from pkg/api/types.go TypeMeta.
|
||||
// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
// Preferences holds general information to be use for cli interactions
|
||||
Preferences Preferences `json:"preferences"`
|
||||
// Clusters is a map of referencable names to cluster configs
|
||||
Clusters []NamedCluster `json:"clusters"`
|
||||
// AuthInfos is a map of referencable names to user configs
|
||||
AuthInfos []NamedAuthInfo `json:"users"`
|
||||
// Contexts is a map of referencable names to context configs
|
||||
Contexts []NamedContext `json:"contexts"`
|
||||
// CurrentContext is the name of the context that you would like to use by default
|
||||
CurrentContext string `json:"current-context"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
type Preferences struct {
|
||||
// +optional
|
||||
Colors bool `json:"colors,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Cluster contains information about how to communicate with a kubernetes cluster
|
||||
type Cluster struct {
|
||||
// Server is the address of the kubernetes cluster (https://hostname:port).
|
||||
Server string `json:"server"`
|
||||
// TLSServerName is used to check server certificate. If TLSServerName is empty, the hostname used to contact the server is used.
|
||||
// +optional
|
||||
TLSServerName string `json:"tls-server-name,omitempty"`
|
||||
// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
|
||||
// +optional
|
||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
|
||||
// CertificateAuthority is the path to a cert file for the certificate authority.
|
||||
// +optional
|
||||
CertificateAuthority string `json:"certificate-authority,omitempty"`
|
||||
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
|
||||
// +optional
|
||||
CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
|
||||
// ProxyURL is the URL to the proxy to be used for all requests made by this
|
||||
// client. URLs with "http", "https", and "socks5" schemes are supported. If
|
||||
// this configuration is not provided or the empty string, the client
|
||||
// attempts to construct a proxy configuration from http_proxy and
|
||||
// https_proxy environment variables. If these environment variables are not
|
||||
// set, the client does not attempt to proxy requests.
|
||||
//
|
||||
// socks5 proxying does not currently support spdy streaming endpoints (exec,
|
||||
// attach, port forward).
|
||||
// +optional
|
||||
ProxyURL string `json:"proxy-url,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
|
||||
type AuthInfo struct {
|
||||
// ClientCertificate is the path to a client cert file for TLS.
|
||||
// +optional
|
||||
ClientCertificate string `json:"client-certificate,omitempty"`
|
||||
// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
|
||||
// +optional
|
||||
ClientCertificateData []byte `json:"client-certificate-data,omitempty"`
|
||||
// ClientKey is the path to a client key file for TLS.
|
||||
// +optional
|
||||
ClientKey string `json:"client-key,omitempty"`
|
||||
// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
|
||||
// +optional
|
||||
ClientKeyData []byte `json:"client-key-data,omitempty" datapolicy:"security-key"`
|
||||
// Token is the bearer token for authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Token string `json:"token,omitempty" datapolicy:"token"`
|
||||
// TokenFile is a pointer to a file that contains a bearer token (as described above). If both Token and TokenFile are present, Token takes precedence.
|
||||
// +optional
|
||||
TokenFile string `json:"tokenFile,omitempty"`
|
||||
// Impersonate is the username to impersonate. The name matches the flag.
|
||||
// +optional
|
||||
Impersonate string `json:"as,omitempty"`
|
||||
// ImpersonateUID is the uid to impersonate.
|
||||
// +optional
|
||||
ImpersonateUID string `json:"as-uid,omitempty"`
|
||||
// ImpersonateGroups is the groups to impersonate.
|
||||
// +optional
|
||||
ImpersonateGroups []string `json:"as-groups,omitempty"`
|
||||
// ImpersonateUserExtra contains additional information for impersonated user.
|
||||
// +optional
|
||||
ImpersonateUserExtra map[string][]string `json:"as-user-extra,omitempty"`
|
||||
// Username is the username for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
// Password is the password for basic authentication to the kubernetes cluster.
|
||||
// +optional
|
||||
Password string `json:"password,omitempty" datapolicy:"password"`
|
||||
// AuthProvider specifies a custom authentication plugin for the kubernetes cluster.
|
||||
// +optional
|
||||
AuthProvider *AuthProviderConfig `json:"auth-provider,omitempty"`
|
||||
// Exec specifies a custom exec-based authentication plugin for the kubernetes cluster.
|
||||
// +optional
|
||||
Exec *ExecConfig `json:"exec,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
|
||||
type Context struct {
|
||||
// Cluster is the name of the cluster for this context
|
||||
Cluster string `json:"cluster"`
|
||||
// AuthInfo is the name of the authInfo for this context
|
||||
AuthInfo string `json:"user"`
|
||||
// Namespace is the default namespace to use on unspecified requests
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
|
||||
// +optional
|
||||
Extensions []NamedExtension `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// NamedCluster relates nicknames to cluster information
|
||||
type NamedCluster struct {
|
||||
// Name is the nickname for this Cluster
|
||||
Name string `json:"name"`
|
||||
// Cluster holds the cluster information
|
||||
Cluster Cluster `json:"cluster"`
|
||||
}
|
||||
|
||||
// NamedContext relates nicknames to context information
|
||||
type NamedContext struct {
|
||||
// Name is the nickname for this Context
|
||||
Name string `json:"name"`
|
||||
// Context holds the context information
|
||||
Context Context `json:"context"`
|
||||
}
|
||||
|
||||
// NamedAuthInfo relates nicknames to auth information
|
||||
type NamedAuthInfo struct {
|
||||
// Name is the nickname for this AuthInfo
|
||||
Name string `json:"name"`
|
||||
// AuthInfo holds the auth information
|
||||
AuthInfo AuthInfo `json:"user"`
|
||||
}
|
||||
|
||||
// NamedExtension relates nicknames to extension information
|
||||
type NamedExtension struct {
|
||||
// Name is the nickname for this Extension
|
||||
Name string `json:"name"`
|
||||
// Extension holds the extension information
|
||||
Extension interface{} `json:"extension"`
|
||||
}
|
||||
|
||||
// AuthProviderConfig holds the configuration for a specified auth provider.
|
||||
type AuthProviderConfig struct {
|
||||
Name string `json:"name"`
|
||||
Config map[string]string `json:"config"`
|
||||
}
|
||||
|
||||
// ExecConfig specifies a command to provide client credentials. The command is exec'd
|
||||
// and outputs structured stdout holding credentials.
|
||||
//
|
||||
// See the client.authentication.k8s.io API group for specifications of the exact input
|
||||
// and output format
|
||||
type ExecConfig struct {
|
||||
// Command to execute.
|
||||
Command string `json:"command"`
|
||||
// Arguments to pass to the command when executing it.
|
||||
// +optional
|
||||
Args []string `json:"args"`
|
||||
// Env defines additional environment variables to expose to the process. These
|
||||
// are unioned with the host's environment, as well as variables client-go uses
|
||||
// to pass argument to the plugin.
|
||||
// +optional
|
||||
Env []ExecEnvVar `json:"env"`
|
||||
|
||||
// Preferred input version of the ExecInfo. The returned ExecCredentials MUST use
|
||||
// the same encoding version as the input.
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// This text is shown to the user when the executable doesn't seem to be
|
||||
// present. For example, `brew install foo-cli` might be a good InstallHint for
|
||||
// foo-cli on Mac OS systems.
|
||||
InstallHint string `json:"installHint,omitempty"`
|
||||
|
||||
// ProvideClusterInfo determines whether or not to provide cluster information,
|
||||
// which could potentially contain very large CA data, to this exec plugin as a
|
||||
// part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set
|
||||
// to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for
|
||||
// reading this environment variable.
|
||||
ProvideClusterInfo bool `json:"provideClusterInfo"`
|
||||
|
||||
// InteractiveMode determines this plugin's relationship with standard input. Valid
|
||||
// values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this
|
||||
// exec plugin wants to use standard input if it is available), or "Always" (this exec
|
||||
// plugin requires standard input to function). See ExecInteractiveMode values for more
|
||||
// details.
|
||||
//
|
||||
// If APIVersion is client.authentication.k8s.io/v1alpha1 or
|
||||
// client.authentication.k8s.io/v1beta1, then this field is optional and defaults
|
||||
// to "IfAvailable" when unset. Otherwise, this field is required.
|
||||
//+optional
|
||||
InteractiveMode ExecInteractiveMode `json:"interactiveMode,omitempty"`
|
||||
}
|
||||
|
||||
// ExecEnvVar is used for setting environment variables when executing an exec-based
|
||||
// credential plugin.
|
||||
type ExecEnvVar struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// ExecInteractiveMode is a string that describes an exec plugin's relationship with standard input.
|
||||
type ExecInteractiveMode string
|
||||
|
||||
const (
|
||||
// NeverExecInteractiveMode declares that this exec plugin never needs to use standard
|
||||
// input, and therefore the exec plugin will be run regardless of whether standard input is
|
||||
// available for user input.
|
||||
NeverExecInteractiveMode ExecInteractiveMode = "Never"
|
||||
// IfAvailableExecInteractiveMode declares that this exec plugin would like to use standard input
|
||||
// if it is available, but can still operate if standard input is not available. Therefore, the
|
||||
// exec plugin will be run regardless of whether stdin is available for user input. If standard
|
||||
// input is available for user input, then it will be provided to this exec plugin.
|
||||
IfAvailableExecInteractiveMode ExecInteractiveMode = "IfAvailable"
|
||||
// AlwaysExecInteractiveMode declares that this exec plugin requires standard input in order to
|
||||
// run, and therefore the exec plugin will only be run if standard input is available for user
|
||||
// input. If standard input is not available for user input, then the exec plugin will not be run
|
||||
// and an error will be returned by the exec plugin runner.
|
||||
AlwaysExecInteractiveMode ExecInteractiveMode = "Always"
|
||||
)
|
||||
|
@ -15,7 +15,6 @@ load helpers.bash
|
||||
run minikube kubectl get pods
|
||||
assert "$status" -eq 0 "get pods in the default namespace"
|
||||
assert "$output" == "No resources found in default namespace."
|
||||
wait_for_default_sa
|
||||
}
|
||||
|
||||
@test "minikube - deploy generated container yaml to minikube" {
|
||||
@ -29,7 +28,6 @@ load helpers.bash
|
||||
run minikube kubectl create namespace $project
|
||||
assert "$status" -eq 0 "create new namespace $project"
|
||||
run minikube kubectl -- apply -f $fname
|
||||
echo $output >&2
|
||||
assert "$status" -eq 0 "deploy $fname to the cluster"
|
||||
assert "$output" == "pod/$cname-pod created"
|
||||
wait_for_pods_to_start
|
||||
@ -59,3 +57,108 @@ load helpers.bash
|
||||
run minikube kubectl delete namespace $project
|
||||
assert $status -eq 0 "delete namespace $project"
|
||||
}
|
||||
|
||||
@test "minikube - apply podman ctr to cluster" {
|
||||
cname="test-ctr-apply"
|
||||
run_podman container create --name $cname $IMAGE top
|
||||
|
||||
# deploy to minikube cluster with kube apply
|
||||
project="ctr-apply"
|
||||
run minikube kubectl create namespace $project
|
||||
assert "$status" -eq 0 "create new namespace $project"
|
||||
run_podman kube apply --kubeconfig $KUBECONFIG --ns $project $cname
|
||||
assert "$output" =~ "Successfully deployed workloads to cluster!"
|
||||
run minikube kubectl -- get pods --namespace $project
|
||||
assert "$status" -eq 0 "kube apply $cname to the cluster"
|
||||
assert "$output" =~ "$cname-pod"
|
||||
wait_for_pods_to_start
|
||||
run minikube kubectl delete namespace $project
|
||||
assert $status -eq 0 "delete namespace $project"
|
||||
}
|
||||
|
||||
@test "minikube - apply podman pod to cluster" {
|
||||
pname="test-pod-apply"
|
||||
run_podman pod create --name $pname
|
||||
run podman container create --pod $pname $IMAGE top
|
||||
|
||||
# deploy to minikube cluster with kube apply
|
||||
project="pod-apply"
|
||||
run minikube kubectl create namespace $project
|
||||
assert "$status" -eq 0 "create new namespace $project"
|
||||
run_podman kube apply --kubeconfig $KUBECONFIG --ns $project $pname
|
||||
assert "$output" =~ "Successfully deployed workloads to cluster!"
|
||||
run minikube kubectl -- get pods --namespace $project
|
||||
assert "$status" -eq 0 "kube apply $pname to the cluster"
|
||||
assert "$output" =~ "$pname"
|
||||
wait_for_pods_to_start
|
||||
run minikube kubectl delete namespace $project
|
||||
assert $status -eq 0 "delete namespace $project"
|
||||
}
|
||||
|
||||
@test "minikube - deploy generated kube yaml with podman kube apply to cluster" {
|
||||
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 -f $fname $pname
|
||||
|
||||
# deploy to minikube cluster with kube apply
|
||||
project="yaml-apply"
|
||||
run minikube kubectl create namespace $project
|
||||
assert "$status" -eq 0 "create new namespace $project"
|
||||
run_podman kube apply --kubeconfig $KUBECONFIG --ns $project -f $fname
|
||||
assert "$output" =~ "Successfully deployed workloads to cluster!"
|
||||
run minikube kubectl -- get pods --namespace $project
|
||||
assert "$status" -eq 0 "kube apply $pname to the cluster"
|
||||
assert "$output" =~ "$pname"
|
||||
wait_for_pods_to_start
|
||||
run minikube kubectl delete namespace $project
|
||||
assert $status -eq 0 "delete namespace $project"
|
||||
}
|
||||
|
||||
@test "minikube - apply podman ctr with volume to cluster" {
|
||||
cname="ctr-vol"
|
||||
vname="myvol"
|
||||
run_podman container create -v $vname:/myvol --name $cname $IMAGE top
|
||||
|
||||
# deploy to minikube cluster with kube apply
|
||||
project="ctr-vol-apply"
|
||||
run minikube kubectl create namespace $project
|
||||
assert "$status" -eq 0 "create new namespace $project"
|
||||
run_podman kube apply --kubeconfig $KUBECONFIG --ns $project $cname $vname
|
||||
assert "$output" =~ "Successfully deployed workloads to cluster!"
|
||||
run minikube kubectl -- get pods --namespace $project
|
||||
assert "$status" -eq 0 "kube apply $cname to the cluster"
|
||||
assert "$output" =~ "$cname-pod"
|
||||
run minikube kubectl -- get pvc --namespace $project
|
||||
assert "$status" -eq 0 "kube apply $vname to the cluster"
|
||||
assert "$output" =~ "$vname"
|
||||
wait_for_pods_to_start
|
||||
run minikube kubectl delete namespace $project
|
||||
assert $status -eq 0 "delete namespace $project"
|
||||
}
|
||||
|
||||
@test "minikube - apply podman ctr with service to cluster" {
|
||||
cname="ctr-svc"
|
||||
run_podman container create -p 3000:4000 --name $cname $IMAGE top
|
||||
|
||||
# deploy to minikube cluster with kube apply
|
||||
project="ctr-svc-apply"
|
||||
run minikube kubectl create namespace $project
|
||||
assert "$status" -eq 0 "create new namespace $project"
|
||||
run_podman kube apply --kubeconfig $KUBECONFIG -s --ns $project $cname
|
||||
assert "$output" =~ "Successfully deployed workloads to cluster!"
|
||||
run minikube kubectl -- get pods --namespace $project
|
||||
assert "$status" -eq 0 "kube apply $cname to the cluster"
|
||||
assert "$output" =~ "$cname-pod"
|
||||
run minikube kubectl -- get svc --namespace $project
|
||||
assert "$status" -eq 0 "kube apply service to the cluster"
|
||||
assert "$output" =~ "$cname-pod"
|
||||
wait_for_pods_to_start
|
||||
run minikube kubectl delete namespace $project
|
||||
assert $status -eq 0 "delete namespace $project"
|
||||
}
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
load ../system/helpers.bash
|
||||
|
||||
KUBECONFIG="$HOME/.kube/config"
|
||||
|
||||
function setup(){
|
||||
# only set up the minikube cluster before the first test
|
||||
if [[ "$BATS_TEST_NUMBER" -eq 1 ]]; then
|
||||
minikube start
|
||||
wait_for_default_sa
|
||||
fi
|
||||
basic_setup
|
||||
}
|
||||
|
Reference in New Issue
Block a user