You've already forked ddns-updater
Merge pull request #6 from mkelcik/new_ident_me_resolver
Add v4.ident.me resolver
This commit is contained in:
@@ -15,7 +15,7 @@ Before run, you need configure this environment variables.
|
||||
- `CLOUDFLARE_ZONE` - (required) zone name with domain you want to check. See: [https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones](https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones)
|
||||
- `ON_CHANGE_COMMENT` - (optional) in the event that the ip address of the dns record changes, this comment will be added to the record
|
||||
- `CHECK_INTERVAL_SECONDS` - (optional) how often will the ip address of the records be checked (default: `300`)
|
||||
- `PUBLIC_IP_RESOLVER` - (optional) public ip address resolver. For now only resolving via `https://ifconfig.me` is implemented. (default: `ifconfig.me`)
|
||||
- `PUBLIC_IP_RESOLVER` - (optional) public ip address resolver. (default: `ifconfig.me`) Available: `ifconfig.me`, `v4.ident.me`
|
||||
|
||||
### Building from source
|
||||
|
||||
@@ -43,6 +43,7 @@ version: "3"
|
||||
services:
|
||||
cf-dns-updater:
|
||||
image: mkelcik/cloudflare-ddns-update:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- CLOUDFLARE_DNS_TO_CHECK=my.testdomain.com,your.testdomain.com
|
||||
- CLOUDFLARE_API_KEY=your_cloudflare_api_key
|
||||
|
||||
@@ -2,6 +2,7 @@ version: "3"
|
||||
services:
|
||||
cf-dns-updater:
|
||||
image: mkelcik/cloudflare-ddns-update:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- CLOUDFLARE_DNS_TO_CHECK=my.testdomain.com,your.testdomain.com
|
||||
- CLOUDFLARE_API_KEY=your_cloudflare_api_key
|
||||
|
||||
10
main.go
10
main.go
@@ -17,13 +17,15 @@ type PublicIpResolver interface {
|
||||
ResolvePublicIp(ctx context.Context) (net.IP, error)
|
||||
}
|
||||
|
||||
func getResolver(resolverName string) PublicIpResolver {
|
||||
func getResolver(resolverName string) (PublicIpResolver, string) {
|
||||
switch resolverName {
|
||||
// HERE add another resolver if needed
|
||||
case public_resolvers.V4IdentMeTag:
|
||||
return public_resolvers.NewV4IdentMeDefault(), public_resolvers.V4IdentMeTag
|
||||
case public_resolvers.IfConfigMeTag:
|
||||
fallthrough
|
||||
default:
|
||||
return public_resolvers.NewDefaultIfConfigMe()
|
||||
return public_resolvers.NewDefaultIfConfigMe(), public_resolvers.IfConfigMeTag
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,14 +50,14 @@ func main() {
|
||||
}
|
||||
|
||||
// public ip resolver
|
||||
publicIpResolver := getResolver(config.PublicIpResolverTag)
|
||||
publicIpResolver, resolverTag := getResolver(config.PublicIpResolverTag)
|
||||
|
||||
checkFunc := func() {
|
||||
currentPublicIP, err := publicIpResolver.ResolvePublicIp(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Current public ip `%s`", currentPublicIP)
|
||||
log.Printf("Current public ip `%s` (%s)", currentPublicIP, resolverTag)
|
||||
|
||||
dns, err := allDNSRecords(ctx, api, cloudflare.ZoneIdentifier(zoneID))
|
||||
if err != nil {
|
||||
|
||||
44
public_resolvers/base_resolver.go
Normal file
44
public_resolvers/base_resolver.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package public_resolvers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Doer interface {
|
||||
Do(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type baseResolver struct {
|
||||
client Doer
|
||||
url string
|
||||
}
|
||||
|
||||
func (i baseResolver) ResolvePublicIp(ctx context.Context) (net.IP, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, i.url, nil)
|
||||
if err != nil {
|
||||
return net.IP{}, fmt.Errorf("error creating ifconfig request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := i.client.Do(req)
|
||||
if err != nil {
|
||||
return net.IP{}, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return net.IP{}, fmt.Errorf("unexpected response code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
ipText, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return net.IP{}, fmt.Errorf("error reading body: %w", err)
|
||||
}
|
||||
|
||||
return net.ParseIP(string(ipText)), nil
|
||||
}
|
||||
97
public_resolvers/base_resolver_test.go
Normal file
97
public_resolvers/base_resolver_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package public_resolvers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// RoundTripFunc .
|
||||
type RoundTripFunc func(req *http.Request) *http.Response
|
||||
|
||||
// RoundTrip .
|
||||
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return f(req), nil
|
||||
}
|
||||
|
||||
// NewTestClient returns *http.Client with Transport replaced to avoid making real calls
|
||||
func NewTestClient(fn RoundTripFunc) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: RoundTripFunc(fn),
|
||||
}
|
||||
}
|
||||
|
||||
func Test_baseResolver_ResolvePublicIp(t *testing.T) {
|
||||
|
||||
testUrl := "http://my-test-url.url"
|
||||
testIp := `192.168.0.100`
|
||||
|
||||
client := NewTestClient(func(req *http.Request) *http.Response {
|
||||
|
||||
if req.URL.String() != testUrl {
|
||||
return &http.Response{
|
||||
StatusCode: 500,
|
||||
// Send response to be tested
|
||||
Body: io.NopCloser(bytes.NewBufferString(`invalid url`)),
|
||||
// Must be set to non-nil value or it panics
|
||||
Header: make(http.Header),
|
||||
}
|
||||
}
|
||||
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
// Send response to be tested
|
||||
Body: io.NopCloser(bytes.NewBufferString(testIp)),
|
||||
// Must be set to non-nil value or it panics
|
||||
Header: make(http.Header),
|
||||
}
|
||||
})
|
||||
|
||||
type fields struct {
|
||||
client Doer
|
||||
url string
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want net.IP
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "check parse ip4",
|
||||
fields: fields{
|
||||
client: client,
|
||||
url: testUrl,
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
},
|
||||
want: net.ParseIP(testIp),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
i := baseResolver{
|
||||
client: tt.fields.client,
|
||||
url: tt.fields.url,
|
||||
}
|
||||
got, err := i.ResolvePublicIp(tt.args.ctx)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ResolvePublicIp() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ResolvePublicIp() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,17 @@
|
||||
package public_resolvers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
IfConfigMeTag = "ifconfig.me"
|
||||
)
|
||||
|
||||
type Doer interface {
|
||||
Do(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
var (
|
||||
ifConfigMeUrl = "https://ifconfig.me"
|
||||
)
|
||||
|
||||
type IfConfigMe struct {
|
||||
client Doer
|
||||
baseResolver
|
||||
}
|
||||
|
||||
func NewDefaultIfConfigMe() *IfConfigMe {
|
||||
@@ -31,32 +20,11 @@ func NewDefaultIfConfigMe() *IfConfigMe {
|
||||
})
|
||||
}
|
||||
|
||||
func NewIfConfigMe(c Doer) *IfConfigMe {
|
||||
return &IfConfigMe{client: c}
|
||||
func NewIfConfigMe(client Doer) *IfConfigMe {
|
||||
return &IfConfigMe{
|
||||
baseResolver: baseResolver{
|
||||
client: client,
|
||||
url: ifConfigMeUrl,
|
||||
},
|
||||
}
|
||||
|
||||
func (i IfConfigMe) ResolvePublicIp(ctx context.Context) (net.IP, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, ifConfigMeUrl, nil)
|
||||
if err != nil {
|
||||
return net.IP{}, fmt.Errorf("error creating ifconfig request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := i.client.Do(req)
|
||||
if err != nil {
|
||||
return net.IP{}, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return net.IP{}, fmt.Errorf("unexpected response code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
ipText, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return net.IP{}, fmt.Errorf("error reading body: %w", err)
|
||||
}
|
||||
|
||||
return net.ParseIP(string(ipText)), nil
|
||||
}
|
||||
|
||||
30
public_resolvers/v4identme.go
Normal file
30
public_resolvers/v4identme.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package public_resolvers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
V4IdentMeTag = "v4.ident.me"
|
||||
v4IdentMeUrl = "https://v4.ident.me/"
|
||||
)
|
||||
|
||||
type V4IdentMe struct {
|
||||
baseResolver
|
||||
}
|
||||
|
||||
func NewV4IdentMeDefault() *V4IdentMe {
|
||||
return NewV4IdentMe(&http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
})
|
||||
}
|
||||
|
||||
func NewV4IdentMe(client Doer) *V4IdentMe {
|
||||
return &V4IdentMe{
|
||||
baseResolver: baseResolver{
|
||||
client: client,
|
||||
url: v4IdentMeUrl,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user