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

View File

@ -859,6 +859,25 @@ _podman_container_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() {
local boolean_options="
--help
@ -2197,6 +2216,14 @@ _podman_logout() {
_complete_ "$options_with_args" "$boolean_options"
}
_podman_generate_kube() {
local options_with_args=""
local boolean_options="
-s
--service
"
_podman_container_runlabel() {
local options_with_args="
--authfile
@ -2538,6 +2565,7 @@ _podman_podman() {
diff
exec
export
generate
history
images
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 (
"fmt"
"math/rand"
"strconv"
"strings"
"time"
"github.com/containers/libpod/pkg/lookup"
"github.com/containers/libpod/pkg/util"
@ -15,23 +18,127 @@ import (
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.
func (c *Container) InspectForKube() (*v1.Pod, error) {
func (c *Container) GenerateForKube() (*v1.Pod, error) {
// Generate the v1.Pod yaml description
return simplePodWithV1Container(c)
}
// 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)
// GenerateForKube takes a slice of libpod containers and generates
// one v1.Pod description
func (p *Pod) GenerateForKube() (*v1.Pod, []v1.ServicePort, error) {
// Generate the v1.Pod yaml description
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 {
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{
Kind: "Pod",
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
labels := make(map[string]string)
labels["app"] = removeUnderscores(ctr.Name())
labels["app"] = removeUnderscores(podName)
om := v12.ObjectMeta{
// 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,
// 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
@ -57,7 +164,20 @@ func simplePodWithV1Container(ctr *Container) (*v1.Pod, error) {
ObjectMeta: om,
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

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())
})
})