diff --git a/REFERENCE.md b/REFERENCE.md
index 4fed1a26..a04a2617 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -11,6 +11,7 @@
* [`letsencrypt`](#letsencrypt): Install and configure Certbot, the LetsEncrypt client
* [`letsencrypt::install`](#letsencrypt--install): Installs the Let's Encrypt client.
* [`letsencrypt::plugin::dns_cloudflare`](#letsencrypt--plugin--dns_cloudflare): Installs and configures the dns-cloudflare plugin
+* [`letsencrypt::plugin::dns_gandi`](#letsencrypt--plugin--dns_gandi): Installs and configures the dns-gandi plugin
* [`letsencrypt::plugin::dns_rfc2136`](#letsencrypt--plugin--dns_rfc2136): Installs and configures the dns-rfc2136 plugin
* [`letsencrypt::plugin::dns_route53`](#letsencrypt--plugin--dns_route53): Installs and configures the dns-route53 plugin
* [`letsencrypt::plugin::nginx`](#letsencrypt--plugin--nginx): install and configure the Let's Encrypt nginx plugin
@@ -408,6 +409,50 @@ Data type: `Stdlib::Absolutepath`
Default value: `"${letsencrypt::config_dir}/dns-cloudflare.ini"`
+### `letsencrypt::plugin::dns_gandi`
+
+This class installs and configures the Let's Encrypt dns-gandi plugin.
+https://pypi.org/project/certbot-plugin-gandi/
+
+#### Parameters
+
+The following parameters are available in the `letsencrypt::plugin::dns_gandi` class:
+
+* [`api_key`](#api_key)
+* [`package_name`](#package_name)
+* [`config_file`](#config_file)
+* [`manage_package`](#manage_package)
+
+##### `api_key`
+
+Data type: `String[1]`
+
+Gandi production api key secret. You can get it in you security tab of your account
+
+##### `package_name`
+
+Data type: `Optional[String[1]]`
+
+The name of the package to install when $manage_package is true.
+
+Default value: ``undef``
+
+##### `config_file`
+
+Data type: `Stdlib::Absolutepath`
+
+The path to the configuration file.
+
+Default value: `"${letsencrypt::config_dir}/dns-gandi.ini"`
+
+##### `manage_package`
+
+Data type: `Boolean`
+
+Manage the plugin package.
+
+Default value: ``true``
+
### `letsencrypt::plugin::dns_rfc2136`
This class installs and configures the Let's Encrypt dns-rfc2136 plugin.
@@ -1059,5 +1104,5 @@ Variant[Integer[0,31], String[1], Array[
List of accepted plugins
-Alias of `Enum['apache', 'standalone', 'webroot', 'nginx', 'dns-azure', 'dns-route53', 'dns-google', 'dns-cloudflare', 'dns-rfc2136', 'manual']`
+Alias of `Enum['apache', 'standalone', 'webroot', 'nginx', 'dns-azure', 'dns-route53', 'dns-google', 'dns-cloudflare', 'dns-rfc2136', 'dns-gandi', 'manual']`
diff --git a/data/os/Debian/11.yaml b/data/os/Debian/11.yaml
new file mode 100644
index 00000000..78ee20ed
--- /dev/null
+++ b/data/os/Debian/11.yaml
@@ -0,0 +1,2 @@
+---
+letsencrypt::plugin::dns_gandi::package_name: python3-certbot-dns-gandi
diff --git a/data/os/Ubuntu/20.04.yaml b/data/os/Ubuntu/20.04.yaml
new file mode 100644
index 00000000..78ee20ed
--- /dev/null
+++ b/data/os/Ubuntu/20.04.yaml
@@ -0,0 +1,2 @@
+---
+letsencrypt::plugin::dns_gandi::package_name: python3-certbot-dns-gandi
diff --git a/data/os/Ubuntu/22.04.yaml b/data/os/Ubuntu/22.04.yaml
new file mode 100644
index 00000000..78ee20ed
--- /dev/null
+++ b/data/os/Ubuntu/22.04.yaml
@@ -0,0 +1,2 @@
+---
+letsencrypt::plugin::dns_gandi::package_name: python3-certbot-dns-gandi
diff --git a/manifests/certonly.pp b/manifests/certonly.pp
index 1eb91435..6b8b441a 100644
--- a/manifests/certonly.pp
+++ b/manifests/certonly.pp
@@ -214,6 +214,17 @@
}
}
+ 'dns-gandi': {
+ require letsencrypt::plugin::dns_gandi
+ $_domains = join($domains, '\' -d \'')
+ $plugin_args = [
+ "--cert-name '${cert_name}' -d",
+ "'${_domains}'",
+ '-a certbot-plugin-gandi:dns',
+ "--certbot-plugin-gandi:dns-credentials ${letsencrypt::config_dir}/dns-gandi.ini",
+ ]
+ }
+
default: {
if $ensure == 'present' {
$_domains = join($domains, '\' -d \'')
diff --git a/manifests/plugin/dns_gandi.pp b/manifests/plugin/dns_gandi.pp
new file mode 100644
index 00000000..c920993d
--- /dev/null
+++ b/manifests/plugin/dns_gandi.pp
@@ -0,0 +1,39 @@
+# @summary Installs and configures the dns-gandi plugin
+#
+# This class installs and configures the Let's Encrypt dns-gandi plugin.
+# https://pypi.org/project/certbot-plugin-gandi/
+#
+# @param api_key Gandi production api key secret. You can get it in you security tab of your account
+# @param package_name The name of the package to install when $manage_package is true.
+# @param config_file The path to the configuration file.
+# @param manage_package Manage the plugin package.
+#
+class letsencrypt::plugin::dns_gandi (
+ String[1] $api_key,
+ String[1] $package_name,
+ Stdlib::Absolutepath $config_file = "${letsencrypt::config_dir}/dns-gandi.ini",
+ Boolean $manage_package = true,
+) {
+ require letsencrypt
+
+ if $manage_package {
+ package { $package_name:
+ ensure => installed,
+ before => File[$config_file],
+ }
+ }
+
+ $ini_vars = {
+ 'certbot_plugin_gandi:dns_api_key' => $api_key,
+ }
+
+ file { $config_file:
+ ensure => file,
+ owner => 'root',
+ group => 'root',
+ mode => '0400',
+ content => epp('letsencrypt/ini.epp', {
+ vars => { '' => $ini_vars },
+ }),
+ }
+}
diff --git a/spec/acceptance/letsencrypt_plugin_dns_gandi_spec.rb b/spec/acceptance/letsencrypt_plugin_dns_gandi_spec.rb
new file mode 100644
index 00000000..8f026857
--- /dev/null
+++ b/spec/acceptance/letsencrypt_plugin_dns_gandi_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper_acceptance'
+
+describe 'letsencrypt::plugin::dns_gandi', if: supported_os_gandi(fact('os')) do
+ it_behaves_like 'an idempotent resource' do
+ let(:manifest) do
+ <<-PUPPET
+ include letsencrypt
+ class { 'letsencrypt::plugin::dns_gandi':
+ api_key => 'dummy-gandi-api-token',
+ }
+ PUPPET
+ end
+ end
+
+ describe file('/etc/letsencrypt/dns-gandi.ini') do
+ it { is_expected.to be_file }
+ it { is_expected.to be_owned_by 'root' }
+ it { is_expected.to be_grouped_into 'root' }
+ it { is_expected.to be_mode 400 }
+ end
+end
diff --git a/spec/classes/plugin/dns_gandi_spec.rb b/spec/classes/plugin/dns_gandi_spec.rb
new file mode 100644
index 00000000..6f87ceec
--- /dev/null
+++ b/spec/classes/plugin/dns_gandi_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'letsencrypt::plugin::dns_gandi' do
+ on_supported_os.each do |os, facts|
+ next unless supported_os_gandi(facts[:os])
+
+ context "on #{os} based operating systems" do
+ let(:facts) { facts }
+ let(:params) { { 'api_key' => 'dummy-gandi-api-token' } }
+ let(:pre_condition) do
+ <<-PUPPET
+ class { 'letsencrypt':
+ email => 'foo@example.com',
+ }
+ PUPPET
+ end
+ let(:package_name) do
+ 'python3-certbot-dns-gandi'
+ end
+
+ context 'with required parameters' do
+ it do
+ is_expected.to compile.with_all_deps
+ end
+
+ describe 'with manage_package => true' do
+ let(:params) { super().merge(manage_package: true) }
+
+ it do
+ is_expected.to contain_class('letsencrypt::plugin::dns_gandi').with_package_name(package_name)
+ is_expected.to contain_package(package_name).with_ensure('installed')
+ end
+ end
+
+ describe 'with manage_package => false' do
+ let(:params) { super().merge(manage_package: false, package_name: 'dns-gandi-package') }
+
+ it { is_expected.not_to contain_package('dns-gandi-package') }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/defines/letsencrypt_certonly_spec.rb b/spec/defines/letsencrypt_certonly_spec.rb
index 88f78c58..d6107aa1 100644
--- a/spec/defines/letsencrypt_certonly_spec.rb
+++ b/spec/defines/letsencrypt_certonly_spec.rb
@@ -207,6 +207,45 @@ class { 'letsencrypt::plugin::dns_cloudflare':
it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a dns-cloudflare --cert-name 'foo.example.com' -d 'foo.example.com' --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/dns-cloudflare.ini --dns-cloudflare-propagation-seconds 10" }
end
+ context 'with dns-gandi plugin' do
+ let(:title) { 'foo.example.com' }
+ let(:params) { { plugin: 'dns-gandi', letsencrypt_command: 'letsencrypt' } }
+ let(:pre_condition) do
+ <<-PUPPET
+ class { 'letsencrypt':
+ email => 'foo@example.com',
+ config_dir => '/etc/letsencrypt',
+ }
+ class { 'letsencrypt::plugin::dns_gandi':
+ package_name => 'irrelevant',
+ api_key => 'dummy-gandi-api-token',
+ }
+ PUPPET
+ end
+
+ it { is_expected.to compile.with_all_deps }
+ it { is_expected.to contain_class('letsencrypt::plugin::dns_gandi') }
+ it { is_expected.to contain_exec('letsencrypt certonly foo.example.com').with_command "letsencrypt --text --agree-tos --non-interactive certonly --rsa-key-size 4096 -a dns-gandi --cert-name 'foo.example.com' -d 'foo.example.com' -a certbot-plugin-gandi:dns --certbot-plugin-gandi:dns-credentials /etc/letsencrypt/dns-gandi.ini" }
+ end
+
+ context 'with dns-gandi plugin without api_key' do
+ let(:title) { 'foo.example.com' }
+ let(:params) { { plugin: 'dns-gandi', letsencrypt_command: 'letsencrypt' } }
+ let(:pre_condition) do
+ <<-PUPPET
+ class { 'letsencrypt':
+ email => 'foo@example.com',
+ config_dir => '/etc/letsencrypt',
+ }
+ class { 'letsencrypt::plugin::dns_gandi':
+ package_name => 'irrelevant',
+ }
+ PUPPET
+ end
+
+ it { is_expected.to compile.and_raise_error(%r{expects a value for parameter 'api_key'}) }
+ end
+
context 'with custom plugin' do
let(:title) { 'foo.example.com' }
let(:params) { { plugin: 'apache' } }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 6515b7bf..40a560a0 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -8,6 +8,7 @@
ENV['COVERAGE'] ||= 'yes' if Dir.exist?(File.expand_path('../lib', __dir__))
require 'voxpupuli/test/spec_helper'
+require 'spec_helper_local'
add_mocked_facts!
diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb
index b4f352d4..6421f5ba 100644
--- a/spec/spec_helper_acceptance.rb
+++ b/spec/spec_helper_acceptance.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'voxpupuli/acceptance/spec_helper_acceptance'
+require 'spec_helper_local'
configure_beaker do |host|
# docker image does not provide cron in all cases
diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb
new file mode 100644
index 00000000..ae675a9b
--- /dev/null
+++ b/spec/spec_helper_local.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+def supported_os_gandi(os)
+ # Gandi plugin is only supported on debian 11 and ubuntu 20.04 and superiors
+ (os['name'] == 'Debian' && os['release']['major'].to_i >= 11) || (os['name'] == 'Ubuntu' && os['release']['major'].to_i >= 20)
+end
diff --git a/spec/type_aliases/plugin_spec.rb b/spec/type_aliases/plugin_spec.rb
index 0891edfe..657c1e5b 100644
--- a/spec/type_aliases/plugin_spec.rb
+++ b/spec/type_aliases/plugin_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe 'Letsencrypt::Plugin' do
- it { is_expected.to allow_values('apache', 'standalone', 'webroot', 'nginx', 'dns-azure', 'dns-route53', 'dns-google', 'dns-cloudflare', 'dns-rfc2136') }
+ it { is_expected.to allow_values('apache', 'standalone', 'webroot', 'nginx', 'dns-azure', 'dns-route53', 'dns-google', 'dns-cloudflare', 'dns-rfc2136', 'dns-gandi') }
it { is_expected.not_to allow_value(nil) }
it { is_expected.not_to allow_value('foo') }
it { is_expected.not_to allow_value('custom') }
diff --git a/types/plugin.pp b/types/plugin.pp
index 56dffb61..7af0a3bf 100644
--- a/types/plugin.pp
+++ b/types/plugin.pp
@@ -9,5 +9,6 @@
'dns-google',
'dns-cloudflare',
'dns-rfc2136',
+ 'dns-gandi',
'manual',
]