|
| 1 | +package ovh |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "strconv" |
| 7 | + "strings" |
| 8 | + "sync" |
| 9 | + "time" |
| 10 | + |
| 11 | + "github.com/libdns/libdns" |
| 12 | + "github.com/ovh/go-ovh/ovh" |
| 13 | +) |
| 14 | + |
| 15 | +// Client is an abstraction of OvhClient |
| 16 | +type Client struct { |
| 17 | + ovhClient *ovh.Client |
| 18 | + mutex sync.Mutex |
| 19 | +} |
| 20 | + |
| 21 | +// Ovh zone record implementation |
| 22 | +type OvhDomainZoneRecord struct { |
| 23 | + ID int64 `json:"id,omitempty"` |
| 24 | + Zone string `json:"zone,omitempty"` |
| 25 | + SubDomain string `json:"subDomain"` |
| 26 | + FieldType string `json:"fieldType,omitempty"` |
| 27 | + Target string `json:"target"` |
| 28 | + TTL int64 `json:"ttl"` |
| 29 | +} |
| 30 | + |
| 31 | +// Ovh zone soa implementation |
| 32 | +type OvhDomainZoneSOA struct { |
| 33 | + Server string `json:"server"` |
| 34 | + Email string `json:"email"` |
| 35 | + Serial int64 `json:"serial"` |
| 36 | + Refresh int64 `json:"refresh"` |
| 37 | + NxDomainTTL int64 `json:"nxDomainTtl"` |
| 38 | + Expire int64 `json:"expire"` |
| 39 | + TTL int64 `json:"ttl"` |
| 40 | +} |
| 41 | + |
| 42 | +// setupClient invokes authentication and store client to the provider instance. |
| 43 | +func (p *Provider) setupClient() error { |
| 44 | + if p.client.ovhClient == nil { |
| 45 | + client, err := ovh.NewClient(p.Endpoint, p.ApplicationKey, p.ApplicationSecret, p.ConsumerKey) |
| 46 | + |
| 47 | + if err != nil { |
| 48 | + return err |
| 49 | + } |
| 50 | + |
| 51 | + p.client.ovhClient = client |
| 52 | + } |
| 53 | + |
| 54 | + return nil |
| 55 | +} |
| 56 | + |
| 57 | +// getRecords gets all records in specified zone on Ovh DNS. |
| 58 | +// TTL as 0 for any record correspond to the default TTL for the zone |
| 59 | +func (p *Provider) getRecords(ctx context.Context, zone string) ([]libdns.Record, error) { |
| 60 | + p.client.mutex.Lock() |
| 61 | + defer p.client.mutex.Unlock() |
| 62 | + |
| 63 | + if err := p.setupClient(); err != nil { |
| 64 | + return nil, err |
| 65 | + } |
| 66 | + |
| 67 | + var dzSOA OvhDomainZoneSOA |
| 68 | + if err := p.client.ovhClient.GetWithContext(ctx, fmt.Sprintf("/domain/zone/%s/soa", zone), &dzSOA); err != nil { |
| 69 | + return nil, err |
| 70 | + } |
| 71 | + |
| 72 | + var idRecords []int64 |
| 73 | + if err := p.client.ovhClient.GetWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record", zone), &idRecords); err != nil { |
| 74 | + return nil, err |
| 75 | + } |
| 76 | + |
| 77 | + var records []libdns.Record |
| 78 | + for _, idr := range idRecords { |
| 79 | + var dzr OvhDomainZoneRecord |
| 80 | + if err := p.client.ovhClient.GetWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record/%d", zone, idr), &dzr); err != nil { |
| 81 | + return nil, err |
| 82 | + } |
| 83 | + |
| 84 | + if dzr.TTL == 0 { |
| 85 | + dzr.TTL = dzSOA.TTL |
| 86 | + } |
| 87 | + |
| 88 | + record := libdns.Record{ |
| 89 | + ID: strconv.FormatInt(dzr.ID, 10), |
| 90 | + Type: dzr.FieldType, |
| 91 | + Name: dzr.SubDomain, |
| 92 | + Value: strings.TrimRight(strings.TrimLeft(dzr.Target, "\""), "\""), |
| 93 | + TTL: time.Duration(dzr.TTL) * time.Second, |
| 94 | + } |
| 95 | + records = append(records, record) |
| 96 | + } |
| 97 | + |
| 98 | + return records, nil |
| 99 | +} |
| 100 | + |
| 101 | +// createRecord creates a new record in the specified zone. |
| 102 | +func (p *Provider) createRecord(ctx context.Context, zone string, record libdns.Record) (libdns.Record, error) { |
| 103 | + p.client.mutex.Lock() |
| 104 | + defer p.client.mutex.Unlock() |
| 105 | + |
| 106 | + if err := p.setupClient(); err != nil { |
| 107 | + return libdns.Record{}, err |
| 108 | + } |
| 109 | + |
| 110 | + var nzr OvhDomainZoneRecord |
| 111 | + if err := p.client.ovhClient.PostWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record", zone), &OvhDomainZoneRecord{FieldType: record.Type, SubDomain: normalizeRecordName(record.Name, zone), Target: record.Value, TTL: int64(record.TTL.Seconds())}, &nzr); err != nil { |
| 112 | + return libdns.Record{}, err |
| 113 | + } |
| 114 | + |
| 115 | + createdRecord := libdns.Record{ |
| 116 | + ID: strconv.FormatInt(nzr.ID, 10), |
| 117 | + Type: nzr.FieldType, |
| 118 | + Name: nzr.SubDomain, |
| 119 | + Value: strings.TrimRight(strings.TrimLeft(nzr.Target, "\""), "\""), |
| 120 | + TTL: time.Duration(nzr.TTL) * time.Second, |
| 121 | + } |
| 122 | + |
| 123 | + return createdRecord, nil |
| 124 | +} |
| 125 | + |
| 126 | +// createOrUpdateRecord creates or updates a record, either by updating existing record or creating new one. |
| 127 | +func (p *Provider) createOrUpdateRecord(ctx context.Context, zone string, record libdns.Record) (libdns.Record, error) { |
| 128 | + if len(record.ID) == 0 { |
| 129 | + return p.createRecord(ctx, zone, record) |
| 130 | + } |
| 131 | + |
| 132 | + return p.updateRecord(ctx, zone, record) |
| 133 | +} |
| 134 | + |
| 135 | +// updateRecord updates a record |
| 136 | +func (p *Provider) updateRecord(ctx context.Context, zone string, record libdns.Record) (libdns.Record, error) { |
| 137 | + p.client.mutex.Lock() |
| 138 | + defer p.client.mutex.Unlock() |
| 139 | + |
| 140 | + if err := p.setupClient(); err != nil { |
| 141 | + return libdns.Record{}, err |
| 142 | + } |
| 143 | + |
| 144 | + var nzr OvhDomainZoneRecord |
| 145 | + if err := p.client.ovhClient.PutWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record/%s", zone, record.ID), &OvhDomainZoneRecord{SubDomain: record.Name, Target: record.Value, TTL: int64(record.TTL.Seconds())}, &nzr); err != nil { |
| 146 | + return libdns.Record{}, err |
| 147 | + } |
| 148 | + |
| 149 | + var uzr OvhDomainZoneRecord |
| 150 | + if err := p.client.ovhClient.GetWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record/%s", zone, record.ID), &uzr); err != nil { |
| 151 | + return libdns.Record{}, err |
| 152 | + } |
| 153 | + |
| 154 | + updatedRecord := libdns.Record{ |
| 155 | + ID: strconv.FormatInt(uzr.ID, 10), |
| 156 | + Type: uzr.FieldType, |
| 157 | + Name: uzr.SubDomain, |
| 158 | + Value: strings.TrimRight(strings.TrimLeft(uzr.Target, "\""), "\""), |
| 159 | + TTL: time.Duration(uzr.TTL) * time.Second, |
| 160 | + } |
| 161 | + |
| 162 | + return updatedRecord, nil |
| 163 | +} |
| 164 | + |
| 165 | +// deleteRecord deletes an existing record. |
| 166 | +// Regardless of the value of the record, if the name and type match, the record will be deleted. |
| 167 | +func (p *Provider) deleteRecord(ctx context.Context, zone string, record libdns.Record) (libdns.Record, error) { |
| 168 | + p.client.mutex.Lock() |
| 169 | + defer p.client.mutex.Unlock() |
| 170 | + |
| 171 | + if err := p.setupClient(); err != nil { |
| 172 | + return libdns.Record{}, err |
| 173 | + } |
| 174 | + |
| 175 | + if err := p.client.ovhClient.DeleteWithContext(ctx, fmt.Sprintf("/domain/zone/%s/record/%s", zone, record.ID), nil); err != nil { |
| 176 | + return libdns.Record{}, err |
| 177 | + } |
| 178 | + |
| 179 | + return record, nil |
| 180 | +} |
| 181 | + |
| 182 | +// refresh trigger a reload of the DNS zone. |
| 183 | +// It must be called after appending, setting or deleting any record |
| 184 | +func (p *Provider) refresh(ctx context.Context, zone string) (error) { |
| 185 | + p.client.mutex.Lock() |
| 186 | + defer p.client.mutex.Unlock() |
| 187 | + |
| 188 | + if err := p.setupClient(); err != nil { |
| 189 | + return err |
| 190 | + } |
| 191 | + |
| 192 | + if err := p.client.ovhClient.PostWithContext(ctx, fmt.Sprintf("/domain/zone/%s/refresh", zone), nil, nil); err != nil { |
| 193 | + return err |
| 194 | + } |
| 195 | + |
| 196 | + return nil |
| 197 | +} |
| 198 | + |
| 199 | +// unFQDN trims any trailing "." from fqdn. OVH's API does not use FQDNs. |
| 200 | +func unFQDN(fqdn string) string { |
| 201 | + return strings.TrimSuffix(fqdn, ".") |
| 202 | +} |
| 203 | + |
| 204 | +// normalizeRecordName remove absolute record name |
| 205 | +func normalizeRecordName(recordName string, zone string) string { |
| 206 | + normalized := unFQDN(recordName) |
| 207 | + normalized = strings.TrimSuffix(normalized, unFQDN(zone)) |
| 208 | + return unFQDN(normalized) |
| 209 | +} |
| 210 | + |
0 commit comments