mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-01-13 10:58:17 +08:00
ccb582b278
* Remove deprecated io/ioutil * fixup! * staticcheck and linting * revert models/provider.go * Fix imports to new style * linting
673 lines
21 KiB
Go
673 lines
21 KiB
Go
package cscglobal
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
|
|
"github.com/mattn/go-isatty"
|
|
)
|
|
|
|
const apiBase = "https://apis.cscglobal.com/dbs/api/v2"
|
|
|
|
// Api layer for CSC Global
|
|
|
|
type requestParams map[string]string
|
|
|
|
type errorResponse struct {
|
|
Code string `json:"code"`
|
|
Description string `json:"description"`
|
|
Value string `json:"value,omitempty"`
|
|
}
|
|
|
|
type nsModRequest struct {
|
|
Domain string `json:"qualifiedDomainName"`
|
|
NameServers []string `json:"nameServers"`
|
|
DNSType string `json:"dnsType,omitempty"`
|
|
Notifications struct {
|
|
Enabled bool `json:"enabled,omitempty"`
|
|
Emails []string `json:"additionalNotificationEmails,omitempty"`
|
|
} `json:"notifications"`
|
|
ShowPrice bool `json:"showPrice,omitempty"`
|
|
CustomFields []string `json:"customFields,omitempty"`
|
|
}
|
|
|
|
type nsModRequestResult struct {
|
|
Result struct {
|
|
Domain string `json:"qualifiedDomainName"`
|
|
Status struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
AdditionalInformation string `json:"additionalInformation"`
|
|
UUID string `json:"uuid"`
|
|
} `json:"status"`
|
|
} `json:"result"`
|
|
}
|
|
|
|
type domainRecord struct {
|
|
Nameserver []string `json:"nameservers"`
|
|
}
|
|
|
|
// Get zone
|
|
|
|
type nativeRecordA = struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
TTL uint32 `json:"ttl"`
|
|
Status string `json:"status"`
|
|
}
|
|
type nativeRecordCNAME = struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
TTL uint32 `json:"ttl"`
|
|
Status string `json:"status"`
|
|
}
|
|
type nativeRecordAAAA = struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
TTL uint32 `json:"ttl"`
|
|
Status string `json:"status"`
|
|
}
|
|
type nativeRecordTXT = struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
TTL uint32 `json:"ttl"`
|
|
Status string `json:"status"`
|
|
}
|
|
type nativeRecordMX = struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
TTL uint32 `json:"ttl"`
|
|
Status string `json:"status"`
|
|
Priority uint16 `json:"priority"`
|
|
}
|
|
type nativeRecordNS = struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
TTL uint32 `json:"ttl"`
|
|
Status string `json:"status"`
|
|
Priority int `json:"priority"`
|
|
}
|
|
type nativeRecordSRV = struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
TTL uint32 `json:"ttl"`
|
|
Status string `json:"status"`
|
|
Priority uint16 `json:"priority"`
|
|
Weight uint16 `json:"weight"`
|
|
Port uint16 `json:"port"`
|
|
}
|
|
type nativeRecordCAA = struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
TTL uint32 `json:"ttl"`
|
|
Status string `json:"status"`
|
|
Tag string `json:"tag"`
|
|
Flag uint8 `json:"flag"`
|
|
}
|
|
type nativeRecordSOA = struct {
|
|
Serial int `json:"serial"`
|
|
Refresh int `json:"refresh"`
|
|
Retry int `json:"retry"`
|
|
Expire int `json:"expire"`
|
|
TTL uint32 `json:"ttlMin"`
|
|
TTLNeg int `json:"ttlNeg"`
|
|
TTLZone int `json:"ttlZone"`
|
|
TechEmail string `json:"techEmail"`
|
|
MasterHost string `json:"masterHost"`
|
|
}
|
|
|
|
type zoneResponse struct {
|
|
ZoneName string `json:"zoneName"`
|
|
HostingType string `json:"hostingType"`
|
|
A []nativeRecordA `json:"a"`
|
|
Cname []nativeRecordCNAME `json:"cname"`
|
|
Aaaa []nativeRecordAAAA `json:"aaaa"`
|
|
Txt []nativeRecordTXT `json:"txt"`
|
|
Mx []nativeRecordMX `json:"mx"`
|
|
Ns []nativeRecordNS `json:"ns"`
|
|
Srv []nativeRecordSRV `json:"srv"`
|
|
Caa []nativeRecordCAA `json:"caa"`
|
|
Soa nativeRecordSOA `json:"soa"`
|
|
}
|
|
|
|
// Zone edits
|
|
|
|
type zoneResourceRecordEdit = struct {
|
|
Action string `json:"action"`
|
|
RecordType string `json:"recordType"`
|
|
CurrentKey string `json:"currentKey,omitempty"`
|
|
CurrentValue string `json:"currentValue,omitempty"`
|
|
NewKey string `json:"newKey,omitempty"`
|
|
NewValue string `json:"newValue,omitempty"`
|
|
NewTTL uint32 `json:"newTtl,omitempty"`
|
|
// MX and SRV:
|
|
NewPriority uint16 `json:"newPriority,omitempty"`
|
|
// SRV:
|
|
NewWeight uint16 `json:"newWeight,omitempty"`
|
|
NewPort uint16 `json:"newPort,omitempty"`
|
|
// CAA:
|
|
// These are pointers so that we can display the zero-value on demand. If
|
|
// they were not pointers, the zero-value ("" and 0) would result in no JSON
|
|
// output for those fields. Sometimes we want to generate fields with
|
|
// zero-values, such as `"newTag":""`. Thus we make these pointers. The
|
|
// zero-value is now "nil". If we want the field to appear in the JSON, we
|
|
// set the pointer to a value. It is no longer nil, and will be output even
|
|
// if the value at the pointer is zero-value.
|
|
// See: https://emretanriverdi.medium.com/json-serialization-in-go-a27aeeb968de
|
|
CurrentTag *string `json:"currentTag,omitempty"`
|
|
NewTag *string `json:"newTag,omitempty"` // "" needs to be sent explicitly.
|
|
NewFlag *uint8 `json:"newFlag,omitempty"` // 0 needs to be sent explictly.
|
|
}
|
|
|
|
type zoneEditRequest = struct {
|
|
ZoneName string `json:"zoneName"`
|
|
Edits *[]zoneResourceRecordEdit `json:"edits"`
|
|
}
|
|
|
|
type zoneEditRequestResultZoneEditRequestResult struct {
|
|
Content struct {
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
} `json:"content"`
|
|
Links struct {
|
|
Self string `json:"self"`
|
|
Status string `json:"status"`
|
|
} `json:"links"`
|
|
}
|
|
|
|
type zoneEditStatusResultZoneEditStatusResult struct {
|
|
Content struct {
|
|
Status string `json:"status"`
|
|
ErrorDescription string `json:"errorDescription"`
|
|
} `json:"content"`
|
|
Links struct {
|
|
Cancel string `json:"cancel"`
|
|
} `json:"links"`
|
|
}
|
|
|
|
func (client *providerClient) getNameservers(domain string) ([]string, error) {
|
|
var bodyString, err = client.get("/domains/" + domain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var dr domainRecord
|
|
json.Unmarshal(bodyString, &dr)
|
|
ns := []string{}
|
|
ns = append(ns, dr.Nameserver...)
|
|
sort.Strings(ns)
|
|
return ns, nil
|
|
}
|
|
|
|
func (client *providerClient) updateNameservers(ns []string, domain string) error {
|
|
req := nsModRequest{
|
|
Domain: domain,
|
|
NameServers: ns,
|
|
DNSType: "OTHER_DNS",
|
|
ShowPrice: false,
|
|
}
|
|
if client.notifyEmails != nil {
|
|
req.Notifications.Enabled = true
|
|
req.Notifications.Emails = client.notifyEmails
|
|
}
|
|
req.CustomFields = []string{}
|
|
|
|
requestBody, err := json.MarshalIndent(req, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bodyString, err := client.put("/domains/nsmodification", requestBody)
|
|
if err != nil {
|
|
return fmt.Errorf("CSC Global: Error update NS : %w", err)
|
|
}
|
|
|
|
var res nsModRequestResult
|
|
json.Unmarshal(bodyString, &res)
|
|
if res.Result.Status.Code != "SUBMITTED" {
|
|
return fmt.Errorf("CSC Global: Error update NS Code: %s Message: %s AdditionalInfo: %s", res.Result.Status.Code, res.Result.Status.Message, res.Result.Status.AdditionalInformation)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// domainsResult is the JSON returned by "/domains". Fields we don't
|
|
// use are commented out.
|
|
type domainsResult struct {
|
|
Meta struct {
|
|
NumResults int `json:"numResults"`
|
|
Pages int `json:"pages"`
|
|
} `json:"meta"`
|
|
Domains []struct {
|
|
QualifiedDomainName string `json:"qualifiedDomainName"`
|
|
// Domain string `json:"domain"`
|
|
// Idn string `json:"idn"`
|
|
// Extension string `json:"extension"`
|
|
// NewGtld bool `json:"newGtld"`
|
|
// ManagedStatus string `json:"managedStatus"`
|
|
// RegistrationDate string `json:"registrationDate"`
|
|
// RegistryExpiryDate string `json:"registryExpiryDate"`
|
|
// PaidThroughDate string `json:"paidThroughDate"`
|
|
// CountryCode string `json:"countryCode"`
|
|
// ServerDeleteProhibited bool `json:"serverDeleteProhibited"`
|
|
// ServerTransferProhibited bool `json:"serverTransferProhibited"`
|
|
// ServerUpdateProhibited bool `json:"serverUpdateProhibited"`
|
|
// DNSType string `json:"dnsType"`
|
|
// WhoisPrivacy bool `json:"whoisPrivacy"`
|
|
// LocalAgent bool `json:"localAgent"`
|
|
// DnssecActivated string `json:"dnssecActivated"`
|
|
// CriticalDomain bool `json:"criticalDomain"`
|
|
// BusinessUnit string `json:"businessUnit"`
|
|
// BrandName string `json:"brandName"`
|
|
// IdnReferenceName string `json:"idnReferenceName"`
|
|
// CustomFields []interface{} `json:"customFields"`
|
|
// Account struct {
|
|
// AccountNumber string `json:"accountNumber"`
|
|
// AccountName string `json:"accountName"`
|
|
// } `json:"account"`
|
|
// Urlf struct {
|
|
// RedirectType string `json:"redirectType"`
|
|
// URLForwarding bool `json:"urlForwarding"`
|
|
// } `json:"urlf"`
|
|
// NameServers []string `json:"nameServers"`
|
|
// WhoisContacts []struct {
|
|
// ContactType string `json:"contactType"`
|
|
// FirstName string `json:"firstName"`
|
|
// LastName string `json:"lastName"`
|
|
// Organization string `json:"organization"`
|
|
// Street1 string `json:"street1"`
|
|
// Street2 string `json:"street2"`
|
|
// City string `json:"city"`
|
|
// StateProvince string `json:"stateProvince"`
|
|
// Country string `json:"country"`
|
|
// PostalCode string `json:"postalCode"`
|
|
// Email string `json:"email"`
|
|
// Phone string `json:"phone"`
|
|
// PhoneExtn string `json:"phoneExtn"`
|
|
// Fax string `json:"fax"`
|
|
// } `json:"whoisContacts"`
|
|
// LastModifiedDate string `json:"lastModifiedDate"`
|
|
// LastModifiedReason string `json:"lastModifiedReason"`
|
|
// LastModifiedDescription string `json:"lastModifiedDescription"`
|
|
} `json:"domains"`
|
|
// Links struct {
|
|
// Self string `json:"self"`
|
|
// } `json:"links"`
|
|
}
|
|
|
|
func (client *providerClient) getDomains() ([]string, error) {
|
|
var bodyString, err = client.get("/domains")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
//printer.Printf("------------------\n")
|
|
//printer.Printf("DEBUG: GETDOMAINS bodystring = %s\n", bodyString)
|
|
//printer.Printf("------------------\n")
|
|
|
|
var dr domainsResult
|
|
json.Unmarshal(bodyString, &dr)
|
|
|
|
if dr.Meta.Pages > 1 {
|
|
return nil, fmt.Errorf("cscglobal getDomains: unimplemented paganation")
|
|
}
|
|
|
|
var r []string
|
|
for _, d := range dr.Domains {
|
|
r = append(r, d.QualifiedDomainName)
|
|
}
|
|
|
|
//printer.Printf("------------------\n")
|
|
//printer.Printf("DEBUG: GETDOMAINS dr = %+v\n", dr)
|
|
//printer.Printf("------------------\n")
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func (client *providerClient) getZoneRecordsAll(zone string) (*zoneResponse, error) {
|
|
var bodyString, err = client.get("/zones/" + zone)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if cscDebug {
|
|
printer.Printf("------------------\n")
|
|
printer.Printf("DEBUG: ZONE RESPONSE = %s\n", bodyString)
|
|
printer.Printf("------------------\n")
|
|
}
|
|
|
|
var dr zoneResponse
|
|
json.Unmarshal(bodyString, &dr)
|
|
|
|
return &dr, nil
|
|
}
|
|
|
|
// sendZoneEditRequest sends a list of changes to be made to the zone.
|
|
// It is best to send all the changes for a zone in one big request
|
|
// because the zone is locked until the change propagates.
|
|
func (client *providerClient) sendZoneEditRequest(domainname string, edits []zoneResourceRecordEdit) error {
|
|
|
|
req := zoneEditRequest{
|
|
ZoneName: domainname,
|
|
Edits: &edits,
|
|
}
|
|
|
|
requestBody, err := json.Marshal(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if cscDebug {
|
|
printer.Printf("DEBUG: edit request = %s\n", requestBody)
|
|
}
|
|
responseBody, err := client.post("/zones/edits", requestBody)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// What did we get back?
|
|
var errResp zoneEditRequestResultZoneEditRequestResult
|
|
err = json.Unmarshal(responseBody, &errResp)
|
|
if err != nil {
|
|
return fmt.Errorf("CSC Global API error: %s DATA: %q", err, errResp)
|
|
}
|
|
if errResp.Content.Status != "SUCCESS" {
|
|
return fmt.Errorf("CSC Global API error: %s DATA: %q", errResp.Content.Status, errResp.Content.Message)
|
|
}
|
|
|
|
// Now we verify that the request was successfully submitted. Do not
|
|
// wait for the change to propagate. Propagation can take ~7
|
|
// minutes. Instead, we wait before doing the next mutation. In
|
|
// the typical case, that will be the next run of dnscontrol, which
|
|
// could be much longer than 7 minutes. Thus, we save a lot of time.
|
|
|
|
statusURL := errResp.Links.Status // The URL to query to check status.
|
|
return client.waitRequestURL(statusURL, true)
|
|
}
|
|
|
|
func (client *providerClient) waitRequest(reqID string) error {
|
|
return client.waitRequestURL(apiBase+"/zones/edits/status/"+reqID, false)
|
|
}
|
|
|
|
// waitRequestURL calls statusURL until status is COMPLETED or FAILED.
|
|
// Set returnEarly == true and it will return if status is PROPAGATING.
|
|
func (client *providerClient) waitRequestURL(statusURL string, returnEarly bool) error {
|
|
t1 := time.Now()
|
|
for {
|
|
statusBody, err := client.geturl(statusURL)
|
|
if err != nil {
|
|
return fmt.Errorf("CSC Global API error: %s DATA: %q", err, statusBody)
|
|
}
|
|
var statusResp zoneEditStatusResultZoneEditStatusResult
|
|
err = json.Unmarshal(statusBody, &statusResp)
|
|
if err != nil {
|
|
return fmt.Errorf("CSC Global API error: %s DATA: %q", err, statusBody)
|
|
}
|
|
status, msg := statusResp.Content.Status, statusResp.Content.ErrorDescription
|
|
//fmt.Printf("DEBUG: stat %s %s\n", statusURL, status)
|
|
|
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
|
dur := time.Since(t1).Round(time.Second)
|
|
if msg == "" {
|
|
printer.Printf("WAITING: % 6s STATUS=%s \r", dur, status)
|
|
} else {
|
|
printer.Printf("WAITING: % 6s STATUS=%s MSG=%q \r", dur, status, msg)
|
|
}
|
|
}
|
|
if status == "FAILED" {
|
|
parts := strings.Split(statusResp.Links.Cancel, "/")
|
|
client.cancelRequest(parts[len(parts)-1])
|
|
return fmt.Errorf("update failed: %s %s", msg, statusURL)
|
|
}
|
|
if status == "COMPLETED" {
|
|
break
|
|
}
|
|
if returnEarly && (status == "PROPAGATING") {
|
|
break
|
|
}
|
|
|
|
time.Sleep(2 * time.Second)
|
|
}
|
|
return nil
|
|
|
|
// Response looks like:
|
|
//{
|
|
// "content": {
|
|
// "status": "SUCCESS",
|
|
// "message": "The publish request was successfully enqueued."
|
|
// },
|
|
// "links": {
|
|
// "self": "https://apis.cscglobal.com/dbs/api/v2/zones/edits/9e139e34-a2a1-462e-88ab-3645833a55d4",
|
|
// "status": "https://apis.cscglobal.com/dbs/api/v2/zones/edits/status/9e139e34-a2a1-462e-88ab-3645833a55d4"
|
|
// }
|
|
// }
|
|
}
|
|
|
|
// Cancel pending/stuck edits
|
|
|
|
type pagedZoneEditResponsePagedZoneEditResponse struct {
|
|
Meta struct {
|
|
NumResults int `json:"numResults"`
|
|
Pages int `json:"pages"`
|
|
} `json:"meta"`
|
|
ZoneEdits []struct {
|
|
ZoneName string `json:"zoneName"`
|
|
ID string `json:"id"`
|
|
Status string `json:"status"`
|
|
} `json:"zoneEdits"`
|
|
}
|
|
|
|
// clearRequests returns after all pending requests for domain are
|
|
// no longer blocking new mutations. Requests in the FAILED state are
|
|
// cancelled (because CSCG wants a human to acknowlege failures but
|
|
// thankfully permits an API call to pretend to be the human).
|
|
func (client *providerClient) clearRequests(domain string) error {
|
|
if cscDebug {
|
|
printer.Printf("DEBUG: Clearing requests for %q\n", domain)
|
|
}
|
|
var bodyString, err = client.get("/zones/edits?filter=zoneName==" + domain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var dr pagedZoneEditResponsePagedZoneEditResponse
|
|
json.Unmarshal(bodyString, &dr)
|
|
|
|
// TODO(tlim): Properly handle paganation.
|
|
if dr.Meta.Pages > 1 {
|
|
return fmt.Errorf("cancelPendingEdits failed: Pages=%d", dr.Meta.Pages)
|
|
}
|
|
|
|
for i, ze := range dr.ZoneEdits {
|
|
if cscDebug {
|
|
if ze.Status != "COMPLETED" && ze.Status != "CANCELED" {
|
|
printer.Printf("REQUEST %d: %s %s\n", i, ze.ID, ze.Status)
|
|
}
|
|
}
|
|
switch ze.Status {
|
|
case "NEW", "SUBMITTED", "PROCESSING", "PROPAGATING":
|
|
printer.Printf("INFO: Waiting for id=%s status=%s\n", ze.ID, ze.Status)
|
|
client.waitRequest(ze.ID)
|
|
case "FAILED":
|
|
printer.Printf("INFO: Deleting request status=%s id=%s\n", ze.Status, ze.ID)
|
|
client.cancelRequest(ze.ID)
|
|
case "COMPLETED", "CANCELED":
|
|
continue
|
|
default:
|
|
return fmt.Errorf("cscglobal ClearRequests: unimplemented status: %q", ze.Status)
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (client *providerClient) cancelRequest(reqID string) error {
|
|
_, err := client.delete("/zones/edits/" + reqID)
|
|
return err
|
|
}
|
|
|
|
func (client *providerClient) put(endpoint string, requestBody []byte) ([]byte, error) {
|
|
hclient := &http.Client{}
|
|
req, _ := http.NewRequest("PUT", apiBase+endpoint, bytes.NewReader(requestBody))
|
|
|
|
// Add headers
|
|
req.Header.Add("apikey", client.key)
|
|
req.Header.Add("Authorization", "Bearer "+client.token)
|
|
req.Header.Add("Accept", "application/json")
|
|
req.Header.Add("Content-Type", "application/json")
|
|
|
|
resp, err := hclient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bodyString, _ := io.ReadAll(resp.Body)
|
|
if resp.StatusCode == 200 {
|
|
return bodyString, nil
|
|
}
|
|
|
|
// Got a error response from API, see if it's json format
|
|
var errResp errorResponse
|
|
err = json.Unmarshal(bodyString, &errResp)
|
|
if err != nil {
|
|
// Some error messages are plain text
|
|
return nil, fmt.Errorf("CSC Global API error (put): %s URL: %s%s",
|
|
bodyString,
|
|
req.Host, req.URL.RequestURI())
|
|
}
|
|
return nil, fmt.Errorf("CSC Global API error code: %s description: %s URL: %s%s",
|
|
errResp.Code, errResp.Description,
|
|
req.Host, req.URL.RequestURI())
|
|
}
|
|
|
|
func (client *providerClient) delete(endpoint string) ([]byte, error) {
|
|
hclient := &http.Client{}
|
|
//printer.Printf("DEBUG: delete endpoint: %q\n", apiBase+endpoint)
|
|
req, _ := http.NewRequest("DELETE", apiBase+endpoint, nil)
|
|
|
|
// Add headers
|
|
req.Header.Add("apikey", client.key)
|
|
req.Header.Add("Authorization", "Bearer "+client.token)
|
|
req.Header.Add("Accept", "application/json")
|
|
req.Header.Add("Content-Type", "application/json")
|
|
|
|
resp, err := hclient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bodyString, _ := io.ReadAll(resp.Body)
|
|
if resp.StatusCode == 200 {
|
|
//printer.Printf("DEBUG: Delete successful (200)\n")
|
|
return bodyString, nil
|
|
}
|
|
//printer.Printf("DEBUG: Delete failed (%d)\n", resp.StatusCode)
|
|
|
|
// Got a error response from API, see if it's json format
|
|
var errResp errorResponse
|
|
err = json.Unmarshal(bodyString, &errResp)
|
|
if err != nil {
|
|
// Some error messages are plain text
|
|
return nil, fmt.Errorf("CSC Global API error (delete): %s URL: %s%s",
|
|
bodyString,
|
|
req.Host, req.URL.RequestURI())
|
|
}
|
|
return nil, fmt.Errorf("CSC Global API error code: %s description: %s URL: %s%s",
|
|
errResp.Code, errResp.Description,
|
|
req.Host, req.URL.RequestURI())
|
|
}
|
|
|
|
func (client *providerClient) post(endpoint string, requestBody []byte) ([]byte, error) {
|
|
hclient := &http.Client{}
|
|
req, _ := http.NewRequest("POST", apiBase+endpoint, bytes.NewBuffer(requestBody))
|
|
|
|
// Add headers
|
|
req.Header.Add("apikey", client.key)
|
|
req.Header.Add("Authorization", "Bearer "+client.token)
|
|
req.Header.Add("Accept", "application/json")
|
|
req.Header.Add("Content-Type", "application/json")
|
|
|
|
resp, err := hclient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bodyString, _ := io.ReadAll(resp.Body)
|
|
//printer.Printf("------------------\n")
|
|
//printer.Printf("DEBUG: resp.StatusCode == %d\n", resp.StatusCode)
|
|
//printer.Printf("POST RESPONSE = %s\n", bodyString)
|
|
//printer.Printf("------------------\n")
|
|
if resp.StatusCode == 201 {
|
|
return bodyString, nil
|
|
}
|
|
|
|
// Got a error response from API, see if it's json format
|
|
var errResp errorResponse
|
|
err = json.Unmarshal(bodyString, &errResp)
|
|
if err != nil {
|
|
// Some error messages are plain text
|
|
return nil, fmt.Errorf("CSC Global API error (post): %s URL: %s%s",
|
|
bodyString,
|
|
req.Host, req.URL.RequestURI())
|
|
}
|
|
return nil, fmt.Errorf("CSC Global API error code: %s description: %s URL: %s%s",
|
|
errResp.Code, errResp.Description,
|
|
req.Host, req.URL.RequestURI())
|
|
}
|
|
|
|
func (client *providerClient) get(endpoint string) ([]byte, error) {
|
|
return client.geturl(apiBase + endpoint)
|
|
}
|
|
|
|
func (client *providerClient) geturl(url string) ([]byte, error) {
|
|
hclient := &http.Client{}
|
|
req, _ := http.NewRequest("GET", url, nil)
|
|
|
|
// Add headers
|
|
req.Header.Add("apikey", client.key)
|
|
req.Header.Add("Authorization", "Bearer "+client.token)
|
|
req.Header.Add("Accept", "application/json")
|
|
|
|
resp, err := hclient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bodyString, _ := io.ReadAll(resp.Body)
|
|
if resp.StatusCode == 200 {
|
|
return bodyString, nil
|
|
}
|
|
|
|
if resp.StatusCode == 400 {
|
|
// 400, error message is in the body as plain text
|
|
return nil, fmt.Errorf("CSC Global API error (geturl): %s URL: %s%s",
|
|
bodyString,
|
|
req.Host, req.URL.RequestURI())
|
|
}
|
|
|
|
// Got a json error response from API
|
|
var errResp errorResponse
|
|
err = json.Unmarshal(bodyString, &errResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, fmt.Errorf("CSC Global API error code: %s description: %s URL: %s%s",
|
|
errResp.Code, errResp.Description,
|
|
req.Host, req.URL.RequestURI())
|
|
}
|