@ -5,7 +5,11 @@ import (
"fmt"
"fmt"
"net/http"
"net/http"
"os"
"os"
"strconv"
"strings"
"sync"
"github.com/dnsimple/dnsimple-go/dnsimple"
"github.com/pkg/errors"
"github.com/pkg/errors"
"github.com/urfave/cli"
"github.com/urfave/cli"
)
)
@ -22,17 +26,97 @@ func main() {
Value : "https://whatismyipv6.buddy.wtf/json" ,
Value : "https://whatismyipv6.buddy.wtf/json" ,
Usage : "url to use to get public ip address" ,
Usage : "url to use to get public ip address" ,
} ,
} ,
cli . StringFlag {
EnvVar : "DNSIMPLE_TOKEN" ,
Name : "dnsimple-token" ,
Usage : "dnsimple token" ,
} ,
}
}
app . Run ( os . Args )
app . Run ( os . Args )
}
}
type runtimeError string
func ( e runtimeError ) Error ( ) string {
return string ( e )
}
func action ( context * cli . Context ) error {
func action ( context * cli . Context ) error {
var wg sync . WaitGroup
if context . NArg ( ) < 1 {
return runtimeError ( "requires host argument" )
}
host := context . Args ( ) . Get ( 0 )
wg . Add ( 1 )
var getIPError error
var ip string
go func ( ) {
defer wg . Done ( )
result , err := getIP ( context . String ( "url" ) )
result , err := getIP ( context . String ( "url" ) )
if err != nil {
if err != nil {
return errors . Wrap ( err , "could not get IP address" )
getIPError = errors . Wrap ( err , "could not get IP address" )
return
}
ip = result . IP
} ( )
var dnSimpleAccountID string
var getRecordError error
var record * dnsimple . ZoneRecord
credentials := dnsimple . NewOauthTokenCredentials ( context . String ( "dnsimple-token" ) )
dnSimpleClient := dnsimple . NewClient ( credentials )
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
accountID , result , err := getRecord ( dnSimpleClient , host , "AAAA" )
dnSimpleAccountID = accountID
if err != nil {
getRecordError = errors . Wrap ( err , "could not get ip for host" )
return
}
record = result
} ( )
wg . Wait ( )
if getIPError != nil {
return errors . Wrap ( getIPError , "could no get ip address" )
}
if getRecordError != nil {
switch errors . Cause ( getRecordError ) . ( type ) {
case notFoundError :
break
default :
return errors . Wrap ( getRecordError , "could no get record" )
}
}
subdomain , domain , _ := splitHost ( host )
if record == nil {
_ , err := dnSimpleClient . Zones . CreateRecord ( dnSimpleAccountID , domain , dnsimple . ZoneRecord {
Name : subdomain ,
Type : "AAAA" ,
Content : ip ,
} )
if err != nil {
return errors . Wrap ( err , "could not create record" )
}
fmt . Printf ( "%s record created for %s as %s\n" , "AAAA" , host , ip )
} else if ip != record . Content {
record . Content = ip
_ , err := dnSimpleClient . Zones . UpdateRecord ( dnSimpleAccountID , domain , record . ID , * record )
if err != nil {
return errors . Wrap ( err , "could not update record" )
}
fmt . Printf ( "%s record for %s updated to %s\n" , "AAAA" , host , ip )
} else {
fmt . Printf ( "%s record for %s already set as %s\n" , "AAAA" , host , ip )
}
}
fmt . Println ( result . IP )
return nil
return nil
}
}
@ -41,6 +125,57 @@ type whatIsMyIPResult struct {
Version string ` json:version `
Version string ` json:version `
}
}
type notFoundError error
func getRecord ( client * dnsimple . Client , host string , kind string ) ( string , * dnsimple . ZoneRecord , error ) {
subdomain , domain , err := splitHost ( host )
if err != nil {
return "" , nil , errors . Wrap ( err , "unable to parse host" )
}
accountID , err := getAccountID ( client )
if err != nil {
return "" , nil , errors . Wrap ( err , "could not get account id" )
}
records , err := client . Zones . ListRecords ( accountID , domain , & dnsimple . ZoneRecordListOptions { } )
if err != nil {
return "" , nil , errors . Wrap ( err , "could not get list of records" )
}
for _ , record := range records . Data {
if record . ZoneID == domain && record . Name == subdomain && record . Type == kind {
return accountID , & record , nil
}
}
return accountID , nil , notFoundError ( errors . New ( "could not find record" ) )
}
func getAccountID ( client * dnsimple . Client ) ( string , error ) {
whoamiResponse , err := client . Identity . Whoami ( )
if err != nil {
return "" , errors . Wrap ( err , "could not get account information from dnsimple" )
}
if whoamiResponse . Data . Account == nil {
return "" , errors . New ( "could not get account information" )
}
return strconv . Itoa ( whoamiResponse . Data . Account . ID ) , nil
}
func splitHost ( host string ) ( subdomain string , domain string , err error ) {
subdomains := strings . Split ( host , "." )
if len ( subdomains ) >= 3 {
domain = strings . Join ( subdomains [ len ( subdomains ) - 2 : ] , "." )
subdomain = strings . Join ( subdomains [ : len ( subdomains ) - 2 ] , "." )
} else if len ( subdomains ) == 2 {
domain = strings . Join ( subdomains , "." )
} else {
err = errors . New ( "invalid domain" )
}
return subdomain , domain , err
}
func getIP ( url string ) ( * whatIsMyIPResult , error ) {
func getIP ( url string ) ( * whatIsMyIPResult , error ) {
result , err := http . Get ( url )
result , err := http . Get ( url )
if err != nil {
if err != nil {