Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/adc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ type Upstream struct {

HashOn string `json:"hash_on,omitempty" yaml:"hash_on,omitempty"`
Key string `json:"key,omitempty" yaml:"key,omitempty"`
Nodes UpstreamNodes `json:"nodes,omitempty" yaml:"nodes,omitempty"`
Nodes UpstreamNodes `json:"nodes" yaml:"nodes"`
PassHost *PassHost `json:"pass_host,omitempty" yaml:"pass_host,omitempty"`
Retries *int64 `json:"retries,omitempty" yaml:"retries,omitempty"`
RetryTimeout *float64 `json:"retry_timeout,omitempty" yaml:"retry_timeout,omitempty"`
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/indexer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const (
ExtensionRef = "extensionRef"
ParametersRef = "parametersRef"
ParentRefs = "parentRefs"
IngressClass = "ingressClass"
SecretIndexRef = "secretRefs"
IngressClass = "ingressClass"
IngressClassRef = "ingressClassRef"
)

Expand Down
15 changes: 12 additions & 3 deletions internal/controller/ingress_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/api7/api7-ingress-controller/internal/provider"
"github.com/api7/gopkg/pkg/log"
"github.com/go-logr/logr"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
networkingv1 "k8s.io/api/networking/v1"
Expand Down Expand Up @@ -267,6 +268,11 @@ func (r *IngressReconciler) processTLS(ctx context.Context, tctx *provider.Trans
return err
}

if secret.Data == nil {
log.Warnw("secret data is nil", zap.String("secret", secret.Namespace+"/"+secret.Name))
continue
}

// add the secret to the translate context
tctx.Secrets[types.NamespacedName{Namespace: ingress.Namespace, Name: tls.SecretName}] = &secret
}
Expand All @@ -283,19 +289,16 @@ func (r *IngressReconciler) processBackends(ctx context.Context, tctx *provider.
if rule.HTTP == nil {
continue
}

for _, path := range rule.HTTP.Paths {
if path.Backend.Service == nil {
continue
}

service := path.Backend.Service
if err := r.processBackendService(ctx, tctx, ingress.Namespace, service); err != nil {
terr = err
}
}
}

return terr
}

Expand Down Expand Up @@ -356,6 +359,11 @@ func (r *IngressReconciler) processBackendService(ctx context.Context, tctx *pro
Name: backendService.Name,
}] = endpointSliceList.Items

tctx.Services[client.ObjectKey{
Namespace: namespace,
Name: backendService.Name,
}] = &service

return nil
}

Expand Down Expand Up @@ -383,6 +391,7 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, ingress *networkin
if err != nil {
return fmt.Errorf("invalid ingress-publish-service format: %s, expected format: namespace/name", publishService)
}
// if the namespace is not specified, use the ingress namespace
if namespace == "" {
namespace = ingress.Namespace
}
Expand Down
3 changes: 3 additions & 0 deletions internal/provider/adc/adc.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ func (d *adcClient) Delete(ctx context.Context, obj client.Object) error {
labels = label.GenLabel(obj)
case *gatewayv1.Gateway:
// delete all resources
case *networkingv1.Ingress:
resourceTypes = append(resourceTypes, "service", "ssl")
labels = label.GenLabel(obj)
}

return d.sync(Task{
Expand Down
13 changes: 7 additions & 6 deletions internal/provider/adc/translator/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,17 @@ func (t *Translator) translateSecret(tctx *provider.TranslateContext, listener g
// Dashboard doesn't allow wildcard hostname
if listener.Hostname != nil && *listener.Hostname != "" {
sslObj.Snis = append(sslObj.Snis, string(*listener.Hostname))
} else {
hosts, err := extractHost(cert)
if err != nil {
return nil, err
}
sslObj.Snis = append(sslObj.Snis, hosts...)
}
hosts, err := extractHost(cert)
if err != nil {
return nil, err
}
if len(hosts) == 0 {
if len(sslObj.Snis) == 0 {
log.Warnw("no valid hostname found in certificate", zap.String("secret", secret.Namespace+"/"+secret.Name))
continue
}
sslObj.Snis = append(sslObj.Snis, hosts...)
// Note: Dashboard doesn't allow duplicate certificate across ssl objects
sslObj.ID = id.GenID(string(cert))
sslObj.Labels = label.GenLabel(obj)
Expand Down
212 changes: 211 additions & 1 deletion internal/provider/adc/translator/ingress.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,220 @@
package translator

import (
"fmt"
"strings"

adctypes "github.com/api7/api7-ingress-controller/api/adc"
"github.com/api7/api7-ingress-controller/internal/controller/label"
"github.com/api7/api7-ingress-controller/internal/id"
"github.com/api7/api7-ingress-controller/internal/provider"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/types"
)

func (t *Translator) translateIngressTLS(ingressTLS *networkingv1.IngressTLS, secret *corev1.Secret, labels map[string]string) (*adctypes.SSL, error) {
// extract the key pair from the secret
cert, key, err := extractKeyPair(secret, true)
if err != nil {
return nil, err
}

hosts := ingressTLS.Hosts
if len(hosts) == 0 {
certHosts, err := extractHost(cert)
if err != nil {
return nil, err
}
hosts = append(hosts, certHosts...)
}
if len(hosts) == 0 {
return nil, fmt.Errorf("no hosts found in ingress TLS")
}

ssl := &adctypes.SSL{
Metadata: adctypes.Metadata{
Labels: labels,
},
Certificates: []adctypes.Certificate{
{
Certificate: string(cert),
Key: string(key),
},
},
Snis: hosts,
}
ssl.ID = id.GenID(string(cert))

return ssl, nil
}

func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *networkingv1.Ingress) (*TranslateResult, error) {
return nil, nil
result := &TranslateResult{}

labels := label.GenLabel(obj)

// handle TLS configuration, convert to SSL objects
for _, tls := range obj.Spec.TLS {
if tls.SecretName == "" {
continue
}
secret := tctx.Secrets[types.NamespacedName{
Namespace: obj.Namespace,
Name: tls.SecretName,
}]
if secret == nil {
continue
}
ssl, err := t.translateIngressTLS(&tls, secret, labels)
if err != nil {
return nil, err
}

result.SSL = append(result.SSL, ssl)
}

// process Ingress rules, convert to Service and Route objects
for i, rule := range obj.Spec.Rules {
// extract hostnames
var hosts []string
if rule.Host != "" {
hosts = append(hosts, rule.Host)
}
// if there is no HTTP path, skip
if rule.HTTP == nil {
continue
}

// create a service for each path
for j, path := range rule.HTTP.Paths {
if path.Backend.Service == nil {
continue
}

service := adctypes.NewDefaultService()
service.Labels = labels
service.Name = adctypes.ComposeServiceNameWithRule(obj.Namespace, obj.Name, fmt.Sprintf("%d-%d", i, j))
service.ID = id.GenID(service.Name)
service.Hosts = hosts

// create an upstream
upstream := adctypes.NewDefaultUpstream()

// get the EndpointSlice of the backend service
backendService := path.Backend.Service
endpointSlices := tctx.EndpointSlices[types.NamespacedName{
Namespace: obj.Namespace,
Name: backendService.Name,
}]

// get the service port configuration
var servicePort int32 = 0
var servicePortName string
if backendService.Port.Number != 0 {
servicePort = backendService.Port.Number
} else if backendService.Port.Name != "" {
servicePortName = backendService.Port.Name
}

getService := tctx.Services[types.NamespacedName{
Namespace: obj.Namespace,
Name: backendService.Name,
}]
if getService == nil {
continue
}

var getServicePort *corev1.ServicePort
for _, port := range getService.Spec.Ports {
port := port
if servicePort > 0 && port.Port == servicePort {
getServicePort = &port
break
}
if servicePortName != "" && port.Name == servicePortName {
getServicePort = &port
break
}
}

// convert the EndpointSlice to upstream nodes
if len(endpointSlices) > 0 {
upstream.Nodes = t.translateEndpointSliceForIngress(1, endpointSlices, getServicePort)
}

// if there is no upstream node, create a placeholder node
if len(upstream.Nodes) == 0 {
upstream.Nodes = adctypes.UpstreamNodes{}
}

service.Upstream = upstream

// create a route
route := adctypes.NewDefaultRoute()
route.Name = adctypes.ComposeRouteName(obj.Namespace, obj.Name, fmt.Sprintf("%d-%d", i, j))
route.ID = id.GenID(route.Name)
route.Labels = labels

uris := []string{path.Path}
if path.PathType != nil {
if *path.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 := path.Path
if strings.HasSuffix(prefix, "/") {
prefix += "*"
} else {
prefix += "/*"
}
uris = append(uris, prefix)
} else if *path.PathType == networkingv1.PathTypeImplementationSpecific {
uris = []string{"/*"}
}
}
route.Uris = uris

service.Routes = []*adctypes.Route{route}
result.Services = append(result.Services, service)
}
}

return result, nil
}

// translateEndpointSliceForIngress create upstream nodes from EndpointSlice
func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices []discoveryv1.EndpointSlice, servciePort *corev1.ServicePort) adctypes.UpstreamNodes {
var nodes adctypes.UpstreamNodes
if len(endpointSlices) == 0 {
return nodes
}

for _, endpointSlice := range endpointSlices {
for _, port := range endpointSlice.Ports {
// if the port number is specified, only use the matching port
if servciePort != nil && *port.Name != servciePort.Name {
continue
}
for _, endpoint := range endpointSlice.Endpoints {
for _, addr := range endpoint.Addresses {
node := adctypes.UpstreamNode{
Host: addr,
Port: int(*port.Port),
Weight: weight,
}
nodes = append(nodes, node)
}
}
}
}

return nodes
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type TranslateContext struct {
EndpointSlices map[types.NamespacedName][]discoveryv1.EndpointSlice
Secrets map[types.NamespacedName]*corev1.Secret
PluginConfigs map[types.NamespacedName]*v1alpha1.PluginConfig
Services map[types.NamespacedName]*corev1.Service
}

func NewDefaultTranslateContext() *TranslateContext {
Expand Down
Loading