clitools

package module
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Apr 17, 2026 License: MIT Imports: 23 Imported by: 2

README

CLI tools

Go Reference

Helpers for building Elephant CLI tools. Handles environment configuration, service endpoint resolution, and OIDC authentication so that individual CLI applications don't have to.

Configuring an environment

Before a CLI tool can authenticate or call services, the target environment needs to be configured. The package provides a configure CLI command (via ConfigureCliCommands) that can be embedded in any application built with urfave/cli:

# Set the base URL for the environment. Service endpoints are derived from it.
myapp --env=prod configure --base-url=https://tt.ecms.se --oidc=https://login.tt.se/realms/elephant

# Individual endpoints can be overridden when a service doesn't follow the
# standard naming convention.
myapp --env=prod configure --endpoint=chrome=https://tt.se/elephant

Setting a base URL like https://tt.ecms.se means that service endpoints are constructed on the fly:

Service Derived URL
repository https://repository.api.tt.ecms.se
index https://index.api.tt.ecms.se
chrome https://tt.ecms.se/elephant
... https://{name}.api.tt.ecms.se

The "chrome" endpoint is special-cased to {base URL}/elephant. All other endpoints follow the pattern {scheme}://{name}.api.{host}.

Explicit endpoints (set with --endpoint) always take precedence over endpoints derived from the base URL.

Endpoint resolution

Application code retrieves endpoints through the ConfigurationHandler:

handler, err := clitools.NewConfigurationHandler("myapp", clitools.DefaultApplicationID, "prod")

// Look up a specific endpoint.
repoURL, ok := handler.GetEndpoint("repository")

// Get all known endpoints (base URL derived + explicit overrides).
allEndpoints := handler.GetEndpoints()

GetEndpoint checks explicit endpoints first, then falls back to constructing the URL from the base URL. Any service name can be resolved this way, so new services work without reconfiguration.

Authentication

The package supports two OIDC flows:

Authorization code with PKCE (interactive CLI usage): GetAccessToken opens a browser for the user to log in and starts a temporary local HTTP server to receive the callback. Tokens are cached and reused until they expire.

Client credentials (service-to-service): GetClientAccessToken returns an oauth2.TokenSource for the given client ID and secret.

See the example in the Go docs for a complete usage example.

Configuration storage

Configuration is stored per environment under $XDG_CONFIG_HOME/elephant-clitools/{environment}/ (or the platform-appropriate user configuration directory, e.g. ~/.config/elephant-clitools/{environment}/ on Linux).

Each environment directory contains:

  • config.json -- OIDC settings, base URL, and endpoint overrides. Shared across all applications using the same environment.
  • tokens.json -- cached access tokens, keyed by application name.

File system locking ensures that concurrent CLI processes don't corrupt the configuration.

Environment variable loading

UserConfigDir() gives preference to XDG_CONFIG_HOME, letting users on any OS specify a Linux-style user config location. If XDG_CONFIG_HOME is empty it behaves just like os.UserConfigDir().

LoadEnv() loads any .env file (override the path by setting DOT_ENV) in the current directory and {user config dir}/{app}/config.env if they exist. Already-set variables are not overridden, and .env takes precedence over config.env.

Documentation

Overview

Example

Example demonstrates authenticating against an Elephant environment and looking up service endpoints. The environment must have been configured beforehand using the "configure" CLI command.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"

	"github.com/ttab/clitools"
)

func main() {
	env := "tt-prod"

	app, err := clitools.NewConfigurationHandler(
		"clitools", clitools.DefaultApplicationID, env,
	)
	if err != nil {
		panic(fmt.Errorf("create configuration handler: %w", err))
	}

	// Look up a service endpoint. If a base URL has been configured this
	// will derive the endpoint automatically.
	repoURL, ok := app.GetEndpoint("repository")
	if !ok {
		panic("no repository endpoint configured")
	}

	fmt.Println("Repository endpoint:", repoURL)

	// Authenticate using the OIDC authorization code flow. This will open
	// a browser window for the user to log in, unless a valid cached token
	// is available.
	token, err := app.GetAccessToken(context.Background(), []string{
		"doc_read",
	})
	if err != nil {
		panic(fmt.Errorf("authenticate: %w", err))
	}

	err = app.Save()
	if err != nil {
		panic(fmt.Errorf("save configuration: %w", err))
	}

	enc := json.NewEncoder(os.Stdout)

	enc.SetIndent("", "  ")

	fmt.Println("Current token:")
	_ = enc.Encode(token)
}

Index

Examples

Constants

View Source
const DefaultApplicationID = "elephant-cli"

DefaultApplicationID when authorising CLI applications.

Variables

This section is empty.

Functions

func ConfigureCliCommands added in v0.5.0

func ConfigureCliCommands(name string, clientID string) *cli.Command

func ElephantAPIEndpoint added in v0.5.0

func ElephantAPIEndpoint(proto, base, name string) string

ElephantAPIEndpoint constructs a standard API endpoint URL.

func LoadEnv added in v0.3.0

func LoadEnv(app string) error

LoadEnv loads any ".env" (override by setting DOT_ENV) in the current path and "[user config dir]/[app]/config.env" if they exist.

This will not override any variables that are set, and the .env file takes precedence over config.env.

func UserConfigDir added in v0.3.0

func UserConfigDir() (string, error)

UserConfigDir gives preference to XDG_CONFIG_HOME, letting users in any OS specify a linux-style user config location. If XDG_CONFIG_HOME is empty it behaves just like os.UserConfigDir().

Types

type AccessToken

type AccessToken struct {
	Token         string    `json:"token"`
	Expires       time.Time `json:"expires"`
	Scopes        []string  `json:"scopes"`
	GrantedScopes []string  `json:"granted_scopes"`
}

AccessToken that can be used to communicate with our APIs.

type ConfigurationHandler

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

func NewConfigurationHandler

func NewConfigurationHandler(
	name string,
	clientID string,
	environment string,
) (*ConfigurationHandler, error)

NewConfigurationHandler crates a configuration handler and loads the current configuration from disk if it's available.

func (*ConfigurationHandler) AddEndpoints added in v0.5.0

func (ac *ConfigurationHandler) AddEndpoints(endpoints map[string]string)

AddEndpoints for the environment.

func (*ConfigurationHandler) GetAccessToken

func (ac *ConfigurationHandler) GetAccessToken(
	ctx context.Context, scopes []string,
) (_ AccessToken, outErr error)

GetAccessToken either returns an existing non-expired token for the environment that matches the requested scope, or starts the authorization flow to get a new token.

During the authorisation flow we will attempt to automatically open a URL in the users browser.

func (*ConfigurationHandler) GetClientAccessToken added in v0.4.0

func (ac *ConfigurationHandler) GetClientAccessToken(
	ctx context.Context,
	clientID string, clientSecret string,
	scopes []string,
) (oauth2.TokenSource, error)

Convenience function for using the OIDC configuration to get a client credentials token source.

func (*ConfigurationHandler) GetEndpoint added in v0.5.0

func (ac *ConfigurationHandler) GetEndpoint(name string) (string, bool)

GetEndpoint returns the specified endpoint. Explicit endpoints take precedence over endpoints derived from the base URL.

func (*ConfigurationHandler) GetEndpoints added in v0.5.0

func (ac *ConfigurationHandler) GetEndpoints() map[string]string

GetEndpoints returns all available endpoints. Endpoints derived from the base URL are included, but explicit endpoints take precedence.

func (*ConfigurationHandler) GetOIDCConfig added in v0.4.0

func (ac *ConfigurationHandler) GetOIDCConfig(
	ctx context.Context,
) (*OIDCConfig, error)

func (*ConfigurationHandler) Load added in v0.2.0

func (ac *ConfigurationHandler) Load() (outErr error)

Load configuration and tokens from disk.

func (*ConfigurationHandler) Save

func (ac *ConfigurationHandler) Save() error

Save configuration and tokens to disk.

func (*ConfigurationHandler) SetBaseURL added in v1.0.0

func (ac *ConfigurationHandler) SetBaseURL(baseURL string) error

SetBaseURL sets the base URL for the environment. Service endpoints will be derived from this URL when not explicitly configured.

func (*ConfigurationHandler) SetOIDCConfigURL added in v0.5.0

func (ac *ConfigurationHandler) SetOIDCConfigURL(
	ctx context.Context,
	configURL string,
) error

type EnvConfiguration added in v0.5.0

type EnvConfiguration struct {
	OIDC      *OIDCEnvironment `json:"oidc"`
	BaseURL   string           `json:"base_url,omitempty"`
	Endpoints map[string]string
}

func (*EnvConfiguration) BaseURLEndpoints added in v1.0.0

func (ec *EnvConfiguration) BaseURLEndpoints() map[string]string

BaseURLEndpoints returns the standard set of elephant endpoints derived from the base URL. Returns an empty map if no base URL is configured.

func (*EnvConfiguration) EndpointFromBaseURL added in v1.0.0

func (ec *EnvConfiguration) EndpointFromBaseURL(name string) (string, bool)

EndpointFromBaseURL constructs an endpoint URL from the configured base URL. Explicit endpoints take precedence and should be checked first.

type OIDCConfig added in v0.2.0

type OIDCConfig struct {
	Issuer                string `json:"issuer"`
	AuthorizationEndpoint string `json:"authorization_endpoint"`
	TokenEndpoint         string `json:"token_endpoint"`
	IntrospectionEndpoint string `json:"introspection_endpoint"`
	UserinfoEndpoint      string `json:"userinfo_endpoint"`
	EndSessionEndpoint    string `json:"end_session_endpoint"`
}

type OIDCEnvironment added in v0.2.0

type OIDCEnvironment struct {
	Refreshed time.Time   `json:"refreshed,omitempty,omitzero"`
	ConfigURL string      `json:"oidc_config_url,omitempty"`
	Config    *OIDCConfig `json:"oidc_config"`
}

func (*OIDCEnvironment) EnsureOIDCConfig added in v0.2.0

func (ce *OIDCEnvironment) EnsureOIDCConfig(
	ctx context.Context, client *http.Client, maxAge time.Duration,
) (outErr error)

Jump to

Keyboard shortcuts

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