Help us shape the future of Go! Take the 2026 Go Developer Survey

clitools

package module
v1.1.1 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2026 License: MIT Imports: 24 Imported by: 4

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.

Well-known environments

configure --well-known sets up the TT and NTB environments in one go:

Environment Base URL OIDC realm
tt-stage https://stage.tt.ecms.se https://login.stage.tt.se/realms/elephant
tt-prod https://tt.ecms.se https://login.tt.se/realms/elephant
ntb-stage https://stage.ntb.ecms.se https://login.stage.tt.se/realms/ntb
ntb-prod https://ntb.ecms.se https://login.tt.se/realms/ntb

It also makes stage and prod aliases for tt-stage and tt-prod, unless an environment with that name is already configured, in which case it is left alone. Running it again refreshes the OIDC realm and base URL of the four environments and leaves any explicit endpoints in place.

myapp configure --well-known

Running configure with no flags at all in a terminal asks whether to do the same, so a first-time user doesn't have to know the flag. Without a terminal it fails asking for --env, as before.

Aliases

An alias is an environment that refers to another one by name. It holds no configuration or tokens of its own: everything is read from and written to the target, so --env stage and --env tt-stage share one login.

myapp configure --env=stage --alias=tt-stage

Configuring an alias with --oidc, --base-url or --endpoint changes its target. Setting --alias on an environment that has configuration of its own replaces it. Aliases are a single hop: the target must be a configured environment that is not itself an alias, and an environment that is the target of an alias cannot become one.

configure list shows every environment, with aliases listed as such.

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. For an alias it holds only the name of the target, {"alias": "tt-stage"}.
  • 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 SetEnvironmentAlias added in v1.1.0

func SetEnvironmentAlias(environment string, target string) error

SetEnvironmentAlias makes environment a reference to target, replacing any configuration environment had of its own. The target must be a configured environment, and cannot itself be an alias, or be aliased to by anything once it becomes one.

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.

If the environment is an alias the handler operates on the environment it refers to, see SetEnvironmentAlias.

func (*ConfigurationHandler) AddEndpoints added in v0.5.0

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

AddEndpoints for the environment.

func (*ConfigurationHandler) Environment added in v1.1.0

func (ac *ConfigurationHandler) Environment() string

Environment returns the name of the environment the handler operates on. This differs from the requested environment when that was an alias.

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) IsConfigured added in v1.1.0

func (ac *ConfigurationHandler) IsConfigured() bool

IsConfigured reports whether the environment has an OIDC configuration, which is what's needed to get access tokens.

func (*ConfigurationHandler) Load added in v0.2.0

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

Load configuration and tokens from disk.

func (*ConfigurationHandler) ResetEndpoints added in v1.0.3

func (ac *ConfigurationHandler) ResetEndpoints()

ResetEndpoints clears any explicitly configured endpoints for the environment.

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 {
	// Alias makes the environment a reference to another environment.
	// An alias holds no configuration or tokens of its own: everything
	// is read from, and written to, the environment it names.
	Alias     string            `json:"alias,omitempty"`
	OIDC      *OIDCEnvironment  `json:"oidc,omitempty"`
	BaseURL   string            `json:"base_url,omitempty"`
	Endpoints map[string]string `json:",omitempty"`
}

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