Skip to content

Commit

Permalink
Add support for registering route53 records
Browse files Browse the repository at this point in the history
  • Loading branch information
monder committed Jan 7, 2019
1 parent 63d6ee1 commit 7fee8e4
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 136 deletions.
30 changes: 26 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,56 +1,78 @@
module github.com/monder/service-target-group

require (
4d63.com/gochecknoglobals v0.0.0-20180908201037-5090db600a84 // indirect
4d63.com/gochecknoinits v0.0.0-20180528051558-14d5915061e5 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/alecthomas/gocyclo v0.0.0-20150208221726-aa8f8b160214 // indirect
github.com/alexflint/go-arg v1.0.0 // indirect
github.com/alexkohler/nakedret v0.0.0-20171106223215-c0e305a4f690 // indirect
github.com/aws/aws-sdk-go v1.16.11
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/client9/misspell v0.3.4 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-ini/ini v1.40.0 // indirect
github.com/go-logr/logr v0.1.0 // indirect
github.com/go-logr/zapr v0.1.0 // indirect
github.com/gogo/protobuf v1.2.0 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff // indirect
github.com/golang/lint v0.0.0-20181217174547-8f45f776aaf1 // indirect
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
github.com/googleapis/gnostic v0.2.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc // indirect
github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f // indirect
github.com/hashicorp/golang-lru v0.5.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb // indirect
github.com/json-iterator/go v1.1.5 // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/kisielk/errcheck v1.2.0 // indirect
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a // indirect
github.com/mdempsky/maligned v0.0.0-20180708014732-6e39bd26a8c8 // indirect
github.com/mdempsky/unconvert v0.0.0-20180703203632-1a9a0a0a3594 // indirect
github.com/mibk/dupl v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/opennota/check v0.0.0-20180911053232-0c771f5545ff // indirect
github.com/pborman/uuid v1.2.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.8.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v0.9.2 // indirect
github.com/securego/gosec v0.0.0-20181211171558-12400f9a1ca7 // indirect
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
github.com/spf13/pflag v1.0.3 // indirect
github.com/stretchr/testify v1.2.2 // indirect
github.com/stripe/safesql v0.0.0-20171221195208-cddf355596fe // indirect
github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 // indirect
github.com/walle/lll v0.0.0-20160702150637-8b13b3fbf731 // indirect
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.9.1 // indirect
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 // indirect
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 // indirect
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 // indirect
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 // indirect
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
golang.org/x/tools v0.0.0-20190106171756-3ef68632349c // indirect
google.golang.org/appengine v1.4.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a // indirect
k8s.io/api v0.0.0-20181221193117-173ce66c1e39
k8s.io/apiextensions-apiserver v0.0.0-20181221201254-261a947e2c38 // indirect
k8s.io/apimachinery v0.0.0-20181222072933-b814ad55d7c5
k8s.io/client-go v10.0.0+incompatible // indirect
k8s.io/klog v0.1.0 // indirect
k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be // indirect
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
mvdan.cc/unparam v0.0.0-20181201214637-68701730a1d7 // indirect
sigs.k8s.io/controller-runtime v0.1.9
sigs.k8s.io/testing_frameworks v0.1.1 // indirect
sigs.k8s.io/yaml v1.1.0 // indirect
Expand Down
138 changes: 6 additions & 132 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,159 +1,33 @@
package main

import (
"context"
"fmt"
"log"
"os"
"reflect"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/elbv2"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"

"github.com/monder/service-target-group/reconciler"
)

func main() {
reconciler := &endpointReconciler{
managedResources: make(map[string]map[string]*elbv2.TargetDescription, 0),
}
r := reconciler.New()

manager, err := builder.SimpleController().
ForType(&corev1.Service{}).
ForType(&corev1.Endpoints{}).
Build(reconciler)
Build(r)

if err != nil {
log.Println("Unable to build controller:", err)
os.Exit(1)
}

reconciler.client = manager.GetClient()
r.SetClient(manager.GetClient())

if err := manager.Start(signals.SetupSignalHandler()); err != nil {
log.Println("Unable to run controller:", err)
os.Exit(1)
}

}

type endpointReconciler struct {
client client.Client
managedResources map[string]map[string]*elbv2.TargetDescription
}

func (r *endpointReconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) {
rss := &corev1.Service{}
err := r.client.Get(context.TODO(), request.NamespacedName, rss)
if errors.IsNotFound(err) {
delete(r.managedResources, request.NamespacedName.String())
// TODO deregister everything?
return reconcile.Result{}, nil
}

targetGroupARN := rss.Annotations["stg.monder.cc/target-group"]
if targetGroupARN == "" { // Skip services that we do not need to register
return reconcile.Result{}, nil
}
parsedARN, err := arn.Parse(targetGroupARN)
if err != nil {
fmt.Println(err.Error())
return reconcile.Result{}, nil
}

rse := &corev1.Endpoints{}
err = r.client.Get(context.TODO(), request.NamespacedName, rse)
if errors.IsNotFound(err) {
delete(r.managedResources, request.NamespacedName.String())
// TODO deregister everything?
return reconcile.Result{}, nil
}

newState := make(map[string]*elbv2.TargetDescription, 0)

for _, s := range rse.Subsets {
for _, p := range s.Ports {
for _, a := range s.Addresses {
newState[fmt.Sprintf("%s:%d", a.IP, p.Port)] = &elbv2.TargetDescription{
Id: aws.String(a.IP),
Port: aws.Int64(int64(p.Port)),
}
}
}
}

if reflect.DeepEqual(newState, r.managedResources[request.NamespacedName.String()]) {
return reconcile.Result{}, nil
}

targetsToDeregister := make([]*elbv2.TargetDescription, 0)
targetsToRegister := make([]*elbv2.TargetDescription, 0)

svc := elbv2.New(session.Must(session.NewSession(&aws.Config{
Region: aws.String(parsedARN.Region),
})))
result, err := svc.DescribeTargetHealth(&elbv2.DescribeTargetHealthInput{
TargetGroupArn: aws.String(targetGroupARN),
})
if err != nil {
fmt.Println(err.Error())
return reconcile.Result{}, nil
}

for _, th := range result.TargetHealthDescriptions {
_, keep := newState[fmt.Sprintf("%s:%d", *th.Target.Id, *th.Target.Port)]
if !keep {
targetsToDeregister = append(targetsToDeregister, th.Target)
}
}

for _, td := range newState {
found := false
for _, th := range result.TargetHealthDescriptions {
if *th.Target.Id == *td.Id && *th.Target.Port == *td.Port && *th.TargetHealth.State != elbv2.TargetHealthStateEnumDraining {
found = true
break
}
}
if !found {
targetsToRegister = append(targetsToRegister, td)
}
}

fmt.Println("dereg:")
fmt.Println(targetsToDeregister)
fmt.Println("reg:")
fmt.Println(targetsToRegister)

// Register
if len(targetsToRegister) > 0 {
_, err = svc.RegisterTargets(&elbv2.RegisterTargetsInput{
TargetGroupArn: aws.String(targetGroupARN),
Targets: targetsToRegister,
})
if err != nil {
fmt.Println(err.Error())
}
}

// Deregister
if len(targetsToDeregister) > 0 {
_, err = svc.DeregisterTargets(&elbv2.DeregisterTargetsInput{
TargetGroupArn: aws.String(targetGroupARN),
Targets: targetsToDeregister,
})
if err != nil {
fmt.Println(err.Error())
}
}

fmt.Println("---")
r.managedResources[request.NamespacedName.String()] = newState
return reconcile.Result{}, nil
}
53 changes: 53 additions & 0 deletions reconciler/new.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package reconciler

import (
"context"
"fmt"

"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/aws/aws-sdk-go/service/route53"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

func New() Reconciler {
return &endpointReconciler{
elbResources: make(map[string]map[string]*elbv2.TargetDescription, 0),
route53Resources: make(map[string]*route53.ResourceRecordSet, 0),
}
}

func (r *endpointReconciler) SetClient(client client.Client) {
r.client = client
}

func (r *endpointReconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) {
rss := &corev1.Service{}
err := r.client.Get(context.TODO(), request.NamespacedName, rss)
if errors.IsNotFound(err) {
delete(r.elbResources, request.NamespacedName.String())
delete(r.route53Resources, request.NamespacedName.String())
// TODO deregister everything?
return reconcile.Result{}, nil
}

targetGroupARN := rss.Annotations["stg.monder.cc/target-group"]
if targetGroupARN != "" {
err = r.ReconcileTargetGroup(request, targetGroupARN)
if err != nil {
fmt.Println(err.Error())
}
}
route53Domain := rss.Annotations["route53.monder.cc/domain-name"]
route53Zone := rss.Annotations["route53.monder.cc/zone"]
if route53Domain != "" && route53Zone != "" {
err = r.ReconcileRoute53(request, route53Zone, route53Domain)
if err != nil {
fmt.Println(err.Error())
}
}
return reconcile.Result{}, nil
}
66 changes: 66 additions & 0 deletions reconciler/route53.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package reconciler

import (
"context"
"fmt"
"reflect"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

func (r *endpointReconciler) ReconcileRoute53(request reconcile.Request, zone string, domain string) error {

rse := &corev1.Endpoints{}
err := r.client.Get(context.TODO(), request.NamespacedName, rse)
if errors.IsNotFound(err) {
delete(r.route53Resources, request.NamespacedName.String())
// TODO deregister everything?
return nil
}

newRecordSet := &route53.ResourceRecordSet{
Name: aws.String(domain),
Type: aws.String(route53.RRTypeA),
TTL: aws.Int64(1),
ResourceRecords: []*route53.ResourceRecord{},
}

for _, s := range rse.Subsets {
for _, a := range s.Addresses {
newRecordSet.ResourceRecords = append(newRecordSet.ResourceRecords, &route53.ResourceRecord{
Value: aws.String(a.IP),
})
}
}

if reflect.DeepEqual(newRecordSet, r.route53Resources[request.NamespacedName.String()]) {
return nil
}

fmt.Printf("updating route53: %s\n", domain)
svc := route53.New(session.Must(session.NewSession(&aws.Config{})))
_, err = svc.ChangeResourceRecordSets(&route53.ChangeResourceRecordSetsInput{
ChangeBatch: &route53.ChangeBatch{
Changes: []*route53.Change{
{
Action: aws.String(route53.ChangeActionUpsert),
ResourceRecordSet: newRecordSet,
},
},
},
HostedZoneId: aws.String(zone),
})

if err != nil {
fmt.Println(err.Error())
}

r.route53Resources[request.NamespacedName.String()] = newRecordSet
return nil
}
Loading

0 comments on commit 7fee8e4

Please sign in to comment.