Repository: vmware-tanzu/velero (Go)

Issue:
restore breaking because last applied configuration has string typed port
**What steps did you take and what happened:**
Try to run a restore with a service having the following annotation
```
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{"openshift.io/generated-by":"OpenShiftNewApp"},"creationTimestamp":null,"labels":{"app":"xyz"},"name":"xyz","namespace":"xyz"},"spec":{"ports":[{"name":"xyz","port":"5000","protocol":"TCP","targetPort":5000}],"readinessProbe":{"failureThreshold":3,"httpGet":{"path":"/healthcheck","port":"5000","scheme":"HTTP"},"initialDelaySeconds":20,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":10},"resources":{},"selector":{"app":"xyz","module":"xyz"},"sessionAffinity":"None","type":"ClusterIP"},"status":{"loadBalancer":{}}}
```

Notice the port has quotes in the value `"port":"5000"` other services which do not have quotes around the port value in the annotations were seen to succeed.


**What did you expect to happen:**
Expected the restore to succeed

**The output of the following commands will help us better understand what's going on**:
```
 Errors:
  Velero:     <none>
  Cluster:    <none>
  Namespaces:   xyz:  error preparing services/xyz/xyz: rpc error: code = Unknown desc = json: cannot unmarshal string into Go struct field ServicePort.spec.ports.port of type int32
```

**Anything else you would like to add:**
The error seems to be happening when marshaling the last applied config here https://github.com/vmware-tanzu/velero/blob/main/pkg/restore/service_action.go#L88


**Environment:**

- Velero version (use `velero version`): 1.5.2
- Velero features (use `velero client config get features`): NA
- Kubernetes version (use `kubectl version`): NA
- Kubernetes installer & version: NA
- Cloud provider or hardware configuration: NA
- OS (e.g. from `/etc/os-release`): NA


**Vote on this issue!**

This is an invitation to the Velero community to vote on issues, you can see the project's [top voted issues listed here](https://github.com/vmware-tanzu/velero/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc).  
Use the "reaction smiley face" up to the right of this comment to vote.

- :+1: for "I would like to see this bug fixed as soon as possible"
- :-1: for "There are more important bugs to focus on right now"



Discussion:
cc @sseago 



Patch:
diff --git a/changelogs/unreleased/3789-alaypatel07 b/changelogs/unreleased/3789-alaypatel07
new file mode 100644
index 0000000000..d22f47c728
--- /dev/null
+++ b/changelogs/unreleased/3789-alaypatel07
@@ -0,0 +1,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 8895e3a603..e8b58f0621 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))
+						}
+
+					}
+				}
 			}
 		}
 	}


Test patch:
diff --git a/pkg/restore/service_action_test.go b/pkg/restore/service_action_test.go
index 4cfcd1a85b..e6b0b15300 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{