diff --git a/.pipelines/build.yml b/.pipelines/build.yml index cc659cfce..0733f5f9e 100644 --- a/.pipelines/build.yml +++ b/.pipelines/build.yml @@ -12,6 +12,11 @@ steps: echo '##vso[task.prependpath]$(GOROOT)/bin' displayName: "Set up the Go workspace" + - task: HelmInstaller@1 + displayName: Helm installer + inputs: + helmVersionToInstall: latest + - script: make lint-all workingDirectory: "$(modulePath)" displayName: "Go and Helm lint check" diff --git a/cmd/appgw-ingress/main.go b/cmd/appgw-ingress/main.go index 31ada741b..e06966d57 100644 --- a/cmd/appgw-ingress/main.go +++ b/cmd/appgw-ingress/main.go @@ -83,9 +83,6 @@ func main() { env.Consolidate(cpConfig) - // adjust ingress class value if overridden by environment variable - setIngressClass(env.IngressClass) - // Workaround for "ERROR: logging before flag.Parse" // See: https://github.com/kubernetes/kubernetes/issues/17162#issuecomment-225596212 _ = flag.CommandLine.Parse([]string{}) @@ -99,11 +96,11 @@ func main() { crdClient := versioned.NewForConfigOrDie(apiConfig) istioCrdClient := istio.NewForConfigOrDie(apiConfig) multiClusterCrdClient := multicluster.NewForConfigOrDie(apiConfig) - recorder := getEventRecorder(kubeClient) + recorder := getEventRecorder(kubeClient, env.IngressClassControllerName) namespaces := getNamespacesToWatch(env.WatchNamespace) metricStore := metricstore.NewMetricStore(env) metricStore.Start() - k8sContext := k8scontext.NewContext(kubeClient, crdClient, multiClusterCrdClient, istioCrdClient, namespaces, *resyncPeriod, metricStore) + k8sContext := k8scontext.NewContext(kubeClient, crdClient, multiClusterCrdClient, istioCrdClient, namespaces, *resyncPeriod, metricStore, env) agicPod := k8sContext.GetAGICPod(env) if err := environment.ValidateEnv(env); err != nil { diff --git a/cmd/appgw-ingress/utils.go b/cmd/appgw-ingress/utils.go index 6d2597d49..163ad8a8b 100644 --- a/cmd/appgw-ingress/utils.go +++ b/cmd/appgw-ingress/utils.go @@ -22,7 +22,6 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/klog/v2" - "github.com/Azure/application-gateway-kubernetes-ingress/pkg/annotations" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/controllererrors" agiccrdscheme "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/agic_crd_client/clientset/versioned/scheme" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/environment" @@ -86,7 +85,7 @@ func getKubeClientConfig() *rest.Config { return config } -func getEventRecorder(kubeClient kubernetes.Interface) record.EventRecorder { +func getEventRecorder(kubeClient kubernetes.Interface, ingressClassControllerName string) record.EventRecorder { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(klog.V(5).Infof) sink := &typedcorev1.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")} @@ -97,7 +96,7 @@ func getEventRecorder(kubeClient kubernetes.Interface) record.EventRecorder { hostname = "unknown-hostname" } source := v1.EventSource{ - Component: annotations.ApplicationGatewayIngressClass, + Component: ingressClassControllerName, Host: hostname, } @@ -115,9 +114,3 @@ func getVerbosity(flagVerbosity int, envVerbosity string) int { klog.Infof("Using verbosity level %d from environment variable %s", envVerbosityInt, environment.VerbosityLevelVarName) return envVerbosityInt } - -func setIngressClass(customIngressClass string) { - if customIngressClass != "" { - annotations.ApplicationGatewayIngressClass = customIngressClass - } -} diff --git a/deploy/azuredeploy.json b/deploy/azuredeploy.json index e805bf2a1..53da76714 100644 --- a/deploy/azuredeploy.json +++ b/deploy/azuredeploy.json @@ -281,7 +281,7 @@ } }, "kubernetesVersion": { - "defaultValue": "1.20.7", + "defaultValue": "1.20.9", "type": "string", "metadata": { "description": "The version of Kubernetes." diff --git a/deploy/azuredeploywindowscluster.json b/deploy/azuredeploywindowscluster.json index da2641f96..2acff0e8c 100644 --- a/deploy/azuredeploywindowscluster.json +++ b/deploy/azuredeploywindowscluster.json @@ -281,7 +281,7 @@ } }, "kubernetesVersion": { - "defaultValue": "1.15.7", + "defaultValue": "1.20.9", "type": "string", "metadata": { "description": "The version of Kubernetes." diff --git a/docs/annotations.md b/docs/annotations.md index 69ede952c..df469477f 100644 --- a/docs/annotations.md +++ b/docs/annotations.md @@ -30,6 +30,7 @@ For an Ingress resource to be observed by AGIC it **must be annotated** with `ku | [appgw.ingress.kubernetes.io/health-probe-interval](#health-probe-interval) | `int32` | `nil` | | `1.4.0-rc1` | | [appgw.ingress.kubernetes.io/health-probe-timeout](#health-probe-timeout) | `int32` | `nil` | | `1.4.0-rc1` | | [appgw.ingress.kubernetes.io/health-probe-unhealthy-threshold](#health-probe-unhealthy-threshold) | `int32` | `nil` | | `1.4.0-rc1` | +| [appgw.ingress.kubernetes.io/rewrite-rule-set](#rewrite-rule-set) | `string` | `nil` | | `1.5.0-rc1` | ## Override Frontend Port @@ -46,7 +47,7 @@ appgw.ingress.kubernetes.io/override-frontend-port: "port" ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-overridefrontendport @@ -60,8 +61,11 @@ spec: paths: - path: /hello/ backend: - serviceName: go-server-service - servicePort: 80 + service: + name: go-server-service + port: + number: 80 + pathType: Exact ``` External request will need to target http://somehost:8080 instead of http://somehost. @@ -79,7 +83,7 @@ appgw.ingress.kubernetes.io/backend-path-prefix: ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-bkprefix @@ -93,8 +97,11 @@ spec: paths: - path: /hello/ backend: - serviceName: go-server-service - servicePort: 80 + service: + name: go-server-service + port: + number: 80 + pathType: Exact ``` In the example above we have defined an ingress resource named `go-server-ingress-bkprefix` with an annotation `appgw.ingress.kubernetes.io/backend-path-prefix: "/test/"` . The annotation tells application gateway to create an HTTP setting which will have a path prefix override for the path `/hello` to `/test/`. @@ -132,7 +139,7 @@ appgw.ingress.kubernetes.io/backend-hostname: "internal.example.com" ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-timeout @@ -146,8 +153,11 @@ spec: paths: - path: /hello/ backend: - serviceName: go-server-service - servicePort: 80 + service: + name: go-server-service + port: + number: 80 + pathType: Exact ``` ## Backend Protocol @@ -164,7 +174,7 @@ appgw.ingress.kubernetes.io/backend-protocol: "https" ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-timeout @@ -178,8 +188,11 @@ spec: paths: - path: /hello/ backend: - serviceName: go-server-service - servicePort: 443 + service: + name: go-server-service + port: + number: 443 + pathType: Exact ``` ## SSL Redirect @@ -199,7 +212,7 @@ appgw.ingress.kubernetes.io/ssl-redirect: "true" ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-redirect @@ -217,8 +230,10 @@ spec: http: paths: - backend: - serviceName: websocket-repeater - servicePort: 80 + service: + name: websocket-repeater + port: + number: 80 ``` ## AppGw SSL Certificate @@ -258,7 +273,7 @@ appgw.ingress.kubernetes.io/appgw-ssl-certificate: "name-of-appgw-installed-cert ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-certificate @@ -272,8 +287,10 @@ spec: http: paths: - backend: - serviceName: websocket-repeater - servicePort: 80 + service: + name: websocket-repeater + port: + number: 80 ``` ## AppGW Trusted Root Certificate @@ -309,7 +326,7 @@ appgw.ingress.kubernetes.io/appgw-trusted-root-certificate: "name-of-my-root-cer ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-certificate @@ -324,8 +341,10 @@ spec: http: paths: - backend: - serviceName: websocket-repeater - servicePort: 80 + service: + name: websocket-repeater + port: + number: 80 ``` ## Connection Draining @@ -343,7 +362,7 @@ appgw.ingress.kubernetes.io/connection-draining-timeout: "60" ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-drain @@ -358,8 +377,11 @@ spec: paths: - path: /hello/ backend: - serviceName: go-server-service - servicePort: 80 + service: + name: go-server-service + port: + number: 80 + pathType: Exact ``` ## Cookie Based Affinity @@ -375,7 +397,7 @@ appgw.ingress.kubernetes.io/cookie-based-affinity: "true" ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-affinity @@ -389,8 +411,43 @@ spec: paths: - path: /hello/ backend: - serviceName: go-server-service + service: + name: go-server-service + port: + number: 80 + pathType: Exact +``` +### distinct cookie name +In addition to cookie-based-affinity, you can set `cookie-based-affinity-distinct-name: "true"` to ensure a different affinity cookie is set per backend. +### + +```yaml +appgw.ingress.kubernetes.io/cookie-based-affinity-distinct-name: "true" +``` +### Example + +```yaml +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: go-server-ingress-affinity + namespace: test-ag + annotations: + kubernetes.io/ingress.class: azure/application-gateway + appgw.ingress.kubernetes.io/cookie-based-affinity: "true" + appgw.ingress.kubernetes.io/cookie-based-affinity-distinct-name: "true" +spec: + rules: + - http: + paths: + - path: /affinity1/ + backend: + serviceName: affinity-service servicePort: 80 + - path: /affinity2/ + backend: + serviceName: other-affinity-service + servicePort: 80 ``` ## Request Timeout @@ -406,7 +463,7 @@ appgw.ingress.kubernetes.io/request-timeout: "20" ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-timeout @@ -420,8 +477,11 @@ spec: paths: - path: /hello/ backend: - serviceName: go-server-service - servicePort: 80 + service: + name: go-server-service + port: + number: 80 + pathType: Exact ``` ## Use Private IP @@ -440,7 +500,7 @@ appgw.ingress.kubernetes.io/use-private-ip: "true" ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-timeout @@ -454,8 +514,11 @@ spec: paths: - path: /hello/ backend: - serviceName: go-server-service - servicePort: 80 + service: + name: go-server-service + port: + number: 80 + pathType: Exact ``` ## Azure Waf Policy For Path @@ -482,9 +545,9 @@ appgw.ingress.kubernetes.io/waf-policy-for-path: "/subscriptions/abcd/resourceGr ``` ### Example -The example below will apply the WAF policy +The example below will apply the WAF policy ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ad-server-ingress @@ -498,13 +561,18 @@ spec: paths: - path: /ad-server backend: - serviceName: ad-server - servicePort: 80 - + service: + name: ad-server + port: + number: 80 + pathType: Exact - path: /auth backend: - serviceName: auth-server - servicePort: 80 + service: + name: auth-server + port: + number: 80 + pathType: Exact ``` Note that the WAF policy will be applied to both `/ad-server` and `/auth` URLs. @@ -521,7 +589,7 @@ appgw.ingress.kubernetes.io/health-probe-hostname: ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-bkprefix @@ -535,8 +603,11 @@ spec: paths: - path: /hello/ backend: - serviceName: go-server-service - servicePort: 80 + service: + name: go-server-service + port: + number: 80 + pathType: Exact ``` ## Health Probe Port @@ -552,7 +623,7 @@ appgw.ingress.kubernetes.io/health-probe-port: ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-bkprefix @@ -571,8 +642,11 @@ spec: paths: - path: / backend: - serviceName: go-server-service - servicePort: 443 + service: + name: go-server-service + port: + number: 443 + pathType: Exact ``` ## Health Probe Path @@ -588,7 +662,7 @@ appgw.ingress.kubernetes.io/health-probe-path: ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-bkprefix @@ -604,8 +678,10 @@ spec: paths: - path: / backend: - serviceName: go-server-service - servicePort: 8080 + service: + name: go-server-service + port: + number: 8080 ``` ## Health Probe Status Codes @@ -621,7 +697,7 @@ appgw.ingress.kubernetes.io/health-probe-status-codes: ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-bkprefix @@ -635,8 +711,11 @@ spec: paths: - path: / backend: - serviceName: go-server-service - servicePort: 8080 + service: + name: go-server-service + port: + number: 8080 + pathType: Exact ``` ## Health Probe Interval @@ -652,7 +731,7 @@ appgw.ingress.kubernetes.io/health-probe-interval: ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-bkprefix @@ -666,8 +745,11 @@ spec: paths: - path: / backend: - serviceName: go-server-service - servicePort: 8080 + service: + name: go-server-service + port: + number: 8080 + pathType: Exact ``` ## Health Probe Timeout @@ -683,7 +765,7 @@ appgw.ingress.kubernetes.io/health-probe-timeout: ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: go-server-ingress-bkprefix @@ -697,8 +779,11 @@ spec: paths: - path: / backend: - serviceName: go-server-service - servicePort: 8080 + service: + name: go-server-service + port: + number: 8080 + pathType: Exact ``` ## Health Probe Unhealthy Threshold @@ -714,7 +799,7 @@ appgw.ingress.kubernetes.io/health-probe-unhealthy-threshold: +``` + +### Example + +```yaml +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: go-server-ingress-bkprefix + namespace: test-ag + annotations: + kubernetes.io/ingress.class: azure/application-gateway + appgw.ingress.kubernetes.io/rewrite-rule-set: add-custom-response-header spec: rules: - http: diff --git a/docs/examples/aspnetapp.yaml b/docs/examples/aspnetapp.yaml index 1cc576176..2205020f7 100644 --- a/docs/examples/aspnetapp.yaml +++ b/docs/examples/aspnetapp.yaml @@ -28,7 +28,7 @@ spec: --- -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: aspnetapp @@ -40,5 +40,8 @@ spec: paths: - path: / backend: - serviceName: aspnetapp - servicePort: 80 + service: + name: aspnetapp + port: + number: 80 + pathType: Exact diff --git a/docs/examples/aspnetappwin.yaml b/docs/examples/aspnetappwin.yaml index eae0a0562..c88542e64 100644 --- a/docs/examples/aspnetappwin.yaml +++ b/docs/examples/aspnetappwin.yaml @@ -30,7 +30,7 @@ spec: --- -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: aspnetapp @@ -42,5 +42,8 @@ spec: paths: - path: / backend: - serviceName: aspnetapp - servicePort: 80 \ No newline at end of file + service: + name: aspnetapp + port: + number: 80 + pathType: Exact \ No newline at end of file diff --git a/docs/examples/guestbook/ing-guestbook-other.yaml b/docs/examples/guestbook/ing-guestbook-other.yaml index daf02d224..b63f5e68c 100644 --- a/docs/examples/guestbook/ing-guestbook-other.yaml +++ b/docs/examples/guestbook/ing-guestbook-other.yaml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: guestbook @@ -11,8 +11,13 @@ spec: paths: - path: backend: - serviceName: - servicePort: 80 + service + name: + port: + number: 80 + pathType: Prefix - backend: - serviceName: frontend - servicePort: 80 + service: + name: frontend + port: + number: 80 diff --git a/docs/examples/guestbook/ing-guestbook-tls-sni.yaml b/docs/examples/guestbook/ing-guestbook-tls-sni.yaml index ada00a4eb..062b29065 100644 --- a/docs/examples/guestbook/ing-guestbook-tls-sni.yaml +++ b/docs/examples/guestbook/ing-guestbook-tls-sni.yaml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: guestbook @@ -14,5 +14,7 @@ spec: http: paths: - backend: - serviceName: frontend - servicePort: 80 \ No newline at end of file + service: + name: frontend + port: + number: 80 \ No newline at end of file diff --git a/docs/examples/guestbook/ing-guestbook-tls.yaml b/docs/examples/guestbook/ing-guestbook-tls.yaml index 913eaf302..82abd3384 100644 --- a/docs/examples/guestbook/ing-guestbook-tls.yaml +++ b/docs/examples/guestbook/ing-guestbook-tls.yaml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: guestbook @@ -11,5 +11,7 @@ spec: - http: paths: - backend: - serviceName: frontend - servicePort: 80 \ No newline at end of file + service: + name: frontend + port: + number: 80 \ No newline at end of file diff --git a/docs/examples/guestbook/ing-guestbook.yaml b/docs/examples/guestbook/ing-guestbook.yaml index 7cc626e19..9602c7ccf 100644 --- a/docs/examples/guestbook/ing-guestbook.yaml +++ b/docs/examples/guestbook/ing-guestbook.yaml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: guestbook @@ -9,5 +9,7 @@ spec: - http: paths: - backend: - serviceName: frontend - servicePort: 80 \ No newline at end of file + service: + name: frontend + port: + number: 80 \ No newline at end of file diff --git a/docs/examples/sample-app/ingress-http.yaml b/docs/examples/sample-app/ingress-http.yaml index 7b94e477f..4510e801b 100644 --- a/docs/examples/sample-app/ingress-http.yaml +++ b/docs/examples/sample-app/ingress-http.yaml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: sample-app @@ -9,5 +9,7 @@ spec: - http: paths: - backend: - serviceName: sample-app - servicePort: 80 \ No newline at end of file + service: + name: sample-app + port: + number: 80 \ No newline at end of file diff --git a/docs/examples/sample-app/ingress-https.yaml b/docs/examples/sample-app/ingress-https.yaml index 4b6348bef..64f2d27d7 100644 --- a/docs/examples/sample-app/ingress-https.yaml +++ b/docs/examples/sample-app/ingress-https.yaml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: sample-app @@ -11,5 +11,7 @@ spec: - http: paths: - backend: - serviceName: sample-app - servicePort: 80 \ No newline at end of file + service: + name: sample-app + port: + number: 80 \ No newline at end of file diff --git a/docs/features/appgw-ssl-certificate.md b/docs/features/appgw-ssl-certificate.md index b28efaaaa..09483984f 100644 --- a/docs/features/appgw-ssl-certificate.md +++ b/docs/features/appgw-ssl-certificate.md @@ -5,7 +5,7 @@ This documents assumes you already have the following Azure tools and resources - [AAD Pod Identity](https://github.com/Azure/aad-pod-identity) installed on your AKS cluster - [Cloud Shell](https://shell.azure.com/) is the Azure shell environment, which has `az` CLI, `kubectl`, and `helm` installed. These tools are required for the commands below. -Please use [Greenfeild Deployment](https://github.com/Azure/application-gateway-kubernetes-ingress/blob/master/docs/setup/install-new.md) to install nonexistents. +Please use [Greenfield Deployment](https://github.com/Azure/application-gateway-kubernetes-ingress/blob/master/docs/setup/install-new.md) to install nonexistents. To use the new feature, make sure the AGIC version is at least at 1.2.0-rc3 ```bash @@ -40,7 +40,7 @@ az network application-gateway ssl-cert create \ ``` ## Configure certificate from Key Vault to AppGw -To configfure certificate from key vault to Application Gateway, an user-assigned managed identity will need to be created and assigned to AppGw, the managed identity will need to have GET secret access to KeyVault. +To configfure certificate from key vault to Application Gateway, an user-assigned managed identity will need to be created and assigned to AppGw, the managed identity will need to have GET secret access to KeyVault. ```bash # Configure your resources @@ -126,7 +126,7 @@ spec: --- -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: aspnetapp @@ -139,7 +139,10 @@ spec: paths: - path: / backend: - serviceName: aspnetapp - servicePort: 80 + service: + name: aspnetapp + port: + number: 80 + pathType: Exact EOF ``` diff --git a/docs/features/cookie-affinity.md b/docs/features/cookie-affinity.md index bcb3ed41a..19b01e6ef 100644 --- a/docs/features/cookie-affinity.md +++ b/docs/features/cookie-affinity.md @@ -3,7 +3,7 @@ As outlined in the [Azure Application Gateway Documentation](https://docs.micros ### Example ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: guestbook @@ -15,6 +15,8 @@ spec: - http: paths: - backend: - serviceName: frontend - servicePort: 80 + service: + name: frontend + port: + number: 80 ``` diff --git a/docs/features/custom-ingress-class.md b/docs/features/custom-ingress-class.md index d26f91b8c..f72a3dc0b 100644 --- a/docs/features/custom-ingress-class.md +++ b/docs/features/custom-ingress-class.md @@ -29,8 +29,10 @@ To use a custom ingress class, paths: - path: /hello/ backend: - serviceName: go-server-service - servicePort: 80 + service: + name: go-server-service + port: + number: 80 ``` ## Reference diff --git a/docs/features/multiple-namespaces.md b/docs/features/multiple-namespaces.md index 335fce431..8e3a77a9a 100644 --- a/docs/features/multiple-namespaces.md +++ b/docs/features/multiple-namespaces.md @@ -49,7 +49,7 @@ and duplicates will removed.. For example, consider the following duplicate ingress resources defined namespaces `staging` and `production` for `www.contoso.com`: ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: websocket-ingress @@ -62,12 +62,14 @@ spec: http: paths: - backend: - serviceName: web-service - servicePort: 80 + service: + name: web-service + port: + number: 80 ``` ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: websocket-ingress @@ -80,8 +82,10 @@ spec: http: paths: - backend: - serviceName: web-service - servicePort: 80 + service: + name: web-service + port: + number: 80 ``` Despite the two ingress resources demanding traffic for `www.contoso.com` to be diff --git a/docs/features/probes.md b/docs/features/probes.md index 191aec6e2..bfd418f88 100644 --- a/docs/features/probes.md +++ b/docs/features/probes.md @@ -4,7 +4,7 @@ The probe properties can be customized by adding a [Readiness or Liveness Probe] ### With `readinessProbe` or `livenessProbe` ```yaml -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: name: aspnetapp diff --git a/docs/how-tos/dns.md b/docs/how-tos/dns.md index 257084e15..d9c4aab29 100644 --- a/docs/how-tos/dns.md +++ b/docs/how-tos/dns.md @@ -7,7 +7,7 @@ Below is a sample Ingress resource, annotated with `kubernetes.io/ingress.class: azure/application-gateway`, which configures `aplpha.contoso.com`. ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: websocket-ingress @@ -21,8 +21,11 @@ spec: paths: - path: / backend: - serviceName: contoso-service - servicePort: 80 + service: + name: contoso-service + port: + number: 80 + pathType: Exact ``` Application Gateway Ingress Controller (AGIC) automatically recognizes the public IP address diff --git a/docs/how-tos/lets-encrypt.md b/docs/how-tos/lets-encrypt.md index 229065348..27f36e811 100644 --- a/docs/how-tos/lets-encrypt.md +++ b/docs/how-tos/lets-encrypt.md @@ -103,7 +103,7 @@ Follow the steps below to install [cert-manager](https://docs.cert-manager.io) o ```bash kubectl apply -f - < [!IMPORTANT] > AKS preview features are self-service opt-in. Previews are provided "as-is" and "as available" and are excluded from the service level agreements and limited warranty. AKS Previews are partially covered by customer support on best effort basis. As such, these features are not meant for production use. For additional information, please see the following support articles: @@ -83,21 +83,25 @@ While this feature is in preview, the following additional limitations apply: Follow the steps below to create an Azure Active Directory (AAD) [service principal object](https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals#service-principal-object). Please record the `appId`, `password`, and `objectId` values - these will be used in the following steps. 1. Create AD service principal ([Read more about RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview)). Paste the following lines in your [Azure Cloud Shell](https://shell.azure.com/): + ```bash az ad sp create-for-rbac --skip-assignment -o json > auth.json appId=$(jq -r ".appId" auth.json) password=$(jq -r ".password" auth.json) ``` - These commands will create `appId` and `password` bash variables, which will be used in the steps below. You can view the value of these with `echo $appId` and `echo $password`. + These commands will create `appId` and `password` bash variables, which will be used in the steps below. You can view the value of these with `echo $appId` and `echo $password`. + +1. Execute the next command in [Cloud Shell](https://shell.azure.com/) to create the `objectId` bash variable, which is the new Service Principal: -1. Execute the next command in [Cloud Shell](https://shell.azure.com/) to create the `objectId` bash variable, which is the new Service Princpial: ```bash objectId=$(az ad sp show --id $appId --query "objectId" -o tsv) ``` + The `objectId` bash variable will be used in the ARM template below. View the value with `echo $objectId`. -1. Paste the entire command below (it is a single command on multiple lines) in [Cloud Shell](https://shell.azure.com/) to create the parameters.json file. It will be used in the ARM template deployment. +1. Paste the entire command below (it is a single command on multiple lines) in [Cloud Shell](https://shell.azure.com/) to create the `parameters.json` file. It will be used in the ARM template deployment. + ```bash cat < parameters.json { @@ -108,9 +112,11 @@ Follow the steps below to create an Azure Active Directory (AAD) [service princi } EOF ``` + To deploy an **RBAC** enabled cluster, set the `aksEnabledRBAC` field to `true`. View the contents of the newly created file with `cat parameters.json`. It will contain the values of the `appId`, `password`, and `objectId` bash variables from the previous steps. ### Deploy Components + The next few steps will add the following list of components to your Azure subscription: - [Azure Kubernetes Service](https://docs.microsoft.com/en-us/azure/aks/intro-kubernetes) @@ -119,7 +125,8 @@ The next few steps will add the following list of components to your Azure subsc - [Public IP Address](https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-public-ip-address) - [Managed Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview), which will be used by [AAD Pod Identity](https://github.com/Azure/aad-pod-identity/blob/master/README.md) -1. Download the ARM template into template.json file. Paste the following in your [shell](https://shell.azure.com/): +1. Download the ARM template into `template.json` file. Paste the following in your [shell](https://shell.azure.com/): + ```bash wget https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/deploy/azuredeploywindowscluster.json -O template.json ``` @@ -136,13 +143,17 @@ The next few steps will add the following list of components to your Azure subsc az group deployment create -g $resourceGroupName -n $deploymentName --template-file template.json --parameters parameters.json ``` + Note: The last command may take a few minutes to complete. 1. Once the deployment finished, download the deployment output into a file named `deployment-outputs.json`. + ```bash az group deployment show -g $resourceGroupName -n $deploymentName --query "properties.outputs" -o json > deployment-outputs.json ``` + View the content of the newly created file with: `cat deployment-outputs.json`. The file will have the following shape (example): + ```json { "aksApiServerAddress": { @@ -183,6 +194,7 @@ an App Gateway. We are now ready to deploy a sample app and an ingress controlle Kubernetes infrastructure. ### Setup Kubernetes Credentials + For the following steps we need setup [kubectl](https://kubectl.docs.kubernetes.io/) command, which we will use to connect to our new Kubernetes cluster. [Cloud Shell](https://shell.azure.com/) has `kubectl` already installed. We will use `az` CLI to obtain credentials for Kubernetes. @@ -196,34 +208,37 @@ az aks get-credentials --resource-group $resourceGroupName --name $aksClusterNam ``` ### Install AAD Pod Identity - Azure Active Directory Pod Identity provides token-based access to - [Azure Resource Manager (ARM)](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview). - [AAD Pod Identity](https://github.com/Azure/aad-pod-identity) will add the following components to your Kubernetes cluster: - 1. Kubernetes [CRDs](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/): `AzureIdentity`, `AzureAssignedIdentity`, `AzureIdentityBinding` - 1. [Managed Identity Controller (MIC)](https://github.com/Azure/aad-pod-identity#managed-identity-controller) component - 1. [Node Managed Identity (NMI)](https://github.com/Azure/aad-pod-identity#node-managed-identity) component +Azure Active Directory Pod Identity provides token-based access to +[Azure Resource Manager (ARM)](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview). +[AAD Pod Identity](https://github.com/Azure/aad-pod-identity) will add the following components to your Kubernetes cluster: - To install AAD Pod Identity to your cluster: +1. Kubernetes [CRDs](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/): `AzureIdentity`, `AzureAssignedIdentity`, `AzureIdentityBinding` +1. [Managed Identity Controller (MIC)](https://github.com/Azure/aad-pod-identity#managed-identity-controller) component +1. [Node Managed Identity (NMI)](https://github.com/Azure/aad-pod-identity#node-managed-identity) component - - *RBAC enabled* AKS cluster +To install AAD Pod Identity to your cluster: + +- *RBAC enabled* AKS cluster ```bash - kubectl apply -f https://raw.githubusercontent.com/Azure/aad-pod-identity/v1.6.0/deploy/infra/deployment-rbac.yaml + kubectl apply -f https://raw.githubusercontent.com/Azure/aad-pod-identity/v1.8.6/deploy/infra/deployment-rbac.yaml ``` - - *RBAC disabled* AKS cluster +- *RBAC disabled* AKS cluster ```bash - kubectl apply -f https://raw.githubusercontent.com/Azure/aad-pod-identity/v1.6.0/deploy/infra/deployment.yaml + kubectl apply -f https://raw.githubusercontent.com/Azure/aad-pod-identity/v1.8.6/deploy/infra/deployment.yaml ``` ### Install Helm + [Helm](https://docs.microsoft.com/en-us/azure/aks/kubernetes-helm) is a package manager for -Kubernetes. This document will use version 3 of helm, which is not backwards compatbile with previous versions. +Kubernetes. This document will use version 3 of helm, which is not backwards compatible with previous versions. 1. Add the AGIC Helm repository: + ```bash helm repo add application-gateway-kubernetes-ingress https://appgwingress.blob.core.windows.net/ingress-azure-helm-package/ helm repo update @@ -245,6 +260,7 @@ Kubernetes. This document will use version 3 of helm, which is not backwards com ``` 3. Edit the newly downloaded [helm-config.yaml](../examples/sample-helm-config.yaml) and fill out the sections `appgw` and `armAuth`. + ```bash sed -i "s||${subscriptionId}|g" helm-config.yaml sed -i "s||${resourceGroupName}|g" helm-config.yaml @@ -273,12 +289,13 @@ Values: Note on Identity: The `identityResourceID` and `identityClientID` are values that were created during the [Create an Identity](https://github.com/Azure/application-gateway-kubernetes-ingress/blob/072626cb4e37f7b7a1b0c4578c38d1eadc3e8701/docs/setup/install-new.md#create-an-identity) steps, and could be obtained again using the following command: + ```bash az identity show -g -n ``` -- `` in the command above is the resource group of your App Gateway. -- `` is the name of the created identity. All identities for a given subscription can be listed using: `az identity list` +- `` in the command above is the resource group of your App Gateway. +- `` is the name of the created identity. All identities for a given subscription can be listed using: `az identity list` 4. Install the Application Gateway ingress controller package: @@ -287,12 +304,13 @@ Values: -f helm-config.yaml \ application-gateway-kubernetes-ingress/ingress-azure \ --set nodeSelector."beta\.kubernetes\.io/os"=linux \ - --version 1.3.0 + --version 1.4.0 ``` - >Note: Use at least version 1.2.0-rc3, e.g. `--version 1.2.0-rc3`, when installing on k8s version >= 1.16 + >Note: Use at least version 1.4.0, i.e. `--version 1.4.0`, when installing on k8s version >= 1.16. Kubernetes >= 1.22 requires version 1.5.0 (or higher). ### Install a Sample App + Now that we have App Gateway, AKS, and AGIC installed we can install a sample app via [Azure Cloud Shell](https://shell.azure.com/): @@ -330,7 +348,7 @@ spec: --- -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: aspnetapp @@ -342,24 +360,28 @@ spec: paths: - path: / backend: - serviceName: aspnetapp - servicePort: 80 + service: + name: aspnetapp + port: + number: 80 + pathType: Exact EOF ``` Alternatively you can: 1. Download the YAML file above: + ```bash curl https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/docs/examples/aspnetappwin.yaml -o aspnetapp.yaml ``` 2. Apply the YAML file: + ```bash kubectl apply -f aspnetapp.yaml ``` - ## Other Examples -The **[tutorials](../tutorials/tutorial.general.md)** document contains more examples on how to expose an AKS -service via HTTP or HTTPS, to the Internet with App Gateway. + +The **[tutorials](../tutorials/tutorial.general.md)** document contains more examples on how to expose an AKS service via HTTP or HTTPS, to the Internet with App Gateway. diff --git a/docs/setup/install-new.md b/docs/setup/install-new.md index d2ebf94bc..ba3013a0d 100644 --- a/docs/setup/install-new.md +++ b/docs/setup/install-new.md @@ -3,7 +3,7 @@ The instructions below assume Application Gateway Ingress Controller (AGIC) will be installed in an environment with no pre-existing components. -### Required Command Line Tools +## Required Command Line Tools We recommend the use of [Azure Cloud Shell](https://shell.azure.com/) for all command line operations below. Launch your shell from shell.azure.com or by clicking the link: @@ -21,27 +21,30 @@ choose to use another environment, please ensure the following command line tool 1. `helm` - Kubernetes package manager: [installation instructions](https://github.com/helm/helm/releases/latest) 1. `jq` - command-line JSON processor: [installation instructions](https://stedolan.github.io/jq/download/) - -### Create an Identity +## Create an Identity Follow the steps below to create an Azure Active Directory (AAD) [service principal object](https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals#service-principal-object). Please record the `appId`, `password`, and `objectId` values - these will be used in the following steps. 1. Create AD service principal ([Read more about RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview)). Paste the following lines in your [Azure Cloud Shell](https://shell.azure.com/): + ```bash az ad sp create-for-rbac --skip-assignment -o json > auth.json appId=$(jq -r ".appId" auth.json) password=$(jq -r ".password" auth.json) ``` - These commands will create `appId` and `password` bash variables, which will be used in the steps below. You can view the value of these with `echo $appId` and `echo $password`. + These commands will create `appId` and `password` bash variables, which will be used in the steps below. You can view the value of these with `echo $appId` and `echo $password`. + +1. Execute the next command in [Cloud Shell](https://shell.azure.com/) to create the `objectId` bash variable, which is the new Service Principal: -1. Execute the next command in [Cloud Shell](https://shell.azure.com/) to create the `objectId` bash variable, which is the new Service Princpial: ```bash objectId=$(az ad sp show --id $appId --query "objectId" -o tsv) ``` + The `objectId` bash variable will be used in the ARM template below. View the value with `echo $objectId`. -1. Paste the entire command below (it is a single command on multiple lines) in [Cloud Shell](https://shell.azure.com/) to create the parameters.json file. It will be used in the ARM template deployment. +1. Paste the entire command below (it is a single command on multiple lines) in [Cloud Shell](https://shell.azure.com/) to create the `parameters.json` file. It will be used in the ARM template deployment. + ```bash cat < parameters.json { @@ -52,9 +55,11 @@ Follow the steps below to create an Azure Active Directory (AAD) [service princi } EOF ``` + To deploy an **RBAC** enabled cluster, set the `aksEnabledRBAC` field to `true`. View the contents of the newly created file with `cat parameters.json`. It will contain the values of the `appId`, `password`, and `objectId` bash variables from the previous steps. -### Deploy Components +## Deploy Components + The next few steps will add the following list of components to your Azure subscription: - [Azure Kubernetes Service](https://docs.microsoft.com/en-us/azure/aks/intro-kubernetes) @@ -63,12 +68,14 @@ The next few steps will add the following list of components to your Azure subsc - [Public IP Address](https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-public-ip-address) - [Managed Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview), which will be used by [AAD Pod Identity](https://github.com/Azure/aad-pod-identity/blob/master/README.md) -1. Download the ARM template into template.json file. Paste the following in your [shell](https://shell.azure.com/): +1. Download the ARM template into `template.json` file. Paste the following in your [shell](https://shell.azure.com/): + ```bash wget https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/deploy/azuredeploy.json -O template.json ``` 1. Deploy the ARM template via [Azure Cloud Shell](https://shell.azure.com/) and the `az` tool. Modify the name of the resource group and region/location, then paste each of the following lines into your [shell](https://shell.azure.com/): + ```bash resourceGroupName="MyResourceGroup" @@ -80,13 +87,17 @@ The next few steps will add the following list of components to your Azure subsc az group deployment create -g $resourceGroupName -n $deploymentName --template-file template.json --parameters parameters.json ``` + Note: The last command may take a few minutes to complete. 1. Once the deployment finished, download the deployment output into a file named `deployment-outputs.json`. + ```bash az group deployment show -g $resourceGroupName -n $deploymentName --query "properties.outputs" -o json > deployment-outputs.json ``` + View the content of the newly created file with: `cat deployment-outputs.json`. The file will have the following shape (example): + ```json { "aksApiServerAddress": { @@ -127,10 +138,12 @@ an App Gateway. We are now ready to deploy a sample app and an ingress controlle Kubernetes infrastructure. ### Setup Kubernetes Credentials + For the following steps we need setup [kubectl](https://kubectl.docs.kubernetes.io/) command, which we will use to connect to our new Kubernetes cluster. [Cloud Shell](https://shell.azure.com/) has `kubectl` already installed. We will use `az` CLI to obtain credentials for Kubernetes. Get credentials for your newly deployed AKS ([read more](https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough#connect-to-the-cluster)): + ```bash # use the deployment-outputs.json created after deployment to get the cluster name and resource group name aksClusterName=$(jq -r ".aksClusterName.value" deployment-outputs.json) @@ -140,35 +153,38 @@ az aks get-credentials --resource-group $resourceGroupName --name $aksClusterNam ``` ### Install AAD Pod Identity - Azure Active Directory Pod Identity provides token-based access to - [Azure Resource Manager (ARM)](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview). - [AAD Pod Identity](https://github.com/Azure/aad-pod-identity) will add the following components to your Kubernetes cluster: - 1. Kubernetes [CRDs](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/): `AzureIdentity`, `AzureAssignedIdentity`, `AzureIdentityBinding` - 1. [Managed Identity Controller (MIC)](https://github.com/Azure/aad-pod-identity#managed-identity-controller) component - 1. [Node Managed Identity (NMI)](https://github.com/Azure/aad-pod-identity#node-managed-identity) component +Azure Active Directory Pod Identity provides token-based access to [Azure Resource Manager (ARM)](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview). + +[AAD Pod Identity](https://github.com/Azure/aad-pod-identity) will add the following components to your Kubernetes cluster: +1. Kubernetes [CRDs](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/): `AzureIdentity`, `AzureAssignedIdentity`, `AzureIdentityBinding` +1. [Managed Identity Controller (MIC)](https://github.com/Azure/aad-pod-identity#managed-identity-controller) component +1. [Node Managed Identity (NMI)](https://github.com/Azure/aad-pod-identity#node-managed-identity) component - To install AAD Pod Identity to your cluster: +To install AAD Pod Identity to your cluster: - - *RBAC enabled* AKS cluster +- *RBAC enabled* AKS cluster - ```bash - kubectl apply -f https://raw.githubusercontent.com/Azure/aad-pod-identity/v1.6.0/deploy/infra/deployment-rbac.yaml - ``` + ```bash + kubectl apply -f https://raw.githubusercontent.com/Azure/aad-pod-identity/v1.8.6/deploy/infra/deployment-rbac.yaml + ``` - - *RBAC disabled* AKS cluster +- *RBAC disabled* AKS cluster + + ```bash + kubectl apply -f https://raw.githubusercontent.com/Azure/aad-pod-identity/v1.8.6/deploy/infra/deployment.yaml + ``` - ```bash - kubectl apply -f https://raw.githubusercontent.com/Azure/aad-pod-identity/v1.6.0/deploy/infra/deployment.yaml - ``` ***Note:*** AAD Pod Identity introduced a [breaking change](https://github.com/Azure/aad-pod-identity/tree/v1.6.0#v160-breaking-change) after v1.5.5 regarding CRD fields become case sensitive, for any AAD Pod Identity version >= 1.6.0 or you plan to apply from master branch such as https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment-rbac.yaml, AGIC version at least [v1.2.0-rc2](https://github.com/Azure/application-gateway-kubernetes-ingress/blob/master/CHANGELOG/CHANGELOG-1.2.md#v120-rc2) will be required, more details please refer to [troubleshooting](../troubleshootings/troubleshooting-agic-fails-with-aad-pod-identity-breakingchange.md). ### Install Helm + [Helm](https://docs.microsoft.com/en-us/azure/aks/kubernetes-helm) is a package manager for -Kubernetes. This document will use version 3 of helm, which is not backwards compatbile with previous versions. +Kubernetes. This document will use version 3 of helm, which is not backwards compatible with previous versions. 1. Add the AGIC Helm repository: + ```bash helm repo add application-gateway-kubernetes-ingress https://appgwingress.blob.core.windows.net/ingress-azure-helm-package/ helm repo update @@ -177,6 +193,7 @@ Kubernetes. This document will use version 3 of helm, which is not backwards com ### Install Ingress Controller Helm Chart 1. Use the `deployment-outputs.json` file created above and create the following variables. + ```bash applicationGatewayName=$(jq -r ".applicationGatewayName.value" deployment-outputs.json) resourceGroupName=$(jq -r ".resourceGroupName.value" deployment-outputs.json) @@ -184,12 +201,15 @@ Kubernetes. This document will use version 3 of helm, which is not backwards com identityClientId=$(jq -r ".identityClientId.value" deployment-outputs.json) identityResourceId=$(jq -r ".identityResourceId.value" deployment-outputs.json) ``` + 2. Download [helm-config.yaml](../examples/sample-helm-config.yaml), which will configure AGIC: + ```bash wget https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/docs/examples/sample-helm-config.yaml -O helm-config.yaml ``` 3. Edit the newly downloaded [helm-config.yaml](../examples/sample-helm-config.yaml) and fill out the sections `appgw` and `armAuth`. + ```bash sed -i "s||${subscriptionId}|g" helm-config.yaml sed -i "s||${resourceGroupName}|g" helm-config.yaml @@ -218,12 +238,13 @@ Values: Note on Identity: The `identityResourceID` and `identityClientID` are values that were created during the [Create an Identity](https://github.com/Azure/application-gateway-kubernetes-ingress/blob/072626cb4e37f7b7a1b0c4578c38d1eadc3e8701/docs/setup/install-new.md#create-an-identity) steps, and could be obtained again using the following command: + ```bash az identity show -g -n ``` -- `` in the command above is the resource group of your App Gateway. -- `` is the name of the created identity. All identities for a given subscription can be listed using: `az identity list` +- `` in the command above is the resource group of your App Gateway. +- `` is the name of the created identity. All identities for a given subscription can be listed using: `az identity list` 4. Install the Application Gateway ingress controller package: @@ -231,12 +252,13 @@ Values: helm install ingress-azure \ -f helm-config.yaml \ application-gateway-kubernetes-ingress/ingress-azure \ - --version 1.3.0 + --version 1.4.0 ``` - >Note: Use at least version 1.2.0-rc1, i.e. `--version 1.2.0-rc1`, when installing on k8s version >= 1.16 + >Note: Use at least version 1.4.0, i.e. `--version 1.4.0`, when installing on k8s version >= 1.16. Kubernetes >= 1.22 requires version 1.5.0 (or higher). ### Install a Sample App + Now that we have App Gateway, AKS, and AGIC installed we can install a sample app via [Azure Cloud Shell](https://shell.azure.com/): @@ -272,7 +294,7 @@ spec: --- -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: aspnetapp @@ -284,24 +306,28 @@ spec: paths: - path: / backend: - serviceName: aspnetapp - servicePort: 80 + service: + name: aspnetapp + port: + number: 80 + pathType: Exact EOF ``` Alternatively you can: 1. Download the YAML file above: + ```bash curl https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/docs/examples/aspnetapp.yaml -o aspnetapp.yaml ``` 2. Apply the YAML file: + ```bash kubectl apply -f aspnetapp.yaml ``` - ## Other Examples -The **[tutorials](../tutorials/tutorial.general.md)** document contains more examples on how toexpose an AKS -service via HTTP or HTTPS, to the Internet with App Gateway. + +The **[tutorials](../tutorials/tutorial.general.md)** document contains more examples on how to expose an AKS service via HTTP or HTTPS, to the Internet with App Gateway. diff --git a/docs/troubleshootings/troubleshooting-installing-a-simple-application.md b/docs/troubleshootings/troubleshooting-installing-a-simple-application.md index d1fd91d07..ae5aa455d 100644 --- a/docs/troubleshootings/troubleshooting-installing-a-simple-application.md +++ b/docs/troubleshootings/troubleshooting-installing-a-simple-application.md @@ -5,7 +5,7 @@ and AGIC installation. Launch your shell from [shell.azure.com](https://shell.az [![Embed launch](https://shell.azure.com/images/launchcloudshell.png "Launch Azure Cloud Shell")](https://shell.azure.com) -In the troubleshooting document, we will debug issues in the AGIC installation by installing a simple application step by step and check the output as we go along. +In the troubleshooting document, we will debug issues in the AGIC installation by installing a simple application step by step and check the output as we go along. The steps below assume: - You have an AKS cluster, with Advanced Networking enabled - AGIC has been installed on the AKS cluster @@ -42,7 +42,7 @@ spec: port: 80 targetPort: 80 --- -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-agic-app-ingress @@ -55,8 +55,11 @@ spec: paths: - path: / backend: - serviceName: test-agic-app-service - servicePort: 80 + service: + name: test-agic-app-service + port: + number: 80 + pathType: Exact EOF ``` @@ -146,7 +149,7 @@ The following must be in place for AGIC to function as expected: $> kubectl get services -o wide --show-labels NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR LABELS - aspnetapp ClusterIP 10.2.63.254 80/TCP 17h app=aspnetapp + aspnetapp ClusterIP 10.2.63.254 80/TCP 17h app=aspnetapp ``` 3. **Ingress**, annotated with `kubernetes.io/ingress.class: azure/application-gateway`, referencing the service above @@ -162,20 +165,22 @@ The following must be in place for AGIC to function as expected: ```bash $> kubectl get ingress aspnetapp -o yaml - apiVersion: extensions/v1beta1 + apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: azure/application-gateway name: aspnetapp spec: - backend: - serviceName: aspnetapp - servicePort: 80 + defaultBackend: + service: + name: aspnetapp + port: + number: 80 ``` The ingress resource must be annotated with `kubernetes.io/ingress.class: azure/application-gateway`. - + #### Verify Observed Nampespace diff --git a/docs/tutorials/tutorial.e2e-ssl.md b/docs/tutorials/tutorial.e2e-ssl.md index f1c5d7b57..4035fa39f 100644 --- a/docs/tutorials/tutorial.e2e-ssl.md +++ b/docs/tutorials/tutorial.e2e-ssl.md @@ -1,7 +1,7 @@ # Tutorial: Setting up E2E SSL In this this tutorial, we will learn how to setup E2E SSL with AGIC on Application Gateway. -We will +We will 1. Generate the frontend and the backend certificates 1. Deploy a simple application with HTTPS 1. Upload the backend certificate's root certificate to Application Gateway @@ -145,7 +145,7 @@ Now, we will configure our ingress to use the `frontend` certificate for fronten ```bash cat << EOF | kubectl apply -f - -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: website-ingress @@ -166,8 +166,11 @@ spec: paths: - path: / backend: - serviceName: website-service - servicePort: 8443 + service: + name: website-service + port: + number: 8443 + pathType: Exact EOF ``` diff --git a/docs/tutorials/tutorial.general.md b/docs/tutorials/tutorial.general.md index 95cf75600..dc80af19c 100644 --- a/docs/tutorials/tutorial.general.md +++ b/docs/tutorials/tutorial.general.md @@ -39,7 +39,7 @@ Now, the `guestbook` application has been deployed. In order to expose the guestbook application we will using the following ingress resource: ```yaml -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: guestbook @@ -50,8 +50,10 @@ spec: - http: paths: - backend: - serviceName: frontend - servicePort: 80 + service: + name: frontend + port: + number: 80 ``` This ingress will expose the `frontend` service of the `guestbook-all-in-one` deployment @@ -85,7 +87,7 @@ Without specifying hostname, the guestbook service will be available on all the 1. Define the following ingress. In the ingress, specify the name of the secret in the `secretName` section. ```yaml - apiVersion: extensions/v1beta1 + apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: guestbook @@ -98,8 +100,10 @@ Without specifying hostname, the guestbook service will be available on all the - http: paths: - backend: - serviceName: frontend - servicePort: 80 + service: + name: frontend + port: + number: 80 ``` *NOTE:* Replace `` in the above Ingress Resource with the name of your secret. Store the above Ingress Resource in a file name `ing-guestbook-tls.yaml`. @@ -114,7 +118,7 @@ Without specifying hostname, the guestbook service will be available on all the Now the `guestbook` application will be available on HTTPS. -In order to make the `guestbook` application available on HTTP, annotate the `Ingress` with +In order to make the `guestbook` application available on HTTP, annotate the `Ingress` with ``` appgw.ingress.kubernetes.io/ssl-redirect: "true" @@ -131,7 +135,7 @@ By specifying hostname, the guestbook service will only be available on the spec In the ingress, specify the name of the secret in the `secretName` section and replace the hostname in the `hosts` section accordingly. ```yaml - apiVersion: extensions/v1beta1 + apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: guestbook @@ -147,8 +151,10 @@ By specifying hostname, the guestbook service will only be available on the spec http: paths: - backend: - serviceName: frontend - servicePort: 80 + service: + name: frontend + port: + number: 80 ``` 1. Deploy `ing-guestbook-tls-sni.yaml` by running diff --git a/functional_tests/cookie_name.json b/functional_tests/cookie_name.json new file mode 100644 index 000000000..3892d7272 --- /dev/null +++ b/functional_tests/cookie_name.json @@ -0,0 +1,262 @@ +{ + "properties": { + "backendAddressPools": [ + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/backendAddressPools/defaultaddresspool", + "name": "defaultaddresspool", + "properties": { + "backendAddresses": [] + } + }, + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/backendAddressPools/pool---namespace---hello-world-a-80-bp-80", + "name": "pool---namespace---hello-world-a-80-bp-80", + "properties": { + "backendAddresses": [ + { + "ipAddress": "1.1.1.1" + }, + { + "ipAddress": "1.1.1.2" + }, + { + "ipAddress": "1.1.1.3" + } + ] + } + }, + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/backendAddressPools/pool---namespace---hello-world-b-80-bp-80", + "name": "pool---namespace---hello-world-b-80-bp-80", + "properties": { + "backendAddresses": [ + { + "ipAddress": "1.1.1.1" + }, + { + "ipAddress": "1.1.1.2" + }, + { + "ipAddress": "1.1.1.3" + } + ] + } + } + ], + "backendHttpSettingsCollection": [ + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/backendHttpSettingsCollection/bp---namespace---hello-world-a-80-80---name--SlashNothingSlashSomething", + "name": "bp---namespace---hello-world-a-80-80---name--SlashNothingSlashSomething", + "properties": { + "affinityCookieName": "appgw-affinity-3362c921cf90d228a7cd586ac3d72008", + "cookieBasedAffinity": "Enabled", + "pickHostNameFromBackendAddress": false, + "port": 80, + "probe": { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/probes/pb---namespace---hello-world-a-80---name--SlashNothingSlashSomething" + }, + "protocol": "Http", + "requestTimeout": 30 + } + }, + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/backendHttpSettingsCollection/bp---namespace---hello-world-b-80-80---name--SlashNothingSlashSomething", + "name": "bp---namespace---hello-world-b-80-80---name--SlashNothingSlashSomething", + "properties": { + "affinityCookieName": "appgw-affinity-ef38541db88f854ddb5b951dd04b333d", + "cookieBasedAffinity": "Enabled", + "pickHostNameFromBackendAddress": false, + "port": 80, + "probe": { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/probes/pb---namespace---hello-world-b-80---name--SlashNothingSlashSomething" + }, + "protocol": "Http", + "requestTimeout": 30 + } + }, + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/backendHttpSettingsCollection/defaulthttpsetting", + "name": "defaulthttpsetting", + "properties": { + "cookieBasedAffinity": "Disabled", + "pickHostNameFromBackendAddress": false, + "port": 80, + "probe": { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/probes/defaultprobe-Http" + }, + "protocol": "Http", + "requestTimeout": 30 + } + } + ], + "frontendIPConfigurations": [ + { + "id": "--front-end-ip-id-1--", + "name": "xx3", + "properties": { + "publicIPAddress": { + "id": "xyz" + } + } + }, + { + "id": "--front-end-ip-id-2--", + "name": "yy3", + "properties": { + "privateIPAddress": "abc" + } + } + ], + "frontendPorts": [ + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/frontendPorts/fp-80", + "name": "fp-80", + "properties": { + "port": 80 + } + } + ], + "httpListeners": [ + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/httpListeners/fl-e1903c8aa3446b7b3207aec6d6ecba8a", + "name": "fl-e1903c8aa3446b7b3207aec6d6ecba8a", + "properties": { + "firewallPolicy": { + "id": "/some/policy/here" + }, + "frontendIPConfiguration": { + "id": "--front-end-ip-id-1--" + }, + "frontendPort": { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/frontendPorts/fp-80" + }, + "hostNames": [], + "protocol": "Http", + "requireServerNameIndication": false + } + } + ], + "probes": [ + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/probes/defaultprobe-Http", + "name": "defaultprobe-Http", + "properties": { + "host": "localhost", + "interval": 30, + "match": {}, + "minServers": 0, + "path": "/", + "pickHostNameFromBackendHttpSettings": false, + "protocol": "Http", + "timeout": 30, + "unhealthyThreshold": 3 + } + }, + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/probes/defaultprobe-Https", + "name": "defaultprobe-Https", + "properties": { + "host": "localhost", + "interval": 30, + "match": {}, + "minServers": 0, + "path": "/", + "pickHostNameFromBackendHttpSettings": false, + "protocol": "Https", + "timeout": 30, + "unhealthyThreshold": 3 + } + }, + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/probes/pb---namespace---hello-world-a-80---name--SlashNothingSlashSomething", + "name": "pb---namespace---hello-world-a-80---name--SlashNothingSlashSomething", + "properties": { + "host": "localhost", + "interval": 30, + "match": {}, + "minServers": 0, + "path": "/A", + "pickHostNameFromBackendHttpSettings": false, + "protocol": "Http", + "timeout": 30, + "unhealthyThreshold": 3 + } + }, + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/probes/pb---namespace---hello-world-b-80---name--SlashNothingSlashSomething", + "name": "pb---namespace---hello-world-b-80---name--SlashNothingSlashSomething", + "properties": { + "host": "localhost", + "interval": 30, + "match": {}, + "minServers": 0, + "path": "/", + "pickHostNameFromBackendHttpSettings": false, + "protocol": "Http", + "timeout": 30, + "unhealthyThreshold": 3 + } + } + ], + "redirectConfigurations": [], + "requestRoutingRules": [ + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/requestRoutingRules/rr-e1903c8aa3446b7b3207aec6d6ecba8a", + "name": "rr-e1903c8aa3446b7b3207aec6d6ecba8a", + "properties": { + "httpListener": { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/httpListeners/fl-e1903c8aa3446b7b3207aec6d6ecba8a" + }, + "ruleType": "PathBasedRouting", + "urlPathMap": { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/urlPathMaps/url-e1903c8aa3446b7b3207aec6d6ecba8a" + } + } + } + ], + "sku": { + "capacity": 3, + "name": "Standard_v2", + "tier": "Standard_v2" + }, + "sslCertificates": [], + "urlPathMaps": [ + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/urlPathMaps/url-e1903c8aa3446b7b3207aec6d6ecba8a", + "name": "url-e1903c8aa3446b7b3207aec6d6ecba8a", + "properties": { + "defaultBackendAddressPool": { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/backendAddressPools/pool---namespace---hello-world-b-80-bp-80" + }, + "defaultBackendHttpSettings": { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/backendHttpSettingsCollection/bp---namespace---hello-world-b-80-80---name--SlashNothingSlashSomething" + }, + "pathRules": [ + { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/urlPathMaps/url-e1903c8aa3446b7b3207aec6d6ecba8a/pathRules/pr---namespace-----name--SlashNothingSlashSomething-rule-0-path-1", + "name": "pr---namespace-----name--SlashNothingSlashSomething-rule-0-path-1", + "properties": { + "backendAddressPool": { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/backendAddressPools/pool---namespace---hello-world-a-80-bp-80" + }, + "backendHttpSettings": { + "id": "/subscriptions/--subscription--/resourceGroups/--resource-group--/providers/Microsoft.Network/applicationGateways/--app-gw-name--/backendHttpSettingsCollection/bp---namespace---hello-world-a-80-80---name--SlashNothingSlashSomething" + }, + "firewallPolicy": { + "id": "/some/policy/here" + }, + "paths": [ + "/A" + ] + } + } + ] + } + } + ] + }, + "tags": { + "ingress-for-aks-cluster-id": "/subscriptions/subid/resourcegroups/aksresgp/providers/Microsoft.ContainerService/managedClusters/aksname", + "managed-by-k8s-ingress": "a/b/c" + } +} \ No newline at end of file diff --git a/functional_tests/functional_test.go b/functional_tests/functional_test.go index 8b088a0a9..07f4f6331 100644 --- a/functional_tests/functional_test.go +++ b/functional_tests/functional_test.go @@ -3,6 +3,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // -------------------------------------------------------------------------------------------- +//go:build unittest // +build unittest package functests @@ -134,7 +135,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, annotations.SslRedirectKey: "true", }, Namespace: tests.Namespace, @@ -172,7 +173,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, }, Namespace: tests.Namespace, Name: tests.Name + "FooBazNoTLS", @@ -205,7 +206,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, annotations.HostNameExtensionKey: "foo.baz", }, Namespace: tests.Namespace, @@ -240,7 +241,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, }, Namespace: tests.OtherNamespace, Name: tests.Name + "OtherNamespace", @@ -522,7 +523,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { tests.OtherNamespace, } k8scontext.IsNetworkingV1PackageSupported = true - ctxt = k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, namespaces, 1000*time.Second, metricstore.NewFakeMetricStore()) + ctxt = k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, namespaces, 1000*time.Second, metricstore.NewFakeMetricStore(), environment.GetFakeEnv()) secKey := utils.GetResourceKey(ingressSecret.Namespace, ingressSecret.Name) _ = ctxt.CertificateSecretStore.ConvertSecret(secKey, ingressSecret) @@ -567,7 +568,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, }, Namespace: tests.Namespace, Name: tests.Name + "A", @@ -601,7 +602,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, }, Namespace: tests.Namespace, Name: tests.Name + "B", @@ -635,7 +636,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, annotations.AppGwSslCertificate: "ssl-certificate", annotations.BackendProtocolKey: "https", annotations.AppGwTrustedRootCertificate: "root-certificate", @@ -673,7 +674,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, annotations.HostNameExtensionKey: "test.com, t*.com", }, Namespace: tests.Namespace, @@ -708,7 +709,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, }, Namespace: tests.Namespace, Name: tests.Name + "SlashNothing", @@ -753,7 +754,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, }, Namespace: tests.Namespace, Name: tests.Name + "SlashNothingSlashSomething", @@ -816,7 +817,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, }, Namespace: tests.Namespace, Name: tests.Name + "MultiplePathRules", @@ -853,7 +854,7 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { ginkgo.It("Https Backend Ingress Resources without backend-protocol specified", func() { newAnnotation := map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, annotations.AppGwSslCertificate: "ssl-certificate", } @@ -1004,6 +1005,26 @@ var _ = ginkgo.Describe("Tests `appgw.ConfigBuilder`", func() { check(cbCtx, "waf_annotation.json", stopChannel, ctxt, configBuilder) }) + ginkgo.It("Cookie Name", func() { + annotatedIngress := ingressSlashNothingSlashSomething + annotatedIngress.Annotations[annotations.CookieBasedAffinityKey] = "true" + annotatedIngress.Annotations[annotations.CookieBasedAffinityDistinctNameKey] = "true" + + cbCtx := &ConfigBuilderContext{ + IngressList: []*networking.Ingress{ + annotatedIngress, + }, + ServiceList: serviceList, + EnvVariables: environment.GetFakeEnv(), + ExistingPortsByNumber: map[Port]n.ApplicationGatewayFrontendPort{ + Port(80): fixtures.GetDefaultPort(), + }, + DefaultAddressPoolID: to.StringPtr("xx"), + DefaultHTTPSettingsID: to.StringPtr("yy"), + } + check(cbCtx, "cookie_name.json", stopChannel, ctxt, configBuilder) + }) + ginkgo.It("Health Probes: same container labels; different namespaces", func() { cbCtx := &ConfigBuilderContext{ IngressList: []*networking.Ingress{ diff --git a/helm/ingress-azure/templates/clusterrole.yaml b/helm/ingress-azure/templates/clusterrole.yaml index 4e3a177b4..d1e25ebb0 100644 --- a/helm/ingress-azure/templates/clusterrole.yaml +++ b/helm/ingress-azure/templates/clusterrole.yaml @@ -40,6 +40,7 @@ rules: - networking.k8s.io resources: - ingresses + - ingressclasses verbs: - get - list diff --git a/helm/ingress-azure/templates/configmap.yaml b/helm/ingress-azure/templates/configmap.yaml index 6f2d7a3c3..a7fdae3d8 100644 --- a/helm/ingress-azure/templates/configmap.yaml +++ b/helm/ingress-azure/templates/configmap.yaml @@ -90,4 +90,20 @@ data: {{- if .Values.kubernetes.ingressClass}} INGRESS_CLASS: "{{ .Values.kubernetes.ingressClass }}" +{{- end}} + +{{- if .Values.kubernetes.ingressClassResource.controllerValue}} + INGRESS_CLASS_RESOURCE_ENABLED: "{{ .Values.kubernetes.ingressClassResource.enabled }}" +{{- end}} + +{{- if .Values.kubernetes.ingressClassResource.name}} + INGRESS_CLASS_RESOURCE_NAME: "{{ .Values.kubernetes.ingressClassResource.name }}" +{{- end}} + +{{- if .Values.kubernetes.ingressClassResource.default}} + INGRESS_CLASS_RESOURCE_DEFAULT: "{{ .Values.kubernetes.ingressClassResource.default }}" +{{- end}} + +{{- if .Values.kubernetes.ingressClassResource.controllerValue}} + INGRESS_CLASS_RESOURCE_CONTROLLER: "{{ .Values.kubernetes.ingressClassResource.controllerValue }}" {{- end}} \ No newline at end of file diff --git a/helm/ingress-azure/templates/ingressclass.yaml b/helm/ingress-azure/templates/ingressclass.yaml new file mode 100644 index 000000000..aac27c7a1 --- /dev/null +++ b/helm/ingress-azure/templates/ingressclass.yaml @@ -0,0 +1,14 @@ +{{- if .Values.kubernetes.ingressClassResource.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + labels: + app.kubernetes.io/component: controller + name: {{ .Values.kubernetes.ingressClassResource.name }} +{{- if .Values.kubernetes.ingressClassResource.default }} + annotations: + ingressclass.kubernetes.io/is-default-class: "true" +{{- end }} +spec: + controller: {{ .Values.kubernetes.ingressClassResource.controllerValue }} +{{- end }} \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/aadpodidbinding.yaml b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/aadpodidbinding.yaml index eb03e755d..68dfd303a 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/aadpodidbinding.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/aadpodidbinding.yaml @@ -4,7 +4,7 @@ apiVersion: "aadpodidentity.k8s.io/v1" kind: AzureIdentityBinding metadata: - name: RELEASE-NAME-azidbinding-ingress-azure + name: release-name-azidbinding-ingress-azure spec: - azureIdentity: RELEASE-NAME-azid-ingress-azure - selector: RELEASE-NAME-ingress-azure \ No newline at end of file + azureIdentity: release-name-azid-ingress-azure + selector: release-name-ingress-azure \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/aadpodidentity.yaml b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/aadpodidentity.yaml index 7bb175691..598dea7b3 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/aadpodidentity.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/aadpodidentity.yaml @@ -4,7 +4,7 @@ apiVersion: "aadpodidentity.k8s.io/v1" kind: AzureIdentity metadata: - name: RELEASE-NAME-azid-ingress-azure + name: release-name-azid-ingress-azure spec: type: 0 resourceID: /a/b/c diff --git a/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/configmap.yaml b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/configmap.yaml index 770324f21..5df21346e 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/configmap.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/configmap.yaml @@ -3,12 +3,12 @@ apiVersion: v1 kind: ConfigMap metadata: - name: RELEASE-NAME-cm-ingress-azure + name: release-name-cm-ingress-azure labels: app: ingress-azure chart: ingress-azure-1.4.0 heritage: Helm - release: RELEASE-NAME + release: release-name data: APPGW_VERBOSITY_LEVEL: "3" MULTI_CLUSTER_MODE: "false" @@ -18,4 +18,7 @@ data: APPGW_NAME: "gateway" APPGW_SUBNET_NAME: "gateway-subnet" AZURE_CLIENT_ID: "0000-0000-0000-0000-00000000" - USE_MANAGED_IDENTITY_FOR_POD: "true" \ No newline at end of file + USE_MANAGED_IDENTITY_FOR_POD: "true" + INGRESS_CLASS_RESOURCE_ENABLED: "true" + INGRESS_CLASS_RESOURCE_NAME: "azure-application-gateway" + INGRESS_CLASS_RESOURCE_CONTROLLER: "azure/application-gateway" \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/deployment.yaml b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/deployment.yaml index 40f322339..c340c9595 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/deployment.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/deployment.yaml @@ -3,29 +3,29 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: RELEASE-NAME-ingress-azure + name: release-name-ingress-azure labels: app: ingress-azure chart: ingress-azure-1.4.0 heritage: Helm - release: RELEASE-NAME + release: release-name spec: replicas: 1 # TODO: Make configurable when leader election is supported. selector: matchLabels: app: ingress-azure - release: RELEASE-NAME + release: release-name template: metadata: labels: app: ingress-azure - release: RELEASE-NAME - aadpodidbinding: RELEASE-NAME-ingress-azure + release: release-name + aadpodidbinding: release-name-ingress-azure annotations: prometheus.io/scrape: "true" prometheus.io/port: "8123" spec: - serviceAccountName: RELEASE-NAME-sa-ingress-azure + serviceAccountName: release-name-sa-ingress-azure securityContext: runAsUser: 0 containers: @@ -57,7 +57,7 @@ spec: fieldPath: metadata.namespace envFrom: - configMapRef: - name: RELEASE-NAME-cm-ingress-azure + name: release-name-cm-ingress-azure volumeMounts: - name: azure mountPath: /etc/appgw/ diff --git a/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/ingressclass.yaml b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/ingressclass.yaml new file mode 100644 index 000000000..fb6386af0 --- /dev/null +++ b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/ingressclass.yaml @@ -0,0 +1,10 @@ +--- +# Source: ingress-azure/templates/ingressclass.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + labels: + app.kubernetes.io/component: controller + name: azure-application-gateway +spec: + controller: azure/application-gateway \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/serviceaccount.yaml b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/serviceaccount.yaml index 8f7403757..e832c39eb 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/serviceaccount.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config-empty/ingress-azure/templates/serviceaccount.yaml @@ -7,5 +7,5 @@ metadata: app: ingress-azure chart: ingress-azure-1.4.0 heritage: Helm - release: RELEASE-NAME - name: RELEASE-NAME-sa-ingress-azure \ No newline at end of file + release: release-name + name: release-name-sa-ingress-azure \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/aadpodidbinding.yaml b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/aadpodidbinding.yaml index eb03e755d..68dfd303a 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/aadpodidbinding.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/aadpodidbinding.yaml @@ -4,7 +4,7 @@ apiVersion: "aadpodidentity.k8s.io/v1" kind: AzureIdentityBinding metadata: - name: RELEASE-NAME-azidbinding-ingress-azure + name: release-name-azidbinding-ingress-azure spec: - azureIdentity: RELEASE-NAME-azid-ingress-azure - selector: RELEASE-NAME-ingress-azure \ No newline at end of file + azureIdentity: release-name-azid-ingress-azure + selector: release-name-ingress-azure \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/aadpodidentity.yaml b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/aadpodidentity.yaml index 7bb175691..598dea7b3 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/aadpodidentity.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/aadpodidentity.yaml @@ -4,7 +4,7 @@ apiVersion: "aadpodidentity.k8s.io/v1" kind: AzureIdentity metadata: - name: RELEASE-NAME-azid-ingress-azure + name: release-name-azid-ingress-azure spec: type: 0 resourceID: /a/b/c diff --git a/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/configmap.yaml b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/configmap.yaml index 1d4c00ae8..77c6ecf21 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/configmap.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/configmap.yaml @@ -3,12 +3,12 @@ apiVersion: v1 kind: ConfigMap metadata: - name: RELEASE-NAME-cm-ingress-azure + name: release-name-cm-ingress-azure labels: app: ingress-azure chart: ingress-azure-1.4.0 heritage: Helm - release: RELEASE-NAME + release: release-name data: APPGW_VERBOSITY_LEVEL: "3" MULTI_CLUSTER_MODE: "false" @@ -19,4 +19,7 @@ data: APPGW_SUBNET_NAME: "gateway-subnet" APPGW_ENABLE_SHARED_APPGW: "true" AZURE_CLIENT_ID: "0000-0000-0000-0000-00000000" - USE_MANAGED_IDENTITY_FOR_POD: "true" \ No newline at end of file + USE_MANAGED_IDENTITY_FOR_POD: "true" + INGRESS_CLASS_RESOURCE_ENABLED: "true" + INGRESS_CLASS_RESOURCE_NAME: "azure-application-gateway" + INGRESS_CLASS_RESOURCE_CONTROLLER: "azure/application-gateway" \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/deployment.yaml b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/deployment.yaml index 40f322339..c340c9595 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/deployment.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/deployment.yaml @@ -3,29 +3,29 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: RELEASE-NAME-ingress-azure + name: release-name-ingress-azure labels: app: ingress-azure chart: ingress-azure-1.4.0 heritage: Helm - release: RELEASE-NAME + release: release-name spec: replicas: 1 # TODO: Make configurable when leader election is supported. selector: matchLabels: app: ingress-azure - release: RELEASE-NAME + release: release-name template: metadata: labels: app: ingress-azure - release: RELEASE-NAME - aadpodidbinding: RELEASE-NAME-ingress-azure + release: release-name + aadpodidbinding: release-name-ingress-azure annotations: prometheus.io/scrape: "true" prometheus.io/port: "8123" spec: - serviceAccountName: RELEASE-NAME-sa-ingress-azure + serviceAccountName: release-name-sa-ingress-azure securityContext: runAsUser: 0 containers: @@ -57,7 +57,7 @@ spec: fieldPath: metadata.namespace envFrom: - configMapRef: - name: RELEASE-NAME-cm-ingress-azure + name: release-name-cm-ingress-azure volumeMounts: - name: azure mountPath: /etc/appgw/ diff --git a/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/ingressclass.yaml b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/ingressclass.yaml new file mode 100644 index 000000000..fb6386af0 --- /dev/null +++ b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/ingressclass.yaml @@ -0,0 +1,10 @@ +--- +# Source: ingress-azure/templates/ingressclass.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + labels: + app.kubernetes.io/component: controller + name: azure-application-gateway +spec: + controller: azure/application-gateway \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/serviceaccount.yaml b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/serviceaccount.yaml index 8f7403757..e832c39eb 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/serviceaccount.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config-prohibited-target/ingress-azure/templates/serviceaccount.yaml @@ -7,5 +7,5 @@ metadata: app: ingress-azure chart: ingress-azure-1.4.0 heritage: Helm - release: RELEASE-NAME - name: RELEASE-NAME-sa-ingress-azure \ No newline at end of file + release: release-name + name: release-name-sa-ingress-azure \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/aadpodidbinding.yaml b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/aadpodidbinding.yaml index eb03e755d..68dfd303a 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/aadpodidbinding.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/aadpodidbinding.yaml @@ -4,7 +4,7 @@ apiVersion: "aadpodidentity.k8s.io/v1" kind: AzureIdentityBinding metadata: - name: RELEASE-NAME-azidbinding-ingress-azure + name: release-name-azidbinding-ingress-azure spec: - azureIdentity: RELEASE-NAME-azid-ingress-azure - selector: RELEASE-NAME-ingress-azure \ No newline at end of file + azureIdentity: release-name-azid-ingress-azure + selector: release-name-ingress-azure \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/aadpodidentity.yaml b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/aadpodidentity.yaml index 7bb175691..598dea7b3 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/aadpodidentity.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/aadpodidentity.yaml @@ -4,7 +4,7 @@ apiVersion: "aadpodidentity.k8s.io/v1" kind: AzureIdentity metadata: - name: RELEASE-NAME-azid-ingress-azure + name: release-name-azid-ingress-azure spec: type: 0 resourceID: /a/b/c diff --git a/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/configmap.yaml b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/configmap.yaml index 9378357ac..a16d12f4f 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/configmap.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/configmap.yaml @@ -3,12 +3,12 @@ apiVersion: v1 kind: ConfigMap metadata: - name: RELEASE-NAME-cm-ingress-azure + name: release-name-cm-ingress-azure labels: app: ingress-azure chart: ingress-azure-1.4.0 heritage: Helm - release: RELEASE-NAME + release: release-name data: APPGW_VERBOSITY_LEVEL: "3" MULTI_CLUSTER_MODE: "false" @@ -19,4 +19,7 @@ data: APPGW_SUBNET_NAME: "gateway-subnet" KUBERNETES_WATCHNAMESPACE: "a,b,c" AZURE_CLIENT_ID: "0000-0000-0000-0000-00000000" - USE_MANAGED_IDENTITY_FOR_POD: "true" \ No newline at end of file + USE_MANAGED_IDENTITY_FOR_POD: "true" + INGRESS_CLASS_RESOURCE_ENABLED: "true" + INGRESS_CLASS_RESOURCE_NAME: "azure-application-gateway" + INGRESS_CLASS_RESOURCE_CONTROLLER: "azure/application-gateway" \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/deployment.yaml b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/deployment.yaml index 5f5c48a74..bbf1264ca 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/deployment.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/deployment.yaml @@ -3,30 +3,30 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: RELEASE-NAME-ingress-azure + name: release-name-ingress-azure labels: app: ingress-azure chart: ingress-azure-1.4.0 heritage: Helm - release: RELEASE-NAME + release: release-name spec: replicas: 1 # TODO: Make configurable when leader election is supported. selector: matchLabels: app: ingress-azure - release: RELEASE-NAME + release: release-name template: metadata: labels: app: ingress-azure - release: RELEASE-NAME - aadpodidbinding: RELEASE-NAME-ingress-azure + release: release-name + aadpodidbinding: release-name-ingress-azure annotations: prometheus.io/scrape: "true" prometheus.io/port: "8123" custom-annotation: custom-value spec: - serviceAccountName: RELEASE-NAME-sa-ingress-azure + serviceAccountName: release-name-sa-ingress-azure securityContext: runAsGroup: 3000 runAsUser: 3000 @@ -66,7 +66,7 @@ spec: fieldPath: metadata.namespace envFrom: - configMapRef: - name: RELEASE-NAME-cm-ingress-azure + name: release-name-cm-ingress-azure volumeMounts: - name: azure mountPath: /etc/appgw/ diff --git a/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/ingressclass.yaml b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/ingressclass.yaml new file mode 100644 index 000000000..fb6386af0 --- /dev/null +++ b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/ingressclass.yaml @@ -0,0 +1,10 @@ +--- +# Source: ingress-azure/templates/ingressclass.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + labels: + app.kubernetes.io/component: controller + name: azure-application-gateway +spec: + controller: azure/application-gateway \ No newline at end of file diff --git a/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/serviceaccount.yaml b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/serviceaccount.yaml index 8f7403757..e832c39eb 100644 --- a/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/serviceaccount.yaml +++ b/helm/ingress-azure/tests/snapshots/sample-config/ingress-azure/templates/serviceaccount.yaml @@ -7,5 +7,5 @@ metadata: app: ingress-azure chart: ingress-azure-1.4.0 heritage: Helm - release: RELEASE-NAME - name: RELEASE-NAME-sa-ingress-azure \ No newline at end of file + release: release-name + name: release-name-sa-ingress-azure \ No newline at end of file diff --git a/helm/ingress-azure/values-template.yaml b/helm/ingress-azure/values-template.yaml index c6f39fd9c..2578c3eee 100644 --- a/helm/ingress-azure/values-template.yaml +++ b/helm/ingress-azure/values-template.yaml @@ -44,9 +44,16 @@ kubernetes: # cpu: 100m # memory: 100Mi -# Set this to override the default ingress class value. DEFAULT: azure/application-gateway -# This can be used to segregate ingress controllers in the same namespace -# ingressClass: agic-2 + # Set this to override the default ingress class value. DEFAULT: azure/application-gateway + # This can be used to segregate ingress controllers in the same namespace + # ingressClass: agic-2 + + # Set Ingress class resource to use the IngressClass V1 resource with Ingress V1 + ingressClassResource: + name: azure-application-gateway + enabled: true + default: false + controllerValue: "azure/application-gateway" ################################################################################ # Specify which application gateway the ingress controller will manage diff --git a/helm/ingress-azure/values.yaml b/helm/ingress-azure/values.yaml index 17d2487b6..31ec0a9a6 100644 --- a/helm/ingress-azure/values.yaml +++ b/helm/ingress-azure/values.yaml @@ -48,9 +48,16 @@ kubernetes: # cpu: 100m # memory: 100Mi -# Set this to override the default ingress class value. DEFAULT: azure/application-gateway -# This can be used to segregate ingress controllers in the same namespace -# ingressClass: agic-2 + # Set this to override the default ingress class value. DEFAULT: azure/application-gateway + # This can be used to segregate ingress controllers in the same namespace + # ingressClass: agic-2 + + # Set Ingress class resource to use the IngressClass V1 resource with Ingress V1 + ingressClassResource: + name: azure-application-gateway + enabled: true + default: false + controllerValue: "azure/application-gateway" ################################################################################ # Specify which application gateway the ingress controller will manage diff --git a/pkg/annotations/ingress_annotations.go b/pkg/annotations/ingress_annotations.go index 28436abb0..1cc2ed430 100644 --- a/pkg/annotations/ingress_annotations.go +++ b/pkg/annotations/ingress_annotations.go @@ -51,6 +51,9 @@ const ( // CookieBasedAffinityKey defines the key to enable/disable cookie based affinity for client connection. CookieBasedAffinityKey = ApplicationGatewayPrefix + "/cookie-based-affinity" + // CookieBasedAffinityDistinctNameKey defines the key to enable/disable distinct cookie names per backend for client connection. + CookieBasedAffinityDistinctNameKey = ApplicationGatewayPrefix + "/cookie-based-affinity-distinct-name" + // RequestTimeoutKey defines the request timeout to the backend. RequestTimeoutKey = ApplicationGatewayPrefix + "/request-timeout" @@ -85,9 +88,6 @@ const ( // that this is a gateway meant for the application gateway ingress controller. IstioGatewayKey = "appgw.ingress.istio.io/v1alpha3" - //DefaultIngressClass defines the default app gateway ingress value - DefaultIngressClass = "azure/application-gateway" - // FirewallPolicy is the key part of a key/value Ingress annotation. // The value of this is an ID of a Firewall Policy. The Firewall Policy must be already defined in Azure. // The policy will be attached to all URL paths declared in the annotated Ingress resource. @@ -99,12 +99,9 @@ const ( // AppGwTrustedRootCertificate indicates the names of trusted root certificates // Multiple root certificates separated by comma, e.g. "cert1,cert2" AppGwTrustedRootCertificate = ApplicationGatewayPrefix + "/appgw-trusted-root-certificate" -) -var ( - // ApplicationGatewayIngressClass defines the value of the `IngressClassKey` and `IstioGatewayKey` - // annotations that will tell the ingress controller whether it should act on this ingress resource or not. - ApplicationGatewayIngressClass = DefaultIngressClass + // RewriteRuleSetKey indicates the name of the rule set to overwrite HTTP headers. + RewriteRuleSetKey = ApplicationGatewayPrefix + "/rewrite-rule-set" ) // ProtocolEnum is the type for protocol @@ -124,21 +121,22 @@ var ProtocolEnumLookup = map[string]ProtocolEnum{ "https": HTTPS, } -// IsApplicationGatewayIngress checks if the Ingress resource can be handled by the Application Gateway ingress controller. -func IsApplicationGatewayIngress(ing *networking.Ingress) (bool, error) { - controllerName, err := parseString(ing, IngressClassKey) - return controllerName == ApplicationGatewayIngressClass, err +// IngressClass returns ingress class annotation value if set +func IngressClass(ing *networking.Ingress) (string, error) { + return parseString(ing, IngressClassKey) } -// IsIstioGatewayIngress checks if this gateway should be handled by AGIC or not -func IsIstioGatewayIngress(gateway *v1alpha3.Gateway) (bool, error) { +// IngressClass returns istio ingress class annotation value if set +func IstioGatewayIngressClass(gateway *v1alpha3.Gateway) (string, error) { val, ok := gateway.Annotations[IstioGatewayKey] if ok { - return val == ApplicationGatewayIngressClass, nil + return val, nil } - return false, controllererrors.NewError( + + return "", controllererrors.NewErrorf( controllererrors.ErrorMissingAnnotation, - "appgw.ingress.istio.io/v1alpha3 not set") + "%s is not set in Ingress %s/%s", IstioGatewayKey, gateway.Namespace, gateway.Name, + ) } // IsSslRedirect for HTTP end points. @@ -230,6 +228,11 @@ func IsCookieBasedAffinity(ing *networking.Ingress) (bool, error) { return parseBool(ing, CookieBasedAffinityKey) } +// IsCookieBasedAffinityDistinctName provides value to enable/disable distinct cookie name based affinity for client connection. +func IsCookieBasedAffinityDistinctName(ing *networking.Ingress) (bool, error) { + return parseBool(ing, CookieBasedAffinityDistinctNameKey) +} + // UsePrivateIP determines whether to use private IP with the ingress func UsePrivateIP(ing *networking.Ingress) (bool, error) { return parseBool(ing, UsePrivateIPKey) @@ -277,6 +280,11 @@ func WAFPolicy(ing *networking.Ingress) (string, error) { return parseString(ing, FirewallPolicy) } +// RewriteRuleSet name +func RewriteRuleSet(ing *networking.Ingress) (string, error) { + return parseString(ing, RewriteRuleSetKey) +} + func parseBool(ing *networking.Ingress, name string) (bool, error) { if val, ok := ing.Annotations[name]; ok { if boolVal, err := strconv.ParseBool(val); err == nil { diff --git a/pkg/annotations/ingress_annotations_test.go b/pkg/annotations/ingress_annotations_test.go index 1397b9efb..19d6bccf5 100644 --- a/pkg/annotations/ingress_annotations_test.go +++ b/pkg/annotations/ingress_annotations_test.go @@ -11,8 +11,6 @@ import ( "fmt" "testing" - "github.com/knative/pkg/apis/istio/v1alpha3" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" networking "k8s.io/api/networking/v1" @@ -39,29 +37,30 @@ func TestIt(t *testing.T) { var _ = Describe("Test ingress annotation functions", func() { annotations := map[string]string{ - "appgw.ingress.kubernetes.io/use-private-ip": "true", - "appgw.ingress.kubernetes.io/override-frontend-port": "444", - "appgw.ingress.kubernetes.io/connection-draining": "true", - "appgw.ingress.kubernetes.io/cookie-based-affinity": "true", - "appgw.ingress.kubernetes.io/ssl-redirect": "true", - "appgw.ingress.kubernetes.io/request-timeout": "123456", - "appgw.ingress.kubernetes.io/connection-draining-timeout": "3456", - "appgw.ingress.kubernetes.io/backend-path-prefix": "prefix-here", - "appgw.ingress.kubernetes.io/backend-hostname": "www.backend.com", - "appgw.ingress.kubernetes.io/hostname-extension": "www.bye.com, www.b*.com", - "appgw.ingress.kubernetes.io/appgw-ssl-certificate": "appgw-cert", - "appgw.ingress.kubernetes.io/appgw-trusted-root-certificate": "appgw-root-cert1,appgw-root-cert2", - "appgw.ingress.kubernetes.io/health-probe-hostname": "myhost.mydomain.com", - "appgw.ingress.kubernetes.io/health-probe-port": "8080", - "appgw.ingress.kubernetes.io/health-probe-path": "/healthz", - "appgw.ingress.kubernetes.io/health-probe-status-codes": "200-399, 401", - "appgw.ingress.kubernetes.io/health-probe-interval": "15", - "appgw.ingress.kubernetes.io/health-probe-timeout": "10", - "appgw.ingress.kubernetes.io/health-probe-unhealthy-threshold": "3", - "kubernetes.io/ingress.class": "azure/application-gateway", - "appgw.ingress.istio.io/v1alpha3": "azure/application-gateway", - "falseKey": "false", - "errorKey": "234error!!", + "appgw.ingress.kubernetes.io/use-private-ip": "true", + "appgw.ingress.kubernetes.io/override-frontend-port": "444", + "appgw.ingress.kubernetes.io/connection-draining": "true", + "appgw.ingress.kubernetes.io/cookie-based-affinity": "true", + "appgw.ingress.kubernetes.io/cookie-based-affinity-distinct-name": "true", + "appgw.ingress.kubernetes.io/ssl-redirect": "true", + "appgw.ingress.kubernetes.io/request-timeout": "123456", + "appgw.ingress.kubernetes.io/connection-draining-timeout": "3456", + "appgw.ingress.kubernetes.io/backend-path-prefix": "prefix-here", + "appgw.ingress.kubernetes.io/backend-hostname": "www.backend.com", + "appgw.ingress.kubernetes.io/hostname-extension": "www.bye.com, www.b*.com", + "appgw.ingress.kubernetes.io/appgw-ssl-certificate": "appgw-cert", + "appgw.ingress.kubernetes.io/appgw-trusted-root-certificate": "appgw-root-cert1,appgw-root-cert2", + "appgw.ingress.kubernetes.io/health-probe-hostname": "myhost.mydomain.com", + "appgw.ingress.kubernetes.io/health-probe-port": "8080", + "appgw.ingress.kubernetes.io/health-probe-path": "/healthz", + "appgw.ingress.kubernetes.io/health-probe-status-codes": "200-399, 401", + "appgw.ingress.kubernetes.io/health-probe-interval": "15", + "appgw.ingress.kubernetes.io/health-probe-timeout": "10", + "appgw.ingress.kubernetes.io/health-probe-unhealthy-threshold": "3", + "kubernetes.io/ingress.class": "azure/application-gateway", + "appgw.ingress.istio.io/v1alpha3": "azure/application-gateway", + "falseKey": "false", + "errorKey": "234error!!", } ing := &networking.Ingress{ @@ -84,6 +83,20 @@ var _ = Describe("Test ingress annotation functions", func() { }) }) + Context("test IsCookieBasedAffinityDistinctName", func() { + It("returns error when ingress has no annotations", func() { + ing := &networking.Ingress{} + actual, err := IsCookieBasedAffinityDistinctName(ing) + Expect(err).To(HaveOccurred()) + Expect(actual).To(Equal(false)) + }) + It("returns true", func() { + actual, err := IsCookieBasedAffinityDistinctName(ing) + Expect(err).ToNot(HaveOccurred()) + Expect(actual).To(Equal(true)) + }) + }) + Context("test appgwSslCertificate", func() { It("returns error when ingress has no annotations", func() { ing := &networking.Ingress{} @@ -295,60 +308,6 @@ var _ = Describe("Test ingress annotation functions", func() { }) }) - Context("test IsIstioGatewayIngress", func() { - It("returns error when gateway has no annotations", func() { - gateway := &v1alpha3.Gateway{} - actual, err := IsIstioGatewayIngress(gateway) - Expect(err).To(HaveOccurred()) - Expect(actual).To(Equal(false)) - }) - It("returns true with correct annotation", func() { - gateway := &v1alpha3.Gateway{ - ObjectMeta: v1.ObjectMeta{ - Annotations: annotations, - }, - } - actual, err := IsIstioGatewayIngress(gateway) - Expect(err).ToNot(HaveOccurred()) - Expect(actual).To(Equal(true)) - }) - }) - - Context("test IsApplicationGatewayIngress", func() { - - BeforeEach(func() { - ApplicationGatewayIngressClass = DefaultIngressClass - }) - - It("returns error when ingress has no annotations", func() { - ing := &networking.Ingress{} - actual, err := IsApplicationGatewayIngress(ing) - Expect(err).To(HaveOccurred()) - Expect(actual).To(Equal(false)) - }) - It("returns true with correct annotation", func() { - actual, err := IsApplicationGatewayIngress(ing) - Expect(err).ToNot(HaveOccurred()) - Expect(actual).To(Equal(true)) - }) - - It("returns true with correct annotation", func() { - ing.Annotations[IngressClassKey] = "custom-class" - ApplicationGatewayIngressClass = "custom-class" - actual, err := IsApplicationGatewayIngress(ing) - Expect(err).ToNot(HaveOccurred()) - Expect(actual).To(Equal(true)) - }) - - It("returns false with incorrect annotation", func() { - ing.Annotations[IngressClassKey] = "custom-class" - actual, err := IsApplicationGatewayIngress(ing) - Expect(ApplicationGatewayIngressClass).To(Equal(DefaultIngressClass)) - Expect(err).ToNot(HaveOccurred()) - Expect(actual).To(Equal(false)) - }) - }) - Context("test UsePrivateIP", func() { It("returns error when ingress has no annotations", func() { ing := &networking.Ingress{} diff --git a/pkg/appgw/appgw_test.go b/pkg/appgw/appgw_test.go index ec4769d66..6c03d3959 100644 --- a/pkg/appgw/appgw_test.go +++ b/pkg/appgw/appgw_test.go @@ -104,7 +104,7 @@ var _ = Describe("Tests `appgw.ConfigBuilder`", func() { Name: ingressName, Namespace: ingressNS, Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, }, }, Spec: networking.IngressSpec{ @@ -451,7 +451,7 @@ var _ = Describe("Tests `appgw.ConfigBuilder`", func() { // Create a `k8scontext` to start listiening to ingress resources. k8scontext.IsNetworkingV1PackageSupported = true - ctxt = k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{ingressNS}, 1000*time.Second, metricstore.NewFakeMetricStore()) + ctxt = k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{ingressNS}, 1000*time.Second, metricstore.NewFakeMetricStore(), environment.GetFakeEnv()) Expect(ctxt).ShouldNot(BeNil(), "Unable to create `k8scontext`") // Initialize the `ConfigBuilder` diff --git a/pkg/appgw/backendhttpsettings.go b/pkg/appgw/backendhttpsettings.go index 482251069..a47021bed 100644 --- a/pkg/appgw/backendhttpsettings.go +++ b/pkg/appgw/backendhttpsettings.go @@ -129,6 +129,10 @@ func (c *appGwConfigBuilder) resolveBackendPort(backendID backendIdentifier) (Po // find the target port number for service port specified in the ingress manifest servicePortInIngress := fmt.Sprint(backendID.Backend.Service.Port.Number) + if backendID.Backend.Service.Port.Name != "" { + servicePortInIngress = fmt.Sprint(backendID.Backend.Service.Port.Name) + } + resolvedBackendPorts := make(map[serviceBackendPortPair]interface{}) for _, servicePort := range service.Spec.Ports { // ignore UDP ports @@ -275,6 +279,12 @@ func (c *appGwConfigBuilder) generateHTTPSettings(backendID backendIdentifier, p c.recorder.Event(backendID.Ingress, v1.EventTypeWarning, events.ReasonInvalidAnnotation, err.Error()) } + if distinctName, err := annotations.IsCookieBasedAffinityDistinctName(backendID.Ingress); err == nil && distinctName { + httpSettings.AffinityCookieName = to.StringPtr(fmt.Sprintf("%s%s", "appgw-affinity-", backendID.serviceFullNameHash())) + } else if err != nil && !controllererrors.IsErrorCode(err, controllererrors.ErrorMissingAnnotation) { + c.recorder.Event(backendID.Ingress, v1.EventTypeWarning, events.ReasonInvalidAnnotation, err.Error()) + } + if reqTimeout, err := annotations.RequestTimeout(backendID.Ingress); err == nil { httpSettings.RequestTimeout = to.Int32Ptr(reqTimeout) } else if !controllererrors.IsErrorCode(err, controllererrors.ErrorMissingAnnotation) { diff --git a/pkg/appgw/backendhttpsettings_test.go b/pkg/appgw/backendhttpsettings_test.go index a77ff2af6..16b7c0456 100644 --- a/pkg/appgw/backendhttpsettings_test.go +++ b/pkg/appgw/backendhttpsettings_test.go @@ -210,4 +210,48 @@ var _ = Describe("Test the creation of Backend http settings from Ingress defini } }) }) + + Context("test backend port referenced with name", func() { + It("should have multiple rules", func() { + Expect(2).To(Equal(len(ingress.Spec.Rules)), "expects 2 rules") + }) + + ingress.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Port.Name = tests.ServiceHTTPPort + ingress.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Port.Number = 0 + ingress.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Port.Name = tests.ServiceHTTPSPort + ingress.Spec.Rules[1].HTTP.Paths[0].Backend.Service.Port.Number = 0 + + cbCtx := &ConfigBuilderContext{ + IngressList: []*networking.Ingress{ingress}, + ServiceList: []*v1.Service{service}, + DefaultAddressPoolID: to.StringPtr("xx"), + DefaultHTTPSettingsID: to.StringPtr("yy"), + } + + configBuilder.mem = memoization{} + configBuilder.newProbesMap(cbCtx) + httpSettings, _, _, _ := configBuilder.getBackendsAndSettingsMap(cbCtx) + + It("correct backend port is chosen in case of target port is resolved to multiple ports", func() { + expectedhttpSettingsLen := 3 + Expect(expectedhttpSettingsLen).To(Equal(len(httpSettings)), "httpSetting count %d should be %d", len(httpSettings), expectedhttpSettingsLen) + + for _, setting := range httpSettings { + if *setting.Name == DefaultBackendHTTPSettingsName { + Expect(int32(80)).To(Equal(*setting.Port), "default backend port %d should be 80", *setting.Port) + } else if strings.Contains(*setting.Name, strconv.Itoa(int(tests.ContainerPort))) { + // http setting for ingress with service port as 80 + Expect(tests.ContainerPort).To(Equal(*setting.Port), "setting %s backend port %d should be 9876", *setting.Name, *setting.Port) + } else if strings.Contains(*setting.Name, "75") { + // http setting for the ingress with service port as 443. Target port is https-port which resolves to multiple backend port + // and the smallest backend port is chosen + Expect(int32(75)).To(Equal(*setting.Port), "setting %s backend port %d should be 75", *setting.Name, *setting.Port) + } else { + // Dummy Failure, This should not happen + Expect(23).To(Equal(75), "setting %s is not expected to be created", *setting.Name) + } + } + }) + }) + }) diff --git a/pkg/appgw/configbuilder_test.go b/pkg/appgw/configbuilder_test.go index 41a5bea8b..ad88ca4db 100644 --- a/pkg/appgw/configbuilder_test.go +++ b/pkg/appgw/configbuilder_test.go @@ -119,7 +119,7 @@ var _ = Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, annotations.SslRedirectKey: "true", }, Namespace: ingressNS, @@ -204,7 +204,7 @@ var _ = Describe("Tests `appgw.ConfigBuilder`", func() { istioCrdClient := istio_fake.NewSimpleClientset() multiClusterCrdClient := multiCluster_fake.NewSimpleClientset() k8scontext.IsNetworkingV1PackageSupported = true - ctxt = k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{ingressNS}, 1000*time.Second, metricstore.NewFakeMetricStore()) + ctxt = k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{ingressNS}, 1000*time.Second, metricstore.NewFakeMetricStore(), environment.GetFakeEnv()) appGwy := &n.ApplicationGateway{ ApplicationGatewayPropertiesFormat: NewAppGwyConfigFixture(), @@ -458,7 +458,7 @@ var _ = Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, }, Name: serviceName, Namespace: ingressNS, @@ -720,7 +720,7 @@ var _ = Describe("Tests `appgw.ConfigBuilder`", func() { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, annotations.SslRedirectKey: "true", annotations.HostNameExtensionKey: "bye.com, test.com", }, diff --git a/pkg/appgw/identifier.go b/pkg/appgw/identifier.go index b8c427cb7..5c316d400 100644 --- a/pkg/appgw/identifier.go +++ b/pkg/appgw/identifier.go @@ -93,6 +93,10 @@ func (agw Identifier) requestRoutingRuleID(settingsName string) string { return agw.gatewayResourceID("requestRoutingRules", settingsName) } +func (agw Identifier) rewriteRuleSetID(rewriteName string) string { + return agw.gatewayResourceID("rewriteRuleSets", rewriteName) +} + func resourceRef(id string) *n.SubResource { return &n.SubResource{ID: to.StringPtr(id)} } diff --git a/pkg/appgw/ingress_rules_test.go b/pkg/appgw/ingress_rules_test.go index 779339060..5bece967e 100644 --- a/pkg/appgw/ingress_rules_test.go +++ b/pkg/appgw/ingress_rules_test.go @@ -7,6 +7,7 @@ import ( networking "k8s.io/api/networking/v1" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/annotations" + "github.com/Azure/application-gateway-kubernetes-ingress/pkg/environment" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/tests" ) @@ -129,7 +130,7 @@ var _ = Describe("MutateAppGateway ingress rules, listeners, and ports", func() // annotation settings below should be ignored newAnnotation := map[string]string{ annotations.AppGwSslCertificate: "appgw-installed-cert", - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, annotations.SslRedirectKey: "true", } diff --git a/pkg/appgw/internaltypes.go b/pkg/appgw/internaltypes.go index ffa596034..67468b974 100644 --- a/pkg/appgw/internaltypes.go +++ b/pkg/appgw/internaltypes.go @@ -106,6 +106,10 @@ func (s serviceIdentifier) serviceFullName() string { return fmt.Sprintf("%v-%v", s.Namespace, s.Name) } +func (s serviceIdentifier) serviceFullNameHash() string { + return fmt.Sprintf("%x", md5.Sum([]byte(s.serviceFullName()))) +} + func (s serviceIdentifier) serviceKey() string { return fmt.Sprintf("%v/%v", s.Namespace, s.Name) } diff --git a/pkg/appgw/public_and_private_ip_test.go b/pkg/appgw/public_and_private_ip_test.go index 82b2d8f6f..2a195dded 100644 --- a/pkg/appgw/public_and_private_ip_test.go +++ b/pkg/appgw/public_and_private_ip_test.go @@ -62,7 +62,7 @@ var _ = Describe("Tests `appgw.ConfigBuilder`", func() { ingressPublicIP := &networking.Ingress{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, }, Namespace: ingressNS, Name: "external-ingress-resource", @@ -112,7 +112,7 @@ var _ = Describe("Tests `appgw.ConfigBuilder`", func() { ingressPrivateIP := &networking.Ingress{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: environment.DefaultIngressClassController, annotations.UsePrivateIPKey: "true", }, Namespace: ingressNS, @@ -226,7 +226,7 @@ var _ = Describe("Tests `appgw.ConfigBuilder`", func() { istioCrdClient := istio_fake.NewSimpleClientset() multiClusterCrdClient := multiCluster_fake.NewSimpleClientset() k8scontext.IsNetworkingV1PackageSupported = true - ctxt := k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{ingressNS}, 1000*time.Second, metricstore.NewFakeMetricStore()) + ctxt := k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{ingressNS}, 1000*time.Second, metricstore.NewFakeMetricStore(), environment.GetFakeEnv()) secret := tests.NewSecretTestFixture() diff --git a/pkg/appgw/requestroutingrules.go b/pkg/appgw/requestroutingrules.go index 105fa7160..7fc7eeb42 100755 --- a/pkg/appgw/requestroutingrules.go +++ b/pkg/appgw/requestroutingrules.go @@ -68,7 +68,8 @@ func (c *appGwConfigBuilder) getRules(cbCtx *ConfigBuilderContext) ([]n.Applicat httpListenersMap := c.groupListenersByListenerIdentifier(cbCtx) pathMap := []n.ApplicationGatewayURLPathMap{} var requestRoutingRules []n.ApplicationGatewayRequestRoutingRule - for listenerID, urlPathMap := range c.getPathMaps(cbCtx) { + urlPathMaps := c.getPathMaps(cbCtx) + for listenerID, urlPathMap := range urlPathMaps { routingRuleName := generateRequestRoutingRuleName(listenerID) httpListener, exists := httpListenersMap[listenerID] if !exists { @@ -97,6 +98,7 @@ func (c *appGwConfigBuilder) getRules(cbCtx *ConfigBuilderContext) ([]n.Applicat rule.BackendAddressPool = nil rule.BackendHTTPSettings = nil } + rule.RewriteRuleSet = urlPathMap.DefaultRewriteRuleSet } else { // Path-based Rule rule.RuleType = n.ApplicationGatewayRequestRoutingRuleTypePathBasedRouting @@ -166,6 +168,7 @@ func (c *appGwConfigBuilder) getPathMaps(cbCtx *ConfigBuilderContext) map[listen } _, azListenerConfig := c.processIngressRuleWithTLS(rule, ingress, cbCtx.EnvVariables) + for listenerID, listenerAzConfig := range azListenerConfig { if _, exists := urlPathMaps[listenerID]; !exists { pathMapName := generateURLPathMapName(listenerID) @@ -227,7 +230,7 @@ func (c *appGwConfigBuilder) getPathMap(cbCtx *ConfigBuilderContext, listenerID } // get defaults provided by the rules if any - defaultAddressPoolID, defaultHTTPSettingsID, defaultRedirectConfigurationID := c.getDefaultFromRule(cbCtx, listenerID, listenerAzConfig, ingress, rule) + defaultAddressPoolID, defaultHTTPSettingsID, defaultRedirectConfigurationID, defaultRewriteRuleSetID := c.getDefaultFromRule(cbCtx, listenerID, listenerAzConfig, ingress, rule) if defaultRedirectConfigurationID != nil { pathMap.DefaultRedirectConfiguration = resourceRef(*defaultRedirectConfigurationID) pathMap.DefaultBackendAddressPool = nil @@ -236,13 +239,16 @@ func (c *appGwConfigBuilder) getPathMap(cbCtx *ConfigBuilderContext, listenerID pathMap.DefaultBackendAddressPool = resourceRef(*defaultAddressPoolID) pathMap.DefaultBackendHTTPSettings = resourceRef(*defaultHTTPSettingsID) } + if defaultRewriteRuleSetID != nil { + pathMap.DefaultRewriteRuleSet = resourceRef(*defaultRewriteRuleSetID) + } pathMap.PathRules = c.getPathRules(cbCtx, listenerID, listenerAzConfig, ingress, rule, ruleIdx) return &pathMap } -func (c *appGwConfigBuilder) getDefaultFromRule(cbCtx *ConfigBuilderContext, listenerID listenerIdentifier, listenerAzConfig listenerAzConfig, ingress *networking.Ingress, rule *networking.IngressRule) (*string, *string, *string) { +func (c *appGwConfigBuilder) getDefaultFromRule(cbCtx *ConfigBuilderContext, listenerID listenerIdentifier, listenerAzConfig listenerAzConfig, ingress *networking.Ingress, rule *networking.IngressRule) (*string, *string, *string, *string) { if sslRedirect, _ := annotations.IsSslRedirect(ingress); sslRedirect && listenerAzConfig.Protocol == n.ApplicationGatewayProtocolHTTP { targetListener := listenerID targetListener.FrontendPort = 443 @@ -253,7 +259,7 @@ func (c *appGwConfigBuilder) getDefaultFromRule(cbCtx *ConfigBuilderContext, lis if _, exists := redirectsSet[*redirectRef.ID]; exists { klog.V(5).Infof("Attached default redirection %s to rule %+v", *redirectRef.ID, *rule) - return nil, nil, redirectRef.ID + return nil, nil, redirectRef.ID, nil } klog.Errorf("Will not attach default redirect to rule; SSL Redirect does not exist: %s", *redirectRef.ID) } @@ -272,19 +278,23 @@ func (c *appGwConfigBuilder) getDefaultFromRule(cbCtx *ConfigBuilderContext, lis backendPools := c.newBackendPoolMap(cbCtx) _, backendHTTPSettingsMap, _, _ := c.getBackendsAndSettingsMap(cbCtx) + var defaultRewriteRuleSet *string if defBackend != nil { // has default backend defaultBackendID := generateBackendID(ingress, defRule, defPath, defBackend) defaultHTTPSettings := backendHTTPSettingsMap[defaultBackendID] defaultAddressPool := backendPools[defaultBackendID] + if rewriteRuleSet, err := annotations.RewriteRuleSet(ingress); err == nil && rewriteRuleSet != "" { + defaultRewriteRuleSet = to.StringPtr(c.appGwIdentifier.rewriteRuleSetID(rewriteRuleSet)) + } if defaultAddressPool != nil && defaultHTTPSettings != nil { poolID := to.StringPtr(c.appGwIdentifier.AddressPoolID(*defaultAddressPool.Name)) settID := to.StringPtr(c.appGwIdentifier.HTTPSettingsID(*defaultHTTPSettings.Name)) - return poolID, settID, nil + return poolID, settID, nil, defaultRewriteRuleSet } } - return cbCtx.DefaultAddressPoolID, cbCtx.DefaultHTTPSettingsID, nil + return cbCtx.DefaultAddressPoolID, cbCtx.DefaultHTTPSettingsID, nil, defaultRewriteRuleSet } func (c *appGwConfigBuilder) getPathRules(cbCtx *ConfigBuilderContext, listenerID listenerIdentifier, listenerAzConfig listenerAzConfig, ingress *networking.Ingress, rule *networking.IngressRule, ruleIdx int) *[]n.ApplicationGatewayPathRule { @@ -317,6 +327,15 @@ func (c *appGwConfigBuilder) getPathRules(cbCtx *ConfigBuilderContext, listenerI klog.V(5).Infof("Attach Firewall Policy %s to Path Rule %s", wafPolicy, paths) } + if rewriteRule, err := annotations.RewriteRuleSet(ingress); err == nil { + pathRule.RewriteRuleSet = resourceRef(c.appGwIdentifier.rewriteRuleSetID(rewriteRule)) + var paths string + if pathRule.Paths != nil { + paths = strings.Join(*pathRule.Paths, ",") + } + klog.V(5).Infof("Attach Rewrite Rule Set %s to Path Rule %s", rewriteRule, paths) + } + if sslRedirect, _ := annotations.IsSslRedirect(ingress); sslRedirect && listenerAzConfig.Protocol == n.ApplicationGatewayProtocolHTTP { targetListener := listenerID targetListener.FrontendPort = 443 @@ -366,6 +385,9 @@ func (c *appGwConfigBuilder) mergePathMap(existingPathMap *n.ApplicationGatewayU existingPathMap.DefaultBackendAddressPool = nil existingPathMap.DefaultBackendHTTPSettings = nil } + if pathMapToMerge.DefaultRewriteRuleSet != nil { + existingPathMap.DefaultRewriteRuleSet = pathMapToMerge.DefaultRewriteRuleSet + } if pathMapToMerge.PathRules == nil || len(*pathMapToMerge.PathRules) == 0 { return existingPathMap diff --git a/pkg/appgw/requestroutingrules_test.go b/pkg/appgw/requestroutingrules_test.go index f8e9f5e81..1ff90cb85 100755 --- a/pkg/appgw/requestroutingrules_test.go +++ b/pkg/appgw/requestroutingrules_test.go @@ -805,4 +805,136 @@ var _ = Describe("Test routing rules generations", func() { } }) }) + + Context("test ingress rewrite rule set with 2 ingresses one with rule set and another without", func() { + configBuilder := newConfigBuilderFixture(nil) + service := tests.NewServiceFixture(*tests.NewServicePortsFixture()...) + ingressPathBased1 := tests.NewIngressFixture() + rewriteRuleSetName := "custom-response-header" + ingressPathBased1.Annotations[annotations.RewriteRuleSetKey] = rewriteRuleSetName + + ingressPathBased2 := tests.NewIngressFixture() + testBackend := tests.NewIngressBackendFixture("test", 80) + testRule := tests.NewIngressRuleFixture(tests.Host, tests.URLPath3, *testBackend) + ingressPathBased2.Spec.Rules = []networking.IngressRule{ + testRule, + } + + cbCtx := &ConfigBuilderContext{ + IngressList: []*networking.Ingress{ingressPathBased1, ingressPathBased2}, + ServiceList: []*v1.Service{service}, + DefaultAddressPoolID: to.StringPtr("xx"), + DefaultHTTPSettingsID: to.StringPtr("yy"), + } + + _ = configBuilder.Listeners(cbCtx) + + pathMaps := configBuilder.getPathMaps(cbCtx) + + sharedRule := &ingressPathBased1.Spec.Rules[0] + + sharedListenerID := generateListenerID(ingressPathBased1, sharedRule, n.ApplicationGatewayProtocolHTTPS, nil, false) + + It("has pathrules", func() { + Expect(*pathMaps[sharedListenerID].PathRules).To(Not(BeNil())) + }) + It("has exactly three path rule", func() { + Expect(len(*pathMaps[sharedListenerID].PathRules)).To(Equal(3)) + }) + expectedRewriteRuleSet := resourceRef(configBuilder.appGwIdentifier.rewriteRuleSetID(rewriteRuleSetName)) + + // the paths defined in both ingresses have rewrite rules since the annotation in the first ingress takes + // precendence + It("has rewrite rule set in first path rule", func() { + Expect((*pathMaps[sharedListenerID].PathRules)[0].RewriteRuleSet).To(Equal(expectedRewriteRuleSet)) + }) + It("has rewrite rule set in second path rule", func() { + Expect((*pathMaps[sharedListenerID].PathRules)[1].RewriteRuleSet).To(Equal(expectedRewriteRuleSet)) + }) + + // the path that is only declared in ingress2 doesn't have rewrite rules + It("has no rewrite rule set", func() { + Expect((*pathMaps[sharedListenerID].PathRules)[2].RewriteRuleSet).To(BeNil()) + }) + }) + + Context("test ingress rewrite rule set with two ingresses with different rule sets", func() { + configBuilder := newConfigBuilderFixture(nil) + service := tests.NewServiceFixture(*tests.NewServicePortsFixture()...) + ingressPathBased1 := tests.NewIngressFixture() + rewriteRuleSetName1 := "custom-response-header1" + ingressPathBased1.Annotations[annotations.RewriteRuleSetKey] = rewriteRuleSetName1 + + ingressPathBased2 := tests.NewIngressFixture() + rewriteRuleSetName2 := "custom-response-header2" + ingressPathBased2.Annotations[annotations.RewriteRuleSetKey] = rewriteRuleSetName2 + testBackend := tests.NewIngressBackendFixture("test", 80) + testRule := tests.NewIngressRuleFixture(tests.Host, tests.URLPath3, *testBackend) + + ingressPathBased2.Spec.Rules = []networking.IngressRule{ + testRule, + } + + cbCtx := &ConfigBuilderContext{ + IngressList: []*networking.Ingress{ingressPathBased1, ingressPathBased2}, + ServiceList: []*v1.Service{service}, + DefaultAddressPoolID: to.StringPtr("xx"), + DefaultHTTPSettingsID: to.StringPtr("yy"), + } + + _ = configBuilder.Listeners(cbCtx) + + pathMaps := configBuilder.getPathMaps(cbCtx) + + sharedRule := &ingressPathBased1.Spec.Rules[0] + + sharedListenerID := generateListenerID(ingressPathBased1, sharedRule, n.ApplicationGatewayProtocolHTTPS, nil, false) + + It("has pathrules", func() { + Expect(*pathMaps[sharedListenerID].PathRules).To(Not(BeNil())) + }) + It("has exactly three path rule", func() { + Expect(len(*pathMaps[sharedListenerID].PathRules)).To(Equal(3)) + }) + expectedRewriteRuleSet1 := resourceRef(configBuilder.appGwIdentifier.rewriteRuleSetID(rewriteRuleSetName1)) + + // the paths defined in both ingresses have rewrite rules declared in the first ingress since it takes + // precendence + It("has rewrite rule set in first path rule", func() { + Expect((*pathMaps[sharedListenerID].PathRules)[0].RewriteRuleSet).To(Equal(expectedRewriteRuleSet1)) + }) + It("has rewrite rule set in second path rule", func() { + Expect((*pathMaps[sharedListenerID].PathRules)[1].RewriteRuleSet).To(Equal(expectedRewriteRuleSet1)) + }) + + expectedRewriteRuleSet2 := resourceRef(configBuilder.appGwIdentifier.rewriteRuleSetID(rewriteRuleSetName2)) + // the path that is only declared in ingress2 has the rewrite rule declared in ingress2 + It("has rewrite rule set from ingress 2", func() { + Expect((*pathMaps[sharedListenerID].PathRules)[2].RewriteRuleSet).To(Equal(expectedRewriteRuleSet2)) + }) + }) + + Context("test ingress rewrite rule set in basic ingress", func() { + configBuilder := newConfigBuilderFixture(nil) + service := tests.NewServiceFixture(*tests.NewServicePortsFixture()...) + ingress := tests.NewIngressTestFixtureBasic(tests.Namespace, "random", false) + rewriteRuleSetName := "custom-response-header" + ingress.Annotations[annotations.RewriteRuleSetKey] = rewriteRuleSetName + + cbCtx := &ConfigBuilderContext{ + IngressList: []*networking.Ingress{ingress}, + ServiceList: []*v1.Service{service}, + DefaultAddressPoolID: to.StringPtr("xx"), + DefaultHTTPSettingsID: to.StringPtr("yy"), + } + + requestRoutingRules, _ := configBuilder.getRules(cbCtx) + + expectedRewriteRuleSet := resourceRef(configBuilder.appGwIdentifier.rewriteRuleSetID(rewriteRuleSetName)) + + It("has rewrite rule set", func() { + Expect(requestRoutingRules[0].RewriteRuleSet).To(Equal(expectedRewriteRuleSet)) + }) + + }) }) diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index a4d444740..ec15197b2 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -35,7 +35,7 @@ var _ = Describe("test NewAppGwIngressController", func() { recorder := record.NewFakeRecorder(0) controller := NewAppGwIngressController(azClient, appGwIdentifier, k8sContext, recorder, metricStore, nil, false) It("should have created the AppGwIngressController struct", func() { - err := controller.Start(environment.GetEnv()) + err := controller.Start(environment.GetFakeEnv()) Expect(err).To(HaveOccurred()) controller.Stop() }) @@ -52,7 +52,7 @@ var _ = Describe("test NewAppGwIngressController", func() { multiClusterCrdClient := multiClusterFake.NewSimpleClientset() // Create a `k8scontext` to start listening to ingress resources. k8scontext.IsNetworkingV1PackageSupported = true - k8sContext := k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{}, 1000*time.Second, metricstore.NewFakeMetricStore()) + k8sContext := k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{}, 1000*time.Second, metricstore.NewFakeMetricStore(), environment.GetFakeEnv()) azClient := azure.NewFakeAzClient() appGwIdentifier := appgw.Identifier{} diff --git a/pkg/controller/mutate_aks_test.go b/pkg/controller/mutate_aks_test.go index 0901b80da..ba59f0767 100644 --- a/pkg/controller/mutate_aks_test.go +++ b/pkg/controller/mutate_aks_test.go @@ -26,6 +26,7 @@ import ( "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/agic_crd_client/clientset/versioned/fake" multiCluster_fake "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/azure_multicluster_crd_client/clientset/versioned/fake" istio_fake "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/istio_crd_client/clientset/versioned/fake" + "github.com/Azure/application-gateway-kubernetes-ingress/pkg/environment" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/k8scontext" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/metricstore" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/tests" @@ -61,7 +62,7 @@ var _ = Describe("process function tests", func() { // Create a `k8scontext` to start listening to ingress resources. k8scontext.IsNetworkingV1PackageSupported = true - ctxt = k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{tests.Namespace}, 1000*time.Second, metricstore.NewFakeMetricStore()) + ctxt = k8scontext.NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{tests.Namespace}, 1000*time.Second, metricstore.NewFakeMetricStore(), environment.GetFakeEnv()) _, err := k8sClient.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) Expect(err).Should(BeNil(), "Unable to create the namespace %s: %v", tests.Name, err) diff --git a/pkg/environment/environment.go b/pkg/environment/environment.go index 83465cee6..b6468e404 100644 --- a/pkg/environment/environment.go +++ b/pkg/environment/environment.go @@ -95,13 +95,33 @@ const ( // ReconcilePeriodSecondsVarName is an environment variable to control reconcile period for the AGIC. ReconcilePeriodSecondsVarName = "RECONCILE_PERIOD_SECONDS" - // IngressClass is an environment variable - IngressClass = "INGRESS_CLASS" + // IngressClassVarName is an environment variable + IngressClassVarName = "INGRESS_CLASS" + + // IngressClassResourceEnabledVarName is an environment variable to enable V1 Ingress class. + IngressClassResourceEnabledVarName = "INGRESS_CLASS_RESOURCE_ENABLED" + + // IngressClassResourceNameVarName is an environment variable which specifies the name of the ingress class object to watch. + IngressClassResourceNameVarName = "INGRESS_CLASS_RESOURCE_NAME" + + // IngressClassResourceDefaultVarName is an environment variable to enable AGIC as default ingress. + IngressClassResourceDefaultVarName = "INGRESS_CLASS_RESOURCE_DEFAULT" + + // IngressClassControllerNameVarName is an environment variable to specify controller class. + IngressClassControllerNameVarName = "INGRESS_CLASS_RESOURCE_CONTROLLER" // MultiClusterModeVarName is an environment variable to control whether AGIC monitors Ingresses or MutliClusterIngresses MultiClusterModeVarName = "MULTI_CLUSTER_MODE" ) +const ( + //DefaultIngressClassController defines the default app gateway ingress value + DefaultIngressClassController = "azure/application-gateway" + + //DefaultIngressClassResourceName defines the default app gateway ingress class object name + DefaultIngressClassResourceName = "azure-application-gateway" +) + var ( portNumberValidator = regexp.MustCompile(`^[0-9]{4,5}$`) skuValidator = regexp.MustCompile(`WAF_v2|Standard_v2`) @@ -122,6 +142,10 @@ type EnvVariables struct { AppGwSkuName string AuthLocation string IngressClass string + IngressClassControllerName string + IngressClassResourceEnabled bool + IngressClassResourceName string + IngressClassResourceDefault bool WatchNamespace string UsePrivateIP bool VerbosityLevel string @@ -165,6 +189,18 @@ func (env *EnvVariables) Consolidate(cpConfig *azure.CloudProviderConfig) { if env.AppGwSubnetName == "" { env.AppGwSubnetName = env.AppGwName + "-subnet" } + + if env.IngressClass != "" { + env.IngressClassControllerName = env.IngressClass + } + + if env.IngressClassControllerName == "" { + env.IngressClassControllerName = DefaultIngressClassController + } + + if env.IngressClassResourceName == "" { + env.IngressClassResourceName = DefaultIngressClassResourceName + } } // GetEnv returns values for defined environment variables for Ingress Controller. @@ -184,7 +220,11 @@ func GetEnv() EnvVariables { AppGwSubnetID: os.Getenv(AppGwSubnetIDVarName), AppGwSkuName: GetEnvironmentVariable(AppGwSkuVarName, "Standard_v2", skuValidator), AuthLocation: os.Getenv(AuthLocationVarName), - IngressClass: os.Getenv(IngressClass), + IngressClass: os.Getenv(IngressClassVarName), + IngressClassResourceEnabled: GetEnvironmentVariable(IngressClassResourceEnabledVarName, "false", boolValidator) == "true", + IngressClassResourceName: os.Getenv(IngressClassResourceNameVarName), + IngressClassResourceDefault: GetEnvironmentVariable(IngressClassResourceDefaultVarName, "false", boolValidator) == "true", + IngressClassControllerName: os.Getenv(IngressClassControllerNameVarName), WatchNamespace: os.Getenv(WatchNamespaceVarName), UsePrivateIP: usePrivateIP, VerbosityLevel: os.Getenv(VerbosityLevelVarName), diff --git a/pkg/environment/fake.go b/pkg/environment/fake.go index e84127630..2396c5447 100644 --- a/pkg/environment/fake.go +++ b/pkg/environment/fake.go @@ -8,13 +8,14 @@ package environment // GetFakeEnv returns fake values for defined environment variables for Ingress Controller. func GetFakeEnv() EnvVariables { env := EnvVariables{ - SubscriptionID: "--SubscriptionID--", - ResourceGroupName: "--ResourceGroupName--", - AppGwName: "--AppGwName--", - AuthLocation: "--AuthLocation--", - WatchNamespace: "--WatchNamespace--", - UsePrivateIP: false, - VerbosityLevel: "123456789", + SubscriptionID: "--SubscriptionID--", + ResourceGroupName: "--ResourceGroupName--", + AppGwName: "--AppGwName--", + AuthLocation: "--AuthLocation--", + WatchNamespace: "--WatchNamespace--", + UsePrivateIP: false, + VerbosityLevel: "123456789", + IngressClassControllerName: DefaultIngressClassController, } return env diff --git a/pkg/k8scontext/context.go b/pkg/k8scontext/context.go index 5d8819814..d331f622d 100644 --- a/pkg/k8scontext/context.go +++ b/pkg/k8scontext/context.go @@ -53,7 +53,7 @@ var namespacesToIgnore = map[string]interface{}{ } // NewContext creates a context based on a Kubernetes client instance. -func NewContext(kubeClient kubernetes.Interface, crdClient versioned.Interface, multiClusterCrdClient multicluster_versioned.Interface, istioCrdClient istio_versioned.Interface, namespaces []string, resyncPeriod time.Duration, metricStore metricstore.MetricStore) *Context { +func NewContext(kubeClient kubernetes.Interface, crdClient versioned.Interface, multiClusterCrdClient multicluster_versioned.Interface, istioCrdClient istio_versioned.Interface, namespaces []string, resyncPeriod time.Duration, metricStore metricstore.MetricStore, envVariables environment.EnvVariables) *Context { informerFactory := informers.NewSharedInformerFactory(kubeClient, resyncPeriod) crdInformerFactory := externalversions.NewSharedInformerFactory(crdClient, resyncPeriod) multiClusterCrdInformerFactory := multicluster_externalversions.NewSharedInformerFactory(multiClusterCrdClient, resyncPeriod) @@ -76,6 +76,7 @@ func NewContext(kubeClient kubernetes.Interface, crdClient versioned.Interface, if IsNetworkingV1PackageSupported { informerCollection.Ingress = informerFactory.Networking().V1().Ingresses().Informer() + informerCollection.IngressClass = informerFactory.Networking().V1().IngressClasses().Informer() } else { informerCollection.Ingress = informerFactory.Extensions().V1beta1().Ingresses().Informer() } @@ -83,6 +84,7 @@ func NewContext(kubeClient kubernetes.Interface, crdClient versioned.Interface, cacheCollection := CacheCollection{ Endpoints: informerCollection.Endpoints.GetStore(), Ingress: informerCollection.Ingress.GetStore(), + IngressClass: informerCollection.IngressClass.GetStore(), Pods: informerCollection.Pods.GetStore(), Secret: informerCollection.Secret.GetStore(), Service: informerCollection.Service.GetStore(), @@ -110,6 +112,11 @@ func NewContext(kubeClient kubernetes.Interface, crdClient versioned.Interface, MetricStore: metricStore, namespaces: make(map[string]interface{}), + + ingressClassControllerName: envVariables.IngressClassControllerName, + ingressClassResourceName: envVariables.IngressClassResourceName, + ingressClassResourceEnabled: envVariables.IngressClassResourceEnabled, + ingressClassResourceDefault: envVariables.IngressClassResourceDefault, } for _, ns := range namespaces { @@ -139,6 +146,7 @@ func NewContext(kubeClient kubernetes.Interface, crdClient versioned.Interface, // Register event handlers. informerCollection.Endpoints.AddEventHandler(resourceHandler) informerCollection.Ingress.AddEventHandler(ingressResourceHandler) + informerCollection.IngressClass.AddEventHandler(resourceHandler) informerCollection.Pods.AddEventHandler(resourceHandler) informerCollection.Secret.AddEventHandler(secretResourceHandler) informerCollection.Service.AddEventHandler(resourceHandler) @@ -180,6 +188,7 @@ func (c *Context) Run(stopChannel chan struct{}, omitCRDs bool, envVariables env c.informers.Service, c.informers.Secret, c.informers.Ingress, + c.informers.IngressClass, //TODO: enabled by ccp feature flag // c.informers.AzureApplicationGatewayBackendPool, @@ -480,13 +489,13 @@ func (c *Context) ListHTTPIngresses() []*networking.Ingress { ingressList = append(ingressList, ingress) } } - return filterAndSort(ingressList) + return c.filterAndSort(ingressList) } -func filterAndSort(ingList []*networking.Ingress) []*networking.Ingress { +func (c *Context) filterAndSort(ingList []*networking.Ingress) []*networking.Ingress { var ingressList []*networking.Ingress for _, ingress := range ingList { - if !IsIngressApplicationGateway(ingress) { + if !c.IsIngressClass(ingress) { continue } if len(ingress.Spec.Rules) > 0 && !hasHTTPRule(ingress) { @@ -626,7 +635,7 @@ func (c *Context) GetEndpointsForVirtualService(virtualService v1alpha3.VirtualS func (c *Context) GetGateways() []*v1alpha3.Gateway { annotatedGateways := make([]*v1alpha3.Gateway, 0) for _, gateway := range c.ListIstioGateways() { - if annotated, _ := annotations.IsIstioGatewayIngress(gateway); annotated { + if annotated := c.IsIstioGatewayIngress(gateway); annotated { annotatedGateways = append(annotatedGateways, gateway) } } @@ -802,12 +811,6 @@ func (c *Context) updateMultiClusterIngressStatus(ingressToUpdate networking.Ing return nil } -// IsIngressApplicationGateway checks if application gateway annotation is present on the ingress -func IsIngressApplicationGateway(ingress *networking.Ingress) bool { - val, _ := annotations.IsApplicationGatewayIngress(ingress) - return val -} - func hasHTTPRule(ingress *networking.Ingress) bool { for _, rule := range ingress.Spec.Rules { if rule.HTTP != nil { @@ -864,3 +867,50 @@ func (c *Context) isServiceReferencedByAnyIngress(service *v1.Service) bool { return false } + +// getIngressClassResource gets ingress class object with specified name +func (c *Context) getIngressClassResource(ingressClassName string) *networking.IngressClass { + ingressClassInterface, exist, err := c.Caches.IngressClass.GetByKey(ingressClassName) + if err != nil { + return nil + } + + if !exist { + return nil + } + + return ingressClassInterface.(*networking.IngressClass) +} + +// IsIngressClass checks if the Ingress resource can be handled by the Application Gateway ingress controller. +func (c *Context) IsIngressClass(ing *networking.Ingress) bool { + + // match by annotation (for Backward compatibility) + if className, err := annotations.IngressClass(ing); err == nil && className != "" { + return className == c.ingressClassControllerName + } + + // match by ingress class resource + if c.ingressClassResourceEnabled { + // if IngressClassName in ingress that compare it with the controller type + if ing.Spec.IngressClassName != nil { + ingressClass := c.getIngressClassResource(*ing.Spec.IngressClassName) + return ingressClass != nil && ingressClass.Spec.Controller == c.ingressClassControllerName + } + + // if IngressClassName is nil, then match if AGIC is default ingress + return c.ingressClassResourceDefault + } + + return false +} + +// IsIstioGatewayIngress checks if this gateway should be handled by AGIC or not +func (c *Context) IsIstioGatewayIngress(gateway *v1alpha3.Gateway) bool { + className, err := annotations.IstioGatewayIngressClass(gateway) + if err != nil { + return false + } + + return className == c.ingressClassControllerName +} diff --git a/pkg/k8scontext/handlers_test.go b/pkg/k8scontext/handlers_test.go index aa550fabd..70b540eed 100644 --- a/pkg/k8scontext/handlers_test.go +++ b/pkg/k8scontext/handlers_test.go @@ -20,6 +20,7 @@ import ( "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/agic_crd_client/clientset/versioned/fake" multiClusterFake "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/azure_multicluster_crd_client/clientset/versioned/fake" istioFake "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/istio_crd_client/clientset/versioned/fake" + "github.com/Azure/application-gateway-kubernetes-ingress/pkg/environment" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/tests" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/utils" @@ -49,7 +50,7 @@ var _ = ginkgo.Describe("K8scontext General Cache Handlers", func() { Expect(err).ToNot(HaveOccurred()) IsNetworkingV1PackageSupported = true - ctx = NewContext(k8sClient, fake.NewSimpleClientset(), multiClusterFake.NewSimpleClientset(), istioFake.NewSimpleClientset(), []string{"ns"}, 1000*time.Second, metricstore.NewFakeMetricStore()) + ctx = NewContext(k8sClient, fake.NewSimpleClientset(), multiClusterFake.NewSimpleClientset(), istioFake.NewSimpleClientset(), []string{"ns"}, 1000*time.Second, metricstore.NewFakeMetricStore(), environment.GetFakeEnv()) h = handlers{ context: ctx, } diff --git a/pkg/k8scontext/ingress_handlers.go b/pkg/k8scontext/ingress_handlers.go index 0f04b38e3..60b80472a 100644 --- a/pkg/k8scontext/ingress_handlers.go +++ b/pkg/k8scontext/ingress_handlers.go @@ -23,7 +23,7 @@ func (h handlers) ingressAdd(obj interface{}) { return } - if !IsIngressApplicationGateway(ing) { + if !h.context.IsIngressClass(ing) { return } @@ -74,7 +74,7 @@ func (h handlers) ingressDelete(obj interface{}) { if ing == nil { return } - if !IsIngressApplicationGateway(ing) { + if !h.context.IsIngressClass(ing) { return } ingKey := utils.GetResourceKey(ing.Namespace, ing.Name) @@ -100,7 +100,7 @@ func (h handlers) ingressUpdate(oldObj, newObj interface{}) { return } oldIng, _ := convert.ToIngressV1(oldObj) - if !IsIngressApplicationGateway(ing) && !IsIngressApplicationGateway(oldIng) { + if !h.context.IsIngressClass(ing) && !h.context.IsIngressClass(oldIng) { return } if ing.Spec.TLS != nil && len(ing.Spec.TLS) > 0 { diff --git a/pkg/k8scontext/ingress_handlers_test.go b/pkg/k8scontext/ingress_handlers_test.go index 9d33c0beb..e9fea030f 100644 --- a/pkg/k8scontext/ingress_handlers_test.go +++ b/pkg/k8scontext/ingress_handlers_test.go @@ -19,6 +19,7 @@ import ( "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/agic_crd_client/clientset/versioned/fake" multiClusterFake "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/azure_multicluster_crd_client/clientset/versioned/fake" istioFake "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/istio_crd_client/clientset/versioned/fake" + "github.com/Azure/application-gateway-kubernetes-ingress/pkg/environment" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/metricstore" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/tests" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/tests/fixtures" @@ -55,7 +56,7 @@ var _ = ginkgo.Describe("K8scontext Ingress Cache Handlers", func() { Expect(err).To(BeNil()) IsNetworkingV1PackageSupported = true - ctx = NewContext(k8sClient, fake.NewSimpleClientset(), multiClusterFake.NewSimpleClientset(), istioFake.NewSimpleClientset(), []string{"ns"}, 1000*time.Second, metricstore.NewFakeMetricStore()) + ctx = NewContext(k8sClient, fake.NewSimpleClientset(), multiClusterFake.NewSimpleClientset(), istioFake.NewSimpleClientset(), []string{"ns"}, 1000*time.Second, metricstore.NewFakeMetricStore(), environment.GetFakeEnv()) h = handlers{ context: ctx, } diff --git a/pkg/k8scontext/k8scontext_test.go b/pkg/k8scontext/k8scontext_test.go index c60cec1a4..4578760ea 100644 --- a/pkg/k8scontext/k8scontext_test.go +++ b/pkg/k8scontext/k8scontext_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/getlantern/deepcopy" + "github.com/knative/pkg/apis/istio/v1alpha3" "github.com/onsi/ginkgo" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" @@ -26,6 +27,7 @@ import ( "github.com/Azure/application-gateway-kubernetes-ingress/pkg/environment" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/metricstore" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/tests" + "github.com/Azure/go-autorest/autorest/to" ) var _ = ginkgo.Describe("K8scontext", func() { @@ -92,7 +94,7 @@ var _ = ginkgo.Describe("K8scontext", func() { // Create a `k8scontext` to start listening to ingress resources. IsNetworkingV1PackageSupported = true - ctxt = NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{ingressNS}, 1000*time.Second, metricstore.NewFakeMetricStore()) + ctxt = NewContext(k8sClient, crdClient, multiClusterCrdClient, istioCrdClient, []string{ingressNS}, 1000*time.Second, metricstore.NewFakeMetricStore(), environment.GetFakeEnv()) Expect(ctxt).ShouldNot(BeNil(), "Unable to create `k8scontext`") }) @@ -182,7 +184,7 @@ var _ = ginkgo.Describe("K8scontext", func() { nonAppGWIngress.Name = ingressName + "123" // Change the `Annotation` so that the controller doesn't see this Ingress. - nonAppGWIngress.Annotations[annotations.IngressClassKey] = annotations.ApplicationGatewayIngressClass + "123" + nonAppGWIngress.Annotations[annotations.IngressClassKey] = environment.DefaultIngressClassController + "123" _, err = k8sClient.NetworkingV1().Ingresses(ingressNS).Create(context.TODO(), nonAppGWIngress, metav1.CreateOptions{}) Expect(err).ToNot(HaveOccurred(), "Unable to create non-Application Gateway ingress resource due to: %v", err) @@ -437,8 +439,188 @@ var _ = ginkgo.Describe("K8scontext", func() { ingrList := []*networking.Ingress{ ingr, } - finalList := filterAndSort(ingrList) + finalList := ctxt.filterAndSort(ingrList) Expect(finalList).To(ContainElement(ingr)) }) }) + + ginkgo.Context("Check IsIstioGatewayIngress", func() { + ginkgo.BeforeEach(func() { + ctxt.ingressClassControllerName = environment.DefaultIngressClassController + }) + + ginkgo.It("returns error when gateway has no annotations", func() { + gateway := &v1alpha3.Gateway{} + actual := ctxt.IsIstioGatewayIngress(gateway) + Expect(actual).To(Equal(false)) + }) + + ginkgo.It("returns true with correct annotation", func() { + gateway := &v1alpha3.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + annotations.IstioGatewayKey: environment.DefaultIngressClassController, + }, + }, + } + actual := ctxt.IsIstioGatewayIngress(gateway) + Expect(actual).To(Equal(true)) + }) + }) + + ginkgo.Context("Check IsApplicationGatewayIngress", func() { + ginkgo.BeforeEach(func() { + ctxt.ingressClassControllerName = environment.DefaultIngressClassController + }) + + ginkgo.It("returns error when ingress has no annotations", func() { + ing := &networking.Ingress{} + actual := ctxt.IsIngressClass(ing) + Expect(actual).To(Equal(false)) + }) + + ginkgo.It("returns true with correct annotation", func() { + actual := ctxt.IsIngressClass(ingress) + Expect(actual).To(Equal(true)) + }) + + ginkgo.It("returns true with correct annotation", func() { + ingress.Annotations[annotations.IngressClassKey] = "custom-class" + ctxt.ingressClassControllerName = "custom-class" + actual := ctxt.IsIngressClass(ingress) + Expect(actual).To(Equal(true)) + }) + + ginkgo.It("returns false with incorrect annotation", func() { + ingress.Annotations[annotations.IngressClassKey] = "custom-class" + actual := ctxt.IsIngressClass(ingress) + Expect(ctxt.ingressClassControllerName).To(Equal(environment.DefaultIngressClassController)) + Expect(actual).To(Equal(false)) + }) + }) + + ginkgo.Context("Check Ingress Class Resource is used correctly for filtering ingress", func() { + ginkgo.BeforeEach(func() { + // create and ingress class in k8s + ingressClass := tests.GetIngressClass() + ctxt.kubeClient.NetworkingV1().IngressClasses().Create(context.TODO(), ingressClass, metav1.CreateOptions{}) + + // update ingress resource to use ingress class name + delete(ingress.Annotations, annotations.IngressClassKey) + ingress.Spec.IngressClassName = to.StringPtr(environment.DefaultIngressClassResourceName) + _, err := k8sClient.NetworkingV1().Ingresses(ingressNS).Update(context.TODO(), ingress, metav1.UpdateOptions{}) + Expect(err).ToNot(HaveOccurred(), "Unabled to update stored ingresses resource due to: %v", err) + + // configure context to enable ingress class resource + ctxt.ingressClassResourceEnabled = true + ctxt.ingressClassResourceName = tests.IngressClassResourceName + ctxt.ingressClassControllerName = tests.IngressClassController + ctxt.ingressClassResourceDefault = false + }) + + ginkgo.It("Should not filter ingress based on ingress class resource if feature is disabled", func() { + // start the informers. This will sync the cache with the latest ingress. + runErr := ctxt.Run(stopChannel, true, environment.GetFakeEnv()) + Expect(runErr).ToNot(HaveOccurred()) + + // disable ingress class feature + ctxt.ingressClassResourceEnabled = false + + // ensure that ingress is present in the informer cache + ingressListInterface := ctxt.Caches.Ingress.List() + Expect(len(ingressListInterface)).To(Equal(1), "Expected to have a single ingress in the cache but found: %d ingresses", len(ingressListInterface)) + + // check that IsIngressClass is true for ingress + Expect(ctxt.IsIngressClass(ingress)).To(BeFalse(), "Expected to not match ingress filter") + + // check that ListHTTPIngresses is able to filter the ingress with the ingress class + testIngresses := ctxt.ListHTTPIngresses() + Expect(len(testIngresses)).To(Equal(0), "Expected no ingress in the k8scontext but found: %d ingresses", len(testIngresses)) + }) + + ginkgo.It("(backward compatibility) Should filter both Ingress with annotation or matching Ingress Class resource", func() { + // add another ingress that doesn't match the expected ingress class + ingressWithIngressClassAnnotation := tests.NewIngressTestFixture(ingressNS, "other-ingress") + _, err := ctxt.kubeClient.NetworkingV1().Ingresses(ingressNS).Create(context.TODO(), &ingressWithIngressClassAnnotation, metav1.CreateOptions{}) + Expect(err).ToNot(HaveOccurred()) + + // start the informers. This will sync the cache with the latest ingress. + runErr := ctxt.Run(stopChannel, true, environment.GetFakeEnv()) + Expect(runErr).ToNot(HaveOccurred()) + + // ensure that ingress is present in the informer cache + ingressListInterface := ctxt.Caches.Ingress.List() + Expect(len(ingressListInterface)).To(Equal(2), "Expected to have a single ingress in the cache but found: %d ingresses", len(ingressListInterface)) + + // check that IsIngressClass is true for ingress + Expect(ctxt.IsIngressClass(ingress)).To(BeTrue(), "Expected ingress to be of matching ingress class") + Expect(ctxt.IsIngressClass(&ingressWithIngressClassAnnotation)).To(BeTrue(), "Expected ingress to be of matching ingress class") + + // check that ListHTTPIngresses is able to filter the ingress with the ingress class + testIngresses := ctxt.ListHTTPIngresses() + Expect(len(testIngresses)).To(Equal(2), "Expected to have a single ingress in the k8scontext but found: %d ingresses", len(testIngresses)) + + // make sure the ingress we got is the ingress we stored. + Expect(testIngresses[0]).To(Equal(ingress), "Expected to retrieve the same ingress that we inserted, but it seems we found the following ingress: %v", testIngresses[0]) + Expect(testIngresses[1]).To(Equal(&ingressWithIngressClassAnnotation), "Expected to retrieve the same ingress that we inserted, but it seems we found the following ingress: %v", testIngresses[1]) + }) + + ginkgo.It("Should not match other ingress classes", func() { + // add another ingress that doesn't match the expected ingress class + ingressForOtherIngressClass := tests.GetVerySimpleIngress() + ingressForOtherIngressClass.Spec.IngressClassName = to.StringPtr("other-ingress-class") + delete(ingressForOtherIngressClass.Annotations, annotations.IngressClassKey) + _, err := ctxt.kubeClient.NetworkingV1().Ingresses(ingressNS).Create(context.TODO(), ingressForOtherIngressClass, metav1.CreateOptions{}) + Expect(err).ToNot(HaveOccurred()) + + // start the informers. This will sync the cache with the latest ingress. + runErr := ctxt.Run(stopChannel, true, environment.GetFakeEnv()) + Expect(runErr).ToNot(HaveOccurred()) + + // ensure that ingress is present in the informer cache + ingressListInterface := ctxt.Caches.Ingress.List() + Expect(len(ingressListInterface)).To(Equal(2), "Expected to have a single ingress in the cache but found: %d ingresses", len(ingressListInterface)) + + // check that IsIngressClass is true for ingress + Expect(ctxt.IsIngressClass(ingressForOtherIngressClass)).To(BeFalse(), "Expected ingress to not match ingress class") + + // check that ListHTTPIngresses is able to filter the ingress with the ingress class + testIngresses := ctxt.ListHTTPIngresses() + Expect(len(testIngresses)).To(Equal(1), "Expected to have a single ingress in the k8scontext but found: %d ingresses", len(testIngresses)) + + // make sure the ingress we got is the ingress we stored. + Expect(testIngresses[0]).To(Equal(ingress), "Expected to retrieve the same ingress that we inserted, but it seems we found the following ingress: %v", testIngresses[0]) + }) + + ginkgo.It("should match ingress without ingressClass if AGIC is marked as default", func() { + // add another ingress that doesn't match the expected ingress class + ingressWithoutIngressClass := tests.NewIngressTestFixture(ingressNS, "other-ingress") + delete(ingressWithoutIngressClass.Annotations, annotations.IngressClassKey) + _, err := ctxt.kubeClient.NetworkingV1().Ingresses(ingressNS).Create(context.TODO(), &ingressWithoutIngressClass, metav1.CreateOptions{}) + Expect(err).ToNot(HaveOccurred()) + + // start the informers. This will sync the cache with the latest ingress. + runErr := ctxt.Run(stopChannel, true, environment.GetFakeEnv()) + Expect(runErr).ToNot(HaveOccurred()) + + // mark AGIC as default ingress class + ctxt.ingressClassResourceDefault = true + + // ensure that ingress is present in the informer cache + ingressListInterface := ctxt.Caches.Ingress.List() + Expect(len(ingressListInterface)).To(Equal(2), "Expected to have a single ingress in the cache but found: %d ingresses", len(ingressListInterface)) + + // check that IsIngressClass is true for ingress + Expect(ctxt.IsIngressClass(ingress)).To(BeTrue(), "Expected ingress to be of matching ingress class") + Expect(ctxt.IsIngressClass(&ingressWithoutIngressClass)).To(BeTrue(), "Expected ingress to be of matching ingress class") + + // check that ListHTTPIngresses is able to filter the ingress with the ingress class + testIngresses := ctxt.ListHTTPIngresses() + Expect(len(testIngresses)).To(Equal(2), "Expected to have a single ingress in the k8scontext but found: %d ingresses", len(testIngresses)) + + // make sure the ingress we got is the ingress we stored. + Expect(testIngresses[0]).To(Equal(ingress), "Expected to retrieve the same ingress that we inserted, but it seems we found the following ingress: %v", testIngresses[0]) + Expect(testIngresses[1]).To(Equal(&ingressWithoutIngressClass), "Expected to retrieve the same ingress that we inserted, but it seems we found the following ingress: %v", testIngresses[1]) + }) + }) }) diff --git a/pkg/k8scontext/secrets_handlers_test.go b/pkg/k8scontext/secrets_handlers_test.go index f12ece3a8..b29cfcbdc 100644 --- a/pkg/k8scontext/secrets_handlers_test.go +++ b/pkg/k8scontext/secrets_handlers_test.go @@ -20,6 +20,7 @@ import ( "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/agic_crd_client/clientset/versioned/fake" multiClusterFake "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/azure_multicluster_crd_client/clientset/versioned/fake" istioFake "github.com/Azure/application-gateway-kubernetes-ingress/pkg/crd_client/istio_crd_client/clientset/versioned/fake" + "github.com/Azure/application-gateway-kubernetes-ingress/pkg/environment" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/tests" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/utils" @@ -49,7 +50,7 @@ var _ = ginkgo.Describe("K8scontext Secrets Cache Handlers", func() { Expect(err).ToNot(HaveOccurred()) IsNetworkingV1PackageSupported = true - ctx = NewContext(k8sClient, fake.NewSimpleClientset(), multiClusterFake.NewSimpleClientset(), istioFake.NewSimpleClientset(), []string{"ns"}, 1000*time.Second, metricstore.NewFakeMetricStore()) + ctx = NewContext(k8sClient, fake.NewSimpleClientset(), multiClusterFake.NewSimpleClientset(), istioFake.NewSimpleClientset(), []string{"ns"}, 1000*time.Second, metricstore.NewFakeMetricStore(), environment.GetFakeEnv()) h = handlers{ context: ctx, } diff --git a/pkg/k8scontext/types.go b/pkg/k8scontext/types.go index 28912f45d..be99bbd8c 100644 --- a/pkg/k8scontext/types.go +++ b/pkg/k8scontext/types.go @@ -16,6 +16,7 @@ import ( type InformerCollection struct { Endpoints cache.SharedIndexInformer Ingress cache.SharedIndexInformer + IngressClass cache.SharedIndexInformer Pods cache.SharedIndexInformer Secret cache.SharedIndexInformer Service cache.SharedIndexInformer @@ -34,6 +35,7 @@ type InformerCollection struct { type CacheCollection struct { Endpoints cache.Store Ingress cache.Store + IngressClass cache.Store Pods cache.Store Secret cache.Store Service cache.Store @@ -68,6 +70,11 @@ type Context struct { MetricStore metricstore.MetricStore namespaces map[string]interface{} + + ingressClassControllerName string + ingressClassResourceName string + ingressClassResourceEnabled bool + ingressClassResourceDefault bool } // IPAddress is type for IP address string diff --git a/pkg/metricstore/metricstore.go b/pkg/metricstore/metricstore.go index 694beed7a..8f3fd2a0e 100644 --- a/pkg/metricstore/metricstore.go +++ b/pkg/metricstore/metricstore.go @@ -13,7 +13,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/Azure/application-gateway-kubernetes-ingress/pkg/annotations" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/controllererrors" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/environment" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/version" @@ -56,7 +55,7 @@ type AGICMetricStore struct { // NewMetricStore returns a new metric store func NewMetricStore(envVariable environment.EnvVariables) MetricStore { constLabels := prometheus.Labels{ - "controller_class": annotations.ApplicationGatewayIngressClass, + "controller_class": envVariable.IngressClassControllerName, "controller_namespace": envVariable.AGICPodNamespace, "controller_pod": envVariable.AGICPodName, "controller_appgw_subscription": envVariable.SubscriptionID, diff --git a/pkg/tests/fixtures.go b/pkg/tests/fixtures.go index a5bbba97d..cbff64ea7 100644 --- a/pkg/tests/fixtures.go +++ b/pkg/tests/fixtures.go @@ -58,8 +58,21 @@ const ( PrivateIPID = "--front-end-ip-id-2--" ServiceHTTPPort = "--service-http-port--" ServiceHTTPSPort = "--service-https-port--" + IngressClassController = "azure/application-gateway" + IngressClassResourceName = "azure-application-gateway" ) +func GetIngressClass() *networking.IngressClass { + return &networking.IngressClass{ + Spec: networking.IngressClassSpec{ + Controller: IngressClassController, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: IngressClassResourceName, + }, + } +} + // GetIngress creates an Ingress test fixture. func GetIngress() (*networking.Ingress, error) { return GetIngressV1FromFile("ingress.yaml") @@ -80,7 +93,7 @@ func GetVerySimpleIngress() *networking.Ingress { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: IngressClassController, }, Name: "websocket-ingress", }, @@ -111,7 +124,7 @@ func GetIngressWithMissingServiceAndServiceWithInvalidPort() *networking.Ingress }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: IngressClassController, }, Namespace: Namespace, Name: "ingress-with-invalid-services", @@ -254,7 +267,7 @@ func NewIngressFixture() *networking.Ingress { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: IngressClassController, annotations.SslRedirectKey: "true", }, Namespace: Namespace, @@ -292,7 +305,7 @@ func NewIngressFixtureSingleSlashPath() *networking.Ingress { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: IngressClassController, annotations.SslRedirectKey: "true", }, Namespace: Namespace, @@ -605,7 +618,7 @@ func NewIngressTestFixture(namespace string, ingressName string) networking.Ingr Name: ingressName, Namespace: namespace, Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: IngressClassController, }, }, Spec: networking.IngressSpec{ @@ -642,7 +655,7 @@ func NewIngressTestFixtureBasic(namespace string, ingressName string, tls bool) Name: ingressName, Namespace: namespace, Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: IngressClassController, }, }, Spec: networking.IngressSpec{ diff --git a/pkg/tests/fixtures/ingress.go b/pkg/tests/fixtures/ingress.go index 89dd2d983..60bb04d8a 100644 --- a/pkg/tests/fixtures/ingress.go +++ b/pkg/tests/fixtures/ingress.go @@ -52,7 +52,7 @@ func GetIngress() *networking.Ingress { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - annotations.IngressClassKey: annotations.ApplicationGatewayIngressClass, + annotations.IngressClassKey: tests.IngressClassController, annotations.SslRedirectKey: "true", }, Namespace: tests.Namespace, diff --git a/proposals/multiple-gateways-single-cluster.md b/proposals/multiple-gateways-single-cluster.md index 86d53c1e7..eb5161b1b 100644 --- a/proposals/multiple-gateways-single-cluster.md +++ b/proposals/multiple-gateways-single-cluster.md @@ -1,6 +1,6 @@ # Multiple Gateways Single Cluster -##### Deploying multiple ingress controller instances to the same cluster +##### Deploying multiple ingress controller instances to the same cluster ### Document Purpose @@ -38,8 +38,10 @@ spec: paths: - path: /hello/ backend: - serviceName: go-server-service - servicePort: 80 + service: + name: go-server-service + port: + number: 80 ``` ### Option 2: Enforce a prefix unique to AGIC @@ -64,7 +66,8 @@ spec: paths: - path: /hello/ backend: - serviceName: go-server-service - servicePort: 80 + service: + name: go-server-service + port: + number: 80 ``` - diff --git a/scripts/e2e/cmd/runner/networking-v1-mfu_one_namespace_one_ingress_test.go b/scripts/e2e/cmd/runner/networking-v1-mfu_one_namespace_one_ingress_test.go index ffa59e15d..cb1281624 100644 --- a/scripts/e2e/cmd/runner/networking-v1-mfu_one_namespace_one_ingress_test.go +++ b/scripts/e2e/cmd/runner/networking-v1-mfu_one_namespace_one_ingress_test.go @@ -404,6 +404,70 @@ var _ = Describe("networking-v1-MFU", func() { Expect(err).To(BeNil()) }) + It("[ingress-class-resource] ingress class resource should work with ingress v1", func() { + namespaceName := "ingress-class-resource" + ns := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespaceName, + }, + } + klog.Info("Creating namespace: ", namespaceName) + _, err = clientset.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) + Expect(err).To(BeNil()) + + yamlPath := "testdata/networking-v1/one-namespace-one-ingress/ingress-class-resource/app.yaml" + klog.Info("Applying yaml: ", yamlPath) + err = applyYaml(clientset, namespaceName, yamlPath) + Expect(err).To(BeNil()) + time.Sleep(30 * time.Second) + + // get ip address for 1 ingress + klog.Info("Getting public IP from Ingress...") + publicIP, _ := getPublicIP(clientset, namespaceName) + Expect(publicIP).ToNot(Equal("")) + + urlHttp := fmt.Sprintf("http://%s/", publicIP) + // https get to return 200 ok + _, err = makeGetRequest(urlHttp, "app.http", 200, true) + Expect(err).To(BeNil()) + + // https get to return 404 ok + _, err = makeGetRequest(urlHttp, "other.http", 404, true) + Expect(err).To(BeNil()) + }) + + It("[rewrite-rule] rewrite-rule annotation attaches a rule set to routing rule", func() { + namespaceName := "e2e-rewrite-rule" + ns := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespaceName, + }, + } + klog.Info("Creating namespace: ", namespaceName) + _, err = clientset.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) + Expect(err).To(BeNil()) + + yamlPath := "testdata/networking-v1/one-namespace-one-ingress/rewrite-rule/app.yaml" + klog.Info("Applying empty secret yaml: ", yamlPath) + err = applyYaml(clientset, namespaceName, yamlPath) + Expect(err).To(BeNil()) + time.Sleep(30 * time.Second) + + // get ip address for 1 ingress + klog.Info("Getting public IP from Ingress...") + publicIP, _ := getPublicIP(clientset, namespaceName) + Expect(publicIP).ToNot(Equal("")) + + urlHttps := fmt.Sprintf("https://%s", publicIP) + // http get to return 200 ok + resp, err := makeGetRequest(urlHttps, "example.com", 200, true) + Expect(err).To(BeNil()) + + // check that rewrite rule is adding a response header "test-header: test-value" + testHeader := resp.Header.Get("test-header") + Expect(testHeader).To(Equal("test-value")) + }) + AfterEach(func() { // clear all namespaces cleanUp(clientset) diff --git a/scripts/e2e/cmd/runner/testdata/networking-v1/one-namespace-one-ingress/ingress-class-resource/app.yaml b/scripts/e2e/cmd/runner/testdata/networking-v1/one-namespace-one-ingress/ingress-class-resource/app.yaml new file mode 100644 index 000000000..a6559af7a --- /dev/null +++ b/scripts/e2e/cmd/runner/testdata/networking-v1/one-namespace-one-ingress/ingress-class-resource/app.yaml @@ -0,0 +1,84 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend-deployment +spec: + selector: + matchLabels: + app: ws-app + replicas: 3 + template: + metadata: + labels: + app: ws-app + spec: + containers: + - name: backend-app + imagePullPolicy: Always + image: docker.io/kennethreitz/httpbin + ports: + - containerPort: 80 + livenessProbe: + httpGet: + path: /status/201 + port: 80 + initialDelaySeconds: 3 + periodSeconds: 3 + readinessProbe: + failureThreshold: 10 + httpGet: + path: /status/202 + port: 80 + scheme: HTTP + +--- +apiVersion: v1 +kind: Service +metadata: + name: backend-service +spec: + selector: + app: ws-app + ports: + - protocol: TCP + port: 80 + targetPort: 80 + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-http +spec: + ingressClassName: azure-application-gateway + rules: + - host: app.http + http: + paths: + - path: / + backend: + service: + name: backend-service + port: + number: 80 + pathType: Exact + +--- + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: other-http +spec: + ingressClassName: other-gateway + rules: + - host: other.http + http: + paths: + - path: / + backend: + service: + name: backend-service + port: + number: 80 + pathType: Exact diff --git a/scripts/e2e/cmd/runner/testdata/networking-v1/one-namespace-one-ingress/rewrite-rule/app.yaml b/scripts/e2e/cmd/runner/testdata/networking-v1/one-namespace-one-ingress/rewrite-rule/app.yaml new file mode 100644 index 000000000..901ac2f0a --- /dev/null +++ b/scripts/e2e/cmd/runner/testdata/networking-v1/one-namespace-one-ingress/rewrite-rule/app.yaml @@ -0,0 +1,52 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend-deployment +spec: + selector: + matchLabels: + app: app + replicas: 1 + template: + metadata: + labels: + app: app + spec: + containers: + - name: backendapp + imagePullPolicy: Always + image: docker.io/kennethreitz/httpbin + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: backend-service +spec: + selector: + app: app + ports: + - protocol: TCP + port: 80 + targetPort: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress + annotations: + kubernetes.io/ingress.class: azure/application-gateway + appgw.ingress.kubernetes.io/rewrite-rule-set: "test-rewrite-rule" +spec: + rules: + - host: example.com + http: + paths: + - path: / + backend: + service: + name: backend-service + port: + number: 80 + pathType: Exact diff --git a/scripts/e2e/cmd/runner/testdata/networking-v1/one-namespace-one-ingress/ssl-e2e-redirect/app.yaml b/scripts/e2e/cmd/runner/testdata/networking-v1/one-namespace-one-ingress/ssl-e2e-redirect/app.yaml index 58cf75593..de6ea8e28 100644 --- a/scripts/e2e/cmd/runner/testdata/networking-v1/one-namespace-one-ingress/ssl-e2e-redirect/app.yaml +++ b/scripts/e2e/cmd/runner/testdata/networking-v1/one-namespace-one-ingress/ssl-e2e-redirect/app.yaml @@ -57,7 +57,8 @@ spec: selector: app: ssl-redirect ports: - - protocol: TCP + - name: https + protocol: TCP port: 443 targetPort: 443 --- @@ -82,14 +83,14 @@ spec: service: name: ssl-redirect-service port: - number: 443 + name: https pathType: Exact - path: /* backend: service: name: ssl-redirect-service port: - number: 443 + name: https pathType: Prefix --- apiVersion: v1