Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enable a dynamic target lookup #28

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ require (
github.com/gorilla/mux v1.8.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mabels/ipaddress/go/ipaddress v0.0.0-20211229223036-692af3b12a67 // indirect
mabels marked this conversation as resolved.
Show resolved Hide resolved
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,8 @@ github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/u
github.com/lucas-clemente/quic-go v0.23.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mabels/ipaddress/go/ipaddress v0.0.0-20211229223036-692af3b12a67 h1:bVDE1M+1x2gmAsV2pdNa/9X4z5K2IitUSfC8L19FkhE=
github.com/mabels/ipaddress/go/ipaddress v0.0.0-20211229223036-692af3b12a67/go.mod h1:1mdRW3hfT/mDTtpLkQ8y0Ez5WHZvH9wx9YMw9zq9OLQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
Expand Down
4 changes: 2 additions & 2 deletions internal/controllers/ingressroute.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type IngressRouteReconciler struct {
func NewIngressRouteReconciler(
client client.Client, logger *zap.Logger, config configv1.Config,
) (IngressRouteReconciler, error) {
integrations, err := integrationsFromConfig(config, client)
integrations, err := integrationsFromConfig(config, client, logger)
if err != nil {
return IngressRouteReconciler{}, fmt.Errorf("failed to initialize integrations: %s", err)
}
Expand Down Expand Up @@ -68,7 +68,7 @@ func (r *IngressRouteReconciler) Reconcile(
Hosts: switchboard.NewHostCollection().
WithTLSHostsIfAvailable(ingressRoute.Spec.TLS).
WithRouteHostsIfRequired(ingressRoute.Spec.Routes).
Hosts(),
Hosts(ingressRoute.ObjectMeta.Annotations),
TLSSecretName: ext.AndThen(ingressRoute.Spec.TLS, func(tls traefik.TLS) string {
return tls.SecretName
}),
Expand Down
46 changes: 44 additions & 2 deletions internal/controllers/ingressroute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package controllers
import (
"context"
"fmt"
"strings"
"testing"

configv1 "github.com/borchero/switchboard/internal/config/v1"
Expand Down Expand Up @@ -157,13 +158,50 @@ func TestIngressMultipleRules(t *testing.T) {
})
}

func TestIngressTargetAnnotation(t *testing.T) {
targets := endpoint.Targets{"3.3.3.3", "4.4.4.4"}
runTest(t, testCase{
Targets: &targets,
Ingress: traefik.IngressRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "my-ingress",
Annotations: map[string]string{
"switchboard.borchero.com/target": strings.Join(targets, ","),
},
},
Spec: traefik.IngressRouteSpec{
Routes: []traefik.Route{{
Kind: "Rule",
Match: "Host(`example.com`)",
Services: []traefik.Service{{
LoadBalancerSpec: traefik.LoadBalancerSpec{
Name: "nginx",
},
}},
}},
TLS: &traefik.TLS{
SecretName: "www-tls-certificate",
Domains: []traefiktypes.Domain{{
Main: "example.net",
SANs: []string{
"*.example.net",
},
}},
},
},
},
DNSNames: []string{"example.net", "*.example.net"},
})
}

//-------------------------------------------------------------------------------------------------
// TESTING UTILITIES
//-------------------------------------------------------------------------------------------------

type testCase struct {
Ingress traefik.IngressRoute
DNSNames []string
Targets *endpoint.Targets
}

func runTest(t *testing.T, test testCase) {
Expand Down Expand Up @@ -220,8 +258,12 @@ func runTest(t *testing.T, test testCase) {
assert.Nil(t, err)
assert.Len(t, endpoint.Spec.Endpoints, len(test.DNSNames))
for _, ep := range endpoint.Spec.Endpoints {
assert.Len(t, ep.Targets, 1)
assert.Equal(t, service.Spec.ClusterIP, ep.Targets[0])
if test.Targets != nil {
assert.Equal(t, *test.Targets, ep.Targets)
} else {
assert.Len(t, ep.Targets, 1)
assert.Equal(t, service.Spec.ClusterIP, ep.Targets[0])
}
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions internal/controllers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
)

func integrationsFromConfig(
config configv1.Config, client client.Client,
config configv1.Config, client client.Client, logger *zap.Logger,
) ([]integrations.Integration, error) {
result := make([]integrations.Integration, 0)
externalDNS := config.Integrations.ExternalDNS
Expand All @@ -32,8 +32,8 @@ func integrationsFromConfig(
client, switchboard.NewServiceTarget(
externalDNS.TargetService.Name,
externalDNS.TargetService.Namespace,
),
))
logger,
)))
} else {
result = append(result, integrations.NewExternalDNS(
client, switchboard.NewStaticTarget(externalDNS.TargetIPs...),
Expand Down
14 changes: 8 additions & 6 deletions internal/controllers/utils_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package controllers

import (
"context"
"testing"

configv1 "github.com/borchero/switchboard/internal/config/v1"
"github.com/borchero/switchboard/internal/k8tests"
"github.com/borchero/zeus/pkg/zeus"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
"github.com/stretchr/testify/assert"
Expand All @@ -18,14 +20,14 @@ func TestIntegrationsFromConfig(t *testing.T) {

// Test all configurations of integrations
config := configv1.Config{}
integrations, err := integrationsFromConfig(config, client)
integrations, err := integrationsFromConfig(config, client, zeus.Logger(context.Background()))
require.Nil(t, err)
assert.Len(t, integrations, 0)

config.Integrations.ExternalDNS = &configv1.ExternalDNSIntegrationConfig{
TargetService: &configv1.ServiceRef{Name: "my-service", Namespace: "my-namespace"},
}
integrations, err = integrationsFromConfig(config, client)
integrations, err = integrationsFromConfig(config, client, zeus.Logger(context.Background()))
require.Nil(t, err)
assert.Len(t, integrations, 1)
assert.Equal(t, "external-dns", integrations[0].Name())
Expand All @@ -41,26 +43,26 @@ func TestIntegrationsFromConfig(t *testing.T) {
},
},
}
integrations, err = integrationsFromConfig(config, client)
integrations, err = integrationsFromConfig(config, client, zeus.Logger(context.Background()))
require.Nil(t, err)
assert.Len(t, integrations, 1)
assert.Equal(t, "cert-manager", integrations[0].Name())

config.Integrations.ExternalDNS = &configv1.ExternalDNSIntegrationConfig{
TargetIPs: []string{"127.0.0.1"},
}
integrations, err = integrationsFromConfig(config, client)
integrations, err = integrationsFromConfig(config, client, zeus.Logger(context.Background()))
require.Nil(t, err)
assert.Len(t, integrations, 2)

// Must fail if external DNS is not configured correctly
config.Integrations.ExternalDNS = &configv1.ExternalDNSIntegrationConfig{}
_, err = integrationsFromConfig(config, client)
_, err = integrationsFromConfig(config, client, zeus.Logger(context.Background()))
require.NotNil(t, err)

config.Integrations.ExternalDNS = &configv1.ExternalDNSIntegrationConfig{
TargetIPs: []string{},
}
_, err = integrationsFromConfig(config, client)
_, err = integrationsFromConfig(config, client, zeus.Logger(context.Background()))
require.NotNil(t, err)
}
4 changes: 2 additions & 2 deletions internal/integrations/certmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (c *certManager) UpdateResource(
) error {
// If the ingress does not specify a TLS secret name or specifies no hosts, no certificate
// needs to be created.
if info.TLSSecretName == nil || len(info.Hosts) == 0 {
if info.TLSSecretName == nil || len(info.Hosts.Names) == 0 {
certificate := certmanager.Certificate{ObjectMeta: c.objectMeta(owner)}
if err := k8s.DeleteIfFound(ctx, c.client, &certificate); err != nil {
return fmt.Errorf("failed to delete TLS certificate: %w", err)
Expand All @@ -62,7 +62,7 @@ func (c *certManager) UpdateResource(
// Spec
template := c.template.Spec.DeepCopy()
template.SecretName = *info.TLSSecretName
template.DNSNames = info.Hosts
template.DNSNames = info.Hosts.Names
if err := mergo.Merge(&resource.Spec, template, mergo.WithOverride); err != nil {
return fmt.Errorf("failed to reconcile specification: %s", err)
}
Expand Down
15 changes: 8 additions & 7 deletions internal/integrations/certmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/borchero/switchboard/internal/k8tests"
"github.com/borchero/switchboard/internal/switchboard"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -47,13 +48,13 @@ func TestCertManagerUpdateResource(t *testing.T) {
require.Nil(t, err)
assert.Len(t, getCertificates(ctx, t, client, namespace), 0)

info = IngressInfo{Hosts: []string{"example.com"}}
info = IngressInfo{Hosts: switchboard.HostsTarget{Names: []string{"example.com"}}}
err = integration.UpdateResource(ctx, &owner, info)
require.Nil(t, err)
assert.Len(t, getCertificates(ctx, t, client, namespace), 0)

// If both are set, we should see a certificate created
info = IngressInfo{Hosts: []string{"example.com"}, TLSSecretName: &tlsName}
info = IngressInfo{Hosts: switchboard.HostsTarget{Names: []string{"example.com"}}, TLSSecretName: &tlsName}
err = integration.UpdateResource(ctx, &owner, info)
require.Nil(t, err)

Expand All @@ -63,16 +64,16 @@ func TestCertManagerUpdateResource(t *testing.T) {
assert.Equal(t, tlsName, certificates[0].Spec.SecretName)
assert.Equal(t, "ClusterIssuer", certificates[0].Spec.IssuerRef.Kind)
assert.Equal(t, "my-issuer", certificates[0].Spec.IssuerRef.Name)
assert.ElementsMatch(t, info.Hosts, certificates[0].Spec.DNSNames)
assert.ElementsMatch(t, info.Hosts.Names, certificates[0].Spec.DNSNames)

// We should see an update if we change any info
info.Hosts = []string{"example.com", "www.example.com"}
info.Hosts = switchboard.HostsTarget{Names: []string{"example.com", "www.example.com"}}
err = integration.UpdateResource(ctx, &owner, info)
require.Nil(t, err)
certificates = getCertificates(ctx, t, client, namespace)
assert.Len(t, certificates, 1)
assert.Equal(t, tlsName, certificates[0].Spec.SecretName)
assert.ElementsMatch(t, info.Hosts, certificates[0].Spec.DNSNames)
assert.ElementsMatch(t, info.Hosts.Names, certificates[0].Spec.DNSNames)

updatedTLSName := "new-test-tls"
info.TLSSecretName = &updatedTLSName
Expand All @@ -81,10 +82,10 @@ func TestCertManagerUpdateResource(t *testing.T) {
certificates = getCertificates(ctx, t, client, namespace)
assert.Len(t, certificates, 1)
assert.Equal(t, updatedTLSName, certificates[0].Spec.SecretName)
assert.ElementsMatch(t, info.Hosts, certificates[0].Spec.DNSNames)
assert.ElementsMatch(t, info.Hosts.Names, certificates[0].Spec.DNSNames)

// When no hosts are set, the certificate should be removed again
info.Hosts = nil
info.Hosts = switchboard.HostsTarget{}
err = integration.UpdateResource(ctx, &owner, info)
require.Nil(t, err)
assert.Len(t, getCertificates(ctx, t, client, namespace), 0)
Expand Down
6 changes: 3 additions & 3 deletions internal/integrations/externaldns.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (e *externalDNS) UpdateResource(
) error {
// If the ingress specifies no hosts, there should be no endpoint. We try deleting it and
// ignore any error if it was not found.
if len(info.Hosts) == 0 {
if len(info.Hosts.Names) == 0 {
dnsEndpoint := endpoint.DNSEndpoint{ObjectMeta: e.objectMeta(owner)}
if err := k8s.DeleteIfFound(ctx, e.client, &dnsEndpoint); err != nil {
return fmt.Errorf("failed to delete DNS endpoint: %w", err)
Expand All @@ -61,7 +61,7 @@ func (e *externalDNS) UpdateResource(
}

// Get the IPs of the target service
targets, err := e.target.Targets(ctx, e.client)
targets, err := e.target.Targets(ctx, e.client, info.Hosts.Target)
if err != nil {
return fmt.Errorf("failed to query IP for DNS A record: %w", err)
}
Expand All @@ -75,7 +75,7 @@ func (e *externalDNS) UpdateResource(
}

// Spec
resource.Spec.Endpoints = e.endpoints(info.Hosts, targets)
resource.Spec.Endpoints = e.endpoints(info.Hosts.Names, targets)
return nil
}); err != nil {
return fmt.Errorf("failed to upsert DNS endpoint: %w", err)
Expand Down
Loading