Skip to content

Add OVH Provider#650

Open
bhubert wants to merge 3 commits intopolymind-inc:masterfrom
bhubert:master
Open

Add OVH Provider#650
bhubert wants to merge 3 commits intopolymind-inc:masterfrom
bhubert:master

Conversation

@bhubert
Copy link
Copy Markdown
Contributor

@bhubert bhubert commented Dec 12, 2023

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

@bhubert
Copy link
Copy Markdown
Contributor Author

bhubert commented Dec 17, 2023

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 ?

Copy link
Copy Markdown
Member

@shibayan shibayan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, the structure and format of the code should be consistent with other provider implementations. Consistency with other provider implementations must be maintained.

e.g. https://github.com/shibayan/keyvault-acmebot/blob/master/KeyVault.Acmebot/Providers/GoDaddyProvider.cs

@bhubert
Copy link
Copy Markdown
Contributor Author

bhubert commented Dec 18, 2023

Overall, the structure and format of the code should be consistent with other provider implementations. Consistency with other provider implementations must be maintained.

e.g. https://github.com/shibayan/keyvault-acmebot/blob/master/KeyVault.Acmebot/Providers/GoDaddyProvider.cs

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 !

@bhubert
Copy link
Copy Markdown
Contributor Author

bhubert commented Mar 24, 2024

Overall, the structure and format of the code should be consistent with other provider implementations. Consistency with other provider implementations must be maintained.
e.g. https://github.com/shibayan/keyvault-acmebot/blob/master/KeyVault.Acmebot/Providers/GoDaddyProvider.cs

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 !

@bhubert bhubert requested a review from shibayan June 3, 2024 06:36
@CisorKnight
Copy link
Copy Markdown

Any chance it could be merged in the main?
I would really like to use keyvault-acmebot with ovh as a dns provider

Copilot AI review requested due to automatic review settings March 18, 2026 15:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 OvhOptions and wires OVH into AcmebotOptions configuration.
  • 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.

Comment on lines +96 to +138
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)
{
Comment on lines +124 to +146
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);

Comment on lines +240 to +245
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!;
Comment on lines +113 to +118
recordIds = await response.Content.ReadFromJsonAsync<string[]>();
}



foreach (var recordId in recordIds!)
}
}
}

Comment on lines +16 to +19
public class OvhDnsProvider : IDnsProvider
{

public OvhDnsProvider(OvhOptions options)

public GoogleDnsOptions? GoogleDns { get; set; }

public OvhOptions? OVH { get; set; }
Comment on lines +30 to +46
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);
}
@shibayan
Copy link
Copy Markdown
Member

@copilot open a new pull request to apply changes based on the comments in this thread

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants