| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package translation |
| |
| import ( |
| "bytes" |
| "fmt" |
| "strings" |
| |
| "go.uber.org/zap" |
| |
| extensionsv1beta1 "k8s.io/api/extensions/v1beta1" |
| networkingv1 "k8s.io/api/networking/v1" |
| networkingv1beta1 "k8s.io/api/networking/v1beta1" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/util/intstr" |
| |
| "github.com/apache/apisix-ingress-controller/pkg/id" |
| apisixv12 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v1" |
| "github.com/apache/apisix-ingress-controller/pkg/log" |
| apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1" |
| ) |
| |
| func (t *translator) translateIngressV1(ing *networkingv1.Ingress) (*TranslateContext, error) { |
| ctx := &TranslateContext{ |
| upstreamMap: make(map[string]struct{}), |
| } |
| plugins := t.translateAnnotations(ing.Annotations) |
| |
| // add https |
| for _, tls := range ing.Spec.TLS { |
| apisixTls := apisixv12.ApisixTls{ |
| TypeMeta: metav1.TypeMeta{ |
| Kind: "ApisixTls", |
| APIVersion: "apisix.apache.org/v1", |
| }, |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: fmt.Sprintf("%v-%v", ing.Name, "tls"), |
| Namespace: ing.Namespace, |
| }, |
| Spec: &apisixv12.ApisixTlsSpec{}, |
| } |
| for _, host := range tls.Hosts { |
| apisixTls.Spec.Hosts = append(apisixTls.Spec.Hosts, apisixv12.HostType(host)) |
| } |
| apisixTls.Spec.Secret = apisixv12.ApisixSecret{ |
| Name: tls.SecretName, |
| Namespace: ing.Namespace, |
| } |
| ssl, err := t.TranslateSSL(&apisixTls) |
| if err != nil { |
| log.Errorw("failed to translate ingress tls to apisix tls", |
| zap.Error(err), |
| zap.Any("ingress", ing), |
| ) |
| return nil, err |
| } |
| ctx.addSSL(ssl) |
| } |
| for _, rule := range ing.Spec.Rules { |
| for _, pathRule := range rule.HTTP.Paths { |
| var ( |
| ups *apisixv1.Upstream |
| err error |
| ) |
| if pathRule.Backend.Service != nil { |
| ups, err = t.translateUpstreamFromIngressV1(ing.Namespace, pathRule.Backend.Service) |
| if err != nil { |
| log.Errorw("failed to translate ingress backend to upstream", |
| zap.Error(err), |
| zap.Any("ingress", ing), |
| ) |
| return nil, err |
| } |
| ctx.addUpstream(ups) |
| } |
| uris := []string{pathRule.Path} |
| if pathRule.PathType != nil && *pathRule.PathType == networkingv1.PathTypePrefix { |
| // As per the specification of Ingress path matching rule: |
| // if the last element of the path is a substring of the |
| // last element in request path, it is not a match, e.g. /foo/bar |
| // matches /foo/bar/baz, but does not match /foo/barbaz. |
| // While in APISIX, /foo/bar matches both /foo/bar/baz and |
| // /foo/barbaz. |
| // In order to be conformant with Ingress specification, here |
| // we create two paths here, the first is the path itself |
| // (exact match), the other is path + "/*" (prefix match). |
| prefix := pathRule.Path |
| if strings.HasSuffix(prefix, "/") { |
| prefix += "*" |
| } else { |
| prefix += "/*" |
| } |
| uris = append(uris, prefix) |
| } |
| route := apisixv1.NewDefaultRoute() |
| route.Name = composeIngressRouteName(rule.Host, pathRule.Path) |
| route.ID = id.GenID(route.Name) |
| route.Host = rule.Host |
| route.Uris = uris |
| if len(plugins) > 0 { |
| route.Plugins = *(plugins.DeepCopy()) |
| } |
| if ups != nil { |
| route.UpstreamId = ups.ID |
| } |
| ctx.addRoute(route) |
| } |
| } |
| return ctx, nil |
| } |
| |
| func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress) (*TranslateContext, error) { |
| ctx := &TranslateContext{ |
| upstreamMap: make(map[string]struct{}), |
| } |
| plugins := t.translateAnnotations(ing.Annotations) |
| // add https |
| for _, tls := range ing.Spec.TLS { |
| apisixTls := apisixv12.ApisixTls{ |
| TypeMeta: metav1.TypeMeta{ |
| Kind: "ApisixTls", |
| APIVersion: "apisix.apache.org/v1", |
| }, |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: fmt.Sprintf("%v-%v", ing.Name, "tls"), |
| Namespace: ing.Namespace, |
| }, |
| Spec: &apisixv12.ApisixTlsSpec{}, |
| } |
| for _, host := range tls.Hosts { |
| apisixTls.Spec.Hosts = append(apisixTls.Spec.Hosts, apisixv12.HostType(host)) |
| } |
| apisixTls.Spec.Secret = apisixv12.ApisixSecret{ |
| Name: tls.SecretName, |
| Namespace: ing.Namespace, |
| } |
| ssl, err := t.TranslateSSL(&apisixTls) |
| if err != nil { |
| log.Errorw("failed to translate ingress tls to apisix tls", |
| zap.Error(err), |
| zap.Any("ingress", ing), |
| ) |
| return nil, err |
| } |
| ctx.addSSL(ssl) |
| } |
| for _, rule := range ing.Spec.Rules { |
| for _, pathRule := range rule.HTTP.Paths { |
| var ( |
| ups *apisixv1.Upstream |
| err error |
| ) |
| if pathRule.Backend.ServiceName != "" { |
| ups, err = t.translateUpstreamFromIngressV1beta1(ing.Namespace, pathRule.Backend.ServiceName, pathRule.Backend.ServicePort) |
| if err != nil { |
| log.Errorw("failed to translate ingress backend to upstream", |
| zap.Error(err), |
| zap.Any("ingress", ing), |
| ) |
| return nil, err |
| } |
| ctx.addUpstream(ups) |
| } |
| uris := []string{pathRule.Path} |
| if pathRule.PathType != nil && *pathRule.PathType == networkingv1beta1.PathTypePrefix { |
| // As per the specification of Ingress path matching rule: |
| // if the last element of the path is a substring of the |
| // last element in request path, it is not a match, e.g. /foo/bar |
| // matches /foo/bar/baz, but does not match /foo/barbaz. |
| // While in APISIX, /foo/bar matches both /foo/bar/baz and |
| // /foo/barbaz. |
| // In order to be conformant with Ingress specification, here |
| // we create two paths here, the first is the path itself |
| // (exact match), the other is path + "/*" (prefix match). |
| prefix := pathRule.Path |
| if strings.HasSuffix(prefix, "/") { |
| prefix += "*" |
| } else { |
| prefix += "/*" |
| } |
| uris = append(uris, prefix) |
| } |
| route := apisixv1.NewDefaultRoute() |
| route.Name = composeIngressRouteName(rule.Host, pathRule.Path) |
| route.ID = id.GenID(route.Name) |
| route.Host = rule.Host |
| route.Uris = uris |
| if len(plugins) > 0 { |
| route.Plugins = *(plugins.DeepCopy()) |
| } |
| if ups != nil { |
| route.UpstreamId = ups.ID |
| } |
| ctx.addRoute(route) |
| } |
| } |
| return ctx, nil |
| } |
| |
| func (t *translator) translateUpstreamFromIngressV1(namespace string, backend *networkingv1.IngressServiceBackend) (*apisixv1.Upstream, error) { |
| var svcPort int32 |
| if backend.Port.Name != "" { |
| svc, err := t.ServiceLister.Services(namespace).Get(backend.Name) |
| if err != nil { |
| return nil, err |
| } |
| for _, port := range svc.Spec.Ports { |
| if port.Name == backend.Port.Name { |
| svcPort = port.Port |
| break |
| } |
| } |
| if svcPort == 0 { |
| return nil, &translateError{ |
| field: "service", |
| reason: "port not found", |
| } |
| } |
| } else { |
| svcPort = backend.Port.Number |
| } |
| ups, err := t.TranslateUpstream(namespace, backend.Name, "", svcPort) |
| if err != nil { |
| return nil, err |
| } |
| ups.Name = apisixv1.ComposeUpstreamName(namespace, backend.Name, "", svcPort) |
| ups.ID = id.GenID(ups.Name) |
| return ups, nil |
| } |
| |
| func (t *translator) translateIngressExtensionsV1beta1(ing *extensionsv1beta1.Ingress) (*TranslateContext, error) { |
| ctx := &TranslateContext{ |
| upstreamMap: make(map[string]struct{}), |
| } |
| plugins := t.translateAnnotations(ing.Annotations) |
| |
| for _, rule := range ing.Spec.Rules { |
| for _, pathRule := range rule.HTTP.Paths { |
| var ( |
| ups *apisixv1.Upstream |
| err error |
| ) |
| if pathRule.Backend.ServiceName != "" { |
| // Structure here is same to ingress.extensions/v1beta1, so just use this method. |
| ups, err = t.translateUpstreamFromIngressV1beta1(ing.Namespace, pathRule.Backend.ServiceName, pathRule.Backend.ServicePort) |
| if err != nil { |
| log.Errorw("failed to translate ingress backend to upstream", |
| zap.Error(err), |
| zap.Any("ingress", ing), |
| ) |
| return nil, err |
| } |
| ctx.addUpstream(ups) |
| } |
| uris := []string{pathRule.Path} |
| if pathRule.PathType != nil && *pathRule.PathType == extensionsv1beta1.PathTypePrefix { |
| // As per the specification of Ingress path matching rule: |
| // if the last element of the path is a substring of the |
| // last element in request path, it is not a match, e.g. /foo/bar |
| // matches /foo/bar/baz, but does not match /foo/barbaz. |
| // While in APISIX, /foo/bar matches both /foo/bar/baz and |
| // /foo/barbaz. |
| // In order to be conformant with Ingress specification, here |
| // we create two paths here, the first is the path itself |
| // (exact match), the other is path + "/*" (prefix match). |
| prefix := pathRule.Path |
| if strings.HasSuffix(prefix, "/") { |
| prefix += "*" |
| } else { |
| prefix += "/*" |
| } |
| uris = append(uris, prefix) |
| } |
| route := apisixv1.NewDefaultRoute() |
| route.Name = composeIngressRouteName(rule.Host, pathRule.Path) |
| route.ID = id.GenID(route.Name) |
| route.Host = rule.Host |
| route.Uris = uris |
| if len(plugins) > 0 { |
| route.Plugins = *(plugins.DeepCopy()) |
| } |
| if ups != nil { |
| route.UpstreamId = ups.ID |
| } |
| ctx.addRoute(route) |
| } |
| } |
| return ctx, nil |
| } |
| |
| func (t *translator) translateUpstreamFromIngressV1beta1(namespace string, svcName string, svcPort intstr.IntOrString) (*apisixv1.Upstream, error) { |
| var portNumber int32 |
| if svcPort.Type == intstr.String { |
| svc, err := t.ServiceLister.Services(namespace).Get(svcName) |
| if err != nil { |
| return nil, err |
| } |
| for _, port := range svc.Spec.Ports { |
| if port.Name == svcPort.StrVal { |
| portNumber = port.Port |
| break |
| } |
| } |
| if portNumber == 0 { |
| return nil, &translateError{ |
| field: "service", |
| reason: "port not found", |
| } |
| } |
| } else { |
| portNumber = svcPort.IntVal |
| } |
| ups, err := t.TranslateUpstream(namespace, svcName, "", portNumber) |
| if err != nil { |
| return nil, err |
| } |
| ups.Name = apisixv1.ComposeUpstreamName(namespace, svcName, "", portNumber) |
| ups.ID = id.GenID(ups.Name) |
| return ups, nil |
| } |
| |
| func composeIngressRouteName(host, path string) string { |
| p := make([]byte, 0, len(host)+len(path)+len("ingress")+2) |
| buf := bytes.NewBuffer(p) |
| |
| buf.WriteString("ingress") |
| buf.WriteByte('_') |
| buf.WriteString(host) |
| buf.WriteByte('_') |
| buf.WriteString(path) |
| |
| return buf.String() |
| |
| } |