diff --git a/providers/loopia/client.go b/providers/loopia/client.go index 43164db41..cffd5b7e7 100644 --- a/providers/loopia/client.go +++ b/providers/loopia/client.go @@ -5,13 +5,14 @@ import ( "encoding/xml" "errors" "fmt" - "golang.org/x/text/cases" - "golang.org/x/text/language" "io" "net/http" "strconv" "strings" "time" + + "golang.org/x/text/cases" + "golang.org/x/text/language" ) /* @@ -78,18 +79,23 @@ Loopia available API return (object) types: */ +// DefaultBaseNOURL and others are RPC end-points. const ( DefaultBaseNOURL = "https://api.loopia.no/RPCSERV" DefaultBaseRSURL = "https://api.loopia.rs/RPCSERV" DefaultBaseSEURL = "https://api.loopia.se/RPCSERV" - defaultNS1 = "ns1.loopia.se." - defaultNS2 = "ns2.loopia.se." +) + +// defaultNS1 and defaultNS2 are default NS records. +const ( + defaultNS1 = "ns1.loopia.se." + defaultNS2 = "ns2.loopia.se." ) // Section 2: Define the API client. -// LoopiaClient is the LoopiaClient handle used to store any client-related state. -type LoopiaClient struct { +// APIClient is the APIClient handle used to store any client-related state. +type APIClient struct { APIUser string APIPassword string BaseURL string @@ -101,7 +107,7 @@ type LoopiaClient struct { } // NewClient creates a new LoopiaClient. -func NewClient(apiUser, apiPassword string, region string, modifyns bool, fetchns bool, debug bool) *LoopiaClient { +func NewClient(apiUser, apiPassword string, region string, modifyns bool, fetchns bool, debug bool) *APIClient { // DefaultBaseURL is url to the XML-RPC api. var DefaultBaseURL string switch region { @@ -114,7 +120,7 @@ func NewClient(apiUser, apiPassword string, region string, modifyns bool, fetchn default: DefaultBaseURL = DefaultBaseSEURL } - return &LoopiaClient{ + return &APIClient{ APIUser: apiUser, APIPassword: apiPassword, BaseURL: DefaultBaseURL, @@ -129,7 +135,7 @@ func NewClient(apiUser, apiPassword string, region string, modifyns bool, fetchn //Create // CreateRecordSimulate only prints info about a record addition. Used for debugging. -func (c *LoopiaClient) CreateRecordSimulate(domain string, subdomain string, record paramStruct) error { +func (c *APIClient) CreateRecordSimulate(domain string, subdomain string, record paramStruct) error { if c.Debug { fmt.Printf("create: domain: %s; subdomain: %s; record: %+v\n", domain, subdomain, record) } @@ -137,7 +143,7 @@ func (c *LoopiaClient) CreateRecordSimulate(domain string, subdomain string, rec } // CreateRecord adds a record. -func (c *LoopiaClient) CreateRecord(domain string, subdomain string, record paramStruct) error { +func (c *APIClient) CreateRecord(domain string, subdomain string, record paramStruct) error { call := &methodCall{ MethodName: "addZoneRecord", Params: []param{ @@ -161,8 +167,8 @@ func (c *LoopiaClient) CreateRecord(domain string, subdomain string, record para //CRUD: Create, Read, Update, Delete //Read -// GetDomains lists all domains. -func (c *LoopiaClient) GetDomains() ([]domainObject, error) { +// getDomains lists all domains. +func (c *APIClient) getDomains() ([]domainObject, error) { call := &methodCall{ MethodName: "getDomains", Params: []param{ @@ -178,8 +184,8 @@ func (c *LoopiaClient) GetDomains() ([]domainObject, error) { return resp.Domains, err } -// GetDomainRecords gets all records for a subdomain -func (c *LoopiaClient) GetDomainRecords(domain string, subdomain string) ([]zoneRecord, error) { +// getDomainRecords gets all records for a subdomain +func (c *APIClient) getDomainRecords(domain string, subdomain string) ([]zoneRecord, error) { call := &methodCall{ MethodName: "getZoneRecords", Params: []param{ @@ -198,7 +204,7 @@ func (c *LoopiaClient) GetDomainRecords(domain string, subdomain string) ([]zone } // GetSubDomains gets all the subdomains within a domain, no records -func (c *LoopiaClient) GetSubDomains(domain string) ([]string, error) { +func (c *APIClient) GetSubDomains(domain string) ([]string, error) { call := &methodCall{ MethodName: "getSubdomains", Params: []param{ @@ -216,60 +222,59 @@ func (c *LoopiaClient) GetSubDomains(domain string) ([]string, error) { } // GetDomainNS gets all NS records for a subdomain, in this case, the apex "@" -func (c *LoopiaClient) GetDomainNS(domain string) ([]string, error) { +func (c *APIClient) GetDomainNS(domain string) ([]string, error) { if c.ModifyNameServers { - return []string{}, nil - } else { - if c.FetchNSEntries { - return []string{defaultNS1, defaultNS2}, nil - } else { - //fetch from the domain - an extra API call. - call := &methodCall{ - MethodName: "getZoneRecords", - Params: []param{ - paramString{Value: c.APIUser}, - paramString{Value: c.APIPassword}, - paramString{Value: domain}, - paramString{Value: "@"}, - }, - } + return nil, nil + } - resp := &zoneRecordsResponse{} - apexNSRecords := []string{} - err := c.rpcCall(call, resp) - if err != nil { - return nil, err - } + if c.FetchNSEntries { + return []string{defaultNS1, defaultNS2}, nil + } + //fetch from the domain - an extra API call. + call := &methodCall{ + MethodName: "getZoneRecords", + Params: []param{ + paramString{Value: c.APIUser}, + paramString{Value: c.APIPassword}, + paramString{Value: domain}, + paramString{Value: "@"}, + }, + } + + resp := &zoneRecordsResponse{} + apexNSRecords := []string{} + err := c.rpcCall(call, resp) + if err != nil { + return nil, err + } + + if c.Debug { + fmt.Printf("DEBUG: getZoneRecords(@) START\n") + } + for i, rec := range resp.ZoneRecords { + ns := rec.GetZR() + if ns.Type == "NS" { + apexNSRecords = append(apexNSRecords, ns.Rdata) if c.Debug { - fmt.Printf("DEBUG: getZoneRecords(@) START\n") + fmt.Printf("DEBUG: HERE %d: %v\n", i, ns) } - for i, rec := range resp.ZoneRecords { - ns := rec.GetZR() - if ns.Type == "NS" { - apexNSRecords = append(apexNSRecords, ns.Rdata) - if c.Debug { - fmt.Printf("DEBUG: HERE %d: %v\n", i, ns) - } - } - } - return apexNSRecords, err } } - return nil, nil + return apexNSRecords, err } //CRUD: Create, Read, Update, Delete //Update // UpdateRecordSimulate only prints info about a record update. Used for debugging. -func (c *LoopiaClient) UpdateRecordSimulate(domain string, subdomain string, rec paramStruct) error { +func (c *APIClient) UpdateRecordSimulate(domain string, subdomain string, rec paramStruct) error { fmt.Printf("got update: domain: %s; subdomain: %s; record: %v\n", domain, subdomain, rec) return nil } // UpdateRecord updates a record. -func (c *LoopiaClient) UpdateRecord(domain string, subdomain string, rec paramStruct) error { +func (c *APIClient) UpdateRecord(domain string, subdomain string, rec paramStruct) error { call := &methodCall{ MethodName: "updateZoneRecord", Params: []param{ @@ -304,13 +309,13 @@ func (c *LoopiaClient) UpdateRecord(domain string, subdomain string, rec paramSt //Delete // DeleteRecordSimulate only prints info about a record deletion. Used for debugging. -func (c *LoopiaClient) DeleteRecordSimulate(domain string, subdomain string, recordID uint32) error { +func (c *APIClient) DeleteRecordSimulate(domain string, subdomain string, recordID uint32) error { fmt.Printf("delete: domain: %s; subdomain: %s; recordID: %d\n", domain, subdomain, recordID) return nil } // DeleteRecord deletes a record. -func (c *LoopiaClient) DeleteRecord(domain string, subdomain string, recordID uint32) error { +func (c *APIClient) DeleteRecord(domain string, subdomain string, recordID uint32) error { call := &methodCall{ MethodName: "removeZoneRecord", Params: []param{ @@ -332,7 +337,7 @@ func (c *LoopiaClient) DeleteRecord(domain string, subdomain string, recordID ui } // DeleteSubdomain deletes a sub-domain and its child records. -func (c *LoopiaClient) DeleteSubdomain(domain, subdomain string) error { +func (c *APIClient) DeleteSubdomain(domain, subdomain string) error { call := &methodCall{ MethodName: "removeSubdomain", Params: []param{ @@ -355,7 +360,7 @@ func (c *LoopiaClient) DeleteSubdomain(domain, subdomain string) error { // rpcCall makes an XML-RPC call to Loopia's RPC endpoint // by marshaling the data given in the call argument to XML and sending that via HTTP Post to Loopia. // The response is then unmarshalled into the resp argument. -func (c *LoopiaClient) rpcCall(call *methodCall, resp response) error { +func (c *APIClient) rpcCall(call *methodCall, resp response) error { callBody, err := xml.MarshalIndent(call, "", " ") if err != nil { return fmt.Errorf("error marshalling the API request XML callBody: %w", err) @@ -398,7 +403,7 @@ func (c *LoopiaClient) rpcCall(call *methodCall, resp response) error { return nil } -func (c *LoopiaClient) httpPost(url string, bodyType string, body io.Reader) ([]byte, error) { +func (c *APIClient) httpPost(url string, bodyType string, body io.Reader) ([]byte, error) { c.requestRateLimiter.beforeRequest() resp, err := c.HTTPClient.Post(url, bodyType, body) c.requestRateLimiter.afterRequest() diff --git a/providers/loopia/client_test.go b/providers/loopia/client_test.go index be609b67e..bab895895 100644 --- a/providers/loopia/client_test.go +++ b/providers/loopia/client_test.go @@ -201,7 +201,7 @@ func TestClient_GetDomainRecords(t *testing.T) { client := NewClient("apiuser", "goodpassword", "", false, true, false) client.BaseURL = serverURL + "/" - recordObjs, err := client.GetDomainRecords(exampleDomain, exampleSubDomain) + recordObjs, err := client.getDomainRecords(exampleDomain, exampleSubDomain) require.NoError(t, err) zr := zRec{ diff --git a/providers/loopia/loopiaProvider.go b/providers/loopia/loopiaProvider.go index d1e414aa0..4f4931fdc 100644 --- a/providers/loopia/loopiaProvider.go +++ b/providers/loopia/loopiaProvider.go @@ -79,7 +79,7 @@ func newReg(conf map[string]string) (providers.Registrar, error) { } // newHelper generates a handle. -func newHelper(m map[string]string, metadata json.RawMessage) (*LoopiaClient, error) { +func newHelper(m map[string]string, metadata json.RawMessage) (*APIClient, error) { if m["username"] == "" { return nil, fmt.Errorf("missing Loopia API username") } @@ -87,22 +87,22 @@ func newHelper(m map[string]string, metadata json.RawMessage) (*LoopiaClient, er return nil, fmt.Errorf("missing Loopia API password") } - const boolean_string_warn = " setting as a 'string': 't', 'true', 'True' etc" + const booleanStringWarn = " setting as a 'string': 't', 'true', 'True' etc" var err error - modify_name_servers := false + modifyNameServers := false if m["modify_name_servers"] != "" { // optional - modify_name_servers, err = strconv.ParseBool(m["modify_name_servers"]) + modifyNameServers, err = strconv.ParseBool(m["modify_name_servers"]) if err != nil { - return nil, fmt.Errorf("creds.json requires the modify_name_servers" + boolean_string_warn) + return nil, fmt.Errorf("creds.json requires the modify_name_servers" + booleanStringWarn) } } - fetch_apex_ns_entries := false + fetchApexNSEntries := false if m["fetch_apex_ns_entries"] != "" { // optional - fetch_apex_ns_entries, err = strconv.ParseBool(m["fetch_apex_ns_entries"]) + fetchApexNSEntries, err = strconv.ParseBool(m["fetch_apex_ns_entries"]) if err != nil { - return nil, fmt.Errorf("creds.json requires the fetch_apex_ns_entries" + boolean_string_warn) + return nil, fmt.Errorf("creds.json requires the fetch_apex_ns_entries" + booleanStringWarn) } } @@ -110,11 +110,11 @@ func newHelper(m map[string]string, metadata json.RawMessage) (*LoopiaClient, er if m["debug"] != "" { //debug is optional dbg, err = strconv.ParseBool(m["debug"]) if err != nil { - return nil, fmt.Errorf("creds.json requires the debug" + boolean_string_warn) + return nil, fmt.Errorf("creds.json requires the debug" + booleanStringWarn) } } - api := NewClient(m["username"], m["password"], strings.ToLower(m["region"]), modify_name_servers, fetch_apex_ns_entries, dbg) + api := NewClient(m["username"], m["password"], strings.ToLower(m["region"]), modifyNameServers, fetchApexNSEntries, dbg) quota := m["rate_limit_per"] err = api.requestRateLimiter.setRateLimitPer(quota) @@ -127,9 +127,9 @@ func newHelper(m map[string]string, metadata json.RawMessage) (*LoopiaClient, er // Section 3: Domain Service Provider (DSP) related functions // ListZones lists the zones on this account. -func (c *LoopiaClient) ListZones() ([]string, error) { +func (c *APIClient) ListZones() ([]string, error) { - listResp, err := c.GetDomains() + listResp, err := c.getDomains() if err != nil { return nil, err } @@ -167,7 +167,7 @@ func (c *LoopiaClient) ListZones() ([]string, error) { // GetDomainCorrections get the current and existing records, // post-process them, and generate corrections. -func (c *LoopiaClient) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { +func (c *APIClient) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { existing, err := c.GetZoneRecords(dc.Name) if err != nil { return nil, err @@ -180,7 +180,7 @@ func (c *LoopiaClient) GetDomainCorrections(dc *models.DomainConfig) ([]*models. // GetZoneRecords gathers the DNS records and converts them to // dnscontrol's format. -func (c *LoopiaClient) GetZoneRecords(domain string) (models.Records, error) { +func (c *APIClient) GetZoneRecords(domain string) (models.Records, error) { // Two approaches. One: get all SubDomains, and get their respective records // simultaneously, or first get subdomains then fill each subdomain with its @@ -207,7 +207,7 @@ func (c *LoopiaClient) GetZoneRecords(domain string) (models.Records, error) { } //step 2: records for subdomains // Get subdomain records: - subdomainrecords, err := c.GetDomainRecords(domain, subdomain) + subdomainrecords, err := c.getDomainRecords(domain, subdomain) if err != nil { return nil, err } @@ -297,7 +297,7 @@ func gatherAffectedLabels(groups map[models.RecordKey][]string) (labels map[stri // a list of functions to call to actually make the desired // correction, and a message to output to the user when the change is // made. -func (c *LoopiaClient) GenerateZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, error) { +func (c *APIClient) GenerateZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, error) { if c.Debug { debugRecords("GenerateZoneRecordsCorrections input:\n", existingRecords) } @@ -409,20 +409,19 @@ func debugRecords(note string, recs []*models.RecordConfig) { // Section 3: Registrar-related functions // GetNameservers returns a list of nameservers for domain. -func (c *LoopiaClient) GetNameservers(domain string) ([]*models.Nameserver, error) { +func (c *APIClient) GetNameservers(domain string) ([]*models.Nameserver, error) { if c.ModifyNameServers { return nil, nil - } else { - nameservers, err := c.GetDomainNS(domain) - if err != nil { - return nil, err - } - return models.ToNameserversStripTD(nameservers) } + nameservers, err := c.GetDomainNS(domain) + if err != nil { + return nil, err + } + return models.ToNameserversStripTD(nameservers) } // GetRegistrarCorrections returns a list of corrections for this registrar. -func (c *LoopiaClient) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { +func (c *APIClient) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { existingNs, err := c.GetDomainNS(dc.Name) if err != nil { diff --git a/providers/loopia/types.go b/providers/loopia/types.go index 66776c5e6..639422afb 100644 --- a/providers/loopia/types.go +++ b/providers/loopia/types.go @@ -110,17 +110,20 @@ type zoneRecord struct { // Properties map[string]interface{} } +// zoneRecordsResponse decodes the API zoneRecords call. type zoneRecordsResponse struct { responseFault XMLName xml.Name `xml:"methodResponse"` ZoneRecords []zoneRecord `xml:"params>param>value>array>data>value>struct"` } +// Property is an XML Key/value. type Property struct { Key string `xml:"name"` Value Value `xml:"value"` } +// Value is a xml any type Value struct { // String string `xml:",any"` String string `xml:"string"` @@ -128,10 +131,17 @@ type Value struct { Bool bool `xml:"bool"` } -func (p Property) Name() string { return p.Key } +// Name is an accessor to the Property's name. +func (p Property) Name() string { return p.Key } + +// String is an accessor to the Property's value when it is a string. func (p Property) String() string { return p.Value.String } -func (p Property) Int() int { return p.Value.Int } -func (p Property) Bool() bool { return p.Value.Bool } + +// Int is an accessor to the Property's value when it is a integer. +func (p Property) Int() int { return p.Value.Int } + +// Bool is an accessor to the Property's value when it is a boolean. +func (p Property) Bool() bool { return p.Value.Bool } func (zr *zoneRecord) GetZR() zRec { record := zRec{} @@ -157,11 +167,11 @@ func (zrec *zRec) SetZR() zoneRecord { return zoneRecord{ XMLName: xml.Name{Local: "struct"}, Properties: []Property{ - Property{Key: "type", Value: Value{String: zrec.Type}}, - Property{Key: "ttl", Value: Value{Int: int(zrec.TTL)}}, - Property{Key: "priority", Value: Value{Int: int(zrec.Priority)}}, - Property{Key: "rdata", Value: Value{String: zrec.Rdata}}, - Property{Key: "record_id", Value: Value{Int: int(zrec.RecordID)}}, + {Key: "type", Value: Value{String: zrec.Type}}, + {Key: "ttl", Value: Value{Int: int(zrec.TTL)}}, + {Key: "priority", Value: Value{Int: int(zrec.Priority)}}, + {Key: "rdata", Value: Value{String: zrec.Rdata}}, + {Key: "record_id", Value: Value{Int: int(zrec.RecordID)}}, }, } }