blob: cc126caf5d9f9b3950763c379d44cfb72224f521 [file] [log] [blame]
// 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()
}