diff --git a/pkg/kubectl/autoscale.go b/pkg/kubectl/autoscale.go index dcbfadde84a..fbe826d07bf 100644 --- a/pkg/kubectl/autoscale.go +++ b/pkg/kubectl/autoscale.go @@ -18,98 +18,69 @@ package kubectl import ( "fmt" - "strconv" autoscalingv1 "k8s.io/api/autoscaling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) -type HorizontalPodAutoscalerV1 struct{} - -func (HorizontalPodAutoscalerV1) ParamNames() []GeneratorParam { - return []GeneratorParam{ - {"default-name", true}, - {"name", false}, - {"scaleRef-kind", false}, - {"scaleRef-name", false}, - {"scaleRef-apiVersion", false}, - {"min", false}, - {"max", true}, - {"cpu-percent", false}, - } +// HorizontalPodAutoscalerV1Generator supports stable generation of a horizontal pod autoscaler. +type HorizontalPodAutoscalerGeneratorV1 struct { + Name string + ScaleRefKind string + ScaleRefName string + ScaleRefApiVersion string + MinReplicas int32 + MaxReplicas int32 + CPUPercent int32 } -func (HorizontalPodAutoscalerV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) { - return generateHPA(genericParams) -} +// Ensure it supports the generator pattern that uses parameters specified during construction. +var _ StructuredGenerator = &HorizontalPodAutoscalerGeneratorV1{} -func generateHPA(genericParams map[string]interface{}) (runtime.Object, error) { - params := map[string]string{} - for key, value := range genericParams { - strVal, isString := value.(string) - if !isString { - return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key) - } - params[key] = strVal - } - - name, found := params["name"] - if !found || len(name) == 0 { - name, found = params["default-name"] - if !found || len(name) == 0 { - return nil, fmt.Errorf("'name' is a required parameter.") - } - } - minString, found := params["min"] - min := -1 - var err error - if found { - if min, err = strconv.Atoi(minString); err != nil { - return nil, err - } - } - maxString, found := params["max"] - if !found { - return nil, fmt.Errorf("'max' is a required parameter.") - } - max, err := strconv.Atoi(maxString) - if err != nil { +// StructuredGenerate outputs a horizontal pod autoscaler object using the configured fields. +func (s *HorizontalPodAutoscalerGeneratorV1) StructuredGenerate() (runtime.Object, error) { + if err := s.validate(); err != nil { return nil, err } - if min > max { - return nil, fmt.Errorf("'max' must be greater than or equal to 'min'.") - } - - cpuString, found := params["cpu-percent"] - cpu := -1 - if found { - if cpu, err = strconv.Atoi(cpuString); err != nil { - return nil, err - } - } - scaler := autoscalingv1.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: s.Name, }, Spec: autoscalingv1.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{ - Kind: params["scaleRef-kind"], - Name: params["scaleRef-name"], - APIVersion: params["scaleRef-apiVersion"], + Kind: s.ScaleRefKind, + Name: s.ScaleRefName, + APIVersion: s.ScaleRefApiVersion, }, - MaxReplicas: int32(max), + MaxReplicas: s.MaxReplicas, }, } - if min > 0 { - v := int32(min) + + if s.MinReplicas > 0 { + v := int32(s.MinReplicas) scaler.Spec.MinReplicas = &v } - if cpu >= 0 { - c := int32(cpu) + if s.CPUPercent >= 0 { + c := int32(s.CPUPercent) scaler.Spec.TargetCPUUtilizationPercentage = &c } + return &scaler, nil } + +// validate check if the caller has set the right fields. +func (s HorizontalPodAutoscalerGeneratorV1) validate() error { + if len(s.Name) == 0 { + return fmt.Errorf("name must be specified") + } + if s.MaxReplicas <= 0 { + return fmt.Errorf("'max' is a required parameter and must be greater than zero") + } + if s.MinReplicas > s.MaxReplicas { + return fmt.Errorf("'max' must be greater than or equal to 'min'") + } + return nil +} + diff --git a/pkg/kubectl/autoscale_test.go b/pkg/kubectl/autoscale_test.go index e87a7075c13..9d2a52811c2 100644 --- a/pkg/kubectl/autoscale_test.go +++ b/pkg/kubectl/autoscale_test.go @@ -26,22 +26,26 @@ import ( func TestHPAGenerate(t *testing.T) { tests := []struct { - name string - params map[string]interface{} - expected *autoscalingv1.HorizontalPodAutoscaler - expectErr bool + name string + HPAName string + scaleRefKind string + scaleRefName string + scaleRefApiVersion string + minReplicas int32 + maxReplicas int32 + CPUPercent int32 + expected *autoscalingv1.HorizontalPodAutoscaler + expectErr bool }{ { - name: "valid case", - params: map[string]interface{}{ - "name": "foo", - "min": "1", - "max": "10", - "cpu-percent": "80", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, + name: "valid case", + HPAName: "foo", + minReplicas: 1, + maxReplicas: 10, + CPUPercent: 80, + scaleRefKind: "kind", + scaleRefName: "name", + scaleRefApiVersion: "apiVersion", expected: &autoscalingv1.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -60,79 +64,53 @@ func TestHPAGenerate(t *testing.T) { expectErr: false, }, { - name: "'name' is a required parameter", - params: map[string]interface{}{ - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, + name: "'name' is a required parameter", + scaleRefKind: "kind", + scaleRefName: "name", + scaleRefApiVersion: "apiVersion", + expectErr: true, }, { - name: "'max' is a required parameter", - params: map[string]interface{}{ - "default-name": "foo", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, + name: "'max' is a required parameter", + HPAName: "foo", + scaleRefKind: "kind", + scaleRefName: "name", + scaleRefApiVersion: "apiVersion", + expectErr: true, }, { - name: "'max' must be greater than or equal to 'min'", - params: map[string]interface{}{ - "name": "foo", - "min": "10", - "max": "1", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, + name: "'max' must be greater than or equal to 'min'", + HPAName: "foo", + minReplicas: 10, + maxReplicas: 1, + scaleRefKind: "kind", + scaleRefName: "name", + scaleRefApiVersion: "apiVersion", + expectErr: true, }, { - name: "cpu-percent must be an integer if specified", - params: map[string]interface{}{ - "name": "foo", - "min": "1", - "max": "10", - "cpu-percent": "", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, - }, - { - name: "'min' must be an integer if specified", - params: map[string]interface{}{ - "name": "foo", - "min": "foo", - "max": "10", - "cpu-percent": "60", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, - }, - { - name: "'max' must be an integer if specified", - params: map[string]interface{}{ - "name": "foo", - "min": "1", - "max": "bar", - "cpu-percent": "90", - "scaleRef-kind": "kind", - "scaleRef-name": "name", - "scaleRef-apiVersion": "apiVersion", - }, - expectErr: true, + name: "'max' must be greater than zero", + HPAName: "foo", + minReplicas: 1, + maxReplicas: -10, + scaleRefKind: "kind", + scaleRefName: "name", + scaleRefApiVersion: "apiVersion", + expectErr: true, }, } - generator := HorizontalPodAutoscalerV1{} + for _, test := range tests { - obj, err := generator.Generate(test.params) + generator := HorizontalPodAutoscalerGeneratorV1{ + Name: test.HPAName, + ScaleRefKind: test.scaleRefKind, + ScaleRefName: test.scaleRefName, + ScaleRefApiVersion: test.scaleRefApiVersion, + MinReplicas: test.minReplicas, + MaxReplicas: test.maxReplicas, + CPUPercent: test.CPUPercent, + } + obj, err := generator.StructuredGenerate() if test.expectErr && err != nil { continue }