Conversation
|
hi @shibayan ! do you think you will accept this PR or is OVH to small in your opinion to make it a great addition to your project ? |
shibayan
left a comment
There was a problem hiding this comment.
Overall, the structure and format of the code should be consistent with other provider implementations. Consistency with other provider implementations must be maintained.
I reordered and rewrote some part of the code to make it looks like GoDaddy provider as much as possible. You were right it's much better like this, thanks ! |
@shibayan it's been a while and i forgot about this PR, could you check if the last version was ok ? thanks ! |
|
Any chance it could be merged in the main? |
There was a problem hiding this comment.
Pull request overview
This PR adds an OVH DNS provider integration to Acmebot so DNS-01 TXT records can be created/removed via OVH’s signed API requests.
Changes:
- Introduces
OvhDnsProvider(and internal OVH API client/signature generation) to manage DNS zones and TXT records. - Adds
OvhOptionsand wires OVH intoAcmebotOptionsconfiguration. - Registers the OVH provider in the DNS provider list in
Program.cs.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
src/Acmebot.App/Providers/OVHProvider.cs |
Implements OVH DNS provider and an internal client that signs OVH API calls. |
src/Acmebot.App/Program.cs |
Registers the new OVH provider in the DI-provided DNS provider list. |
src/Acmebot.App/Options/OVHOptions.cs |
Adds configuration options required for OVH API authentication. |
src/Acmebot.App/Options/AcmebotOptions.cs |
Exposes OVH options from the root Acmebot configuration section. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| var recordIds = Array.Empty<string>(); | ||
| var url = "https://api.ovh.com/1.0/domain/zone/" + zone + "/record"; | ||
| using (var requestMessage = | ||
| new HttpRequestMessage(HttpMethod.Get, url)) | ||
| { | ||
| var time = await GetTime(); | ||
| var signature = GenerateSignature(_applicationSecret, _consumerKey, | ||
| time, requestMessage.Method.Method, url); | ||
|
|
||
| requestMessage.Headers.Add("X-Ovh-Application", _applicationKey); | ||
| requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey); | ||
| requestMessage.Headers.Add("X-Ovh-Signature", signature); | ||
| requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString()); | ||
|
|
||
|
|
||
| var response = await _httpClient.SendAsync(requestMessage); | ||
| response.EnsureSuccessStatusCode(); | ||
| recordIds = await response.Content.ReadFromJsonAsync<string[]>(); | ||
| } | ||
|
|
||
|
|
||
|
|
||
| foreach (var recordId in recordIds!) | ||
| { | ||
| url = "https://api.ovh.com/1.0/domain/zone/" + zone + "/record/" + recordId; | ||
| using (var requestMessage = | ||
| new HttpRequestMessage(HttpMethod.Get, url)) | ||
| { | ||
| var time = await GetTime(); | ||
| var signature = GenerateSignature(_applicationSecret, _consumerKey, | ||
| time, requestMessage.Method.Method, url); | ||
|
|
||
| requestMessage.Headers.Add("X-Ovh-Application", _applicationKey); | ||
| requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey); | ||
| requestMessage.Headers.Add("X-Ovh-Signature", signature); | ||
| requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString()); | ||
|
|
||
| var response = await _httpClient.SendAsync(requestMessage); | ||
| response.EnsureSuccessStatusCode(); | ||
| var ovhRecord = await response.Content.ReadFromJsonAsync<OvhRecord>(); | ||
|
|
||
| if (ovhRecord!.fieldType == type && ovhRecord.subDomain == relativeRecordName) | ||
| { |
| var time = await GetTime(); | ||
| var signature = GenerateSignature(_applicationSecret, _consumerKey, | ||
| time, requestMessage.Method.Method, url); | ||
|
|
||
| requestMessage.Headers.Add("X-Ovh-Application", _applicationKey); | ||
| requestMessage.Headers.Add("X-Ovh-Consumer", _consumerKey); | ||
| requestMessage.Headers.Add("X-Ovh-Signature", signature); | ||
| requestMessage.Headers.Add("X-Ovh-Timestamp", time.ToString()); | ||
|
|
||
| var response = await _httpClient.SendAsync(requestMessage); | ||
| response.EnsureSuccessStatusCode(); | ||
| var ovhRecord = await response.Content.ReadFromJsonAsync<OvhRecord>(); | ||
|
|
||
| if (ovhRecord!.fieldType == type && ovhRecord.subDomain == relativeRecordName) | ||
| { | ||
| url = "https://api.ovh.com/1.0/domain/zone/" + zone + "/record/" + recordId; | ||
| using (var requestMessage2 = | ||
| new HttpRequestMessage(HttpMethod.Delete, url)) | ||
| { | ||
| var time2 = await GetTime(); | ||
| var signature2 = GenerateSignature(_applicationSecret, _consumerKey, | ||
| time2, requestMessage2.Method.Method, url); | ||
|
|
| public string? id { get; set; } | ||
| public string? zone { get; set; } | ||
| public string? subDomain { get; set; } | ||
| public string? fieldType { get; set; } | ||
| public string? target { get; set; } | ||
| public int ttl { get; set; } |
| dnsProviders.TryAdd(options.GandiLiveDns, o => new GandiLiveDnsProvider(o)); | ||
| dnsProviders.TryAdd(options.GoDaddy, o => new GoDaddyProvider(o)); | ||
| dnsProviders.TryAdd(options.GoogleDns, o => new GoogleDnsProvider(o)); | ||
| dnsProviders.TryAdd(options.OVH, o => new OvhDnsProvider(o)); |
| var response = await _httpClient.SendAsync(requestMessage); | ||
| response.EnsureSuccessStatusCode(); | ||
| var domains = await response.Content.ReadFromJsonAsync<string[]>(); | ||
| return domains!; |
| recordIds = await response.Content.ReadFromJsonAsync<string[]>(); | ||
| } | ||
|
|
||
|
|
||
|
|
||
| foreach (var recordId in recordIds!) |
| public class OvhDnsProvider : IDnsProvider | ||
| { | ||
|
|
||
| public OvhDnsProvider(OvhOptions options) |
|
|
||
| public GoogleDnsOptions? GoogleDns { get; set; } | ||
|
|
||
| public OvhOptions? OVH { get; set; } |
| public async Task<IReadOnlyList<DnsZone>> ListZonesAsync(CancellationToken cancellationToken = default) | ||
| { | ||
| var zones = await _client.ListZonesAsync(); | ||
|
|
||
| return zones.Select(x => new DnsZone(this) { Id = x, Name = x }).ToArray(); | ||
| } | ||
|
|
||
| public Task CreateTxtRecordAsync(DnsZone zone, string relativeRecordName, IEnumerable<string> values, CancellationToken cancellationToken = default) | ||
| { | ||
| var entries = values.Select(x => new DnsEntry { Name = relativeRecordName, Type = "TXT", TTL = 600, Data = x }).ToArray(); | ||
| return _client.AddRecordAsync(zone.Name, entries); | ||
| } | ||
|
|
||
| public Task DeleteTxtRecordAsync(DnsZone zone, string relativeRecordName, CancellationToken cancellationToken = default) | ||
| { | ||
| return _client.DeleteRecordAsync(zone.Name, "TXT", relativeRecordName); | ||
| } |
|
@copilot open a new pull request to apply changes based on the comments in this thread |
I added OVH which is a cloud / DNS / domain provider really popular in France and at least Europe.
The code is complex because their api is complicated to call with a signature to generate based on the content of the call that needs to be added in header on each requests