Skip to content

Commit

Permalink
Merge pull request #4274 from IntegralProgrammer/dnsimple-user-api-token
Browse files Browse the repository at this point in the history
feat(DNSimple): User API tokens
  • Loading branch information
k8s-ci-robot committed Apr 25, 2024
2 parents 9f81bbe + ed3efdb commit aee99e2
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 9 deletions.
16 changes: 14 additions & 2 deletions docs/tutorials/dnsimple.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ This tutorial describes how to setup ExternalDNS for usage with DNSimple.

Make sure to use **>=0.4.6** version of ExternalDNS for this tutorial.

## Created a DNSimple API Access Token
## Create a DNSimple API Access Token

A DNSimple API access token can be acquired by following the [provided documentation from DNSimple](https://support.dnsimple.com/articles/api-access-token/)

The environment variable `DNSIMPLE_OAUTH` must be set to the API token generated for to run ExternalDNS with DNSimple.
The environment variable `DNSIMPLE_OAUTH` must be set to the generated API token to run ExternalDNS with DNSimple.

When the generated DNSimple API access token is a _User token_, as opposed to an _Account token_, the following environment variables must also be set:
- `DNSIMPLE_ACCOUNT_ID`: Set this to the account ID which the domains to be managed by ExternalDNS belong to (eg. `1001234`).
- `DNSIMPLE_ZONES`: Set this to a comma separated list of DNS zones to be managed by ExternalDNS (eg. `mydomain.com,example.com`).

## Deploy ExternalDNS

Expand Down Expand Up @@ -44,6 +48,10 @@ spec:
env:
- name: DNSIMPLE_OAUTH
value: "YOUR_DNSIMPLE_API_KEY"
- name: DNSIMPLE_ACCOUNT_ID
value: "SET THIS IF USING A DNSIMPLE USER ACCESS TOKEN"
- name: DNSIMPLE_ZONES
value: "SET THIS IF USING A DNSIMPLE USER ACCESS TOKEN"
```

### Manifest (for clusters with RBAC enabled)
Expand Down Expand Up @@ -109,6 +117,10 @@ spec:
env:
- name: DNSIMPLE_OAUTH
value: "YOUR_DNSIMPLE_API_KEY"
- name: DNSIMPLE_ACCOUNT_ID
value: "SET THIS IF USING A DNSIMPLE USER ACCESS TOKEN"
- name: DNSIMPLE_ZONES
value: "SET THIS IF USING A DNSIMPLE USER ACCESS TOKEN"
```


Expand Down
31 changes: 27 additions & 4 deletions provider/dnsimple/dnsimple.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,14 @@ func NewDnsimpleProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provid
dryRun: dryRun,
}

whoamiResponse, err := provider.identity.Whoami(context.Background())
if err != nil {
return nil, err
provider.accountID = os.Getenv("DNSIMPLE_ACCOUNT_ID")
if provider.accountID == "" {
whoamiResponse, err := provider.identity.Whoami(context.Background())
if err != nil {
return nil, err
}
provider.accountID = int64ToString(whoamiResponse.Data.Account.ID)
}
provider.accountID = int64ToString(whoamiResponse.Data.Account.ID)
return provider, nil
}

Expand All @@ -136,9 +139,29 @@ func (p *dnsimpleProvider) GetAccountID(ctx context.Context) (accountID string,
return int64ToString(whoamiResponse.Data.Account.ID), nil
}

func ZonesFromZoneString(zonestring string) map[string]dnsimple.Zone {
zones := make(map[string]dnsimple.Zone)
zoneNames := strings.Split(zonestring, ",")
for indexId, zoneName := range zoneNames {
zone := dnsimple.Zone{Name: zoneName, ID: int64(indexId)}
zones[int64ToString(zone.ID)] = zone
}
return zones
}

// Returns a list of filtered Zones
func (p *dnsimpleProvider) Zones(ctx context.Context) (map[string]dnsimple.Zone, error) {
zones := make(map[string]dnsimple.Zone)

// If the DNSIMPLE_ZONES environment variable is specified, generate a list of Zones from it
// This is useful for when the DNSIMPLE_OAUTH environment variable is a User API token and
// not an Account API token as the User API token will not have permissions to list Zones
// belong to another account which the User has access permissions for.
envZonesStr := os.Getenv("DNSIMPLE_ZONES")
if envZonesStr != "" {
return ZonesFromZoneString(envZonesStr), nil
}

page := 1
listOptions := &dnsimple.ZoneListOptions{}
for {
Expand Down
50 changes: 47 additions & 3 deletions provider/dnsimple/dnsimple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ import (
)

var (
mockProvider dnsimpleProvider
dnsimpleListRecordsResponse dnsimple.ZoneRecordsResponse
dnsimpleListZonesResponse dnsimple.ZonesResponse
mockProvider dnsimpleProvider
dnsimpleListRecordsResponse dnsimple.ZoneRecordsResponse
dnsimpleListZonesResponse dnsimple.ZonesResponse
dnsimpleListZonesFromEnvResponse dnsimple.ZonesResponse
)

func TestDnsimpleServices(t *testing.T) {
Expand All @@ -55,6 +56,16 @@ func TestDnsimpleServices(t *testing.T) {
Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}},
Data: zones,
}
firstEnvDefinedZone := dnsimple.Zone{
ID: 0,
AccountID: 12345,
Name: "example-from-env.com",
}
envDefinedZones := []dnsimple.Zone{firstEnvDefinedZone}
dnsimpleListZonesFromEnvResponse = dnsimple.ZonesResponse{
Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}},
Data: envDefinedZones,
}
firstRecord := dnsimple.ZoneRecord{
ID: 2,
ZoneID: "example.com",
Expand Down Expand Up @@ -151,6 +162,15 @@ func testDnsimpleProviderZones(t *testing.T) {
mockProvider.accountID = "2"
_, err = mockProvider.Zones(ctx)
assert.NotNil(t, err)

mockProvider.accountID = "3"
os.Setenv("DNSIMPLE_ZONES", "example-from-env.com")
result, err = mockProvider.Zones(ctx)
assert.Nil(t, err)
validateDnsimpleZones(t, result, dnsimpleListZonesFromEnvResponse.Data)

mockProvider.accountID = "2"
os.Unsetenv("DNSIMPLE_ZONES")
}

func testDnsimpleProviderRecords(t *testing.T) {
Expand Down Expand Up @@ -207,6 +227,17 @@ func testDnsimpleSuitableZone(t *testing.T) {

zone := dnsimpleSuitableZone("example-beta.example.com", zones)
assert.Equal(t, zone.Name, "example.com")

os.Setenv("DNSIMPLE_ZONES", "environment-example.com,example.environment-example.com")
mockProvider.accountID = "3"
zones, err = mockProvider.Zones(ctx)
assert.Nil(t, err)

zone = dnsimpleSuitableZone("hello.example.environment-example.com", zones)
assert.Equal(t, zone.Name, "example.environment-example.com")

os.Unsetenv("DNSIMPLE_ZONES")
mockProvider.accountID = "1"
}

func TestNewDnsimpleProvider(t *testing.T) {
Expand All @@ -215,10 +246,23 @@ func TestNewDnsimpleProvider(t *testing.T) {
if err == nil {
t.Errorf("Expected to fail new provider on bad token")
}

os.Unsetenv("DNSIMPLE_OAUTH")
_, err = NewDnsimpleProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true)
if err == nil {
t.Errorf("Expected to fail new provider on empty token")
}

os.Setenv("DNSIMPLE_OAUTH", "xxxxxxxxxxxxxxxxxxxxxxxxxx")
os.Setenv("DNSIMPLE_ACCOUNT_ID", "12345678")
providerTypedProvider, err := NewDnsimpleProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true)
dnsimpleTypedProvider := providerTypedProvider.(*dnsimpleProvider)
if err != nil {
t.Errorf("Unexpected error thrown when testing NewDnsimpleProvider with the DNSIMPLE_ACCOUNT_ID environment variable set")
}
assert.Equal(t, dnsimpleTypedProvider.accountID, "12345678")
os.Unsetenv("DNSIMPLE_OAUTH")
os.Unsetenv("DNSIMPLE_ACCOUNT_ID")
}

func testDnsimpleGetRecordID(t *testing.T) {
Expand Down

0 comments on commit aee99e2

Please sign in to comment.