github.com/verrazzano/verrazzano@v1.7.0/pkg/ingresstrait/ingresstrait.go (about) 1 // Copyright (c) 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package ingresstrait 5 6 import ( 7 "context" 8 "fmt" 9 vzapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1" 10 corev1 "k8s.io/api/core/v1" 11 k8net "k8s.io/api/networking/v1" 12 "k8s.io/apimachinery/pkg/types" 13 "sigs.k8s.io/controller-runtime/pkg/client" 14 "strings" 15 ) 16 17 func CoallateAllHostsForTrait(cli client.Client, trait *vzapi.IngressTrait, appName string, appNamespace string) ([]string, error) { 18 allHosts := []string{} 19 var err error 20 for _, rule := range trait.Spec.Rules { 21 if allHosts, err = CreateHostsFromIngressTraitRule(cli, rule, trait, appName, appNamespace, allHosts...); err != nil { 22 23 print(err) 24 return nil, err 25 } 26 } 27 return allHosts, nil 28 } 29 30 func CreateHostsFromIngressTraitRule(cli client.Client, rule vzapi.IngressRule, trait *vzapi.IngressTrait, appName string, appNamespace string, toList ...string) ([]string, error) { 31 32 validHosts := toList 33 useDefaultHost := true 34 for _, h := range rule.Hosts { 35 h = strings.TrimSpace(h) 36 if _, hostAlreadyPresent := findHost(validHosts, h); hostAlreadyPresent { 37 // Avoid duplicates 38 useDefaultHost = false 39 continue 40 } 41 // Ignore empty or wildcard hostname 42 if len(h) == 0 || strings.Contains(h, "*") { 43 continue 44 } 45 h = strings.ToLower(strings.TrimSpace(h)) 46 validHosts = append(validHosts, h) 47 useDefaultHost = false 48 } 49 // Add done if a host was added to the host list 50 if !useDefaultHost { 51 return validHosts, nil 52 } 53 54 // Generate a default hostname 55 56 hostName, err := buildAppFullyQualifiedHostName(cli, trait, appName, appNamespace) 57 58 if err != nil { 59 return nil, err 60 } 61 // Only add the generated hostname if it doesn't exist in hte list 62 if _, hostAlreadyPresent := findHost(validHosts, hostName); !hostAlreadyPresent { 63 validHosts = append(validHosts, hostName) 64 } 65 return validHosts, nil 66 } 67 68 // buildAppFullyQualifiedHostName generates a DNS host name for the application using the following structure: 69 // <app>.<namespace>.<dns-subdomain> where 70 // 71 // app is the OAM application name 72 // namespace is the namespace of the OAM application 73 // dns-subdomain is The DNS subdomain name 74 75 func buildAppFullyQualifiedHostName(cli client.Client, trait *vzapi.IngressTrait, appName string, appNamespace string) (string, error) { 76 77 domainName, err := buildNamespacedDomainName(cli, trait, appNamespace) 78 79 if err != nil { 80 return "", err 81 } 82 return fmt.Sprintf("%s.%s", appName, domainName), nil 83 84 } 85 86 // buildNamespacedDomainName generates a domain name for the application using the following structure: 87 // <namespace>.<dns-subdomain> where 88 // 89 // namespace is the namespace of the OAM application 90 // dns-subdomain is The DNS subdomain name 91 92 func buildNamespacedDomainName(cli client.Client, trait *vzapi.IngressTrait, appNamespace string) (string, error) { 93 94 const externalDNSKey = "external-dns.alpha.kubernetes.io/target" 95 const wildcardDomainKey = "verrazzano.io/dns.wildcard.domain" 96 97 // Extract the domain name from the Verrazzano ingress 98 ingress := k8net.Ingress{} 99 err := cli.Get(context.TODO(), types.NamespacedName{Name: "verrazzano-ingress", Namespace: "verrazzano-system"}, &ingress) 100 if err != nil { 101 return "", err 102 } 103 externalDNSAnno, ok := ingress.Annotations[externalDNSKey] 104 if !ok || len(externalDNSAnno) == 0 { 105 return "", fmt.Errorf("Annotation %s missing from Verrazzano ingress, unable to generate DNS name", externalDNSKey) 106 } 107 108 domainArray := strings.SplitN(externalDNSAnno, ".", 2) 109 domain := domainArray[0] 110 if len(domainArray) >= 2 { 111 domain = domainArray[1] 112 } 113 114 // Get the DNS wildcard domain from the annotation if it exist. This annotation is only available 115 // when the install is using DNS type wildcard (nip.io, sslip.io, etc.) 116 suffix := "" 117 wildcardDomainAnno, ok := ingress.Annotations[wildcardDomainKey] 118 if ok { 119 suffix = wildcardDomainAnno 120 } 121 122 // Build the domain name using Istio info 123 if len(suffix) != 0 { 124 domain, err = buildDomainNameForWildcard(cli, suffix) 125 if err != nil { 126 return "", err 127 } 128 } 129 130 return fmt.Sprintf("%s.%s", appNamespace, domain), nil 131 132 } 133 134 // findHost searches for a host in the provided list. If found it will 135 // return it's key, otherwise it will return -1 and a bool of false. 136 func findHost(hosts []string, newHost string) (int, bool) { 137 for i, host := range hosts { 138 if strings.EqualFold(host, newHost) { 139 return i, true 140 } 141 } 142 return -1, false 143 } 144 145 // buildDomainNameForWildcard generates a domain name in the format of "<IP>.<wildcard-domain>" 146 // Get the IP from Istio resources 147 func buildDomainNameForWildcard(cli client.Reader, suffix string) (string, error) { 148 istioIngressGateway := "istio-ingressgateway" 149 istioSystemNamespace := "istio-system" 150 istio := corev1.Service{} 151 err := cli.Get(context.TODO(), types.NamespacedName{Name: istioIngressGateway, Namespace: istioSystemNamespace}, &istio) 152 if err != nil { 153 return "", err 154 } 155 var IP string 156 if istio.Spec.Type == corev1.ServiceTypeLoadBalancer || istio.Spec.Type == corev1.ServiceTypeNodePort { 157 if len(istio.Spec.ExternalIPs) > 0 { 158 IP = istio.Spec.ExternalIPs[0] 159 } else if len(istio.Status.LoadBalancer.Ingress) > 0 { 160 IP = istio.Status.LoadBalancer.Ingress[0].IP 161 } else { 162 return "", fmt.Errorf("%s is missing loadbalancer IP", istioIngressGateway) 163 } 164 } else { 165 return "", fmt.Errorf("unsupported service type %s for istio_ingress", string(istio.Spec.Type)) 166 } 167 domain := IP + "." + suffix 168 return domain, nil 169 }