mirror of
https://github.com/containers/podman.git
synced 2025-05-17 15:18:43 +08:00
Comply to Kubernetes specifications for annotation size.
An annotation is a pair of key-value. The key has two parts, viz. a name and an optional prefix in DNS format. The limitations on name is 63, prefix 253 chars. The limitation on total size of all key+value pairs combined is 256KB. https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/#syntax-and-character-set Fixes: https://github.com/containers/podman/issues/21663 Signed-off-by: Vikas Goel <vikas.goel@gmail.com>
This commit is contained in:
@ -83,6 +83,7 @@ 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)")
|
||||
_ = flags.MarkHidden(noTruncAnnotationsFlagName)
|
||||
|
||||
podmanOnlyFlagName := "podman-only"
|
||||
flags.BoolVar(&generateOptions.PodmanOnly, podmanOnlyFlagName, false, "Add podman-only reserved annotations to the generated YAML file (Cannot be used by Kubernetes)")
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/containers/podman/v5/cmd/podman/utils"
|
||||
"github.com/containers/podman/v5/libpod/define"
|
||||
"github.com/containers/podman/v5/libpod/shutdown"
|
||||
"github.com/containers/podman/v5/pkg/annotations"
|
||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||
"github.com/containers/podman/v5/pkg/errorhandling"
|
||||
"github.com/containers/podman/v5/pkg/util"
|
||||
@ -171,6 +172,7 @@ func playFlags(cmd *cobra.Command) {
|
||||
|
||||
noTruncFlagName := "no-trunc"
|
||||
flags.BoolVar(&playOptions.UseLongAnnotations, noTruncFlagName, false, "Use annotations that are not truncated to the Kubernetes maximum length of 63 characters")
|
||||
_ = flags.MarkHidden(noTruncFlagName)
|
||||
|
||||
if !registry.IsRemote() {
|
||||
certDirFlagName := "cert-dir"
|
||||
@ -253,12 +255,13 @@ func play(cmd *cobra.Command, args []string) error {
|
||||
if playOptions.Annotations == nil {
|
||||
playOptions.Annotations = make(map[string]string)
|
||||
}
|
||||
if len(val) > define.MaxKubeAnnotation && !playOptions.UseLongAnnotations {
|
||||
return fmt.Errorf("annotation exceeds maximum size, %d, of kubernetes annotation: %s", define.MaxKubeAnnotation, val)
|
||||
}
|
||||
playOptions.Annotations[key] = val
|
||||
}
|
||||
|
||||
if err := annotations.ValidateAnnotations(playOptions.Annotations); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, mac := range playOptions.macs {
|
||||
m, err := net.ParseMAC(mac)
|
||||
if err != nil {
|
||||
|
@ -39,11 +39,6 @@ Also note that both Deployment and DaemonSet can only have `restartPolicy` set t
|
||||
|
||||
Output to the given file instead of STDOUT. If the file already exists, `kube generate` refuses to replace it and returns an error.
|
||||
|
||||
#### **--no-trunc**
|
||||
|
||||
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)
|
||||
|
@ -217,10 +217,6 @@ When no network option is specified and *host* network mode is not configured in
|
||||
|
||||
This option conflicts with host added in the Kubernetes YAML.
|
||||
|
||||
#### **--no-trunc**
|
||||
|
||||
Use annotations that are not truncated to the Kubernetes maximum length of 63 characters
|
||||
|
||||
#### **--publish**=*[[ip:][hostPort]:]containerPort[/protocol]*
|
||||
|
||||
Define or override a port definition in the YAML file.
|
||||
|
@ -160,8 +160,8 @@ const (
|
||||
// the k8s behavior of waiting for the intialDelaySeconds to be over before updating the status
|
||||
KubeHealthCheckAnnotation = "io.podman.annotations.kube.health.check"
|
||||
|
||||
// MaxKubeAnnotation is the max length of annotations allowed by Kubernetes.
|
||||
MaxKubeAnnotation = 63
|
||||
// TotalAnnotationSizeLimitB is the max length of annotations allowed by Kubernetes.
|
||||
TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
|
||||
)
|
||||
|
||||
// IsReservedAnnotation returns true if the specified value corresponds to an
|
||||
|
@ -13,7 +13,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/containers/common/libnetwork/types"
|
||||
"github.com/containers/common/pkg/config"
|
||||
@ -36,14 +35,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, podmanOnly bool) (*v1.Pod, error) {
|
||||
func GenerateForKube(ctx context.Context, ctrs []*Container, getService, podmanOnly bool) (*v1.Pod, error) {
|
||||
// Generate the v1.Pod yaml description
|
||||
return simplePodWithV1Containers(ctx, ctrs, getService, useLongAnnotations, podmanOnly)
|
||||
return simplePodWithV1Containers(ctx, ctrs, getService, podmanOnly)
|
||||
}
|
||||
|
||||
// GenerateForKube takes a slice of libpod containers and generates
|
||||
// one v1.Pod description
|
||||
func (p *Pod) GenerateForKube(ctx context.Context, getService, useLongAnnotations, podmanOnly bool) (*v1.Pod, []v1.ServicePort, error) {
|
||||
func (p *Pod) GenerateForKube(ctx context.Context, getService, podmanOnly bool) (*v1.Pod, []v1.ServicePort, error) {
|
||||
// Generate the v1.Pod yaml description
|
||||
var (
|
||||
ports []v1.ContainerPort
|
||||
@ -95,7 +94,7 @@ func (p *Pod) GenerateForKube(ctx context.Context, getService, useLongAnnotation
|
||||
hostUsers = infraContainer.IDMappings().HostUIDMapping && infraContainer.IDMappings().HostGIDMapping
|
||||
infraName = infraContainer.config.Name
|
||||
}
|
||||
pod, err := p.podWithContainers(ctx, allContainers, ports, hostNetwork, hostUsers, getService, useLongAnnotations, podmanOnly, infraName)
|
||||
pod, err := p.podWithContainers(ctx, allContainers, ports, hostNetwork, hostUsers, getService, podmanOnly, infraName)
|
||||
if err != nil {
|
||||
return nil, servicePorts, err
|
||||
}
|
||||
@ -451,16 +450,6 @@ func newServicePortState() servicePortState {
|
||||
}
|
||||
}
|
||||
|
||||
func truncateKubeAnnotation(str string, useLongAnnotations bool) string {
|
||||
str = strings.TrimSpace(str)
|
||||
if useLongAnnotations || utf8.RuneCountInString(str) < define.MaxKubeAnnotation {
|
||||
return str
|
||||
}
|
||||
trunc := string([]rune(str)[:define.MaxKubeAnnotation])
|
||||
logrus.Warnf("Truncation Annotation: %q to %q: Kubernetes only allows %d characters", str, trunc, define.MaxKubeAnnotation)
|
||||
return trunc
|
||||
}
|
||||
|
||||
// containerPortsToServicePorts takes a slice of containerports and generates a
|
||||
// slice of service ports
|
||||
func (state *servicePortState) containerPortsToServicePorts(containerPorts []v1.ContainerPort) ([]v1.ServicePort, error) {
|
||||
@ -507,7 +496,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, podmanOnly bool, infraName string) (*v1.Pod, error) {
|
||||
func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, ports []v1.ContainerPort, hostNetwork, hostUsers, getService, podmanOnly bool, infraName string) (*v1.Pod, error) {
|
||||
deDupPodVolumes := make(map[string]*v1.Volume)
|
||||
first := true
|
||||
podContainers := make([]v1.Container, 0, len(containers))
|
||||
@ -529,11 +518,11 @@ func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, po
|
||||
if !podmanOnly && (define.IsReservedAnnotation(k) || annotations.IsReservedAnnotation(k)) {
|
||||
continue
|
||||
}
|
||||
podAnnotations[fmt.Sprintf("%s/%s", k, removeUnderscores(ctr.Name()))] = truncateKubeAnnotation(v, useLongAnnotations)
|
||||
podAnnotations[fmt.Sprintf("%s/%s", k, removeUnderscores(ctr.Name()))] = v
|
||||
}
|
||||
// Convert auto-update labels into kube annotations
|
||||
for k, v := range getAutoUpdateAnnotations(ctr.Name(), ctr.Labels(), useLongAnnotations) {
|
||||
podAnnotations[k] = truncateKubeAnnotation(v, useLongAnnotations)
|
||||
for k, v := range getAutoUpdateAnnotations(ctr.Name(), ctr.Labels()) {
|
||||
podAnnotations[k] = v
|
||||
}
|
||||
isInit := ctr.IsInitCtr()
|
||||
// Since hostname is only set at pod level, set the hostname to the hostname of the first container we encounter
|
||||
@ -556,7 +545,7 @@ func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, po
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range annotations {
|
||||
podAnnotations[define.BindMountPrefix] = truncateKubeAnnotation(k+":"+v, useLongAnnotations)
|
||||
podAnnotations[define.BindMountPrefix] = k + ":" + v
|
||||
}
|
||||
// Since port bindings for the pod are handled by the
|
||||
// infra container, wipe them here only if we are sharing the net namespace
|
||||
@ -605,7 +594,7 @@ func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, po
|
||||
// If the infraName is not the podID-infra, that means the user set another infra name using
|
||||
// --infra-name during pod creation
|
||||
if infraName != "" && infraName != p.ID()[:12]+"-infra" {
|
||||
podAnnotations[define.InfraNameAnnotation] = truncateKubeAnnotation(infraName, useLongAnnotations)
|
||||
podAnnotations[define.InfraNameAnnotation] = infraName
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -674,7 +663,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, podmanOnly bool) (*v1.Pod, error) {
|
||||
func simplePodWithV1Containers(ctx context.Context, ctrs []*Container, getService, podmanOnly bool) (*v1.Pod, error) {
|
||||
kubeCtrs := make([]v1.Container, 0, len(ctrs))
|
||||
kubeInitCtrs := []v1.Container{}
|
||||
kubeVolumes := make([]v1.Volume, 0)
|
||||
@ -694,12 +683,12 @@ func simplePodWithV1Containers(ctx context.Context, ctrs []*Container, getServic
|
||||
if !podmanOnly && (define.IsReservedAnnotation(k) || annotations.IsReservedAnnotation(k)) {
|
||||
continue
|
||||
}
|
||||
kubeAnnotations[fmt.Sprintf("%s/%s", k, removeUnderscores(ctr.Name()))] = truncateKubeAnnotation(v, useLongAnnotations)
|
||||
kubeAnnotations[fmt.Sprintf("%s/%s", k, removeUnderscores(ctr.Name()))] = v
|
||||
}
|
||||
|
||||
// Convert auto-update labels into kube annotations
|
||||
for k, v := range getAutoUpdateAnnotations(ctr.Name(), ctr.Labels(), useLongAnnotations) {
|
||||
kubeAnnotations[k] = truncateKubeAnnotation(v, useLongAnnotations)
|
||||
for k, v := range getAutoUpdateAnnotations(ctr.Name(), ctr.Labels()) {
|
||||
kubeAnnotations[k] = v
|
||||
}
|
||||
|
||||
isInit := ctr.IsInitCtr()
|
||||
@ -752,7 +741,7 @@ func simplePodWithV1Containers(ctx context.Context, ctrs []*Container, getServic
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range annotations {
|
||||
kubeAnnotations[define.BindMountPrefix] = truncateKubeAnnotation(k+":"+v, useLongAnnotations)
|
||||
kubeAnnotations[define.BindMountPrefix] = k + ":" + v
|
||||
}
|
||||
if isInit {
|
||||
kubeInitCtrs = append(kubeInitCtrs, kubeCtr)
|
||||
@ -1384,7 +1373,7 @@ func removeUnderscores(s string) string {
|
||||
|
||||
// getAutoUpdateAnnotations searches for auto-update container labels
|
||||
// and returns them as kube annotations
|
||||
func getAutoUpdateAnnotations(ctrName string, ctrLabels map[string]string, useLongAnnotations bool) map[string]string {
|
||||
func getAutoUpdateAnnotations(ctrName string, ctrLabels map[string]string) map[string]string {
|
||||
autoUpdateLabel := "io.containers.autoupdate"
|
||||
annotations := make(map[string]string)
|
||||
|
||||
@ -1394,7 +1383,7 @@ func getAutoUpdateAnnotations(ctrName string, ctrLabels map[string]string, useLo
|
||||
// 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] = truncateKubeAnnotation(v, useLongAnnotations)
|
||||
annotations[kc] = v
|
||||
}
|
||||
}
|
||||
|
||||
|
124
pkg/annotations/validate.go
Normal file
124
pkg/annotations/validate.go
Normal file
@ -0,0 +1,124 @@
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/podman/v5/libpod/define"
|
||||
)
|
||||
|
||||
// regexErrorMsg returns a string explanation of a regex validation failure.
|
||||
func regexErrorMsg(msg string, fmt string, examples ...string) string {
|
||||
if len(examples) == 0 {
|
||||
return msg + " (regex used for validation is '" + fmt + "')"
|
||||
}
|
||||
msg += " (e.g. "
|
||||
for i := range examples {
|
||||
if i > 0 {
|
||||
msg += " or "
|
||||
}
|
||||
msg += "'" + examples[i] + "', "
|
||||
}
|
||||
msg += "regex used for validation is '" + fmt + "')"
|
||||
return msg
|
||||
}
|
||||
|
||||
const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
|
||||
const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
|
||||
const dns1123SubdomainErrorMsg string = "annotations must be formatted as a valid lowercase RFC1123 subdomain of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
|
||||
|
||||
// DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123)
|
||||
const DNS1123SubdomainMaxLength int = 253
|
||||
|
||||
var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
|
||||
|
||||
// isDNS1123Subdomain tests for a string that conforms to the definition of a
|
||||
// subdomain in DNS (RFC 1123).
|
||||
func isDNS1123Subdomain(value string) error {
|
||||
if len(value) > DNS1123SubdomainMaxLength {
|
||||
return fmt.Errorf("prefix part must be no more than %d characters", DNS1123SubdomainMaxLength)
|
||||
}
|
||||
|
||||
if !dns1123SubdomainRegexp.MatchString(value) {
|
||||
return fmt.Errorf(regexErrorMsg(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const qnameCharFmt string = "[A-Za-z0-9]"
|
||||
const qnameExtCharFmt string = "[-A-Za-z0-9_.]"
|
||||
const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
|
||||
const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
|
||||
const qualifiedNameMaxLength int = 63
|
||||
|
||||
var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")
|
||||
|
||||
// isQualifiedName tests whether the value passed is what Kubernetes calls a
|
||||
// "qualified name". This is a format used in various places throughout the
|
||||
// system. If the value is not valid, a list of error strings is returned.
|
||||
// Otherwise an empty list (or nil) is returned.
|
||||
func isQualifiedName(value string) error {
|
||||
parts := strings.Split(value, "/")
|
||||
var name string
|
||||
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
name = parts[0]
|
||||
case 2:
|
||||
var prefix string
|
||||
prefix, name = parts[0], parts[1]
|
||||
if len(prefix) == 0 {
|
||||
return fmt.Errorf("prefix part of %s must be non-empty", value)
|
||||
} else if err := isDNS1123Subdomain(prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("a qualified name of %s "+
|
||||
regexErrorMsg(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+
|
||||
" with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')", value)
|
||||
}
|
||||
|
||||
if len(name) == 0 {
|
||||
return fmt.Errorf("name part of %s must be non-empty", value)
|
||||
} else if len(name) > qualifiedNameMaxLength {
|
||||
return fmt.Errorf("name part of %s must be no more than %d characters", value, qualifiedNameMaxLength)
|
||||
}
|
||||
|
||||
if !qualifiedNameRegexp.MatchString(name) {
|
||||
return fmt.Errorf("name part of %s "+
|
||||
regexErrorMsg(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc"), value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAnnotationsSize(annotations map[string]string) error {
|
||||
var totalSize int64
|
||||
for k, v := range annotations {
|
||||
totalSize += (int64)(len(k)) + (int64)(len(v))
|
||||
}
|
||||
if totalSize > (int64)(define.TotalAnnotationSizeLimitB) {
|
||||
return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, define.TotalAnnotationSizeLimitB)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAnnotations validates that a set of annotations are correctly
|
||||
// defined.
|
||||
func ValidateAnnotations(annotations map[string]string) error {
|
||||
for k := range annotations {
|
||||
// The rule is QualifiedName except that case doesn't matter,
|
||||
// so convert to lowercase before checking.
|
||||
if err := isQualifiedName(strings.ToLower(k)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateAnnotationsSize(annotations); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
80
pkg/annotations/validate_test.go
Normal file
80
pkg/annotations/validate_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/podman/v5/libpod/define"
|
||||
)
|
||||
|
||||
func TestValidateAnnotations(t *testing.T) {
|
||||
successCases := []map[string]string{
|
||||
{"simple": "bar"},
|
||||
{"now-with-dashes": "bar"},
|
||||
{"1-starts-with-num": "bar"},
|
||||
{"1234": "bar"},
|
||||
{"simple/simple": "bar"},
|
||||
{"now-with-dashes/simple": "bar"},
|
||||
{"now-with-dashes/now-with-dashes": "bar"},
|
||||
{"now.with.dots/simple": "bar"},
|
||||
{"now-with.dashes-and.dots/simple": "bar"},
|
||||
{"1-num.2-num/3-num": "bar"},
|
||||
{"1234/5678": "bar"},
|
||||
{"1.2.3.4/5678": "bar"},
|
||||
{"UpperCase123": "bar"},
|
||||
{"a": strings.Repeat("b", define.TotalAnnotationSizeLimitB-1)},
|
||||
{
|
||||
"a": strings.Repeat("b", define.TotalAnnotationSizeLimitB/2-1),
|
||||
"c": strings.Repeat("d", define.TotalAnnotationSizeLimitB/2-1),
|
||||
},
|
||||
}
|
||||
|
||||
for i := range successCases {
|
||||
if err := ValidateAnnotations(successCases[i]); err != nil {
|
||||
t.Errorf("case[%d] expected success, got %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
nameErrorCases := []map[string]string{
|
||||
{"nospecialchars^=@": "bar"},
|
||||
{"cantendwithadash-": "bar"},
|
||||
{"only/one/slash": "bar"},
|
||||
{strings.Repeat("a", 254): "bar"},
|
||||
}
|
||||
|
||||
for i := range nameErrorCases {
|
||||
if err := ValidateAnnotations(nameErrorCases[i]); err == nil {
|
||||
t.Errorf("case[%d]: expected failure", i)
|
||||
}
|
||||
}
|
||||
|
||||
totalSizeErrorCases := []map[string]string{
|
||||
{"a": strings.Repeat("b", define.TotalAnnotationSizeLimitB)},
|
||||
{
|
||||
"a": strings.Repeat("b", define.TotalAnnotationSizeLimitB/2),
|
||||
"c": strings.Repeat("d", define.TotalAnnotationSizeLimitB/2),
|
||||
},
|
||||
}
|
||||
|
||||
for i := range totalSizeErrorCases {
|
||||
if err := ValidateAnnotations(totalSizeErrorCases[i]); err == nil {
|
||||
t.Errorf("case[%d] expected failure", i)
|
||||
}
|
||||
}
|
||||
}
|
@ -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, options.PodmanOnly)
|
||||
po, err := libpod.GenerateForKube(ctx, ctrs, options.Service, options.PodmanOnly)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -283,7 +283,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, options.PodmanOnly)
|
||||
po, sp, err := p.GenerateForKube(ctx, options.Service, options.PodmanOnly)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/containers/podman/v5/cmd/podman/parse"
|
||||
"github.com/containers/podman/v5/libpod"
|
||||
"github.com/containers/podman/v5/libpod/define"
|
||||
"github.com/containers/podman/v5/pkg/annotations"
|
||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||
entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types"
|
||||
v1apps "github.com/containers/podman/v5/pkg/k8s.io/api/apps/v1"
|
||||
@ -289,11 +290,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options
|
||||
|
||||
podTemplateSpec.ObjectMeta = podYAML.ObjectMeta
|
||||
podTemplateSpec.Spec = podYAML.Spec
|
||||
for name, val := range podYAML.Annotations {
|
||||
if len(val) > define.MaxKubeAnnotation && !options.UseLongAnnotations {
|
||||
return nil, fmt.Errorf("annotation %q=%q value length exceeds Kubernetes max %d", name, val, define.MaxKubeAnnotation)
|
||||
}
|
||||
}
|
||||
|
||||
for name, val := range options.Annotations {
|
||||
if podYAML.Annotations == nil {
|
||||
podYAML.Annotations = make(map[string]string)
|
||||
@ -301,6 +298,10 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options
|
||||
podYAML.Annotations[name] = val
|
||||
}
|
||||
|
||||
if err := annotations.ValidateAnnotations(podYAML.Annotations); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, proxies, err := ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options, &ipIndex, podYAML.Annotations, configMaps, serviceContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1562,117 +1562,6 @@ USER test1`
|
||||
Expect(pod.Spec.Hostname).To(Equal(""))
|
||||
})
|
||||
|
||||
It("--no-trunc on container with long annotation", func() {
|
||||
ctrName := "demo"
|
||||
vol1 := filepath.Join(podmanTest.TempDir, RandomString(99))
|
||||
err := os.MkdirAll(vol1, 0755)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
session := podmanTest.Podman([]string{"create", "-v", vol1 + ":/tmp/foo:Z", "--name", ctrName, CITEST_IMAGE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
|
||||
kube := podmanTest.Podman([]string{"kube", "generate", "--no-trunc", ctrName})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(ExitCleanly())
|
||||
|
||||
pod := new(v1.Pod)
|
||||
err = yaml.Unmarshal(kube.Out.Contents(), pod)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(pod.Annotations).To(HaveKeyWithValue(define.BindMountPrefix, vol1+":Z"))
|
||||
Expect(pod.Annotations).To(Not(HaveKeyWithValue(define.BindMountPrefix, vol1[:define.MaxKubeAnnotation])))
|
||||
})
|
||||
|
||||
It("on container with long annotation", func() {
|
||||
ctrName := "demo"
|
||||
vol1 := filepath.Join(podmanTest.TempDir, RandomString(99))
|
||||
err := os.MkdirAll(vol1, 0755)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
session := podmanTest.Podman([]string{"create", "-v", vol1 + ":/tmp/foo:Z", "--name", ctrName, CITEST_IMAGE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
|
||||
kube := podmanTest.Podman([]string{"kube", "generate", ctrName})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(Exit(0))
|
||||
if IsRemote() {
|
||||
Expect(kube.ErrorToString()).To(BeEmpty())
|
||||
} else {
|
||||
Expect(kube.ErrorToString()).To(ContainSubstring("Truncation Annotation:"))
|
||||
Expect(kube.ErrorToString()).To(ContainSubstring("Kubernetes only allows 63 characters"))
|
||||
}
|
||||
|
||||
pod := new(v1.Pod)
|
||||
err = yaml.Unmarshal(kube.Out.Contents(), pod)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(pod.Annotations).To(HaveKeyWithValue(define.BindMountPrefix, vol1[:define.MaxKubeAnnotation]))
|
||||
Expect(pod.Annotations).To(Not(HaveKeyWithValue(define.BindMountPrefix, vol1+":Z")))
|
||||
})
|
||||
|
||||
It("--no-trunc on pod with long annotation", func() {
|
||||
ctrName := "demoCtr"
|
||||
podName := "demoPod"
|
||||
vol1 := filepath.Join(podmanTest.TempDir, RandomString(99))
|
||||
err := os.MkdirAll(vol1, 0755)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
session := podmanTest.Podman([]string{"pod", "create", podName})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
|
||||
session = podmanTest.Podman([]string{"create", "-v", vol1 + ":/tmp/foo:Z", "--name", ctrName, "--pod", podName, CITEST_IMAGE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
|
||||
kube := podmanTest.Podman([]string{"kube", "generate", "--no-trunc", podName})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(ExitCleanly())
|
||||
|
||||
pod := new(v1.Pod)
|
||||
err = yaml.Unmarshal(kube.Out.Contents(), pod)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(pod.Annotations).To(HaveKeyWithValue(define.BindMountPrefix, vol1+":Z"))
|
||||
Expect(pod.Annotations).To(Not(HaveKeyWithValue(define.BindMountPrefix, vol1[:define.MaxKubeAnnotation])))
|
||||
})
|
||||
|
||||
It("on pod with long annotation", func() {
|
||||
ctrName := "demoCtr"
|
||||
podName := "demoPod"
|
||||
vol1 := filepath.Join(podmanTest.TempDir, RandomString(99))
|
||||
err := os.MkdirAll(vol1, 0755)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
session := podmanTest.Podman([]string{"pod", "create", podName})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
|
||||
session = podmanTest.Podman([]string{"create", "-v", vol1 + ":/tmp/foo:Z", "--name", ctrName, "--pod", podName, CITEST_IMAGE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
|
||||
kube := podmanTest.Podman([]string{"kube", "generate", podName})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(Exit(0))
|
||||
|
||||
if IsRemote() {
|
||||
Expect(kube.ErrorToString()).To(BeEmpty())
|
||||
} else {
|
||||
Expect(kube.ErrorToString()).To(ContainSubstring("Truncation Annotation:"))
|
||||
Expect(kube.ErrorToString()).To(ContainSubstring("Kubernetes only allows 63 characters"))
|
||||
}
|
||||
|
||||
pod := new(v1.Pod)
|
||||
err = yaml.Unmarshal(kube.Out.Contents(), pod)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(pod.Annotations).To(HaveKeyWithValue(define.BindMountPrefix, vol1[:define.MaxKubeAnnotation]))
|
||||
Expect(pod.Annotations).To(Not(HaveKeyWithValue(define.BindMountPrefix, vol1+":Z")))
|
||||
})
|
||||
|
||||
It("--podman-only on container with --volumes-from", func() {
|
||||
ctr1 := "ctr1"
|
||||
ctr2 := "ctr2"
|
||||
|
@ -5897,44 +5897,29 @@ spec:
|
||||
Expect(kube.ErrorToString()).To(ContainSubstring("since Network Namespace set to host: invalid argument"))
|
||||
})
|
||||
|
||||
It("test with --no-trunc", func() {
|
||||
ctrName := "demo"
|
||||
vol1 := filepath.Join(podmanTest.TempDir, RandomString(99))
|
||||
err := os.MkdirAll(vol1, 0755)
|
||||
It("test with annotation size beyond limits", func() {
|
||||
key := "name"
|
||||
val := RandomString(define.TotalAnnotationSizeLimitB - len(key) + 1)
|
||||
pod := getPod(withAnnotation(key, val))
|
||||
err := generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
session := podmanTest.Podman([]string{"run", "-v", vol1 + ":/tmp/foo:Z", "--name", ctrName, CITEST_IMAGE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
|
||||
file := filepath.Join(podmanTest.TempDir, ctrName+".yml")
|
||||
session = podmanTest.Podman([]string{"kube", "generate", "--no-trunc", "-f", file, ctrName})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
|
||||
session = podmanTest.Podman([]string{"kube", "play", "--no-trunc", file})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
kube := podmanTest.Podman([]string{"kube", "play", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(Exit(125))
|
||||
Expect(kube.ErrorToString()).To(ContainSubstring("annotations size " + strconv.Itoa(len(key+val)) + " is larger than limit " + strconv.Itoa(define.TotalAnnotationSizeLimitB)))
|
||||
})
|
||||
|
||||
It("test with long annotation", func() {
|
||||
ctrName := "demo"
|
||||
vol1 := filepath.Join(podmanTest.TempDir, RandomString(99))
|
||||
err := os.MkdirAll(vol1, 0755)
|
||||
It("test with annotation size within limits", func() {
|
||||
key := "name"
|
||||
val := RandomString(define.TotalAnnotationSizeLimitB - len(key))
|
||||
pod := getPod(withAnnotation(key, val))
|
||||
err := generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
session := podmanTest.Podman([]string{"run", "-v", vol1 + ":/tmp/foo:Z", "--name", ctrName, CITEST_IMAGE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
|
||||
file := filepath.Join(podmanTest.TempDir, ctrName+".yml")
|
||||
session = podmanTest.Podman([]string{"kube", "generate", "--no-trunc", "-f", file, ctrName})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(ExitCleanly())
|
||||
|
||||
session = podmanTest.Podman([]string{"kube", "play", file})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(125))
|
||||
kube := podmanTest.Podman([]string{"kube", "play", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(ExitCleanly())
|
||||
})
|
||||
|
||||
It("test pod with volumes-from annotation in yaml", func() {
|
||||
|
@ -455,32 +455,7 @@ _EOF
|
||||
run_podman pod rm -t 0 -f test_pod
|
||||
}
|
||||
|
||||
@test "podman play --annotation > Max" {
|
||||
TESTDIR=$PODMAN_TMPDIR/testdir
|
||||
RANDOMSTRING=$(random_string 65)
|
||||
mkdir -p $TESTDIR
|
||||
echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml
|
||||
run_podman 125 play kube --annotation "name=$RANDOMSTRING" $PODMAN_TMPDIR/test.yaml
|
||||
assert "$output" =~ "annotation exceeds maximum size, 63, of kubernetes annotation:" "Expected to fail with Length greater than 63"
|
||||
}
|
||||
|
||||
@test "podman play --no-trunc --annotation > Max" {
|
||||
TESTDIR=$PODMAN_TMPDIR/testdir
|
||||
RANDOMSTRING=$(random_string 65)
|
||||
mkdir -p $TESTDIR
|
||||
echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml
|
||||
run_podman play kube --no-trunc --annotation "name=$RANDOMSTRING" $PODMAN_TMPDIR/test.yaml
|
||||
}
|
||||
|
||||
@test "podman play Yaml with annotation > Max" {
|
||||
RANDOMSTRING=$(random_string 65)
|
||||
|
||||
_write_test_yaml "annotations=test: ${RANDOMSTRING}" command=id
|
||||
run_podman 125 play kube - < $PODMAN_TMPDIR/test.yaml
|
||||
assert "$output" =~ "annotation \"test\"=\"$RANDOMSTRING\" value length exceeds Kubernetes max 63" "Expected to fail with annotation length greater than 63"
|
||||
}
|
||||
|
||||
@test "podman play Yaml --no-trunc with annotation > Max" {
|
||||
@test "podman play Yaml deprecated --no-trunc annotation" {
|
||||
RANDOMSTRING=$(random_string 65)
|
||||
|
||||
_write_test_yaml "annotations=test: ${RANDOMSTRING}" command=id
|
||||
|
Reference in New Issue
Block a user