config

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2026 License: MIT Imports: 14 Imported by: 0

README

Configuration Management Package

This package provides comprehensive configuration management for the DotEnv CLI, including secure storage of API keys, multi-context support, and environment variable overrides.

Features

  • Multi-Context Support: Manage multiple organizations/environments
  • Secure Storage: API keys are encrypted using AES-256-GCM
  • Environment Overrides: All settings can be overridden via environment variables
  • Atomic Operations: Safe file writing with backup support
  • Thread Safety: Concurrent access protection

Usage

Basic Usage
import "github.com/dotenv/cli/internal/config"

// Create a new manager
mgr, err := config.NewManager("")
if err != nil {
    log.Fatal(err)
}

// Get current context
ctx, err := mgr.GetCurrentContext()
if err != nil {
    log.Fatal(err)
}

// Access API credentials
apiURL, _ := mgr.GetAPIURL()
apiKey, _ := mgr.GetAPIKey()
org, _ := mgr.GetOrganization()
Context Management
// Get context manager
cm := mgr.GetContextManager()

// Create a new context
err = cm.Create("production", "https://api.dotenv.cloud", "your-api-key", "my-org")

// Switch contexts
err = cm.Use("production")

// List all contexts
contexts := cm.List()
for _, ctx := range contexts {
    fmt.Println(ctx)
}

// Rename a context
err = cm.Rename("old-name", "new-name")

// Delete a context
err = cm.Delete("staging")
Environment Variables

The following environment variables are supported:

  • DOTENV_CONFIG_DIR: Custom configuration directory
  • DOTENV_API_KEY: Override API key
  • DOTENV_API_URL: Override API URL
  • DOTENV_ORGANIZATION: Override organization
  • DOTENV_CONTEXT: Override current context
  • DOTENV_DEBUG: Enable debug mode
  • DOTENV_TLS_SKIP_VERIFY: Skip TLS verification
Configuration File

The configuration is stored in YAML format at ~/.dotenv/config.yaml:

version: "1.0"
telemetry_enabled: false
current_context: production
contexts:
  production:
    name: production
    api_url: https://api.dotenv.cloud
    api_key: <encrypted>
    organization: my-org
    created_at: 2025-06-18T10:00:00Z
    updated_at: 2025-06-18T10:00:00Z
    last_update: 2025-06-18T10:00:00Z
preferences:
  default_format: env
  color_output: true
  auto_update: true

Security

  • API keys are encrypted using AES-256-GCM with machine-specific keys
  • Configuration files are created with 0600 permissions (owner read/write only)
  • For production use, consider integrating with system keychains:
    • macOS: Keychain Services
    • Linux: Secret Service API
    • Windows: Credential Manager

Testing

Run the test suite:

go test ./internal/config/...

Run integration tests:

INTEGRATION_TEST=1 go test ./internal/config/... -tags=integration

Documentation

Index

Constants

View Source
const (
	// EnvAPIKey is the env var name (not a credential itself).
	EnvAPIKey        = "DOTENV_API_KEY" //nolint:gosec // env var name, not a credential
	EnvAPIURL        = "DOTENV_API_URL"
	EnvOrganization  = "DOTENV_ORGANIZATION"
	EnvContext       = "DOTENV_CONTEXT"
	EnvDebug         = "DOTENV_DEBUG"
	EnvNoColor       = "DOTENV_NO_COLOR"
	EnvQuiet         = "DOTENV_QUIET"
	EnvTLSSkipVerify = "DOTENV_TLS_SKIP_VERIFY"
	EnvConfigDir     = "DOTENV_CONFIG_DIR"
	// EnvClientKey is the env var name holding a client-managed encryption key
	// VALUE (not a path). Consulted only when a client key is actually needed.
	EnvClientKey = "DOTENV_CLIENT_KEY"
)

Environment variable names

Variables

This section is empty.

Functions

func ConfigDir

func ConfigDir() (string, error)

ConfigDir returns the config directory path.

func ConfigPath

func ConfigPath() (string, error)

ConfigPath returns the default config file path.

func EnsureDir

func EnsureDir(path string) error

EnsureDir ensures a directory exists

func FileExists

func FileExists(path string) bool

FileExists checks if a file exists

func GetAPIURL

func GetAPIURL(defaultURL string) string

GetAPIURL returns the API URL with proper precedence: 1. From environment variable DOTENV_API_URL 2. From provided default (e.g., from current account) 3. Default to https://api.dotenv.cloud

func GetDefaultAPIURL

func GetDefaultAPIURL() string

GetDefaultAPIURL returns the API URL from environment or default This is a convenience function when no account-specific URL is available

func IsTerminal

func IsTerminal() bool

IsTerminal checks if output is a terminal

func ShouldSkipTLSVerify

func ShouldSkipTLSVerify() bool

ShouldSkipTLSVerify returns true if TLS verification should be skipped.

The DOTENV_TLS_SKIP_VERIFY override is only honored when the effective API URL points at a local development host. This prevents a stray or injected environment variable from silently disabling certificate verification against a real endpoint (e.g. api.dotenv.cloud) and enabling a man-in-the-middle that would expose bearer tokens and secrets in transit.

func UserHomeDir

func UserHomeDir() (string, error)

UserHomeDir returns the user's home directory

func UsingEnvCredential

func UsingEnvCredential(flagAPIKey string) bool

UsingEnvCredential reports whether an explicit API key is active (via the --api-key flag value passed in, or the DOTENV_API_KEY env var). This is the single predicate for "are we in environment-credential (CI/CD) mode", used by the resolver, organization refresh, and identity reporting so they agree.

Types

type Account

type Account struct {
	Name      string    `yaml:"name"`
	AuthType  string    `yaml:"auth_type"` // "oauth" or "api_key"
	APIURL    string    `yaml:"api_url"`
	CreatedAt time.Time `yaml:"created_at"`
	UpdatedAt time.Time `yaml:"updated_at"`
	LastUsed  time.Time `yaml:"last_used"`

	// Auth data (consistent for both OAuth and API Key)
	Auth AuthData `yaml:"auth"`

	// For OAuth accounts
	Organizations          []OrgInfo  `yaml:"organizations,omitempty"`
	CurrentOrganization    string     `yaml:"current_organization,omitempty"` // ULID reference
	OrganizationsFetchedAt *time.Time `yaml:"organizations_fetched_at,omitempty"`

	// For API Key accounts
	Organization          *OrgInfo   `yaml:"organization,omitempty"`
	OrganizationFetchedAt *time.Time `yaml:"organization_fetched_at,omitempty"`
}

Account represents an authentication account (OAuth or API Key)

func (*Account) GetCurrentOrganization

func (a *Account) GetCurrentOrganization() (*OrgInfo, error)

GetCurrentOrganization returns the current organization info

func (*Account) GetCurrentOrganizationULID

func (a *Account) GetCurrentOrganizationULID() string

GetCurrentOrganizationULID returns the current organization ULID

func (*Account) GetOrganizationIdentifier

func (a *Account) GetOrganizationIdentifier() (string, error)

GetOrganizationIdentifier returns a valid identifier for API calls Since the web API only supports ULID, always return ULID

func (*Account) GetToken

func (a *Account) GetToken() string

GetToken returns the appropriate authentication token

func (*Account) GetTokenType

func (a *Account) GetTokenType() string

GetTokenType returns the token type for Authorization header

func (*Account) IsAPIKey

func (a *Account) IsAPIKey() bool

IsAPIKey returns true if this is an API key account

func (*Account) IsOAuth

func (a *Account) IsOAuth() bool

IsOAuth returns true if this is an OAuth account

func (*Account) IsRefreshTokenExpired

func (a *Account) IsRefreshTokenExpired() bool

IsRefreshTokenExpired checks if OAuth refresh token is expired

func (*Account) IsTokenExpired

func (a *Account) IsTokenExpired() bool

IsTokenExpired checks if OAuth token is expired

func (*Account) NeedsOrganizationRefresh

func (a *Account) NeedsOrganizationRefresh() bool

NeedsOrganizationRefresh checks if organization data should be refreshed

func (*Account) SetCurrentOrganization

func (a *Account) SetCurrentOrganization(ulid string) error

SetCurrentOrganization sets the current organization for OAuth accounts

type AccountManager

type AccountManager struct {
	// contains filtered or unexported fields
}

AccountManager manages accounts in the configuration

func NewAccountManager

func NewAccountManager(configPath string) (*AccountManager, error)

NewAccountManager creates a new account manager

func (*AccountManager) Create

func (am *AccountManager) Create(name, apiURL, authType string) error

Create creates a new account

func (*AccountManager) CreateWithAPIKey

func (am *AccountManager) CreateWithAPIKey(name, apiURL, apiKey string, orgInfo *OrgInfo) error

CreateWithAPIKey creates a new API key account

func (*AccountManager) CreateWithOAuth

func (am *AccountManager) CreateWithOAuth(name, apiURL string, tokens TokenResponse, orgs []OrgInfo, selectedOrgULID string) error

CreateWithOAuth creates a new OAuth account

func (*AccountManager) Get

func (am *AccountManager) Get(name string) (*Account, error)

Get returns an account by name

func (*AccountManager) GetCurrent

func (am *AccountManager) GetCurrent() (*Account, error)

GetCurrent returns the current account

func (*AccountManager) List

func (am *AccountManager) List() []string

List returns all account names

func (*AccountManager) RefreshOrganizations

func (am *AccountManager) RefreshOrganizations(accountName string, orgs []OrgInfo) (bool, error)

RefreshOrganizations updates the organization list for an account Returns true if the current organization was removed

func (*AccountManager) RefreshToken

func (am *AccountManager) RefreshToken(name string, tokens TokenResponse) error

RefreshToken updates OAuth tokens

func (*AccountManager) Remove

func (am *AccountManager) Remove(name string) error

Remove removes an account

func (*AccountManager) Rename

func (am *AccountManager) Rename(oldName, newName string) error

Rename renames an account

func (*AccountManager) SetOrganization

func (am *AccountManager) SetOrganization(accountName, orgULID string) error

SetOrganization sets the current organization for an OAuth account

func (*AccountManager) Update

func (am *AccountManager) Update(name string, updates map[string]interface{}) error

Update updates an existing account

func (*AccountManager) Use

func (am *AccountManager) Use(name string) error

Use sets an account as the current active account

type ActiveAuth

type ActiveAuth struct {
	Source AuthSource

	// APIKey is set when Source == AuthSourceEnv.
	APIKey string
	// Organization is the ULID to scope requests to (may be empty).
	Organization string
	// APIURL is the resolved base URL.
	APIURL string
	// Label is a human-friendly identifier for messaging (account name, or
	// "environment credentials").
	Label string
	// UsesAccountStore is true only when the stored account file is the active
	// credential source. Organization refresh / token refresh apply only then.
	UsesAccountStore bool
	// Account is non-nil only when Source == AuthSourceAccount.
	Account *Account
}

ActiveAuth is the single resolved answer to "who am I and which organization" for one CLI invocation. It is the one source of truth consulted by client construction, organization refresh, and the identity banner — so those never disagree (e.g. refreshing a stale stored account while an API key is set).

func ResolveAuth

func ResolveAuth(flagAPIKey string, env *EnvConfig, account *Account) ActiveAuth

ResolveAuth decides the active identity with this precedence (per product spec):

  1. An explicit API key (flag or DOTENV_API_KEY) → ephemeral environment identity, announced; the account store is bypassed (CI/CD friendly).
  2. Otherwise the current/default stored account.
  3. Otherwise none — the caller should prompt the user to log in.

flagAPIKey is the value of the --api-key flag (empty if unset). env is the loaded environment overrides. account is the current stored account (nil if none / not loaded).

type AuthData

type AuthData struct {
	// For API key auth
	APIKey string `yaml:"api_key,omitempty"`

	// For OAuth auth
	AccessToken           string    `yaml:"access_token,omitempty"`
	RefreshToken          string    `yaml:"refresh_token,omitempty"`
	TokenType             string    `yaml:"token_type,omitempty"`
	ExpiresAt             time.Time `yaml:"expires_at,omitempty"`
	RefreshTokenExpiresAt time.Time `yaml:"refresh_token_expires_at,omitempty"`
}

AuthData holds authentication credentials

type AuthSource

type AuthSource string

AuthSource identifies where the active credentials for a CLI invocation came from.

const (
	// AuthSourceEnv means credentials came from the environment / --api-key
	// flag (CI/CD). The local account store is not consulted.
	AuthSourceEnv AuthSource = "env"
	// AuthSourceAccount means credentials came from the stored account file.
	AuthSourceAccount AuthSource = "account"
	// AuthSourceNone means no credentials are available; the user must log in.
	AuthSourceNone AuthSource = "none"
)

type Config

type Config struct {
	Version          string             `yaml:"version"`
	TelemetryEnabled bool               `yaml:"telemetry_enabled"`
	CurrentAccount   string             `yaml:"current_account"`
	Accounts         map[string]Account `yaml:"accounts"`
	Preferences      Preferences        `yaml:"preferences,omitempty"`
	LastUpdateCheck  *time.Time         `yaml:"last_update_check,omitempty"`
	// Legacy fields for migration detection
	CurrentContext string             `yaml:"current_context,omitempty"`
	Contexts       map[string]Context `yaml:"contexts,omitempty"`
}

Config represents the complete CLI configuration

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns a new default configuration

func (*Config) AddAccount

func (c *Config) AddAccount(name string, account *Account) error

AddAccount adds or updates an account

func (*Config) AddContext

func (c *Config) AddContext(_ string, _ *Context) error

AddContext adds or updates a context (legacy)

func (*Config) GetAPIURL

func (c *Config) GetAPIURL() (string, error)

GetAPIURL returns the API URL for the current account

func (*Config) GetCurrentAccount

func (c *Config) GetCurrentAccount() (*Account, error)

GetCurrentAccount returns the current active account

func (*Config) GetCurrentContext

func (c *Config) GetCurrentContext() (*Context, error)

GetCurrentContext returns the current active context (legacy, for compatibility)

func (*Config) HasLegacyConfig

func (c *Config) HasLegacyConfig() bool

HasLegacyConfig checks if the config has old context-based structure

func (*Config) ListAccounts

func (c *Config) ListAccounts() []string

ListAccounts returns a list of all account names

func (*Config) ListContexts

func (c *Config) ListContexts() []string

ListContexts returns a list of all context names (legacy)

func (*Config) RemoveAccount

func (c *Config) RemoveAccount(name string) error

RemoveAccount removes an account

func (*Config) RemoveContext

func (c *Config) RemoveContext(_ string) error

RemoveContext removes a context (legacy)

func (*Config) RenameAccount

func (c *Config) RenameAccount(oldName, newName string) error

RenameAccount renames an account

func (*Config) RenameContext

func (c *Config) RenameContext(_, newName string) error

RenameContext renames a context (legacy)

func (*Config) SetCurrentAccount

func (c *Config) SetCurrentAccount(name string) error

SetCurrentAccount sets the current active account

func (*Config) SetCurrentContext

func (c *Config) SetCurrentContext(_ string) error

SetCurrentContext sets the current active context (legacy)

type Context

type Context struct {
	Name                string             `yaml:"name"`
	APIURL              string             `yaml:"api_url"`
	APIKey              string             `yaml:"api_key,omitempty"`
	Organization        string             `yaml:"organization,omitempty"`
	AuthType            string             `yaml:"auth_type"`
	Auth                AuthData           `yaml:"auth,omitempty"`
	Organizations       []OrganizationInfo `yaml:"organizations,omitempty"`
	CurrentOrganization string             `yaml:"current_organization,omitempty"`
	CreatedAt           time.Time          `yaml:"created_at"`
	UpdatedAt           time.Time          `yaml:"updated_at"`
	LastUpdate          time.Time          `yaml:"last_update"`
	Metadata            Metadata           `yaml:"metadata,omitempty"`
}

Context represents a legacy DotEnv context (for migration detection)

func (*Context) GetEffectiveAPIKey

func (c *Context) GetEffectiveAPIKey() string

GetEffectiveAPIKey returns the API key (for backward compatibility)

func (*Context) GetEffectiveOrganization

func (c *Context) GetEffectiveOrganization() string

GetEffectiveOrganization returns the current organization

func (*Context) IsAPIKeyContext

func (c *Context) IsAPIKeyContext() bool

IsAPIKeyContext returns true if this is an API key context

func (*Context) IsOAuthContext

func (c *Context) IsOAuthContext() bool

IsOAuthContext returns true if this is an OAuth context

func (*Context) IsTokenExpired

func (c *Context) IsTokenExpired() bool

IsTokenExpired returns true if the OAuth token is expired

type EnvConfig

type EnvConfig struct {
	APIKey        string
	APIURL        string
	Organization  string
	Context       string
	Debug         bool
	NoColor       bool
	Quiet         bool
	TLSSkipVerify bool
	ConfigDir     string
}

EnvConfig provides environment variable overrides

func LoadEnvConfig

func LoadEnvConfig() *EnvConfig

LoadEnvConfig loads configuration from environment variables

func (*EnvConfig) Apply

func (e *EnvConfig) Apply(ctx *Context) *Context

Apply applies environment overrides to a context

func (*EnvConfig) ApplyToConfig

func (e *EnvConfig) ApplyToConfig(config *Config) (*Config, error)

ApplyToConfig applies environment overrides to the entire configuration

func (*EnvConfig) HasOverrides

func (e *EnvConfig) HasOverrides() bool

HasOverrides checks if any environment overrides are set

type Loader

type Loader struct {
	// contains filtered or unexported fields
}

Loader handles configuration loading and saving

func NewLoader

func NewLoader(path string) *Loader

NewLoader creates a new configuration loader

func (*Loader) Backup

func (l *Loader) Backup() error

Backup creates a backup of the current configuration

func (*Loader) Exists

func (l *Loader) Exists() bool

Exists checks if configuration file exists

func (*Loader) Load

func (l *Loader) Load() (*Config, error)

Load reads and decrypts the configuration

func (*Loader) Path

func (l *Loader) Path() string

Path returns the configuration file path

func (*Loader) RestoreBackup

func (l *Loader) RestoreBackup() error

RestoreBackup restores configuration from backup

func (*Loader) Save

func (l *Loader) Save(config *Config) error

Save encrypts and writes the configuration. Writes are guarded by an advisory file lock so concurrent CLI invocations don't race the atomic rename and silently drop one writer's edits.

type Metadata

type Metadata struct {
	UserEmail        string   `yaml:"user_email,omitempty"`
	OrganizationID   string   `yaml:"organization_id,omitempty"`
	OrganizationName string   `yaml:"organization_name,omitempty"`
	OrganizationPlan string   `yaml:"organization_plan,omitempty"`
	Permissions      []string `yaml:"permissions,omitempty"`
}

Metadata holds additional context information

type OrgInfo

type OrgInfo struct {
	ULID string `yaml:"ulid"`
	Name string `yaml:"name"`
}

OrgInfo represents organization information

func ResolveOrganization

func ResolveOrganization(identifier string, orgs []OrgInfo) (*OrgInfo, error)

ResolveOrganization finds an organization by ULID

type OrganizationInfo

type OrganizationInfo struct {
	Slug string `yaml:"slug"`
	Name string `yaml:"name"`
	ID   int64  `yaml:"id,omitempty"`
}

OrganizationInfo represents an organization the user has access to

type Preferences

type Preferences struct {
	DefaultFormat      string            `yaml:"default_format,omitempty"`
	ColorOutput        bool              `yaml:"color_output"`
	AutoUpdate         bool              `yaml:"auto_update"`
	UpdateChannel      string            `yaml:"update_channel,omitempty"`
	AnalyticsID        string            `yaml:"analytics_id,omitempty"`
	CustomHeaders      map[string]string `yaml:"custom_headers,omitempty"`
	DefaultPullOptions PullOptions       `yaml:"default_pull_options,omitempty"`
}

Preferences holds user preferences

type PullOptions

type PullOptions struct {
	ResolveVariables bool   `yaml:"resolve_variables"`
	OutputFormat     string `yaml:"output_format"`
}

PullOptions represents default options for pull command

type TokenResponse

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
}

TokenResponse represents OAuth token response

type Validator

type Validator struct {
	// contains filtered or unexported fields
}

Validator validates configuration values

func NewValidator

func NewValidator() *Validator

NewValidator creates a new validator

func (*Validator) ValidateAPIKey

func (v *Validator) ValidateAPIKey(key string) error

ValidateAPIKey validates an API key format

func (*Validator) ValidateAPIURL

func (v *Validator) ValidateAPIURL(apiURL string) error

ValidateAPIURL validates an API URL

func (*Validator) ValidateAccountName

func (v *Validator) ValidateAccountName(name string) error

ValidateAccountName validates an account name

func (*Validator) ValidateConfig

func (v *Validator) ValidateConfig(config *Config) error

ValidateConfig validates the entire configuration

func (*Validator) ValidateOrganization

func (v *Validator) ValidateOrganization(org string) error

ValidateOrganization validates an organization slug

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL