Documentation
¶
Overview ¶
Package azureclient resolves the Azure credential a service client is built from — once and shared, or afresh per operation, as the caller chooses.
It exists so that resolving the Azure identity chain is written once for the estate rather than in every adapter that needs a client. An adapter takes an azcore.TokenCredential and calls its own service's constructor — azsecrets.NewClient, azappconfig.NewClient, azblob.NewClient — which does no I/O; the expensive, failure-prone part is getting the credential, and that is all this module does.
Choosing a rung ¶
azureclient.FromCredential(cred) // you built it; used as-is, nothing is resolved azureclient.Ambient() // the ambient identity chain, resolved once and shared azureclient.PerCall() // the ambient chain, resolved afresh every time
Ambient is the usual choice for anything long-lived: it resolves lazily on first use, at most once concurrently, and — unlike sync.OnceValues — never caches a failure, so a managed identity that was not ready at startup does not wedge the process.
PerCall is for the caller that must not hold a credential between operations. That is a posture, not a slower Ambient.
All three return the same Source, so switching is a one-word change.
There is no endpoint here ¶
Unlike AWS, where a region is part of the configuration and this module's counterpart refuses to guess one, an Azure credential carries no endpoint. A vault URL, an App Configuration endpoint and a blob service URL are per-service and belong to the adapter that knows which service it is. This module resolves *who you are*, never *what you are talking to*.
What it does refuse is a nil credential, including a typed nil — a nil *azidentity.DefaultAzureCredential in an azcore.TokenCredential interface is not nil to a == comparison, and would panic on first use rather than at the construction that could have caught it.
Sharing is explicit ¶
Two adapters that each call Ambient get two independent sources and resolve the chain twice. That is correct: they may deliberately want different tenants, and a hidden process-wide cache would make the first one's transient failure everybody's. A caller who wants one chain builds one Source and hands it to each adapter.
Specified by org 0003 (P-2, P-3, P-12, P-13, P-14), which applies org 0002's connection lifecycle across the estate.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNoCredential = errors.NewSentinel("azureclient.no_credential",
"no Azure credential supplied; pass one to FromCredential, or use Ambient to resolve the identity chain")
ErrNoCredential reports a nil credential supplied to FromCredential.
It covers the typed nil too, which is the one worth having: a nil *azidentity.DefaultAzureCredential carried in an azcore.TokenCredential interface compares unequal to nil, so without this guard it would be accepted here and panic at the first GetToken, far from the mistake.
Functions ¶
This section is empty.
Types ¶
type Option ¶
type Option func(*options)
Option configures a source.
func WithBuildTimeout ¶
WithBuildTimeout bounds a single resolution attempt. It is per attempt, not a total budget: a failure caches nothing, so a later call gets a fresh allowance.
func WithCredentialOptions ¶
func WithCredentialOptions(opts *azidentity.DefaultAzureCredentialOptions) Option
WithCredentialOptions passes the SDK's own options through to azidentity.NewDefaultAzureCredential — a sovereign cloud, a disabled instance discovery, additionally allowed tenants.
It is the escape hatch for everything WithTenantID does not cover, and it replaces any options accumulated so far rather than merging into them, because a caller supplying the whole struct is stating the whole intent.
func WithLifetimeContext ¶
WithLifetimeContext scopes an Ambient source's resolution attempts to the life of whatever owns it. PerCall ignores it, being already scoped to its caller.
func WithLogger ¶
WithLogger enables DEBUG diagnostics. nil disables them, which is the default: a library that logs without being asked writes to somebody else's stderr.
Records carry only non-secret identifiers — the credential's concrete type and how long resolution took. Tokens and secrets are never logged, at any level, and nothing is logged above DEBUG.
func WithTenantID ¶
WithTenantID scopes the ambient chain to one Microsoft Entra tenant.
An empty string is ignored, so a caller threading through an unset flag cannot clear a tenant another option set. Leaving it unset lets the credential authenticate to whichever tenant is requested, which is the SDK's own default.
type Source ¶
type Source interface {
// AzureCredential returns the credential, resolving it if the source's
// strategy says to. It is safe for concurrent use.
AzureCredential(ctx context.Context) (azcore.TokenCredential, error)
}
Source yields the Azure credential a service client is built from.
The method is named for what it returns rather than a generic Get, so a call site reads as what it is and two providers' sources are not accidentally interchangeable.
func Ambient ¶
Ambient returns a source that resolves the ambient Azure identity chain — environment, workload identity, managed identity, developer tooling — the first time it is asked, and shares that result.
It returns immediately and performs no I/O; resolution happens off the construction path, at most once concurrently, and a failure is retried rather than cached.
Example ¶
ExampleAmbient shows the usual wiring. It has no Output because resolving the chain needs a real Azure identity; it is here to be read and kept compiling.
The source is built at startup and resolves nothing until something asks. The first call runs one bounded attempt, every later call reuses it, and a failure is retried rather than cached.
package main
import (
"context"
"gitlab.com/phpboyscout/go/azureclient"
)
func main() {
src := azureclient.Ambient(azureclient.WithTenantID("contoso"))
cred, err := src.AzureCredential(context.Background())
if err != nil {
return
}
// Building the service client from here does no I/O:
//
// client, err := azsecrets.NewClient(vaultURL, cred, nil)
_ = cred
}
Output:
func FromCredential ¶
func FromCredential(cred azcore.TokenCredential, opts ...Option) (Source, error)
FromCredential returns a source over a credential the caller already built — a specific client secret, a workload identity, a certificate.
The credential is used as-is and nothing is ever resolved, so this rung has no build policy to apply. It is checked for nil now rather than left to panic on first use.
Example (TypedNil) ¶
ExampleFromCredential_typedNil shows the guard a plain nil check misses.
A nil *azidentity.DefaultAzureCredential carried in an azcore.TokenCredential interface is NOT nil to a == comparison — which is exactly what you are holding if you dropped a constructor's error. Without the guard it is accepted here and panics at the first GetToken, a long way from the mistake.
package main
import (
"errors"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"gitlab.com/phpboyscout/go/azureclient"
)
func main() {
var typed *azidentity.DefaultAzureCredential // nil, but not a nil interface
_, err := azureclient.FromCredential(typed)
fmt.Println(typed == nil, errors.Is(err, azureclient.ErrNoCredential))
}
Output: true true