mirror of
https://github.com/containers/podman.git
synced 2025-10-16 18:53:19 +08:00
Merge pull request #10202 from EduardoVega/9763-kube-auto-update
Add support to preserve auto-update labels in play / generate kube
This commit is contained in:
@ -14,7 +14,7 @@ The label "image" is an alternative to "registry" maintained for backwards compa
|
||||
An image is considered updated if the digest in the local storage is different than the one of the remote image.
|
||||
If an image must be updated, Podman pulls it down and restarts the systemd unit executing the container.
|
||||
|
||||
The registry policy requires a requires a fully-qualified image reference (e.g., quay.io/podman/stable:latest) to be used to create the container.
|
||||
The registry policy requires a fully-qualified image reference (e.g., quay.io/podman/stable:latest) to be used to create the container.
|
||||
This enforcement is necessary to know which image to actually check and pull.
|
||||
If an image ID was used, Podman would not know which image to check/pull anymore.
|
||||
|
||||
|
@ -218,9 +218,15 @@ func (p *Pod) podWithContainers(containers []*Container, ports []v1.ContainerPor
|
||||
deDupPodVolumes := make(map[string]*v1.Volume)
|
||||
first := true
|
||||
podContainers := make([]v1.Container, 0, len(containers))
|
||||
podAnnotations := make(map[string]string)
|
||||
dnsInfo := v1.PodDNSConfig{}
|
||||
for _, ctr := range containers {
|
||||
if !ctr.IsInfra() {
|
||||
// Convert auto-update labels into kube annotations
|
||||
for k, v := range getAutoUpdateAnnotations(removeUnderscores(ctr.Name()), ctr.Labels()) {
|
||||
podAnnotations[k] = v
|
||||
}
|
||||
|
||||
ctr, volumes, _, err := containerToV1Container(ctr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -267,10 +273,16 @@ func (p *Pod) podWithContainers(containers []*Container, ports []v1.ContainerPor
|
||||
podVolumes = append(podVolumes, *vol)
|
||||
}
|
||||
|
||||
return addContainersAndVolumesToPodObject(podContainers, podVolumes, p.Name(), &dnsInfo, hostNetwork), nil
|
||||
return newPodObject(
|
||||
p.Name(),
|
||||
podAnnotations,
|
||||
podContainers,
|
||||
podVolumes,
|
||||
&dnsInfo,
|
||||
hostNetwork), nil
|
||||
}
|
||||
|
||||
func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.Volume, podName string, dnsOptions *v1.PodDNSConfig, hostNetwork bool) *v1.Pod {
|
||||
func newPodObject(podName string, annotations map[string]string, containers []v1.Container, volumes []v1.Volume, dnsOptions *v1.PodDNSConfig, hostNetwork bool) *v1.Pod {
|
||||
tm := v12.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
@ -287,6 +299,7 @@ func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.
|
||||
// will reflect time this is run (not container create time) because the conversion
|
||||
// of the container create time to v1 Time is probably not warranted nor worthwhile.
|
||||
CreationTimestamp: v12.Now(),
|
||||
Annotations: annotations,
|
||||
}
|
||||
ps := v1.PodSpec{
|
||||
Containers: containers,
|
||||
@ -311,7 +324,13 @@ func simplePodWithV1Containers(ctrs []*Container) (*v1.Pod, error) {
|
||||
kubeVolumes := make([]v1.Volume, 0)
|
||||
hostNetwork := true
|
||||
podDNS := v1.PodDNSConfig{}
|
||||
kubeAnnotations := make(map[string]string)
|
||||
for _, ctr := range ctrs {
|
||||
// Convert auto-update labels into kube annotations
|
||||
for k, v := range getAutoUpdateAnnotations(removeUnderscores(ctr.Name()), ctr.Labels()) {
|
||||
kubeAnnotations[k] = v
|
||||
}
|
||||
|
||||
if !ctr.HostNetwork() {
|
||||
hostNetwork = false
|
||||
}
|
||||
@ -355,7 +374,13 @@ func simplePodWithV1Containers(ctrs []*Container) (*v1.Pod, error) {
|
||||
}
|
||||
} // end if ctrDNS
|
||||
}
|
||||
return addContainersAndVolumesToPodObject(kubeCtrs, kubeVolumes, strings.ReplaceAll(ctrs[0].Name(), "_", ""), &podDNS, hostNetwork), nil
|
||||
return newPodObject(
|
||||
strings.ReplaceAll(ctrs[0].Name(), "_", ""),
|
||||
kubeAnnotations,
|
||||
kubeCtrs,
|
||||
kubeVolumes,
|
||||
&podDNS,
|
||||
hostNetwork), nil
|
||||
}
|
||||
|
||||
// containerToV1Container converts information we know about a libpod container
|
||||
@ -792,3 +817,21 @@ func generateKubeVolumeDeviceFromLinuxDevice(devices []specs.LinuxDevice) []v1.V
|
||||
func removeUnderscores(s string) string {
|
||||
return strings.Replace(s, "_", "", -1)
|
||||
}
|
||||
|
||||
// getAutoUpdateAnnotations searches for auto-update container labels
|
||||
// and returns them as kube annotations
|
||||
func getAutoUpdateAnnotations(ctrName string, ctrLabels map[string]string) map[string]string {
|
||||
autoUpdateLabel := "io.containers.autoupdate"
|
||||
annotations := make(map[string]string)
|
||||
|
||||
for k, v := range ctrLabels {
|
||||
if strings.Contains(k, autoUpdateLabel) {
|
||||
// since labels can variate between containers within a pod, they will be
|
||||
// identified with the container name when converted into kube annotations
|
||||
kc := fmt.Sprintf("%s/%s", k, ctrName)
|
||||
annotations[kc] = v
|
||||
}
|
||||
}
|
||||
|
||||
return annotations
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/podman/v3/libpod"
|
||||
"github.com/containers/podman/v3/libpod/define"
|
||||
"github.com/containers/podman/v3/pkg/autoupdate"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/containers/podman/v3/pkg/specgen"
|
||||
"github.com/containers/podman/v3/pkg/specgen/generate"
|
||||
@ -73,7 +74,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en
|
||||
podTemplateSpec.ObjectMeta = podYAML.ObjectMeta
|
||||
podTemplateSpec.Spec = podYAML.Spec
|
||||
|
||||
r, err := ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options, &ipIndex)
|
||||
r, err := ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options, &ipIndex, podYAML.Annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -143,7 +144,7 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM
|
||||
// create "replicas" number of pods
|
||||
for i = 0; i < numReplicas; i++ {
|
||||
podName := fmt.Sprintf("%s-pod-%d", deploymentName, i)
|
||||
podReport, err := ic.playKubePod(ctx, podName, &podSpec, options, ipIndex)
|
||||
podReport, err := ic.playKubePod(ctx, podName, &podSpec, options, ipIndex, deploymentYAML.Annotations)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error encountered while bringing up pod %s", podName)
|
||||
}
|
||||
@ -152,7 +153,7 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM
|
||||
return &report, nil
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions, ipIndex *int) (*entities.PlayKubeReport, error) {
|
||||
func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions, ipIndex *int, annotations map[string]string) (*entities.PlayKubeReport, error) {
|
||||
var (
|
||||
writer io.Writer
|
||||
playKubePod entities.PlayKubePod
|
||||
@ -265,6 +266,9 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
||||
|
||||
containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers))
|
||||
for _, container := range podYAML.Spec.Containers {
|
||||
// Contains all labels obtained from kube
|
||||
labels := make(map[string]string)
|
||||
|
||||
// NOTE: set the pull policy to "newer". This will cover cases
|
||||
// where the "latest" tag requires a pull and will also
|
||||
// transparently handle "localhost/" prefixed files which *may*
|
||||
@ -292,6 +296,22 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handle kube annotations
|
||||
for k, v := range annotations {
|
||||
switch k {
|
||||
// Auto update annotation without container name will apply to
|
||||
// all containers within the pod
|
||||
case autoupdate.Label, autoupdate.AuthfileLabel:
|
||||
labels[k] = v
|
||||
// Auto update annotation with container name will apply only
|
||||
// to the specified container
|
||||
case fmt.Sprintf("%s/%s", autoupdate.Label, container.Name),
|
||||
fmt.Sprintf("%s/%s", autoupdate.AuthfileLabel, container.Name):
|
||||
prefixAndCtr := strings.Split(k, "/")
|
||||
labels[prefixAndCtr[0]] = v
|
||||
}
|
||||
}
|
||||
|
||||
specgenOpts := kube.CtrSpecGenOptions{
|
||||
Container: container,
|
||||
Image: pulledImages[0],
|
||||
@ -305,6 +325,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
||||
NetNSIsHost: p.NetNS.IsHost(),
|
||||
SecretsManager: secretsManager,
|
||||
LogDriver: options.LogDriver,
|
||||
Labels: labels,
|
||||
}
|
||||
specGen, err := kube.ToSpecGen(ctx, &specgenOpts)
|
||||
if err != nil {
|
||||
|
@ -100,6 +100,8 @@ type CtrSpecGenOptions struct {
|
||||
SecretsManager *secrets.SecretsManager
|
||||
// LogDriver which should be used for the container
|
||||
LogDriver string
|
||||
// Labels define key-value pairs of metadata
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGenerator, error) {
|
||||
@ -278,6 +280,19 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
|
||||
s.NetNS.NSMode = specgen.Host
|
||||
}
|
||||
|
||||
// Add labels that come from kube
|
||||
if len(s.Labels) == 0 {
|
||||
// If there are no labels, let's use the map that comes
|
||||
// from kube
|
||||
s.Labels = opts.Labels
|
||||
} else {
|
||||
// If there are already labels in the map, append the ones
|
||||
// obtained from kube
|
||||
for k, v := range opts.Labels {
|
||||
s.Labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
|
@ -873,4 +873,54 @@ USER test1`
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
It("podman generate kube on container with auto update labels", func() {
|
||||
top := podmanTest.Podman([]string{"run", "-dt", "--name", "top", "--label", "io.containers.autoupdate=local", ALPINE, "top"})
|
||||
top.WaitWithDefaultTimeout()
|
||||
Expect(top.ExitCode()).To(Equal(0))
|
||||
|
||||
kube := podmanTest.Podman([]string{"generate", "kube", "top"})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Equal(0))
|
||||
|
||||
pod := new(v1.Pod)
|
||||
err := yaml.Unmarshal(kube.Out.Contents(), pod)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
v, ok := pod.GetAnnotations()["io.containers.autoupdate/top"]
|
||||
Expect(ok).To(Equal(true))
|
||||
Expect(v).To(Equal("local"))
|
||||
})
|
||||
|
||||
It("podman generate kube on pod with auto update labels in all containers", func() {
|
||||
pod1 := podmanTest.Podman([]string{"pod", "create", "--name", "pod1"})
|
||||
pod1.WaitWithDefaultTimeout()
|
||||
Expect(pod1.ExitCode()).To(Equal(0))
|
||||
|
||||
top1 := podmanTest.Podman([]string{"run", "-dt", "--name", "top1", "--pod", "pod1", "--label", "io.containers.autoupdate=registry", "--label", "io.containers.autoupdate.authfile=/some/authfile.json", ALPINE, "top"})
|
||||
top1.WaitWithDefaultTimeout()
|
||||
Expect(top1.ExitCode()).To(Equal(0))
|
||||
|
||||
top2 := podmanTest.Podman([]string{"run", "-dt", "--name", "top2", "--pod", "pod1", "--label", "io.containers.autoupdate=registry", "--label", "io.containers.autoupdate.authfile=/some/authfile.json", ALPINE, "top"})
|
||||
top2.WaitWithDefaultTimeout()
|
||||
Expect(top2.ExitCode()).To(Equal(0))
|
||||
|
||||
kube := podmanTest.Podman([]string{"generate", "kube", "pod1"})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Equal(0))
|
||||
|
||||
pod := new(v1.Pod)
|
||||
err := yaml.Unmarshal(kube.Out.Contents(), pod)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
for _, ctr := range []string{"top1", "top2"} {
|
||||
v, ok := pod.GetAnnotations()["io.containers.autoupdate/"+ctr]
|
||||
Expect(ok).To(Equal(true))
|
||||
Expect(v).To(Equal("registry"))
|
||||
|
||||
v, ok = pod.GetAnnotations()["io.containers.autoupdate.authfile/"+ctr]
|
||||
Expect(ok).To(Equal(true))
|
||||
Expect(v).To(Equal("/some/authfile.json"))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -768,6 +768,12 @@ func getCtr(options ...ctrOption) *Ctr {
|
||||
|
||||
type ctrOption func(*Ctr)
|
||||
|
||||
func withName(name string) ctrOption {
|
||||
return func(c *Ctr) {
|
||||
c.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
func withCmd(cmd []string) ctrOption {
|
||||
return func(c *Ctr) {
|
||||
c.Cmd = cmd
|
||||
@ -2304,4 +2310,79 @@ invalid kube kind
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Not(Equal(0)))
|
||||
})
|
||||
|
||||
It("podman play kube with auto update annotations for all containers", func() {
|
||||
ctr01Name := "ctr01"
|
||||
ctr02Name := "ctr02"
|
||||
podName := "foo"
|
||||
autoUpdateRegistry := "io.containers.autoupdate"
|
||||
autoUpdateRegistryValue := "registry"
|
||||
autoUpdateAuthfile := "io.containers.autoupdate.authfile"
|
||||
autoUpdateAuthfileValue := "/some/authfile.json"
|
||||
|
||||
ctr01 := getCtr(withName(ctr01Name))
|
||||
ctr02 := getCtr(withName(ctr02Name))
|
||||
|
||||
pod := getPod(
|
||||
withPodName(podName),
|
||||
withCtr(ctr01),
|
||||
withCtr(ctr02),
|
||||
withAnnotation(autoUpdateRegistry, autoUpdateRegistryValue),
|
||||
withAnnotation(autoUpdateAuthfile, autoUpdateAuthfileValue))
|
||||
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Equal(0))
|
||||
|
||||
for _, ctr := range []string{podName + "-" + ctr01Name, podName + "-" + ctr02Name} {
|
||||
inspect := podmanTest.Podman([]string{"inspect", ctr, "--format", "'{{.Config.Labels}}'"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect.ExitCode()).To(Equal(0))
|
||||
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(autoUpdateRegistry + ":" + autoUpdateRegistryValue))
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(autoUpdateAuthfile + ":" + autoUpdateAuthfileValue))
|
||||
}
|
||||
})
|
||||
|
||||
It("podman play kube with auto update annotations for first container only", func() {
|
||||
ctr01Name := "ctr01"
|
||||
ctr02Name := "ctr02"
|
||||
autoUpdateRegistry := "io.containers.autoupdate"
|
||||
autoUpdateRegistryValue := "local"
|
||||
|
||||
ctr01 := getCtr(withName(ctr01Name))
|
||||
ctr02 := getCtr(withName(ctr02Name))
|
||||
|
||||
pod := getPod(
|
||||
withCtr(ctr01),
|
||||
withCtr(ctr02),
|
||||
)
|
||||
|
||||
deployment := getDeployment(
|
||||
withPod(pod),
|
||||
withDeploymentAnnotation(autoUpdateRegistry+"/"+ctr01Name, autoUpdateRegistryValue),
|
||||
)
|
||||
|
||||
err = generateKubeYaml("deployment", deployment, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Equal(0))
|
||||
|
||||
podName := getPodNamesInDeployment(deployment)[0].Name
|
||||
|
||||
inspect := podmanTest.Podman([]string{"inspect", podName + "-" + ctr01Name, "--format", "'{{.Config.Labels}}'"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect.ExitCode()).To(Equal(0))
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(autoUpdateRegistry + ":" + autoUpdateRegistryValue))
|
||||
|
||||
inspect = podmanTest.Podman([]string{"inspect", podName + "-" + ctr02Name, "--format", "'{{.Config.Labels}}'"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect.ExitCode()).To(Equal(0))
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(`map[]`))
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user