Merge branch 'main' into feature-1635-with-labels
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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": {},
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user