From 5468757ad7b45c7fa07ff0a586e7a96d8a6ec428 Mon Sep 17 00:00:00 2001 From: Yaron Dayagi Date: Wed, 2 Feb 2022 21:02:22 +0200 Subject: play kube envVar.valueFrom.resourceFieldRef add support for env vars values from container resources see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvarsource-v1-core relates to issue https://github.com/containers/podman/issues/12756 Signed-off-by: Yaron Dayagi --- pkg/specgen/generate/kube/kube.go | 69 +++++++++ pkg/specgen/generate/kube/play_test.go | 272 ++++++++++++++++++++++++--------- 2 files changed, 266 insertions(+), 75 deletions(-) (limited to 'pkg') diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index beaec1135..475401016 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -4,8 +4,10 @@ import ( "context" "encoding/json" "fmt" + "math" "net" "regexp" + "strconv" "strings" "time" @@ -650,6 +652,10 @@ func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (*string, error) { if env.ValueFrom.FieldRef != nil { return envVarValueFieldRef(env, opts) } + + if env.ValueFrom.ResourceFieldRef != nil { + return envVarValueResourceFieldRef(env, opts) + } } return &env.Value, nil @@ -688,6 +694,69 @@ func envVarValueFieldRef(env v1.EnvVar, opts *CtrSpecGenOptions) (*string, error ) } +func envVarValueResourceFieldRef(env v1.EnvVar, opts *CtrSpecGenOptions) (*string, error) { + divisor := env.ValueFrom.ResourceFieldRef.Divisor + if divisor.IsZero() { // divisor not set, use default + divisor.Set(1) + } + + var value *resource.Quantity + resources := opts.Container.Resources + resourceName := env.ValueFrom.ResourceFieldRef.Resource + var isValidDivisor bool + + switch resourceName { + case "limits.memory": + value = resources.Limits.Memory() + isValidDivisor = isMemoryDivisor(divisor) + case "limits.cpu": + value = resources.Limits.Cpu() + isValidDivisor = isCPUDivisor(divisor) + case "requests.memory": + value = resources.Requests.Memory() + isValidDivisor = isMemoryDivisor(divisor) + case "requests.cpu": + value = resources.Requests.Cpu() + isValidDivisor = isCPUDivisor(divisor) + default: + return nil, errors.Errorf( + "Can not set env %v. Reason: resource %v is either not valid or not supported", + env.Name, resourceName, + ) + } + + if !isValidDivisor { + return nil, errors.Errorf( + "Can not set env %s. Reason: divisor value %s is not valid", + env.Name, divisor.String(), + ) + } + + // k8s rounds up the result to the nearest integer + intValue := int(math.Ceil(value.AsApproximateFloat64() / divisor.AsApproximateFloat64())) + stringValue := strconv.Itoa(intValue) + + return &stringValue, nil +} + +func isMemoryDivisor(divisor resource.Quantity) bool { + switch divisor.String() { + case "1", "1k", "1M", "1G", "1T", "1P", "1E", "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei": + return true + default: + return false + } +} + +func isCPUDivisor(divisor resource.Quantity) bool { + switch divisor.String() { + case "1", "1m": + return true + default: + return false + } +} + // getPodPorts converts a slice of kube container descriptions to an // array of portmapping func getPodPorts(containers []v1.Container) []types.PortMapping { diff --git a/pkg/specgen/generate/kube/play_test.go b/pkg/specgen/generate/kube/play_test.go index 2ac268c79..282324310 100644 --- a/pkg/specgen/generate/kube/play_test.go +++ b/pkg/specgen/generate/kube/play_test.go @@ -2,13 +2,17 @@ package kube import ( "encoding/json" + "fmt" "io/ioutil" + "math" "os" + "strconv" "testing" "github.com/containers/common/pkg/secrets" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" v12 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -189,15 +193,13 @@ func TestEnvVarValue(t *testing.T) { assert.NoError(t, err) defer os.RemoveAll(d) secretsManager := createSecrets(t, d) - value := "foo" - emptyValue := "" tests := []struct { name string envVar v1.EnvVar options CtrSpecGenOptions succeed bool - expected *string + expected string }{ { "ConfigMapExists", @@ -216,7 +218,7 @@ func TestEnvVarValue(t *testing.T) { ConfigMaps: configMapList, }, true, - &value, + "foo", }, { "ContainerKeyDoesNotExistInConfigMap", @@ -235,7 +237,7 @@ func TestEnvVarValue(t *testing.T) { ConfigMaps: configMapList, }, false, - nil, + nilString, }, { "OptionalContainerKeyDoesNotExistInConfigMap", @@ -255,7 +257,7 @@ func TestEnvVarValue(t *testing.T) { ConfigMaps: configMapList, }, true, - nil, + nilString, }, { "ConfigMapDoesNotExist", @@ -274,7 +276,7 @@ func TestEnvVarValue(t *testing.T) { ConfigMaps: configMapList, }, false, - nil, + nilString, }, { "OptionalConfigMapDoesNotExist", @@ -294,7 +296,7 @@ func TestEnvVarValue(t *testing.T) { ConfigMaps: configMapList, }, true, - nil, + nilString, }, { "EmptyConfigMapList", @@ -313,7 +315,7 @@ func TestEnvVarValue(t *testing.T) { ConfigMaps: []v1.ConfigMap{}, }, false, - nil, + nilString, }, { "OptionalEmptyConfigMapList", @@ -333,7 +335,7 @@ func TestEnvVarValue(t *testing.T) { ConfigMaps: []v1.ConfigMap{}, }, true, - nil, + nilString, }, { "SecretExists", @@ -352,7 +354,7 @@ func TestEnvVarValue(t *testing.T) { SecretsManager: secretsManager, }, true, - &value, + "foo", }, { "ContainerKeyDoesNotExistInSecret", @@ -371,7 +373,7 @@ func TestEnvVarValue(t *testing.T) { SecretsManager: secretsManager, }, false, - nil, + nilString, }, { "OptionalContainerKeyDoesNotExistInSecret", @@ -391,7 +393,7 @@ func TestEnvVarValue(t *testing.T) { SecretsManager: secretsManager, }, true, - nil, + nilString, }, { "SecretDoesNotExist", @@ -410,7 +412,7 @@ func TestEnvVarValue(t *testing.T) { SecretsManager: secretsManager, }, false, - nil, + nilString, }, { "OptionalSecretDoesNotExist", @@ -430,7 +432,7 @@ func TestEnvVarValue(t *testing.T) { SecretsManager: secretsManager, }, true, - nil, + nilString, }, { "FieldRefMetadataName", @@ -443,10 +445,10 @@ func TestEnvVarValue(t *testing.T) { }, }, CtrSpecGenOptions{ - PodName: value, + PodName: "test", }, true, - &value, + "test", }, { "FieldRefMetadataUID", @@ -459,10 +461,10 @@ func TestEnvVarValue(t *testing.T) { }, }, CtrSpecGenOptions{ - PodID: value, + PodID: "ec71ff37c67b688598c0008187ab0960dc34e1dfdcbf3a74e3d778bafcfe0977", }, true, - &value, + "ec71ff37c67b688598c0008187ab0960dc34e1dfdcbf3a74e3d778bafcfe0977", }, { "FieldRefMetadataLabelsExist", @@ -475,10 +477,10 @@ func TestEnvVarValue(t *testing.T) { }, }, CtrSpecGenOptions{ - Labels: map[string]string{"label": value}, + Labels: map[string]string{"label": "label"}, }, true, - &value, + "label", }, { "FieldRefMetadataLabelsEmpty", @@ -494,7 +496,7 @@ func TestEnvVarValue(t *testing.T) { Labels: map[string]string{"label": ""}, }, true, - &emptyValue, + "", }, { "FieldRefMetadataLabelsNotExist", @@ -508,7 +510,7 @@ func TestEnvVarValue(t *testing.T) { }, CtrSpecGenOptions{}, true, - &emptyValue, + "", }, { "FieldRefMetadataAnnotationsExist", @@ -521,10 +523,10 @@ func TestEnvVarValue(t *testing.T) { }, }, CtrSpecGenOptions{ - Annotations: map[string]string{"annotation": value}, + Annotations: map[string]string{"annotation": "annotation"}, }, true, - &value, + "annotation", }, { "FieldRefMetadataAnnotationsEmpty", @@ -540,7 +542,7 @@ func TestEnvVarValue(t *testing.T) { Annotations: map[string]string{"annotation": ""}, }, true, - &emptyValue, + "", }, { "FieldRefMetadataAnnotationsNotExist", @@ -554,7 +556,7 @@ func TestEnvVarValue(t *testing.T) { }, CtrSpecGenOptions{}, true, - &emptyValue, + "", }, { "FieldRefInvalid1", @@ -568,7 +570,7 @@ func TestEnvVarValue(t *testing.T) { }, CtrSpecGenOptions{}, false, - nil, + nilString, }, { "FieldRefInvalid2", @@ -582,7 +584,7 @@ func TestEnvVarValue(t *testing.T) { }, CtrSpecGenOptions{}, false, - nil, + nilString, }, { "FieldRefNotSupported", @@ -596,7 +598,101 @@ func TestEnvVarValue(t *testing.T) { }, CtrSpecGenOptions{}, false, - nil, + nilString, + }, + { + "ResourceFieldRefNotSupported", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ResourceFieldRef: &v1.ResourceFieldSelector{ + Resource: "limits.dummy", + }, + }, + }, + CtrSpecGenOptions{}, + false, + nilString, + }, + { + "ResourceFieldRefMemoryDivisorNotValid", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ResourceFieldRef: &v1.ResourceFieldSelector{ + Resource: "limits.memory", + Divisor: resource.MustParse("2M"), + }, + }, + }, + CtrSpecGenOptions{}, + false, + nilString, + }, + { + "ResourceFieldRefCpuDivisorNotValid", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ResourceFieldRef: &v1.ResourceFieldSelector{ + Resource: "limits.cpu", + Divisor: resource.MustParse("2m"), + }, + }, + }, + CtrSpecGenOptions{}, + false, + nilString, + }, + { + "ResourceFieldRefNoDivisor", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ResourceFieldRef: &v1.ResourceFieldSelector{ + Resource: "limits.memory", + }, + }, + }, + CtrSpecGenOptions{ + Container: container, + }, + true, + memoryString, + }, + { + "ResourceFieldRefMemoryDivisor", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ResourceFieldRef: &v1.ResourceFieldSelector{ + Resource: "limits.memory", + Divisor: resource.MustParse("1Mi"), + }, + }, + }, + CtrSpecGenOptions{ + Container: container, + }, + true, + strconv.Itoa(int(math.Ceil(float64(memoryInt) / 1024 / 1024))), + }, + { + "ResourceFieldRefCpuDivisor", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ResourceFieldRef: &v1.ResourceFieldSelector{ + Resource: "requests.cpu", + Divisor: resource.MustParse("1m"), + }, + }, + }, + CtrSpecGenOptions{ + Container: container, + }, + true, + strconv.Itoa(int(float64(cpuInt) / 0.001)), }, } @@ -605,59 +701,85 @@ func TestEnvVarValue(t *testing.T) { t.Run(test.name, func(t *testing.T) { result, err := envVarValue(test.envVar, &test.options) assert.Equal(t, err == nil, test.succeed) - assert.Equal(t, test.expected, result) + if test.expected == nilString { + assert.Nil(t, result) + } else { + fmt.Println(*result, test.expected) + assert.Equal(t, &(test.expected), result) + } }) } } -var configMapList = []v1.ConfigMap{ - { - TypeMeta: v12.TypeMeta{ - Kind: "ConfigMap", - }, - ObjectMeta: v12.ObjectMeta{ - Name: "bar", - }, - Data: map[string]string{ - "myvar": "bar", - }, - }, - { - TypeMeta: v12.TypeMeta{ - Kind: "ConfigMap", - }, - ObjectMeta: v12.ObjectMeta{ - Name: "foo", +var ( + nilString = "" + configMapList = []v1.ConfigMap{ + { + TypeMeta: v12.TypeMeta{ + Kind: "ConfigMap", + }, + ObjectMeta: v12.ObjectMeta{ + Name: "bar", + }, + Data: map[string]string{ + "myvar": "bar", + }, }, - Data: map[string]string{ - "myvar": "foo", + { + TypeMeta: v12.TypeMeta{ + Kind: "ConfigMap", + }, + ObjectMeta: v12.ObjectMeta{ + Name: "foo", + }, + Data: map[string]string{ + "myvar": "foo", + }, }, - }, -} + } -var optional = true + optional = true -var k8sSecrets = []v1.Secret{ - { - TypeMeta: v12.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: v12.ObjectMeta{ - Name: "bar", - }, - Data: map[string][]byte{ - "myvar": []byte("bar"), - }, - }, - { - TypeMeta: v12.TypeMeta{ - Kind: "Secret", + k8sSecrets = []v1.Secret{ + { + TypeMeta: v12.TypeMeta{ + Kind: "Secret", + }, + ObjectMeta: v12.ObjectMeta{ + Name: "bar", + }, + Data: map[string][]byte{ + "myvar": []byte("bar"), + }, }, - ObjectMeta: v12.ObjectMeta{ - Name: "foo", + { + TypeMeta: v12.TypeMeta{ + Kind: "Secret", + }, + ObjectMeta: v12.ObjectMeta{ + Name: "foo", + }, + Data: map[string][]byte{ + "myvar": []byte("foo"), + }, }, - Data: map[string][]byte{ - "myvar": []byte("foo"), + } + + cpuInt = 4 + cpuString = strconv.Itoa(cpuInt) + memoryInt = 30000000 + memoryString = strconv.Itoa(memoryInt) + container = v1.Container{ + Name: "test", + Resources: v1.ResourceRequirements{ + Limits: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse(cpuString), + v1.ResourceMemory: resource.MustParse(memoryString), + }, + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse(cpuString), + v1.ResourceMemory: resource.MustParse(memoryString), + }, }, - }, -} + } +) -- cgit v1.2.3-54-g00ecf