aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/generate.go23
-rw-r--r--cmd/podman/generate_kube.go (renamed from cmd/podman/kube_generate.go)67
-rw-r--r--cmd/podman/kube.go23
-rw-r--r--commands.md1
-rw-r--r--completions/bash/podman28
-rw-r--r--docs/podman-generate-kube.1.md119
-rw-r--r--docs/podman-generate.1.md19
-rw-r--r--libpod/kube.go142
-rw-r--r--test/e2e/generate_kube_test.go106
9 files changed, 466 insertions, 62 deletions
diff --git a/cmd/podman/generate.go b/cmd/podman/generate.go
new file mode 100644
index 000000000..765d0ee70
--- /dev/null
+++ b/cmd/podman/generate.go
@@ -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,
+ }
+)
diff --git a/cmd/podman/kube_generate.go b/cmd/podman/generate_kube.go
index a18912668..de9f701b0 100644
--- a/cmd/podman/kube_generate.go
+++ b/cmd/podman/generate_kube.go
@@ -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))
diff --git a/cmd/podman/kube.go b/cmd/podman/kube.go
deleted file mode 100644
index 2cb407c09..000000000
--- a/cmd/podman/kube.go
+++ /dev/null
@@ -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,
- }
-)
diff --git a/commands.md b/commands.md
index 8e1a2dd7a..43796722f 100644
--- a/commands.md
+++ b/commands.md
@@ -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)|
diff --git a/completions/bash/podman b/completions/bash/podman
index 3382b6c5a..eab82ec1f 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -876,6 +876,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
@@ -2219,6 +2238,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
@@ -2702,6 +2729,7 @@ _podman_podman() {
diff
exec
export
+ generate
history
images
import
diff --git a/docs/podman-generate-kube.1.md b/docs/podman-generate-kube.1.md
new file mode 100644
index 000000000..59c3353a5
--- /dev/null
+++ b/docs/podman-generate-kube.1.md
@@ -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)
diff --git a/docs/podman-generate.1.md b/docs/podman-generate.1.md
new file mode 100644
index 000000000..f19f48511
--- /dev/null
+++ b/docs/podman-generate.1.md
@@ -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
diff --git a/libpod/kube.go b/libpod/kube.go
index 1a5f80878..05a6537c4 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -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
diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go
new file mode 100644
index 000000000..0ee078455
--- /dev/null
+++ b/test/e2e/generate_kube_test.go
@@ -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())
+ })
+})