164 lines
3.8 KiB
Go
164 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
|
"github.com/aws/aws-sdk-go/service/route53"
|
|
|
|
"github.com/robfig/cron/v3"
|
|
yaml "gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// Record defines a single record to upsert and the schedule for updates
|
|
type Record struct {
|
|
Cron string
|
|
Name string
|
|
TTL int64
|
|
Zone string
|
|
}
|
|
|
|
// Config defines the source endpoint and a set of records to update
|
|
type Config struct {
|
|
Records []Record
|
|
Source string
|
|
}
|
|
|
|
// FindPublicAddress fetches the public address from an external HTTP/S endpoint
|
|
func FindPublicAddress(url string) ([]string, error) {
|
|
ipResp, ipError := http.Get(url)
|
|
if ipError != nil {
|
|
log.Printf("error getting public address: %s", ipError.Error())
|
|
return nil, ipError
|
|
}
|
|
|
|
defer ipResp.Body.Close()
|
|
body, bodyError := ioutil.ReadAll(ipResp.Body)
|
|
if bodyError != nil {
|
|
log.Printf("error getting response body: %s", bodyError.Error())
|
|
return nil, bodyError
|
|
}
|
|
|
|
return []string{
|
|
string(body),
|
|
}, nil
|
|
}
|
|
|
|
// LoadConfig loads the config data (source and records) from a YAML file
|
|
func LoadConfig(path string) (*Config, error) {
|
|
data, readError := ioutil.ReadFile(path)
|
|
if readError != nil {
|
|
log.Printf("error reading config: %s", readError.Error())
|
|
return nil, readError
|
|
}
|
|
|
|
dest := &Config{}
|
|
yamlError := yaml.Unmarshal(data, dest)
|
|
if yamlError != nil {
|
|
log.Printf("error parsing config: %s", yamlError.Error())
|
|
return nil, yamlError
|
|
}
|
|
|
|
return dest, nil
|
|
}
|
|
|
|
// MapResourceValues converts address strings to route53's special record type
|
|
func MapResourceValues(values []string) []*route53.ResourceRecord {
|
|
records := make([]*route53.ResourceRecord, len(values))
|
|
for i, v := range values {
|
|
records[i] = &route53.ResourceRecord{
|
|
Value: aws.String(v),
|
|
}
|
|
}
|
|
return records
|
|
}
|
|
|
|
// UpdateRecord upserts a single record in a zone
|
|
func UpdateRecord(conf *Config, r Record) {
|
|
// create a session
|
|
sess, sessError := session.NewSession()
|
|
if sessError != nil {
|
|
log.Printf("error creating aws session: %s", sessError.Error())
|
|
return
|
|
}
|
|
|
|
// get current ip
|
|
values, valueError := FindPublicAddress(conf.Source)
|
|
if valueError != nil {
|
|
log.Printf("error fetching external address: %s", valueError.Error())
|
|
return
|
|
}
|
|
|
|
log.Printf("updating '%s' to '%s'", r.Name, values)
|
|
|
|
// prepare the update
|
|
updates := &route53.ChangeResourceRecordSetsInput{
|
|
ChangeBatch: &route53.ChangeBatch{
|
|
Changes: []*route53.Change{
|
|
{
|
|
Action: aws.String("UPSERT"),
|
|
ResourceRecordSet: &route53.ResourceRecordSet{
|
|
Name: aws.String(r.Name),
|
|
TTL: aws.Int64(r.TTL),
|
|
Type: aws.String("A"),
|
|
ResourceRecords: MapResourceValues(values),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
HostedZoneId: aws.String(r.Zone),
|
|
}
|
|
|
|
// apply
|
|
svc := route53.New(sess)
|
|
updateOutput, updateError := svc.ChangeResourceRecordSets(updates)
|
|
if updateError != nil {
|
|
log.Printf("error updating route53 records: %s", updateError.Error())
|
|
return
|
|
}
|
|
|
|
log.Printf("updated route53 records: %v", updateOutput)
|
|
}
|
|
|
|
// ScheduleJob adds a new record and update job to the cron pool
|
|
func ScheduleJob(conf *Config, c *cron.Cron, r Record) {
|
|
log.Printf("scheduling cron job for %s", r.Name)
|
|
c.AddFunc(r.Cron, func() {
|
|
log.Printf("executing cron job for %s", r.Name)
|
|
UpdateRecord(conf, r)
|
|
})
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
log.Printf("not enough arguments: %s config.yml", os.Args[0])
|
|
return
|
|
}
|
|
|
|
confPath := os.Args[1]
|
|
log.Printf("loading config from '%s'", confPath)
|
|
conf, confError := LoadConfig(confPath)
|
|
if confError != nil {
|
|
log.Printf("error loading config: %s", confError.Error())
|
|
return
|
|
}
|
|
|
|
// schedule cron jobs
|
|
c := cron.New()
|
|
for _, r := range conf.Records {
|
|
ScheduleJob(conf, c, r)
|
|
}
|
|
c.Start()
|
|
|
|
stop := make(chan os.Signal)
|
|
signal.Notify(stop, os.Interrupt, os.Kill)
|
|
|
|
<-stop
|
|
c.Stop()
|
|
}
|