package otc import ( "bytes" "encoding/json" "errors" "fmt" "io" "net/http" ) type recordset struct { Name string `json:"name"` Description string `json:"description"` Type string `json:"type"` TTL int `json:"ttl"` Records []string `json:"records"` } type nameResponse struct { Name string `json:"name"` } type userResponse struct { Name string `json:"name"` Password string `json:"password"` Domain nameResponse `json:"domain"` } type passwordResponse struct { User userResponse `json:"user"` } type identityResponse struct { Methods []string `json:"methods"` Password passwordResponse `json:"password"` } type scopeResponse struct { Project nameResponse `json:"project"` } type authResponse struct { Identity identityResponse `json:"identity"` Scope scopeResponse `json:"scope"` } type loginResponse struct { Auth authResponse `json:"auth"` } type endpointResponse struct { Token token `json:"token"` } type token struct { Catalog []catalog `json:"catalog"` } type catalog struct { Type string `json:"type"` Endpoints []endpoint `json:"endpoints"` } type endpoint struct { URL string `json:"url"` } type zoneItem struct { ID string `json:"id"` Name string `json:"name"` } type zonesResponse struct { Zones []zoneItem `json:"zones"` } type recordSet struct { ID string `json:"id"` } type recordSetsResponse struct { RecordSets []recordSet `json:"recordsets"` } // Starts a new OTC API Session. Authenticates using userName, password // and receives a token to be used in for subsequent requests. func (d *DNSProvider) login() error { return d.loginRequest() } func (d *DNSProvider) loginRequest() error { userResp := userResponse{ Name: d.config.UserName, Password: d.config.Password, Domain: nameResponse{ Name: d.config.DomainName, }, } loginResp := loginResponse{ Auth: authResponse{ Identity: identityResponse{ Methods: []string{"password"}, Password: passwordResponse{ User: userResp, }, }, Scope: scopeResponse{ Project: nameResponse{ Name: d.config.ProjectName, }, }, }, } body, err := json.Marshal(loginResp) if err != nil { return err } req, err := http.NewRequest(http.MethodPost, d.config.IdentityEndpoint, bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: d.config.HTTPClient.Timeout} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= http.StatusBadRequest { return fmt.Errorf("OTC API request failed with HTTP status code %d", resp.StatusCode) } d.token = resp.Header.Get("X-Subject-Token") if d.token == "" { return errors.New("unable to get auth token") } var endpointResp endpointResponse err = json.NewDecoder(resp.Body).Decode(&endpointResp) if err != nil { return err } var endpoints []endpoint for _, v := range endpointResp.Token.Catalog { if v.Type == "dns" { endpoints = append(endpoints, v.Endpoints...) } } if len(endpoints) > 0 { d.baseURL = fmt.Sprintf("%s/v2", endpoints[0].URL) } else { return errors.New("unable to get dns endpoint") } return nil } func (d *DNSProvider) getZoneID(zone string) (string, error) { resource := fmt.Sprintf("zones?name=%s", zone) resp, err := d.sendRequest(http.MethodGet, resource, nil) if err != nil { return "", err } var zonesRes zonesResponse err = json.NewDecoder(resp).Decode(&zonesRes) if err != nil { return "", err } if len(zonesRes.Zones) < 1 { return "", fmt.Errorf("zone %s not found", zone) } for _, z := range zonesRes.Zones { if z.Name == zone { return z.ID, nil } } return "", fmt.Errorf("zone %s not found", zone) } func (d *DNSProvider) getRecordSetID(zoneID, fqdn string) (string, error) { resource := fmt.Sprintf("zones/%s/recordsets?type=TXT&name=%s", zoneID, fqdn) resp, err := d.sendRequest(http.MethodGet, resource, nil) if err != nil { return "", err } var recordSetsRes recordSetsResponse err = json.NewDecoder(resp).Decode(&recordSetsRes) if err != nil { return "", err } if len(recordSetsRes.RecordSets) < 1 { return "", errors.New("record not found") } if len(recordSetsRes.RecordSets) > 1 { return "", errors.New("to many records found") } if recordSetsRes.RecordSets[0].ID == "" { return "", errors.New("id not found") } return recordSetsRes.RecordSets[0].ID, nil } func (d *DNSProvider) deleteRecordSet(zoneID, recordID string) error { resource := fmt.Sprintf("zones/%s/recordsets/%s", zoneID, recordID) _, err := d.sendRequest(http.MethodDelete, resource, nil) return err } func (d *DNSProvider) sendRequest(method, resource string, payload interface{}) (io.Reader, error) { url := fmt.Sprintf("%s/%s", d.baseURL, resource) var body io.Reader if payload != nil { content, err := json.Marshal(payload) if err != nil { return nil, err } body = bytes.NewReader(content) } req, err := http.NewRequest(method, url, body) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") if len(d.token) > 0 { req.Header.Set("X-Auth-Token", d.token) } resp, err := d.config.HTTPClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode >= http.StatusBadRequest { return nil, fmt.Errorf("OTC API request %s failed with HTTP status code %d", url, resp.StatusCode) } body1, err := io.ReadAll(resp.Body) if err != nil { return nil, err } return bytes.NewReader(body1), nil }