Skip to content

Commit

Permalink
Add --podmanonly flag to podman generate kube
Browse files Browse the repository at this point in the history
Adds an `--podmanonly` flag to `podman generate kube` to allow for
reserved annotations to be included in the generated YAML file.

Associated with: #19102

Signed-off-by: Jake Correnti <jakecorrenti+github@proton.me>
  • Loading branch information
jakecorrenti committed Jul 12, 2023
1 parent 9d9f4aa commit d26a6c5
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 16 deletions.
3 changes: 3 additions & 0 deletions cmd/podman/kube/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ func generateFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) {
noTruncAnnotationsFlagName := "no-trunc"
flags.BoolVar(&generateOptions.UseLongAnnotations, noTruncAnnotationsFlagName, false, "Don't truncate annotations to Kubernetes length (63 chars)")

podmanonlyFlagName := "podmanonly"
flags.BoolVar(&generateOptions.PodmanOnly, podmanonlyFlagName, false, "Add podman-only reserved annotations to the generated YAML file (Cannot be used by Kubernetes")

flags.SetNormalizeFunc(utils.AliasFlags)
}

Expand Down
4 changes: 4 additions & 0 deletions docs/source/markdown/podman-kube-generate.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Note that the generated Kubernetes YAML file can be used to re-run the deploymen

## OPTIONS

#### **--podmanonly**

Add podman-only reserved annotations in generated YAML file (Cannot be used by Kubernetes)

#### **--filename**, **-f**=*filename*

Output to the given file instead of STDOUT. If the file already exists, `kube generate` refuses to replace it and returns an error.
Expand Down
16 changes: 8 additions & 8 deletions libpod/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ import (

// GenerateForKube takes a slice of libpod containers and generates
// one v1.Pod description that includes just a single container.
func GenerateForKube(ctx context.Context, ctrs []*Container, getService, useLongAnnotations bool) (*v1.Pod, error) {
func GenerateForKube(ctx context.Context, ctrs []*Container, getService, useLongAnnotations, podmanOnly bool) (*v1.Pod, error) {
// Generate the v1.Pod yaml description
return simplePodWithV1Containers(ctx, ctrs, getService, useLongAnnotations)
return simplePodWithV1Containers(ctx, ctrs, getService, useLongAnnotations, podmanOnly)
}

// GenerateForKube takes a slice of libpod containers and generates
// one v1.Pod description
func (p *Pod) GenerateForKube(ctx context.Context, getService, useLongAnnotations bool) (*v1.Pod, []v1.ServicePort, error) {
func (p *Pod) GenerateForKube(ctx context.Context, getService, useLongAnnotations, podmanOnly bool) (*v1.Pod, []v1.ServicePort, error) {
// Generate the v1.Pod yaml description
var (
ports []v1.ContainerPort
Expand Down Expand Up @@ -91,7 +91,7 @@ func (p *Pod) GenerateForKube(ctx context.Context, getService, useLongAnnotation
hostNetwork = infraContainer.NetworkMode() == string(namespaces.NetworkMode(specgen.Host))
hostUsers = infraContainer.IDMappings().HostUIDMapping && infraContainer.IDMappings().HostGIDMapping
}
pod, err := p.podWithContainers(ctx, allContainers, ports, hostNetwork, hostUsers, getService, useLongAnnotations)
pod, err := p.podWithContainers(ctx, allContainers, ports, hostNetwork, hostUsers, getService, useLongAnnotations, podmanOnly)
if err != nil {
return nil, servicePorts, err
}
Expand Down Expand Up @@ -426,7 +426,7 @@ func containersToServicePorts(containers []v1.Container) ([]v1.ServicePort, erro
return sps, nil
}

func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, ports []v1.ContainerPort, hostNetwork, hostUsers, getService, useLongAnnotations bool) (*v1.Pod, error) {
func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, ports []v1.ContainerPort, hostNetwork, hostUsers, getService, useLongAnnotations, podmanOnly bool) (*v1.Pod, error) {
deDupPodVolumes := make(map[string]*v1.Volume)
first := true
podContainers := make([]v1.Container, 0, len(containers))
Expand All @@ -442,7 +442,7 @@ func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, po
for _, ctr := range containers {
if !ctr.IsInfra() {
for k, v := range ctr.config.Spec.Annotations {
if define.IsReservedAnnotation(k) || annotations.IsReservedAnnotation(k) {
if !podmanOnly && (define.IsReservedAnnotation(k) || annotations.IsReservedAnnotation(k)) {
continue
}
podAnnotations[fmt.Sprintf("%s/%s", k, removeUnderscores(ctr.Name()))] = truncateKubeAnnotation(v, useLongAnnotations)
Expand Down Expand Up @@ -574,7 +574,7 @@ func newPodObject(podName string, annotations map[string]string, initCtrs, conta

// simplePodWithV1Containers 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 simplePodWithV1Containers(ctx context.Context, ctrs []*Container, getService, useLongAnnotations bool) (*v1.Pod, error) {
func simplePodWithV1Containers(ctx context.Context, ctrs []*Container, getService, useLongAnnotations, podmanOnly bool) (*v1.Pod, error) {
kubeCtrs := make([]v1.Container, 0, len(ctrs))
kubeInitCtrs := []v1.Container{}
kubeVolumes := make([]v1.Volume, 0)
Expand All @@ -588,7 +588,7 @@ func simplePodWithV1Containers(ctx context.Context, ctrs []*Container, getServic
for _, ctr := range ctrs {
ctrNames = append(ctrNames, removeUnderscores(ctr.Name()))
for k, v := range ctr.config.Spec.Annotations {
if define.IsReservedAnnotation(k) || annotations.IsReservedAnnotation(k) {
if !podmanOnly && (define.IsReservedAnnotation(k) || annotations.IsReservedAnnotation(k)) {
continue
}
kubeAnnotations[fmt.Sprintf("%s/%s", k, removeUnderscores(ctr.Name()))] = truncateKubeAnnotation(v, useLongAnnotations)
Expand Down
12 changes: 7 additions & 5 deletions pkg/api/handlers/libpod/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,12 @@ func GenerateKube(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
Names []string `schema:"names"`
Service bool `schema:"service"`
Type string `schema:"type"`
Replicas int32 `schema:"replicas"`
NoTrunc bool `schema:"noTrunc"`
PodmanOnly bool `schema:"podmanOnly"`
Names []string `schema:"names"`
Service bool `schema:"service"`
Type string `schema:"type"`
Replicas int32 `schema:"replicas"`
NoTrunc bool `schema:"noTrunc"`
}{
// Defaults would go here.
Replicas: 1,
Expand All @@ -117,6 +118,7 @@ func GenerateKube(w http.ResponseWriter, r *http.Request) {

containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.GenerateKubeOptions{
PodmanOnly: query.PodmanOnly,
Service: query.Service,
Type: generateType,
Replicas: query.Replicas,
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/server/register_kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ func (s *APIServer) registerKubeHandlers(r *mux.Router) error {
// description: Generate Kubernetes YAML based on a pod or container.
// parameters:
// - in: query
// name: podmanOnly
// type: boolean
// default: false
// description: add podman-only reserved annotations in generated YAML file (Cannot be used by Kubernetes)
// - in: query
// name: names
// type: array
// items:
Expand Down
2 changes: 2 additions & 0 deletions pkg/bindings/generate/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package generate
//
//go:generate go run ../generator/generator.go KubeOptions
type KubeOptions struct {
// PodmanOnly - add podman-only reserved annotations to generated YAML file (Cannot be used by Kubernetes)
PodmanOnly *bool
// Service - generate YAML for a Kubernetes _service_ object.
Service *bool
// Type - the k8s kind to be generated i.e Pod or Deployment
Expand Down
15 changes: 15 additions & 0 deletions pkg/bindings/generate/types_kube_options.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pkg/domain/entities/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type GenerateSystemdReport struct {

// GenerateKubeOptions control the generation of Kubernetes YAML files.
type GenerateKubeOptions struct {
// PodmanOnly - add podman-only reserved annotations in the generated YAML file (Cannot be used by Kubernetes)
PodmanOnly bool
// Service - generate YAML for a Kubernetes _service_ object.
Service bool
// Type - the k8s kind to be generated i.e Pod or Deployment
Expand Down
4 changes: 2 additions & 2 deletions pkg/domain/infra/abi/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string,

// Generate the kube pods from containers.
if len(ctrs) >= 1 {
po, err := libpod.GenerateForKube(ctx, ctrs, options.Service, options.UseLongAnnotations)
po, err := libpod.GenerateForKube(ctx, ctrs, options.Service, options.UseLongAnnotations, options.PodmanOnly)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -273,7 +273,7 @@ func getKubePods(ctx context.Context, pods []*libpod.Pod, options entities.Gener
svcs := [][]byte{}

for _, p := range pods {
po, sp, err := p.GenerateForKube(ctx, options.Service, options.UseLongAnnotations)
po, sp, err := p.GenerateForKube(ctx, options.Service, options.UseLongAnnotations, options.PodmanOnly)
if err != nil {
return nil, nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/domain/infra/tunnel/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string,
//
// Note: Caller is responsible for closing returned Reader
func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string, opts entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
options := new(generate.KubeOptions).WithService(opts.Service).WithType(opts.Type).WithReplicas(opts.Replicas).WithNoTrunc(opts.UseLongAnnotations)
options := new(generate.KubeOptions).WithService(opts.Service).WithType(opts.Type).WithReplicas(opts.Replicas).WithNoTrunc(opts.UseLongAnnotations).WithPodmanOnly(opts.PodmanOnly)
return generate.Kube(ic.ClientCtx, nameOrIDs, options)
}

Expand Down
181 changes: 181 additions & 0 deletions test/e2e/generate_kube_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package integration

import (
"fmt"
"os"
"os/user"
"path/filepath"
Expand Down Expand Up @@ -1657,4 +1658,184 @@ USER test1`
Expect(pod.Annotations).To(HaveKeyWithValue(define.BindMountPrefix, vol1[:define.MaxKubeAnnotation]))
Expect(pod.Annotations).To(Not(HaveKeyWithValue(define.BindMountPrefix, vol1+":Z")))
})

It("podman kube generate --podmanonly on container with --volumes-from", func() {
ctr1 := "ctr1"
ctr2 := "ctr2"
vol1 := filepath.Join(podmanTest.TempDir, "vol-test1")

err := os.MkdirAll(vol1, 0755)
Expect(err).ToNot(HaveOccurred())

session := podmanTest.Podman([]string{"create", "--name", ctr1, "-v", vol1, ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

session = podmanTest.Podman([]string{"create", "--volumes-from", ctr1, "--name", ctr2, ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

kube := podmanTest.Podman([]string{"kube", "generate", "--podmanonly", ctr2})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))

pod := new(v1.Pod)
err = yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).ToNot(HaveOccurred())
Expect(pod.Annotations).To(HaveKeyWithValue(define.InspectAnnotationVolumesFrom+"/"+ctr2, ctr1))
})

It("podman kube generate --podmanonly on container with --rm", func() {
ctr := "ctr"

session := podmanTest.Podman([]string{"create", "--rm", "--name", ctr, ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

kube := podmanTest.Podman([]string{"kube", "generate", "--podmanonly", ctr})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))

pod := new(v1.Pod)
err = yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).ToNot(HaveOccurred())
Expect(pod.Annotations).To(HaveKeyWithValue(define.InspectAnnotationAutoremove+"/"+ctr, define.InspectResponseTrue))
})

It("podman kube generate --podmanonly on container with --privileged", func() {
ctr := "ctr"

session := podmanTest.Podman([]string{"create", "--privileged", "--name", ctr, ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

kube := podmanTest.Podman([]string{"kube", "generate", "--podmanonly", ctr})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))

pod := new(v1.Pod)
err = yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).ToNot(HaveOccurred())
Expect(pod.Annotations).To(HaveKeyWithValue(define.InspectAnnotationPrivileged+"/"+ctr, define.InspectResponseTrue))
})

It("podman kube generate --podmanonly on container with --init", func() {
ctr := "ctr"

session := podmanTest.Podman([]string{"create", "--init", "--name", ctr, ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

kube := podmanTest.Podman([]string{"kube", "generate", "--podmanonly", ctr})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))

pod := new(v1.Pod)
err = yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).ToNot(HaveOccurred())
Expect(pod.Annotations).To(HaveKeyWithValue(define.InspectAnnotationInit+"/"+ctr, define.InspectResponseTrue))
})

It("podman kube generate --podmanonly on container with --cidfile", func() {
ctr := "ctr"
cidFile := filepath.Join(podmanTest.TempDir, RandomString(10)+".txt")

session := podmanTest.Podman([]string{"create", "--cidfile", cidFile, "--name", ctr, ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

kube := podmanTest.Podman([]string{"kube", "generate", "--podmanonly", ctr})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))

pod := new(v1.Pod)
err = yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).ToNot(HaveOccurred())
Expect(pod.Annotations).To(HaveKeyWithValue(define.InspectAnnotationCIDFile+"/"+ctr, cidFile))
})

It("podman kube generate --podmanonly on container with --security-opt seccomp=unconfined", func() {
ctr := "ctr"

session := podmanTest.Podman([]string{"create", "--security-opt", "seccomp=unconfined", "--name", ctr, ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

kube := podmanTest.Podman([]string{"kube", "generate", "--podmanonly", ctr})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))

pod := new(v1.Pod)
err = yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).ToNot(HaveOccurred())
Expect(pod.Annotations).To(HaveKeyWithValue(define.InspectAnnotationSeccomp+"/"+ctr, "unconfined"))
})

It("podman kube generate --podmanonly on container with --security-opt apparmor=unconfined", func() {
ctr := "ctr"

session := podmanTest.Podman([]string{"create", "--security-opt", "apparmor=unconfined", "--name", ctr, ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

kube := podmanTest.Podman([]string{"kube", "generate", "--podmanonly", ctr})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))

pod := new(v1.Pod)
err = yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).ToNot(HaveOccurred())
Expect(pod.Annotations).To(HaveKeyWithValue(define.InspectAnnotationApparmor+"/"+ctr, "unconfined"))
})

It("podman kube generate --podmanonly on container with --security-opt label=level:s0", func() {
ctr := "ctr"

session := podmanTest.Podman([]string{"create", "--security-opt", "label=level:s0", "--name", ctr, ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

kube := podmanTest.Podman([]string{"kube", "generate", "--podmanonly", ctr})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))

pod := new(v1.Pod)
err = yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).ToNot(HaveOccurred())
Expect(pod.Annotations).To(HaveKeyWithValue(define.InspectAnnotationLabel+"/"+ctr, "level:s0"))
})

It("podman kube generate --podmanonly on container with --publish-all", func() {
podmanTest.AddImageToRWStore(ALPINE)
dockerfile := fmt.Sprintf(`FROM %s
EXPOSE 2002
EXPOSE 2001-2003
EXPOSE 2004-2005/tcp`, ALPINE)
imageName := "testimg"
podmanTest.BuildImage(dockerfile, imageName, "false")

// Verify that the buildah is just passing through the EXPOSE keys
inspect := podmanTest.Podman([]string{"inspect", imageName})
inspect.WaitWithDefaultTimeout()
image := inspect.InspectImageJSON()
Expect(image).To(HaveLen(1))
Expect(image[0].Config.ExposedPorts).To(HaveLen(3))
Expect(image[0].Config.ExposedPorts).To(HaveKey("2002/tcp"))
Expect(image[0].Config.ExposedPorts).To(HaveKey("2001-2003/tcp"))
Expect(image[0].Config.ExposedPorts).To(HaveKey("2004-2005/tcp"))

ctr := "ctr"
session := podmanTest.Podman([]string{"create", "--publish-all", "--name", ctr, imageName, "true"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

kube := podmanTest.Podman([]string{"kube", "generate", "--podmanonly", ctr})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))

pod := new(v1.Pod)
err = yaml.Unmarshal(kube.Out.Contents(), pod)
Expect(err).ToNot(HaveOccurred())
Expect(pod.Annotations).To(HaveKeyWithValue(define.InspectAnnotationPublishAll+"/"+ctr, define.InspectResponseTrue))
})
})

0 comments on commit d26a6c5

Please sign in to comment.