From 31ced582a9a79fab7fd7129db45711987dc5f4bc Mon Sep 17 00:00:00 2001 From: Alay Patel Date: Wed, 12 May 2021 15:40:00 -0400 Subject: [PATCH] service_action: use unstructured to marshal selective fields (#3789) * use unstructured to marshal selective fields Signed-off-by: Alay Patel * add a sample test for string port in applied config Signed-off-by: Alay Patel * update changelog Signed-off-by: Alay Patel --- changelogs/unreleased/3789-alaypatel07 | 1 + pkg/restore/service_action.go | 48 +++++++++++++++++++++++--- pkg/restore/service_action_test.go | 47 +++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/3789-alaypatel07 diff --git a/changelogs/unreleased/3789-alaypatel07 b/changelogs/unreleased/3789-alaypatel07 new file mode 100644 index 000000000..d22f47c72 --- /dev/null +++ b/changelogs/unreleased/3789-alaypatel07 @@ -0,0 +1 @@ +use unstructured to marshal selective fields for service restore action \ No newline at end of file diff --git a/pkg/restore/service_action.go b/pkg/restore/service_action.go index 8895e3a60..e8b58f062 100644 --- a/pkg/restore/service_action.go +++ b/pkg/restore/service_action.go @@ -18,6 +18,7 @@ package restore import ( "encoding/json" + "strconv" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -84,14 +85,51 @@ func deleteNodePorts(service *corev1api.Service) error { explicitNodePorts := sets.NewString() lastAppliedConfig, ok := service.Annotations[annotationLastAppliedConfig] if ok { - appliedService := new(corev1api.Service) - if err := json.Unmarshal([]byte(lastAppliedConfig), appliedService); err != nil { + appliedServiceUnstructured := new(map[string]interface{}) + if err := json.Unmarshal([]byte(lastAppliedConfig), appliedServiceUnstructured); err != nil { return errors.WithStack(err) } - for _, port := range appliedService.Spec.Ports { - if port.NodePort > 0 { - explicitNodePorts.Insert(port.Name) + ports, bool, err := unstructured.NestedSlice(*appliedServiceUnstructured, "spec", "ports") + + if err != nil { + return errors.WithStack(err) + } + + if bool { + for _, port := range ports { + p, ok := port.(map[string]interface{}) + if !ok { + continue + } + nodePort, nodePortBool, err := unstructured.NestedFieldNoCopy(p, "nodePort") + if err != nil { + return errors.WithStack(err) + } + if nodePortBool { + nodePortInt := 0 + switch nodePort.(type) { + case int32: + nodePortInt = int(nodePort.(int32)) + case float64: + nodePortInt = int(nodePort.(float64)) + case string: + nodePortInt, err = strconv.Atoi(nodePort.(string)) + if err != nil { + return errors.WithStack(err) + } + } + if nodePortInt > 0 { + portName, ok := p["name"] + if !ok { + // unnamed port + explicitNodePorts.Insert("") + } else { + explicitNodePorts.Insert(portName.(string)) + } + + } + } } } } diff --git a/pkg/restore/service_action_test.go b/pkg/restore/service_action_test.go index 4cfcd1a85..e6b0b1530 100644 --- a/pkg/restore/service_action_test.go +++ b/pkg/restore/service_action_test.go @@ -48,6 +48,21 @@ func svcJSON(ports ...corev1api.ServicePort) string { return string(data) } +func svcJSONFromUnstructured(ports ...map[string]interface{}) string { + svc := map[string]interface{}{ + "spec": map[string]interface{}{ + "ports": ports, + }, + } + + data, err := json.Marshal(svc) + if err != nil { + panic(err) + } + + return string(data) +} + func TestServiceActionExecute(t *testing.T) { tests := []struct { @@ -233,6 +248,38 @@ func TestServiceActionExecute(t *testing.T) { }, }, }, + { + name: "unnamed nodePort should be deleted when named a string nodePort specified in annotation", + obj: corev1api.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-1", + Annotations: map[string]string{ + annotationLastAppliedConfig: svcJSONFromUnstructured(map[string]interface{}{"name": "http", "nodePort": "8080"}), + }, + }, + Spec: corev1api.ServiceSpec{ + Ports: []corev1api.ServicePort{ + { + NodePort: 8080, + }, + }, + }, + }, + restore: builder.ForRestore(api.DefaultNamespace, "").Result(), + expectedRes: corev1api.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-1", + Annotations: map[string]string{ + annotationLastAppliedConfig: svcJSONFromUnstructured(map[string]interface{}{"name": "http", "nodePort": "8080"}), + }, + }, + Spec: corev1api.ServiceSpec{ + Ports: []corev1api.ServicePort{ + {}, + }, + }, + }, + }, { name: "named nodePort should be preserved when specified in annotation", obj: corev1api.Service{