...

Source file src/github.com/datawire/ambassador/v2/pkg/snapshot/v1/annotations_test.go

Documentation: github.com/datawire/ambassador/v2/pkg/snapshot/v1

     1  package snapshot_test
     2  
     3  import (
     4  	"encoding/json"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    10  
    11  	amb "github.com/datawire/ambassador/v2/pkg/api/getambassador.io/v3alpha1"
    12  	"github.com/datawire/ambassador/v2/pkg/kates"
    13  	"github.com/datawire/ambassador/v2/pkg/kates/k8s_resource_types"
    14  	snapshotTypes "github.com/datawire/ambassador/v2/pkg/snapshot/v1"
    15  	"github.com/datawire/dlib/dlog"
    16  )
    17  
    18  func getModuleSpec(t *testing.T, rawconfig string) amb.UntypedDict {
    19  	moduleConfig := amb.UntypedDict{}
    20  	if err := json.Unmarshal([]byte(rawconfig), &moduleConfig); err != nil {
    21  		t.Fatal(t)
    22  	}
    23  	return moduleConfig
    24  }
    25  
    26  func TestParseAnnotations(t *testing.T) {
    27  	mapping := `
    28  ---
    29  apiVersion: getambassador.io/v3alpha1
    30  kind: Mapping
    31  name: quote-backend
    32  prefix: /backend/
    33  service: quote:80
    34  `
    35  
    36  	svc := &kates.Service{
    37  		TypeMeta: metav1.TypeMeta{
    38  			Kind: "Service",
    39  		},
    40  		ObjectMeta: metav1.ObjectMeta{
    41  			Name:      "svc",
    42  			Namespace: "ambassador",
    43  			Annotations: map[string]string{
    44  				"getambassador.io/config": mapping,
    45  			},
    46  		},
    47  	}
    48  
    49  	svcWithEmptyAnnotation := &kates.Service{
    50  		TypeMeta: metav1.TypeMeta{
    51  			Kind: "Service",
    52  		},
    53  		ObjectMeta: metav1.ObjectMeta{
    54  			Name:      "svc-empty",
    55  			Namespace: "ambassador",
    56  			Annotations: map[string]string{
    57  				"getambassador.io/config": "",
    58  			},
    59  		},
    60  	}
    61  
    62  	svcWithMissingAnnotation := &kates.Service{
    63  		TypeMeta: metav1.TypeMeta{
    64  			Kind: "Service",
    65  		},
    66  		ObjectMeta: metav1.ObjectMeta{
    67  			Name:        "svc-missing",
    68  			Namespace:   "ambassador",
    69  			Annotations: map[string]string{},
    70  		},
    71  	}
    72  
    73  	ingHost := `
    74  ---
    75  apiVersion: getambassador.io/v3alpha1
    76  kind: Mapping
    77  name: cool-mapping
    78  prefix: /blah/
    79  `
    80  
    81  	ingress := &k8s_resource_types.Ingress{
    82  		TypeMeta: metav1.TypeMeta{
    83  			Kind: "Ingress",
    84  		},
    85  		ObjectMeta: metav1.ObjectMeta{
    86  			Name:      "ingress",
    87  			Namespace: "somens",
    88  			Annotations: map[string]string{
    89  				"getambassador.io/config": ingHost,
    90  			},
    91  		},
    92  	}
    93  
    94  	ambSvcAnnotations := `
    95  ---
    96  apiVersion: getambassador.io/v3alpha1
    97  kind: Module
    98  name: ambassador
    99  config:
   100    diagnostics:
   101      enabled: true
   102  ---
   103  apiVersion: getambassador.io/v3alpha1
   104  kind: KubernetesEndpointResolver
   105  name: endpoint`
   106  
   107  	ambSvc := &kates.Service{
   108  		TypeMeta: metav1.TypeMeta{
   109  			Kind: "Service",
   110  		},
   111  		ObjectMeta: metav1.ObjectMeta{
   112  			Name:      "ambassador",
   113  			Namespace: "ambassador",
   114  			Annotations: map[string]string{
   115  				"getambassador.io/config": ambSvcAnnotations,
   116  			},
   117  		},
   118  	}
   119  
   120  	unparsedAnnotation := `
   121  ---
   122  apiVersion: getambassador.io/v3alpha1
   123  kind: Mapping
   124  name: dont-parse
   125  prefix: /blah/`
   126  
   127  	ignoredHost := &amb.Host{
   128  		TypeMeta: metav1.TypeMeta{
   129  			Kind: "Host",
   130  		},
   131  		ObjectMeta: metav1.ObjectMeta{
   132  			Name:      "ambassador",
   133  			Namespace: "ambassador",
   134  			Annotations: map[string]string{
   135  				"getambassador.io/config": unparsedAnnotation,
   136  			},
   137  		},
   138  	}
   139  
   140  	ks := &snapshotTypes.KubernetesSnapshot{
   141  		Services:  []*kates.Service{svc, ambSvc, svcWithEmptyAnnotation, svcWithMissingAnnotation},
   142  		Ingresses: []*snapshotTypes.Ingress{{Ingress: *ingress}},
   143  		Hosts:     []*amb.Host{ignoredHost},
   144  	}
   145  
   146  	ctx := dlog.NewTestContext(t, false)
   147  
   148  	err := ks.PopulateAnnotations(ctx)
   149  	assert.NoError(t, err)
   150  	assert.Equal(t, len(ks.Services), 4)
   151  	assert.Equal(t, map[string]snapshotTypes.AnnotationList{
   152  		"Service/svc.ambassador": {
   153  			&amb.Mapping{
   154  				TypeMeta: metav1.TypeMeta{
   155  					Kind:       "Mapping",
   156  					APIVersion: "getambassador.io/v3alpha1",
   157  				},
   158  				ObjectMeta: metav1.ObjectMeta{
   159  					Name:      "quote-backend",
   160  					Namespace: "ambassador",
   161  				},
   162  				Spec: amb.MappingSpec{
   163  					Prefix:  "/backend/",
   164  					Service: "quote:80",
   165  				},
   166  			},
   167  		},
   168  		"Ingress/ingress.somens": {
   169  			&kates.Unstructured{
   170  				Object: map[string]interface{}{
   171  					"apiVersion": "getambassador.io/v3alpha1",
   172  					"kind":       "Mapping",
   173  					"metadata": map[string]interface{}{
   174  						"name":      "cool-mapping",
   175  						"namespace": "somens",
   176  					},
   177  					"spec": map[string]interface{}{
   178  						"prefix": "/blah/",
   179  					},
   180  					"errors": "spec.service in body is required",
   181  				},
   182  			},
   183  		},
   184  		"Service/ambassador.ambassador": {
   185  			&amb.Module{
   186  				TypeMeta: metav1.TypeMeta{
   187  					Kind:       "Module",
   188  					APIVersion: "getambassador.io/v3alpha1",
   189  				},
   190  				ObjectMeta: metav1.ObjectMeta{
   191  					Name:      "ambassador",
   192  					Namespace: "ambassador",
   193  				},
   194  				Spec: amb.ModuleSpec{
   195  					Config: getModuleSpec(t, `{"diagnostics":{"enabled":true}}`),
   196  				},
   197  			},
   198  			&amb.KubernetesEndpointResolver{
   199  				TypeMeta: metav1.TypeMeta{
   200  					Kind:       "KubernetesEndpointResolver",
   201  					APIVersion: "getambassador.io/v3alpha1",
   202  				},
   203  				ObjectMeta: metav1.ObjectMeta{
   204  					Name:      "endpoint",
   205  					Namespace: "ambassador",
   206  				},
   207  			},
   208  		},
   209  	}, ks.Annotations)
   210  }
   211  
   212  func TestConvertAnnotation(t *testing.T) {
   213  	testcases := map[string]struct {
   214  		inputString       string
   215  		inputParentNS     string
   216  		inputParentLabels map[string]string
   217  
   218  		outputObj kates.Object
   219  	}{
   220  		"mapping": {
   221  			inputString: `
   222  ---
   223  apiVersion: getambassador.io/v3alpha1
   224  kind: Mapping
   225  name: cool-mapping
   226  prefix: /blah/
   227  service: quote:80`,
   228  			inputParentNS:     "somens",
   229  			inputParentLabels: map[string]string{},
   230  			outputObj: &amb.Mapping{
   231  				TypeMeta: metav1.TypeMeta{
   232  					Kind:       "Mapping",
   233  					APIVersion: "getambassador.io/v3alpha1",
   234  				},
   235  				ObjectMeta: metav1.ObjectMeta{
   236  					Name:      "cool-mapping",
   237  					Namespace: "somens",
   238  					Labels:    map[string]string{},
   239  				},
   240  				Spec: amb.MappingSpec{
   241  					Prefix:  "/blah/",
   242  					Service: "quote:80",
   243  				},
   244  			},
   245  		},
   246  		"old-group-v0": {
   247  			inputString: `
   248  ---
   249  apiVersion: getambassador.io/v3alpha1
   250  kind: Mapping
   251  name: cool-mapping
   252  prefix: /blah/
   253  service: quote:80`,
   254  			inputParentNS:     "somens",
   255  			inputParentLabels: map[string]string{},
   256  			outputObj: &amb.Mapping{
   257  				TypeMeta: metav1.TypeMeta{
   258  					Kind:       "Mapping",
   259  					APIVersion: "getambassador.io/v3alpha1",
   260  				},
   261  				ObjectMeta: metav1.ObjectMeta{
   262  					Name:      "cool-mapping",
   263  					Namespace: "somens",
   264  					Labels:    map[string]string{},
   265  				},
   266  				Spec: amb.MappingSpec{
   267  					Prefix:  "/blah/",
   268  					Service: "quote:80",
   269  				},
   270  			},
   271  		},
   272  		"old-group-v1": {
   273  			inputString: `
   274  ---
   275  apiVersion: getambassador.io/v3alpha1
   276  kind: Mapping
   277  name: cool-mapping
   278  prefix: /blah/
   279  service: quote:80`,
   280  			inputParentNS:     "somens",
   281  			inputParentLabels: map[string]string{},
   282  			outputObj: &amb.Mapping{
   283  				TypeMeta: metav1.TypeMeta{
   284  					Kind:       "Mapping",
   285  					APIVersion: "getambassador.io/v3alpha1",
   286  				},
   287  				ObjectMeta: metav1.ObjectMeta{
   288  					Name:      "cool-mapping",
   289  					Namespace: "somens",
   290  					Labels:    map[string]string{},
   291  				},
   292  				Spec: amb.MappingSpec{
   293  					Prefix:  "/blah/",
   294  					Service: "quote:80",
   295  				},
   296  			},
   297  		},
   298  		"label-override": {
   299  			inputString: `
   300  ---
   301  apiVersion: getambassador.io/v3alpha1
   302  kind: Mapping
   303  name: cool-mapping
   304  metadata_labels:
   305    bleep: blorp
   306  prefix: /blah/
   307  service: quote:80`,
   308  			inputParentNS:     "somens",
   309  			inputParentLabels: map[string]string{"should": "override"},
   310  			outputObj: &amb.Mapping{
   311  				TypeMeta: metav1.TypeMeta{
   312  					Kind:       "Mapping",
   313  					APIVersion: "getambassador.io/v3alpha1",
   314  				},
   315  				ObjectMeta: metav1.ObjectMeta{
   316  					Name:      "cool-mapping",
   317  					Namespace: "somens",
   318  					Labels: map[string]string{
   319  						"bleep": "blorp",
   320  					},
   321  				},
   322  				Spec: amb.MappingSpec{
   323  					Prefix:  "/blah/",
   324  					Service: "quote:80",
   325  				},
   326  			},
   327  		},
   328  		"parent-labels": {
   329  			inputString: `
   330  ---
   331  apiVersion: getambassador.io/v3alpha1
   332  kind: Mapping
   333  name: cool-mapping
   334  prefix: /blah/
   335  service: quote:80`,
   336  			inputParentNS: "somens",
   337  			inputParentLabels: map[string]string{
   338  				"use": "theselabels",
   339  			},
   340  			outputObj: &amb.Mapping{
   341  				TypeMeta: metav1.TypeMeta{
   342  					Kind:       "Mapping",
   343  					APIVersion: "getambassador.io/v3alpha1",
   344  				},
   345  				ObjectMeta: metav1.ObjectMeta{
   346  					Name:      "cool-mapping",
   347  					Namespace: "somens",
   348  					Labels: map[string]string{
   349  						"use": "theselabels",
   350  					},
   351  				},
   352  				Spec: amb.MappingSpec{
   353  					Prefix:  "/blah/",
   354  					Service: "quote:80",
   355  				},
   356  			},
   357  		},
   358  		"module": {
   359  			inputString: `
   360  ---
   361  apiVersion: getambassador.io/v3alpha1
   362  kind: Module
   363  name: ambassador
   364  config:
   365    diagnostics:
   366      enabled: true`,
   367  			inputParentNS:     "somens",
   368  			inputParentLabels: map[string]string{},
   369  			outputObj: &amb.Module{
   370  				TypeMeta: metav1.TypeMeta{
   371  					Kind:       "Module",
   372  					APIVersion: "getambassador.io/v3alpha1",
   373  				},
   374  				ObjectMeta: metav1.ObjectMeta{
   375  					Name:      "ambassador",
   376  					Namespace: "somens",
   377  					Labels:    map[string]string{},
   378  				},
   379  				Spec: amb.ModuleSpec{
   380  					Config: getModuleSpec(t, `{"diagnostics":{"enabled":true}}`),
   381  				},
   382  			},
   383  		},
   384  	}
   385  
   386  	for tcName, tc := range testcases {
   387  		tc := tc
   388  		t.Run(tcName, func(t *testing.T) {
   389  			parentObj := &kates.Unstructured{
   390  				Object: map[string]interface{}{
   391  					"apiVersion": "v1",
   392  					"kind":       "Service",
   393  					"metadata": map[string]interface{}{
   394  						"name":      "parentname",
   395  						"namespace": tc.inputParentNS,
   396  						"labels": func() map[string]interface{} {
   397  							ret := make(map[string]interface{}, len(tc.inputParentLabels))
   398  							for k, v := range tc.inputParentLabels {
   399  								ret[k] = v
   400  							}
   401  							return ret
   402  						}(),
   403  						"annotations": map[string]interface{}{
   404  							"getambassador.io/config": tc.inputString,
   405  						},
   406  					},
   407  				},
   408  			}
   409  
   410  			ctx := dlog.NewTestContext(t, false)
   411  
   412  			objs, err := snapshotTypes.ParseAnnotationResources(parentObj)
   413  			require.NoError(t, err)
   414  			require.Len(t, objs, 1)
   415  			obj, err := snapshotTypes.ValidateAndConvertObject(ctx, objs[0])
   416  			require.NoError(t, err)
   417  			assert.Equal(t, tc.outputObj, obj)
   418  		})
   419  	}
   420  }
   421  

View as plain text