github.com/nginxinc/kubernetes-ingress@v1.12.5/internal/k8s/validation_test.go (about)

     1  package k8s
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  	"testing"
     8  
     9  	networking "k8s.io/api/networking/v1beta1"
    10  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  	"k8s.io/apimachinery/pkg/util/validation/field"
    12  )
    13  
    14  func TestValidateIngress(t *testing.T) {
    15  	tests := []struct {
    16  		ing            *networking.Ingress
    17  		expectedErrors []string
    18  		msg            string
    19  	}{
    20  		{
    21  			ing: &networking.Ingress{
    22  				Spec: networking.IngressSpec{
    23  					Rules: []networking.IngressRule{
    24  						{
    25  							Host: "example.com",
    26  						},
    27  					},
    28  				},
    29  			},
    30  			expectedErrors: nil,
    31  			msg:            "valid input",
    32  		},
    33  		{
    34  			ing: &networking.Ingress{
    35  				ObjectMeta: meta_v1.ObjectMeta{
    36  					Annotations: map[string]string{
    37  						"nginx.org/mergeable-ingress-type": "invalid",
    38  					},
    39  				},
    40  				Spec: networking.IngressSpec{
    41  					Rules: []networking.IngressRule{
    42  						{
    43  							Host: "",
    44  						},
    45  					},
    46  				},
    47  			},
    48  			expectedErrors: []string{
    49  				`annotations.nginx.org/mergeable-ingress-type: Invalid value: "invalid": must be one of: 'master' or 'minion'`,
    50  				"spec.rules[0].host: Required value",
    51  			},
    52  			msg: "invalid ingress",
    53  		},
    54  		{
    55  			ing: &networking.Ingress{
    56  				ObjectMeta: meta_v1.ObjectMeta{
    57  					Annotations: map[string]string{
    58  						"nginx.org/mergeable-ingress-type": "master",
    59  					},
    60  				},
    61  				Spec: networking.IngressSpec{
    62  					Rules: []networking.IngressRule{
    63  						{
    64  							Host: "example.com",
    65  							IngressRuleValue: networking.IngressRuleValue{
    66  								HTTP: &networking.HTTPIngressRuleValue{
    67  									Paths: []networking.HTTPIngressPath{
    68  										{
    69  											Path: "/",
    70  										},
    71  									},
    72  								},
    73  							},
    74  						},
    75  					},
    76  				},
    77  			},
    78  			expectedErrors: []string{
    79  				"spec.rules[0].http.paths: Too many: 1: must have at most 0 items",
    80  			},
    81  			msg: "invalid master",
    82  		},
    83  		{
    84  			ing: &networking.Ingress{
    85  				ObjectMeta: meta_v1.ObjectMeta{
    86  					Annotations: map[string]string{
    87  						"nginx.org/mergeable-ingress-type": "minion",
    88  					},
    89  				},
    90  				Spec: networking.IngressSpec{
    91  					Rules: []networking.IngressRule{
    92  						{
    93  							Host:             "example.com",
    94  							IngressRuleValue: networking.IngressRuleValue{},
    95  						},
    96  					},
    97  				},
    98  			},
    99  			expectedErrors: []string{
   100  				"spec.rules[0].http.paths: Required value: must include at least one path",
   101  			},
   102  			msg: "invalid minion",
   103  		},
   104  	}
   105  
   106  	for _, test := range tests {
   107  		allErrs := validateIngress(test.ing, false, false, false, false)
   108  		assertion := assertErrors("validateIngress()", test.msg, allErrs, test.expectedErrors)
   109  		if assertion != "" {
   110  			t.Error(assertion)
   111  		}
   112  	}
   113  }
   114  
   115  func TestValidateNginxIngressAnnotations(t *testing.T) {
   116  	tests := []struct {
   117  		annotations           map[string]string
   118  		specServices          map[string]bool
   119  		isPlus                bool
   120  		appProtectEnabled     bool
   121  		internalRoutesEnabled bool
   122  		snippetsEnabled       bool
   123  		expectedErrors        []string
   124  		msg                   string
   125  	}{
   126  		{
   127  			annotations:           map[string]string{},
   128  			specServices:          map[string]bool{},
   129  			isPlus:                false,
   130  			appProtectEnabled:     false,
   131  			internalRoutesEnabled: false,
   132  			expectedErrors:        nil,
   133  			msg:                   "valid no annotations",
   134  		},
   135  
   136  		{
   137  			annotations: map[string]string{
   138  				"nginx.org/lb-method":              "invalid_method",
   139  				"nginx.org/mergeable-ingress-type": "invalid",
   140  			},
   141  			specServices:          map[string]bool{},
   142  			isPlus:                false,
   143  			appProtectEnabled:     false,
   144  			internalRoutesEnabled: false,
   145  			expectedErrors: []string{
   146  				`annotations.nginx.org/lb-method: Invalid value: "invalid_method": Invalid load balancing method: "invalid_method"`,
   147  				`annotations.nginx.org/mergeable-ingress-type: Invalid value: "invalid": must be one of: 'master' or 'minion'`,
   148  			},
   149  			msg: "invalid multiple annotations messages in alphabetical order",
   150  		},
   151  
   152  		{
   153  			annotations: map[string]string{
   154  				"nginx.org/mergeable-ingress-type": "master",
   155  			},
   156  			specServices:          map[string]bool{},
   157  			isPlus:                false,
   158  			appProtectEnabled:     false,
   159  			internalRoutesEnabled: false,
   160  			expectedErrors:        nil,
   161  			msg:                   "valid input with master annotation",
   162  		},
   163  		{
   164  			annotations: map[string]string{
   165  				"nginx.org/mergeable-ingress-type": "minion",
   166  			},
   167  			specServices:          map[string]bool{},
   168  			isPlus:                false,
   169  			appProtectEnabled:     false,
   170  			internalRoutesEnabled: false,
   171  			expectedErrors:        nil,
   172  			msg:                   "valid input with minion annotation",
   173  		},
   174  		{
   175  			annotations: map[string]string{
   176  				"nginx.org/mergeable-ingress-type": "",
   177  			},
   178  			specServices:          map[string]bool{},
   179  			isPlus:                false,
   180  			appProtectEnabled:     false,
   181  			internalRoutesEnabled: false,
   182  			expectedErrors: []string{
   183  				"annotations.nginx.org/mergeable-ingress-type: Required value",
   184  			},
   185  			msg: "invalid mergeable type annotation 1",
   186  		},
   187  		{
   188  			annotations: map[string]string{
   189  				"nginx.org/mergeable-ingress-type": "abc",
   190  			},
   191  			specServices:          map[string]bool{},
   192  			isPlus:                false,
   193  			appProtectEnabled:     false,
   194  			internalRoutesEnabled: false,
   195  			expectedErrors: []string{
   196  				`annotations.nginx.org/mergeable-ingress-type: Invalid value: "abc": must be one of: 'master' or 'minion'`,
   197  			},
   198  			msg: "invalid mergeable type annotation 2",
   199  		},
   200  
   201  		{
   202  			annotations: map[string]string{
   203  				"nginx.org/lb-method": "random",
   204  			},
   205  			specServices:          map[string]bool{},
   206  			isPlus:                false,
   207  			appProtectEnabled:     false,
   208  			internalRoutesEnabled: false,
   209  			expectedErrors:        nil,
   210  			msg:                   "valid nginx.org/lb-method annotation, nginx normal",
   211  		},
   212  		{
   213  			annotations: map[string]string{
   214  				"nginx.org/lb-method": "least_time header",
   215  			},
   216  			specServices:          map[string]bool{},
   217  			isPlus:                false,
   218  			appProtectEnabled:     false,
   219  			internalRoutesEnabled: false,
   220  			expectedErrors: []string{
   221  				`annotations.nginx.org/lb-method: Invalid value: "least_time header": Invalid load balancing method: "least_time header"`,
   222  			},
   223  			msg: "invalid nginx.org/lb-method annotation, nginx plus only",
   224  		},
   225  		{
   226  			annotations: map[string]string{
   227  				"nginx.org/lb-method": "invalid_method",
   228  			},
   229  			specServices:          map[string]bool{},
   230  			isPlus:                false,
   231  			appProtectEnabled:     false,
   232  			internalRoutesEnabled: false,
   233  			expectedErrors: []string{
   234  				`annotations.nginx.org/lb-method: Invalid value: "invalid_method": Invalid load balancing method: "invalid_method"`,
   235  			},
   236  			msg: "invalid nginx.org/lb-method annotation",
   237  		},
   238  
   239  		{
   240  			annotations: map[string]string{
   241  				"nginx.com/health-checks": "true",
   242  			},
   243  			specServices:          map[string]bool{},
   244  			isPlus:                false,
   245  			appProtectEnabled:     false,
   246  			internalRoutesEnabled: false,
   247  			expectedErrors: []string{
   248  				"annotations.nginx.com/health-checks: Forbidden: annotation requires NGINX Plus",
   249  			},
   250  			msg: "invalid nginx.com/health-checks annotation, nginx plus only",
   251  		},
   252  		{
   253  			annotations: map[string]string{
   254  				"nginx.com/health-checks": "true",
   255  			},
   256  			specServices:          map[string]bool{},
   257  			isPlus:                true,
   258  			appProtectEnabled:     false,
   259  			internalRoutesEnabled: false,
   260  			expectedErrors:        nil,
   261  			msg:                   "valid nginx.com/health-checks annotation",
   262  		},
   263  		{
   264  			annotations: map[string]string{
   265  				"nginx.com/health-checks": "not_a_boolean",
   266  			},
   267  			specServices:          map[string]bool{},
   268  			isPlus:                true,
   269  			appProtectEnabled:     false,
   270  			internalRoutesEnabled: false,
   271  			expectedErrors: []string{
   272  				`annotations.nginx.com/health-checks: Invalid value: "not_a_boolean": must be a boolean`,
   273  			},
   274  			msg: "invalid nginx.com/health-checks annotation",
   275  		},
   276  
   277  		{
   278  			annotations: map[string]string{
   279  				"nginx.com/health-checks-mandatory": "true",
   280  			},
   281  			specServices:          map[string]bool{},
   282  			isPlus:                false,
   283  			appProtectEnabled:     false,
   284  			internalRoutesEnabled: false,
   285  			expectedErrors: []string{
   286  				"annotations.nginx.com/health-checks-mandatory: Forbidden: annotation requires NGINX Plus",
   287  			},
   288  			msg: "invalid nginx.com/health-checks-mandatory annotation, nginx plus only",
   289  		},
   290  		{
   291  			annotations: map[string]string{
   292  				"nginx.com/health-checks":           "true",
   293  				"nginx.com/health-checks-mandatory": "true",
   294  			},
   295  			specServices:          map[string]bool{},
   296  			isPlus:                true,
   297  			appProtectEnabled:     false,
   298  			internalRoutesEnabled: false,
   299  			expectedErrors:        nil,
   300  			msg:                   "valid nginx.com/health-checks-mandatory annotation",
   301  		},
   302  		{
   303  			annotations: map[string]string{
   304  				"nginx.com/health-checks":           "true",
   305  				"nginx.com/health-checks-mandatory": "not_a_boolean",
   306  			},
   307  			specServices:          map[string]bool{},
   308  			isPlus:                true,
   309  			appProtectEnabled:     false,
   310  			internalRoutesEnabled: false,
   311  			expectedErrors: []string{
   312  				`annotations.nginx.com/health-checks-mandatory: Invalid value: "not_a_boolean": must be a boolean`,
   313  			},
   314  			msg: "invalid nginx.com/health-checks-mandatory, must be a boolean",
   315  		},
   316  		{
   317  			annotations: map[string]string{
   318  				"nginx.com/health-checks-mandatory": "true",
   319  			},
   320  			specServices:          map[string]bool{},
   321  			isPlus:                true,
   322  			appProtectEnabled:     false,
   323  			internalRoutesEnabled: false,
   324  			expectedErrors: []string{
   325  				"annotations.nginx.com/health-checks-mandatory: Forbidden: related annotation nginx.com/health-checks: must be set",
   326  			},
   327  			msg: "invalid nginx.com/health-checks-mandatory, related annotation nginx.com/health-checks not set",
   328  		},
   329  		{
   330  			annotations: map[string]string{
   331  				"nginx.com/health-checks":           "false",
   332  				"nginx.com/health-checks-mandatory": "true",
   333  			},
   334  			specServices:          map[string]bool{},
   335  			isPlus:                true,
   336  			appProtectEnabled:     false,
   337  			internalRoutesEnabled: false,
   338  			expectedErrors: []string{
   339  				"annotations.nginx.com/health-checks-mandatory: Forbidden: related annotation nginx.com/health-checks: must be true",
   340  			},
   341  			msg: "invalid nginx.com/health-checks-mandatory nginx.com/health-checks is not true",
   342  		},
   343  
   344  		{
   345  			annotations: map[string]string{
   346  				"nginx.com/health-checks-mandatory-queue": "true",
   347  			},
   348  			specServices:          map[string]bool{},
   349  			isPlus:                false,
   350  			appProtectEnabled:     false,
   351  			internalRoutesEnabled: false,
   352  			expectedErrors: []string{
   353  				"annotations.nginx.com/health-checks-mandatory-queue: Forbidden: annotation requires NGINX Plus",
   354  			},
   355  			msg: "invalid nginx.com/health-checks-mandatory-queue annotation, nginx plus only",
   356  		},
   357  		{
   358  			annotations: map[string]string{
   359  				"nginx.com/health-checks":                 "true",
   360  				"nginx.com/health-checks-mandatory":       "true",
   361  				"nginx.com/health-checks-mandatory-queue": "5",
   362  			},
   363  			specServices:          map[string]bool{},
   364  			isPlus:                true,
   365  			appProtectEnabled:     false,
   366  			internalRoutesEnabled: false,
   367  			expectedErrors:        nil,
   368  			msg:                   "valid nginx.com/health-checks-mandatory-queue annotation",
   369  		},
   370  		{
   371  			annotations: map[string]string{
   372  				"nginx.com/health-checks":                 "true",
   373  				"nginx.com/health-checks-mandatory":       "true",
   374  				"nginx.com/health-checks-mandatory-queue": "not_a_number",
   375  			},
   376  			specServices:          map[string]bool{},
   377  			isPlus:                true,
   378  			appProtectEnabled:     false,
   379  			internalRoutesEnabled: false,
   380  			expectedErrors: []string{
   381  				`annotations.nginx.com/health-checks-mandatory-queue: Invalid value: "not_a_number": must be a non-negative integer`,
   382  			},
   383  			msg: "invalid nginx.com/health-checks-mandatory-queue, must be a number",
   384  		},
   385  		{
   386  			annotations: map[string]string{
   387  				"nginx.com/health-checks-mandatory-queue": "5",
   388  			},
   389  			specServices:          map[string]bool{},
   390  			isPlus:                true,
   391  			appProtectEnabled:     false,
   392  			internalRoutesEnabled: false,
   393  			expectedErrors: []string{
   394  				"annotations.nginx.com/health-checks-mandatory-queue: Forbidden: related annotation nginx.com/health-checks-mandatory: must be set",
   395  			},
   396  			msg: "invalid nginx.com/health-checks-mandatory-queue, related annotation nginx.com/health-checks-mandatory not set",
   397  		},
   398  		{
   399  			annotations: map[string]string{
   400  				"nginx.com/health-checks":                 "true",
   401  				"nginx.com/health-checks-mandatory":       "false",
   402  				"nginx.com/health-checks-mandatory-queue": "5",
   403  			},
   404  			specServices:          map[string]bool{},
   405  			isPlus:                true,
   406  			appProtectEnabled:     false,
   407  			internalRoutesEnabled: false,
   408  			expectedErrors: []string{
   409  				"annotations.nginx.com/health-checks-mandatory-queue: Forbidden: related annotation nginx.com/health-checks-mandatory: must be true",
   410  			},
   411  			msg: "invalid nginx.com/health-checks-mandatory-queue nginx.com/health-checks-mandatory is not true",
   412  		},
   413  
   414  		{
   415  			annotations: map[string]string{
   416  				"nginx.com/slow-start": "true",
   417  			},
   418  			specServices:          map[string]bool{},
   419  			isPlus:                false,
   420  			appProtectEnabled:     false,
   421  			internalRoutesEnabled: false,
   422  			expectedErrors: []string{
   423  				"annotations.nginx.com/slow-start: Forbidden: annotation requires NGINX Plus",
   424  			},
   425  			msg: "invalid nginx.com/slow-start annotation, nginx plus only",
   426  		},
   427  		{
   428  			annotations: map[string]string{
   429  				"nginx.com/slow-start": "60s",
   430  			},
   431  			specServices:          map[string]bool{},
   432  			isPlus:                true,
   433  			appProtectEnabled:     false,
   434  			internalRoutesEnabled: false,
   435  			expectedErrors:        nil,
   436  			msg:                   "valid nginx.com/slow-start annotation",
   437  		},
   438  		{
   439  			annotations: map[string]string{
   440  				"nginx.com/slow-start": "not_a_time",
   441  			},
   442  			specServices:          map[string]bool{},
   443  			isPlus:                true,
   444  			appProtectEnabled:     false,
   445  			internalRoutesEnabled: false,
   446  			expectedErrors: []string{
   447  				`annotations.nginx.com/slow-start: Invalid value: "not_a_time": must be a time`,
   448  			},
   449  			msg: "invalid nginx.com/slow-start annotation",
   450  		},
   451  
   452  		{
   453  			annotations: map[string]string{
   454  				"nginx.org/server-tokens": "true",
   455  			},
   456  			specServices:          map[string]bool{},
   457  			isPlus:                false,
   458  			appProtectEnabled:     false,
   459  			internalRoutesEnabled: false,
   460  			expectedErrors:        nil,
   461  			msg:                   "valid nginx.org/server-tokens annotation, nginx",
   462  		},
   463  		{
   464  			annotations: map[string]string{
   465  				"nginx.org/server-tokens": "custom_setting",
   466  			},
   467  			specServices:          map[string]bool{},
   468  			isPlus:                true,
   469  			appProtectEnabled:     false,
   470  			internalRoutesEnabled: false,
   471  			expectedErrors:        nil,
   472  			msg:                   "valid nginx.org/server-tokens annotation, nginx plus",
   473  		},
   474  		{
   475  			annotations: map[string]string{
   476  				"nginx.org/server-tokens": "custom_setting",
   477  			},
   478  			specServices:          map[string]bool{},
   479  			isPlus:                false,
   480  			appProtectEnabled:     false,
   481  			internalRoutesEnabled: false,
   482  			expectedErrors: []string{
   483  				`annotations.nginx.org/server-tokens: Invalid value: "custom_setting": must be a boolean`,
   484  			},
   485  			msg: "invalid nginx.org/server-tokens annotation, must be a boolean",
   486  		},
   487  
   488  		{
   489  			annotations: map[string]string{
   490  				"nginx.org/server-snippets": "snippet-1",
   491  			},
   492  			specServices:          map[string]bool{},
   493  			isPlus:                false,
   494  			appProtectEnabled:     false,
   495  			internalRoutesEnabled: false,
   496  			snippetsEnabled:       true,
   497  			expectedErrors:        nil,
   498  			msg:                   "valid nginx.org/server-snippets annotation, single-value",
   499  		},
   500  		{
   501  			annotations: map[string]string{
   502  				"nginx.org/server-snippets": "snippet-1\nsnippet-2\nsnippet-3",
   503  			},
   504  			specServices:          map[string]bool{},
   505  			isPlus:                false,
   506  			appProtectEnabled:     false,
   507  			internalRoutesEnabled: false,
   508  			snippetsEnabled:       true,
   509  			expectedErrors:        nil,
   510  			msg:                   "valid nginx.org/server-snippets annotation, multi-value",
   511  		},
   512  		{
   513  			annotations: map[string]string{
   514  				"nginx.org/server-snippets": "snippet-1",
   515  			},
   516  			specServices:          map[string]bool{},
   517  			isPlus:                false,
   518  			appProtectEnabled:     false,
   519  			internalRoutesEnabled: false,
   520  			snippetsEnabled:       false,
   521  			expectedErrors: []string{
   522  				`annotations.nginx.org/server-snippets: Forbidden: snippet specified but snippets feature is not enabled`,
   523  			},
   524  			msg: "invalid nginx.org/server-snippets annotation when snippets are disabled",
   525  		},
   526  
   527  		{
   528  			annotations: map[string]string{
   529  				"nginx.org/location-snippets": "snippet-1",
   530  			},
   531  			specServices:          map[string]bool{},
   532  			isPlus:                false,
   533  			appProtectEnabled:     false,
   534  			internalRoutesEnabled: false,
   535  			snippetsEnabled:       true,
   536  			expectedErrors:        nil,
   537  			msg:                   "valid nginx.org/location-snippets annotation, single-value",
   538  		},
   539  		{
   540  			annotations: map[string]string{
   541  				"nginx.org/location-snippets": "snippet-1\nsnippet-2\nsnippet-3",
   542  			},
   543  			specServices:          map[string]bool{},
   544  			isPlus:                false,
   545  			appProtectEnabled:     false,
   546  			internalRoutesEnabled: false,
   547  			snippetsEnabled:       true,
   548  			expectedErrors:        nil,
   549  			msg:                   "valid nginx.org/location-snippets annotation, multi-value",
   550  		},
   551  		{
   552  			annotations: map[string]string{
   553  				"nginx.org/location-snippets": "snippet-1",
   554  			},
   555  			specServices:          map[string]bool{},
   556  			isPlus:                false,
   557  			appProtectEnabled:     false,
   558  			internalRoutesEnabled: false,
   559  			snippetsEnabled:       false,
   560  			expectedErrors: []string{
   561  				`annotations.nginx.org/location-snippets: Forbidden: snippet specified but snippets feature is not enabled`,
   562  			},
   563  			msg: "invalid nginx.org/location-snippets annotation when snippets are disabled",
   564  		},
   565  
   566  		{
   567  			annotations: map[string]string{
   568  				"nginx.org/proxy-connect-timeout": "10s",
   569  			},
   570  			specServices:          map[string]bool{},
   571  			isPlus:                false,
   572  			appProtectEnabled:     false,
   573  			internalRoutesEnabled: false,
   574  			expectedErrors:        nil,
   575  			msg:                   "valid nginx.org/proxy-connect-timeout annotation",
   576  		},
   577  		{
   578  			annotations: map[string]string{
   579  				"nginx.org/proxy-connect-timeout": "not_a_time",
   580  			},
   581  			specServices:          map[string]bool{},
   582  			isPlus:                false,
   583  			appProtectEnabled:     false,
   584  			internalRoutesEnabled: false,
   585  			expectedErrors: []string{
   586  				`annotations.nginx.org/proxy-connect-timeout: Invalid value: "not_a_time": must be a time`,
   587  			},
   588  			msg: "invalid nginx.org/proxy-connect-timeout annotation",
   589  		},
   590  
   591  		{
   592  			annotations: map[string]string{
   593  				"nginx.org/proxy-read-timeout": "10s",
   594  			},
   595  			specServices:          map[string]bool{},
   596  			isPlus:                false,
   597  			appProtectEnabled:     false,
   598  			internalRoutesEnabled: false,
   599  			expectedErrors:        nil,
   600  			msg:                   "valid nginx.org/proxy-read-timeout annotation",
   601  		},
   602  		{
   603  			annotations: map[string]string{
   604  				"nginx.org/proxy-read-timeout": "not_a_time",
   605  			},
   606  			specServices:          map[string]bool{},
   607  			isPlus:                false,
   608  			appProtectEnabled:     false,
   609  			internalRoutesEnabled: false,
   610  			expectedErrors: []string{
   611  				`annotations.nginx.org/proxy-read-timeout: Invalid value: "not_a_time": must be a time`,
   612  			},
   613  			msg: "invalid nginx.org/proxy-read-timeout annotation",
   614  		},
   615  
   616  		{
   617  			annotations: map[string]string{
   618  				"nginx.org/proxy-send-timeout": "10s",
   619  			},
   620  			specServices:          map[string]bool{},
   621  			isPlus:                false,
   622  			appProtectEnabled:     false,
   623  			internalRoutesEnabled: false,
   624  			expectedErrors:        nil,
   625  			msg:                   "valid nginx.org/proxy-send-timeout annotation",
   626  		},
   627  		{
   628  			annotations: map[string]string{
   629  				"nginx.org/proxy-send-timeout": "not_a_time",
   630  			},
   631  			specServices:          map[string]bool{},
   632  			isPlus:                false,
   633  			appProtectEnabled:     false,
   634  			internalRoutesEnabled: false,
   635  			expectedErrors: []string{
   636  				`annotations.nginx.org/proxy-send-timeout: Invalid value: "not_a_time": must be a time`,
   637  			},
   638  			msg: "invalid nginx.org/proxy-send-timeout annotation",
   639  		},
   640  
   641  		{
   642  			annotations: map[string]string{
   643  				"nginx.org/proxy-hide-headers": "header-1",
   644  			},
   645  			specServices:          map[string]bool{},
   646  			isPlus:                false,
   647  			appProtectEnabled:     false,
   648  			internalRoutesEnabled: false,
   649  			expectedErrors:        nil,
   650  			msg:                   "valid nginx.org/proxy-hide-headers annotation, single-value",
   651  		},
   652  		{
   653  			annotations: map[string]string{
   654  				"nginx.org/proxy-hide-headers": "header-1,header-2,header-3",
   655  			},
   656  			specServices:          map[string]bool{},
   657  			isPlus:                false,
   658  			appProtectEnabled:     false,
   659  			internalRoutesEnabled: false,
   660  			expectedErrors:        nil,
   661  			msg:                   "valid nginx.org/proxy-hide-headers annotation, multi-value",
   662  		},
   663  
   664  		{
   665  			annotations: map[string]string{
   666  				"nginx.org/proxy-pass-headers": "header-1",
   667  			},
   668  			specServices:          map[string]bool{},
   669  			isPlus:                false,
   670  			appProtectEnabled:     false,
   671  			internalRoutesEnabled: false,
   672  			expectedErrors:        nil,
   673  			msg:                   "valid nginx.org/proxy-pass-headers annotation, single-value",
   674  		},
   675  		{
   676  			annotations: map[string]string{
   677  				"nginx.org/proxy-pass-headers": "header-1,header-2,header-3",
   678  			},
   679  			specServices:          map[string]bool{},
   680  			isPlus:                false,
   681  			appProtectEnabled:     false,
   682  			internalRoutesEnabled: false,
   683  			expectedErrors:        nil,
   684  			msg:                   "valid nginx.org/proxy-pass-headers annotation, multi-value",
   685  		},
   686  
   687  		{
   688  			annotations: map[string]string{
   689  				"nginx.org/client-max-body-size": "16M",
   690  			},
   691  			specServices:          map[string]bool{},
   692  			isPlus:                false,
   693  			appProtectEnabled:     false,
   694  			internalRoutesEnabled: false,
   695  			expectedErrors:        nil,
   696  			msg:                   "valid nginx.org/client-max-body-size annotation",
   697  		},
   698  		{
   699  			annotations: map[string]string{
   700  				"nginx.org/client-max-body-size": "not_an_offset",
   701  			},
   702  			specServices:          map[string]bool{},
   703  			isPlus:                false,
   704  			appProtectEnabled:     false,
   705  			internalRoutesEnabled: false,
   706  			expectedErrors: []string{
   707  				`annotations.nginx.org/client-max-body-size: Invalid value: "not_an_offset": must be an offset`,
   708  			},
   709  			msg: "invalid nginx.org/client-max-body-size annotation",
   710  		},
   711  
   712  		{
   713  			annotations: map[string]string{
   714  				"nginx.org/redirect-to-https": "true",
   715  			},
   716  			specServices:          map[string]bool{},
   717  			isPlus:                false,
   718  			appProtectEnabled:     false,
   719  			internalRoutesEnabled: false,
   720  			expectedErrors:        nil,
   721  			msg:                   "valid nginx.org/redirect-to-https annotation",
   722  		},
   723  		{
   724  			annotations: map[string]string{
   725  				"nginx.org/redirect-to-https": "not_a_boolean",
   726  			},
   727  			specServices:          map[string]bool{},
   728  			isPlus:                false,
   729  			appProtectEnabled:     false,
   730  			internalRoutesEnabled: false,
   731  			expectedErrors: []string{
   732  				`annotations.nginx.org/redirect-to-https: Invalid value: "not_a_boolean": must be a boolean`,
   733  			},
   734  			msg: "invalid nginx.org/redirect-to-https annotation",
   735  		},
   736  
   737  		{
   738  			annotations: map[string]string{
   739  				"ingress.kubernetes.io/ssl-redirect": "true",
   740  			},
   741  			specServices:          map[string]bool{},
   742  			isPlus:                false,
   743  			appProtectEnabled:     false,
   744  			internalRoutesEnabled: false,
   745  			expectedErrors:        nil,
   746  			msg:                   "valid ingress.kubernetes.io/ssl-redirect annotation",
   747  		},
   748  		{
   749  			annotations: map[string]string{
   750  				"ingress.kubernetes.io/ssl-redirect": "not_a_boolean",
   751  			},
   752  			specServices:          map[string]bool{},
   753  			isPlus:                false,
   754  			appProtectEnabled:     false,
   755  			internalRoutesEnabled: false,
   756  			expectedErrors: []string{
   757  				`annotations.ingress.kubernetes.io/ssl-redirect: Invalid value: "not_a_boolean": must be a boolean`,
   758  			},
   759  			msg: "invalid ingress.kubernetes.io/ssl-redirect annotation",
   760  		},
   761  
   762  		{
   763  			annotations: map[string]string{
   764  				"nginx.org/proxy-buffering": "true",
   765  			},
   766  			specServices:          map[string]bool{},
   767  			isPlus:                false,
   768  			appProtectEnabled:     false,
   769  			internalRoutesEnabled: false,
   770  			expectedErrors:        nil,
   771  			msg:                   "valid nginx.org/proxy-buffering annotation",
   772  		},
   773  		{
   774  			annotations: map[string]string{
   775  				"nginx.org/proxy-buffering": "not_a_boolean",
   776  			},
   777  			specServices:          map[string]bool{},
   778  			isPlus:                false,
   779  			appProtectEnabled:     false,
   780  			internalRoutesEnabled: false,
   781  			expectedErrors: []string{
   782  				`annotations.nginx.org/proxy-buffering: Invalid value: "not_a_boolean": must be a boolean`,
   783  			},
   784  			msg: "invalid nginx.org/proxy-buffering annotation",
   785  		},
   786  
   787  		{
   788  			annotations: map[string]string{
   789  				"nginx.org/hsts": "true",
   790  			},
   791  			specServices:          map[string]bool{},
   792  			isPlus:                false,
   793  			appProtectEnabled:     false,
   794  			internalRoutesEnabled: false,
   795  			expectedErrors:        nil,
   796  			msg:                   "valid nginx.org/hsts annotation",
   797  		},
   798  		{
   799  			annotations: map[string]string{
   800  				"nginx.org/hsts": "not_a_boolean",
   801  			},
   802  			specServices:          map[string]bool{},
   803  			isPlus:                false,
   804  			appProtectEnabled:     false,
   805  			internalRoutesEnabled: false,
   806  			expectedErrors: []string{
   807  				`annotations.nginx.org/hsts: Invalid value: "not_a_boolean": must be a boolean`,
   808  			},
   809  			msg: "invalid nginx.org/hsts annotation",
   810  		},
   811  
   812  		{
   813  			annotations: map[string]string{
   814  				"nginx.org/hsts":         "true",
   815  				"nginx.org/hsts-max-age": "120",
   816  			},
   817  			specServices:          map[string]bool{},
   818  			isPlus:                false,
   819  			appProtectEnabled:     false,
   820  			internalRoutesEnabled: false,
   821  			expectedErrors:        nil,
   822  			msg:                   "valid nginx.org/hsts-max-age annotation",
   823  		},
   824  		{
   825  			annotations: map[string]string{
   826  				"nginx.org/hsts":         "false",
   827  				"nginx.org/hsts-max-age": "120",
   828  			},
   829  			specServices:          map[string]bool{},
   830  			isPlus:                false,
   831  			appProtectEnabled:     false,
   832  			internalRoutesEnabled: false,
   833  			expectedErrors:        nil,
   834  			msg:                   "valid nginx.org/hsts-max-age nginx.org/hsts can be false",
   835  		},
   836  		{
   837  			annotations: map[string]string{
   838  				"nginx.org/hsts":         "true",
   839  				"nginx.org/hsts-max-age": "not_a_number",
   840  			},
   841  			specServices:          map[string]bool{},
   842  			isPlus:                false,
   843  			appProtectEnabled:     false,
   844  			internalRoutesEnabled: false,
   845  			expectedErrors: []string{
   846  				`annotations.nginx.org/hsts-max-age: Invalid value: "not_a_number": must be an integer`,
   847  			},
   848  			msg: "invalid nginx.org/hsts-max-age, must be a number",
   849  		},
   850  		{
   851  			annotations: map[string]string{
   852  				"nginx.org/hsts-max-age": "true",
   853  			},
   854  			specServices:          map[string]bool{},
   855  			isPlus:                false,
   856  			appProtectEnabled:     false,
   857  			internalRoutesEnabled: false,
   858  			expectedErrors: []string{
   859  				"annotations.nginx.org/hsts-max-age: Forbidden: related annotation nginx.org/hsts: must be set",
   860  			},
   861  			msg: "invalid nginx.org/hsts-max-age, related annotation nginx.org/hsts not set",
   862  		},
   863  
   864  		{
   865  			annotations: map[string]string{
   866  				"nginx.org/hsts":                    "true",
   867  				"nginx.org/hsts-include-subdomains": "true",
   868  			},
   869  			specServices:          map[string]bool{},
   870  			isPlus:                false,
   871  			appProtectEnabled:     false,
   872  			internalRoutesEnabled: false,
   873  			expectedErrors:        nil,
   874  			msg:                   "valid nginx.org/hsts-include-subdomains annotation",
   875  		},
   876  		{
   877  			annotations: map[string]string{
   878  				"nginx.org/hsts":                    "false",
   879  				"nginx.org/hsts-include-subdomains": "true",
   880  			},
   881  			specServices:          map[string]bool{},
   882  			isPlus:                false,
   883  			appProtectEnabled:     false,
   884  			internalRoutesEnabled: false,
   885  			expectedErrors:        nil,
   886  			msg:                   "valid nginx.org/hsts-include-subdomains, nginx.org/hsts can be false",
   887  		},
   888  		{
   889  			annotations: map[string]string{
   890  				"nginx.org/hsts":                    "true",
   891  				"nginx.org/hsts-include-subdomains": "not_a_boolean",
   892  			},
   893  			specServices:          map[string]bool{},
   894  			isPlus:                false,
   895  			appProtectEnabled:     false,
   896  			internalRoutesEnabled: false,
   897  			expectedErrors: []string{
   898  				`annotations.nginx.org/hsts-include-subdomains: Invalid value: "not_a_boolean": must be a boolean`,
   899  			},
   900  			msg: "invalid nginx.org/hsts-include-subdomains, must be a boolean",
   901  		},
   902  		{
   903  			annotations: map[string]string{
   904  				"nginx.org/hsts-include-subdomains": "true",
   905  			},
   906  			specServices:          map[string]bool{},
   907  			isPlus:                false,
   908  			appProtectEnabled:     false,
   909  			internalRoutesEnabled: false,
   910  			expectedErrors: []string{
   911  				"annotations.nginx.org/hsts-include-subdomains: Forbidden: related annotation nginx.org/hsts: must be set",
   912  			},
   913  			msg: "invalid nginx.org/hsts-include-subdomains, related annotation nginx.org/hsts not set",
   914  		},
   915  
   916  		{
   917  			annotations: map[string]string{
   918  				"nginx.org/hsts":              "true",
   919  				"nginx.org/hsts-behind-proxy": "true",
   920  			},
   921  			specServices:          map[string]bool{},
   922  			isPlus:                false,
   923  			appProtectEnabled:     false,
   924  			internalRoutesEnabled: false,
   925  			expectedErrors:        nil,
   926  			msg:                   "valid nginx.org/hsts-behind-proxy annotation",
   927  		},
   928  		{
   929  			annotations: map[string]string{
   930  				"nginx.org/hsts":              "false",
   931  				"nginx.org/hsts-behind-proxy": "true",
   932  			},
   933  			specServices:          map[string]bool{},
   934  			isPlus:                false,
   935  			appProtectEnabled:     false,
   936  			internalRoutesEnabled: false,
   937  			expectedErrors:        nil,
   938  			msg:                   "valid nginx.org/hsts-behind-proxy, nginx.org/hsts can be false",
   939  		},
   940  		{
   941  			annotations: map[string]string{
   942  				"nginx.org/hsts":              "true",
   943  				"nginx.org/hsts-behind-proxy": "not_a_boolean",
   944  			},
   945  			specServices:          map[string]bool{},
   946  			isPlus:                false,
   947  			appProtectEnabled:     false,
   948  			internalRoutesEnabled: false,
   949  			expectedErrors: []string{
   950  				`annotations.nginx.org/hsts-behind-proxy: Invalid value: "not_a_boolean": must be a boolean`,
   951  			},
   952  			msg: "invalid nginx.org/hsts-behind-proxy, must be a boolean",
   953  		},
   954  		{
   955  			annotations: map[string]string{
   956  				"nginx.org/hsts-behind-proxy": "true",
   957  			},
   958  			specServices:          map[string]bool{},
   959  			isPlus:                false,
   960  			appProtectEnabled:     false,
   961  			internalRoutesEnabled: false,
   962  			expectedErrors: []string{
   963  				"annotations.nginx.org/hsts-behind-proxy: Forbidden: related annotation nginx.org/hsts: must be set",
   964  			},
   965  			msg: "invalid nginx.org/hsts-behind-proxy, related annotation nginx.org/hsts not set",
   966  		},
   967  
   968  		{
   969  			annotations: map[string]string{
   970  				"nginx.org/proxy-buffers": "8 8k",
   971  			},
   972  			specServices:          map[string]bool{},
   973  			isPlus:                false,
   974  			appProtectEnabled:     false,
   975  			internalRoutesEnabled: false,
   976  			expectedErrors:        nil,
   977  			msg:                   "valid nginx.org/proxy-buffers annotation",
   978  		},
   979  		{
   980  			annotations: map[string]string{
   981  				"nginx.org/proxy-buffers": "not_a_proxy_buffers_spec",
   982  			},
   983  			specServices:          map[string]bool{},
   984  			isPlus:                false,
   985  			appProtectEnabled:     false,
   986  			internalRoutesEnabled: false,
   987  			expectedErrors: []string{
   988  				`annotations.nginx.org/proxy-buffers: Invalid value: "not_a_proxy_buffers_spec": must be a proxy buffer spec`,
   989  			},
   990  			msg: "invalid nginx.org/proxy-buffers annotation",
   991  		},
   992  
   993  		{
   994  			annotations: map[string]string{
   995  				"nginx.org/proxy-buffer-size": "16k",
   996  			},
   997  			specServices:          map[string]bool{},
   998  			isPlus:                false,
   999  			appProtectEnabled:     false,
  1000  			internalRoutesEnabled: false,
  1001  			expectedErrors:        nil,
  1002  			msg:                   "valid nginx.org/proxy-buffer-size annotation",
  1003  		},
  1004  		{
  1005  			annotations: map[string]string{
  1006  				"nginx.org/proxy-buffer-size": "not_a_size",
  1007  			},
  1008  			specServices:          map[string]bool{},
  1009  			isPlus:                false,
  1010  			appProtectEnabled:     false,
  1011  			internalRoutesEnabled: false,
  1012  			expectedErrors: []string{
  1013  				`annotations.nginx.org/proxy-buffer-size: Invalid value: "not_a_size": must be a size`,
  1014  			},
  1015  			msg: "invalid nginx.org/proxy-buffer-size annotation",
  1016  		},
  1017  
  1018  		{
  1019  			annotations: map[string]string{
  1020  				"nginx.org/proxy-max-temp-file-size": "128M",
  1021  			},
  1022  			specServices:          map[string]bool{},
  1023  			isPlus:                false,
  1024  			appProtectEnabled:     false,
  1025  			internalRoutesEnabled: false,
  1026  			expectedErrors:        nil,
  1027  			msg:                   "valid nginx.org/proxy-max-temp-file-size annotation",
  1028  		},
  1029  		{
  1030  			annotations: map[string]string{
  1031  				"nginx.org/proxy-max-temp-file-size": "not_a_size",
  1032  			},
  1033  			specServices:          map[string]bool{},
  1034  			isPlus:                false,
  1035  			appProtectEnabled:     false,
  1036  			internalRoutesEnabled: false,
  1037  			expectedErrors: []string{
  1038  				`annotations.nginx.org/proxy-max-temp-file-size: Invalid value: "not_a_size": must be a size`,
  1039  			},
  1040  			msg: "invalid nginx.org/proxy-max-temp-file-size annotation",
  1041  		},
  1042  
  1043  		{
  1044  			annotations: map[string]string{
  1045  				"nginx.org/upstream-zone-size": "512k",
  1046  			},
  1047  			specServices:          map[string]bool{},
  1048  			isPlus:                false,
  1049  			appProtectEnabled:     false,
  1050  			internalRoutesEnabled: false,
  1051  			expectedErrors:        nil,
  1052  			msg:                   "valid nginx.org/upstream-zone-size annotation",
  1053  		},
  1054  		{
  1055  			annotations: map[string]string{
  1056  				"nginx.org/upstream-zone-size": "not a size",
  1057  			},
  1058  			specServices:          map[string]bool{},
  1059  			isPlus:                false,
  1060  			appProtectEnabled:     false,
  1061  			internalRoutesEnabled: false,
  1062  			expectedErrors: []string{
  1063  				`annotations.nginx.org/upstream-zone-size: Invalid value: "not a size": must be a size`,
  1064  			},
  1065  			msg: "invalid nginx.org/upstream-zone-size annotation",
  1066  		},
  1067  
  1068  		{
  1069  			annotations: map[string]string{
  1070  				"nginx.com/jwt-realm": "true",
  1071  			},
  1072  			specServices:          map[string]bool{},
  1073  			isPlus:                false,
  1074  			appProtectEnabled:     false,
  1075  			internalRoutesEnabled: false,
  1076  			expectedErrors: []string{
  1077  				"annotations.nginx.com/jwt-realm: Forbidden: annotation requires NGINX Plus",
  1078  			},
  1079  			msg: "invalid nginx.com/jwt-realm annotation, nginx plus only",
  1080  		},
  1081  		{
  1082  			annotations: map[string]string{
  1083  				"nginx.com/jwt-realm": "my-jwt-realm",
  1084  			},
  1085  			specServices:          map[string]bool{},
  1086  			isPlus:                true,
  1087  			appProtectEnabled:     false,
  1088  			internalRoutesEnabled: false,
  1089  			expectedErrors:        nil,
  1090  			msg:                   "valid nginx.com/jwt-realm annotation",
  1091  		},
  1092  
  1093  		{
  1094  			annotations: map[string]string{
  1095  				"nginx.com/jwt-key": "true",
  1096  			},
  1097  			specServices:          map[string]bool{},
  1098  			isPlus:                false,
  1099  			appProtectEnabled:     false,
  1100  			internalRoutesEnabled: false,
  1101  			expectedErrors: []string{
  1102  				"annotations.nginx.com/jwt-key: Forbidden: annotation requires NGINX Plus",
  1103  			},
  1104  			msg: "invalid nginx.com/jwt-key annotation, nginx plus only",
  1105  		},
  1106  		{
  1107  			annotations: map[string]string{
  1108  				"nginx.com/jwt-key": "my-jwk",
  1109  			},
  1110  			specServices:          map[string]bool{},
  1111  			isPlus:                true,
  1112  			appProtectEnabled:     false,
  1113  			internalRoutesEnabled: false,
  1114  			expectedErrors:        nil,
  1115  			msg:                   "valid nginx.com/jwt-key annotation",
  1116  		},
  1117  
  1118  		{
  1119  			annotations: map[string]string{
  1120  				"nginx.com/jwt-token": "true",
  1121  			},
  1122  			specServices:          map[string]bool{},
  1123  			isPlus:                false,
  1124  			appProtectEnabled:     false,
  1125  			internalRoutesEnabled: false,
  1126  			expectedErrors: []string{
  1127  				"annotations.nginx.com/jwt-token: Forbidden: annotation requires NGINX Plus",
  1128  			},
  1129  			msg: "invalid nginx.com/jwt-token annotation, nginx plus only",
  1130  		},
  1131  		{
  1132  			annotations: map[string]string{
  1133  				"nginx.com/jwt-token": "$cookie_auth_token",
  1134  			},
  1135  			specServices:          map[string]bool{},
  1136  			isPlus:                true,
  1137  			appProtectEnabled:     false,
  1138  			internalRoutesEnabled: false,
  1139  			expectedErrors:        nil,
  1140  			msg:                   "valid nginx.com/jwt-token annotation",
  1141  		},
  1142  
  1143  		{
  1144  			annotations: map[string]string{
  1145  				"nginx.com/jwt-login-url": "true",
  1146  			},
  1147  			specServices:          map[string]bool{},
  1148  			isPlus:                false,
  1149  			appProtectEnabled:     false,
  1150  			internalRoutesEnabled: false,
  1151  			expectedErrors: []string{
  1152  				"annotations.nginx.com/jwt-login-url: Forbidden: annotation requires NGINX Plus",
  1153  			},
  1154  			msg: "invalid nginx.com/jwt-login-url annotation, nginx plus only",
  1155  		},
  1156  		{
  1157  			annotations: map[string]string{
  1158  				"nginx.com/jwt-login-url": "https://login.example.com",
  1159  			},
  1160  			specServices:          map[string]bool{},
  1161  			isPlus:                true,
  1162  			appProtectEnabled:     false,
  1163  			internalRoutesEnabled: false,
  1164  			expectedErrors:        nil,
  1165  			msg:                   "valid nginx.com/jwt-login-url annotation",
  1166  		},
  1167  
  1168  		{
  1169  			annotations: map[string]string{
  1170  				"nginx.org/listen-ports": "80,8080,9090",
  1171  			},
  1172  			specServices:          map[string]bool{},
  1173  			isPlus:                false,
  1174  			appProtectEnabled:     false,
  1175  			internalRoutesEnabled: false,
  1176  			expectedErrors:        nil,
  1177  			msg:                   "valid nginx.org/listen-ports annotation",
  1178  		},
  1179  		{
  1180  			annotations: map[string]string{
  1181  				"nginx.org/listen-ports": "not_a_port_list",
  1182  			},
  1183  			specServices:          map[string]bool{},
  1184  			isPlus:                false,
  1185  			appProtectEnabled:     false,
  1186  			internalRoutesEnabled: false,
  1187  			expectedErrors: []string{
  1188  				`annotations.nginx.org/listen-ports: Invalid value: "not_a_port_list": must be a comma-separated list of port numbers`,
  1189  			},
  1190  			msg: "invalid nginx.org/listen-ports annotation",
  1191  		},
  1192  
  1193  		{
  1194  			annotations: map[string]string{
  1195  				"nginx.org/listen-ports-ssl": "443,8443",
  1196  			},
  1197  			specServices:          map[string]bool{},
  1198  			isPlus:                false,
  1199  			appProtectEnabled:     false,
  1200  			internalRoutesEnabled: false,
  1201  			expectedErrors:        nil,
  1202  			msg:                   "valid nginx.org/listen-ports-ssl annotation",
  1203  		},
  1204  		{
  1205  			annotations: map[string]string{
  1206  				"nginx.org/listen-ports-ssl": "not_a_port_list",
  1207  			},
  1208  			specServices:          map[string]bool{},
  1209  			isPlus:                false,
  1210  			appProtectEnabled:     false,
  1211  			internalRoutesEnabled: false,
  1212  			expectedErrors: []string{
  1213  				`annotations.nginx.org/listen-ports-ssl: Invalid value: "not_a_port_list": must be a comma-separated list of port numbers`,
  1214  			},
  1215  			msg: "invalid nginx.org/listen-ports-ssl annotation",
  1216  		},
  1217  
  1218  		{
  1219  			annotations: map[string]string{
  1220  				"nginx.org/keepalive": "1000",
  1221  			},
  1222  			specServices:          map[string]bool{},
  1223  			isPlus:                false,
  1224  			appProtectEnabled:     false,
  1225  			internalRoutesEnabled: false,
  1226  			expectedErrors:        nil,
  1227  			msg:                   "valid nginx.org/keepalive annotation",
  1228  		},
  1229  		{
  1230  			annotations: map[string]string{
  1231  				"nginx.org/keepalive": "not_a_number",
  1232  			},
  1233  			specServices:          map[string]bool{},
  1234  			isPlus:                false,
  1235  			appProtectEnabled:     false,
  1236  			internalRoutesEnabled: false,
  1237  			expectedErrors: []string{
  1238  				`annotations.nginx.org/keepalive: Invalid value: "not_a_number": must be an integer`,
  1239  			},
  1240  			msg: "invalid nginx.org/keepalive annotation",
  1241  		},
  1242  
  1243  		{
  1244  			annotations: map[string]string{
  1245  				"nginx.org/max-fails": "5",
  1246  			},
  1247  			specServices:          map[string]bool{},
  1248  			isPlus:                false,
  1249  			appProtectEnabled:     false,
  1250  			internalRoutesEnabled: false,
  1251  			expectedErrors:        nil,
  1252  			msg:                   "valid nginx.org/max-fails annotation",
  1253  		},
  1254  		{
  1255  			annotations: map[string]string{
  1256  				"nginx.org/max-fails": "-100",
  1257  			},
  1258  			specServices:          map[string]bool{},
  1259  			isPlus:                false,
  1260  			appProtectEnabled:     false,
  1261  			internalRoutesEnabled: false,
  1262  			expectedErrors: []string{
  1263  				`annotations.nginx.org/max-fails: Invalid value: "-100": must be a non-negative integer`,
  1264  			},
  1265  			msg: "invalid nginx.org/max-fails annotation, negative number",
  1266  		},
  1267  		{
  1268  			annotations: map[string]string{
  1269  				"nginx.org/max-fails": "not_a_number",
  1270  			},
  1271  			specServices:          map[string]bool{},
  1272  			isPlus:                false,
  1273  			appProtectEnabled:     false,
  1274  			internalRoutesEnabled: false,
  1275  			expectedErrors: []string{
  1276  				`annotations.nginx.org/max-fails: Invalid value: "not_a_number": must be a non-negative integer`,
  1277  			},
  1278  			msg: "invalid nginx.org/max-fails annotation, not a number",
  1279  		},
  1280  
  1281  		{
  1282  			annotations: map[string]string{
  1283  				"nginx.org/max-conns": "10",
  1284  			},
  1285  			specServices:          map[string]bool{},
  1286  			isPlus:                false,
  1287  			appProtectEnabled:     false,
  1288  			internalRoutesEnabled: false,
  1289  			expectedErrors:        nil,
  1290  			msg:                   "valid nginx.org/max-conns annotation",
  1291  		},
  1292  		{
  1293  			annotations: map[string]string{
  1294  				"nginx.org/max-conns": "-100",
  1295  			},
  1296  			specServices:          map[string]bool{},
  1297  			isPlus:                false,
  1298  			appProtectEnabled:     false,
  1299  			internalRoutesEnabled: false,
  1300  			expectedErrors: []string{
  1301  				`annotations.nginx.org/max-conns: Invalid value: "-100": must be a non-negative integer`,
  1302  			},
  1303  			msg: "invalid nginx.org/max-conns annotation, negative number",
  1304  		},
  1305  		{
  1306  			annotations: map[string]string{
  1307  				"nginx.org/max-conns": "not_a_number",
  1308  			},
  1309  			specServices:          map[string]bool{},
  1310  			isPlus:                false,
  1311  			appProtectEnabled:     false,
  1312  			internalRoutesEnabled: false,
  1313  			expectedErrors: []string{
  1314  				`annotations.nginx.org/max-conns: Invalid value: "not_a_number": must be a non-negative integer`,
  1315  			},
  1316  			msg: "invalid nginx.org/max-conns annotation",
  1317  		},
  1318  
  1319  		{
  1320  			annotations: map[string]string{
  1321  				"nginx.org/fail-timeout": "10s",
  1322  			},
  1323  			specServices:          map[string]bool{},
  1324  			isPlus:                false,
  1325  			appProtectEnabled:     false,
  1326  			internalRoutesEnabled: false,
  1327  			expectedErrors:        nil,
  1328  			msg:                   "valid nginx.org/fail-timeout annotation",
  1329  		},
  1330  		{
  1331  			annotations: map[string]string{
  1332  				"nginx.org/fail-timeout": "not_a_time",
  1333  			},
  1334  			specServices:          map[string]bool{},
  1335  			isPlus:                false,
  1336  			appProtectEnabled:     false,
  1337  			internalRoutesEnabled: false,
  1338  			expectedErrors: []string{
  1339  				`annotations.nginx.org/fail-timeout: Invalid value: "not_a_time": must be a time`,
  1340  			},
  1341  			msg: "invalid nginx.org/fail-timeout annotation",
  1342  		},
  1343  
  1344  		{
  1345  			annotations: map[string]string{
  1346  				"appprotect.f5.com/app-protect-enable": "true",
  1347  			},
  1348  			specServices:          map[string]bool{},
  1349  			isPlus:                true,
  1350  			appProtectEnabled:     false,
  1351  			internalRoutesEnabled: false,
  1352  			expectedErrors: []string{
  1353  				"annotations.appprotect.f5.com/app-protect-enable: Forbidden: annotation requires AppProtect",
  1354  			},
  1355  			msg: "invalid appprotect.f5.com/app-protect-enable annotation, requires app protect",
  1356  		},
  1357  		{
  1358  			annotations: map[string]string{
  1359  				"appprotect.f5.com/app-protect-enable": "true",
  1360  			},
  1361  			specServices:          map[string]bool{},
  1362  			isPlus:                true,
  1363  			appProtectEnabled:     true,
  1364  			internalRoutesEnabled: false,
  1365  			expectedErrors:        nil,
  1366  			msg:                   "valid appprotect.f5.com/app-protect-enable annotation",
  1367  		},
  1368  		{
  1369  			annotations: map[string]string{
  1370  				"appprotect.f5.com/app-protect-enable": "not_a_boolean",
  1371  			},
  1372  			specServices:          map[string]bool{},
  1373  			isPlus:                true,
  1374  			appProtectEnabled:     true,
  1375  			internalRoutesEnabled: false,
  1376  			expectedErrors: []string{
  1377  				`annotations.appprotect.f5.com/app-protect-enable: Invalid value: "not_a_boolean": must be a boolean`,
  1378  			},
  1379  			msg: "invalid appprotect.f5.com/app-protect-enable annotation",
  1380  		},
  1381  
  1382  		{
  1383  			annotations: map[string]string{
  1384  				"appprotect.f5.com/app-protect-security-log-enable": "true",
  1385  			},
  1386  			specServices:          map[string]bool{},
  1387  			isPlus:                true,
  1388  			appProtectEnabled:     false,
  1389  			internalRoutesEnabled: false,
  1390  			expectedErrors: []string{
  1391  				"annotations.appprotect.f5.com/app-protect-security-log-enable: Forbidden: annotation requires AppProtect",
  1392  			},
  1393  			msg: "invalid appprotect.f5.com/app-protect-security-log-enable annotation, requires app protect",
  1394  		},
  1395  		{
  1396  			annotations: map[string]string{
  1397  				"appprotect.f5.com/app-protect-security-log-enable": "true",
  1398  			},
  1399  			specServices:          map[string]bool{},
  1400  			isPlus:                true,
  1401  			appProtectEnabled:     true,
  1402  			internalRoutesEnabled: false,
  1403  			expectedErrors:        nil,
  1404  			msg:                   "valid appprotect.f5.com/app-protect-security-log-enable annotation",
  1405  		},
  1406  		{
  1407  			annotations: map[string]string{
  1408  				"appprotect.f5.com/app-protect-security-log-enable": "not_a_boolean",
  1409  			},
  1410  			specServices:          map[string]bool{},
  1411  			isPlus:                true,
  1412  			appProtectEnabled:     true,
  1413  			internalRoutesEnabled: false,
  1414  			expectedErrors: []string{
  1415  				`annotations.appprotect.f5.com/app-protect-security-log-enable: Invalid value: "not_a_boolean": must be a boolean`,
  1416  			},
  1417  			msg: "invalid appprotect.f5.com/app-protect-security-log-enable annotation",
  1418  		},
  1419  
  1420  		{
  1421  			annotations: map[string]string{
  1422  				"nsm.nginx.com/internal-route": "true",
  1423  			},
  1424  			specServices:          map[string]bool{},
  1425  			isPlus:                true,
  1426  			appProtectEnabled:     false,
  1427  			internalRoutesEnabled: false,
  1428  			expectedErrors: []string{
  1429  				"annotations.nsm.nginx.com/internal-route: Forbidden: annotation requires Internal Routes enabled",
  1430  			},
  1431  			msg: "invalid nsm.nginx.com/internal-route annotation, requires internal routes",
  1432  		},
  1433  		{
  1434  			annotations: map[string]string{
  1435  				"nsm.nginx.com/internal-route": "true",
  1436  			},
  1437  			specServices:          map[string]bool{},
  1438  			isPlus:                true,
  1439  			appProtectEnabled:     false,
  1440  			internalRoutesEnabled: true,
  1441  			expectedErrors:        nil,
  1442  			msg:                   "valid nsm.nginx.com/internal-route annotation",
  1443  		},
  1444  		{
  1445  			annotations: map[string]string{
  1446  				"nsm.nginx.com/internal-route": "not_a_boolean",
  1447  			},
  1448  			specServices:          map[string]bool{},
  1449  			isPlus:                true,
  1450  			appProtectEnabled:     false,
  1451  			internalRoutesEnabled: true,
  1452  			expectedErrors: []string{
  1453  				`annotations.nsm.nginx.com/internal-route: Invalid value: "not_a_boolean": must be a boolean`,
  1454  			},
  1455  			msg: "invalid nsm.nginx.com/internal-route annotation",
  1456  		},
  1457  
  1458  		{
  1459  			annotations: map[string]string{
  1460  				"nginx.org/websocket-services": "service-1",
  1461  			},
  1462  			specServices: map[string]bool{
  1463  				"service-1": true,
  1464  			},
  1465  			isPlus:                false,
  1466  			appProtectEnabled:     false,
  1467  			internalRoutesEnabled: false,
  1468  			expectedErrors:        nil,
  1469  			msg:                   "valid nginx.org/websocket-services annotation, single-value",
  1470  		},
  1471  		{
  1472  			annotations: map[string]string{
  1473  				"nginx.org/websocket-services": "service-1,service-2",
  1474  			},
  1475  			specServices: map[string]bool{
  1476  				"service-1": true,
  1477  				"service-2": true,
  1478  			},
  1479  			isPlus:                false,
  1480  			appProtectEnabled:     false,
  1481  			internalRoutesEnabled: false,
  1482  			expectedErrors:        nil,
  1483  			msg:                   "valid nginx.org/websocket-services annotation, multi-value",
  1484  		},
  1485  		{
  1486  			annotations: map[string]string{
  1487  				"nginx.org/websocket-services": "service-1,service-2",
  1488  			},
  1489  			specServices: map[string]bool{
  1490  				"service-1": true,
  1491  			},
  1492  			isPlus:                false,
  1493  			appProtectEnabled:     false,
  1494  			internalRoutesEnabled: false,
  1495  			expectedErrors: []string{
  1496  				`annotations.nginx.org/websocket-services: Invalid value: "service-1,service-2": must be a comma-separated list of services. The following services were not found: service-2`,
  1497  			},
  1498  			msg: "invalid nginx.org/websocket-services annotation, service does not exist",
  1499  		},
  1500  
  1501  		{
  1502  			annotations: map[string]string{
  1503  				"nginx.org/ssl-services": "service-1",
  1504  			},
  1505  			specServices: map[string]bool{
  1506  				"service-1": true,
  1507  			},
  1508  			isPlus:                false,
  1509  			appProtectEnabled:     false,
  1510  			internalRoutesEnabled: false,
  1511  			expectedErrors:        nil,
  1512  			msg:                   "valid nginx.org/ssl-services annotation, single-value",
  1513  		},
  1514  		{
  1515  			annotations: map[string]string{
  1516  				"nginx.org/ssl-services": "service-1,service-2",
  1517  			},
  1518  			specServices: map[string]bool{
  1519  				"service-1": true,
  1520  				"service-2": true,
  1521  			},
  1522  			isPlus:                false,
  1523  			appProtectEnabled:     false,
  1524  			internalRoutesEnabled: false,
  1525  			expectedErrors:        nil,
  1526  			msg:                   "valid nginx.org/ssl-services annotation, multi-value",
  1527  		},
  1528  		{
  1529  			annotations: map[string]string{
  1530  				"nginx.org/ssl-services": "service-1,service-2",
  1531  			},
  1532  			specServices: map[string]bool{
  1533  				"service-1": true,
  1534  			},
  1535  			isPlus:                false,
  1536  			appProtectEnabled:     false,
  1537  			internalRoutesEnabled: false,
  1538  			expectedErrors: []string{
  1539  				`annotations.nginx.org/ssl-services: Invalid value: "service-1,service-2": must be a comma-separated list of services. The following services were not found: service-2`,
  1540  			},
  1541  			msg: "invalid nginx.org/ssl-services annotation, service does not exist",
  1542  		},
  1543  
  1544  		{
  1545  			annotations: map[string]string{
  1546  				"nginx.org/grpc-services": "service-1",
  1547  			},
  1548  			specServices: map[string]bool{
  1549  				"service-1": true,
  1550  			},
  1551  			isPlus:                false,
  1552  			appProtectEnabled:     false,
  1553  			internalRoutesEnabled: false,
  1554  			expectedErrors:        nil,
  1555  			msg:                   "valid nginx.org/grpc-services annotation, single-value",
  1556  		},
  1557  		{
  1558  			annotations: map[string]string{
  1559  				"nginx.org/grpc-services": "service-1,service-2",
  1560  			},
  1561  			specServices: map[string]bool{
  1562  				"service-1": true,
  1563  				"service-2": true,
  1564  			},
  1565  			isPlus:                false,
  1566  			appProtectEnabled:     false,
  1567  			internalRoutesEnabled: false,
  1568  			expectedErrors:        nil,
  1569  			msg:                   "valid nginx.org/grpc-services annotation, multi-value",
  1570  		},
  1571  		{
  1572  			annotations: map[string]string{
  1573  				"nginx.org/grpc-services": "service-1,service-2",
  1574  			},
  1575  			specServices: map[string]bool{
  1576  				"service-1": true,
  1577  			},
  1578  			isPlus:                false,
  1579  			appProtectEnabled:     false,
  1580  			internalRoutesEnabled: false,
  1581  			expectedErrors: []string{
  1582  				`annotations.nginx.org/grpc-services: Invalid value: "service-1,service-2": must be a comma-separated list of services. The following services were not found: service-2`,
  1583  			},
  1584  			msg: "invalid nginx.org/grpc-services annotation, service does not exist",
  1585  		},
  1586  
  1587  		{
  1588  			annotations: map[string]string{
  1589  				"nginx.org/rewrites": "serviceName=service-1 rewrite=rewrite-1",
  1590  			},
  1591  			specServices:          map[string]bool{},
  1592  			isPlus:                false,
  1593  			appProtectEnabled:     false,
  1594  			internalRoutesEnabled: false,
  1595  			expectedErrors:        nil,
  1596  			msg:                   "valid nginx.org/rewrites annotation, single-value",
  1597  		},
  1598  		{
  1599  			annotations: map[string]string{
  1600  				"nginx.org/rewrites": "serviceName=service-1 rewrite=rewrite-1;serviceName=service-2 rewrite=rewrite-2",
  1601  			},
  1602  			specServices:          map[string]bool{},
  1603  			isPlus:                false,
  1604  			appProtectEnabled:     false,
  1605  			internalRoutesEnabled: false,
  1606  			expectedErrors:        nil,
  1607  			msg:                   "valid nginx.org/rewrites annotation, multi-value",
  1608  		},
  1609  		{
  1610  			annotations: map[string]string{
  1611  				"nginx.org/rewrites": "not_a_rewrite",
  1612  			},
  1613  			specServices:          map[string]bool{},
  1614  			isPlus:                true,
  1615  			appProtectEnabled:     false,
  1616  			internalRoutesEnabled: true,
  1617  			expectedErrors: []string{
  1618  				`annotations.nginx.org/rewrites: Invalid value: "not_a_rewrite": must be a semicolon-separated list of rewrites`,
  1619  			},
  1620  			msg: "invalid nginx.org/rewrites annotation",
  1621  		},
  1622  
  1623  		{
  1624  			annotations: map[string]string{
  1625  				"nginx.com/sticky-cookie-services": "true",
  1626  			},
  1627  			specServices:          map[string]bool{},
  1628  			isPlus:                false,
  1629  			appProtectEnabled:     false,
  1630  			internalRoutesEnabled: false,
  1631  			expectedErrors: []string{
  1632  				"annotations.nginx.com/sticky-cookie-services: Forbidden: annotation requires NGINX Plus",
  1633  			},
  1634  			msg: "invalid nginx.com/sticky-cookie-services annotation, nginx plus only",
  1635  		},
  1636  		{
  1637  			annotations: map[string]string{
  1638  				"nginx.com/sticky-cookie-services": "serviceName=service-1 srv_id expires=1h path=/service-1",
  1639  			},
  1640  			specServices:          map[string]bool{},
  1641  			isPlus:                true,
  1642  			appProtectEnabled:     false,
  1643  			internalRoutesEnabled: false,
  1644  			expectedErrors:        nil,
  1645  			msg:                   "valid nginx.com/sticky-cookie-services annotation, single-value",
  1646  		},
  1647  		{
  1648  			annotations: map[string]string{
  1649  				"nginx.com/sticky-cookie-services": "serviceName=service-1 srv_id expires=1h path=/service-1;serviceName=service-2 srv_id expires=2h path=/service-2",
  1650  			},
  1651  			specServices:          map[string]bool{},
  1652  			isPlus:                true,
  1653  			appProtectEnabled:     false,
  1654  			internalRoutesEnabled: false,
  1655  			expectedErrors:        nil,
  1656  			msg:                   "valid nginx.com/sticky-cookie-services annotation, multi-value",
  1657  		},
  1658  		{
  1659  			annotations: map[string]string{
  1660  				"nginx.com/sticky-cookie-services": "not_a_rewrite",
  1661  			},
  1662  			specServices:          map[string]bool{},
  1663  			isPlus:                true,
  1664  			appProtectEnabled:     false,
  1665  			internalRoutesEnabled: false,
  1666  			expectedErrors: []string{
  1667  				`annotations.nginx.com/sticky-cookie-services: Invalid value: "not_a_rewrite": must be a semicolon-separated list of sticky services`,
  1668  			},
  1669  			msg: "invalid nginx.com/sticky-cookie-services annotation",
  1670  		},
  1671  	}
  1672  
  1673  	for _, test := range tests {
  1674  		t.Run(test.msg, func(t *testing.T) {
  1675  			allErrs := validateIngressAnnotations(
  1676  				test.annotations,
  1677  				test.specServices,
  1678  				test.isPlus,
  1679  				test.appProtectEnabled,
  1680  				test.internalRoutesEnabled,
  1681  				field.NewPath("annotations"),
  1682  				test.snippetsEnabled,
  1683  			)
  1684  			assertion := assertErrors("validateIngressAnnotations()", test.msg, allErrs, test.expectedErrors)
  1685  			if assertion != "" {
  1686  				t.Error(assertion)
  1687  			}
  1688  		})
  1689  	}
  1690  }
  1691  
  1692  func TestValidateIngressSpec(t *testing.T) {
  1693  	tests := []struct {
  1694  		spec           *networking.IngressSpec
  1695  		expectedErrors []string
  1696  		msg            string
  1697  	}{
  1698  		{
  1699  			spec: &networking.IngressSpec{
  1700  				Rules: []networking.IngressRule{
  1701  					{
  1702  						Host: "foo.example.com",
  1703  					},
  1704  				},
  1705  			},
  1706  			expectedErrors: nil,
  1707  			msg:            "valid input",
  1708  		},
  1709  		{
  1710  			spec: &networking.IngressSpec{
  1711  				Rules: []networking.IngressRule{},
  1712  			},
  1713  			expectedErrors: []string{
  1714  				"spec.rules: Required value",
  1715  			},
  1716  			msg: "zero rules",
  1717  		},
  1718  		{
  1719  			spec: &networking.IngressSpec{
  1720  				Rules: []networking.IngressRule{
  1721  					{
  1722  						Host: "",
  1723  					},
  1724  				},
  1725  			},
  1726  			expectedErrors: []string{
  1727  				"spec.rules[0].host: Required value",
  1728  			},
  1729  			msg: "empty host",
  1730  		},
  1731  		{
  1732  			spec: &networking.IngressSpec{
  1733  				Rules: []networking.IngressRule{
  1734  					{
  1735  						Host: "foo.example.com",
  1736  					},
  1737  					{
  1738  						Host: "foo.example.com",
  1739  					},
  1740  				},
  1741  			},
  1742  			expectedErrors: []string{
  1743  				`spec.rules[1].host: Duplicate value: "foo.example.com"`,
  1744  			},
  1745  			msg: "duplicated host",
  1746  		},
  1747  	}
  1748  
  1749  	for _, test := range tests {
  1750  		allErrs := validateIngressSpec(test.spec, field.NewPath("spec"))
  1751  		assertion := assertErrors("validateIngressSpec()", test.msg, allErrs, test.expectedErrors)
  1752  		if assertion != "" {
  1753  			t.Error(assertion)
  1754  		}
  1755  	}
  1756  }
  1757  
  1758  func TestValidateMasterSpec(t *testing.T) {
  1759  	tests := []struct {
  1760  		spec           *networking.IngressSpec
  1761  		expectedErrors []string
  1762  		msg            string
  1763  	}{
  1764  		{
  1765  			spec: &networking.IngressSpec{
  1766  				Rules: []networking.IngressRule{
  1767  					{
  1768  						Host: "foo.example.com",
  1769  						IngressRuleValue: networking.IngressRuleValue{
  1770  							HTTP: &networking.HTTPIngressRuleValue{
  1771  								Paths: []networking.HTTPIngressPath{},
  1772  							},
  1773  						},
  1774  					},
  1775  				},
  1776  			},
  1777  			expectedErrors: nil,
  1778  			msg:            "valid input",
  1779  		},
  1780  		{
  1781  			spec: &networking.IngressSpec{
  1782  				Rules: []networking.IngressRule{
  1783  					{
  1784  						Host: "foo.example.com",
  1785  					},
  1786  					{
  1787  						Host: "bar.example.com",
  1788  					},
  1789  				},
  1790  			},
  1791  			expectedErrors: []string{
  1792  				"spec.rules: Too many: 2: must have at most 1 items",
  1793  			},
  1794  			msg: "too many hosts",
  1795  		},
  1796  		{
  1797  			spec: &networking.IngressSpec{
  1798  				Rules: []networking.IngressRule{
  1799  					{
  1800  						Host: "foo.example.com",
  1801  						IngressRuleValue: networking.IngressRuleValue{
  1802  							HTTP: &networking.HTTPIngressRuleValue{
  1803  								Paths: []networking.HTTPIngressPath{
  1804  									{
  1805  										Path: "/",
  1806  									},
  1807  								},
  1808  							},
  1809  						},
  1810  					},
  1811  				},
  1812  			},
  1813  			expectedErrors: []string{
  1814  				"spec.rules[0].http.paths: Too many: 1: must have at most 0 items",
  1815  			},
  1816  			msg: "too many paths",
  1817  		},
  1818  	}
  1819  
  1820  	for _, test := range tests {
  1821  		allErrs := validateMasterSpec(test.spec, field.NewPath("spec"))
  1822  		assertion := assertErrors("validateMasterSpec()", test.msg, allErrs, test.expectedErrors)
  1823  		if assertion != "" {
  1824  			t.Error(assertion)
  1825  		}
  1826  	}
  1827  }
  1828  
  1829  func TestValidateMinionSpec(t *testing.T) {
  1830  	tests := []struct {
  1831  		spec           *networking.IngressSpec
  1832  		expectedErrors []string
  1833  		msg            string
  1834  	}{
  1835  		{
  1836  			spec: &networking.IngressSpec{
  1837  				Rules: []networking.IngressRule{
  1838  					{
  1839  						Host: "foo.example.com",
  1840  						IngressRuleValue: networking.IngressRuleValue{
  1841  							HTTP: &networking.HTTPIngressRuleValue{
  1842  								Paths: []networking.HTTPIngressPath{
  1843  									{
  1844  										Path: "/",
  1845  									},
  1846  								},
  1847  							},
  1848  						},
  1849  					},
  1850  				},
  1851  			},
  1852  			expectedErrors: nil,
  1853  			msg:            "valid input",
  1854  		},
  1855  		{
  1856  			spec: &networking.IngressSpec{
  1857  				Rules: []networking.IngressRule{
  1858  					{
  1859  						Host: "foo.example.com",
  1860  					},
  1861  					{
  1862  						Host: "bar.example.com",
  1863  					},
  1864  				},
  1865  			},
  1866  			expectedErrors: []string{
  1867  				"spec.rules: Too many: 2: must have at most 1 items",
  1868  			},
  1869  			msg: "too many hosts",
  1870  		},
  1871  		{
  1872  			spec: &networking.IngressSpec{
  1873  				Rules: []networking.IngressRule{
  1874  					{
  1875  						Host: "foo.example.com",
  1876  						IngressRuleValue: networking.IngressRuleValue{
  1877  							HTTP: &networking.HTTPIngressRuleValue{
  1878  								Paths: []networking.HTTPIngressPath{},
  1879  							},
  1880  						},
  1881  					},
  1882  				},
  1883  			},
  1884  			expectedErrors: []string{
  1885  				"spec.rules[0].http.paths: Required value: must include at least one path",
  1886  			},
  1887  			msg: "too few paths",
  1888  		},
  1889  		{
  1890  			spec: &networking.IngressSpec{
  1891  				TLS: []networking.IngressTLS{
  1892  					{
  1893  						Hosts: []string{"foo.example.com"},
  1894  					},
  1895  				},
  1896  				Rules: []networking.IngressRule{
  1897  					{
  1898  						Host: "foo.example.com",
  1899  						IngressRuleValue: networking.IngressRuleValue{
  1900  							HTTP: &networking.HTTPIngressRuleValue{
  1901  								Paths: []networking.HTTPIngressPath{
  1902  									{
  1903  										Path: "/",
  1904  									},
  1905  								},
  1906  							},
  1907  						},
  1908  					},
  1909  				},
  1910  			},
  1911  			expectedErrors: []string{
  1912  				"spec.tls: Too many: 1: must have at most 0 items",
  1913  			},
  1914  			msg: "tls is forbidden",
  1915  		},
  1916  	}
  1917  
  1918  	for _, test := range tests {
  1919  		allErrs := validateMinionSpec(test.spec, field.NewPath("spec"))
  1920  		assertion := assertErrors("validateMinionSpec()", test.msg, allErrs, test.expectedErrors)
  1921  		if assertion != "" {
  1922  			t.Error(assertion)
  1923  		}
  1924  	}
  1925  }
  1926  
  1927  func assertErrors(funcName string, msg string, allErrs field.ErrorList, expectedErrors []string) string {
  1928  	errors := errorListToStrings(allErrs)
  1929  	if !reflect.DeepEqual(errors, expectedErrors) {
  1930  		result := strings.Join(errors, "\n")
  1931  		expected := strings.Join(expectedErrors, "\n")
  1932  
  1933  		return fmt.Sprintf("%s returned \n%s \nbut expected \n%s \nfor the case of %s", funcName, result, expected, msg)
  1934  	}
  1935  
  1936  	return ""
  1937  }
  1938  
  1939  func errorListToStrings(list field.ErrorList) []string {
  1940  	var result []string
  1941  
  1942  	for _, e := range list {
  1943  		result = append(result, e.Error())
  1944  	}
  1945  
  1946  	return result
  1947  }