github.com/argoproj/argo-cd/v2@v2.10.9/controller/cache/info_test.go (about)

     1  package cache
     2  
     3  import (
     4  	"sort"
     5  	"strings"
     6  	"testing"
     7  
     8  	"k8s.io/apimachinery/pkg/api/resource"
     9  
    10  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    11  	"github.com/argoproj/pkg/errors"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  	v1 "k8s.io/api/core/v1"
    15  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    16  	"sigs.k8s.io/yaml"
    17  
    18  	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    19  	"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
    20  )
    21  
    22  func strToUnstructured(jsonStr string) *unstructured.Unstructured {
    23  	obj := make(map[string]interface{})
    24  	err := yaml.Unmarshal([]byte(jsonStr), &obj)
    25  	errors.CheckError(err)
    26  	return &unstructured.Unstructured{Object: obj}
    27  }
    28  
    29  var (
    30  	testService = strToUnstructured(`
    31    apiVersion: v1
    32    kind: Service
    33    metadata:
    34      name: helm-guestbook
    35      namespace: default
    36      resourceVersion: "123"
    37      uid: "4"
    38    spec:
    39      selector:
    40        app: guestbook
    41      type: LoadBalancer
    42    status:
    43      loadBalancer:
    44        ingress:
    45        - hostname: localhost`)
    46  
    47  	testLinkAnnotatedService = strToUnstructured(`
    48    apiVersion: v1
    49    kind: Service
    50    metadata:
    51      name: helm-guestbook
    52      namespace: default
    53      resourceVersion: "123"
    54      uid: "4"
    55      annotations:
    56        link.argocd.argoproj.io/external-link: http://my-grafana.example.com/pre-generated-link
    57    spec:
    58      selector:
    59        app: guestbook
    60      type: LoadBalancer
    61    status:
    62      loadBalancer:
    63        ingress:
    64        - hostname: localhost`)
    65  
    66  	testIngress = strToUnstructured(`
    67    apiVersion: extensions/v1beta1
    68    kind: Ingress
    69    metadata:
    70      name: helm-guestbook
    71      namespace: default
    72      uid: "4"
    73    spec:
    74      backend:
    75        serviceName: not-found-service
    76        servicePort: 443
    77      rules:
    78      - host: helm-guestbook.example.com
    79        http:
    80          paths:
    81          - backend:
    82              serviceName: helm-guestbook
    83              servicePort: 443
    84            path: /
    85          - backend:
    86              serviceName: helm-guestbook
    87              servicePort: https
    88            path: /
    89      tls:
    90      - host: helm-guestbook.example.com
    91      secretName: my-tls-secret
    92    status:
    93      loadBalancer:
    94        ingress:
    95        - ip: 107.178.210.11`)
    96  
    97  	testLinkAnnotatedIngress = strToUnstructured(`
    98    apiVersion: extensions/v1beta1
    99    kind: Ingress
   100    metadata:
   101      name: helm-guestbook
   102      namespace: default
   103      uid: "4"
   104      annotations:
   105        link.argocd.argoproj.io/external-link: http://my-grafana.example.com/ingress-link
   106    spec:
   107      backend:
   108        serviceName: not-found-service
   109        servicePort: 443
   110      rules:
   111      - host: helm-guestbook.example.com
   112        http:
   113          paths:
   114          - backend:
   115              serviceName: helm-guestbook
   116              servicePort: 443
   117            path: /
   118          - backend:
   119              serviceName: helm-guestbook
   120              servicePort: https
   121            path: /
   122      tls:
   123      - host: helm-guestbook.example.com
   124      secretName: my-tls-secret
   125    status:
   126      loadBalancer:
   127        ingress:
   128        - ip: 107.178.210.11`)
   129  
   130  	testIngressWildCardPath = strToUnstructured(`
   131    apiVersion: extensions/v1beta1
   132    kind: Ingress
   133    metadata:
   134      name: helm-guestbook
   135      namespace: default
   136      uid: "4"
   137    spec:
   138      backend:
   139        serviceName: not-found-service
   140        servicePort: 443
   141      rules:
   142      - host: helm-guestbook.example.com
   143        http:
   144          paths:
   145          - backend:
   146              serviceName: helm-guestbook
   147              servicePort: 443
   148            path: /*
   149          - backend:
   150              serviceName: helm-guestbook
   151              servicePort: https
   152            path: /*
   153      tls:
   154      - host: helm-guestbook.example.com
   155      secretName: my-tls-secret
   156    status:
   157      loadBalancer:
   158        ingress:
   159        - ip: 107.178.210.11`)
   160  
   161  	testIngressWithoutTls = strToUnstructured(`
   162    apiVersion: extensions/v1beta1
   163    kind: Ingress
   164    metadata:
   165      name: helm-guestbook
   166      namespace: default
   167      uid: "4"
   168    spec:
   169      backend:
   170        serviceName: not-found-service
   171        servicePort: 443
   172      rules:
   173      - host: helm-guestbook.example.com
   174        http:
   175          paths:
   176          - backend:
   177              serviceName: helm-guestbook
   178              servicePort: 443
   179            path: /
   180          - backend:
   181              serviceName: helm-guestbook
   182              servicePort: https
   183            path: /
   184    status:
   185      loadBalancer:
   186        ingress:
   187        - ip: 107.178.210.11`)
   188  
   189  	testIngressNetworkingV1 = strToUnstructured(`
   190    apiVersion: networking.k8s.io/v1
   191    kind: Ingress
   192    metadata:
   193      name: helm-guestbook
   194      namespace: default
   195      uid: "4"
   196    spec:
   197      backend:
   198        service:
   199          name: not-found-service
   200          port:
   201            number: 443
   202      rules:
   203      - host: helm-guestbook.example.com
   204        http:
   205          paths:
   206          - backend:
   207              service:
   208                name: helm-guestbook
   209                port:
   210                  number: 443
   211            path: /
   212          - backend:
   213              service:
   214                name: helm-guestbook
   215                port:
   216                  name: https
   217            path: /
   218      tls:
   219      - host: helm-guestbook.example.com
   220      secretName: my-tls-secret
   221    status:
   222      loadBalancer:
   223        ingress:
   224        - ip: 107.178.210.11`)
   225  
   226  	testIstioVirtualService = strToUnstructured(`
   227  apiVersion: networking.istio.io/v1alpha3
   228  kind: VirtualService
   229  metadata:
   230    name: hello-world
   231    namespace: demo
   232  spec:
   233    http:
   234      - match:
   235          - uri:
   236              prefix: "/1"
   237        route:
   238          - destination:
   239              host: service_full.demo.svc.cluster.local
   240          - destination:
   241              host: service_namespace.namespace
   242      - match:
   243          - uri:
   244              prefix: "/2"
   245        route:
   246          - destination:
   247              host: service
   248  `)
   249  )
   250  
   251  func TestGetPodInfo(t *testing.T) {
   252  	pod := strToUnstructured(`
   253    apiVersion: v1
   254    kind: Pod
   255    metadata:
   256      name: helm-guestbook-pod
   257      namespace: default
   258      ownerReferences:
   259      - apiVersion: extensions/v1beta1
   260        kind: ReplicaSet
   261        name: helm-guestbook-rs
   262      resourceVersion: "123"
   263      labels:
   264        app: guestbook
   265    spec:
   266      nodeName: minikube
   267      containers:
   268      - image: bar
   269        resources:
   270          requests:
   271            memory: 128Mi
   272  `)
   273  
   274  	info := &ResourceInfo{}
   275  	populateNodeInfo(pod, info, []string{})
   276  	assert.Equal(t, []v1alpha1.InfoItem{
   277  		{Name: "Node", Value: "minikube"},
   278  		{Name: "Containers", Value: "0/1"},
   279  	}, info.Info)
   280  	assert.Equal(t, []string{"bar"}, info.Images)
   281  	assert.Equal(t, &PodInfo{
   282  		NodeName:         "minikube",
   283  		ResourceRequests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("128Mi")},
   284  	}, info.PodInfo)
   285  	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{Labels: map[string]string{"app": "guestbook"}}, info.NetworkingInfo)
   286  }
   287  
   288  func TestGetNodeInfo(t *testing.T) {
   289  	node := strToUnstructured(`
   290  apiVersion: v1
   291  kind: Node
   292  metadata:
   293    name: minikube
   294  spec: {}
   295  status:
   296    capacity:
   297      cpu: "6"
   298      memory: 6091320Ki
   299    nodeInfo:
   300      architecture: amd64
   301      operatingSystem: linux
   302      osImage: Ubuntu 20.04 LTS
   303  `)
   304  
   305  	info := &ResourceInfo{}
   306  	populateNodeInfo(node, info, []string{})
   307  	assert.Equal(t, &NodeInfo{
   308  		Name:       "minikube",
   309  		Capacity:   v1.ResourceList{v1.ResourceMemory: resource.MustParse("6091320Ki"), v1.ResourceCPU: resource.MustParse("6")},
   310  		SystemInfo: v1.NodeSystemInfo{Architecture: "amd64", OperatingSystem: "linux", OSImage: "Ubuntu 20.04 LTS"},
   311  	}, info.NodeInfo)
   312  }
   313  
   314  func TestGetServiceInfo(t *testing.T) {
   315  	info := &ResourceInfo{}
   316  	populateNodeInfo(testService, info, []string{})
   317  	assert.Equal(t, 0, len(info.Info))
   318  	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
   319  		TargetLabels: map[string]string{"app": "guestbook"},
   320  		Ingress:      []v1.LoadBalancerIngress{{Hostname: "localhost"}},
   321  	}, info.NetworkingInfo)
   322  }
   323  
   324  func TestGetLinkAnnotatedServiceInfo(t *testing.T) {
   325  	info := &ResourceInfo{}
   326  	populateNodeInfo(testLinkAnnotatedService, info, []string{})
   327  	assert.Equal(t, 0, len(info.Info))
   328  	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
   329  		TargetLabels: map[string]string{"app": "guestbook"},
   330  		Ingress:      []v1.LoadBalancerIngress{{Hostname: "localhost"}},
   331  		ExternalURLs: []string{"http://my-grafana.example.com/pre-generated-link"},
   332  	}, info.NetworkingInfo)
   333  }
   334  
   335  func TestGetIstioVirtualServiceInfo(t *testing.T) {
   336  	info := &ResourceInfo{}
   337  	populateNodeInfo(testIstioVirtualService, info, []string{})
   338  	assert.Equal(t, 0, len(info.Info))
   339  	require.NotNil(t, info.NetworkingInfo)
   340  	require.NotNil(t, info.NetworkingInfo.TargetRefs)
   341  	assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{
   342  		Kind:      kube.ServiceKind,
   343  		Name:      "service_full",
   344  		Namespace: "demo",
   345  	})
   346  	assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{
   347  		Kind:      kube.ServiceKind,
   348  		Name:      "service_namespace",
   349  		Namespace: "namespace",
   350  	})
   351  	assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{
   352  		Kind:      kube.ServiceKind,
   353  		Name:      "service",
   354  		Namespace: "demo",
   355  	})
   356  }
   357  
   358  func TestGetIngressInfo(t *testing.T) {
   359  	var tests = []struct {
   360  		Ingress *unstructured.Unstructured
   361  	}{
   362  		{testIngress},
   363  		{testIngressNetworkingV1},
   364  	}
   365  	for _, tc := range tests {
   366  		info := &ResourceInfo{}
   367  		populateNodeInfo(tc.Ingress, info, []string{})
   368  		assert.Equal(t, 0, len(info.Info))
   369  		sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
   370  			return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
   371  		})
   372  		assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
   373  			Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
   374  			TargetRefs: []v1alpha1.ResourceRef{{
   375  				Namespace: "default",
   376  				Group:     "",
   377  				Kind:      kube.ServiceKind,
   378  				Name:      "not-found-service",
   379  			}, {
   380  				Namespace: "default",
   381  				Group:     "",
   382  				Kind:      kube.ServiceKind,
   383  				Name:      "helm-guestbook",
   384  			}},
   385  			ExternalURLs: []string{"https://helm-guestbook.example.com/"},
   386  		}, info.NetworkingInfo)
   387  	}
   388  }
   389  
   390  func TestGetLinkAnnotatedIngressInfo(t *testing.T) {
   391  	info := &ResourceInfo{}
   392  	populateNodeInfo(testLinkAnnotatedIngress, info, []string{})
   393  	assert.Equal(t, 0, len(info.Info))
   394  	sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
   395  		return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
   396  	})
   397  	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
   398  		Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
   399  		TargetRefs: []v1alpha1.ResourceRef{{
   400  			Namespace: "default",
   401  			Group:     "",
   402  			Kind:      kube.ServiceKind,
   403  			Name:      "not-found-service",
   404  		}, {
   405  			Namespace: "default",
   406  			Group:     "",
   407  			Kind:      kube.ServiceKind,
   408  			Name:      "helm-guestbook",
   409  		}},
   410  		ExternalURLs: []string{"http://my-grafana.example.com/ingress-link", "https://helm-guestbook.example.com/"},
   411  	}, info.NetworkingInfo)
   412  }
   413  
   414  func TestGetIngressInfoWildCardPath(t *testing.T) {
   415  	info := &ResourceInfo{}
   416  	populateNodeInfo(testIngressWildCardPath, info, []string{})
   417  	assert.Equal(t, 0, len(info.Info))
   418  	sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
   419  		return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
   420  	})
   421  	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
   422  		Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
   423  		TargetRefs: []v1alpha1.ResourceRef{{
   424  			Namespace: "default",
   425  			Group:     "",
   426  			Kind:      kube.ServiceKind,
   427  			Name:      "not-found-service",
   428  		}, {
   429  			Namespace: "default",
   430  			Group:     "",
   431  			Kind:      kube.ServiceKind,
   432  			Name:      "helm-guestbook",
   433  		}},
   434  		ExternalURLs: []string{"https://helm-guestbook.example.com/"},
   435  	}, info.NetworkingInfo)
   436  }
   437  
   438  func TestGetIngressInfoWithoutTls(t *testing.T) {
   439  	info := &ResourceInfo{}
   440  	populateNodeInfo(testIngressWithoutTls, info, []string{})
   441  	assert.Equal(t, 0, len(info.Info))
   442  	sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
   443  		return strings.Compare(info.NetworkingInfo.TargetRefs[j].Name, info.NetworkingInfo.TargetRefs[i].Name) < 0
   444  	})
   445  	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
   446  		Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
   447  		TargetRefs: []v1alpha1.ResourceRef{{
   448  			Namespace: "default",
   449  			Group:     "",
   450  			Kind:      kube.ServiceKind,
   451  			Name:      "not-found-service",
   452  		}, {
   453  			Namespace: "default",
   454  			Group:     "",
   455  			Kind:      kube.ServiceKind,
   456  			Name:      "helm-guestbook",
   457  		}},
   458  		ExternalURLs: []string{"http://helm-guestbook.example.com/"},
   459  	}, info.NetworkingInfo)
   460  }
   461  
   462  func TestGetIngressInfoWithHost(t *testing.T) {
   463  	ingress := strToUnstructured(`
   464    apiVersion: extensions/v1beta1
   465    kind: Ingress
   466    metadata:
   467      name: helm-guestbook
   468      namespace: default
   469    spec:
   470      rules:
   471      - http:
   472          paths:
   473          - backend:
   474              serviceName: helm-guestbook
   475              servicePort: 443
   476            path: /
   477      tls:
   478      - secretName: my-tls
   479    status:
   480      loadBalancer:
   481        ingress:
   482        - ip: 107.178.210.11`)
   483  
   484  	info := &ResourceInfo{}
   485  	populateNodeInfo(ingress, info, []string{})
   486  
   487  	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
   488  		Ingress: []v1.LoadBalancerIngress{{IP: "107.178.210.11"}},
   489  		TargetRefs: []v1alpha1.ResourceRef{{
   490  			Namespace: "default",
   491  			Group:     "",
   492  			Kind:      kube.ServiceKind,
   493  			Name:      "helm-guestbook",
   494  		}},
   495  		ExternalURLs: []string{"https://107.178.210.11/"},
   496  	}, info.NetworkingInfo)
   497  }
   498  func TestGetIngressInfoNoHost(t *testing.T) {
   499  	ingress := strToUnstructured(`
   500    apiVersion: extensions/v1beta1
   501    kind: Ingress
   502    metadata:
   503      name: helm-guestbook
   504      namespace: default
   505    spec:
   506      rules:
   507      - http:
   508          paths:
   509          - backend:
   510              serviceName: helm-guestbook
   511              servicePort: 443
   512            path: /
   513      tls:
   514      - secretName: my-tls
   515        `)
   516  
   517  	info := &ResourceInfo{}
   518  	populateNodeInfo(ingress, info, []string{})
   519  
   520  	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
   521  		TargetRefs: []v1alpha1.ResourceRef{{
   522  			Namespace: "default",
   523  			Group:     "",
   524  			Kind:      kube.ServiceKind,
   525  			Name:      "helm-guestbook",
   526  		}},
   527  	}, info.NetworkingInfo)
   528  	assert.Equal(t, len(info.NetworkingInfo.ExternalURLs), 0)
   529  }
   530  func TestExternalUrlWithSubPath(t *testing.T) {
   531  	ingress := strToUnstructured(`
   532    apiVersion: networking.k8s.io/v1
   533    kind: Ingress
   534    metadata:
   535      name: helm-guestbook
   536      namespace: default
   537    spec:
   538      rules:
   539      - http:
   540          paths:
   541          - backend:
   542              serviceName: helm-guestbook
   543              servicePort: 443
   544            path: /my/sub/path/
   545      tls:
   546      - secretName: my-tls
   547    status:
   548      loadBalancer:
   549        ingress:
   550        - ip: 107.178.210.11`)
   551  
   552  	info := &ResourceInfo{}
   553  	populateNodeInfo(ingress, info, []string{})
   554  
   555  	expectedExternalUrls := []string{"https://107.178.210.11/my/sub/path/"}
   556  	assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
   557  }
   558  func TestExternalUrlWithMultipleSubPaths(t *testing.T) {
   559  	ingress := strToUnstructured(`
   560    apiVersion: networking.k8s.io/v1
   561    kind: Ingress
   562    metadata:
   563      name: helm-guestbook
   564      namespace: default
   565    spec:
   566      rules:
   567      - host: helm-guestbook.example.com
   568        http:
   569          paths:
   570          - backend:
   571              serviceName: helm-guestbook
   572              servicePort: 443
   573            path: /my/sub/path/
   574          - backend:
   575              serviceName: helm-guestbook-2
   576              servicePort: 443
   577            path: /my/sub/path/2
   578          - backend:
   579              serviceName: helm-guestbook-3
   580              servicePort: 443
   581      tls:
   582      - secretName: my-tls
   583    status:
   584      loadBalancer:
   585        ingress:
   586        - ip: 107.178.210.11`)
   587  
   588  	info := &ResourceInfo{}
   589  	populateNodeInfo(ingress, info, []string{})
   590  
   591  	expectedExternalUrls := []string{"https://helm-guestbook.example.com/my/sub/path/", "https://helm-guestbook.example.com/my/sub/path/2", "https://helm-guestbook.example.com"}
   592  	actualURLs := info.NetworkingInfo.ExternalURLs
   593  	sort.Strings(expectedExternalUrls)
   594  	sort.Strings(actualURLs)
   595  	assert.Equal(t, expectedExternalUrls, actualURLs)
   596  }
   597  func TestExternalUrlWithNoSubPath(t *testing.T) {
   598  	ingress := strToUnstructured(`
   599    apiVersion: networking.k8s.io/v1
   600    kind: Ingress
   601    metadata:
   602      name: helm-guestbook
   603      namespace: default
   604    spec:
   605      rules:
   606      - http:
   607          paths:
   608          - backend:
   609              serviceName: helm-guestbook
   610              servicePort: 443
   611      tls:
   612      - secretName: my-tls
   613    status:
   614      loadBalancer:
   615        ingress:
   616        - ip: 107.178.210.11`)
   617  
   618  	info := &ResourceInfo{}
   619  	populateNodeInfo(ingress, info, []string{})
   620  
   621  	expectedExternalUrls := []string{"https://107.178.210.11"}
   622  	assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
   623  }
   624  
   625  func TestExternalUrlWithNetworkingApi(t *testing.T) {
   626  	ingress := strToUnstructured(`
   627    apiVersion: networking.k8s.io/v1beta1
   628    kind: Ingress
   629    metadata:
   630      name: helm-guestbook
   631      namespace: default
   632    spec:
   633      rules:
   634      - http:
   635          paths:
   636          - backend:
   637              serviceName: helm-guestbook
   638              servicePort: 443
   639      tls:
   640      - secretName: my-tls
   641    status:
   642      loadBalancer:
   643        ingress:
   644        - ip: 107.178.210.11`)
   645  
   646  	info := &ResourceInfo{}
   647  	populateNodeInfo(ingress, info, []string{})
   648  
   649  	expectedExternalUrls := []string{"https://107.178.210.11"}
   650  	assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
   651  }
   652  
   653  func TestCustomLabel(t *testing.T) {
   654  	configmap := strToUnstructured(`
   655    apiVersion: v1
   656    kind: ConfigMap
   657    metadata:
   658      name: cm`)
   659  
   660  	info := &ResourceInfo{}
   661  	populateNodeInfo(configmap, info, []string{"my-label"})
   662  
   663  	assert.Equal(t, 0, len(info.Info))
   664  
   665  	configmap = strToUnstructured(`
   666    apiVersion: v1
   667    kind: ConfigMap
   668    metadata:
   669      name: cm
   670      labels:
   671        my-label: value`)
   672  
   673  	info = &ResourceInfo{}
   674  	populateNodeInfo(configmap, info, []string{"my-label", "other-label"})
   675  
   676  	assert.Equal(t, 1, len(info.Info))
   677  	assert.Equal(t, "my-label", info.Info[0].Name)
   678  	assert.Equal(t, "value", info.Info[0].Value)
   679  
   680  	configmap = strToUnstructured(`
   681    apiVersion: v1
   682    kind: ConfigMap
   683    metadata:
   684      name: cm
   685      labels:
   686        my-label: value
   687        other-label: value2`)
   688  
   689  	info = &ResourceInfo{}
   690  	populateNodeInfo(configmap, info, []string{"my-label", "other-label"})
   691  
   692  	assert.Equal(t, 2, len(info.Info))
   693  	assert.Equal(t, "my-label", info.Info[0].Name)
   694  	assert.Equal(t, "value", info.Info[0].Value)
   695  	assert.Equal(t, "other-label", info.Info[1].Name)
   696  	assert.Equal(t, "value2", info.Info[1].Value)
   697  }
   698  
   699  func TestManifestHash(t *testing.T) {
   700  	manifest := strToUnstructured(`
   701    apiVersion: v1
   702    kind: Pod
   703    metadata:
   704      name: helm-guestbook-pod
   705      namespace: default
   706      ownerReferences:
   707      - apiVersion: extensions/v1beta1
   708        kind: ReplicaSet
   709        name: helm-guestbook-rs
   710      resourceVersion: "123"
   711      labels:
   712        app: guestbook
   713    spec:
   714      nodeName: minikube
   715      containers:
   716      - image: bar
   717        resources:
   718          requests:
   719            memory: 128Mi
   720  `)
   721  
   722  	ignores := []v1alpha1.ResourceIgnoreDifferences{
   723  		{
   724  			Group:        "*",
   725  			Kind:         "*",
   726  			JSONPointers: []string{"/metadata/resourceVersion"},
   727  		},
   728  	}
   729  
   730  	data, _ := strToUnstructured(`
   731    apiVersion: v1
   732    kind: Pod
   733    metadata:
   734      name: helm-guestbook-pod
   735      namespace: default
   736      ownerReferences:
   737      - apiVersion: extensions/v1beta1
   738        kind: ReplicaSet
   739        name: helm-guestbook-rs
   740      labels:
   741        app: guestbook
   742    spec:
   743      nodeName: minikube
   744      containers:
   745      - image: bar
   746        resources:
   747          requests:
   748            memory: 128Mi
   749  `).MarshalJSON()
   750  
   751  	expected := hash(data)
   752  
   753  	hash, err := generateManifestHash(manifest, ignores, nil, normalizers.IgnoreNormalizerOpts{})
   754  	assert.Equal(t, expected, hash)
   755  	assert.Nil(t, err)
   756  }