From 3f4f9b808336a6d611d079f406ddcd47f35c16ed Mon Sep 17 00:00:00 2001 From: Phil Pennock Date: Tue, 22 Jul 2025 12:51:21 -0400 Subject: [PATCH] DNSIMPLE: DOCS: handle multiple provider instances (#3678) --- .../advanced-features/concurrency-verified.md | 19 +++++++++++++++++++ providers/dnsimple/dnsimpleProvider.go | 10 +++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/documentation/advanced-features/concurrency-verified.md b/documentation/advanced-features/concurrency-verified.md index 75654d3b5..cc52ec014 100644 --- a/documentation/advanced-features/concurrency-verified.md +++ b/documentation/advanced-features/concurrency-verified.md @@ -34,3 +34,22 @@ An account ID determined on the first query? That probably needs to be protected, even if every fetch returns the same data. The `-race` build is a helpful hint but is not a guarantee. + +#### Multiple Providers + +Most simple use-cases likely use just one copy of a given provider, managing +zones in an account. But there can be multiple _distinct_ copies, each for +different accounts. Someone might use this while migrating accounts, for +instance. You might have two fields in `creds.json` both with a `TYPE` of +your provider. + +The uses of the provider objects should never create copies; each is created +by a constructor, but thereafter is a singleton per constructed provider. +Thus it is safe to have synchronization objects inside the provider struct. + +See, for example, the `dnsimple` provider, where there is a `sync.Once` _per +object_, not at a global level, so that the `.accountID` can be fetched just +once per configured provider. Because `sync.Once` contains a reference to +`sync.noCopy`, the `go vet` command will catch attempts to copy that object, +and so will catch attempts to copy the containing `dnsimpleProvider` object. + diff --git a/providers/dnsimple/dnsimpleProvider.go b/providers/dnsimple/dnsimpleProvider.go index a31eeef69..3cfdbbed8 100644 --- a/providers/dnsimple/dnsimpleProvider.go +++ b/providers/dnsimple/dnsimpleProvider.go @@ -70,13 +70,17 @@ var nameServerSuffixes = []string{ ".dnsimple-edge.com.", } -var onceFetchAccountId sync.Once - // dnsimpleProvider is the handle for this provider. type dnsimpleProvider struct { AccountToken string // The account access token BaseURL string // An alternate base URI + // We can have multiple _distinct_ versions of this struct, authenticated to + // different accounts, so _each_ version needs to be initialized just once. + // Note that sync.Once contains a reference to sync.noCopy so this will cause + // `go vet` to catch attempts to copy this outer provider struct. + onceFetchAccountId sync.Once + // This is protected under onceFetchAccountId so that this is fully safe concurrently. accountID string // Account id cache } @@ -305,7 +309,7 @@ func (c *dnsimpleProvider) getClient() *dnsimpleapi.Client { func (c *dnsimpleProvider) getAccountID() (string, error) { var onceErr error - onceFetchAccountId.Do(func() { + c.onceFetchAccountId.Do(func() { client := c.getClient() whoamiResponse, err := client.Identity.Whoami(context.Background()) if err != nil {