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

[PR-156 follow-up] Generate endpoints hostnames if go-template is specified #160

Merged
merged 17 commits into from
Apr 18, 2017
Merged
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Features:

- Generate DNS Name from template for services/ingress if annotation is missing but `--fqdntemplate` is specified
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flag is missing a - :)

- Route 53: Support creation of records in multiple hosted zones.
- Route 53: Support creation of ALIAS records when endpoint target is a ELB/ALB.
- Ownership via TXT records
Expand Down
13 changes: 11 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,17 @@ func main() {
log.Fatal(err)
}

source.Register("service", source.NewServiceSource(client, cfg.Namespace, cfg.Compatibility))
source.Register("ingress", source.NewIngressSource(client, cfg.Namespace))
serviceSource, err := source.NewServiceSource(client, cfg.Namespace, cfg.FqdnTemplate, cfg.Compatibility)
if err != nil {
log.Fatal(err)
}
source.Register("service", serviceSource)

ingressSource, err := source.NewIngressSource(client, cfg.Namespace, cfg.FqdnTemplate)
if err != nil {
log.Fatal(err)
}
source.Register("ingress", ingressSource)

sources, err := source.LookupMultiple(cfg.Sources)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type Config struct {
Registry string
RecordOwnerID string
TXTPrefix string
FqdnTemplate string
}

// NewConfig returns new Config object
Expand Down Expand Up @@ -81,5 +82,7 @@ func (cfg *Config) ParseFlags(args []string) error {
flags.StringVar(&cfg.Registry, "registry", "noop", "type of registry for ownership: <noop|txt>")
flags.StringVar(&cfg.RecordOwnerID, "record-owner-id", "", "id for keeping track of the managed records")
flags.StringVar(&cfg.TXTPrefix, "txt-prefix", "", `prefix assigned to DNS name of the associated TXT record; e.g. for --txt-prefix=abc_ [CNAME example.org] <-> [TXT abc_example.org]`)
flags.StringVar(&cfg.FqdnTemplate, "fqdn-template", "", `fallback template to generate DNS name if annotation is missing;
e.g. --fqdn-template={{.Name}}-{{.Namespace}}.example.org will use service/ingress name/namespace`)
return flags.Parse(args)
}
6 changes: 6 additions & 0 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func TestParseFlags(t *testing.T) {
Registry: "noop",
RecordOwnerID: "",
TXTPrefix: "",
FqdnTemplate: "",
},
},
{
Expand All @@ -79,6 +80,7 @@ func TestParseFlags(t *testing.T) {
Registry: "noop",
RecordOwnerID: "",
TXTPrefix: "",
FqdnTemplate: "",
},
},
{
Expand All @@ -105,6 +107,7 @@ func TestParseFlags(t *testing.T) {
Registry: "noop",
RecordOwnerID: "",
TXTPrefix: "",
FqdnTemplate: "",
},
},
{
Expand Down Expand Up @@ -136,6 +139,7 @@ func TestParseFlags(t *testing.T) {
Registry: "noop",
RecordOwnerID: "",
TXTPrefix: "",
FqdnTemplate: "",
},
},
{
Expand All @@ -159,6 +163,7 @@ func TestParseFlags(t *testing.T) {
"--registry=txt",
"--record-owner-id=owner-1",
"--txt-prefix=associated-txt-record",
"--fqdn-template={{.Name}}.service.example.com",
"--version"}},
expected: &Config{
InCluster: true,
Expand All @@ -181,6 +186,7 @@ func TestParseFlags(t *testing.T) {
Registry: "txt",
RecordOwnerID: "owner-1",
TXTPrefix: "associated-txt-record",
FqdnTemplate: "{{.Name}}.service.example.com",
},
},
{
Expand Down
70 changes: 60 additions & 10 deletions source/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ limitations under the License.
package source

import (
"bytes"
"strings"
"text/template"

log "github.com/Sirupsen/logrus"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
Expand All @@ -28,13 +33,29 @@ import (
// Ingress implementation will use the spec.rules.host value for the hostname
// Ingress annotations are ignored
type ingressSource struct {
client kubernetes.Interface
namespace string
client kubernetes.Interface
namespace string
fqdntemplate *template.Template
}

// NewIngressSource creates a new ingressSource with the given client and namespace scope.
func NewIngressSource(client kubernetes.Interface, namespace string) Source {
return &ingressSource{client: client, namespace: namespace}
func NewIngressSource(client kubernetes.Interface, namespace string, fqdntemplate string) (Source, error) {
var tmpl *template.Template
var err error
if fqdntemplate != "" {
tmpl, err = template.New("endpoint").Funcs(template.FuncMap{
"trimPrefix": strings.TrimPrefix,
}).Parse(fqdntemplate)
if err != nil {
return nil, err
}
}

return &ingressSource{
client: client,
namespace: namespace,
fqdntemplate: tmpl,
}, nil
}

// Endpoints returns endpoint objects for each host-target combination that should be processed.
Expand All @@ -48,23 +69,52 @@ func (sc *ingressSource) Endpoints() ([]*endpoint.Endpoint, error) {
endpoints := []*endpoint.Endpoint{}

for _, ing := range ingresses.Items {
// Check controller annotation to see if we are responsible.
controller, ok := ing.Annotations[controllerAnnotationKey]
if ok && controller != controllerAnnotationValue { //TODO(ideahitme): log the skip
continue
}

ingEndpoints := endpointsFromIngress(&ing)

// apply template if host is missing on ingress
if len(ingEndpoints) == 0 && sc.fqdntemplate != nil {
ingEndpoints = sc.endpointsFromTemplate(&ing)
}

endpoints = append(endpoints, ingEndpoints...)
}

return endpoints, nil
}

// endpointsFromIngress extracts the endpoints from ingress object
func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint {
func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint

// Check controller annotation to see if we are responsible.
controller, exists := ing.Annotations[controllerAnnotationKey]
if exists && controller != controllerAnnotationValue {
return endpoints
var buf bytes.Buffer
err := sc.fqdntemplate.Execute(&buf, ing)
if err != nil {
log.Errorf("failed to apply template: %v", err)
return nil
}

hostname := buf.String()
for _, lb := range ing.Status.LoadBalancer.Ingress {
if lb.IP != "" {
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.IP, ""))
}
if lb.Hostname != "" {
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.Hostname, ""))
}
}

return endpoints
}

// endpointsFromIngress extracts the endpoints from ingress object
func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint

for _, rule := range ing.Spec.Rules {
if rule.Host == "" {
continue
Expand Down
83 changes: 81 additions & 2 deletions source/ingress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ package source
import (
"testing"

"github.com/kubernetes-incubator/external-dns/endpoint"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/apis/extensions/v1beta1"

"github.com/kubernetes-incubator/external-dns/endpoint"
)

// Validates that ingressSource is a Source
Expand All @@ -33,6 +34,39 @@ func TestIngress(t *testing.T) {
t.Run("Endpoints", testIngressEndpoints)
}

func TestNewIngressSource(t *testing.T) {
for _, ti := range []struct {
title string
fqdntemplate string
expectError bool
}{
{
title: "invalid template",
expectError: true,
fqdntemplate: "{{.Name",
},
{
title: "valid empty template",
expectError: false,
},
{
title: "valid template",
expectError: false,
fqdntemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com",
},
} {
t.Run(ti.title, func(t *testing.T) {
_, err := NewIngressSource(fake.NewSimpleClientset(), "", ti.fqdntemplate)
if ti.expectError && err == nil {
t.Error("invalid template should return err")
}
if !ti.expectError && err != nil {
t.Error(err)
}
})
}
}

func testEndpointsFromIngress(t *testing.T) {
for _, ti := range []struct {
title string
Expand Down Expand Up @@ -130,6 +164,7 @@ func testIngressEndpoints(t *testing.T) {
targetNamespace string
ingressItems []fakeIngress
expected []*endpoint.Endpoint
fqdntemplate string
}{
{
title: "no ingress",
Expand Down Expand Up @@ -252,6 +287,50 @@ func testIngressEndpoints(t *testing.T) {
},
expected: []*endpoint.Endpoint{},
},
{
title: "template for ingress if host is missing",
targetNamespace: "",
ingressItems: []fakeIngress{
{
name: "fake1",
namespace: namespace,
annotations: map[string]string{
controllerAnnotationKey: controllerAnnotationValue,
},
dnsnames: []string{},
ips: []string{"8.8.8.8"},
hostnames: []string{"elb.com"},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "fake1.ext-dns.test.com",
Target: "8.8.8.8",
},
{
DNSName: "fake1.ext-dns.test.com",
Target: "elb.com",
},
},
fqdntemplate: "{{.Name}}.ext-dns.test.com",
},
{
title: "another controller annotation skipped even with template",
targetNamespace: "",
ingressItems: []fakeIngress{
{
name: "fake1",
namespace: namespace,
annotations: map[string]string{
controllerAnnotationKey: "other-controller",
},
dnsnames: []string{},
ips: []string{"8.8.8.8"},
},
},
expected: []*endpoint.Endpoint{},
fqdntemplate: "{{.Name}}.ext-dns.test.com",
},
} {
t.Run(ti.title, func(t *testing.T) {
ingresses := make([]*v1beta1.Ingress, 0)
Expand All @@ -260,7 +339,7 @@ func testIngressEndpoints(t *testing.T) {
}

fakeClient := fake.NewSimpleClientset()
ingressSource := NewIngressSource(fakeClient, ti.targetNamespace)
ingressSource, _ := NewIngressSource(fakeClient, ti.targetNamespace, ti.fqdntemplate)
for _, ingress := range ingresses {
_, err := fakeClient.Extensions().Ingresses(ingress.Namespace).Create(ingress)
if err != nil {
Expand Down
Loading