Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 31 additions & 12 deletions upup/pkg/fi/cloudup/new_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,19 +285,16 @@ func NewCluster(opt *NewClusterOptions, clientset simple.Clientset) (*NewCluster
}

for _, featureGate := range opt.KubernetesFeatureGates {
enabled := true
if featureGate[0] == '+' {
featureGate = featureGate[1:]
}
if featureGate[0] == '-' {
enabled = false
featureGate = featureGate[1:]
featureGate, enabled, err := parseKubernetesFeatureGate(featureGate)
if err != nil {
return nil, err
}
cluster.Spec.Kubelet.FeatureGates[featureGate] = strconv.FormatBool(enabled)
cluster.Spec.KubeAPIServer.FeatureGates[featureGate] = strconv.FormatBool(enabled)
cluster.Spec.KubeControllerManager.FeatureGates[featureGate] = strconv.FormatBool(enabled)
cluster.Spec.KubeProxy.FeatureGates[featureGate] = strconv.FormatBool(enabled)
cluster.Spec.KubeScheduler.FeatureGates[featureGate] = strconv.FormatBool(enabled)
value := strconv.FormatBool(enabled)
cluster.Spec.Kubelet.FeatureGates[featureGate] = value
cluster.Spec.KubeAPIServer.FeatureGates[featureGate] = value
cluster.Spec.KubeControllerManager.FeatureGates[featureGate] = value
cluster.Spec.KubeProxy.FeatureGates[featureGate] = value
cluster.Spec.KubeScheduler.FeatureGates[featureGate] = value
}
}

Expand Down Expand Up @@ -599,6 +596,28 @@ func NewCluster(opt *NewClusterOptions, clientset simple.Clientset) (*NewCluster
return &result, nil
}

func parseKubernetesFeatureGate(featureGate string) (string, bool, error) {
featureGate = strings.TrimSpace(featureGate)
if featureGate == "" {
return "", false, fmt.Errorf("kubernetes feature gate must not be empty")
}

enabled := true
switch featureGate[0] {
case '+':
featureGate = strings.TrimSpace(featureGate[1:])
case '-':
enabled = false
featureGate = strings.TrimSpace(featureGate[1:])
}

if featureGate == "" {
return "", false, fmt.Errorf("kubernetes feature gate must include a feature name")
}

return featureGate, enabled, nil
}

func setupVPC(opt *NewClusterOptions, cluster *api.Cluster, cloud fi.Cloud) error {
ctx := context.TODO()
cluster.Spec.Networking.NetworkID = opt.NetworkID
Expand Down
62 changes: 62 additions & 0 deletions upup/pkg/fi/cloudup/new_cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"strings"
"testing"

"k8s.io/kops/pkg/client/simple/vfsclientset"
"k8s.io/kops/util/pkg/vfs"
"sigs.k8s.io/yaml"

Expand Down Expand Up @@ -434,6 +435,67 @@ func TestSetupTopology(t *testing.T) {
}
}

func TestNewClusterValidatesKubernetesFeatureGates(t *testing.T) {
vfsContext := vfs.NewTestingVFSContext()
basePath, err := vfsContext.BuildVfsPath("memfs://tests")
if err != nil {
t.Fatalf("error building test state store: %v", err)
}
clientset := vfsclientset.NewVFSClientset(vfsContext, basePath)

tests := []struct {
name string
gates []string
wantErr string
}{
{
name: "should reject empty feature gate if entry is blank",
gates: []string{""},
wantErr: "must not be empty",
},
{
name: "should reject feature gate if entry is sign only plus",
gates: []string{"+"},
wantErr: "must include a feature name",
},
{
name: "should reject feature gate if entry is sign only minus",
gates: []string{"-"},
wantErr: "must include a feature name",
},
{
name: "should continue past feature gate validation if entry is valid",
gates: []string{"+ReadWriteOncePod"},
wantErr: "must specify at least one zone",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
opt := &NewClusterOptions{
ClusterName: "test.example.com",
Channel: "file://tests/channels/channel.yaml",
KubernetesVersion: "v1.32.0",
KubernetesFeatureGates: test.gates,
}

defer func() {
if r := recover(); r != nil {
t.Fatalf("NewCluster panicked: %v", r)
}
}()

_, err := NewCluster(opt, clientset)
if err == nil {
t.Fatalf("expected error containing %q", test.wantErr)
}
if !strings.Contains(err.Error(), test.wantErr) {
t.Fatalf("unexpected error %q, expected to contain %q", err, test.wantErr)
}
})
}
}

func TestDefaultImage(t *testing.T) {
tests := []struct {
cluster *api.Cluster
Expand Down