Documentation
¶
Overview ¶
Package profiles manages named Dash0 configuration profiles.
Profiles are stored in ~/.dash0/profiles.json as named sets of connection parameters (API URL, auth token, OTLP URL, dataset). One profile is marked as active in ~/.dash0/activeProfile and used as the default configuration. Environment variables (DASH0_API_URL, DASH0_AUTH_TOKEN, DASH0_OTLP_URL, DASH0_DATASET) override the active profile.
This package imports the root dash0 package for dash0.ClientOption and dash0.DatasetPtr -- never the reverse.
Index ¶
- Constants
- Variables
- func CanonicalAPIURL(raw string) (string, error)
- func WithConfiguration(ctx context.Context, cfg *Configuration) context.Context
- type Configuration
- func FromContext(ctx context.Context) *Configuration
- func ResolveConfiguration(apiUrl, authToken string, opts ...StoreOption) (*Configuration, error)
- func ResolveConfigurationContext(ctx context.Context, apiUrl, authToken string, opts ...StoreOption) (*Configuration, error)
- func ResolveConfigurationWithOtlp(apiUrl, authToken, otlpUrl, dataset string, opts ...StoreOption) (*Configuration, error)
- func ResolveConfigurationWithOtlpContext(ctx context.Context, apiUrl, authToken, otlpUrl, dataset string, ...) (*Configuration, error)
- type OAuthClientRecord
- type OAuthClientStore
- type OAuthState
- type Profile
- type ProfilesFile
- type Store
- func (s *Store) AddProfile(profile Profile) error
- func (s *Store) AddProfileContext(ctx context.Context, profile Profile) error
- func (s *Store) GetActiveConfiguration() (*Configuration, error)
- func (s *Store) GetActiveConfigurationContext(ctx context.Context) (*Configuration, error)
- func (s *Store) GetActiveProfile() (*Profile, error)
- func (s *Store) GetProfiles() ([]Profile, error)
- func (s *Store) RemoveProfile(profileName string) error
- func (s *Store) RemoveProfileContext(ctx context.Context, profileName string) error
- func (s *Store) SetActiveProfile(profileName string) error
- func (s *Store) SetActiveProfileContext(ctx context.Context, profileName string) error
- func (s *Store) UpdateProfile(name string, updateFn func(*Configuration)) error
- func (s *Store) UpdateProfileContext(ctx context.Context, name string, updateFn func(*Configuration)) error
- type StoreOption
Examples ¶
- CanonicalAPIURL
- Configuration.ClientOptions
- Configuration.DatasetPtr
- NewOAuthClientStore
- NewStore
- NewStore (WithConfigDir)
- OAuthClientStore.Delete
- OAuthClientStore.Get
- OAuthClientStore.Put
- ResolveConfiguration (ParameterOverride)
- ResolveConfigurationContext
- ResolveConfigurationWithOtlpContext
- Store.AddProfileContext
- Store.GetActiveConfiguration
- Store.GetActiveConfiguration (EnvVarOverride)
- Store.GetActiveConfigurationContext
- Store.RemoveProfile
- Store.RemoveProfileContext
- Store.SetActiveProfileContext
- Store.UpdateProfileContext
- WithConfiguration
Constants ¶
const ( // ConfigDirName is the name of the directory containing Dash0 configuration. ConfigDirName = ".dash0" // ProfilesFileName is the name of the file containing profile configurations. ProfilesFileName = "profiles.json" // ActiveProfileFileName is the name of the file containing the active profile name. ActiveProfileFileName = "activeProfile" // EnvApiUrl is the environment variable for the Dash0 API URL. EnvApiUrl = "DASH0_API_URL" // EnvAuthToken is the environment variable for the Dash0 auth token. EnvAuthToken = "DASH0_AUTH_TOKEN" // EnvOtlpUrl is the environment variable for the Dash0 OTLP URL. EnvOtlpUrl = "DASH0_OTLP_URL" // EnvDataset is the environment variable for the Dash0 dataset. EnvDataset = "DASH0_DATASET" // EnvConfigDir is the environment variable that overrides the default // configuration directory. EnvConfigDir = "DASH0_CONFIG_DIR" )
const MaxProfileNameLength = 64
MaxProfileNameLength is the maximum allowed length of a profile name. The cap keeps profile names within filesystem-friendly bounds and prevents pathological inputs from inflating the activeProfile pointer file.
const OAuthClientsFileName = "oauth-clients.json"
OAuthClientsFileName is the file storing cached dynamic client registrations.
const OAuthRefreshExpiresInDefault = 1 * time.Hour
OAuthRefreshExpiresInDefault is the lifetime assumed when the IdP omits expires_in. RFC 6749 §5.1 makes expires_in RECOMMENDED but not required; rather than reject a compliant response that omits it, the library falls back to this conservative default, which is comfortably above OAuthRefreshMinExpiresIn so an immediate refresh storm does not follow.
const OAuthRefreshMaxExpiresIn = 24 * time.Hour
OAuthRefreshMaxExpiresIn caps the trusted lifetime of an OAuth access token. A token endpoint that returns expires_in beyond this ceiling is treated as untrustworthy and the response is rejected before the caller can adopt it.
const OAuthRefreshMinExpiresIn = 2 * OAuthRefreshThreshold
OAuthRefreshMinExpiresIn is the minimum trusted lifetime of a refreshed access token. Tokens shorter than this would cross OAuthRefreshThreshold almost immediately and trigger a per-minute refresh storm; the IdP either misconfigured the client or is unhealthy, and the safe response is to reject the rotation and surface a clear error. The 2× factor preserves a useful working window: at least one OAuthRefreshThreshold of "fresh" plus another of "still serviceable".
const OAuthRefreshThreshold = 5 * time.Minute
OAuthRefreshThreshold is how far before expiry a token refresh is attempted.
Variables ¶
var ( // ErrNoActiveProfile is returned when there is no active profile. ErrNoActiveProfile = errors.New("no active profile configured") // ErrProfileNotFound is returned when a requested profile is not found. ErrProfileNotFound = errors.New("profile not found") )
var ErrOAuthClientsFileCorrupt = errors.New("oauth-clients.json is corrupt")
ErrOAuthClientsFileCorrupt is wrapped into the error returned by OAuthClientStore.Get when the on-disk file exists but is unparseable. OAuthClientStore.Put and OAuthClientStore.Delete handle corruption internally by quarantining the bad file (see [OAuthClientStore.load]), rather than failing.
var ErrReauthenticationRequired = errors.New("OAuth refresh token rejected; re-authentication required")
ErrReauthenticationRequired is returned by refresh-bearing operations when the OAuth refresh token is no longer accepted by the authorization server (RFC 6749 invalid_grant) and the caller must initiate a fresh interactive login. Callers should treat this as a terminal state for the affected profile: the stored refresh token has been cleared from disk before this error is returned, so a subsequent call no longer retries the dead credential.
var ErrRevocationFailed = errors.New("OAuth refresh token revocation failed; the token may still be valid on the authorization server")
ErrRevocationFailed is wrapped into the result of Store.RemoveProfile (and equivalents) when the local profile was successfully removed but the best-effort revocation of the OAuth refresh token failed. The refresh token may still be live on the authorization server until its natural expiry. Callers should detect this via errors.Is and surface a "revoke manually" hint to the user.
Functions ¶
func CanonicalAPIURL ¶ added in v1.15.0
CanonicalAPIURL normalises an API URL into the key form used by OAuthClientStore. It lowercases the scheme and host, strips userinfo (so credentials never reach the on-disk key), drops the port when it matches the scheme default (so "https://api.example.com" and "https://api.example.com:443" share one cache entry), normalises the path with path.Clean, trims a trailing slash from the path, and drops query and fragment components. Returns an error when the input is not a parseable absolute URL.
Example ¶
package main
import (
"fmt"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
key, err := profiles.CanonicalAPIURL("HTTPS://API.EU-WEST-1.AWS.DASH0.COM/?x=1#frag")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(key)
}
Output: https://api.eu-west-1.aws.dash0.com
func WithConfiguration ¶
func WithConfiguration(ctx context.Context, cfg *Configuration) context.Context
WithConfiguration returns a new context with the given configuration stored.
Example ¶
package main
import (
"context"
"fmt"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
cfg := &profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_example-token",
Dataset: "production",
}
ctx := profiles.WithConfiguration(context.Background(), cfg)
retrieved := profiles.FromContext(ctx)
fmt.Println(retrieved.ApiUrl)
}
Output: https://api.eu-west-1.aws.dash0.com
Types ¶
type Configuration ¶
type Configuration struct {
ApiUrl string `json:"apiUrl"`
AuthToken string `json:"authToken"`
OtlpUrl string `json:"otlpUrl,omitempty"`
Dataset string `json:"dataset,omitempty"`
// OAuth is set when the profile should authorize using an OAuth access token.
// Potentially refreshed when close to the expiry.
// The actual OAuth access token is located within the AuthToken field.
OAuth *OAuthState `json:"oauth,omitempty"`
}
Configuration represents a Dash0 configuration with connection parameters.
func FromContext ¶
func FromContext(ctx context.Context) *Configuration
FromContext retrieves the configuration from ctx, or nil if not present.
func ResolveConfiguration ¶
func ResolveConfiguration(apiUrl, authToken string, opts ...StoreOption) (*Configuration, error)
ResolveConfiguration loads the active profile, applies environment variable overrides, then applies the given parameter overrides on top. Non-empty parameters take highest precedence.
This is a convenience function that creates a temporary Store internally. Pass StoreOption values (e.g. WithConfigDir) to control how the profile store is constructed.
OAuth refresh, if needed, uses context.Background; use ResolveConfigurationContext to plumb a cancellable context.
Example (ParameterOverride) ¶
package main
import (
"fmt"
"os"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
// Parameters take highest precedence, overriding both profiles and env vars.
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewStore(profiles.WithConfigDir(configDir))
_ = store.AddProfile(profiles.Profile{
Name: "dev",
Configuration: profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_dev-token",
},
})
cfg, err := profiles.ResolveConfiguration(
"https://api.us-west-2.aws.dash0.com", // overrides profile's API URL
"", // falls back to profile's auth token
profiles.WithConfigDir(configDir),
)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(cfg.ApiUrl)
fmt.Println(cfg.AuthToken)
}
Output: https://api.us-west-2.aws.dash0.com auth_dev-token
func ResolveConfigurationContext ¶ added in v1.15.0
func ResolveConfigurationContext(ctx context.Context, apiUrl, authToken string, opts ...StoreOption) (*Configuration, error)
ResolveConfigurationContext is the context-aware variant of ResolveConfiguration.
Example ¶
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewStore(profiles.WithConfigDir(configDir))
_ = store.AddProfile(profiles.Profile{
Name: "dev",
Configuration: profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_dev-token",
},
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cfg, err := profiles.ResolveConfigurationContext(ctx, "", "",
profiles.WithConfigDir(configDir),
)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(cfg.ApiUrl)
}
Output: https://api.eu-west-1.aws.dash0.com
func ResolveConfigurationWithOtlp ¶
func ResolveConfigurationWithOtlp(apiUrl, authToken, otlpUrl, dataset string, opts ...StoreOption) (*Configuration, error)
ResolveConfigurationWithOtlp is like ResolveConfiguration but also accepts OTLP URL and dataset overrides.
OAuth refresh, if needed, uses context.Background; use ResolveConfigurationWithOtlpContext to plumb a cancellable context.
func ResolveConfigurationWithOtlpContext ¶ added in v1.15.0
func ResolveConfigurationWithOtlpContext(ctx context.Context, apiUrl, authToken, otlpUrl, dataset string, opts ...StoreOption) (*Configuration, error)
ResolveConfigurationWithOtlpContext is the context-aware variant of ResolveConfigurationWithOtlp.
Example ¶
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewStore(profiles.WithConfigDir(configDir))
_ = store.AddProfile(profiles.Profile{
Name: "dev",
Configuration: profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_dev-token",
},
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cfg, err := profiles.ResolveConfigurationWithOtlpContext(ctx,
"", "",
"https://ingress.eu-west-1.aws.dash0.com",
"production",
profiles.WithConfigDir(configDir),
)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(cfg.OtlpUrl, cfg.Dataset)
}
Output: https://ingress.eu-west-1.aws.dash0.com production
func (*Configuration) ClientOptions ¶
func (cfg *Configuration) ClientOptions() []dash0.ClientOption
ClientOptions returns dash0.ClientOption values that configure a client from this Configuration. Non-empty fields are mapped as follows:
- ApiUrl -> dash0.WithApiUrl
- AuthToken -> dash0.WithAuthToken
- OtlpUrl -> dash0.WithOtlpEndpoint with dash0.OtlpEncodingJson
The Dataset field is not mapped because it is a per-request parameter, not a client-level setting. Use Configuration.DatasetPtr to convert it for API calls.
Callers can append additional options to override or supplement the returned slice:
opts := cfg.ClientOptions()
opts = append(opts, dash0.WithUserAgent("my-tool/1.0"))
client, err := dash0.NewClient(opts...)
Example ¶
package main
import (
"fmt"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
cfg := &profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_example-token",
}
opts := cfg.ClientOptions()
fmt.Printf("produced %d client options\n", len(opts))
}
Output: produced 2 client options
func (*Configuration) DatasetPtr ¶
func (cfg *Configuration) DatasetPtr() *string
DatasetPtr returns the dataset as a *string suitable for Dash0 API calls. It returns nil for empty strings and "default", matching dash0.DatasetPtr.
Example ¶
package main
import (
"fmt"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
cfg := &profiles.Configuration{Dataset: "production"}
ptr := cfg.DatasetPtr()
fmt.Println(*ptr)
cfgDefault := &profiles.Configuration{Dataset: "default"}
ptrDefault := cfgDefault.DatasetPtr()
fmt.Println(ptrDefault)
}
Output: production <nil>
type OAuthClientRecord ¶ added in v1.15.0
type OAuthClientRecord struct {
ClientID string `json:"clientId"`
RegistrationAccessToken string `json:"registrationAccessToken,omitempty"`
RedirectURI string `json:"redirectUri"`
}
OAuthClientRecord is a cached dynamic client registration (RFC 7591).
type OAuthClientStore ¶ added in v1.15.0
type OAuthClientStore struct {
// contains filtered or unexported fields
}
OAuthClientStore caches dynamic client registrations keyed by canonical API URL. The file is created mode 0600 because RegistrationAccessToken (RFC 7592) is a long-lived management credential.
In-process mutation (Get/Put/Delete) is serialized by mu; cross-process mutation is serialized via an OS-level advisory lock on [oauthClientsLockFileName] so two CLI invocations sharing the same config directory cannot lose-update each other's RegistrationAccessToken values.
func NewOAuthClientStore ¶ added in v1.15.0
func NewOAuthClientStore(opts ...StoreOption) (*OAuthClientStore, error)
NewOAuthClientStore mirrors NewStore: explicit WithConfigDir > DASH0_CONFIG_DIR > ~/.dash0/. Same best-effort hygiene as NewStore: best-effort dir-mode tightening and stale-tempfile cleanup when the directory already exists.
Example ¶
package main
import (
"fmt"
"os"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, err := profiles.NewOAuthClientStore(profiles.WithConfigDir(configDir))
if err != nil {
fmt.Println("error:", err)
return
}
_, found, err := store.Get("https://api.eu-west-1.aws.dash0.com")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("found:", found)
}
Output: found: false
func (*OAuthClientStore) Delete ¶ added in v1.15.0
func (s *OAuthClientStore) Delete(apiURL string) error
Delete removes the record for apiURL. A miss is a no-op. Cross-process serialization mirrors OAuthClientStore.Put.
Example ¶
package main
import (
"fmt"
"os"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewOAuthClientStore(profiles.WithConfigDir(configDir))
_ = store.Put("https://api.eu-west-1.aws.dash0.com", profiles.OAuthClientRecord{
ClientID: "client-123",
RedirectURI: "http://localhost:8080/callback",
})
if err := store.Delete("https://api.eu-west-1.aws.dash0.com"); err != nil {
fmt.Println("error:", err)
return
}
_, found, _ := store.Get("https://api.eu-west-1.aws.dash0.com")
fmt.Println("found after delete:", found)
}
Output: found after delete: false
func (*OAuthClientStore) Get ¶ added in v1.15.0
func (s *OAuthClientStore) Get(apiURL string) (OAuthClientRecord, bool, error)
Get returns the cached record for apiURL, or (zero, false, nil) on miss. A missing file is a miss, not an error. A corrupt file returns ErrOAuthClientsFileCorrupt wrapped with the quarantine path so the operator can decide whether to investigate.
Example ¶
package main
import (
"fmt"
"os"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewOAuthClientStore(profiles.WithConfigDir(configDir))
_ = store.Put("https://api.eu-west-1.aws.dash0.com", profiles.OAuthClientRecord{
ClientID: "client-123",
RedirectURI: "http://localhost:8080/callback",
})
rec, found, err := store.Get("https://api.eu-west-1.aws.dash0.com")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(found, rec.ClientID)
}
Output: true client-123
func (*OAuthClientStore) Put ¶ added in v1.15.0
func (s *OAuthClientStore) Put(apiURL string, rec OAuthClientRecord) error
Put upserts a record for apiURL. The read-modify-write sequence is guarded by both an in-process mutex and a cross-process advisory lock on [oauthClientsLockFileName] so two CLI invocations sharing the same config directory cannot lose-update each other's RegistrationAccessToken values. If the on-disk file is corrupt it is quarantined (renamed with a timestamp suffix) before the new record is written; the previous registrations are lost from the active file but preserved on disk for manual recovery.
Example ¶
package main
import (
"fmt"
"os"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, err := profiles.NewOAuthClientStore(profiles.WithConfigDir(configDir))
if err != nil {
fmt.Println("error:", err)
return
}
if err := store.Put("https://api.eu-west-1.aws.dash0.com", profiles.OAuthClientRecord{
ClientID: "client-123",
RedirectURI: "http://localhost:8080/callback",
}); err != nil {
fmt.Println("error:", err)
return
}
rec, found, _ := store.Get("https://api.eu-west-1.aws.dash0.com")
fmt.Println(found, rec.ClientID)
}
Output: true client-123
type OAuthState ¶ added in v1.15.0
type OAuthState struct {
ClientID string `json:"clientId,omitempty"`
RefreshToken string `json:"refreshToken,omitempty"`
ExpiresAt time.Time `json:"expiresAt,omitzero"`
}
OAuthState carries the OAuth-specific state that must survive across CLI invocations: the dynamic client identifier issued at registration (RFC 7591), the long-lived refresh token used to mint new access tokens, and the access token's expiry deadline. The current access token lives in Configuration.AuthToken; OAuthState only describes how to refresh it.
type Profile ¶
type Profile struct {
Name string `json:"name"`
Configuration Configuration `json:"configuration"`
}
Profile represents a named configuration profile.
type ProfilesFile ¶
type ProfilesFile struct {
Profiles []Profile `json:"profiles"`
}
ProfilesFile represents the file storing multiple profiles.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store handles profile storage and retrieval.
Cross-process refresh: Store serializes OAuth token refreshes both within a process (via an in-memory mutex) and across processes that share the same config directory (via an OS-level advisory lock on .profile-lock, backed by github.com/gofrs/flock).
func NewStore ¶
func NewStore(opts ...StoreOption) (*Store, error)
NewStore creates a new profile store.
The configuration directory is resolved in this order:
- Explicit WithConfigDir option (if provided).
- The DASH0_CONFIG_DIR environment variable (if set).
- ~/.dash0/ (default).
Best-effort hygiene on the resolved directory: the mode is tightened to [configDirMode] (0700) when broader, and any leftover writeFileAtomic temp files older than [staleTempFileAge] are removed. SIGKILL or a panic mid-write skips the deferred cleanup in writeFileAtomic, so .tmp-* files can accumulate. Both steps tolerate a missing directory (it will be created later by writeFileAtomic when the first write happens).
Example ¶
package main
import (
"fmt"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
svc, err := profiles.NewStore()
if err != nil {
fmt.Println("error:", err)
return
}
allProfiles, err := svc.GetProfiles()
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("found %d profiles\n", len(allProfiles))
}
Output:
Example (WithConfigDir) ¶
package main
import (
"fmt"
"os"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
svc, err := profiles.NewStore(profiles.WithConfigDir(configDir))
if err != nil {
fmt.Println("error:", err)
return
}
allProfiles, err := svc.GetProfiles()
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("found %d profiles\n", len(allProfiles))
}
Output: found 0 profiles
func (*Store) AddProfile ¶
AddProfile adds a new profile to the configuration. If a profile with the same name already exists, it is replaced. When adding the first profile, it is automatically set as active.
The read-modify-write sequence is serialized cross-process via the .profile-lock sentinel; see Store for the locking model. Lock acquisition uses context.Background; callers that need to bound the wait should use Store.AddProfileContext.
func (*Store) AddProfileContext ¶ added in v1.15.0
AddProfileContext is the context-aware variant of Store.AddProfile. The ctx bounds the wait to acquire the cross-process [.profile-lock]. Returns an error from [validateProfileName] when the profile name is empty, contains control characters or path separators, starts with '.', or exceeds MaxProfileNameLength.
Example ¶
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewStore(profiles.WithConfigDir(configDir))
// The Context variant lets the caller bound the wait for the
// cross-process .profile-lock acquisition — useful when a long-running
// agent flow already has a deadline context to honor.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := store.AddProfileContext(ctx, profiles.Profile{
Name: "dev",
Configuration: profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_dev-token",
},
})
if err != nil {
fmt.Println("error:", err)
return
}
all, _ := store.GetProfiles()
fmt.Println("count:", len(all))
}
Output: count: 1
func (*Store) GetActiveConfiguration ¶
func (s *Store) GetActiveConfiguration() (*Configuration, error)
GetActiveConfiguration returns the currently active configuration. Environment variables take precedence over the active profile. If the active profile uses OAuth, the access token is refreshed when close to expiry, using context.Background for the refresh request. Use Store.GetActiveConfigurationContext to plumb a cancellable context through the refresh.
Example ¶
package main
import (
"fmt"
"os"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
// Create a temporary config directory with a profile.
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewStore(profiles.WithConfigDir(configDir))
_ = store.AddProfile(profiles.Profile{
Name: "dev",
Configuration: profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_dev-token",
Dataset: "staging",
},
})
cfg, err := store.GetActiveConfiguration()
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(cfg.ApiUrl)
fmt.Println(cfg.Dataset)
}
Output: https://api.eu-west-1.aws.dash0.com staging
Example (EnvVarOverride) ¶
package main
import (
"fmt"
"os"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
// Environment variables override values from the active profile.
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewStore(profiles.WithConfigDir(configDir))
_ = store.AddProfile(profiles.Profile{
Name: "dev",
Configuration: profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_dev-token",
Dataset: "staging",
},
})
// DASH0_DATASET overrides the profile's dataset.
_ = os.Setenv(profiles.EnvDataset, "production")
defer func() { _ = os.Unsetenv(profiles.EnvDataset) }()
cfg, err := store.GetActiveConfiguration()
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(cfg.ApiUrl)
fmt.Println(cfg.Dataset)
}
Output: https://api.eu-west-1.aws.dash0.com production
func (*Store) GetActiveConfigurationContext ¶ added in v1.15.0
func (s *Store) GetActiveConfigurationContext(ctx context.Context) (*Configuration, error)
GetActiveConfigurationContext is the context-aware variant of Store.GetActiveConfiguration. The ctx is propagated through the OAuth refresh round-trip so a hung authorization server does not pin the caller for the full HTTP timeout.
Example ¶
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
// The Context variant plumbs cancellation through the OAuth refresh
// round-trip so a hung authorization server cannot pin the caller for
// the full HTTP timeout.
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewStore(profiles.WithConfigDir(configDir))
_ = store.AddProfile(profiles.Profile{
Name: "dev",
Configuration: profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_dev-token",
},
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cfg, err := store.GetActiveConfigurationContext(ctx)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(cfg.ApiUrl)
}
Output: https://api.eu-west-1.aws.dash0.com
func (*Store) GetActiveProfile ¶
GetActiveProfile returns the currently active profile.
func (*Store) GetProfiles ¶
GetProfiles returns all available profiles.
func (*Store) RemoveProfile ¶
RemoveProfile removes a profile from the configuration. If the profile has OAuth state, the refresh token is revoked before removal, using context.Background for the revoke request. Use Store.RemoveProfileContext to plumb a cancellable context.
Revocation is best-effort: if it fails, the local profile is still removed and the returned error wraps ErrRevocationFailed. Callers can detect this with errors.Is and surface a "revoke manually" hint -- the refresh token may still be live on the authorization server.
If the removed profile was the active profile, the first remaining profile becomes active.
Example ¶
package main
import (
"fmt"
"os"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewStore(profiles.WithConfigDir(configDir))
_ = store.AddProfile(profiles.Profile{
Name: "dev",
Configuration: profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_dev-token",
},
})
if err := store.RemoveProfile("dev"); err != nil {
fmt.Println("error:", err)
return
}
all, _ := store.GetProfiles()
fmt.Println("remaining:", len(all))
}
Output: remaining: 0
func (*Store) RemoveProfileContext ¶ added in v1.15.0
RemoveProfileContext is the context-aware variant of Store.RemoveProfile.
The token-revocation HTTP round-trip runs AFTER the in-process and cross-process locks are released, so a hung authorization server cannot pin sibling CLI invocations sharing the same config directory. The cost is that the locks no longer cover the revocation; an interleaved process observing the now-removed profile will not see the revoke in-flight. That is acceptable because the refresh token is already gone from disk before revocation begins; the worst-case server-side residue is the unrevoked refresh token until its natural expiry.
Example ¶
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewStore(profiles.WithConfigDir(configDir))
_ = store.AddProfile(profiles.Profile{
Name: "dev",
Configuration: profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_dev-token",
},
})
// The Context variant plumbs cancellation through the OAuth refresh-token
// revocation HTTP call, so the caller can give up on a slow IdP.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := store.RemoveProfileContext(ctx, "dev"); err != nil {
fmt.Println("error:", err)
return
}
all, _ := store.GetProfiles()
fmt.Println("remaining:", len(all))
}
Output: remaining: 0
func (*Store) SetActiveProfile ¶
SetActiveProfile sets the active profile by name. Returns ErrProfileNotFound if no profile with the given name exists.
The change is serialized cross-process via the .profile-lock sentinel; see Store for the locking model. Lock acquisition uses context.Background; callers that need to bound the wait should use Store.SetActiveProfileContext.
func (*Store) SetActiveProfileContext ¶ added in v1.15.0
SetActiveProfileContext is the context-aware variant of Store.SetActiveProfile. The ctx bounds the wait to acquire the cross-process [.profile-lock].
Example ¶
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewStore(profiles.WithConfigDir(configDir))
_ = store.AddProfile(profiles.Profile{
Name: "dev",
Configuration: profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_dev-token",
},
})
_ = store.AddProfile(profiles.Profile{
Name: "prod",
Configuration: profiles.Configuration{
ApiUrl: "https://api.us-west-2.aws.dash0.com",
AuthToken: "auth_prod-token",
},
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := store.SetActiveProfileContext(ctx, "prod"); err != nil {
fmt.Println("error:", err)
return
}
active, _ := store.GetActiveProfile()
fmt.Println(active.Name)
}
Output: prod
func (*Store) UpdateProfile ¶
func (s *Store) UpdateProfile(name string, updateFn func(*Configuration)) error
UpdateProfile finds a profile by name and applies updateFn to its configuration, then saves. Returns an error if no profile with the given name exists.
The read-modify-write sequence is serialized cross-process via the .profile-lock sentinel; see Store for the locking model. Lock acquisition uses context.Background; callers that need to bound the wait should use Store.UpdateProfileContext.
func (*Store) UpdateProfileContext ¶ added in v1.15.0
func (s *Store) UpdateProfileContext(ctx context.Context, name string, updateFn func(*Configuration)) error
UpdateProfileContext is the context-aware variant of Store.UpdateProfile. The ctx bounds the wait to acquire the cross-process [.profile-lock].
Example ¶
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/dash0hq/dash0-api-client-go/profiles"
)
func main() {
configDir, _ := os.MkdirTemp("", "dash0-example-*")
defer func() { _ = os.RemoveAll(configDir) }()
store, _ := profiles.NewStore(profiles.WithConfigDir(configDir))
_ = store.AddProfile(profiles.Profile{
Name: "dev",
Configuration: profiles.Configuration{
ApiUrl: "https://api.eu-west-1.aws.dash0.com",
AuthToken: "auth_dev-token",
},
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := store.UpdateProfileContext(ctx, "dev", func(cfg *profiles.Configuration) {
cfg.Dataset = "production"
}); err != nil {
fmt.Println("error:", err)
return
}
all, _ := store.GetProfiles()
fmt.Println(all[0].Configuration.Dataset)
}
Output: production
type StoreOption ¶
type StoreOption func(*storeConfig)
StoreOption configures a Store.
func WithConfigDir ¶
func WithConfigDir(dir string) StoreOption
WithConfigDir overrides the default configuration directory (~/.dash0/). This is useful for testing or for applications that store profiles in a non-standard location.