diff --git a/pkg/appstate/ingress.go b/pkg/appstate/ingress.go index 891eab3d86..a4b2428d7b 100644 --- a/pkg/appstate/ingress.go +++ b/pkg/appstate/ingress.go @@ -7,6 +7,7 @@ import ( "github.com/replicatedhq/kots/pkg/appstate/types" "github.com/replicatedhq/kots/pkg/k8sutil" "github.com/replicatedhq/kots/pkg/logger" + v1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -118,6 +119,7 @@ func makeIngressResourceState(r *networkingv1.Ingress, state types.State) types. } func CalculateIngressState(clientset kubernetes.Interface, r *networkingv1.Ingress) types.State { + ctx := context.TODO() ns := r.Namespace backend := r.Spec.DefaultBackend @@ -139,30 +141,43 @@ func CalculateIngressState(clientset kubernetes.Interface, r *networkingv1.Ingre ns = metav1.NamespaceSystem } - var states []types.State + services := []*v1.Service{} // includes nils which are mapped to unavailable if backend != nil { - states = append(states, ingressGetStateFromBackend(clientset, ns, *backend)) + service, _ := clientset.CoreV1().Services(ns).Get(ctx, backend.Service.Name, metav1.GetOptions{}) + services = append(services, service) } for _, rules := range r.Spec.Rules { for _, path := range rules.HTTP.Paths { - states = append(states, ingressGetStateFromBackend(clientset, r.Namespace, path.Backend)) + service, _ := clientset.CoreV1().Services(r.Namespace).Get(ctx, path.Backend.Service.Name, metav1.GetOptions{}) + services = append(services, service) } } - // https://github.com/kubernetes/kubernetes/blob/badcd4af3f592376ce891b7c1b7a43ed6a18a348/pkg/printers/internalversion/printers.go#L1067 - states = append(states, ingressGetStateFromExternalIP(r)) - return types.MinState(states...) -} -func ingressGetStateFromBackend(clientset kubernetes.Interface, namespace string, backend networkingv1.IngressBackend) (minState types.State) { - if backend.Service == nil { - return types.StateUnavailable + hasLoadBalancer := false + for _, service := range services { + if service != nil && service.Spec.Type == v1.ServiceTypeLoadBalancer { + hasLoadBalancer = true + break + } } - service, _ := clientset.CoreV1().Services(namespace).Get(context.TODO(), backend.Service.Name, metav1.GetOptions{}) - if service == nil { - return types.StateUnavailable + + var states []types.State + for _, service := range services { + if service == nil { + states = append(states, types.StateUnavailable) + } else { + states = append(states, serviceGetStateFromEndpoints(clientset, service)) + } + } + + // An ingress will have an IP associated with it if it's type is LoadBalancer. + if hasLoadBalancer { + // https://github.com/kubernetes/kubernetes/blob/badcd4af3f592376ce891b7c1b7a43ed6a18a348/pkg/printers/internalversion/printers.go#L1067 + states = append(states, ingressGetStateFromExternalIP(r)) } - return serviceGetStateFromEndpoints(clientset, service) + + return types.MinState(states...) } func ingressGetStateFromExternalIP(ing *networkingv1.Ingress) types.State { diff --git a/pkg/appstate/ingress_test.go b/pkg/appstate/ingress_test.go index 17073acc17..b826fc4858 100644 --- a/pkg/appstate/ingress_test.go +++ b/pkg/appstate/ingress_test.go @@ -16,7 +16,7 @@ import ( func mockClientsetK8sVersion(expectedMajor string, expectedMinor string) kubernetes.Interface { clientset := fake.NewSimpleClientset( - // add a service + // Defaul backend service and endpoint &v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "default-http-backend", @@ -52,6 +52,82 @@ func mockClientsetK8sVersion(expectedMajor string, expectedMinor string) kuberne }, }, }, + + // LoadBalancer service and endpoint + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "app-lb", + Namespace: "", + }, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeLoadBalancer, + Ports: []v1.ServicePort{ + { + Name: "http", + Port: 8080, + }, + }, + }, + }, + &v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "app-lb", + Namespace: "", + }, + Subsets: []v1.EndpointSubset{ + { + Ports: []v1.EndpointPort{ + { + Name: "http", + Port: 8080, + }, + }, + Addresses: []v1.EndpointAddress{ + { + IP: "172.0.0.2", + }, + }, + }, + }, + }, + + // NodePort service and endpoint + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "app-nodeport", + Namespace: "", + }, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeNodePort, + Ports: []v1.ServicePort{ + { + Name: "http", + Port: 8080, + }, + }, + }, + }, + &v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "app-nodeport", + Namespace: "", + }, + Subsets: []v1.EndpointSubset{ + { + Ports: []v1.EndpointPort{ + { + Name: "http", + Port: 8080, + }, + }, + Addresses: []v1.EndpointAddress{ + { + IP: "172.0.0.2", + }, + }, + }, + }, + }, ) clientset.Discovery().(*discoveryfake.FakeDiscovery).FakedServerVersion = &version.Info{ Major: expectedMajor, @@ -88,22 +164,30 @@ func TestCalculateIngressState(t *testing.T) { }, }, want: types.StateReady, - }, - { - name: "expect unavailable state when ingress with k8s version > 1.22 and no default backend", - args: args{ - clientset: mockClientsetK8sVersion("1", "23"), - r: &networkingv1.Ingress{ - Spec: networkingv1.IngressSpec{}, - }, - }, - want: types.StateUnavailable, }, { - name: "expect ready state when ingress with k8s version > 1.22 and no default backend and with load balancer status", + name: "expect ready state when there is a load balancer and an IP address", args: args{ clientset: mockClientsetK8sVersion("1", "23"), r: &networkingv1.Ingress{ - Spec: networkingv1.IngressSpec{}, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "app-lb", + }, + }, + }, + }, + }, + }, + }, + }, + }, Status: networkingv1.IngressStatus{ LoadBalancer: networkingv1.IngressLoadBalancerStatus{ Ingress: []networkingv1.IngressLoadBalancerIngress{ @@ -116,6 +200,61 @@ func TestCalculateIngressState(t *testing.T) { }, }, want: types.StateReady, + }, { + name: "expect ready state when there is no LoadBalancer and no address is assigned", + args: args{ + clientset: mockClientsetK8sVersion("1", "23"), + r: &networkingv1.Ingress{ + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "app-nodeport", + }, + }, + }, + }, + }, + }, + }, + }, + }, + Status: networkingv1.IngressStatus{}, + }, + }, + want: types.StateReady, + }, { + name: "expect unavailable state when there is a LoadBalancer but no address is assigned", + args: args{ + clientset: mockClientsetK8sVersion("1", "23"), + r: &networkingv1.Ingress{ + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "app-lb", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: types.StateUnavailable, }, } for _, tt := range tests {