1package godo 2 3import ( 4 "context" 5 "fmt" 6 "net/http" 7) 8 9const domainsBasePath = "v2/domains" 10 11// DomainsService is an interface for managing DNS with the DigitalOcean API. 12// See: https://developers.digitalocean.com/documentation/v2#domains and 13// https://developers.digitalocean.com/documentation/v2#domain-records 14type DomainsService interface { 15 List(context.Context, *ListOptions) ([]Domain, *Response, error) 16 Get(context.Context, string) (*Domain, *Response, error) 17 Create(context.Context, *DomainCreateRequest) (*Domain, *Response, error) 18 Delete(context.Context, string) (*Response, error) 19 20 Records(context.Context, string, *ListOptions) ([]DomainRecord, *Response, error) 21 Record(context.Context, string, int) (*DomainRecord, *Response, error) 22 DeleteRecord(context.Context, string, int) (*Response, error) 23 EditRecord(context.Context, string, int, *DomainRecordEditRequest) (*DomainRecord, *Response, error) 24 CreateRecord(context.Context, string, *DomainRecordEditRequest) (*DomainRecord, *Response, error) 25} 26 27// DomainsServiceOp handles communication with the domain related methods of the 28// DigitalOcean API. 29type DomainsServiceOp struct { 30 client *Client 31} 32 33var _ DomainsService = &DomainsServiceOp{} 34 35// Domain represents a DigitalOcean domain 36type Domain struct { 37 Name string `json:"name"` 38 TTL int `json:"ttl"` 39 ZoneFile string `json:"zone_file"` 40} 41 42// domainRoot represents a response from the DigitalOcean API 43type domainRoot struct { 44 Domain *Domain `json:"domain"` 45} 46 47type domainsRoot struct { 48 Domains []Domain `json:"domains"` 49 Links *Links `json:"links"` 50} 51 52// DomainCreateRequest respresents a request to create a domain. 53type DomainCreateRequest struct { 54 Name string `json:"name"` 55 IPAddress string `json:"ip_address,omitempty"` 56} 57 58// DomainRecordRoot is the root of an individual Domain Record response 59type domainRecordRoot struct { 60 DomainRecord *DomainRecord `json:"domain_record"` 61} 62 63// DomainRecordsRoot is the root of a group of Domain Record responses 64type domainRecordsRoot struct { 65 DomainRecords []DomainRecord `json:"domain_records"` 66 Links *Links `json:"links"` 67} 68 69// DomainRecord represents a DigitalOcean DomainRecord 70type DomainRecord struct { 71 ID int `json:"id,float64,omitempty"` 72 Type string `json:"type,omitempty"` 73 Name string `json:"name,omitempty"` 74 Data string `json:"data,omitempty"` 75 Priority int `json:"priority"` 76 Port int `json:"port,omitempty"` 77 TTL int `json:"ttl,omitempty"` 78 Weight int `json:"weight"` 79 Flags int `json:"flags"` 80 Tag string `json:"tag,omitempty"` 81} 82 83// DomainRecordEditRequest represents a request to update a domain record. 84type DomainRecordEditRequest struct { 85 Type string `json:"type,omitempty"` 86 Name string `json:"name,omitempty"` 87 Data string `json:"data,omitempty"` 88 Priority int `json:"priority"` 89 Port int `json:"port,omitempty"` 90 TTL int `json:"ttl,omitempty"` 91 Weight int `json:"weight"` 92 Flags int `json:"flags"` 93 Tag string `json:"tag,omitempty"` 94} 95 96func (d Domain) String() string { 97 return Stringify(d) 98} 99 100func (d Domain) URN() string { 101 return ToURN("Domain", d.Name) 102} 103 104// List all domains. 105func (s DomainsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Domain, *Response, error) { 106 path := domainsBasePath 107 path, err := addOptions(path, opt) 108 if err != nil { 109 return nil, nil, err 110 } 111 112 req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 113 if err != nil { 114 return nil, nil, err 115 } 116 117 root := new(domainsRoot) 118 resp, err := s.client.Do(ctx, req, root) 119 if err != nil { 120 return nil, resp, err 121 } 122 if l := root.Links; l != nil { 123 resp.Links = l 124 } 125 126 return root.Domains, resp, err 127} 128 129// Get individual domain. It requires a non-empty domain name. 130func (s *DomainsServiceOp) Get(ctx context.Context, name string) (*Domain, *Response, error) { 131 if len(name) < 1 { 132 return nil, nil, NewArgError("name", "cannot be an empty string") 133 } 134 135 path := fmt.Sprintf("%s/%s", domainsBasePath, name) 136 137 req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 138 if err != nil { 139 return nil, nil, err 140 } 141 142 root := new(domainRoot) 143 resp, err := s.client.Do(ctx, req, root) 144 if err != nil { 145 return nil, resp, err 146 } 147 148 return root.Domain, resp, err 149} 150 151// Create a new domain 152func (s *DomainsServiceOp) Create(ctx context.Context, createRequest *DomainCreateRequest) (*Domain, *Response, error) { 153 if createRequest == nil { 154 return nil, nil, NewArgError("createRequest", "cannot be nil") 155 } 156 157 path := domainsBasePath 158 159 req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest) 160 if err != nil { 161 return nil, nil, err 162 } 163 164 root := new(domainRoot) 165 resp, err := s.client.Do(ctx, req, root) 166 if err != nil { 167 return nil, resp, err 168 } 169 return root.Domain, resp, err 170} 171 172// Delete domain 173func (s *DomainsServiceOp) Delete(ctx context.Context, name string) (*Response, error) { 174 if len(name) < 1 { 175 return nil, NewArgError("name", "cannot be an empty string") 176 } 177 178 path := fmt.Sprintf("%s/%s", domainsBasePath, name) 179 180 req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) 181 if err != nil { 182 return nil, err 183 } 184 185 resp, err := s.client.Do(ctx, req, nil) 186 187 return resp, err 188} 189 190// Converts a DomainRecord to a string. 191func (d DomainRecord) String() string { 192 return Stringify(d) 193} 194 195// Converts a DomainRecordEditRequest to a string. 196func (d DomainRecordEditRequest) String() string { 197 return Stringify(d) 198} 199 200// Records returns a slice of DomainRecords for a domain 201func (s *DomainsServiceOp) Records(ctx context.Context, domain string, opt *ListOptions) ([]DomainRecord, *Response, error) { 202 if len(domain) < 1 { 203 return nil, nil, NewArgError("domain", "cannot be an empty string") 204 } 205 206 path := fmt.Sprintf("%s/%s/records", domainsBasePath, domain) 207 path, err := addOptions(path, opt) 208 if err != nil { 209 return nil, nil, err 210 } 211 212 req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 213 if err != nil { 214 return nil, nil, err 215 } 216 217 root := new(domainRecordsRoot) 218 resp, err := s.client.Do(ctx, req, root) 219 if err != nil { 220 return nil, resp, err 221 } 222 if l := root.Links; l != nil { 223 resp.Links = l 224 } 225 226 return root.DomainRecords, resp, err 227} 228 229// Record returns the record id from a domain 230func (s *DomainsServiceOp) Record(ctx context.Context, domain string, id int) (*DomainRecord, *Response, error) { 231 if len(domain) < 1 { 232 return nil, nil, NewArgError("domain", "cannot be an empty string") 233 } 234 235 if id < 1 { 236 return nil, nil, NewArgError("id", "cannot be less than 1") 237 } 238 239 path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id) 240 241 req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 242 if err != nil { 243 return nil, nil, err 244 } 245 246 record := new(domainRecordRoot) 247 resp, err := s.client.Do(ctx, req, record) 248 if err != nil { 249 return nil, resp, err 250 } 251 252 return record.DomainRecord, resp, err 253} 254 255// DeleteRecord deletes a record from a domain identified by id 256func (s *DomainsServiceOp) DeleteRecord(ctx context.Context, domain string, id int) (*Response, error) { 257 if len(domain) < 1 { 258 return nil, NewArgError("domain", "cannot be an empty string") 259 } 260 261 if id < 1 { 262 return nil, NewArgError("id", "cannot be less than 1") 263 } 264 265 path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id) 266 267 req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) 268 if err != nil { 269 return nil, err 270 } 271 272 resp, err := s.client.Do(ctx, req, nil) 273 274 return resp, err 275} 276 277// EditRecord edits a record using a DomainRecordEditRequest 278func (s *DomainsServiceOp) EditRecord(ctx context.Context, 279 domain string, 280 id int, 281 editRequest *DomainRecordEditRequest, 282) (*DomainRecord, *Response, error) { 283 if len(domain) < 1 { 284 return nil, nil, NewArgError("domain", "cannot be an empty string") 285 } 286 287 if id < 1 { 288 return nil, nil, NewArgError("id", "cannot be less than 1") 289 } 290 291 if editRequest == nil { 292 return nil, nil, NewArgError("editRequest", "cannot be nil") 293 } 294 295 path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id) 296 297 req, err := s.client.NewRequest(ctx, http.MethodPut, path, editRequest) 298 if err != nil { 299 return nil, nil, err 300 } 301 302 root := new(domainRecordRoot) 303 resp, err := s.client.Do(ctx, req, root) 304 if err != nil { 305 return nil, resp, err 306 } 307 308 return root.DomainRecord, resp, err 309} 310 311// CreateRecord creates a record using a DomainRecordEditRequest 312func (s *DomainsServiceOp) CreateRecord(ctx context.Context, 313 domain string, 314 createRequest *DomainRecordEditRequest) (*DomainRecord, *Response, error) { 315 if len(domain) < 1 { 316 return nil, nil, NewArgError("domain", "cannot be empty string") 317 } 318 319 if createRequest == nil { 320 return nil, nil, NewArgError("createRequest", "cannot be nil") 321 } 322 323 path := fmt.Sprintf("%s/%s/records", domainsBasePath, domain) 324 req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest) 325 326 if err != nil { 327 return nil, nil, err 328 } 329 330 d := new(domainRecordRoot) 331 resp, err := s.client.Do(ctx, req, d) 332 if err != nil { 333 return nil, resp, err 334 } 335 336 return d.DomainRecord, resp, err 337} 338