mirror of
https://github.com/containers/podman.git
synced 2025-07-14 10:09:13 +08:00
Merge pull request #15091 from umohnani8/lift
Add podman kube apply command
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**
|
#### **--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
|
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.
|
||||||
random port is assigned by Podman in the specification.
|
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
|
||||||
|
@ -14,12 +14,13 @@ file input. Containers will be automatically started.
|
|||||||
|
|
||||||
| Command | Man Page | Description |
|
| 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. |
|
| 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. |
|
| 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. |
|
| play | [podman-kube-play(1)](podman-kube-play.1.md) | Create containers, pods and volumes based on Kubernetes YAML. |
|
||||||
|
|
||||||
## SEE ALSO
|
## 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
|
## HISTORY
|
||||||
December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com)
|
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) {
|
func KubeGenerate(w http.ResponseWriter, r *http.Request) {
|
||||||
GenerateKube(w, r)
|
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"
|
// $ref: "#/responses/internalError"
|
||||||
r.HandleFunc(VersionedPath("/libpod/generate/kube"), s.APIHandler(libpod.GenerateKube)).Methods(http.MethodGet)
|
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)
|
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
|
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) {
|
func Generate(ctx context.Context, nameOrIDs []string, options generate.KubeOptions) (*entities.GenerateKubeReport, error) {
|
||||||
return generate.Kube(ctx, nameOrIDs, &options)
|
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 - define the user namespace to use.
|
||||||
Userns *string
|
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)
|
SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error)
|
||||||
HealthCheckRun(ctx context.Context, nameOrID string, options HealthCheckOptions) (*define.HealthCheckResults, error)
|
HealthCheckRun(ctx context.Context, nameOrID string, options HealthCheckOptions) (*define.HealthCheckResults, error)
|
||||||
Info(ctx context.Context) (*define.Info, 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
|
NetworkConnect(ctx context.Context, networkname string, options NetworkConnectOptions) error
|
||||||
NetworkCreate(ctx context.Context, network types.Network) (*types.Network, error)
|
NetworkCreate(ctx context.Context, network types.Network) (*types.Network, error)
|
||||||
NetworkDisconnect(ctx context.Context, networkname string, options NetworkDisconnectOptions) 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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/containers/image/v5/types"
|
||||||
"github.com/containers/podman/v4/pkg/bindings/generate"
|
"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"
|
"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) {
|
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")
|
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
|
// +kubebuilder:validation:MaxLength=316
|
||||||
Error *string `json:"error,omitempty"`
|
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
|
run minikube kubectl get pods
|
||||||
assert "$status" -eq 0 "get pods in the default namespace"
|
assert "$status" -eq 0 "get pods in the default namespace"
|
||||||
assert "$output" == "No resources found in default namespace."
|
assert "$output" == "No resources found in default namespace."
|
||||||
wait_for_default_sa
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "minikube - deploy generated container yaml to minikube" {
|
@test "minikube - deploy generated container yaml to minikube" {
|
||||||
@ -29,7 +28,6 @@ load helpers.bash
|
|||||||
run minikube kubectl create namespace $project
|
run minikube kubectl create namespace $project
|
||||||
assert "$status" -eq 0 "create new namespace $project"
|
assert "$status" -eq 0 "create new namespace $project"
|
||||||
run minikube kubectl -- apply -f $fname
|
run minikube kubectl -- apply -f $fname
|
||||||
echo $output >&2
|
|
||||||
assert "$status" -eq 0 "deploy $fname to the cluster"
|
assert "$status" -eq 0 "deploy $fname to the cluster"
|
||||||
assert "$output" == "pod/$cname-pod created"
|
assert "$output" == "pod/$cname-pod created"
|
||||||
wait_for_pods_to_start
|
wait_for_pods_to_start
|
||||||
@ -59,3 +57,108 @@ load helpers.bash
|
|||||||
run minikube kubectl delete namespace $project
|
run minikube kubectl delete namespace $project
|
||||||
assert $status -eq 0 "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
|
load ../system/helpers.bash
|
||||||
|
|
||||||
|
KUBECONFIG="$HOME/.kube/config"
|
||||||
|
|
||||||
function setup(){
|
function setup(){
|
||||||
# only set up the minikube cluster before the first test
|
# only set up the minikube cluster before the first test
|
||||||
if [[ "$BATS_TEST_NUMBER" -eq 1 ]]; then
|
if [[ "$BATS_TEST_NUMBER" -eq 1 ]]; then
|
||||||
minikube start
|
minikube start
|
||||||
|
wait_for_default_sa
|
||||||
fi
|
fi
|
||||||
basic_setup
|
basic_setup
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user