mirror of
https://github.com/qdm12/ddns-updater.git
synced 2026-04-05 08:53:52 -04:00
Linode support (#144)
This commit is contained in:
@@ -31,6 +31,7 @@
|
|||||||
- Google
|
- Google
|
||||||
- He.net
|
- He.net
|
||||||
- Infomaniak
|
- Infomaniak
|
||||||
|
- Linode
|
||||||
- LuaDNS
|
- LuaDNS
|
||||||
- Namecheap
|
- Namecheap
|
||||||
- NoIP
|
- NoIP
|
||||||
@@ -138,6 +139,7 @@ Check the documentation for your DNS provider:
|
|||||||
- [Google](docs/google.md)
|
- [Google](docs/google.md)
|
||||||
- [He.net](docs/he.net.md)
|
- [He.net](docs/he.net.md)
|
||||||
- [Infomaniak](docs/infomaniak.md)
|
- [Infomaniak](docs/infomaniak.md)
|
||||||
|
- [Linode](docs/linode.md)
|
||||||
- [LuaDNS](docs/luadns.md)
|
- [LuaDNS](docs/luadns.md)
|
||||||
- [Namecheap](docs/namecheap.md)
|
- [Namecheap](docs/namecheap.md)
|
||||||
- [NoIP](docs/noip.md)
|
- [NoIP](docs/noip.md)
|
||||||
|
|||||||
34
docs/linode.md
Normal file
34
docs/linode.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Linode
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"provider": "linode",
|
||||||
|
"domain": "domain.com",
|
||||||
|
"host": "@",
|
||||||
|
"token": "token",
|
||||||
|
"ip_version": "ipv4"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compulsory parameters
|
||||||
|
|
||||||
|
- `"domain"`
|
||||||
|
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
|
||||||
|
- `"token"`
|
||||||
|
|
||||||
|
### Optional parameters
|
||||||
|
|
||||||
|
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
|
||||||
|
|
||||||
|
## Domain setup
|
||||||
|
|
||||||
|
1. Create a personal access token with `domains` set, with read and write privileges, ideally that never expires. You can refer to [@AnujRNair's comment](https://github.com/qdm12/ddns-updater/pull/144#discussion_r559292678) and to [Linode's guide](https://www.linode.com/docs/products/tools/cloud-manager/guides/cloud-api-keys).
|
||||||
|
1. The program will create the A or AAAA record for you if it doesn't exist already.
|
||||||
@@ -18,6 +18,7 @@ const (
|
|||||||
GOOGLE models.Provider = "google"
|
GOOGLE models.Provider = "google"
|
||||||
HE models.Provider = "he"
|
HE models.Provider = "he"
|
||||||
INFOMANIAK models.Provider = "infomaniak"
|
INFOMANIAK models.Provider = "infomaniak"
|
||||||
|
LINODE models.Provider = "linode"
|
||||||
LUADNS models.Provider = "luadns"
|
LUADNS models.Provider = "luadns"
|
||||||
NAMECHEAP models.Provider = "namecheap"
|
NAMECHEAP models.Provider = "namecheap"
|
||||||
NOIP models.Provider = "noip"
|
NOIP models.Provider = "noip"
|
||||||
@@ -43,6 +44,7 @@ func ProviderChoices() []models.Provider {
|
|||||||
GOOGLE,
|
GOOGLE,
|
||||||
HE,
|
HE,
|
||||||
INFOMANIAK,
|
INFOMANIAK,
|
||||||
|
LINODE,
|
||||||
LUADNS,
|
LUADNS,
|
||||||
NAMECHEAP,
|
NAMECHEAP,
|
||||||
NOIP,
|
NOIP,
|
||||||
|
|||||||
@@ -136,6 +136,8 @@ func makeSettingsFromObject(common commonSettings, rawSettings json.RawMessage,
|
|||||||
settingsConstructor = settings.NewHe
|
settingsConstructor = settings.NewHe
|
||||||
case constants.INFOMANIAK:
|
case constants.INFOMANIAK:
|
||||||
settingsConstructor = settings.NewInfomaniak
|
settingsConstructor = settings.NewInfomaniak
|
||||||
|
case constants.LINODE:
|
||||||
|
settingsConstructor = settings.NewLinode
|
||||||
case constants.LUADNS:
|
case constants.LUADNS:
|
||||||
settingsConstructor = settings.NewLuaDNS
|
settingsConstructor = settings.NewLuaDNS
|
||||||
case constants.NAMECHEAP:
|
case constants.NAMECHEAP:
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ var (
|
|||||||
// Intermediary steps errors.
|
// Intermediary steps errors.
|
||||||
var (
|
var (
|
||||||
ErrCreateRecord = errors.New("cannot create record")
|
ErrCreateRecord = errors.New("cannot create record")
|
||||||
|
ErrGetDomainID = errors.New("cannot get domain ID")
|
||||||
ErrGetRecordID = errors.New("cannot get record ID")
|
ErrGetRecordID = errors.New("cannot get record ID")
|
||||||
ErrGetRecordInZone = errors.New("cannot get record in zone") // LuaDNS
|
ErrGetRecordInZone = errors.New("cannot get record in zone") // LuaDNS
|
||||||
ErrGetZoneID = errors.New("cannot get zone ID") // LuaDNS
|
ErrGetZoneID = errors.New("cannot get zone ID") // LuaDNS
|
||||||
@@ -48,6 +49,7 @@ var (
|
|||||||
ErrBannedUserAgent = errors.New("user agend is banned")
|
ErrBannedUserAgent = errors.New("user agend is banned")
|
||||||
ErrConflictingRecord = errors.New("conflicting record")
|
ErrConflictingRecord = errors.New("conflicting record")
|
||||||
ErrDNSServerSide = errors.New("server side DNS error")
|
ErrDNSServerSide = errors.New("server side DNS error")
|
||||||
|
ErrDomainDisabled = errors.New("record disabled")
|
||||||
ErrDomainIDNotFound = errors.New("ID not found in domain record")
|
ErrDomainIDNotFound = errors.New("ID not found in domain record")
|
||||||
ErrFeatureUnavailable = errors.New("feature is not available to the user")
|
ErrFeatureUnavailable = errors.New("feature is not available to the user")
|
||||||
ErrHostnameNotExists = errors.New("hostname does not exist")
|
ErrHostnameNotExists = errors.New("hostname does not exist")
|
||||||
@@ -61,6 +63,7 @@ var (
|
|||||||
ErrPrivateIPSent = errors.New("private IP cannot be routed")
|
ErrPrivateIPSent = errors.New("private IP cannot be routed")
|
||||||
ErrRecordNotEditable = errors.New("record is not editable") // Dreamhost
|
ErrRecordNotEditable = errors.New("record is not editable") // Dreamhost
|
||||||
ErrRecordNotFound = errors.New("record not found")
|
ErrRecordNotFound = errors.New("record not found")
|
||||||
|
ErrRequestMarshal = errors.New("cannot marshal request body")
|
||||||
ErrUnknownResponse = errors.New("unknown response received")
|
ErrUnknownResponse = errors.New("unknown response received")
|
||||||
ErrUnmarshalResponse = errors.New("cannot unmarshal update response")
|
ErrUnmarshalResponse = errors.New("cannot unmarshal update response")
|
||||||
ErrUnsuccessfulResponse = errors.New("unsuccessful response")
|
ErrUnsuccessfulResponse = errors.New("unsuccessful response")
|
||||||
|
|||||||
@@ -21,3 +21,11 @@ func setAuthBearer(request *http.Request, token string) {
|
|||||||
func setAuthSSOKey(request *http.Request, key, secret string) {
|
func setAuthSSOKey(request *http.Request, key, secret string) {
|
||||||
request.Header.Set("Authorization", "sso-key "+key+":"+secret)
|
request.Header.Set("Authorization", "sso-key "+key+":"+secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setOauth(request *http.Request, value string) {
|
||||||
|
request.Header.Set("oauth", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setXFilter(request *http.Request, value string) {
|
||||||
|
request.Header.Set("X-Filter", value)
|
||||||
|
}
|
||||||
|
|||||||
357
internal/settings/linode.go
Normal file
357
internal/settings/linode.go
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/qdm12/ddns-updater/internal/constants"
|
||||||
|
"github.com/qdm12/ddns-updater/internal/models"
|
||||||
|
"github.com/qdm12/ddns-updater/internal/regex"
|
||||||
|
)
|
||||||
|
|
||||||
|
type linode struct {
|
||||||
|
domain string
|
||||||
|
host string
|
||||||
|
ipVersion models.IPVersion
|
||||||
|
token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLinode(data json.RawMessage, domain, host string, ipVersion models.IPVersion,
|
||||||
|
_ bool, _ regex.Matcher) (s Settings, err error) {
|
||||||
|
extraSettings := struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}{}
|
||||||
|
if err := json.Unmarshal(data, &extraSettings); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l := &linode{
|
||||||
|
domain: domain,
|
||||||
|
host: host,
|
||||||
|
ipVersion: ipVersion,
|
||||||
|
token: extraSettings.Token,
|
||||||
|
}
|
||||||
|
if err := l.isValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) isValid() error {
|
||||||
|
if len(l.token) == 0 {
|
||||||
|
return ErrEmptyToken
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) String() string {
|
||||||
|
return toString(l.domain, l.host, constants.LINODE, l.ipVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) Domain() string {
|
||||||
|
return l.domain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) Host() string {
|
||||||
|
return l.host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) DNSLookup() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) IPVersion() models.IPVersion {
|
||||||
|
return l.ipVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) BuildDomainName() string {
|
||||||
|
return buildDomainName(l.host, l.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) HTML() models.HTMLRow {
|
||||||
|
return models.HTMLRow{
|
||||||
|
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", l.BuildDomainName(), l.BuildDomainName())),
|
||||||
|
Host: models.HTML(l.Host()),
|
||||||
|
Provider: "<a href=\"https://cloud.linode.com/\">Linode</a>",
|
||||||
|
IPVersion: models.HTML(l.ipVersion),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using https://www.linode.com/docs/api/domains/
|
||||||
|
func (l *linode) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||||
|
domainID, err := l.getDomainID(ctx, client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrGetDomainID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
recordType := A
|
||||||
|
if ip.To4() == nil {
|
||||||
|
recordType = AAAA
|
||||||
|
}
|
||||||
|
|
||||||
|
recordID, err := l.getRecordID(ctx, client, domainID, recordType)
|
||||||
|
if errors.Is(err, ErrNotFound) {
|
||||||
|
err := l.createRecord(ctx, client, domainID, recordType, ip)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrCreateRecord, err)
|
||||||
|
}
|
||||||
|
return ip, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrGetRecordID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := l.updateRecord(ctx, client, domainID, recordID, ip); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrUpdateRecord, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type linodeError struct {
|
||||||
|
Field string `json:"field"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) setHeaders(request *http.Request) {
|
||||||
|
setUserAgent(request)
|
||||||
|
setContentType(request, "application/json")
|
||||||
|
setAuthBearer(request, l.token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) getDomainID(ctx context.Context, client *http.Client) (domainID int, err error) {
|
||||||
|
u := url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "api.linode.com",
|
||||||
|
Path: "/v4/domains",
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
l.setHeaders(request)
|
||||||
|
setOauth(request, "domains:read_only")
|
||||||
|
setXFilter(request, `{"domain": "`+l.domain+`"}`)
|
||||||
|
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
err = fmt.Errorf("%w: %d", ErrBadHTTPStatus, response.StatusCode)
|
||||||
|
return 0, fmt.Errorf("%w: %s", err, l.getError(response.Body))
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
var obj struct {
|
||||||
|
Data []struct {
|
||||||
|
ID *int `json:"id,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(&obj); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := obj.Data
|
||||||
|
switch len(domains) {
|
||||||
|
case 0:
|
||||||
|
return 0, ErrNotFound
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("%w: %d records instead of 1",
|
||||||
|
ErrNumberOfResultsReceived, len(domains))
|
||||||
|
}
|
||||||
|
|
||||||
|
if domains[0].Status == "disabled" {
|
||||||
|
return 0, ErrDomainDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if domains[0].ID == nil {
|
||||||
|
return 0, ErrDomainIDNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return *domains[0].ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) getRecordID(ctx context.Context, client *http.Client,
|
||||||
|
domainID int, recordType string) (recordID int, err error) {
|
||||||
|
u := url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "api.linode.com",
|
||||||
|
Path: "/v4/domains/" + strconv.Itoa(domainID) + "/records",
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
l.setHeaders(request)
|
||||||
|
setOauth(request, "domains:read_only")
|
||||||
|
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
err = fmt.Errorf("%w: %d", ErrBadHTTPStatus, response.StatusCode)
|
||||||
|
return 0, fmt.Errorf("%w: %s", err, l.getError(response.Body))
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
var obj struct {
|
||||||
|
Data []struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Host string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(&obj); err != nil {
|
||||||
|
return 0, fmt.Errorf("%w: %s", ErrUnmarshalResponse, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domainRecord := range obj.Data {
|
||||||
|
if domainRecord.Type == recordType && domainRecord.Host == l.host {
|
||||||
|
return domainRecord.ID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) createRecord(ctx context.Context, client *http.Client,
|
||||||
|
domainID int, recordType string, ip net.IP) (err error) {
|
||||||
|
u := url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "api.linode.com",
|
||||||
|
Path: "/v4/domains/" + strconv.Itoa(domainID) + "/records",
|
||||||
|
}
|
||||||
|
|
||||||
|
type domainRecord struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Host string `json:"name"`
|
||||||
|
IP string `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
requestData := domainRecord{
|
||||||
|
Type: recordType,
|
||||||
|
Host: l.host,
|
||||||
|
IP: ip.String(),
|
||||||
|
}
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
if err := encoder.Encode(requestData); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrRequestMarshal, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.setHeaders(request)
|
||||||
|
setOauth(request, "domains:read_write")
|
||||||
|
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
err = fmt.Errorf("%w: %d", ErrBadHTTPStatus, response.StatusCode)
|
||||||
|
return fmt.Errorf("%w: %s", err, l.getError(response.Body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseData domainRecord
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
if err := decoder.Decode(&responseData); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrUnmarshalResponse, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newIP := net.ParseIP(responseData.IP)
|
||||||
|
if newIP == nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrIPReceivedMalformed, responseData.IP)
|
||||||
|
} else if !newIP.Equal(ip) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrIPReceivedMismatch, newIP.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) updateRecord(ctx context.Context, client *http.Client,
|
||||||
|
domainID, recordID int, ip net.IP) (err error) {
|
||||||
|
u := url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "api.linode.com",
|
||||||
|
Path: "/v4/domains/" + strconv.Itoa(domainID) + "/records/" + strconv.Itoa(recordID),
|
||||||
|
}
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
IP string `json:"target"`
|
||||||
|
}{
|
||||||
|
IP: ip.String(),
|
||||||
|
}
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
if err := encoder.Encode(data); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrRequestMarshal, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequestWithContext(ctx, http.MethodPut, u.String(), buffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.setHeaders(request)
|
||||||
|
setOauth(request, "domains:read_write")
|
||||||
|
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
err = fmt.Errorf("%w: %d", ErrBadHTTPStatus, response.StatusCode)
|
||||||
|
return fmt.Errorf("%w: %s", err, l.getError(response.Body))
|
||||||
|
}
|
||||||
|
|
||||||
|
data.IP = ""
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
if err := decoder.Decode(&data); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrUnmarshalResponse, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newIP := net.ParseIP(data.IP)
|
||||||
|
if newIP == nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrIPReceivedMalformed, data.IP)
|
||||||
|
} else if !newIP.Equal(ip) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrIPReceivedMismatch, newIP.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linode) getError(body io.Reader) (err error) {
|
||||||
|
var errorObj linodeError
|
||||||
|
b, err := ioutil.ReadAll(body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(b, &errorObj); err != nil {
|
||||||
|
return fmt.Errorf("%s", bodyDataToSingleLine(string(b)))
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s: %s", errorObj.Field, errorObj.Reason)
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ func toString(domain, host string, provider models.Provider, ipVersion models.IP
|
|||||||
return fmt.Sprintf("[domain: %s | host: %s | provider: %s | ip: %s]", domain, host, provider, ipVersion)
|
return fmt.Sprintf("[domain: %s | host: %s | provider: %s | ip: %s]", domain, host, provider, ipVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bodyToSingleLine(body io.ReadCloser) (s string) {
|
func bodyToSingleLine(body io.Reader) (s string) {
|
||||||
b, err := ioutil.ReadAll(body)
|
b, err := ioutil.ReadAll(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
Reference in New Issue
Block a user