Documentation
¶
Overview ¶
Package session is the dtctl session layer: everything a Dynatrace CLI tool needs to go from "the user's machine" to "an authenticated client for the right tenant".
It owns the shared state contract (docs/dev/CONFIG_CONTRACT.md):
- the config-file model — contexts, tokens, preferences, safety levels — with discovery, env expansion, schema versioning, and round-trip preservation of unknown fields (golden fixtures in testdata/contract)
- credential resolution — OS keyring (service "dtctl"), file-based OAuth store fallback, inline config tokens
dtctl and dtctl-* plugins all consume this package, which is what makes "drop into any of them wherever dtctl points" work. Configuration management (creating contexts, login flows) is dtctl's job alone; other consumers treat the config file as read-only.
The alias, hook, and spill fields carried by Config are CLI-owned schema data: this package round-trips and exposes them, but never executes them — alias expansion and hook execution live in dtctl's cmd layer.
Index ¶
- Constants
- Variables
- func BuiltinProfileNames() []string
- func CacheDir() string
- func CheckKeyring() error
- func ConfigDir() string
- func DataDir() string
- func DecodeRefreshTokenExpiry(refreshToken string) (time.Time, bool)
- func DefaultConfigPath() string
- func EnsureKeyringCollection(ctx context.Context) error
- func ExtractUserIDFromToken(token string) (string, error)
- func FindLocalConfig() string
- func GetTokenForContext(cfg *Config, environmentURL, tokenRef string) (string, error)
- func GetTokenWithFallback(cfg *Config, tokenRef string) (string, error)
- func GetTokenWithOAuthSupport(cfg *Config, tokenRef string) (string, error)
- func IsFileTokenStorage() bool
- func IsKeyringAvailable() bool
- func IsOAuthStorageAvailable() bool
- func IsOAuthToken(tokenName string) bool
- func IsPlatformToken(token string) bool
- func IsTokenExpired(tokens *TokenSet) bool
- func KeyringBackend() string
- func MigrateTokensToKeyring(cfg *Config) (int, error)
- func OAuthStorageBackend() string
- func RefreshedTokenForContext(cfg *Config, environmentURL, tokenRef, rejected string) (string, error)
- func StateDir() string
- func ValidateAliasName(name string) error
- type AliasEntry
- type AliasFile
- type CheckResult
- type Checker
- type Client
- func (c *Client) BaseURL() string
- func (c *Client) CurrentUser() (*UserInfo, error)
- func (c *Client) CurrentUserID() (string, error)
- func (c *Client) EnableTokenRefresh(resolve func(rejected string) (string, error))
- func (c *Client) HTTP() *resty.Client
- func (c *Client) SetToken(token string)
- func (c *Client) SetVerbosity(level int)
- func (c *Client) Token() string
- type ClientOption
- type Config
- func (c *Config) CurrentContextObj() (*Context, error)
- func (c *Config) DeleteAlias(name string) error
- func (c *Config) DeleteContext(name string) error
- func (c *Config) EffectiveSpillConfig() SpillConfig
- func (c *Config) ExportAliases(path string) error
- func (c *Config) GetAlias(name string) (string, bool)
- func (c *Config) GetContext(name string) (*NamedContext, error)
- func (c *Config) GetPostApplyHook() string
- func (c *Config) GetPreApplyHook() string
- func (c *Config) GetToken(tokenRef string) (string, error)
- func (c *Config) IgnoredExecKeys() bool
- func (c *Config) ImportAliases(path string, overwrite bool, builtinCheck func(string) bool) ([]string, error)
- func (c *Config) IsLocal() bool
- func (c *Config) ListAliases() []AliasEntry
- func (c *Config) LocalConfigPath() string
- func (c *Config) MustGetToken(tokenRef string) string
- func (c *Config) ProfileExists(name string) bool
- func (c *Config) PruneEmptyEnvironments(keepContext string, placeholderNames map[string]bool)
- func (c *Config) ResolveProfile() (*Profile, error)
- func (c *Config) Save() error
- func (c *Config) SaveTo(path string) error
- func (c *Config) SetAlias(name, expansion string, builtinCheck func(string) bool) error
- func (c *Config) SetContext(name, environment, tokenRef string)
- func (c *Config) SetContextWithOptions(name, environment, tokenRef string, opts *ContextOptions)
- func (c *Config) SetToken(name, token string) error
- type Context
- type ContextOptions
- type Environment
- type Hooks
- type NamedContext
- type NamedToken
- type OAuthConfig
- type OAuthFileStore
- type OAuthFlow
- type OAuthUserInfo
- type Operation
- type Preferences
- type Profile
- type ResourceOwnership
- type SafetyError
- type SafetyLevel
- type SpillConfig
- type StoredToken
- type TokenManager
- func (tm *TokenManager) CachedScopes(tokenName string) []string
- func (tm *TokenManager) DeleteToken(tokenName string) error
- func (tm *TokenManager) GetToken(tokenName string) (string, error)
- func (tm *TokenManager) GetTokenInfo(tokenName string) (*StoredToken, error)
- func (tm *TokenManager) RefreshToken(tokenName string) (*TokenSet, error)
- func (tm *TokenManager) SaveToken(tokenName string, tokens *TokenSet) error
- func (tm *TokenManager) SetWarnFunc(fn func(format string, args ...any))
- type TokenSet
- type TokenStore
- type UserInfo
Constants ¶
const ( // KeyringService is the service name used for keyring storage KeyringService = "dtctl" // EnvDisableKeyring can be set to disable keyring integration EnvDisableKeyring = "DTCTL_DISABLE_KEYRING" // EnvTokenStorage controls the OAuth token storage backend. // Set to "file" to use file-based storage instead of the OS keyring. // This is useful for headless Linux, WSL, CI/CD, and container environments // where a system keyring is not available. // // Valid values: "keyring" (default), "file" EnvTokenStorage = "DTCTL_TOKEN_STORAGE" // ErrMsgCollectionUnlock is the error substring returned by the Secret Service // backend when a persistent keyring collection does not exist or cannot be // unlocked. Centralised here so callers match on a single constant instead // of a fragile raw string. ErrMsgCollectionUnlock = "failed to unlock correct collection" )
const ( // OAuthTokenPrefix is prepended to OAuth token names in keyring OAuthTokenPrefix = "oauth:" // TokenRefreshBuffer is how long before expiry we refresh tokens TokenRefreshBuffer = 5 * time.Minute )
const CurrentAPIVersion = "v1"
CurrentAPIVersion is the config schema version this build reads and writes. The schema evolves additively within a version: unknown fields are ignored on load and preserved on save (see SaveTo), so the version only changes on a breaking redefinition of existing fields. See docs/dev/CONFIG_CONTRACT.md.
const EnvConfig = "DTCTL_CONFIG"
EnvConfig names an explicit config file, equivalent to passing --config. When set, dtctl loads exactly that file and treats it as trusted (its aliases and apply hooks are honored), skipping auto-discovery entirely. This is the supported way to run in a prepared, trusted workspace — e.g. kb-run creates a clean directory with a .dtctl.yaml and exports this variable, so an agent invoking `dtctl apply` runs the intended hooks without a --config flag. Because it names a specific operator-chosen file (not a directory to search), it does not reopen the untrusted-working-directory vector that IsLocal() guards against: a stray .dtctl.yaml elsewhere on disk can never be picked up.
const LocalConfigName = ".dtctl.yaml"
LocalConfigName is the name of the per-project config file
const ProfileEnvVar = "DTCTL_PROFILE"
ProfileEnvVar is the environment variable that selects the active command profile, taking precedence over any context-bound profile.
const ProfileFull = "full"
ProfileFull is the reserved name for the unrestricted command tree. Selecting it (via env or context) is equivalent to selecting no profile at all.
Variables ¶
var ErrOAuthSessionRevoked = errors.New("OAuth session revoked")
ErrOAuthSessionRevoked indicates the cached OAuth refresh token has been invalidated server-side (HTTP 400 invalid_grant). Callers should evict the cache and fall back to a non-OAuth credential where available.
Functions ¶
func BuiltinProfileNames ¶
func BuiltinProfileNames() []string
BuiltinProfileNames returns the sorted names of the built-in presets (excluding the special "full" profile). Used for docs/validation surfaces.
func CacheDir ¶
func CacheDir() string
CacheDir returns the cache directory path following XDG Base Directory spec
func CheckKeyring ¶
func CheckKeyring() error
CheckKeyring probes the OS keyring and returns nil if it is usable, or a descriptive error explaining why it is not.
func ConfigDir ¶
func ConfigDir() string
ConfigDir returns the config directory path following XDG Base Directory spec
func DataDir ¶
func DataDir() string
DataDir returns the data directory path following XDG Base Directory spec
func DecodeRefreshTokenExpiry ¶
DecodeRefreshTokenExpiry returns the exp claim from a JWT refresh token. Returns zero time and false if the token is not a decodable JWT with an exp claim.
func DefaultConfigPath ¶
func DefaultConfigPath() string
DefaultConfigPath returns the default config file path following XDG Base Directory spec Returns: XDG_CONFIG_HOME/dtctl/config (typically ~/.config/dtctl/config)
func EnsureKeyringCollection ¶
EnsureKeyringCollection checks whether a usable Secret Service collection exists and, if not, creates a persistent "login" collection. On Linux/WSL gnome-keyring may start with only a transient "session" collection; this function creates the permanent one, which may trigger an OS password prompt.
The provided context allows the caller to cancel the operation (e.g. via Ctrl+C); without cancellation the function polls for up to 2 minutes waiting for the user to complete the password prompt.
func ExtractUserIDFromToken ¶
ExtractUserIDFromToken extracts the user ID (sub claim) from a JWT token.
func FindLocalConfig ¶
func FindLocalConfig() string
FindLocalConfig searches for a .dtctl.yaml file starting from the current directory and walking up to the root. Returns empty string if not found.
func GetTokenForContext ¶
GetTokenForContext retrieves a token from config with OAuth token refresh support, detecting the OAuth configuration from the supplied environment URL. Use this when the token may belong to a context other than the current one (e.g. `dtctl ctx token <name>`), since the OAuth environment determines both the refresh endpoint and the storage key.
func GetTokenWithFallback ¶
GetTokenWithFallback tries to get a token from keyring first, then falls back to config
func GetTokenWithOAuthSupport ¶
GetTokenWithOAuthSupport retrieves a token from config with OAuth token refresh support, using the current context's environment to detect the OAuth configuration.
func IsFileTokenStorage ¶
func IsFileTokenStorage() bool
IsFileTokenStorage reports whether the user has explicitly opted into file-based OAuth token storage via DTCTL_TOKEN_STORAGE=file.
func IsKeyringAvailable ¶
func IsKeyringAvailable() bool
IsKeyringAvailable checks if keyring storage is available on this system
func IsOAuthStorageAvailable ¶
func IsOAuthStorageAvailable() bool
IsOAuthStorageAvailable reports whether OAuth tokens can be stored and retrieved — either via the OS keyring or file-based storage.
func IsOAuthToken ¶
IsOAuthToken checks if a token name refers to an OAuth token
func IsPlatformToken ¶
platformTokenPrefix identifies Dynatrace platform tokens — opaque bearer tokens (not JWTs). The /platform/metadata/v1/user endpoint requires the 'app-engine:apps:run' scope; platform tokens that lack it get a 403, and the token itself cannot be JWT-decoded as a user-ID fallback. IsPlatformToken reports whether token is a Dynatrace platform token.
func IsTokenExpired ¶
IsTokenExpired checks if a token is expired
func KeyringBackend ¶
func KeyringBackend() string
KeyringBackend returns a string describing the keyring backend in use
func MigrateTokensToKeyring ¶
MigrateTokensToKeyring migrates tokens from config file to keyring Returns the number of tokens migrated and any error
func OAuthStorageBackend ¶
func OAuthStorageBackend() string
OAuthStorageBackend returns a human-readable label describing where OAuth tokens are (or will be) stored.
func RefreshedTokenForContext ¶
func RefreshedTokenForContext(cfg *Config, environmentURL, tokenRef, rejected string) (string, error)
RefreshedTokenForContext re-resolves the context's bearer token after a request was rejected with HTTP 401. GetTokenForContext already refreshes tokens that are near expiry locally; when the cached token still looks valid but the server rejected it anyway (clock skew, stale ExpiresAt in compact keyring storage), the OAuth refresh is forced so the retry never re-sends the token the server just bounced. Static API tokens come back unchanged — the caller sees rejected == fresh and gives up.
func StateDir ¶
func StateDir() string
StateDir returns the state directory path following XDG Base Directory spec (persistent but disposable data: history, logs). Typically ~/.local/state/dtctl.
func ValidateAliasName ¶
ValidateAliasName checks that an alias name is syntactically valid.
Types ¶
type AliasEntry ¶
AliasEntry is a single alias for display purposes.
type CheckResult ¶
CheckResult contains the result of a safety check
type Checker ¶
type Checker struct {
// contains filtered or unexported fields
}
Checker performs safety level checks for operations
func NewChecker ¶
NewChecker creates a new safety checker for a context
func NewCheckerWithLevel ¶
func NewCheckerWithLevel(contextName string, level SafetyLevel) *Checker
NewCheckerWithLevel creates a new safety checker with an explicit safety level
func (*Checker) Check ¶
func (c *Checker) Check(op Operation, ownership ResourceOwnership) CheckResult
Check verifies if an operation is allowed under the current safety level
func (*Checker) CheckError ¶
func (c *Checker) CheckError(op Operation, ownership ResourceOwnership) error
CheckError performs a safety check and returns a *SafetyError if not allowed.
func (*Checker) ContextName ¶
ContextName returns the context name
func (*Checker) FormatError ¶
func (c *Checker) FormatError(result CheckResult) string
FormatError formats a CheckResult as an error message
func (*Checker) SafetyLevel ¶
func (c *Checker) SafetyLevel() SafetyLevel
SafetyLevel returns the current safety level
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the authenticated HTTP client for a Dynatrace environment.
func NewClient ¶
func NewClient(baseURL, token string, opts ...ClientOption) (*Client, error)
NewClient creates a new client with base URL and token.
func NewClientFromConfig ¶
func NewClientFromConfig(cfg *Config, opts ...ClientOption) (*Client, error)
NewClientFromConfig creates an authenticated client for the config's current context: OAuth-aware token resolution (keyring, file store, inline token), and re-resolution on 401 so long-running sessions survive OAuth access-token expiry.
func NewForTesting ¶
NewForTesting creates a client with retries disabled, suitable for unit tests that use httptest servers. This avoids the 3×1s retry wait on 500/429 responses.
func (*Client) CurrentUser ¶
CurrentUser fetches the current user info from the metadata API. Requires scope: app-engine:apps:run
func (*Client) CurrentUserID ¶
CurrentUserID returns the current user's ID. First tries the metadata API, falls back to JWT token decoding.
func (*Client) EnableTokenRefresh ¶
EnableTokenRefresh registers a retry-on-401 hook: resolve is called with the rejected token and must return a fresh one (typically by refreshing an expired OAuth access token). The request is retried only when a genuinely new token was obtained; static tokens and failed refreshes surface the original 401. Safe for concurrent requests — one refresh serves them all.
resolve runs while tokenMu is held, so Token/SetToken and other 401 handling on this client block for its duration — bounded by the refresh lock timeout plus one token-endpoint round-trip. That serialization is deliberate (concurrent requests would only collect more 401s until the refresh lands); embedders should not call Token from a latency-sensitive loop while requests are in flight.
func (*Client) SetToken ¶
SetToken updates the bearer token used for all subsequent HTTP requests. This is used to inject a freshly refreshed OAuth token without recreating the client.
func (*Client) SetVerbosity ¶
SetVerbosity sets the verbosity level for logging Level 0: normal (no debug output) Level 1: show request/response summary Level 2+: show full request/response details (sensitive headers always redacted)
type ClientOption ¶
type ClientOption func(*clientOptions)
ClientOption customizes client construction.
func WithUserAgent ¶
func WithUserAgent(ua string) ClientOption
WithUserAgent sets the full User-Agent product token (e.g. "dtctl/1.2.3"). The AI-agent-environment suffix is appended automatically.
func WithUserAgentProduct ¶
func WithUserAgentProduct(product, version string) ClientOption
WithUserAgentProduct sets the User-Agent from a product name and version, e.g. WithUserAgentProduct("dtctl-foo", "0.5.0") → "dtctl-foo/0.5.0".
type Config ¶
type Config struct {
APIVersion string `yaml:"apiVersion"`
Kind string `yaml:"kind"`
CurrentContext string `yaml:"current-context"`
Contexts []NamedContext `yaml:"contexts"`
Tokens []NamedToken `yaml:"tokens"`
Preferences Preferences `yaml:"preferences"`
Aliases map[string]string `yaml:"aliases,omitempty"`
// Spill holds the global result-spill settings (D15). Per-context overrides
// live on Context.Spill.
Spill SpillConfig `yaml:"spill,omitempty"`
// Profiles is the set of named command profiles (default-deny allowlists of
// commands). A profile is selected via DTCTL_PROFILE or a context binding;
// see profile.go and docs/dev/COMMAND_PROFILES_DESIGN.md.
Profiles map[string]Profile `yaml:"profiles,omitempty"`
// contains filtered or unexported fields
}
Config represents the dtctl configuration
func Load ¶
Load loads the configuration with the following precedence:
- Explicit config from the DTCTL_CONFIG environment variable (trusted)
- Local config (.dtctl.yaml in current directory or parent directories)
- Global config (XDG_CONFIG_HOME/dtctl/config)
If a local config is found, it is used exclusively (not merged with global).
Security: an auto-discovered local .dtctl.yaml is treated as untrusted (the classic "untrusted working directory / checked-out repo / shared dir" scenario). Code-execution keys — shell aliases and apply hooks — defined in such a config are never honored: alias resolution and hook execution check IsLocal() and skip them. These keys are honored only from the global config, an explicit --config file, or a config named by DTCTL_CONFIG (all loaded via LoadFrom without markLocal), which carry stronger ownership expectations. The keys are still loaded into the struct (and never mutated here) so that config-management commands round-trip the file without silently destroying a user's own aliases or hooks.
func LoadFromWithoutExpansion ¶
LoadFromWithoutExpansion loads the configuration from a specific path without expanding environment variables. Use this to inspect raw template values.
func LoadWithoutExpansion ¶
LoadWithoutExpansion loads the configuration without expanding environment variables, using the same search order as Load (local config, then global config).
func (*Config) CurrentContextObj ¶
CurrentContextObj returns the current context object
func (*Config) DeleteAlias ¶
DeleteAlias removes an alias by name. Returns an error if it does not exist.
func (*Config) DeleteContext ¶
DeleteContext removes a context by name. Returns an error if the context is not found.
func (*Config) EffectiveSpillConfig ¶
func (c *Config) EffectiveSpillConfig() SpillConfig
EffectiveSpillConfig merges the global spill config with the current context's override (context wins per field, D15). Env and flag layers are applied by the caller on top of this base.
func (*Config) ExportAliases ¶
ExportAliases writes aliases to a file in YAML format.
func (*Config) GetAlias ¶
GetAlias returns the expansion for an alias, or empty string if not found.
func (*Config) GetContext ¶
func (c *Config) GetContext(name string) (*NamedContext, error)
GetContext returns a named context by name
func (*Config) GetPostApplyHook ¶
GetPostApplyHook returns the effective post-apply hook command. Per-context hooks take precedence over global (preferences) hooks. The special value "none" explicitly disables the global hook for a context.
func (*Config) GetPreApplyHook ¶
GetPreApplyHook returns the effective pre-apply hook command. Per-context hooks take precedence over global (preferences) hooks. The special value "none" explicitly disables the global hook for a context.
func (*Config) GetToken ¶
GetToken retrieves a token by reference name. It first tries the OS keyring (checking both regular and OAuth tokens), then file-based OAuth token storage, then falls back to the config file.
func (*Config) IgnoredExecKeys ¶
IgnoredExecKeys reports whether code-execution keys (aliases, apply hooks) are present in the auto-discovered local config and are therefore ignored at runtime. See markLocal.
func (*Config) ImportAliases ¶
func (c *Config) ImportAliases(path string, overwrite bool, builtinCheck func(string) bool) ([]string, error)
ImportAliases reads aliases from a YAML file and merges them into the config. If overwrite is false, existing aliases are not replaced and conflicts are returned as a list of names.
func (*Config) IsLocal ¶
IsLocal reports whether the config was loaded from an auto-discovered local .dtctl.yaml (as opposed to the global config or an explicit --config file). Code-execution keys (aliases, apply hooks) are ignored when this is true.
func (*Config) ListAliases ¶
func (c *Config) ListAliases() []AliasEntry
ListAliases returns all aliases sorted alphabetically by name.
func (*Config) LocalConfigPath ¶
LocalConfigPath returns the path of the auto-discovered local .dtctl.yaml the config was loaded from, or "" if it was not loaded from a local config.
func (*Config) MustGetToken ¶
MustGetToken retrieves a token by reference name, returning empty string on error
func (*Config) ProfileExists ¶
ProfileExists reports whether a profile name is resolvable — either "full", a user-defined profile, or a built-in preset. Used by config-management commands to warn on likely typos when binding a profile to a context.
func (*Config) PruneEmptyEnvironments ¶
PruneEmptyEnvironments removes contexts whose names are in placeholderNames, except the named keepContext. Pass the context names from the raw (unexpanded) config file to avoid pruning contexts backed by currently-unset env vars.
func (*Config) ResolveProfile ¶
ResolveProfile determines the active profile using the precedence
DTCTL_PROFILE env > context-bound profile > none (= full)
It returns nil for the full (unrestricted) command tree — the default and backward-compatible behavior. A referenced profile name that does not exist (as a user profile or built-in preset) is a fast, explicit error rather than a silent fallback to full, which would be a surprising surface expansion.
func (*Config) SaveTo ¶
SaveTo saves the configuration to a specific path.
Contract rule (docs/dev/CONFIG_CONTRACT.md): fields unknown to this build — written by a newer dtctl or another schema-v1 writer — are grafted back from the file being overwritten, so an older writer never destroys them.
func (*Config) SetAlias ¶
SetAlias adds or updates an alias. Returns an error if the name is invalid. builtinCheck is called to verify the name does not shadow a built-in command.
func (*Config) SetContext ¶
SetContext creates or updates a context
func (*Config) SetContextWithOptions ¶
func (c *Config) SetContextWithOptions(name, environment, tokenRef string, opts *ContextOptions)
SetContextWithOptions creates or updates a context with optional fields
func (*Config) SetToken ¶
SetToken creates or updates a token. If keyring is available, the token is stored securely in the OS keyring and only a reference is kept in the config file. Any cached OAuth tokens for this credential name are invalidated so that a rotated platform token does not keep using a stale refresh token.
type Context ¶
type Context struct {
Environment string `yaml:"environment" table:"ENVIRONMENT"`
TokenRef string `yaml:"token-ref" table:"TOKEN-REF"`
SafetyLevel SafetyLevel `yaml:"safety-level,omitempty" table:"SAFETY-LEVEL"`
Description string `yaml:"description,omitempty" table:"DESCRIPTION,wide"`
AccountUUID string `yaml:"account-uuid,omitempty" table:"ACCOUNT-UUID,wide"`
// Profile binds a command profile to this context, restricting the visible
// command surface when the context is active (unless overridden by
// DTCTL_PROFILE). Empty means the full command tree. See profile.go.
Profile string `yaml:"profile,omitempty" table:"PROFILE,wide"`
Hooks Hooks `yaml:"hooks,omitempty"`
// Spill overrides the global spill settings for this context (D15). Nil
// fields inherit the global spill config.
Spill *SpillConfig `yaml:"spill,omitempty"`
}
Context holds the connection information for a Dynatrace environment
func (*Context) GetEffectiveSafetyLevel ¶
func (c *Context) GetEffectiveSafetyLevel() SafetyLevel
GetEffectiveSafetyLevel returns the effective safety level for a context If no safety level is set, returns the default (readwrite-all)
type ContextOptions ¶
type ContextOptions struct {
SafetyLevel SafetyLevel
Description string
Profile string
}
ContextOptions holds optional fields for context configuration
type Environment ¶
type Environment string
Environment represents a Dynatrace environment type
const ( EnvironmentProd Environment = "prod" EnvironmentDev Environment = "dev" EnvironmentHard Environment = "hard" )
func DetectEnvironment ¶
func DetectEnvironment(environmentURL string) Environment
DetectEnvironment determines the environment type from a Dynatrace URL
type Hooks ¶
type Hooks struct {
PreApply string `yaml:"pre-apply,omitempty"`
PostApply string `yaml:"post-apply,omitempty"`
}
Hooks holds hook commands for lifecycle events
type NamedContext ¶
type NamedContext struct {
Name string `yaml:"name" table:"NAME"`
Context Context `yaml:"context" table:"-"`
}
NamedContext holds a context with its name
type NamedToken ¶
NamedToken holds a token with its name
type OAuthConfig ¶
type OAuthConfig struct {
AuthURL string
TokenURL string
UserInfoURL string
ClientID string
// Scopes are requested during the interactive login flow only; token
// refresh re-issues the original grant's scopes, so refresh-only
// consumers (the 401-retry path, TokenManager auto-refresh) may leave
// them empty. The scope-composition tables live in dtctl's pkg/auth —
// callers running a login flow pass the composed set in.
Scopes []string
Port int
Environment Environment
SafetyLevel SafetyLevel
EnvironmentURL string
}
func DefaultOAuthConfig ¶
func DefaultOAuthConfig() *OAuthConfig
DefaultOAuthConfig returns the default OAuth configuration for production. No scopes are set — sufficient for refresh-only use (see OAuthConfig.Scopes).
func OAuthConfigForEnvironment ¶
func OAuthConfigForEnvironment(env Environment, safetyLevel SafetyLevel, scopes []string) *OAuthConfig
OAuthConfigForEnvironment creates an OAuth configuration for the specified environment. scopes may be nil for refresh-only use; login flows pass the scope set composed for the safety level (dtctl's pkg/auth owns that composition).
func OAuthConfigFromEnvironmentURL ¶
func OAuthConfigFromEnvironmentURL(environmentURL string, safetyLevel SafetyLevel, scopes []string) *OAuthConfig
OAuthConfigFromEnvironmentURL creates an OAuth configuration by detecting the environment from a URL. scopes may be nil for refresh-only use.
type OAuthFileStore ¶
type OAuthFileStore struct {
// contains filtered or unexported fields
}
OAuthFileStore provides file-based storage for OAuth tokens. Tokens are stored as individual JSON files under $XDG_DATA_HOME/dtctl/oauth-tokens/, with 0600 permissions (owner-only read/write).
This is used as a fallback when the OS keyring is unavailable (e.g. headless Linux, WSL, CI/CD environments, containers).
func NewOAuthFileStore ¶
func NewOAuthFileStore() *OAuthFileStore
NewOAuthFileStore creates a new file-based OAuth token store.
func NewOAuthFileStoreWithDir ¶
func NewOAuthFileStoreWithDir(dir string) *OAuthFileStore
NewOAuthFileStoreWithDir creates a file store using a specific directory (for testing).
func (*OAuthFileStore) DeleteToken ¶
func (fs *OAuthFileStore) DeleteToken(name string) error
DeleteToken removes a token file.
func (*OAuthFileStore) GetToken ¶
func (fs *OAuthFileStore) GetToken(name string) (string, error)
GetToken reads a token from a file.
func (*OAuthFileStore) SetToken ¶
func (fs *OAuthFileStore) SetToken(name, token string) error
SetToken writes a token to a file.
type OAuthFlow ¶
type OAuthFlow struct {
// contains filtered or unexported fields
}
func NewOAuthFlow ¶
func NewOAuthFlow(config *OAuthConfig) (*OAuthFlow, error)
func (*OAuthFlow) GetUserInfo ¶
func (f *OAuthFlow) GetUserInfo(accessToken string) (*OAuthUserInfo, error)
func (*OAuthFlow) RefreshToken ¶
type OAuthUserInfo ¶
type OAuthUserInfo struct {
Sub string `json:"sub"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
Name string `json:"name"`
}
OAuthUserInfo is the SSO userinfo-endpoint response (distinct from the platform metadata UserInfo returned by Client.CurrentUser).
type Operation ¶
type Operation string
Operation represents a type of operation that can be performed
const ( // OperationRead is a read-only operation OperationRead Operation = "read" // OperationCreate is a create operation OperationCreate Operation = "create" // OperationUpdate is an update operation OperationUpdate Operation = "update" // OperationDelete is a delete operation OperationDelete Operation = "delete" // OperationDeleteBucket is a bucket deletion operation (data loss) OperationDeleteBucket Operation = "delete-bucket" )
type Preferences ¶
type Preferences struct {
Output string `yaml:"output,omitempty"`
Editor string `yaml:"editor,omitempty"`
Hooks Hooks `yaml:"hooks,omitempty"`
}
Preferences holds user preferences
type Profile ¶
type Profile struct {
// Name is the profile's key in the profiles map (or the built-in preset
// name). It is populated at resolution time and never serialized — the map
// key already carries it.
Name string `yaml:"-"`
// Description is a human-readable summary of the profile's purpose.
Description string `yaml:"description,omitempty"`
// Commands is the allowlist of command-path prefixes. An entry matches a
// command when it equals or is a segment-prefix of that command's path, so
// listing a parent verb (e.g. "describe") includes its whole subtree. There
// is no denylist — everything not matched (and not always-available) is masked.
Commands []string `yaml:"commands,omitempty"`
}
Profile is a default-deny allowlist of commands. It shapes every discovery surface at once (--help, `dtctl commands`, shell completion) and hard-blocks invocation of any command outside the set. See docs/dev/COMMAND_PROFILES_DESIGN.md.
func (*Profile) Allows ¶
Allows reports whether a command identified by its space-joined path (relative to the root command, e.g. "get workflows") is visible/runnable under this profile. A command is allowed when:
- it is in the always-available set, or
- it is at or below an allowlisted entry (the entry is a segment-prefix of the path — listing "describe" allows "describe analyzer"), or
- it is an ancestor of an allowlisted entry (the path is a segment-prefix of the entry — a parent verb stays reachable so its allowed child can be run).
Everything else is masked.
type ResourceOwnership ¶
type ResourceOwnership int
ResourceOwnership indicates whether a resource is owned by the current user
const ( // OwnershipUnknown means ownership cannot be determined OwnershipUnknown ResourceOwnership = iota // OwnershipOwn means the resource is owned by the current user OwnershipOwn OwnershipShared )
func DetermineOwnership ¶
func DetermineOwnership(resourceOwnerID, currentUserID string) ResourceOwnership
DetermineOwnership compares resource owner with current user ID to determine ownership. Returns OwnershipOwn if they match, OwnershipShared if they don't, or OwnershipUnknown if either value is empty.
type SafetyError ¶
type SafetyError struct {
ContextName string
SafetyLevel SafetyLevel
Operation Operation
Reason string
Suggestions []string
}
SafetyError represents a safety check failure
func (*SafetyError) Error ¶
func (e *SafetyError) Error() string
type SafetyLevel ¶
type SafetyLevel string
SafetyLevel defines the allowed operations for a context
const ( // SafetyLevelReadOnly allows only read operations SafetyLevelReadOnly SafetyLevel = "readonly" // SafetyLevelReadWriteMine allows create/update/delete of own resources only SafetyLevelReadWriteMine SafetyLevel = "readwrite-mine" // SafetyLevelReadWriteAll allows modification of all resources (no bucket deletion) SafetyLevelReadWriteAll SafetyLevel = "readwrite-all" // SafetyLevelDangerouslyUnrestricted allows all operations including data deletion SafetyLevelDangerouslyUnrestricted SafetyLevel = "dangerously-unrestricted" // DefaultSafetyLevel is used when no safety level is specified. // We use readwrite-all as default to avoid breaking existing workflows. // This allows all operations except bucket deletion, which is the most // common use case and matches pre-safety-level behavior. DefaultSafetyLevel = SafetyLevelReadWriteAll )
func ValidSafetyLevels ¶
func ValidSafetyLevels() []SafetyLevel
ValidSafetyLevels returns all valid safety level values
func (SafetyLevel) IsValid ¶
func (s SafetyLevel) IsValid() bool
IsValid checks if the safety level is valid
func (SafetyLevel) String ¶
func (s SafetyLevel) String() string
String returns the string representation of the safety level
type SpillConfig ¶
type SpillConfig struct {
Mode string `yaml:"mode,omitempty"` // auto|always|never
Dir string `yaml:"dir,omitempty"` // base directory for spilled files
Format string `yaml:"format,omitempty"` // jsonl|json|csv|parquet (default jsonl)
Threshold string `yaml:"threshold,omitempty"` // e.g. "50KB"
TTL string `yaml:"ttl,omitempty"` // e.g. "24h"
}
SpillConfig holds the result-spill settings (D15). Threshold and TTL are kept as human-friendly strings in the file (e.g. "50KB", "24h") and parsed when resolving the effective settings. All fields are optional; an unset field inherits from the next layer in the precedence chain (flag → env → context-config → global-config → built-in default).
type StoredToken ¶
StoredToken represents a stored OAuth token set
type TokenManager ¶
type TokenManager struct {
// contains filtered or unexported fields
}
TokenManager manages OAuth tokens including storage and refresh
func NewTokenManager ¶
func NewTokenManager(oauthConfig *OAuthConfig) (*TokenManager, error)
NewTokenManager creates a new token manager
func (*TokenManager) CachedScopes ¶
func (tm *TokenManager) CachedScopes(tokenName string) []string
CachedScopes returns the granted scopes preserved in the scope-companion keyring entry, or nil when there is none. It is used as a fallback for scope display when the primary token entry dropped the scope string to fit size limits. Only consulted for keyring storage; the file store keeps the full token (scope included), so no companion is needed there.
func (*TokenManager) DeleteToken ¶
func (tm *TokenManager) DeleteToken(tokenName string) error
DeleteToken removes a stored OAuth token
func (*TokenManager) GetToken ¶
func (tm *TokenManager) GetToken(tokenName string) (string, error)
GetToken retrieves and optionally refreshes a token.
When multiple processes run in parallel (e.g. concurrent dtctl invocations) they may all see a compact token (no access_token) or an about-to-expire token and all attempt to refresh simultaneously. Because OAuth uses refresh token rotation, only the first refresh succeeds; the others receive "invalid_grant". To prevent this, we acquire a cross-process advisory lock before any refresh and re-read the token after acquiring it, so that the 2nd+ processes reuse the access_token the first one wrote.
func (*TokenManager) GetTokenInfo ¶
func (tm *TokenManager) GetTokenInfo(tokenName string) (*StoredToken, error)
GetTokenInfo retrieves information about a stored OAuth token
func (*TokenManager) RefreshToken ¶
func (tm *TokenManager) RefreshToken(tokenName string) (*TokenSet, error)
RefreshToken forces a refresh of an OAuth token by exchanging the stored refresh token for a new token set and persisting the result.
The refresh runs under the same cross-process lock GetToken uses: OAuth refresh-token rotation invalidates a refresh token after first use, so an unguarded forced refresh (e.g. a long-running consumer reacting to a 401 while a parallel dtctl invocation refreshes on expiry) would strand one side with "invalid_grant". After acquiring the lock the token is re-read — if another process refreshed while we waited (the stored access token changed and is not near expiry), that fresher token set is returned instead of spending another refresh. A genuinely-forced refresh (nothing changed while waiting) always proceeds, so a token the server rejects despite looking valid can never be returned to the caller unrefreshed. Like GetToken, the lock is best-effort: on lock failure a warning is printed and the refresh proceeds unguarded.
func (*TokenManager) SaveToken ¶
func (tm *TokenManager) SaveToken(tokenName string, tokens *TokenSet) error
SaveToken stores an OAuth token set
func (*TokenManager) SetWarnFunc ¶
func (tm *TokenManager) SetWarnFunc(fn func(format string, args ...any))
SetWarnFunc replaces the destination for non-fatal warnings (e.g. a failed best-effort refresh-lock acquisition). The default writes to stderr, which suits a CLI; embedders that own the terminal (a TUI like dynatui) should route warnings into their own surface instead. A nil fn silences warnings.
type TokenStore ¶
type TokenStore struct {
// contains filtered or unexported fields
}
TokenStore provides secure token storage using the OS keyring
func (*TokenStore) DeleteToken ¶
func (ts *TokenStore) DeleteToken(name string) error
DeleteToken removes a token from the OS keyring
func (*TokenStore) GetToken ¶
func (ts *TokenStore) GetToken(name string) (string, error)
GetToken retrieves a token from the OS keyring
func (*TokenStore) SetToken ¶
func (ts *TokenStore) SetToken(name, token string) error
SetToken stores a token securely in the OS keyring