Repository: kubernetes-sigs/external-dns (language: go)

Issue:
GANDI: support for personal access token 
use of Gandi apikey is [deprecated in favor of personal access token.](https://api.gandi.net/docs/authentication/)  
personal access token are also better because you can define a scope on the APIs the PAT can access, and specifically for external-dns, the domains the PAT can manage.

`github.com/go-gandi/go-gandi` has already the support of PAT in the release 0.7.0, which is the version [external-dns uses to build](https://github.com/kubernetes-sigs/external-dns/blob/042d13b6e30575f501fdf866061433cca28fa580/go.mod#L31C16-L31C21). 


Discussion / hints:

Fix patch:
diff --git a/docs/tutorials/gandi.md b/docs/tutorials/gandi.md
index 449a5253ab..12ab0eff58 100644
--- a/docs/tutorials/gandi.md
+++ b/docs/tutorials/gandi.md
@@ -8,11 +8,13 @@ Make sure to use **>=0.7.7** version of ExternalDNS for this tutorial.
 
 Create a new DNS zone where you want to create your records in. Let's use `example.com` as an example here. Make sure the zone uses
 
-## Creating Gandi API Key
+## Creating Gandi Personal Access Token (PAT)
 
-Generate an API key on [your account](https://account.gandi.net) (click on "Security").
+Generate a Personal Access Token on [your account](https://admin.gandi.net) (click on "User Settings") with `Manage domain name technical configurations` permission.
 
-The environment variable `GANDI_KEY` will be needed to run ExternalDNS with Gandi.
+The environment variable `GANDI_PAT` will be needed to run ExternalDNS with Gandi.
+
+You can also set `GANDI_KEY` if you have an old API key.
 
 ## Deploy ExternalDNS
 
@@ -45,8 +47,8 @@ spec:
         - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
         - --provider=gandi
         env:
-        - name: GANDI_KEY
-          value: "YOUR_GANDI_API_KEY"
+        - name: GANDI_PAT
+          value: "YOUR_GANDI_PAT"
 ```
 
 ### Manifest (for clusters with RBAC enabled)
@@ -109,8 +111,8 @@ spec:
         - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
         - --provider=gandi
         env:
-        - name: GANDI_KEY
-          value: "YOUR_GANDI_API_KEY"
+        - name: GANDI_PAT
+          value: "YOUR_GANDI_PAT"
 ```
 
 
diff --git a/provider/gandi/gandi.go b/provider/gandi/gandi.go
index b3b7287c9c..bc06d862d6 100644
--- a/provider/gandi/gandi.go
+++ b/provider/gandi/gandi.go
@@ -52,16 +52,21 @@ type GandiProvider struct {
 }
 
 func NewGandiProvider(ctx context.Context, domainFilter endpoint.DomainFilter, dryRun bool) (*GandiProvider, error) {
-	key, ok := os.LookupEnv("GANDI_KEY")
-	if !ok {
-		return nil, errors.New("no environment variable GANDI_KEY provided")
+	key, ok_key := os.LookupEnv("GANDI_KEY")
+	pat, ok_pat := os.LookupEnv("GANDI_PAT")
+	if !(ok_key || ok_pat) {
+		return nil, errors.New("no environment variable GANDI_KEY or GANDI_PAT provided")
+	}
+	if ok_key {
+		log.Warning("Usage of GANDI_KEY (API Key) is deprecated. Please consider creating a Personal Access Token (PAT) instead, see https://api.gandi.net/docs/authentication/")
 	}
 	sharingID, _ := os.LookupEnv("GANDI_SHARING_ID")
 
 	g := config.Config{
-		APIKey:    key,
-		SharingID: sharingID,
-		Debug:     false,
+		APIKey:              key,
+		PersonalAccessToken: pat,
+		SharingID:           sharingID,
+		Debug:               false,
 		// dry-run doesn't work but it won't hurt passing the flag
 		DryRun: dryRun,
 	}


Test patch:
diff --git a/provider/gandi/gandi_test.go b/provider/gandi/gandi_test.go
index 44e630ad14..e3b4cca193 100644
--- a/provider/gandi/gandi_test.go
+++ b/provider/gandi/gandi_test.go
@@ -162,6 +162,20 @@ func TestNewGandiProvider(t *testing.T) {
 	}
 	assert.Equal(t, true, provider.DryRun)
 
+	_ = os.Setenv("GANDI_PAT", "myGandiPAT")
+	provider, err = NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), true)
+	if err != nil {
+		t.Errorf("failed : %s", err)
+	}
+	assert.Equal(t, true, provider.DryRun)
+
+	_ = os.Unsetenv("GANDI_KEY")
+	provider, err = NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), true)
+	if err != nil {
+		t.Errorf("failed : %s", err)
+	}
+	assert.Equal(t, true, provider.DryRun)
+
 	_ = os.Setenv("GANDI_SHARING_ID", "aSharingId")
 	provider, err = NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), false)
 	if err != nil {
@@ -169,7 +183,7 @@ func TestNewGandiProvider(t *testing.T) {
 	}
 	assert.Equal(t, false, provider.DryRun)
 
-	_ = os.Unsetenv("GANDI_KEY")
+	_ = os.Unsetenv("GANDI_PAT")
 	_, err = NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), true)
 	if err == nil {
 		t.Errorf("expected to fail")