1package internal 2 3import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "net/http" 10) 11 12const ( 13 identityBaseURL = "https://identity.%s.conoha.io" 14 dnsServiceBaseURL = "https://dns-service.%s.conoha.io" 15) 16 17// IdentityRequest is an authentication request body. 18type IdentityRequest struct { 19 Auth Auth `json:"auth"` 20} 21 22// Auth is an authentication information. 23type Auth struct { 24 TenantID string `json:"tenantId"` 25 PasswordCredentials PasswordCredentials `json:"passwordCredentials"` 26} 27 28// PasswordCredentials is API-user's credentials. 29type PasswordCredentials struct { 30 Username string `json:"username"` 31 Password string `json:"password"` 32} 33 34// IdentityResponse is an authentication response body. 35type IdentityResponse struct { 36 Access Access `json:"access"` 37} 38 39// Access is an identity information. 40type Access struct { 41 Token Token `json:"token"` 42} 43 44// Token is an api access token. 45type Token struct { 46 ID string `json:"id"` 47} 48 49// DomainListResponse is a response of a domain listing request. 50type DomainListResponse struct { 51 Domains []Domain `json:"domains"` 52} 53 54// Domain is a hosted domain entry. 55type Domain struct { 56 ID string `json:"id"` 57 Name string `json:"name"` 58} 59 60// RecordListResponse is a response of record listing request. 61type RecordListResponse struct { 62 Records []Record `json:"records"` 63} 64 65// Record is a record entry. 66type Record struct { 67 ID string `json:"id,omitempty"` 68 Name string `json:"name"` 69 Type string `json:"type"` 70 Data string `json:"data"` 71 TTL int `json:"ttl"` 72} 73 74// Client is a ConoHa API client. 75type Client struct { 76 token string 77 endpoint string 78 httpClient *http.Client 79} 80 81// NewClient returns a client instance logged into the ConoHa service. 82func NewClient(region string, auth Auth, httpClient *http.Client) (*Client, error) { 83 if httpClient == nil { 84 httpClient = &http.Client{} 85 } 86 87 c := &Client{httpClient: httpClient} 88 89 c.endpoint = fmt.Sprintf(identityBaseURL, region) 90 91 identity, err := c.getIdentity(auth) 92 if err != nil { 93 return nil, fmt.Errorf("failed to login: %w", err) 94 } 95 96 c.token = identity.Access.Token.ID 97 c.endpoint = fmt.Sprintf(dnsServiceBaseURL, region) 98 99 return c, nil 100} 101 102func (c *Client) getIdentity(auth Auth) (*IdentityResponse, error) { 103 req := &IdentityRequest{Auth: auth} 104 105 identity := &IdentityResponse{} 106 107 err := c.do(http.MethodPost, "/v2.0/tokens", req, identity) 108 if err != nil { 109 return nil, err 110 } 111 112 return identity, nil 113} 114 115// GetDomainID returns an ID of specified domain. 116func (c *Client) GetDomainID(domainName string) (string, error) { 117 domainList := &DomainListResponse{} 118 119 err := c.do(http.MethodGet, "/v1/domains", nil, domainList) 120 if err != nil { 121 return "", err 122 } 123 124 for _, domain := range domainList.Domains { 125 if domain.Name == domainName { 126 return domain.ID, nil 127 } 128 } 129 return "", fmt.Errorf("no such domain: %s", domainName) 130} 131 132// GetRecordID returns an ID of specified record. 133func (c *Client) GetRecordID(domainID, recordName, recordType, data string) (string, error) { 134 recordList := &RecordListResponse{} 135 136 err := c.do(http.MethodGet, fmt.Sprintf("/v1/domains/%s/records", domainID), nil, recordList) 137 if err != nil { 138 return "", err 139 } 140 141 for _, record := range recordList.Records { 142 if record.Name == recordName && record.Type == recordType && record.Data == data { 143 return record.ID, nil 144 } 145 } 146 return "", errors.New("no such record") 147} 148 149// CreateRecord adds new record. 150func (c *Client) CreateRecord(domainID string, record Record) error { 151 return c.do(http.MethodPost, fmt.Sprintf("/v1/domains/%s/records", domainID), record, nil) 152} 153 154// DeleteRecord removes specified record. 155func (c *Client) DeleteRecord(domainID, recordID string) error { 156 return c.do(http.MethodDelete, fmt.Sprintf("/v1/domains/%s/records/%s", domainID, recordID), nil, nil) 157} 158 159func (c *Client) do(method, path string, payload, result interface{}) error { 160 body := bytes.NewReader(nil) 161 162 if payload != nil { 163 bodyBytes, err := json.Marshal(payload) 164 if err != nil { 165 return err 166 } 167 body = bytes.NewReader(bodyBytes) 168 } 169 170 req, err := http.NewRequest(method, c.endpoint+path, body) 171 if err != nil { 172 return err 173 } 174 175 req.Header.Set("Accept", "application/json") 176 req.Header.Set("Content-Type", "application/json") 177 req.Header.Set("X-Auth-Token", c.token) 178 179 resp, err := c.httpClient.Do(req) 180 if err != nil { 181 return err 182 } 183 184 if resp.StatusCode != http.StatusOK { 185 respBody, err := io.ReadAll(resp.Body) 186 if err != nil { 187 return err 188 } 189 defer resp.Body.Close() 190 191 return fmt.Errorf("HTTP request failed with status code %d: %s", resp.StatusCode, string(respBody)) 192 } 193 194 if result != nil { 195 respBody, err := io.ReadAll(resp.Body) 196 if err != nil { 197 return err 198 } 199 defer resp.Body.Close() 200 201 return json.Unmarshal(respBody, result) 202 } 203 204 return nil 205} 206