Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --podman-only flag to podman generate kube #19211

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 := "podman-only"
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 @@ -39,6 +39,10 @@ Output to the given file instead of STDOUT. If the file already exists, `kube ge
Don't truncate annotations to the Kubernetes maximum length of 63 characters.
Note: enabling this flag means the generated YAML file is not Kubernetes compatible and can only be used with `podman kube play`

#### **--podman-only**

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

#### **--replicas**, **-r**=*replica count*

The value to set `replicas` to when generating a **Deployment** kind.
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 @@ -135,6 +135,11 @@ func (s *APIServer) registerKubeHandlers(r *mux.Router) error {
// type: boolean
// default: false
// description: don't truncate annotations to the Kubernetes maximum length of 63 characters
// - in: query
// name: podmanOnly
// type: boolean
// default: false
// description: add podman-only reserved annotations in generated YAML file (cannot be used by Kubernetes)
// produces:
// - text/vnd.yaml
// - application/json
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 --podman-only 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", "--podman-only", 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 --podman-only 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", "--podman-only", 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 --podman-only 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", "--podman-only", 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 --podman-only 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", "--podman-only", 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 --podman-only 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", "--podman-only", 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 --podman-only 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", "--podman-only", 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 --podman-only 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", "--podman-only", 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 --podman-only 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", "--podman-only", 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 --podman-only 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", "--podman-only", 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))
})
})