Skip to content

Instantly share code, notes, and snippets.

@sawanoboly
Created April 17, 2015 01:41
Show Gist options
  • Save sawanoboly/fe4e17f6991bb034ade2 to your computer and use it in GitHub Desktop.
Save sawanoboly/fe4e17f6991bb034ade2 to your computer and use it in GitHub Desktop.
GolangでHTTPのヘルスチェックをベースにDNSのレコードを更新してみる ref: http://qiita.com/sawanoboly/items/7cf7bcc956b28a7de14e
.envrc
layout go
export DNSIMPLE_TOKEN='*********************'
export DNSIMPLE_USER='*********************@higanworks.com'
export DNS_TARGET_DOMAIN='example.net'
export DNS_TARGET_RR='wwwww'
export DNS_TARGET_HOSTS='210.152.xxx.xx1,210.152.xxx.xx2'
export DNS_BACKUP_HOSTS='210.152.xxx.xx3'
export AWS_ACCESS_KEY='*********************'
export AWS_SECRET_ACCESS_KEY='*********************'
gox -osarch="linux/amd64 darwin/amd64" --output="build/{{.OS}}/{{.Arch}}/healthbased-dnsrr"
package main
import (
"fmt"
dnsimple "github.com/rubyist/go-dnsimple"
"os"
)
type DnsimpleManage struct {
client *dnsimple.DNSimpleClient
TargetHosts []string
BackupHosts []string
TargetRR string
targetDomainName string
targetDomainId int
countAlivedTarget int
apiToken string
email string
}
func (dm *DnsimpleManage) RecordExists(records []dnsimple.Record, host string) int {
for _, val := range records {
if val.Name != dm.TargetRR {
continue
}
if val.Content == host {
fmt.Printf("Record: %s -> %s\n", val.Name, val.Content)
return val.Id
}
}
return 0
}
func (dm *DnsimpleManage) getDomainId() int {
domains, _ := dm.client.Domains()
for _, domain := range domains {
if domain.Name == dm.targetDomainName {
fmt.Printf("Domain: %s %d\n", domain.Name, domain.Id)
return domain.Id
}
}
return 0
}
func (dm *DnsimpleManage) healthBased_rr() int {
dm.countAlivedTarget = len(dm.TargetHosts)
dm.client = dnsimple.NewClient(dm.apiToken, dm.email)
// Get a list of your domains
dm.targetDomainId = dm.getDomainId()
if dm.targetDomainId == 0 {
fmt.Printf("Exit: Target Domain Not found.\n")
os.Exit(1)
}
// Get a list of records for a domain
records, _ := dm.client.Records(dm.targetDomainName, "", "")
for _, val := range dm.TargetHosts {
r_id := dm.RecordExists(records, val)
status, err := tryHttp(val)
if err != nil {
if r_id != 0 {
fmt.Printf("Dead But Exists!: I'll delete [ %d ]. \n", r_id)
delRec := dnsimple.Record{Id: r_id, DomainId: dm.targetDomainId}
err := delRec.Delete(dm.client)
if err != nil {
fmt.Printf("Delete returned error: %v\n", err)
}
} else {
fmt.Printf("Result: Dead\n")
}
dm.countAlivedTarget--
continue
}
fmt.Printf("Result: %d\n", status)
// Create a new Record
// go func() {
if r_id == 0 {
newRec := dnsimple.Record{Name: dm.TargetRR, Content: val, RecordType: "A", TTL: 600}
rec, _ := dm.client.CreateRecord(dm.targetDomainName, newRec)
fmt.Printf("RecordID: %d\n", rec.Id)
} else {
fmt.Printf("Record Already Exists\n")
}
// }()
}
return dm.countAlivedTarget
}
func (dm *DnsimpleManage) enableBackup() int {
return 0
}
$ ./build/darwin/amd64/healthbased-dnsrr dnsimple
Domain: example.net 119xxx
Record: wwwww -> 210.152.xxx.xx1
Dead But Exists!: I'll delete [ 39xxxxx ].
Record: wwwww -> 210.152.xxx.xx2
Result: 200
Record Already Exists
$ ./build/darwin/amd64/healthbased-dnsrr route53
Found: /hostedzone/Z1KM1PIxxxxxxx: example.net.
Finding: wwwww.example.net.
Current RR record: ["210.152.xxx.xx2"]
Check HTTP: 210.152.xxx.xx1 200
Check HTTP: 210.152.xxx.xx2 200
Avaliable targets: ["210.152.xxx.xx1" "210.152.xxx.xx2"]
Found difference, I'll fix it.
Change Status: PENDING
$ tree build
build
├── darwin
│   └── amd64
│   └── healthbased-dnsrr
└── linux
└── amd64
└── healthbased-dnsrr
4 directories, 2 files
package main
import (
"net/http"
"time"
)
var httpClient = &http.Client{Timeout: time.Duration(10) * time.Second}
func tryHttp(host string) (int, error) {
resp, err := httpClient.Get("http://" + host + "/")
if err != nil {
return 0, err
}
defer resp.Body.Close()
return resp.StatusCode, err
}
package main
import (
"fmt"
envmap "github.com/higanworks/go-envmap"
"os"
"strings"
)
var envs = envmap.All()
var targetHosts = strings.Split(envs["DNS_TARGET_HOSTS"], ",")
var backupHosts = strings.Split(envs["DNS_BACKUP_HOSTS"], ",")
type DnsManage interface {
healthBased_rr() int
// enableBackup() error
}
func main() {
os.Exit(realMain())
}
func realMain() int {
var manager DnsManage
if len(os.Args) < 2 {
fmt.Printf("Usage: healthbased-dnsrr [dnsimple|route53]\n")
return 1
}
switch os.Args[1] {
default:
fmt.Printf("Usage: healthbased-dnsrr [dnsimple|route53]\n")
return 1
case "dnsimple":
manager = &DnsimpleManage{
TargetHosts: targetHosts,
BackupHosts: backupHosts,
TargetRR: envs["DNS_TARGET_RR"],
targetDomainName: envs["DNS_TARGET_DOMAIN"],
apiToken: envs["DNSIMPLE_TOKEN"],
email: envs["DNSIMPLE_USER"],
}
case "route53":
manager = &Route53Manage{
TargetHosts: targetHosts,
BackupHosts: backupHosts,
TargetRR: envs["DNS_TARGET_RR"],
targetDomainName: envs["DNS_TARGET_DOMAIN"],
}
}
life := manager.healthBased_rr()
if life == 0 {
fmt.Printf("All Dead\n")
// manager.enableBackup()
}
return 0
}
package main
import (
"fmt"
"github.com/higanworks/goamz/route53"
"github.com/mitchellh/goamz/aws"
"reflect"
"sort"
"strings"
)
type Route53Manage struct {
client *route53.Route53
TargetHosts []string
currentRRHosts []string
updateRRHosts []string
BackupHosts []string
TargetRR string
targetDomainName string
targetDomainId string
countAlivedTarget int
}
func (dm *Route53Manage) extractDomainId(res_id string) string {
id := strings.Split(res_id, "/")
return id[2]
}
func (dm *Route53Manage) RecordExists(records []route53.ResourceRecordSet) route53.ResourceRecordSet {
fmt.Printf("Finding: " + dm.TargetRR + "." + dm.targetDomainName + ".\n")
for _, val := range records {
if val.Name == dm.TargetRR+"."+dm.targetDomainName+"." {
return val
} else if val.Name == dm.TargetRR+"."+dm.targetDomainName {
return val
}
}
return route53.ResourceRecordSet{}
}
func (dm *Route53Manage) getDomainId() string {
res, _ := dm.client.ListHostedZonesByName(dm.targetDomainName, "", 1)
for _, zone := range res.HostedZones {
if zone.Name == dm.targetDomainName+"." {
fmt.Printf("Found: %s: %s\n", zone.ID, zone.Name)
return dm.extractDomainId(zone.ID)
} else if zone.Name == dm.targetDomainName {
fmt.Printf("Found: %s: %s\n", zone.ID, zone.Name)
return dm.extractDomainId(zone.ID)
}
}
return "0"
}
func (dm *Route53Manage) healthBased_rr() int {
dm.countAlivedTarget = len(dm.TargetHosts)
auth, _ := aws.EnvAuth() // AWS_ACCESS_KEY, AWS_SECRET_ACCESS_KEY
dm.client = route53.New(auth, aws.USEast)
// Get a list of your domains
dm.targetDomainId = dm.getDomainId()
res, err := dm.client.ListResourceRecordSets(dm.targetDomainId, nil)
if err != nil {
panic(err)
}
rrr := dm.RecordExists(res.Records)
if rrr.Name != "" {
dm.currentRRHosts = rrr.Records
fmt.Printf("Current RR record: %q\n", dm.currentRRHosts)
} else {
fmt.Printf("Current RR record: Nothing\n")
}
for _, host := range dm.TargetHosts {
status, _ := tryHttp(host)
if err != nil {
status = 0
}
fmt.Printf("Check HTTP: %s %d\n", host, status)
if status != 200 {
dm.countAlivedTarget--
continue
}
dm.updateRRHosts = append(dm.updateRRHosts, host)
}
sort.StringSlice(dm.updateRRHosts).Sort()
sort.StringSlice(dm.currentRRHosts).Sort()
sort.StringSlice(dm.BackupHosts).Sort()
fmt.Printf("Avaliable targets: %q\n", dm.updateRRHosts)
if !reflect.DeepEqual(dm.updateRRHosts, dm.currentRRHosts) {
var req route53.ChangeResourceRecordSetsRequest
var req_rr route53.ResourceRecordSet
var change route53.Change
if dm.countAlivedTarget != 0 {
fmt.Printf("Found difference, I'll fix it.\n")
req_rr.Name = dm.TargetRR + "." + dm.targetDomainName + "."
req_rr.Type = "A"
req_rr.TTL = 600
req_rr.Records = dm.updateRRHosts
change.Action = "UPSERT"
change.Record = req_rr
req.Comment = change.Action + " to " + dm.TargetRR + "." + dm.targetDomainName + "."
req.Changes = append(req.Changes, change)
res, err2 := dm.client.ChangeResourceRecordSets(dm.targetDomainId, &req)
if err2 != nil {
panic(err)
}
fmt.Printf("Change Status: %s\n", res.ChangeInfo.Status)
} else {
// Delete if BackupHosts Empty
if dm.targetDomainId != "0" {
fmt.Printf("All Hosts dead. I'll delete rrset.\n")
req_rr.Name = dm.TargetRR + "." + dm.targetDomainName + "."
req_rr.Type = "A"
req_rr.TTL = 600
req_rr.Records = dm.currentRRHosts
change.Action = "DELETE"
change.Record = req_rr
req.Comment = change.Action + " to " + dm.TargetRR + "." + dm.targetDomainName + "."
req.Changes = append(req.Changes, change)
res, err2 := dm.client.ChangeResourceRecordSets(dm.targetDomainId, &req)
if err2 != nil {
panic(err2)
}
fmt.Printf("Change Status: %s\n", res.ChangeInfo.Status)
}
}
}
return dm.countAlivedTarget
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment