Merge branch 'main' into feature-1635-with-labels

This commit is contained in:
jose luis
2024-04-22 13:47:24 +02:00
18 changed files with 1664 additions and 33 deletions
+2 -2
View File
@@ -21,7 +21,7 @@ import (
"strconv"
"time"
"github.com/compose-spec/compose-go/types"
"github.com/compose-spec/compose-go/v2/types"
deployapi "github.com/openshift/api/apps/v1"
"github.com/pkg/errors"
"github.com/spf13/cast"
@@ -251,7 +251,7 @@ func (s *ServiceConfig) GetConfigMapKeyFromMeta(name string) (string, error) {
}
config := s.ConfigsMetaData[name]
if config.External.External {
if config.External {
return "", errors.Errorf("config %s is external", name)
}
+12 -4
View File
@@ -17,6 +17,7 @@ limitations under the License.
package compose
import (
"context"
"fmt"
"os"
"reflect"
@@ -24,8 +25,8 @@ import (
"strings"
"time"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
"github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/types"
"github.com/fatih/structs"
"github.com/google/shlex"
"github.com/kubernetes/kompose/pkg/kobject"
@@ -167,7 +168,7 @@ func (c *Compose) LoadFile(files []string, profiles []string) (kobject.KomposeOb
return kobject.KomposeObject{}, errors.Wrap(err, "Unable to create compose options")
}
project, err := cli.ProjectFromOptions(projectOptions)
project, err := cli.ProjectFromOptions(context.Background(), projectOptions)
if err != nil {
return kobject.KomposeObject{}, errors.Wrap(err, "Unable to load files")
}
@@ -566,7 +567,7 @@ func dockerComposeToKomposeMapping(composeObject *types.Project) (kobject.Kompos
parseEnvironment(&composeServiceConfig, &serviceConfig)
// Get env_file
serviceConfig.EnvFile = composeServiceConfig.EnvFile
parseEnvFiles(&composeServiceConfig, &serviceConfig)
// Parse the ports
// v3 uses a new format called "long syntax" starting in 3.2
@@ -695,6 +696,13 @@ func parseEnvironment(composeServiceConfig *types.ServiceConfig, serviceConfig *
}
}
func parseEnvFiles(composeServiceConfig *types.ServiceConfig, serviceConfig *kobject.ServiceConfig) {
for _, value := range composeServiceConfig.EnvFiles {
serviceConfig.EnvFile = append(serviceConfig.EnvFile, value.Path)
// value.Required is ignored
}
}
func handleCronJobConcurrencyPolicy(policy string) (batchv1.ConcurrencyPolicy, error) {
switch policy {
case "Allow":
+50 -4
View File
@@ -24,7 +24,7 @@ import (
"testing"
"time"
"github.com/compose-spec/compose-go/types"
"github.com/compose-spec/compose-go/v2/types"
"github.com/google/go-cmp/cmp"
"github.com/kubernetes/kompose/pkg/kobject"
"github.com/pkg/errors"
@@ -429,6 +429,52 @@ func TestLoadEnvVar(t *testing.T) {
}
}
func TestParseEnvFiles(t *testing.T) {
tests := []struct {
service types.ServiceConfig
want []string
}{
{service: types.ServiceConfig{
Name: "baz",
Image: "foo/baz",
EnvFiles: []types.EnvFile{
{
Path: "",
Required: false,
},
{
Path: "foo",
Required: false,
},
{
Path: "bar",
Required: true,
},
},
},
want: []string{"", "foo", "bar"},
},
{
service: types.ServiceConfig{
Name: "baz",
Image: "foo/baz",
EnvFiles: []types.EnvFile{},
},
want: []string{},
},
}
for _, tt := range tests {
sc := kobject.ServiceConfig{
EnvFile: []string{},
}
parseEnvFiles(&tt.service, &sc)
if !reflect.DeepEqual(sc.EnvFile, tt.want) {
t.Errorf("Expected %q, got %q", tt.want, sc.EnvFile)
}
}
}
// TestUnsupportedKeys test checkUnsupportedKey function with various
// docker-compose projects
func TestUnsupportedKeys(t *testing.T) {
@@ -441,7 +487,7 @@ func TestUnsupportedKeys(t *testing.T) {
},
},
Services: types.Services{
types.ServiceConfig{
"foo": types.ServiceConfig{
Name: "foo",
Image: "foo/bar",
Build: &types.BuildConfig{
@@ -453,7 +499,7 @@ func TestUnsupportedKeys(t *testing.T) {
"net1": {},
},
},
types.ServiceConfig{
"bar": types.ServiceConfig{
Name: "bar",
Image: "bar/foo",
Build: &types.BuildConfig{
@@ -476,7 +522,7 @@ func TestUnsupportedKeys(t *testing.T) {
projectWithDefaultNetwork := &types.Project{
Services: types.Services{
types.ServiceConfig{
"foo": types.ServiceConfig{
Networks: map[string]*types.ServiceNetworkConfig{
"default": {},
},
+8
View File
@@ -94,6 +94,14 @@ const (
LabelInitContainerImage = "kompose.init.containers.image"
// LabelInitContainerCommand defines commands
LabelInitContainerCommand = "kompose.init.containers.command"
// LabelHpaMinReplicas defines min pod replicas
LabelHpaMinReplicas = "kompose.hpa.replicas.min"
// LabelHpaMaxReplicas defines max pod replicas
LabelHpaMaxReplicas = "kompose.hpa.replicas.max"
// LabelHpaCpu defines scaling decisions based on CPU utilization
LabelHpaCPU = "kompose.hpa.cpu"
// LabelHpaMemory defines scaling decisions based on memory utilization
LabelHpaMemory = "kompose.hpa.memory"
// LabelNameOverride defines the override resource name
LabelNameOverride = "kompose.service.name_override"
)
+144 -1
View File
@@ -31,7 +31,7 @@ import (
"text/template"
"time"
"github.com/compose-spec/compose-go/types"
"github.com/compose-spec/compose-go/v2/types"
"github.com/joho/godotenv"
"github.com/kubernetes/kompose/pkg/kobject"
"github.com/kubernetes/kompose/pkg/loader/compose"
@@ -41,6 +41,7 @@ import (
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
appsv1 "k8s.io/api/apps/v1"
hpa "k8s.io/api/autoscaling/v2beta2"
api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -48,6 +49,29 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
// Default values for Horizontal Pod Autoscaler (HPA)
const (
DefaultMinReplicas = 1
DefaultMaxReplicas = 3
DefaultCPUUtilization = 50
DefaultMemoryUtilization = 70
)
// LabelKeys are the keys for HPA related labels in the service
var LabelKeys = []string{
compose.LabelHpaCPU,
compose.LabelHpaMemory,
compose.LabelHpaMinReplicas,
compose.LabelHpaMaxReplicas,
}
type HpaValues struct {
MinReplicas int32
MaxReplicas int32
CPUtilization int32
MemoryUtilization int32
}
/**
* Generate Helm Chart configuration
*/
@@ -1030,3 +1054,122 @@ func parseContainerCommandsFromStr(line string) []string {
}
return commands
}
// searchHPAValues is useful to check if labels
// contains any labels related to Horizontal Pod Autoscaler
func searchHPAValues(labels map[string]string) bool {
for _, value := range LabelKeys {
if _, ok := labels[value]; ok {
return true
}
}
return false
}
// createHPAResources creates a HorizontalPodAutoscaler (HPA) resource
// It sets the number of replicas in the service to 0 because
// the number of replicas will be managed by the HPA
func createHPAResources(name string, service *kobject.ServiceConfig) hpa.HorizontalPodAutoscaler {
valuesHpa := getResourceHpaValues(service)
service.Replicas = 0
metrics := getHpaMetricSpec(valuesHpa)
scalerSpecs := hpa.HorizontalPodAutoscaler{
TypeMeta: metav1.TypeMeta{
Kind: "HorizontalPodAutoscaler",
APIVersion: "autoscaling/v2",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: hpa.HorizontalPodAutoscalerSpec{
ScaleTargetRef: hpa.CrossVersionObjectReference{
Kind: "Deployment",
Name: name,
APIVersion: "apps/v1",
},
MinReplicas: &valuesHpa.MinReplicas,
MaxReplicas: valuesHpa.MaxReplicas,
Metrics: metrics,
},
}
return scalerSpecs
}
// getResourceHpaValues retrieves the min/max replicas and CPU/memory utilization values
// control if maxReplicas is less than minReplicas
func getResourceHpaValues(service *kobject.ServiceConfig) HpaValues {
minReplicas := getHpaValue(service, compose.LabelHpaMinReplicas, DefaultMinReplicas)
maxReplicas := getHpaValue(service, compose.LabelHpaMaxReplicas, DefaultMaxReplicas)
if maxReplicas < minReplicas {
log.Warnf("maxReplicas %d is less than minReplicas %d. Using minReplicas value %d", maxReplicas, minReplicas, minReplicas)
maxReplicas = minReplicas
}
cpuUtilization := validatePercentageMetric(service, compose.LabelHpaCPU, DefaultCPUUtilization)
memoryUtilization := validatePercentageMetric(service, compose.LabelHpaMemory, DefaultMemoryUtilization)
return HpaValues{
MinReplicas: minReplicas,
MaxReplicas: maxReplicas,
CPUtilization: cpuUtilization,
MemoryUtilization: memoryUtilization,
}
}
// validatePercentageMetric validates the CPU or memory metrics value
// ensuring that it falls within the acceptable range [1, 100].
func validatePercentageMetric(service *kobject.ServiceConfig, metricLabel string, defaultValue int32) int32 {
metricValue := getHpaValue(service, metricLabel, defaultValue)
if metricValue > 100 || metricValue < 1 {
log.Warnf("Metric value %d is not within the acceptable range [1, 100]. Using default value %d", metricValue, defaultValue)
return defaultValue
}
return metricValue
}
// getHpaValue convert the label value to integer
// If the label is not present or the conversion fails
// it returns the provided default value
func getHpaValue(service *kobject.ServiceConfig, label string, defaultValue int32) int32 {
valueFromLabel, err := strconv.Atoi(service.Labels[label])
if err != nil || valueFromLabel < 0 {
log.Warnf("Error converting label %s. Using default value %d", label, defaultValue)
return defaultValue
}
return int32(valueFromLabel)
}
// getHpaMetricSpec returns a list of metric specs for the HPA resource
// Target type is hardcoded to hpa.UtilizationMetricType
// Each MetricSpec specifies the type metric CPU/memory and average utilization value
// to trigger scaling
func getHpaMetricSpec(hpaValues HpaValues) []hpa.MetricSpec {
var metrics []hpa.MetricSpec
if hpaValues.CPUtilization > 0 {
metrics = append(metrics, hpa.MetricSpec{
Type: hpa.ResourceMetricSourceType,
Resource: &hpa.ResourceMetricSource{
Name: api.ResourceCPU,
Target: hpa.MetricTarget{
Type: hpa.UtilizationMetricType,
AverageUtilization: &hpaValues.CPUtilization,
},
},
})
}
if hpaValues.MemoryUtilization > 0 {
metrics = append(metrics, hpa.MetricSpec{
Type: hpa.ResourceMetricSourceType,
Resource: &hpa.ResourceMetricSource{
Name: api.ResourceMemory,
Target: hpa.MetricTarget{
Type: hpa.UtilizationMetricType,
AverageUtilization: &hpaValues.MemoryUtilization,
},
},
})
}
return metrics
}
File diff suppressed because it is too large Load Diff
+19 -2
View File
@@ -29,7 +29,7 @@ import (
"strconv"
"strings"
"github.com/compose-spec/compose-go/types"
"github.com/compose-spec/compose-go/v2/types"
"github.com/fatih/structs"
"github.com/kubernetes/kompose/pkg/kobject"
"github.com/kubernetes/kompose/pkg/loader/compose"
@@ -1314,7 +1314,7 @@ func (k *Kubernetes) createConfigMapFromComposeConfig(name string, service kobje
for _, config := range service.Configs {
currentConfigName := config.Source
currentConfigObj := service.ConfigsMetaData[currentConfigName]
if currentConfigObj.External.External {
if currentConfigObj.External {
continue
}
currentFileName := currentConfigObj.File
@@ -1654,6 +1654,10 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
return nil, err
}
}
err = k.configHorizontalPodScaler(name, service, opt, &objects)
if err != nil {
return nil, errors.Wrap(err, "Error creating Kubernetes HPA")
}
allobjects = append(allobjects, objects...)
}
@@ -1718,3 +1722,16 @@ func (k *Kubernetes) UpdateController(obj runtime.Object, updateTemplate func(*a
}
return nil
}
// configHorizontalPodScaler create Hpa resource also append to the objects
// first checks if the service labels contain any HPA labels using the searchHPAValues
func (k *Kubernetes) configHorizontalPodScaler(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object) (err error) {
found := searchHPAValues(service.Labels)
if !found {
return nil
}
hpa := createHPAResources(name, &service)
*objects = append(*objects, &hpa)
return nil
}
@@ -23,7 +23,7 @@ import (
"strings"
"testing"
"github.com/compose-spec/compose-go/types"
"github.com/compose-spec/compose-go/v2/types"
"github.com/kubernetes/kompose/pkg/kobject"
"github.com/kubernetes/kompose/pkg/loader/compose"
+1 -1
View File
@@ -121,7 +121,7 @@ func parseVolume(volume string) (name, host, container, mode string, err error)
return
}
// parseVolume parses window volume.
// parseWindowsVolume parses window volume.
// example: windows host mount to windows container
// volume = dataVolumeName:C:\Users\Data:D:\config:rw
// it can be parsed: