github.com/akamai/AkamaiOPEN-edgegrid-golang/v4@v4.1.0/pkg/dns/record.go (about) 1 package dns 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 8 "net" 9 "sync" 10 ) 11 12 // Records contains operations available on a Record resource. 13 type Records interface { 14 // RecordToMap returns a map containing record content. 15 RecordToMap(context.Context, *RecordBody) map[string]interface{} 16 // NewRecordBody returns bare bones tsig key struct. 17 NewRecordBody(context.Context, RecordBody) *RecordBody 18 // GetRecordList retrieves recordset list based on type. 19 // 20 // See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-recordsets 21 GetRecordList(context.Context, string, string, string) (*RecordSetResponse, error) 22 // GetRdata retrieves record rdata, e.g. target. 23 GetRdata(context.Context, string, string, string) ([]string, error) 24 // ProcessRdata process rdata. 25 ProcessRdata(context.Context, []string, string) []string 26 // ParseRData parses rdata. returning map. 27 ParseRData(context.Context, string, []string) map[string]interface{} 28 // GetRecord retrieves a recordset and returns as RecordBody. 29 // 30 // See: https://techdocs.akamai.com/edge-dns/reference/get-zone-name-type 31 GetRecord(context.Context, string, string, string) (*RecordBody, error) 32 // CreateRecord creates recordset. 33 // 34 // See: https://techdocs.akamai.com/edge-dns/reference/post-zones-zone-names-name-types-type 35 CreateRecord(context.Context, *RecordBody, string, ...bool) error 36 // DeleteRecord removes recordset. 37 // 38 // See: https://techdocs.akamai.com/edge-dns/reference/delete-zone-name-type 39 DeleteRecord(context.Context, *RecordBody, string, ...bool) error 40 // UpdateRecord replaces the recordset. 41 // 42 // See: https://techdocs.akamai.com/edge-dns/reference/put-zones-zone-names-name-types-type 43 UpdateRecord(context.Context, *RecordBody, string, ...bool) error 44 // FullIPv6 is utility method to convert IP to string. 45 FullIPv6(context.Context, net.IP) string 46 // PadCoordinates is utility method to convert IP to normalize coordinates. 47 PadCoordinates(context.Context, string) string 48 } 49 50 // RecordBody contains request body for dns record 51 type RecordBody struct { 52 Name string `json:"name,omitempty"` 53 RecordType string `json:"type,omitempty"` 54 TTL int `json:"ttl,omitempty"` 55 // Active field no longer used in v2 56 Active bool `json:"active,omitempty"` 57 Target []string `json:"rdata,omitempty"` 58 } 59 60 var ( 61 zoneRecordWriteLock sync.Mutex 62 ) 63 64 // Validate validates RecordBody 65 func (rec *RecordBody) Validate() error { 66 67 if len(rec.Name) < 1 { 68 return fmt.Errorf("Record body is missing Name") 69 } 70 if len(rec.RecordType) < 1 { 71 return fmt.Errorf("Record body is missing RecordType") 72 } 73 if rec.TTL == 0 { 74 return fmt.Errorf("Record body is missing TTL") 75 } 76 if rec.Target == nil || len(rec.Target) < 1 { 77 return fmt.Errorf("Record body is missing Target") 78 } 79 80 return nil 81 } 82 83 func (p *dns) RecordToMap(ctx context.Context, record *RecordBody) map[string]interface{} { 84 85 logger := p.Log(ctx) 86 logger.Debug("RecordToMap") 87 88 if err := record.Validate(); err != nil { 89 logger.Errorf("Record to map failed. %w", err) 90 return nil 91 } 92 93 return map[string]interface{}{ 94 "name": record.Name, 95 "ttl": record.TTL, 96 "recordtype": record.RecordType, 97 // active no longer used 98 "active": record.Active, 99 "target": record.Target, 100 } 101 } 102 103 func (p *dns) NewRecordBody(ctx context.Context, params RecordBody) *RecordBody { 104 105 logger := p.Log(ctx) 106 logger.Debug("NewRecordBody") 107 108 recordbody := &RecordBody{Name: params.Name} 109 return recordbody 110 } 111 112 // Eval option lock arg passed into writable endpoints. Default is true, e.g. lock 113 func localLock(lockArg []bool) bool { 114 115 for _, lock := range lockArg { 116 // should only be one entry 117 return lock 118 } 119 120 return true 121 122 } 123 124 func (p *dns) CreateRecord(ctx context.Context, record *RecordBody, zone string, recLock ...bool) error { 125 // This lock will restrict the concurrency of API calls 126 // to 1 save request at a time. This is needed for the Soa.Serial value which 127 // is required to be incremented for every subsequent update to a zone 128 // so we have to save just one request at a time to ensure this is always 129 // incremented properly 130 131 if localLock(recLock) { 132 zoneRecordWriteLock.Lock() 133 defer zoneRecordWriteLock.Unlock() 134 } 135 136 logger := p.Log(ctx) 137 logger.Debug("CreateRecord") 138 logger.Debugf("DNS Lib Create Record: [%v]", record) 139 if err := record.Validate(); err != nil { 140 logger.Errorf("Record content not valid: %w", err) 141 return fmt.Errorf("Record content not valid. [%w]", err) 142 } 143 144 reqbody, err := convertStructToReqBody(record) 145 if err != nil { 146 return fmt.Errorf("failed to generate request body: %w", err) 147 } 148 149 var rec RecordBody 150 postURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", zone, record.Name, record.RecordType) 151 req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqbody) 152 if err != nil { 153 return fmt.Errorf("failed to create CreateRecord request: %w", err) 154 } 155 156 resp, err := p.Exec(req, &rec) 157 if err != nil { 158 return fmt.Errorf("CreateRecord request failed: %w", err) 159 } 160 161 if resp.StatusCode != http.StatusCreated { 162 return p.Error(resp) 163 } 164 165 return nil 166 } 167 168 func (p *dns) UpdateRecord(ctx context.Context, record *RecordBody, zone string, recLock ...bool) error { 169 // This lock will restrict the concurrency of API calls 170 // to 1 save request at a time. This is needed for the Soa.Serial value which 171 // is required to be incremented for every subsequent update to a zone 172 // so we have to save just one request at a time to ensure this is always 173 // incremented properly 174 175 if localLock(recLock) { 176 zoneRecordWriteLock.Lock() 177 defer zoneRecordWriteLock.Unlock() 178 } 179 180 logger := p.Log(ctx) 181 logger.Debug("UpdateRecord") 182 logger.Debugf("DNS Lib Update Record: [%v]", record) 183 if err := record.Validate(); err != nil { 184 logger.Errorf("Record content not valid: %s", err.Error()) 185 return fmt.Errorf("Record content not valid. [%w]", err) 186 } 187 188 reqbody, err := convertStructToReqBody(record) 189 if err != nil { 190 return fmt.Errorf("failed to generate request body: %w", err) 191 } 192 193 var rec RecordBody 194 putURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", zone, record.Name, record.RecordType) 195 req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, reqbody) 196 if err != nil { 197 return fmt.Errorf("failed to create UpdateRecord request: %w", err) 198 } 199 200 resp, err := p.Exec(req, &rec) 201 if err != nil { 202 return fmt.Errorf("UpdateRecord request failed: %w", err) 203 } 204 205 if resp.StatusCode != http.StatusOK { 206 return p.Error(resp) 207 } 208 209 return nil 210 } 211 212 func (p *dns) DeleteRecord(ctx context.Context, record *RecordBody, zone string, recLock ...bool) error { 213 // This lock will restrict the concurrency of API calls 214 // to 1 save request at a time. This is needed for the Soa.Serial value which 215 // is required to be incremented for every subsequent update to a zone 216 // so we have to save just one request at a time to ensure this is always 217 // incremented properly 218 219 if localLock(recLock) { 220 zoneRecordWriteLock.Lock() 221 defer zoneRecordWriteLock.Unlock() 222 } 223 224 logger := p.Log(ctx) 225 logger.Debug("DeleteRecord") 226 227 if err := record.Validate(); err != nil { 228 logger.Errorf("Record content not valid: %w", err) 229 return fmt.Errorf("Record content not valid. [%w]", err) 230 } 231 232 //var mtbody string 233 deleteURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", zone, record.Name, record.RecordType) 234 req, err := http.NewRequestWithContext(ctx, http.MethodDelete, deleteURL, nil) 235 if err != nil { 236 return fmt.Errorf("failed to create DeleteRecord request: %w", err) 237 } 238 239 resp, err := p.Exec(req, nil) //, &mtbody) 240 if err != nil { 241 return fmt.Errorf("DeleteRecord request failed: %w", err) 242 } 243 244 if resp.StatusCode != http.StatusNoContent { 245 return p.Error(resp) 246 } 247 248 return nil 249 }