1package gandi23import (4 "bytes"5 "context"6 "encoding/json"7 "fmt"8 "net/http"9 "strings"1011 "github.com/libdns/libdns"12)1314func (p *Provider) setRecord(ctx context.Context, zone string, rr libdns.RR, domain gandiDomain) error {15 p.mutex.Lock()16 defer p.mutex.Unlock()1718 req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", domain.DomainRecordsHref, rr.Name, rr.Type), nil)19 if err != nil {20 return err21 }2223 var oldGandiRecord gandiRecord24 status, err := p.doRequest(req, &oldGandiRecord)2526 // ignore if no record is found, then we can create one safely27 if status.Code != http.StatusNotFound && err != nil {28 return err29 }3031 // we check if the new value does not already exists and append it if not32 var exists bool33 for _, val := range oldGandiRecord.RRSetValues {34 if val == rr.Data {35 exists = true36 break37 }38 }3940 recValues := oldGandiRecord.RRSetValues41 if !exists {42 recValues = append(recValues, rr.Data)43 }4445 // we just create a new record, if an existing record was found, we just append the new value to the existing ones46 newGandiRecord := gandiRecord{47 RRSetTTL: int(rr.TTL.Seconds()),48 RRSetType: rr.Type,49 RRSetName: rr.Name,50 RRSetValues: recValues,51 }5253 raw, err := json.Marshal(newGandiRecord)54 if err != nil {55 return err56 }5758 // we update existing record or create a new record if it does not exist yet59 req, err = http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("%s/%s/%s", domain.DomainRecordsHref, rr.Name, rr.Type), bytes.NewReader(raw))60 if err != nil {61 return err62 }6364 req.Header.Set("Content-Type", "application/json")6566 _, err = p.doRequest(req, nil)6768 return err69}7071func (p *Provider) deleteRecord(ctx context.Context, zone string, rr libdns.RR, domain gandiDomain) error {72 p.mutex.Lock()73 defer p.mutex.Unlock()7475 req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", domain.DomainRecordsHref, rr.Name, rr.Type), nil)76 if err != nil {77 return err78 }7980 // check if the record exists beforehand81 var rec gandiRecord82 _, err = p.doRequest(req, &rec)83 if err != nil {84 return err85 }8687 // might contain request crafting error88 var requestErr error8990 if len(rec.RRSetValues) > 1 {91 // if it contains multiple values, the best is to update the record instead of deleting all the values92 newRRSetValues := []string{}93 for _, val := range rec.RRSetValues {94 if val != rr.Data {95 newRRSetValues = append(newRRSetValues, val)96 }97 }98 rec.RRSetValues = newRRSetValues99100 raw, err := json.Marshal(rec)101 if err != nil {102 return err103 }104105 req, requestErr = http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("%s/%s/%s", domain.DomainRecordsHref, rr.Name, rr.Type), bytes.NewReader(raw))106 } else {107 // if there is only one entry, we make sure that the value to delete is matching the one we found108 // otherwise we may delete the wrong record109 if strings.Trim(rec.RRSetValues[0], "\"") != rr.Data {110 return fmt.Errorf("LiveDNS returned a %v (%v)", http.StatusNotFound, "Can't find such a DNS value")111 }112113 req, requestErr = http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/%s/%s", domain.DomainRecordsHref, rr.Name, rr.Type), nil)114 }115116 // we check if NewRequestWithContext threw an error117 if requestErr != nil {118 return requestErr119 }120121 req.Header.Set("Content-Type", "application/json")122123 _, requestErr = p.doRequest(req, nil)124 return requestErr125}126127func (p *Provider) getDomain(ctx context.Context, zone string) (gandiDomain, error) {128 p.mutex.Lock()129 defer p.mutex.Unlock()130131 // we trim the dot at the end of the zone name to get the fqdn132 // and then use it to fetch domain information through the api133 fqdn := strings.TrimRight(zone, ".")134135 if p.domains == nil {136 p.domains = make(map[string]gandiDomain)137 }138 if domain, ok := p.domains[fqdn]; ok {139 return domain, nil140 }141142 // might contain request crafting error143 var requestErr error144145 req, requestErr := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.gandi.net/v5/livedns/domains/%s", fqdn), nil)146 if requestErr != nil {147 return gandiDomain{}, requestErr148 }149150 var domain gandiDomain151152 if _, requestErr = p.doRequest(req, &domain); requestErr != nil {153 return gandiDomain{}, requestErr154 }155156 p.domains[fqdn] = domain157158 return domain, nil159}160161func (p *Provider) doRequest(req *http.Request, result interface{}) (gandiStatus, error) {162 if p.BearerToken != "" {163 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", p.BearerToken))164 } else if p.APIToken != "" {165 req.Header.Set("Authorization", fmt.Sprintf("Apikey %s", p.APIToken))166 }167 req.Header.Set("Accept", "application/json")168169 resp, err := http.DefaultClient.Do(req)170171 if err != nil {172 return gandiStatus{}, err173 }174175 defer resp.Body.Close()176177 if resp.StatusCode >= 400 {178 var response gandiStatus179 if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {180 return gandiStatus{}, err181 }182183 return response, fmt.Errorf("LiveDNS returned a %v (%v)", response.Code, response.Message)184 }185186 // the api does not return the json object on 201 or 204, so we just stop here187 if resp.StatusCode > 200 {188 return gandiStatus{}, nil189 }190191 // if we get a 200, we parse the json object192 if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {193 return gandiStatus{}, err194 }195196 return gandiStatus{}, nil197}