service_action: use unstructured to marshal selective fields (#3789)

* use unstructured to marshal selective fields

Signed-off-by: Alay Patel <alay1431@gmail.com>

* add a sample test for string port in applied config

Signed-off-by: Alay Patel <alay1431@gmail.com>

* update changelog

Signed-off-by: Alay Patel <alay1431@gmail.com>
This commit is contained in:
Alay Patel
2021-05-12 15:40:00 -04:00
committed by GitHub
parent 8f46d9808b
commit 31ced582a9
3 changed files with 91 additions and 5 deletions

View File

@@ -0,0 +1 @@
use unstructured to marshal selective fields for service restore action

View File

@@ -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))
}
}
}
}
}
}

View File

@@ -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{