generate kube

add the ability to generate kubernetes pod and service yaml representations
of libpod containers and pods.

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude
2018-11-21 16:00:50 -06:00
parent 5f6ad82524
commit c8436b4912
9 changed files with 466 additions and 62 deletions

23
cmd/podman/generate.go Normal file
View File

@ -0,0 +1,23 @@
package main
import (
"github.com/urfave/cli"
)
var (
generateSubCommands = []cli.Command{
containerKubeCommand,
}
generateDescription = "generate structured data based for a containers and pods"
kubeCommand = cli.Command{
Name: "generate",
Usage: "generated structured data",
Description: generateDescription,
ArgsUsage: "",
Subcommands: generateSubCommands,
UseShortOptionHandling: true,
OnUsageError: usageErrorHandler,
Hidden: true,
}
)

View File

@ -6,10 +6,11 @@ import (
"github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/rootless"
podmanVersion "github.com/containers/libpod/version"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
"k8s.io/api/core/v1"
) )
var ( var (
@ -18,16 +19,15 @@ var (
Name: "service, s", Name: "service, s",
Usage: "only generate YAML for kubernetes service object", Usage: "only generate YAML for kubernetes service object",
}, },
LatestFlag,
} }
containerKubeDescription = "Generate Kubernetes Pod YAML" containerKubeDescription = "Generate Kubernetes Pod YAML"
containerKubeCommand = cli.Command{ containerKubeCommand = cli.Command{
Name: "generate", Name: "kube",
Usage: "Generate Kubernetes pod YAML for a container", Usage: "Generate Kubernetes pod YAML for a container or pod",
Description: containerKubeDescription, Description: containerKubeDescription,
Flags: sortFlags(containerKubeFlags), Flags: sortFlags(containerKubeFlags),
Action: generateKubeYAMLCmd, Action: generateKubeYAMLCmd,
ArgsUsage: "CONTAINER-NAME", ArgsUsage: "CONTAINER|POD-NAME",
UseShortOptionHandling: true, UseShortOptionHandling: true,
OnUsageError: usageErrorHandler, OnUsageError: usageErrorHandler,
} }
@ -36,9 +36,13 @@ var (
// generateKubeYAMLCmdgenerates or replays kube // generateKubeYAMLCmdgenerates or replays kube
func generateKubeYAMLCmd(c *cli.Context) error { func generateKubeYAMLCmd(c *cli.Context) error {
var ( var (
container *libpod.Container podYAML *v1.Pod
err error container *libpod.Container
output []byte err error
output []byte
pod *libpod.Pod
mashalledBytes []byte
servicePorts []v1.ServicePort
) )
if rootless.IsRootless() { if rootless.IsRootless() {
@ -46,10 +50,7 @@ func generateKubeYAMLCmd(c *cli.Context) error {
} }
args := c.Args() args := c.Args()
if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) { if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) {
return errors.Errorf("you must provide one container ID or name or --latest") return errors.Errorf("you must provide one container|pod ID or name or --latest")
}
if c.Bool("service") {
return errors.Wrapf(libpod.ErrNotImplemented, "service generation")
} }
runtime, err := libpodruntime.GetRuntime(c) runtime, err := libpodruntime.GetRuntime(c)
@ -59,33 +60,43 @@ func generateKubeYAMLCmd(c *cli.Context) error {
defer runtime.Shutdown(false) defer runtime.Shutdown(false)
// Get the container in question // Get the container in question
if c.Bool("latest") { container, err = runtime.LookupContainer(args[0])
container, err = runtime.GetLatestContainer() if err != nil {
pod, err = runtime.LookupPod(args[0])
if err != nil {
return err
}
podYAML, servicePorts, err = pod.GenerateForKube()
} else { } else {
container, err = runtime.LookupContainer(args[0]) if len(container.Dependencies()) > 0 {
return errors.Wrapf(libpod.ErrNotImplemented, "containers with dependencies")
}
podYAML, err = container.GenerateForKube()
} }
if err != nil { if err != nil {
return err return err
} }
if len(container.Dependencies()) > 0 { if c.Bool("service") {
return errors.Wrapf(libpod.ErrNotImplemented, "containers with dependencies") serviceYAML := libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts)
mashalledBytes, err = yaml.Marshal(serviceYAML)
} else {
// Marshall the results
mashalledBytes, err = yaml.Marshal(podYAML)
} }
podYAML, err := container.InspectForKube()
if err != nil { if err != nil {
return err return err
} }
developmentComment := []byte("# Generation of Kubenetes YAML is still under development!\n") header := `# Generation of Kubenetes YAML is still under development!
logrus.Warn("This function is still under heavy development.") #
// Marshall the results # Save the output of this file and use kubectl create -f to import
b, err := yaml.Marshal(podYAML) # it into Kubernetes.
if err != nil { #
return err # Created with podman-%s
} `
output = append(output, developmentComment...) output = append(output, []byte(fmt.Sprintf(header, podmanVersion.Version))...)
output = append(output, b...) output = append(output, mashalledBytes...)
// Output the v1.Pod with the v1.Container // Output the v1.Pod with the v1.Container
fmt.Println(string(output)) fmt.Println(string(output))

View File

@ -1,23 +0,0 @@
package main
import (
"github.com/urfave/cli"
)
var (
kubeSubCommands = []cli.Command{
containerKubeCommand,
}
kubeDescription = "Work with Kubernetes objects"
kubeCommand = cli.Command{
Name: "kube",
Usage: "Import and export Kubernetes objections from and to Podman",
Description: containerDescription,
ArgsUsage: "",
Subcommands: kubeSubCommands,
UseShortOptionHandling: true,
OnUsageError: usageErrorHandler,
Hidden: true,
}
)

View File

@ -16,6 +16,7 @@
| [podman-diff(1)](/docs/podman-diff.1.md) | Inspect changes on a container or image's filesystem |[![...](/docs/play.png)](https://asciinema.org/a/FXfWB9CKYFwYM4EfqW3NSZy1G)| | [podman-diff(1)](/docs/podman-diff.1.md) | Inspect changes on a container or image's filesystem |[![...](/docs/play.png)](https://asciinema.org/a/FXfWB9CKYFwYM4EfqW3NSZy1G)|
| [podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container | [podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container
| [podman-export(1)](/docs/podman-export.1.md) | Export container's filesystem contents as a tar archive |[![...](/docs/play.png)](https://asciinema.org/a/913lBIRAg5hK8asyIhhkQVLtV)| | [podman-export(1)](/docs/podman-export.1.md) | Export container's filesystem contents as a tar archive |[![...](/docs/play.png)](https://asciinema.org/a/913lBIRAg5hK8asyIhhkQVLtV)|
| [podman-generate(1)](/docs/podman-generate.1.md) | Generate structured output based on Podman containers and pods | |
| [podman-history(1)](/docs/podman-history.1.md) | Shows the history of an image |[![...](/docs/play.png)](https://asciinema.org/a/bCvUQJ6DkxInMELZdc5DinNSx)| | [podman-history(1)](/docs/podman-history.1.md) | Shows the history of an image |[![...](/docs/play.png)](https://asciinema.org/a/bCvUQJ6DkxInMELZdc5DinNSx)|
| [podman-image(1)](/docs/podman-image.1.md) | Manage Images|| | [podman-image(1)](/docs/podman-image.1.md) | Manage Images||
| [podman-images(1)](/docs/podman-images.1.md) | List images in local storage |[![...](/docs/play.png)](https://asciinema.org/a/133649)| | [podman-images(1)](/docs/podman-images.1.md) | List images in local storage |[![...](/docs/play.png)](https://asciinema.org/a/133649)|

View File

@ -859,6 +859,25 @@ _podman_container_wait() {
_podman_wait _podman_wait
} }
_podman_generate() {
local boolean_options="
--help
-h
"
subcommands="
kube
"
__podman_subcommands "$subcommands $aliases" && return
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) )
;;
esac
}
_podman_container() { _podman_container() {
local boolean_options=" local boolean_options="
--help --help
@ -2197,6 +2216,14 @@ _podman_logout() {
_complete_ "$options_with_args" "$boolean_options" _complete_ "$options_with_args" "$boolean_options"
} }
_podman_generate_kube() {
local options_with_args=""
local boolean_options="
-s
--service
"
_podman_container_runlabel() { _podman_container_runlabel() {
local options_with_args=" local options_with_args="
--authfile --authfile
@ -2538,6 +2565,7 @@ _podman_podman() {
diff diff
exec exec
export export
generate
history history
images images
import import

View File

@ -0,0 +1,119 @@
% podman-generate Podman Man Pages
% Brent Baude
% December 2018
# NAME
podman-generate-kube - Generate Kubernetes YAML
# SYNOPSIS
**podman generate kube **
[**-h**|**--help**]
[**-s**][**--service**]
CONTAINER|POD
# DESCRIPTION
**podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from a podman container or pod. Whether
the input is for a container or pod, Podman will always generate the specification as a Pod. The input may be in the form
of a pod or container name or ID.
The **service** option can be used to generate a Service specification for the corresponding Pod ouput. 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.
# OPTIONS:
**s** **--service**
Generate a service file for the resulting Pod YAML.
## Examples ##
Create Kubernetes Pod YAML for a container called `some-mariadb` .
```
$ sudo podman generate kube some-mariadb
# Generation of Kubenetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-0.11.2-dev
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: 2018-12-03T19:07:59Z
labels:
app: some-mariadb
name: some-mariadb-libpod
spec:
containers:
- command:
- docker-entrypoint.sh
- mysqld
env:
- name: PATH
value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
- name: TERM
value: xterm
- name: HOSTNAME
- name: container
value: podman
- name: GOSU_VERSION
value: "1.10"
- name: GPG_KEYS
value: "199369E5404BD5FC7D2FE43BCBCB082A1BB943DB \t177F4010FE56CA3336300305F1656F24C74CD1D8
\t430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A \t4D1BB29D63D98E422B2113B19334A25F8507EFA5"
- name: MARIADB_MAJOR
value: "10.3"
- name: MARIADB_VERSION
value: 1:10.3.10+maria~bionic
- name: MYSQL_ROOT_PASSWORD
value: x
image: quay.io/baude/demodb:latest
name: some-mariadb
ports:
- containerPort: 3306
hostPort: 36533
protocol: TCP
resources: {}
securityContext:
allowPrivilegeEscalation: true
privileged: false
readOnlyRootFilesystem: false
tty: true
workingDir: /
status: {}
```
Create Kubernetes service YAML for a container called `some-mariabdb`
```
$ sudo podman generate kube -s some-mariadb
# Generation of Kubenetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-0.11.2-dev
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2018-12-03T19:08:24Z
labels:
app: some-mariadb
name: some-mariadb-libpod
spec:
ports:
- name: "3306"
nodePort: 30929
port: 3306
protocol: TCP
targetPort: 0
selector:
app: some-mariadb
type: NodePort
status:
loadBalancer: {}
```
## SEE ALSO
podman(1), podman-container, podman-pod
# HISTORY
Decemeber 2018, Originally compiled by Brent Baude (bbaude at redhat dot com)

19
docs/podman-generate.1.md Normal file
View File

@ -0,0 +1,19 @@
% podman-generate(1)
## NAME
podman\-container - generate structured data based for a containers and pods
## SYNOPSIS
**podman generate** *subcommand*
## DESCRIPTION
The generate command will create structured output (like YAML) based on a container or pod.
## COMMANDS
| Command | Man Page | Description |
| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
| kube | [podman-generate-kube(1)](podman-generate-kube.1.md) | Generate Kubernetes YAML based on a pod or container
## SEE ALSO
podman, podman-pod, podman-container

View File

@ -2,7 +2,10 @@ package libpod
import ( import (
"fmt" "fmt"
"math/rand"
"strconv"
"strings" "strings"
"time"
"github.com/containers/libpod/pkg/lookup" "github.com/containers/libpod/pkg/lookup"
"github.com/containers/libpod/pkg/util" "github.com/containers/libpod/pkg/util"
@ -15,23 +18,127 @@ import (
v12 "k8s.io/apimachinery/pkg/apis/meta/v1" v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// InspectForKube takes a slice of libpod containers and generates // GenerateForKube takes a slice of libpod containers and generates
// one v1.Pod description that includes just a single container. // one v1.Pod description that includes just a single container.
func (c *Container) InspectForKube() (*v1.Pod, error) { func (c *Container) GenerateForKube() (*v1.Pod, error) {
// Generate the v1.Pod yaml description // Generate the v1.Pod yaml description
return simplePodWithV1Container(c) return simplePodWithV1Container(c)
} }
// simplePodWithV1Container is a function used by inspect when kube yaml needs to be generated // GenerateForKube takes a slice of libpod containers and generates
// for a single container. we "insert" that container description in a pod. // one v1.Pod description
func simplePodWithV1Container(ctr *Container) (*v1.Pod, error) { func (p *Pod) GenerateForKube() (*v1.Pod, []v1.ServicePort, error) {
var containers []v1.Container // Generate the v1.Pod yaml description
result, err := containerToV1Container(ctr) var servicePorts []v1.ServicePort
allContainers, err := p.allContainers()
if err != nil {
return nil, servicePorts, err
}
// If the pod has no containers, no sense to generate YAML
if len(allContainers) == 0 {
return nil, servicePorts, errors.Errorf("pod %s has no containers", p.ID())
}
// If only an infra container is present, makes no sense to generate YAML
if len(allContainers) == 1 && p.HasInfraContainer() {
return nil, servicePorts, errors.Errorf("pod %s only has an infra container", p.ID())
}
if p.HasInfraContainer() {
infraContainer, err := p.getInfraContainer()
if err != nil {
return nil, servicePorts, err
}
ports, err := ocicniPortMappingToContainerPort(infraContainer.config.PortMappings)
if err != nil {
return nil, servicePorts, err
}
servicePorts = containerPortsToServicePorts(ports)
}
pod, err := p.podWithContainers(allContainers)
return pod, servicePorts, err
}
func (p *Pod) getInfraContainer() (*Container, error) {
infraID, err := p.InfraContainerID()
if err != nil { if err != nil {
return nil, err return nil, err
} }
containers = append(containers, result) return p.runtime.LookupContainer(infraID)
}
// GenerateKubeServiceFromV1Pod creates a v1 service object from a v1 pod object
func GenerateKubeServiceFromV1Pod(pod *v1.Pod, servicePorts []v1.ServicePort) v1.Service {
service := v1.Service{}
selector := make(map[string]string)
selector["app"] = pod.Labels["app"]
ports := servicePorts
if len(ports) == 0 {
ports = containersToServicePorts(pod.Spec.Containers)
}
serviceSpec := v1.ServiceSpec{
Ports: ports,
Selector: selector,
Type: v1.ServiceTypeNodePort,
}
service.Spec = serviceSpec
service.ObjectMeta = pod.ObjectMeta
tm := v12.TypeMeta{
Kind: "Service",
APIVersion: pod.TypeMeta.APIVersion,
}
service.TypeMeta = tm
return service
}
// containerPortsToServicePorts takes a slice of containerports and generates a
// slice of service ports
func containerPortsToServicePorts(containerPorts []v1.ContainerPort) []v1.ServicePort {
var sps []v1.ServicePort
for _, cp := range containerPorts {
nodePort := 30000 + rand.Intn(32767-30000+1)
servicePort := v1.ServicePort{
Protocol: cp.Protocol,
Port: cp.ContainerPort,
NodePort: int32(nodePort),
Name: strconv.Itoa(int(cp.ContainerPort)),
}
sps = append(sps, servicePort)
}
return sps
}
// containersToServicePorts takes a slice of v1.Containers and generates an
// inclusive list of serviceports to expose
func containersToServicePorts(containers []v1.Container) []v1.ServicePort {
var sps []v1.ServicePort
// Without the call to rand.Seed, a program will produce the same sequence of pseudo-random numbers
// for each execution. Legal nodeport range is 30000-32767
rand.Seed(time.Now().UnixNano())
for _, ctr := range containers {
sps = append(sps, containerPortsToServicePorts(ctr.Ports)...)
}
return sps
}
func (p *Pod) podWithContainers(containers []*Container) (*v1.Pod, error) {
var podContainers []v1.Container
for _, ctr := range containers {
result, err := containerToV1Container(ctr)
if err != nil {
return nil, err
}
if !ctr.IsInfra() {
podContainers = append(podContainers, result)
}
}
return addContainersToPodObject(podContainers, p.Name()), nil
}
func addContainersToPodObject(containers []v1.Container, podName string) *v1.Pod {
tm := v12.TypeMeta{ tm := v12.TypeMeta{
Kind: "Pod", Kind: "Pod",
APIVersion: "v1", APIVersion: "v1",
@ -39,10 +146,10 @@ func simplePodWithV1Container(ctr *Container) (*v1.Pod, error) {
// Add a label called "app" with the containers name as a value // Add a label called "app" with the containers name as a value
labels := make(map[string]string) labels := make(map[string]string)
labels["app"] = removeUnderscores(ctr.Name()) labels["app"] = removeUnderscores(podName)
om := v12.ObjectMeta{ om := v12.ObjectMeta{
// The name of the pod is container_name-libpod // The name of the pod is container_name-libpod
Name: fmt.Sprintf("%s-libpod", removeUnderscores(ctr.Name())), Name: fmt.Sprintf("%s-libpod", removeUnderscores(podName)),
Labels: labels, Labels: labels,
// CreationTimestamp seems to be required, so adding it; in doing so, the timestamp // CreationTimestamp seems to be required, so adding it; in doing so, the timestamp
// will reflect time this is run (not container create time) because the conversion // will reflect time this is run (not container create time) because the conversion
@ -57,7 +164,20 @@ func simplePodWithV1Container(ctr *Container) (*v1.Pod, error) {
ObjectMeta: om, ObjectMeta: om,
Spec: ps, Spec: ps,
} }
return &p, nil return &p
}
// simplePodWithV1Container is a function used by inspect when kube yaml needs to be generated
// for a single container. we "insert" that container description in a pod.
func simplePodWithV1Container(ctr *Container) (*v1.Pod, error) {
var containers []v1.Container
result, err := containerToV1Container(ctr)
if err != nil {
return nil, err
}
containers = append(containers, result)
return addContainersToPodObject(containers, ctr.Name()), nil
} }
// containerToV1Container converts information we know about a libpod container // containerToV1Container converts information we know about a libpod container

View File

@ -0,0 +1,106 @@
package integration
import (
"fmt"
"os"
. "github.com/containers/libpod/test/utils"
"github.com/ghodss/yaml"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Podman generate kube", func() {
var (
tempdir string
err error
podmanTest *PodmanTestIntegration
)
BeforeEach(func() {
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
}
podmanTest = PodmanTestCreate(tempdir)
podmanTest.RestoreAllArtifacts()
})
AfterEach(func() {
podmanTest.Cleanup()
f := CurrentGinkgoTestDescription()
timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
GinkgoWriter.Write([]byte(timedResult))
})
It("podman generate pod kube on bogus object", func() {
session := podmanTest.Podman([]string{"generate", "kube", "foobar"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Not(Equal(0)))
})
It("podman generate service kube on bogus object", func() {
session := podmanTest.Podman([]string{"generate", "kube", "-s", "foobar"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Not(Equal(0)))
})
It("podman generate kube on container", func() {
session := podmanTest.RunTopContainer("top")
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
kube := podmanTest.Podman([]string{"generate", "kube", "top"})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))
_, err := yaml.Marshal(kube.OutputToString())
Expect(err).To(BeNil())
})
It("podman generate service kube on container", func() {
session := podmanTest.RunTopContainer("top")
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
kube := podmanTest.Podman([]string{"generate", "kube", "-s", "top"})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))
_, err := yaml.Marshal(kube.OutputToString())
Expect(err).To(BeNil())
})
It("podman generate kube on pod", func() {
_, rc, _ := podmanTest.CreatePod("toppod")
Expect(rc).To(Equal(0))
session := podmanTest.RunTopContainerInPod("topcontainer", "toppod")
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
kube := podmanTest.Podman([]string{"generate", "kube", "toppod"})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))
_, err := yaml.Marshal(kube.OutputToString())
Expect(err).To(BeNil())
})
It("podman generate service kube on pod", func() {
_, rc, _ := podmanTest.CreatePod("toppod")
Expect(rc).To(Equal(0))
session := podmanTest.RunTopContainerInPod("topcontainer", "toppod")
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
kube := podmanTest.Podman([]string{"generate", "kube", "-s", "toppod"})
kube.WaitWithDefaultTimeout()
Expect(kube.ExitCode()).To(Equal(0))
_, err := yaml.Marshal(kube.OutputToString())
Expect(err).To(BeNil())
})
})