Skip to content

Commit

Permalink
refactor: query porkbun record before create
Browse files Browse the repository at this point in the history
  • Loading branch information
davidramiro committed Feb 23, 2023
1 parent 11cde81 commit 9701368
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 62 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/davidramiro/frigabun
go 1.20

require (
github.com/go-faker/faker/v4 v4.0.0
github.com/rs/zerolog v1.29.0
gopkg.in/yaml.v3 v3.0.1
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-faker/faker/v4 v4.0.0 h1:tfgFaeizVlYGOS1tVo/vcWcKhkNgG1NWm8ibRG0f+aQ=
github.com/go-faker/faker/v4 v4.0.0/go.mod h1:uuNc0PSRxF8nMgjGrrrU4Nw5cF30Jc6Kd0/FUTTYbhg=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
Expand Down
8 changes: 4 additions & 4 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,25 @@ func HandleUpdateRequest(c echo.Context) error {

for i := range subdomains {
if request.Registrar == "gandi" {
dnsInfo := &gandi.GandiDnsInfo{
dns := &gandi.GandiDns{
IP: request.IP,
Domain: request.Domain,
Subdomain: subdomains[i],
ApiKey: request.ApiKey,
}
err := dnsInfo.AddRecord()
err := dns.SaveRecord()
if err != nil {
return c.String(err.Code, err.Message)
}
} else if request.Registrar == "porkbun" {
dnsInfo := &porkbun.PorkbunDnsInfo{
dns := &porkbun.PorkbunDns{
IP: request.IP,
Domain: request.Domain,
Subdomain: subdomains[i],
ApiKey: request.ApiKey,
SecretApiKey: request.ApiSecretKey,
}
err := dnsInfo.AddRecord()
err := dns.AddRecord()
if err != nil {
return c.String(err.Code, err.Message)
}
Expand Down
5 changes: 3 additions & 2 deletions internal/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"

"github.com/davidramiro/frigabun/internal/config"
"github.com/go-faker/faker/v4"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -62,7 +63,7 @@ func TestInvalidDomain(t *testing.T) {

func TestGandiUpdateWithValidRequest(t *testing.T) {
q := make(url.Values)
q.Set("ip", config.AppConfig.Test.Gandi.IP)
q.Set("ip", faker.IPv4())
q.Set("domain", config.AppConfig.Test.Gandi.Domain)
q.Set("subdomain", config.AppConfig.Test.Gandi.Subdomain+"2")
q.Set("apiKey", config.AppConfig.Test.Gandi.ApiKey)
Expand All @@ -83,7 +84,7 @@ func TestGandiUpdateWithValidRequest(t *testing.T) {

func TestPorkbunUpdateWithValidRequest(t *testing.T) {
q := make(url.Values)
q.Set("ip", config.AppConfig.Test.Porkbun.IP)
q.Set("ip", faker.IPv4())
q.Set("domain", config.AppConfig.Test.Porkbun.Domain)
q.Set("subdomain", config.AppConfig.Test.Porkbun.Subdomain+"2")
q.Set("apikey", config.AppConfig.Test.Porkbun.ApiKey)
Expand Down
5 changes: 4 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import (
func main() {
logger.InitLog()

config.InitConfig()
err := config.InitConfig()
if err != nil {
logger.Log.Fatal().Err(err).Msg("error initializing config")
}

initEcho()
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/gandi/gandi.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/davidramiro/frigabun/internal/logger"
)

type GandiDnsInfo struct {
type GandiDns struct {
IP string
Domain string
Subdomain string
Expand All @@ -30,7 +30,7 @@ type GandiUpdateError struct {
Message string
}

func (g *GandiDnsInfo) AddRecord() *GandiUpdateError {
func (g *GandiDns) SaveRecord() *GandiUpdateError {

gandiRequest := &GandiApiRequest{
Subdomain: g.Subdomain,
Expand Down
20 changes: 10 additions & 10 deletions pkg/gandi/gandi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,41 +24,41 @@ func init() {
}

func TestUpdateEndpointWithValidRequest(t *testing.T) {
testDnsInfo := &GandiDnsInfo{
testDnsInfo := &GandiDns{
IP: config.AppConfig.Test.Gandi.IP,
Domain: config.AppConfig.Test.Gandi.Domain,
Subdomain: config.AppConfig.Test.Gandi.Subdomain,
ApiKey: config.AppConfig.Test.Gandi.ApiKey,
}

err := testDnsInfo.AddRecord()
err := testDnsInfo.SaveRecord()

assert.Nil(t, err)
}

func TestUpdateEndpointWithInvalidIp(t *testing.T) {
testDnsInfo := &GandiDnsInfo{
testDnsInfo := &GandiDns{
IP: "::1",
Domain: config.AppConfig.Test.Gandi.Domain,
Subdomain: config.AppConfig.Test.Gandi.Subdomain,
ApiKey: config.AppConfig.Test.Gandi.ApiKey,
}

err := testDnsInfo.AddRecord()
err := testDnsInfo.SaveRecord()

assert.NotNil(t, err)
assert.Equal(t, http.StatusBadRequest, err.Code)
assert.Contains(t, err.Message, "IPv4")
}

func TestUpdateEndpointWithMissingParam(t *testing.T) {
testDnsInfo := &GandiDnsInfo{
testDnsInfo := &GandiDns{
Domain: config.AppConfig.Test.Gandi.Domain,
Subdomain: config.AppConfig.Test.Gandi.Subdomain,
ApiKey: config.AppConfig.Test.Gandi.ApiKey,
}

err := testDnsInfo.AddRecord()
err := testDnsInfo.SaveRecord()

assert.NotNil(t, err)
assert.Equal(t, http.StatusBadRequest, err.Code)
Expand All @@ -67,13 +67,13 @@ func TestUpdateEndpointWithMissingParam(t *testing.T) {
}

func TestUpdateEndpointWithMissingAuth(t *testing.T) {
testDnsInfo := &GandiDnsInfo{
testDnsInfo := &GandiDns{
IP: config.AppConfig.Test.Gandi.IP,
Domain: config.AppConfig.Test.Gandi.Domain,
Subdomain: config.AppConfig.Test.Gandi.Subdomain,
}

err := testDnsInfo.AddRecord()
err := testDnsInfo.SaveRecord()

assert.NotNil(t, err)
assert.Equal(t, http.StatusForbidden, err.Code)
Expand All @@ -82,14 +82,14 @@ func TestUpdateEndpointWithMissingAuth(t *testing.T) {
}

func TestUpdateEndpointWithInvalidDomain(t *testing.T) {
testDnsInfo := &GandiDnsInfo{
testDnsInfo := &GandiDns{
Domain: "example.com",
IP: config.AppConfig.Test.Gandi.IP,
Subdomain: config.AppConfig.Test.Gandi.Subdomain,
ApiKey: config.AppConfig.Test.Gandi.ApiKey,
}

err := testDnsInfo.AddRecord()
err := testDnsInfo.SaveRecord()

assert.NotNil(t, err)
assert.Equal(t, http.StatusNotFound, err.Code)
Expand Down
89 changes: 66 additions & 23 deletions pkg/porkbun/porkbun.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import (
"fmt"
"io"
"net/http"
"time"

"github.com/davidramiro/frigabun/internal/config"
"github.com/davidramiro/frigabun/internal/logger"
)

type PorkbunDnsInfo struct {
type PorkbunDns struct {
IP string
Domain string
Subdomain string
Expand All @@ -29,12 +28,19 @@ type PorkbunApiRequest struct {
SecretApiKey string `json:"secretapikey"`
}

type PorkbunQueryResponse struct {
Status string `json:"status"`
Records []struct {
Name string `json:"name"`
} `json:"records"`
}

type PorkbunUpdateError struct {
Code int
Message string
}

func (p *PorkbunDnsInfo) AddRecord() *PorkbunUpdateError {
func (p *PorkbunDns) AddRecord() *PorkbunUpdateError {

porkbunRequest := &PorkbunApiRequest{
Subdomain: p.Subdomain,
Expand All @@ -45,45 +51,83 @@ func (p *PorkbunDnsInfo) AddRecord() *PorkbunUpdateError {
SecretApiKey: p.SecretApiKey,
}

deleteErr := deleteOldRecord(p, porkbunRequest)
if deleteErr != nil {
logger.Log.Warn().Msg("deleting old porkbun request failed")
queryErr := porkbunRequest.queryRecord(p)

if queryErr != nil && queryErr.Code == 409 {

logger.Log.Info().Msg("record exists, updating")
updateErr := porkbunRequest.updateRecord(p)

if updateErr != nil {
logger.Log.Error().Str("err", updateErr.Message).Msg("porkbun rejected updated record")
return &PorkbunUpdateError{Code: 400, Message: updateErr.Message}
}

} else {
createErr := porkbunRequest.createRecord(p)
if createErr != nil {
logger.Log.Error().Str("err", createErr.Message).Msg("porkbun rejected new record")
return &PorkbunUpdateError{Code: 400, Message: createErr.Message}
}
}

time.Sleep(2 * time.Second)
return nil
}

func (p *PorkbunApiRequest) queryRecord(dns *PorkbunDns) *PorkbunUpdateError {
endpoint := fmt.Sprintf("%s/dns/retrieveByNameType/%s/A/%s", config.AppConfig.Porkbun.BaseUrl, dns.Domain, dns.Subdomain)

postErr := porkbunRequest.postNewRecord(p)
if postErr != nil {
logger.Log.Error().Str("err", postErr.Message).Msg("porkbun rejected new record")
return &PorkbunUpdateError{Code: 400, Message: postErr.Message}
logger.Log.Info().Str("subdomain", p.Subdomain).Str("endpoint", endpoint).Str("IP", p.IP).Msg("checking if record exists")

var r PorkbunQueryResponse

resp, updErr := executeRequest(endpoint, p)
if updErr != nil {
return updErr
}

time.Sleep(2 * time.Second)
b, _ := io.ReadAll(resp.Body)
err := json.Unmarshal(b, &r)

if resp.StatusCode != http.StatusOK || r.Status != "SUCCESS" || err != nil {

logger.Log.Error().Msg("could not query record:" + string(b))
return &PorkbunUpdateError{400, "could not query record: " + string(b)}
}

if r.Status == "SUCCESS" && len(r.Records) > 0 {
for _, e := range r.Records {
if e.Name == dns.Subdomain+"."+dns.Domain {
return &PorkbunUpdateError{409, "record already exists"}
}
}
}

return nil
}

func deleteOldRecord(dnsInfo *PorkbunDnsInfo, porkbunRequest *PorkbunApiRequest) *PorkbunUpdateError {
endpoint := fmt.Sprintf("%s/dns/deleteByNameType/%s/A/%s", config.AppConfig.Porkbun.BaseUrl, dnsInfo.Domain, dnsInfo.Subdomain)
func (p *PorkbunApiRequest) createRecord(dns *PorkbunDns) *PorkbunUpdateError {
endpoint := fmt.Sprintf("%s/dns/create/%s", config.AppConfig.Porkbun.BaseUrl, dns.Domain)

logger.Log.Info().Str("subdomain", porkbunRequest.Subdomain).Str("endpoint", endpoint).Str("IP", porkbunRequest.IP).Msg("deleting old record")
logger.Log.Info().Str("subdomain", p.Subdomain).Str("endpoint", endpoint).Str("IP", p.IP).Msg("creating new record")

resp, err := executeRequest(endpoint, porkbunRequest)
resp, err := executeRequest(endpoint, p)
if err != nil {
return err
}

if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusBadRequest {
return &PorkbunUpdateError{400, "could not delete old record"}
if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
return &PorkbunUpdateError{400, "could not create record: " + string(b)}
}

return nil
}

func (p *PorkbunApiRequest) postNewRecord(dnsInfo *PorkbunDnsInfo) *PorkbunUpdateError {
endpoint := fmt.Sprintf("%s/dns/create/%s", config.AppConfig.Porkbun.BaseUrl, dnsInfo.Domain)
func (p *PorkbunApiRequest) updateRecord(dns *PorkbunDns) *PorkbunUpdateError {
endpoint := fmt.Sprintf("%s/dns/editByNameType/%s/A/%s", config.AppConfig.Porkbun.BaseUrl, dns.Domain, dns.Subdomain)

logger.Log.Info().Str("subdomain", p.Subdomain).Str("endpoint", endpoint).Str("IP", p.IP).Msg("creating new record")
logger.Log.Info().Str("subdomain", p.Subdomain).Str("endpoint", endpoint).Str("IP", p.IP).Msg("updating record")

resp, err := executeRequest(endpoint, p)
if err != nil {
Expand All @@ -92,8 +136,7 @@ func (p *PorkbunApiRequest) postNewRecord(dnsInfo *PorkbunDnsInfo) *PorkbunUpdat

if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
logger.Log.Error().Msg("porkbun rejected request")
return &PorkbunUpdateError{400, "could not create record: " + string(b)}
return &PorkbunUpdateError{400, "could not update record: " + string(b)}
}

return nil
Expand Down
Loading

0 comments on commit 9701368

Please sign in to comment.