mirror of
https://github.com/containers/podman.git
synced 2025-07-13 01:12:14 +08:00
Merge pull request #15091 from umohnani8/lift
Add podman kube apply command
This commit is contained in:
cmd/podman/kube
docs/source/markdown
pkg
api
bindings/kube
domain
k8s.io/api/core/v1
test/minikube
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