Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(DNSimple): User API tokens #4274

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
9b3c285
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Feb 20, 2024
ea2d259
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Feb 26, 2024
26694e4
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Feb 26, 2024
d0c121b
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Feb 26, 2024
d475eb6
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Feb 26, 2024
3babcbc
Fix provider/dnsimple/dnsimple_test.go: Add the missing call to NewDn…
IntegralProgrammer Feb 26, 2024
acf188f
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Feb 26, 2024
eb59b9b
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Mar 15, 2024
c54a9a8
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Mar 15, 2024
9c24ecc
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Mar 15, 2024
660b20b
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Mar 15, 2024
3eb852f
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Mar 15, 2024
487501d
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Mar 15, 2024
ed3efdb
Allow for DNSimple User API tokens to be used by implementing the DNS…
IntegralProgrammer Mar 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change wasn't really within the scope of this PR but this should be added in any case as without it, NewDnsimpleProvider isn't actually tested with an unset DNSIMPLE_OAUTH environment variable.

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
Loading