Skip to content

[backport v2.10] Refactors keyValueArgs handling for improved efficiency (#928) #931

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
54 changes: 30 additions & 24 deletions pkg/resources/provisioning.cattle.io/v1/cluster/mutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,22 @@ type keyValueArg struct {

type keyValueArgs []keyValueArg

// newKeyValueArgs initializes and returns an empty keyValueArgs slice.
func newKeyValueArgs() *keyValueArgs {
return &keyValueArgs{}
}

// parseFromRawArgs converts an interface representing a slice of "key=value" strings into a slice of keyValueArg.
func (kv *keyValueArgs) parseFromRawArgs(input interface{}) {
// parseFromRawArgs converts an interface representing a slice of "key=value" strings & returns a slice of keyValueArg.
func parseFromRawArgs(input interface{}) (keyValueArgs, error) {
parsed := convert.ToInterfaceSlice(input)
if parsed == nil {
logrus.Errorf("failed to convert input into slice: invalid type: %v", input)
return
return nil, fmt.Errorf("failed to convert input into slice: invalid type: %T, expected interface{}", input)
}
args := keyValueArgs{}
for _, arg := range parsed {
key, val, found := strings.Cut(convert.ToString(arg), "=")
if !found {
logrus.Warnf("skipping argument [%s] which does not have right format", arg)
continue
}
kv.update(key, val)
args.update(key, val)
}
return args, nil
}

// update updates the value for the given key if it exists in the slice; otherwise it appends a new key-value pair.
Expand Down Expand Up @@ -261,11 +257,14 @@ func (m *ProvisioningClusterMutator) handlePSACT(request *admission.Request, clu
}
// drop relevant fields if they exist in the cluster
dropMachineSelectorFile(machineSelectorFileForPSA(secretName, mountPath, ""), cluster, true)
args := getKubeAPIServerArg(cluster)
newArgs := slices.DeleteFunc(*args, func(arg keyValueArg) bool {
args, err := getKubeAPIServerArgs(cluster)
if err != nil {
return nil, fmt.Errorf("[provisioning cluster mutator] failed to get the kube-apiserver arguments: %w", err)
}
newArgs := slices.DeleteFunc(args, func(arg keyValueArg) bool {
return arg.key == kubeAPIAdmissionConfigOption && arg.value == mountPath
})
setKubeAPIServerArg(newArgs, cluster)
setKubeAPIServerArgs(newArgs, cluster)
} else {
// Now, handle the case of PSACT being set when creating or updating the cluster
template, err := m.psact.Get(templateName)
Expand Down Expand Up @@ -293,9 +292,12 @@ func (m *ProvisioningClusterMutator) handlePSACT(request *admission.Request, clu
dropMachineSelectorFile(machineSelectorFileForPSA(secretName, mountPath, ""), cluster, true)
hash := sha256.Sum256(fileContent)
addMachineSelectorFile(machineSelectorFileForPSA(secretName, mountPath, base64.StdEncoding.EncodeToString(hash[:])), cluster)
args := getKubeAPIServerArg(cluster)
args, err := getKubeAPIServerArgs(cluster)
if err != nil {
return nil, fmt.Errorf("[provisioning cluster mutator] failed to get the kube-apiserver arguments: %w", err)
}
args.update(kubeAPIAdmissionConfigOption, mountPath)
setKubeAPIServerArg(*args, cluster)
setKubeAPIServerArgs(args, cluster)
}
}
return admission.ResponseAllowed(), nil
Expand Down Expand Up @@ -333,20 +335,24 @@ func (m *ProvisioningClusterMutator) ensureSecret(namespace, name string, data m
return nil
}

// getKubeAPIServerArg returns a slice of keyValueArg representing the parsed value of
// getKubeAPIServerArgs returns a slice of keyValueArg representing the parsed value of
// "kube-apiserver-arg" from the cluster's MachineGlobalConfig.
// An empty slice is returned if "kube-apiserver-arg" is not set in the cluster.
func getKubeAPIServerArg(cluster *v1.Cluster) *keyValueArgs {
args := newKeyValueArgs()
if rawArgs, exists := cluster.Spec.RKEConfig.MachineGlobalConfig.Data["kube-apiserver-arg"]; exists {
args.parseFromRawArgs(rawArgs)
// An empty slice is returned if "kube-apiserver-arg" is not set or an error is encountered during parsing.
func getKubeAPIServerArgs(cluster *v1.Cluster) (keyValueArgs, error) {
rawArgs, exists := cluster.Spec.RKEConfig.MachineGlobalConfig.Data["kube-apiserver-arg"]
if !exists {
return keyValueArgs{}, nil
}
args, err := parseFromRawArgs(rawArgs)
if err != nil {
return keyValueArgs{}, err
}
return args
return args, nil
}

// setKubeAPIServerArg uses the provided arg to overwrite the value of kube-apiserver-arg under the cluster's MachineGlobalConfig.
// setKubeAPIServerArgs uses the provided arg to overwrite the value of kube-apiserver-arg under the cluster's MachineGlobalConfig.
// If the provided arg is an empty map, setKubeAPIServerArg removes the existing kube-apiserver-arg from the cluster's MachineGlobalConfig.
func setKubeAPIServerArg(args []keyValueArg, cluster *v1.Cluster) {
func setKubeAPIServerArgs(args keyValueArgs, cluster *v1.Cluster) {
if len(args) == 0 {
delete(cluster.Spec.RKEConfig.MachineGlobalConfig.Data, "kube-apiserver-arg")
return
Expand Down
70 changes: 48 additions & 22 deletions pkg/resources/provisioning.cattle.io/v1/cluster/mutator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cluster

import (
"encoding/json"
"errors"
"reflect"
"testing"

Expand All @@ -16,16 +17,19 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)

func Test_GetKubeAPIServerArg(t *testing.T) {
func Test_GetKubeAPIServerArgs(t *testing.T) {
errInvalidType := errors.New("failed to convert input into slice: invalid type: []string, expected interface{}")
tests := []struct {
name string
cluster *v1.Cluster
expected *keyValueArgs
name string
cluster *v1.Cluster
expected keyValueArgs
expectedErr error
}{
{
name: "cluster without kube-apiserver-arg",
cluster: clusterWithoutKubeAPIServerArg(),
expected: &keyValueArgs{},
name: "cluster without kube-apiserver-arg",
cluster: clusterWithoutKubeAPIServerArg(),
expected: keyValueArgs{},
expectedErr: nil,
},
{
name: "cluster without MachineGlobalConfig",
Expand All @@ -34,45 +38,60 @@ func Test_GetKubeAPIServerArg(t *testing.T) {
RKEConfig: &v1.RKEConfig{},
},
},
expected: &keyValueArgs{},
expected: keyValueArgs{},
expectedErr: nil,
},
{
name: "cluster with kube-apiserver-arg",
cluster: clusterWithKubeAPIServerArg(),
expected: &keyValueArgs{
expected: keyValueArgs{
{key: "foo", value: "bar"},
{key: "foo2", value: "bar2"},
},
expectedErr: nil,
},
{
name: "cluster with kube-apiserver-arg-2",
cluster: clusterWithKubeAPIServerArg2(),
expected: &keyValueArgs{
expected: keyValueArgs{
{key: "foo", value: "bar"},
{key: "foo2", value: "bar2"},
{key: "foo3", value: "bar3=baz3"},
},
expectedErr: nil,
},
{
name: "cluster with duplicate keys in kube-apiserver-arg",
cluster: clusterWithKubeAPIServerArg3(),
expected: &keyValueArgs{
expected: keyValueArgs{
{key: "foo", value: "bar"},
{key: "foo2", value: "bar2=baz2"},
},
expectedErr: nil,
},
{
name: "cluster with invalid data in kube-apiserver-arg",
cluster: clusterWithInvalidKubeAPIServerArg(),
expected: keyValueArgs{},
expectedErr: errInvalidType,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := getKubeAPIServerArg(tt.cluster)
if !reflect.DeepEqual(*tt.expected, *got) {
t.Errorf("got: [%v], expected: [%v]", *got, *tt.expected)
got, err := getKubeAPIServerArgs(tt.cluster)
if tt.expectedErr != nil {
if err == nil || err.Error() != tt.expectedErr.Error() {
t.Errorf("expected getKubeAPIServerArgs() error = %v, got %v", tt.expectedErr, err)
}
}
if !reflect.DeepEqual(tt.expected, got) {
t.Errorf("got: %v, expected: %v", got, tt.expected)
}
})
}
}

func Test_SetKubeAPIServerArg(t *testing.T) {
func Test_SetKubeAPIServerArgs(t *testing.T) {
tests := []struct {
name string
arg keyValueArgs
Expand Down Expand Up @@ -143,13 +162,11 @@ func Test_SetKubeAPIServerArg(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := newKeyValueArgs()
expected := newKeyValueArgs()
setKubeAPIServerArg(tt.arg, tt.cluster)
got.parseFromRawArgs(tt.cluster.Spec.RKEConfig.MachineGlobalConfig.Data["kube-apiserver-arg"])
expected.parseFromRawArgs(tt.expected.Spec.RKEConfig.MachineGlobalConfig.Data["kube-apiserver-arg"])
if !reflect.DeepEqual(*got, *expected) {
t.Errorf("got: %v, expected: %v", *got, *expected)
setKubeAPIServerArgs(tt.arg, tt.cluster)
got, _ := parseFromRawArgs(tt.cluster.Spec.RKEConfig.MachineGlobalConfig.Data["kube-apiserver-arg"])
expected, _ := parseFromRawArgs(tt.expected.Spec.RKEConfig.MachineGlobalConfig.Data["kube-apiserver-arg"])
if !reflect.DeepEqual(got, expected) {
t.Errorf("got: %v, expected: %v", got, expected)
}
})
}
Expand Down Expand Up @@ -468,6 +485,15 @@ func clusterWithKubeAPIServerArg3() *v1.Cluster {
return cluster
}

func clusterWithInvalidKubeAPIServerArg() *v1.Cluster {
cluster := clusterWithoutKubeAPIServerArg()
var arg []string
arg = append(arg, "foo=bar")
arg = append(arg, "foo2=bar2")
cluster.Spec.RKEConfig.MachineGlobalConfig.Data["kube-apiserver-arg"] = arg
return cluster
}

func machineSelectorFile3() rkev1.RKEProvisioningFiles {
file := machineSelectorFile1()
file.FileSources[0].Secret.Items[0].Hash = "expected-value-for-file-3"
Expand Down
10 changes: 8 additions & 2 deletions pkg/resources/provisioning.cattle.io/v1/cluster/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,10 @@ func (p *provisioningAdmitter) validatePSACT(request *admission.Request, respons
return fmt.Errorf("[provisioning cluster validator] machineSelectorFile for PSA should not be in the cluster Spec")
}
// validate that the flags are not set
args := getKubeAPIServerArg(cluster)
args, err := getKubeAPIServerArgs(cluster)
if err != nil {
return fmt.Errorf("[provisioning cluster validator] failed to get the kube-apiserver arguments: %w", err)
}
if args.keyHasValue(kubeAPIAdmissionConfigOption, mountPath) {
return fmt.Errorf("[provisioning cluster validator] admission-control-config-file under kube-apiserver-arg should not be set to %s", mountPath)
}
Expand Down Expand Up @@ -501,7 +504,10 @@ func (p *provisioningAdmitter) validatePSACT(request *admission.Request, respons
return fmt.Errorf("[provisioning cluster validator] machineSelectorFile for PSA should be in the cluster Spec")
}
// validate that the flags are set
args := getKubeAPIServerArg(cluster)
args, err := getKubeAPIServerArgs(cluster)
if err != nil {
return fmt.Errorf("[provisioning cluster validator] failed to get the kube-apiserver arguments: %w", err)
}
if !args.keyHasValue(kubeAPIAdmissionConfigOption, mountPath) {
return fmt.Errorf("[provisioning cluster validator] admission-control-config-file under kube-apiserver-arg should be set to %s", mountPath)
}
Expand Down
Loading