From 66ab47c6a52137dce3202596d4e858aebdde56e0 Mon Sep 17 00:00:00 2001 From: Niall Fitzpatrick <18366490+Niallfitzy1@users.noreply.github.com> Date: Tue, 5 Sep 2023 01:45:16 +0100 Subject: [PATCH 1/2] fix: correctly format record names to ensure matching coordinates --- _test/test.go | 52 ++++++++--- go.mod | 4 +- go.sum | 5 +- provider.go | 244 ++++++++++++++++---------------------------------- types.go | 20 ++--- 5 files changed, 133 insertions(+), 192 deletions(-) diff --git a/_test/test.go b/_test/test.go index 87e5b50..c3ba935 100644 --- a/_test/test.go +++ b/_test/test.go @@ -9,7 +9,7 @@ import ( "github.com/joho/godotenv" "github.com/libdns/libdns" - porkbun "github.com/libdns/porkbun" + "github.com/libdns/porkbun" ) func main() { @@ -36,17 +36,17 @@ func main() { _, err := provider.CheckCredentials(context.TODO()) if err != nil { - log.Fatalln("Credential check failed: %s\n", err.Error()) + log.Fatalf("Credential check failed: %s\n", err.Error()) } //Get records - records, err := provider.GetRecords(context.TODO(), zone) + initialRecords, err := provider.GetRecords(context.TODO(), zone) if err != nil { - log.Fatalln("Failed to fetch records: %s\n", err.Error()) + log.Fatalf("Failed to fetch records: %s\n", err.Error()) } log.Println("Records fetched:") - for _, record := range records { + for _, record := range initialRecords { fmt.Printf("%s (.%s): %s, %s\n", record.Name, zone, record.Value, record.Type) } @@ -58,7 +58,7 @@ func main() { //Create record appendedRecords, err := provider.AppendRecords(context.TODO(), zone, []libdns.Record{ - libdns.Record{ + { Type: recordType, Name: testFullName, TTL: ttl, @@ -67,13 +67,24 @@ func main() { }) if err != nil { - log.Fatalln("ERROR: %s\n", err.Error()) + log.Fatalf("ERROR: %s\n", err.Error()) + } + + //Get records + postCreatedRecords, err := provider.GetRecords(context.TODO(), zone) + if err != nil { + log.Fatalf("Failed to fetch records: %s\n", err.Error()) } + + if len(postCreatedRecords) != len(initialRecords)+1 { + log.Fatalln("Additional record not created") + } + fmt.Printf("Created record: \n%v\n", appendedRecords[0]) // Update record updatedRecords, err := provider.SetRecords(context.TODO(), zone, []libdns.Record{ - libdns.Record{ + { Type: recordType, Name: testFullName, TTL: ttl, @@ -82,13 +93,23 @@ func main() { }) if err != nil { - log.Fatalln("ERROR: %s\n", err.Error()) + log.Fatalf("ERROR: %s\n", err.Error()) } fmt.Printf("Updated record: \n%v\n", updatedRecords[0]) + //Get records + updatedRecords, err = provider.GetRecords(context.TODO(), zone) + if err != nil { + log.Fatalf("Failed to fetch records: %s\n", err.Error()) + } + + if len(updatedRecords) != len(initialRecords)+1 { + log.Fatalln("Additional record created instead of updating existing") + } + // Delete record deleteRecords, err := provider.DeleteRecords(context.TODO(), zone, []libdns.Record{ - libdns.Record{ + { Type: recordType, Name: testFullName, }, @@ -97,6 +118,17 @@ func main() { if err != nil { log.Fatalln("ERROR: %s\n", err.Error()) } + + //Get records + updatedRecords, err = provider.GetRecords(context.TODO(), zone) + if err != nil { + log.Fatalf("Failed to fetch records: %s\n", err.Error()) + } + + if len(updatedRecords) != len(initialRecords) { + log.Fatalln("Additional record not cleaned up") + } + fmt.Printf("Deleted record: \n%v\n", deleteRecords[0]) } diff --git a/go.mod b/go.mod index 6d4a848..e5134bc 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/libdns/porkbun -go 1.18 +go 1.20 require github.com/libdns/libdns v0.2.1 -require github.com/joho/godotenv v1.4.0 // indirect +require github.com/joho/godotenv v1.5.1 // indirect diff --git a/go.sum b/go.sum index 8787b15..510ca77 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ -github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= -github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/joho/godotenv v1.5.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= diff --git a/provider.go b/provider.go index 9c6b35f..8098e9d 100644 --- a/provider.go +++ b/provider.go @@ -8,9 +8,10 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "log" "net/http" + "net/url" "strconv" "strings" "time" @@ -41,122 +42,66 @@ func trimZone(zone string) string { return strings.TrimSuffix(zone, ".") } -func (p *Provider) CheckCredentials(ctx context.Context) (string, error) { - client := http.Client{} - +func (p *Provider) CheckCredentials(_ context.Context) (string, error) { credentialJson, err := json.Marshal(p.getCredentials()) if err != nil { - log.Fatal(err) return "", err } - req, err := http.NewRequest("POST", p.getApiHost()+"/ping", bytes.NewReader(credentialJson)) + response, err := makeHttpRequest[PingResponse](p, "ping", bytes.NewReader(credentialJson), PingResponse{}) if err != nil { return "", err } - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - return "", err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - bodyBytes, _ := ioutil.ReadAll(resp.Body) - return "", errors.New(string(bodyBytes)) - } - result, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) + if response.Status != "SUCCESS" { return "", err } - resultObj := PingResponse{} - - err = json.Unmarshal(result, &resultObj) - if err != nil { - log.Fatal(err) - return "", err - } - - if resultObj.ResponseStatus.Status != "SUCCESS" { - return "", resultObj.ResponseStatus - } - - return resultObj.YourIP, nil + return response.YourIP, nil } // GetRecords lists all the records in the zone. -func (p *Provider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) { - client := http.Client{} +func (p *Provider) GetRecords(_ context.Context, zone string) ([]libdns.Record, error) { trimmedZone := trimZone(zone) credentialJson, err := json.Marshal(p.getCredentials()) if err != nil { - log.Fatal(err) return nil, err } + response, err := makeHttpRequest[ApiRecordsResponse](p, "dns/retrieve/"+trimmedZone, bytes.NewReader(credentialJson), ApiRecordsResponse{}) - req, err := http.NewRequest("POST", p.getApiHost()+"/dns/retrieve/"+trimmedZone, bytes.NewReader(credentialJson)) if err != nil { - log.Fatal(err) return nil, err } - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - bodyBytes, _ := ioutil.ReadAll(resp.Body) - return nil, fmt.Errorf("could not get records: Zone: %s; Status: %v; Body: %s", - trimmedZone, resp.StatusCode, string(bodyBytes)) - } - result, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) - return nil, err - } - - resultObj := ApiRecordsResponse{} - - err = json.Unmarshal(result, &resultObj) - if err != nil { - log.Fatal(err) - return nil, err + if response.Status != "SUCCESS" { + return nil, errors.New(fmt.Sprintf("Invalid response status %s", response.Status)) } var records []libdns.Record - - for _, record := range resultObj.Records { + for _, record := range response.Records { ttl, err := time.ParseDuration(record.TTL + "s") if err != nil { - log.Fatal(err) return nil, err } priority, _ := strconv.Atoi(record.Prio) - records = append(records, libdns.Record{ + formatted := libdns.Record{ ID: record.ID, - Name: record.Name, + Name: record.Name + ".", Priority: priority, TTL: ttl, Type: record.Type, Value: record.Content, - }) + } + records = append(records, formatted) } return records, nil } // AppendRecords adds records to the zone. It returns the records that were added. -func (p *Provider) AppendRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) { - client := http.Client{} - +func (p *Provider) AppendRecords(_ context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) { credentials := p.getCredentials() trimmedZone := trimZone(zone) @@ -168,47 +113,22 @@ func (p *Provider) AppendRecords(ctx context.Context, zone string, records []lib } ttlInSeconds := int(record.TTL / time.Second) trimmedName := libdns.RelativeName(record.Name, zone) + reqBody := RecordCreateRequest{&credentials, record.Value, trimmedName, strconv.Itoa(ttlInSeconds), record.Type} reqJson, err := json.Marshal(reqBody) if err != nil { - log.Fatal(err) - return nil, err - } - req, err := http.NewRequest("POST", fmt.Sprintf("%s/dns/create/%s", p.getApiHost(), trimmedZone), bytes.NewReader(reqJson)) - if err != nil { - log.Fatal(err) return nil, err } - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - bodyBytes, _ := ioutil.ReadAll(resp.Body) - return nil, fmt.Errorf("could not create record:(%s) in Zone: %s; Status: %v; Body: %s", - fmt.Sprint(reqBody), trimmedZone, resp.StatusCode, string(bodyBytes)) - } + response, err := makeHttpRequest(p, fmt.Sprintf("dns/create/%s", trimmedZone), bytes.NewReader(reqJson), ResponseStatus{}) - result, err := ioutil.ReadAll(resp.Body) if err != nil { - log.Fatal(err) + print(err) return nil, err } - resultObj := ResponseStatus{} - - err = json.Unmarshal(result, &resultObj) - if err != nil { - log.Fatal(err) - return nil, err - } - - if resultObj.Status != "SUCCESS" { - log.Fatal(resultObj) - return nil, resultObj + if response.Status != "SUCCESS" { + return nil, errors.New(fmt.Sprintf("Invalid response status %s", response.Status)) } createdRecords = append(createdRecords, record) } @@ -216,10 +136,8 @@ func (p *Provider) AppendRecords(ctx context.Context, zone string, records []lib return createdRecords, nil } -// AppendRecords adds records to the zone. It returns the records that were added. -func (p *Provider) UpdateRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) { - client := http.Client{} - +// UpdateRecords adds records to the zone. It returns the records that were added. +func (p *Provider) UpdateRecords(_ context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) { credentials := p.getCredentials() trimmedZone := trimZone(zone) @@ -234,45 +152,16 @@ func (p *Provider) UpdateRecords(ctx context.Context, zone string, records []lib reqBody := RecordUpdateRequest{&credentials, record.Value, strconv.Itoa(ttlInSeconds)} reqJson, err := json.Marshal(reqBody) if err != nil { - log.Fatal(err) return nil, err } - req, err := http.NewRequest("POST", fmt.Sprintf("%s/dns/editByNameType/%s/%s/%s", p.getApiHost(), trimmedZone, record.Type, trimmedName), bytes.NewReader(reqJson)) + response, err := makeHttpRequest(p, fmt.Sprintf("dns/editByNameType/%s/%s/%s", trimmedZone, record.Type, trimmedName), bytes.NewReader(reqJson), ResponseStatus{}) if err != nil { - log.Fatal(err) return nil, err } - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - bodyBytes, _ := ioutil.ReadAll(resp.Body) - return nil, fmt.Errorf("could not update record:(%s) in Zone: %s; Status: %v; Body: %s", - fmt.Sprint(reqBody), trimmedZone, resp.StatusCode, string(bodyBytes)) - } - result, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) + if response.Status != "SUCCESS" { return nil, err } - - resultObj := ResponseStatus{} - - err = json.Unmarshal(result, &resultObj) - if err != nil { - log.Fatal(err) - return nil, err - } - - if resultObj.Status != "SUCCESS" { - log.Fatal(resultObj) - return nil, resultObj - } createdRecords = append(createdRecords, record) } @@ -284,9 +173,9 @@ func (p *Provider) UpdateRecords(ctx context.Context, zone string, records []lib func (p *Provider) SetRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) { existingRecords, err := p.GetRecords(ctx, zone) if err != nil { - log.Fatal(err) return nil, err } + existingCoordinates := NewSet() for _, r := range existingRecords { existingCoordinates.Add(p.getRecordCoordinates(r)) @@ -302,16 +191,20 @@ func (p *Provider) SetRecords(ctx context.Context, zone string, records []libdns } } - p.AppendRecords(ctx, zone, creates) - p.UpdateRecords(ctx, zone, updates) + _, err = p.AppendRecords(ctx, zone, creates) + if err != nil { + return nil, err + } + _, err = p.UpdateRecords(ctx, zone, updates) + if err != nil { + return nil, err + } return records, nil } // DeleteRecords deletes the records from the zone. It returns the records that were deleted. -func (p *Provider) DeleteRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) { - client := http.Client{} - +func (p *Provider) DeleteRecords(_ context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) { credentials := p.getCredentials() trimmedZone := trimZone(zone) @@ -320,46 +213,63 @@ func (p *Provider) DeleteRecords(ctx context.Context, zone string, records []lib for _, record := range records { reqJson, err := json.Marshal(credentials) if err != nil { - log.Fatal(err) return nil, err } trimmedName := libdns.RelativeName(record.Name, zone) - req, err := http.NewRequest("POST", fmt.Sprintf("%s/dns/deleteByNameType/%s/%s/%s", p.getApiHost(), trimmedZone, record.Type, trimmedName), bytes.NewReader(reqJson)) - if err != nil { - log.Fatal(err) - return nil, err - } - resp, err := client.Do(req) + _, err = makeHttpRequest(p, fmt.Sprintf("dns/deleteByNameType/%s/%s/%s", trimmedZone, record.Type, trimmedName), bytes.NewReader(reqJson), ResponseStatus{}) if err != nil { - log.Fatal(err) return nil, err } - defer resp.Body.Close() + deletedRecords = append(deletedRecords, record) + } - if resp.StatusCode != http.StatusOK { - bodyBytes, _ := ioutil.ReadAll(resp.Body) - return nil, fmt.Errorf("could not delete record:(Type: %s Name: %s) in Zone: %s; Status: %v; Body: %s", - record.Type, record.Name, trimmedZone, resp.StatusCode, string(bodyBytes)) - } + return deletedRecords, nil +} - result, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) - return nil, err - } +func makeHttpRequest[T any](p *Provider, endpoint string, body io.Reader, responseType T) (T, error) { + client := http.Client{} - resultObj := ResponseStatus{} + fullUrl := p.getApiHost() + endpoint + u, err := url.Parse(fullUrl) + if err != nil { + return responseType, err + } + println(u.String()) - err = json.Unmarshal(result, &resultObj) + req, err := http.NewRequest("POST", u.String(), body) + if err != nil { + return responseType, err + } + resp, err := client.Do(req) + if err != nil { + return responseType, err + } + defer func(Body io.ReadCloser) { + err := Body.Close() if err != nil { - log.Fatal(err) - return nil, err + log.Fatal("Couldn't close body") } - deletedRecords = append(deletedRecords, record) + }(resp.Body) + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + err = errors.New("Invalid http response status, " + string(bodyBytes)) + return responseType, err } - return deletedRecords, nil + result, err := io.ReadAll(resp.Body) + if err != nil { + return responseType, err + } + + err = json.Unmarshal(result, &responseType) + + if err != nil { + return responseType, err + } + + return responseType, nil } // Interface guards diff --git a/types.go b/types.go index 7beacaa..8d5ce59 100644 --- a/types.go +++ b/types.go @@ -49,27 +49,25 @@ type RecordUpdateRequest struct { TTL string `json:"ttl"` } -var exists = struct{}{} - -type set struct { - m map[string]struct{} +type Set struct { + m map[string]bool } -func NewSet() *set { - s := &set{} - s.m = make(map[string]struct{}) +func NewSet() *Set { + s := &Set{} + s.m = make(map[string]bool) return s } -func (s *set) Add(value string) { - s.m[value] = exists +func (s *Set) Add(value string) { + s.m[value] = true } -func (s *set) Remove(value string) { +func (s *Set) Remove(value string) { delete(s.m, value) } -func (s *set) Contains(value string) bool { +func (s *Set) Contains(value string) bool { _, c := s.m[value] return c } From a8a4c57b003b07c105db9b60296dad01ab00602b Mon Sep 17 00:00:00 2001 From: Niall Fitzpatrick <18366490+Niallfitzy1@users.noreply.github.com> Date: Tue, 5 Sep 2023 01:56:05 +0100 Subject: [PATCH 2/2] chore: go mod tidy --- _test/{test.go => integration_test.go} | 0 go.mod | 2 -- go.sum | 3 --- 3 files changed, 5 deletions(-) rename _test/{test.go => integration_test.go} (100%) diff --git a/_test/test.go b/_test/integration_test.go similarity index 100% rename from _test/test.go rename to _test/integration_test.go diff --git a/go.mod b/go.mod index e5134bc..e04ba12 100644 --- a/go.mod +++ b/go.mod @@ -3,5 +3,3 @@ module github.com/libdns/porkbun go 1.20 require github.com/libdns/libdns v0.2.1 - -require github.com/joho/godotenv v1.5.1 // indirect diff --git a/go.sum b/go.sum index 510ca77..ba9d0cf 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,2 @@ -github.com/joho/godotenv v1.5.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=