-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.go
163 lines (139 loc) · 3.81 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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()
}