7 changed files with 391 additions and 182 deletions
@ -1,42 +1,43 @@
|
||||
#!/bin/bash -e |
||||
|
||||
VERSION="0.0.1" |
||||
VERSION="0.1.0" |
||||
|
||||
rm -rf dockerbuild || true |
||||
mkdir dockerbuild |
||||
|
||||
#cp Dockerfile dockerbuild/Dockerfile-amd64 |
||||
cp Dockerfile dockerbuild/Dockerfile-amd64 |
||||
cp Dockerfile dockerbuild/Dockerfile-arm |
||||
#cp Dockerfile dockerbuild/Dockerfile-arm64 |
||||
|
||||
#sed -E 's|FROM alpine|FROM amd64/alpine|' -i dockerbuild/Dockerfile-amd64 |
||||
sed -E 's|FROM alpine|FROM amd64/alpine|' -i dockerbuild/Dockerfile-amd64 |
||||
sed -E 's|FROM alpine|FROM arm32v7/alpine|' -i dockerbuild/Dockerfile-arm |
||||
#sed -E 's|FROM alpine|FROM arm64v8/alpine|' -i dockerbuild/Dockerfile-arm64 |
||||
|
||||
#sed -E 's/GOARCH=/GOARCH=amd64/' -i dockerbuild/Dockerfile-amd64 |
||||
sed -E 's/GOARCH=/GOARCH=amd64/' -i dockerbuild/Dockerfile-amd64 |
||||
sed -E 's/GOARCH=/GOARCH=arm/' -i dockerbuild/Dockerfile-arm |
||||
#sed -E 's/GOARCH=/GOARCH=arm64/' -i dockerbuild/Dockerfile-arm64 |
||||
|
||||
#docker build -f dockerbuild/Dockerfile-amd64 -t sequentialread/gandi-dns-updater:$VERSION-amd64 . |
||||
docker build -f dockerbuild/Dockerfile-arm -t sequentialread/gandi-dns-updater:$VERSION-arm . |
||||
#docker build -f dockerbuild/Dockerfile-arm64 -t sequentialread/gandi-dns-updater:$VERSION-arm64 . |
||||
docker build -f dockerbuild/Dockerfile-amd64 -t sequentialread/dns-updater:$VERSION-amd64 . |
||||
docker build -f dockerbuild/Dockerfile-arm -t sequentialread/dns-updater:$VERSION-arm . |
||||
#docker build -f dockerbuild/Dockerfile-arm64 -t sequentialread/dns-updater:$VERSION-arm64 . |
||||
|
||||
#docker push sequentialread/gandi-dns-updater:$VERSION-amd64 |
||||
docker push sequentialread/gandi-dns-updater:$VERSION-arm |
||||
#docker push sequentialread/gandi-dns-updater:$VERSION-arm64 |
||||
docker push sequentialread/dns-updater:$VERSION-amd64 |
||||
docker push sequentialread/dns-updater:$VERSION-arm |
||||
#docker push sequentialread/dns-updater:$VERSION-arm64 |
||||
|
||||
export DOCKER_CLI_EXPERIMENTAL=enabled |
||||
|
||||
# docker manifest create sequentialread/gandi-dns-updater:$VERSION \ |
||||
# sequentialread/gandi-dns-updater:$VERSION-amd64 \ |
||||
# sequentialread/gandi-dns-updater:$VERSION-arm \ |
||||
# sequentialread/gandi-dns-updater:$VERSION-arm64 |
||||
# docker manifest create sequentialread/dns-updater:$VERSION \ |
||||
# sequentialread/dns-updater:$VERSION-amd64 \ |
||||
# sequentialread/dns-updater:$VERSION-arm \ |
||||
# sequentialread/dns-updater:$VERSION-arm64 |
||||
|
||||
docker manifest create sequentialread/gandi-dns-updater:$VERSION \ |
||||
sequentialread/gandi-dns-updater:$VERSION-arm \ |
||||
docker manifest create sequentialread/dns-updater:$VERSION \ |
||||
sequentialread/dns-updater:$VERSION-arm \ |
||||
sequentialread/dns-updater:$VERSION-amd64 |
||||
|
||||
#docker manifest annotate --arch amd64 sequentialread/gandi-dns-updater:$VERSION sequentialread/gandi-dns-updater:$VERSION-amd64 |
||||
docker manifest annotate --arch arm sequentialread/gandi-dns-updater:$VERSION sequentialread/gandi-dns-updater:$VERSION-arm |
||||
#docker manifest annotate --arch arm64 sequentialread/gandi-dns-updater:$VERSION sequentialread/gandi-dns-updater:$VERSION-arm64 |
||||
docker manifest annotate --arch amd64 sequentialread/dns-updater:$VERSION sequentialread/dns-updater:$VERSION-amd64 |
||||
docker manifest annotate --arch arm sequentialread/dns-updater:$VERSION sequentialread/dns-updater:$VERSION-arm |
||||
#docker manifest annotate --arch arm64 sequentialread/dns-updater:$VERSION sequentialread/dns-updater:$VERSION-arm64 |
||||
|
||||
docker manifest push sequentialread/gandi-dns-updater:$VERSION |
||||
docker manifest push sequentialread/dns-updater:$VERSION |
@ -1,5 +1,4 @@
|
||||
{ |
||||
"GandiRecordTTLSeconds": 300, |
||||
"HealthPollingSeconds": 10, |
||||
"ResetAfterConsecutiveFailures": 10 |
||||
} |
@ -0,0 +1,185 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"log" |
||||
"net/http" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
|
||||
errors "git.sequentialread.com/forest/pkg-errors" |
||||
) |
||||
|
||||
// https://porkbun.com/api/json/v3/documentation
|
||||
|
||||
type PorkbunService struct { |
||||
Client *http.Client |
||||
APIUrl string |
||||
RecordTTLSeconds int |
||||
Auth PorkbunAuth |
||||
} |
||||
|
||||
type PorkbunAuth struct { |
||||
KeyId string `json:"apikey"` |
||||
SecretKey string `json:"secretapikey"` |
||||
} |
||||
|
||||
type PorkbunRecordsResponse struct { |
||||
Records []PorkbunRecord `json:"records"` |
||||
} |
||||
|
||||
type PorkbunStatusResponse struct { |
||||
Status string `json:"status"` |
||||
} |
||||
|
||||
type PorkbunRecord struct { |
||||
Id string `json:"id"` |
||||
Name string `json:"name"` |
||||
Type string `json:"type"` |
||||
Content string `json:"content"` |
||||
TTL string `json:"ttl"` |
||||
Priority string `json:"prio"` |
||||
Notes string `json:"notes"` |
||||
} |
||||
|
||||
type PorkbunUpdate struct { |
||||
KeyId string `json:"apikey"` |
||||
SecretKey string `json:"secretapikey"` |
||||
Name string `json:"name"` |
||||
Type string `json:"type"` |
||||
Content string `json:"content"` |
||||
TTL string `json:"ttl"` |
||||
Priority string `json:"prio"` |
||||
} |
||||
|
||||
func NewPorkbunService(keyId, secretKey string, recordTTLSeconds int) *PorkbunService { |
||||
return &PorkbunService{ |
||||
Client: &http.Client{Timeout: 30 * time.Second}, |
||||
APIUrl: "https://porkbun.com/api/json/v3", |
||||
Auth: PorkbunAuth{ |
||||
KeyId: keyId, |
||||
SecretKey: secretKey, |
||||
}, |
||||
RecordTTLSeconds: recordTTLSeconds, |
||||
} |
||||
} |
||||
|
||||
func (service *PorkbunService) TryToUpdateRecordsForDomain(domain DomainConfig, currentPublicIPv4 string) bool { |
||||
path := fmt.Sprintf("/dns/retrieve/%s", domain.Domain) |
||||
authJsonBytes, _ := json.Marshal(service.Auth) |
||||
statusCode, responseBytes, err := service.PorkbunHTTP("POST", path, bytes.NewBuffer(authJsonBytes)) |
||||
if err != nil { |
||||
// if porkbun isn't responding at all, we can just give up and try again in 10 seconds.
|
||||
log.Println("waiting for porkbun to respond...") |
||||
return false |
||||
} |
||||
if statusCode >= 300 { |
||||
// if we got a non-200 status code from gandi, thats a fatal error. Log it.
|
||||
log.Printf("porkbun (GET %s) returned http %d: \n%s\n\n", path, statusCode, responseBytes) |
||||
return false |
||||
} |
||||
var responseObj PorkbunRecordsResponse |
||||
err = json.Unmarshal(responseBytes, &responseObj) |
||||
if err != nil { |
||||
log.Printf("porkbun (GET %s) returned http %d, but it didn't return valid json: %s: \n%s\n\n", path, statusCode, err, responseBytes) |
||||
return false |
||||
} |
||||
|
||||
configIndexesToAdd := map[int]bool{} |
||||
for i, _ := range domain.Records { |
||||
configIndexesToAdd[i] = true |
||||
} |
||||
for _, porkbunRecord := range responseObj.Records { |
||||
for i, configRecord := range domain.Records { |
||||
if configRecord.FQDN(domain.Domain) == porkbunRecord.Name && recordTypesMatch(configRecord.Type, porkbunRecord.Type) { |
||||
configIndexesToAdd[i] = false |
||||
if porkbunRecord.Content != currentPublicIPv4 { |
||||
updateJsonBytes, _ := json.Marshal(PorkbunUpdate{ |
||||
KeyId: service.Auth.KeyId, |
||||
SecretKey: service.Auth.SecretKey, |
||||
Name: strings.ReplaceAll(configRecord.Name, "@", ""), |
||||
Type: configRecord.Type, |
||||
TTL: strconv.Itoa(service.RecordTTLSeconds), |
||||
Content: currentPublicIPv4, |
||||
}) |
||||
path := fmt.Sprintf("/dns/edit/%s/%s", domain.Domain, porkbunRecord.Id) |
||||
log.Printf("porkbun POST %s\n", path) |
||||
statusCode, responseBytes, err := service.PorkbunHTTP("POST", path, bytes.NewBuffer(updateJsonBytes)) |
||||
if err != nil { |
||||
log.Println("waiting for porkbun to respond...") |
||||
return false |
||||
} |
||||
var responseObj PorkbunStatusResponse |
||||
err = json.Unmarshal(responseBytes, &responseObj) |
||||
if err != nil { |
||||
log.Printf("porkbun (POST %s) returned http %d, but it didn't return valid json: %s: \n%s\n\n", path, statusCode, err, responseBytes) |
||||
return false |
||||
} |
||||
if responseObj.Status != "SUCCESS" { |
||||
log.Printf("porkbun (POST %s) returned http %d: \n%s\n\n", path, statusCode, responseBytes) |
||||
return false |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
for i, configRecord := range domain.Records { |
||||
if configIndexesToAdd[i] { |
||||
createJsonBytes, _ := json.Marshal(PorkbunUpdate{ |
||||
KeyId: service.Auth.KeyId, |
||||
SecretKey: service.Auth.SecretKey, |
||||
Name: strings.ReplaceAll(configRecord.Name, "@", ""), |
||||
Type: configRecord.Type, |
||||
TTL: strconv.Itoa(service.RecordTTLSeconds), |
||||
Content: currentPublicIPv4, |
||||
}) |
||||
path := fmt.Sprintf("/dns/create/%s", domain.Domain) |
||||
log.Printf("porkbun POST %s\n", path) |
||||
statusCode, responseBytes, err := service.PorkbunHTTP("POST", path, bytes.NewBuffer(createJsonBytes)) |
||||
if err != nil { |
||||
log.Println("waiting for porkbun to respond...") |
||||
return false |
||||
} |
||||
var responseObj PorkbunStatusResponse |
||||
err = json.Unmarshal(responseBytes, &responseObj) |
||||
if err != nil { |
||||
log.Printf("porkbun (POST %s) returned http %d, but it didn't return valid json: %s: \n%s\n\n", path, statusCode, err, responseBytes) |
||||
return false |
||||
} |
||||
if responseObj.Status != "SUCCESS" { |
||||
log.Printf("porkbun (POST %s) returned http %d: \n%s\n\n", path, statusCode, responseBytes) |
||||
return false |
||||
} |
||||
} |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
func (service *PorkbunService) PorkbunHTTP( |
||||
method string, |
||||
url string, |
||||
body io.Reader, |
||||
) (int, []byte, error) { |
||||
|
||||
request, err := http.NewRequest(method, url, body) |
||||
if err != nil { |
||||
return 0, nil, errors.Wrapf(err, "failed to create HTTP request calling %s %s", method, url) |
||||
} |
||||
response, err := service.Client.Do(request) |
||||
if err != nil { |
||||
return 0, nil, errors.Wrapf(err, "HTTP request error when calling %s %s", method, url) |
||||
} |
||||
bytes, err := ioutil.ReadAll(response.Body) |
||||
if err != nil { |
||||
return response.StatusCode, nil, errors.Wrapf(err, "HTTP read error when calling %s %s ", method, url) |
||||
} |
||||
|
||||
return response.StatusCode, bytes, err |
||||
} |
Loading…
Reference in new issue