Skip to content

Commit 64d3839

Browse files
author
David Prandzioch
committed
Merge branch 'develop'
2 parents 19f083b + df7caca commit 64d3839

8 files changed

+247
-83
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[1.1.0]
2+
* Update Debian Jessie to Debian Stretch
3+
* Multistage Dockerfile resulting in smaller production image
4+
* Code refactoring
5+
* Extended response
6+
* Basic unit test coverage
7+
* Documentation on running from DockerHub
8+
9+
[1.0.0]
10+
* Initial release

Dockerfile

+15-11
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1-
FROM debian:jessie
2-
MAINTAINER David Prandzioch <hello@davd.eu>
3-
1+
FROM debian:stretch as builder
42
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
5-
apt-get install -q -y bind9 dnsutils golang git-core && \
3+
apt-get install -q -y golang git-core && \
64
apt-get clean
75

8-
RUN chmod 770 /var/cache/bind
9-
10-
COPY setup.sh /root/setup.sh
11-
RUN chmod +x /root/setup.sh
12-
136
ENV GOPATH=/root/go
147
RUN mkdir -p /root/go/src
158
COPY rest-api /root/go/src/dyndns
16-
RUN cd /root/go/src/dyndns && go get
9+
RUN cd /root/go/src/dyndns && go get && go test -v
10+
11+
FROM debian:stretch
12+
MAINTAINER David Prandzioch <hello+ddns@davd.eu>
1713

14+
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
15+
apt-get install -q -y bind9 dnsutils && \
16+
apt-get clean
17+
18+
RUN chmod 770 /var/cache/bind
19+
COPY setup.sh /root/setup.sh
20+
RUN chmod +x /root/setup.sh
1821
COPY named.conf.options /etc/bind/named.conf.options
22+
COPY --from=builder /root/go/bin/dyndns /root/dyndns
1923

2024
EXPOSE 53 8080
21-
CMD ["sh", "-c", "/root/setup.sh ; service bind9 start ; /root/go/bin/dyndns"]
25+
CMD ["sh", "-c", "/root/setup.sh ; service bind9 start ; /root/dyndns"]

Makefile

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
image:
2-
docker build -t davd/dyndns-server .
2+
docker build -t davd/docker-ddns:latest .
33

44
console:
5-
docker run -it -p 8080:8080 -p 53:53 -p 53:53/udp --rm davd/dyndns-server bash
5+
docker run -it -p 8080:8080 -p 53:53 -p 53:53/udp --rm davd/docker-ddns:latest bash
6+
7+
devconsole:
8+
docker run -it --rm -v ${PWD}/rest-api:/usr/src/app -w /usr/src/app golang:1.8.5 bash
69

710
server_test:
8-
docker run -it -p 8080:8080 -p 53:53 -p 53:53/udp --env-file envfile --rm davd/dyndns-server
11+
docker run -it -p 8080:8080 -p 53:53 -p 53:53/udp --env-file envfile --rm davd/docker-ddns:latest
12+
13+
unit_tests:
14+
docker run -it --rm -v ${PWD}/rest-api:/go/src/dyndns -w /go/src/dyndns golang:1.8.5 /bin/bash -c "go get && go test -v"
915

1016
api_test:
11-
curl "http://localhost:8080/update?secret=changeme&domain=foo&addr=1.2.3.4"
12-
dig @localhost foo.example.org
17+
curl "http://docker.local:8080/update?secret=changeme&domain=foo&addr=1.2.3.4"
18+
dig @docker.local foo.example.org
19+
20+
api_test_invalid_params:
21+
curl "http://docker.local:8080/update?secret=changeme&addr=1.2.3.4"
22+
dig @docker.local foo.example.org
1323

1424
api_test_recursion:
15-
dig @localhost google.com
25+
dig @docker.local google.com
1626

1727
deploy: image
18-
docker run -it -d -p 8080:8080 -p 53:53 -p 53:53/udp --env-file envfile --name=dyndns davd/dyndns-server
28+
docker run -it -d -p 8080:8080 -p 53:53 -p 53:53/udp --env-file envfile --name=dyndns davd/docker-ddns:latest

rest-api/ipparser/ipparser.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package ipparser
2+
3+
import (
4+
"net"
5+
)
6+
7+
func ValidIP4(ipAddress string) bool {
8+
testInput := net.ParseIP(ipAddress)
9+
if testInput == nil {
10+
return false
11+
}
12+
13+
return (testInput.To4() != nil)
14+
}
15+
16+
func ValidIP6(ip6Address string) bool {
17+
testInputIP6 := net.ParseIP(ip6Address)
18+
if testInputIP6 == nil {
19+
return false
20+
}
21+
22+
return (testInputIP6.To16() != nil)
23+
}
24+

rest-api/ipparser_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
"dyndns/ipparser"
6+
)
7+
8+
func TestValidIP4ToReturnTrueOnValidAddress(t *testing.T) {
9+
result := ipparser.ValidIP4("1.2.3.4")
10+
11+
if result != true {
12+
t.Fatalf("Expected ValidIP(1.2.3.4) to be true but got false")
13+
}
14+
}
15+
16+
func TestValidIP4ToReturnFalseOnInvalidAddress(t *testing.T) {
17+
result := ipparser.ValidIP4("abcd")
18+
19+
if result == true {
20+
t.Fatalf("Expected ValidIP(abcd) to be false but got true")
21+
}
22+
}
23+
24+
func TestValidIP4ToReturnFalseOnEmptyAddress(t *testing.T) {
25+
result := ipparser.ValidIP4("")
26+
27+
if result == true {
28+
t.Fatalf("Expected ValidIP() to be false but got true")
29+
}
30+
}

rest-api/main.go

+4-65
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,12 @@ import (
1010
"os/exec"
1111
"bytes"
1212
"encoding/json"
13-
"net"
1413

1514
"github.com/gorilla/mux"
1615
)
1716

1817
var appConfig = &Config{}
1918

20-
type WebserviceResponse struct {
21-
Success bool
22-
Message string
23-
}
24-
2519
func main() {
2620
appConfig.LoadConfig("/etc/dyndns.json")
2721

@@ -32,70 +26,15 @@ func main() {
3226
log.Fatal(http.ListenAndServe(":8080", router))
3327
}
3428

35-
func validIP4(ipAddress string) bool {
36-
testInput := net.ParseIP(ipAddress)
37-
if testInput == nil {
38-
return false
39-
}
40-
41-
return (testInput.To4() != nil)
42-
}
43-
44-
func validIP6(ip6Address string) bool {
45-
testInputIP6 := net.ParseIP(ip6Address)
46-
if testInputIP6 == nil {
47-
return false
48-
}
49-
50-
return (testInputIP6.To16() != nil)
51-
}
52-
5329
func Update(w http.ResponseWriter, r *http.Request) {
54-
response := WebserviceResponse{}
55-
56-
var sharedSecret string
57-
var domain string
58-
var address string
59-
60-
vals := r.URL.Query()
61-
sharedSecret = vals["secret"][0]
62-
domain = vals["domain"][0]
63-
address = vals["addr"][0]
64-
65-
if sharedSecret != appConfig.SharedSecret {
66-
log.Println(fmt.Sprintf("Invalid shared secret: %s", sharedSecret))
67-
response.Success = false
68-
response.Message = "Invalid Credentials"
69-
json.NewEncoder(w).Encode(response)
70-
return;
71-
}
72-
73-
w.Header().Set("Content-Type", "application/json")
74-
75-
var addrType string
76-
77-
if validIP4(address) {
78-
addrType = "A"
79-
} else if validIP6(address) {
80-
addrType = "AAAA"
81-
} else {
82-
response.Success = false
83-
response.Message = fmt.Sprintf("%s is neither a valid IPv4 nor IPv6 address", address)
84-
}
85-
86-
if addrType != "" {
87-
if domain == "" {
88-
response.Success = false
89-
response.Message = fmt.Sprintf("Domain not set", address)
90-
log.Println(fmt.Sprintf("Domain not set"))
91-
return;
92-
}
30+
response := BuildWebserviceResponseFromRequest(r, appConfig)
9331

94-
result := UpdateRecord(domain, address, addrType)
32+
if response.Success {
33+
result := UpdateRecord(response.Domain, response.Address, response.AddrType)
9534

9635
if result == "" {
9736
response.Success = true
98-
response.Message = fmt.Sprintf("Updated %s record for %s to IP address %s", addrType, domain, address)
37+
response.Message = fmt.Sprintf("Updated %s record for %s to IP address %s", response.AddrType, response.Domain, response.Address)
9938
} else {
10039
response.Success = false
10140
response.Message = result

rest-api/request_handler.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"fmt"
6+
"net/http"
7+
8+
"dyndns/ipparser"
9+
)
10+
11+
type WebserviceResponse struct {
12+
Success bool
13+
Message string
14+
Domain string
15+
Address string
16+
AddrType string
17+
}
18+
19+
func BuildWebserviceResponseFromRequest(r *http.Request, appConfig *Config) WebserviceResponse {
20+
response := WebserviceResponse{}
21+
22+
var sharedSecret string
23+
24+
vals := r.URL.Query()
25+
sharedSecret = vals.Get("secret")
26+
response.Domain = vals.Get("domain")
27+
response.Address = vals.Get("addr")
28+
29+
if sharedSecret != appConfig.SharedSecret {
30+
log.Println(fmt.Sprintf("Invalid shared secret: %s", sharedSecret))
31+
response.Success = false
32+
response.Message = "Invalid Credentials"
33+
return response
34+
}
35+
36+
if response.Domain == "" {
37+
response.Success = false
38+
response.Message = fmt.Sprintf("Domain not set")
39+
log.Println("Domain not set")
40+
return response
41+
}
42+
43+
if ipparser.ValidIP4(response.Address) {
44+
response.AddrType = "A"
45+
} else if ipparser.ValidIP6(response.Address) {
46+
response.AddrType = "AAAA"
47+
} else {
48+
response.Success = false
49+
response.Message = fmt.Sprintf("%s is neither a valid IPv4 nor IPv6 address", response.Address)
50+
log.Println(fmt.Sprintf("Invalid address: %s", response.Address))
51+
return response
52+
}
53+
54+
response.Success = true
55+
56+
return response
57+
}

rest-api/request_handler_test.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
"net/http"
6+
)
7+
8+
func TestBuildWebserviceResponseFromRequestToReturnValidObject(t *testing.T) {
9+
var appConfig = &Config{}
10+
appConfig.SharedSecret = "changeme"
11+
12+
req, _ := http.NewRequest("POST", "/update?secret=changeme&domain=foo&addr=1.2.3.4", nil)
13+
result := BuildWebserviceResponseFromRequest(req, appConfig)
14+
15+
if result.Success != true {
16+
t.Fatalf("Expected WebserviceResponse.Success to be true")
17+
}
18+
19+
if result.Domain != "foo" {
20+
t.Fatalf("Expected WebserviceResponse.Domain to be foo")
21+
}
22+
23+
if result.Address != "1.2.3.4" {
24+
t.Fatalf("Expected WebserviceResponse.Address to be 1.2.3.4")
25+
}
26+
27+
if result.AddrType != "A" {
28+
t.Fatalf("Expected WebserviceResponse.AddrType to be A")
29+
}
30+
}
31+
32+
func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoSecretIsGiven(t *testing.T) {
33+
var appConfig = &Config{}
34+
appConfig.SharedSecret = "changeme"
35+
36+
req, _ := http.NewRequest("POST", "/update", nil)
37+
result := BuildWebserviceResponseFromRequest(req, appConfig)
38+
39+
if result.Success != false {
40+
t.Fatalf("Expected WebserviceResponse.Success to be false")
41+
}
42+
}
43+
44+
func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenInvalidSecretIsGiven(t *testing.T) {
45+
var appConfig = &Config{}
46+
appConfig.SharedSecret = "changeme"
47+
48+
req, _ := http.NewRequest("POST", "/update?secret=foo", nil)
49+
result := BuildWebserviceResponseFromRequest(req, appConfig)
50+
51+
if result.Success != false {
52+
t.Fatalf("Expected WebserviceResponse.Success to be false")
53+
}
54+
}
55+
56+
func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoDomainIsGiven(t *testing.T) {
57+
var appConfig = &Config{}
58+
appConfig.SharedSecret = "changeme"
59+
60+
req, _ := http.NewRequest("POST", "/update?secret=changeme", nil)
61+
result := BuildWebserviceResponseFromRequest(req, appConfig)
62+
63+
if result.Success != false {
64+
t.Fatalf("Expected WebserviceResponse.Success to be false")
65+
}
66+
}
67+
68+
func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenNoAddressIsGiven(t *testing.T) {
69+
var appConfig = &Config{}
70+
appConfig.SharedSecret = "changeme"
71+
72+
req, _ := http.NewRequest("POST", "/update?secret=changeme&domain=foo", nil)
73+
result := BuildWebserviceResponseFromRequest(req, appConfig)
74+
75+
if result.Success != false {
76+
t.Fatalf("Expected WebserviceResponse.Success to be false")
77+
}
78+
}
79+
80+
func TestBuildWebserviceResponseFromRequestToReturnInvalidObjectWhenInvalidAddressIsGiven(t *testing.T) {
81+
var appConfig = &Config{}
82+
appConfig.SharedSecret = "changeme"
83+
84+
req, _ := http.NewRequest("POST", "/update?secret=changeme&domain=foo&addr=1.41:2", nil)
85+
result := BuildWebserviceResponseFromRequest(req, appConfig)
86+
87+
if result.Success != false {
88+
t.Fatalf("Expected WebserviceResponse.Success to be false")
89+
}
90+
}

0 commit comments

Comments
 (0)