common

package
v1.15.0 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package common — humane error rendering for API responses and network failures.

The guiding philosophy: the CLI owes its users clear, honest, explicit messages when something goes wrong. Dumping a raw JSON blob at the user's terminal is a cop-out. Every error should read like a human wrote it — informal where it helps, informative always, honest when it's our fault.

Typical usage from a command:

resp, err := common.DoRequest(http.MethodPost, url, body)
if err != nil {
    fmt.Println(common.TransportError("create slice", err))
    return
}
defer resp.Body.Close()

respBody, err := common.CheckResponse(resp, "create slice")
if err != nil {
    fmt.Println(err)
    return
}
// ... use respBody

prompt.go — minimal interactive prompts using only stdlib + golang.org/x/term (which we already pull in indirectly via cobra). Replaced manifoldco/promptui — that package was unmaintained since 2021, and our two call sites are simple enough that 30 lines of stdlib reads cleaner than the dependency.

Index

Constants

View Source
const CLIModulePath = "github.com/ondrift/cli/cmd/drift"

CLIModulePath is the go-installable path of the drift binary.

View Source
const CLIRepo = "ondrift/cli"

CLIRepo is the GitHub owner/repo the drift CLI is released from.

View Source
const SessionFile = "~/.drift/session.json"

Variables

View Source
var APIBaseURL = "https://api.ondrift.eu"

APIBaseURL is the base URL for the Drift API gateway. It defaults to the public production gateway so a plain `go install` works out of the box; a local/dev build points it elsewhere via:

go build -ldflags "-X github.com/ondrift/cli/common.APIBaseURL=http://api.localhost:30036"

At runtime, the DRIFT_API_URL environment variable takes precedence over the compiled-in default (useful for self-hosted instances or staging).

View Source
var ConfiguratorBaseURL = "https://configurator.ondrift.eu"

ConfiguratorBaseURL is the base URL for the configurator service. The CLI hits this directly (rather than via the api gateway) for the slice create/resize browser handoff: handoff mints a session, redeem polls for the result. Like APIBaseURL it defaults to production and a local/dev build overrides it via -ldflags.

At runtime, the DRIFT_CONFIGURATOR_URL environment variable takes precedence over the compiled-in default.

Functions

func AtomicHeader

func AtomicHeader() string

AtomicHeader styles the "Atomic" section header.

func BackboneHeader

func BackboneHeader() string

BackboneHeader styles the "Backbone" section header.

func BoldText

func BoldText(s string) string

BoldText returns the input wrapped in ANSI bold (no color).

func CanvasHeader

func CanvasHeader() string

CanvasHeader styles the "Canvas" section header.

func CapitalizeFirst

func CapitalizeFirst(s string) string

func Check

func Check() string

Check returns a styled checkmark for successful items.

func CheckResponse

func CheckResponse(resp *http.Response, op string) ([]byte, error)

CheckResponse validates an HTTP response. On 2xx it returns the fully-read body and a nil error. On any other status it parses the server's error message (if any) and returns a humane *APIError.

Callers remain responsible for `defer resp.Body.Close()`.

func CompareVersions added in v1.11.1

func CompareVersions(a, b string) int

CompareVersions compares two "vMAJOR.MINOR.PATCH" strings. A leading "v" is optional, missing segments count as 0, and any pre-release/build suffix ("-rc1", "+meta") is ignored. Returns -1 if a<b, 0 if equal, +1 if a>b.

func Cross added in v1.1.0

func Cross() string

Cross returns a styled ✗ for failed items.

func DoJSONRequest

func DoJSONRequest(method, url string, body io.Reader) (*http.Response, error)

DoJSONRequest is a convenience wrapper for JSON request bodies.

func DoRequest

func DoRequest(method, url string, body io.Reader) (*http.Response, error)

DoRequest executes an authenticated HTTP request. If the server returns 401 (token expired), it automatically refreshes the JWT using the stored refresh token and retries once. The body parameter is read, buffered, and replayed on retry so callers don't need to worry about re-seekable readers.

func DoRequestWithContentType

func DoRequestWithContentType(method, url, contentType string, body io.Reader) (*http.Response, error)

DoRequestWithContentType is like DoRequest but sets the Content-Type header on both the original request and the post-refresh retry. Pass an empty contentType to skip setting it.

func DoRequestWithContext added in v1.6.0

func DoRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*http.Response, error)

DoRequestWithContext is like DoRequest but binds the request (and its post-refresh retry) to ctx. Use it for best-effort calls that must not stall a command on the default 30s client timeout — pass a context with a short deadline and treat a deadline error as "skip the optimization".

func DoRequestWithHeaders

func DoRequestWithHeaders(method, url string, body io.Reader, headers map[string]string) (*http.Response, error)

DoRequestWithHeaders is like DoRequest but applies the given headers to both the original request and the post-refresh retry.

func GetActiveSlice

func GetActiveSlice() string

GetActiveSlice returns the active slice name, or empty string if none set.

func GetOrCreateDeviceID

func GetOrCreateDeviceID() string

GetOrCreateDeviceID returns a stable per-workstation random ID. Used to bind refresh tokens: the server stores the ID at login, and a presented refresh token whose device_id doesn't match is treated as theft (revokes every live token for that user). Read-once cached on first call.

Stored alongside the session file at ~/.drift/device_id with mode 0600. A stealer that copies session.json without device_id is locked out at the next refresh.

func GetTokenFromSession

func GetTokenFromSession() (token string, refreshToken string, err error)

func GetUsername

func GetUsername() string

GetUsername extracts the username from the stored JWT access token. Returns an empty string if the session or token is missing/unparseable.

func Highlight

func Highlight(s string) string

Highlight styles a value that should stand out, such as the template name.

func Hint

func Hint(s string) string

Hint styles a parenthetical hint string (e.g. source file annotation).

func IsStyleEnabled

func IsStyleEnabled() bool

IsStyleEnabled reports whether ANSI styling should be emitted. Styling is suppressed when NO_COLOR is set, when output is being piped, or when the terminal advertises itself as dumb.

func NewAuthenticatedRequest

func NewAuthenticatedRequest(method, url string, body io.Reader) (*http.Request, error)

func OpenBrowser

func OpenBrowser(rawURL string) error

OpenBrowser launches the user's default web browser at the given URL.

The URL arrives over the network (a configurator handoff response). Even though that response comes back over HTTPS in production, an attacker with TLS-MITM access or a compromised configurator could influence the URL. The OS-level launchers (`open` on macOS, `xdg-open` on Linux, `cmd /c start` on Windows) all interpret a URL that begins with `-` as a flag — and macOS's `open --background` would silently load the URL in any zero-day-vulnerable browser without bringing it to the foreground. Two defences:

  1. Validate the URL parses with a known scheme (https, or http to localhost for dev) before passing it to any shell. Reject anything else.
  2. On macOS / Linux, insert `--` between the command and the URL so the launcher stops interpreting flags.

We avoid pulling in a third-party dependency for the launch itself — the per-OS command is a one-liner. If the launch fails (no display, SSH session, container, locked-down environment) we return an error so the caller can fall back to printing the URL for the user to open manually.

func PromptForInput

func PromptForInput(label string) string

PromptForInput prints a label and reads a line from stdin. The returned string has its trailing newline stripped. If stdin is closed or unreadable, returns the empty string.

func PromptForInputHidden

func PromptForInputHidden(label string) string

PromptForInputHidden prints a label and reads a line from stdin with terminal echo disabled — so the typed value (a password) doesn't appear on screen. Falls back to non-hidden read if stdin isn't a terminal (e.g. piped input in CI).

term.ReadPassword restores the terminal to its prior state on return, so the user's shell doesn't end up in a no-echo state even on signal-driven termination.

func ReadPasswordFromStdin

func ReadPasswordFromStdin() string

ReadPasswordFromStdin reads a single line of plaintext from stdin, strips the trailing newline, and returns it. Used by `--password-stdin` flags so the password never appears as a CLI argument (which would expose it to `ps`, shell history, and process-listing log surfaces). Standard pattern matched by gh, docker login, kubectl, doctl, op. Returns the empty string on read error.

func RequireActiveSlice

func RequireActiveSlice() (string, error)

RequireActiveSlice returns the active slice or an error instructing the user to select one with "drift slice use <name>".

func SaveActiveSlice

func SaveActiveSlice(name string) error

SaveActiveSlice persists the active slice name into the session file.

func SaveSession

func SaveSession(token, refresh_token string) error

func TransportError

func TransportError(op string, err error) error

TransportError wraps a network-level failure (DNS, dial, TLS, timeout) in a humane message. Pass the same `op` string you'd give to CheckResponse.

This is the right function to call when DoRequest returns an error — the return value is already a formatted, printable error.

func ZipFolder

func ZipFolder(folderPath string) (*bytes.Buffer, error)

Types

type APIError

type APIError struct {
	// Op is a short, lowercase, imperative description of what the CLI
	// was trying to do ("create slice", "deploy atomic function"). Used
	// as the lead-in: "Couldn't {op}: ...".
	Op string

	// Status is the HTTP status code the server returned.
	Status int

	// Detail is the server-supplied reason, extracted from the JSON body
	// ("error" or "message" fields). May be empty when the body is not
	// JSON or when the server didn't include one.
	Detail string

	// Raw is the trimmed response body. Used only as a last-resort
	// fallback when Detail is empty AND the status code has no specific
	// mapping — we'd rather show the raw body than nothing.
	Raw string
}

APIError is the humane rendering of a non-2xx response from the Drift API. It implements the error interface; callers should just `fmt.Println(err)`.

func (*APIError) Error

func (e *APIError) Error() string

type LatestRelease added in v1.11.1

type LatestRelease struct {
	Tag string // e.g. "v1.11.0"
	URL string // best-effort link to see what's in it
}

LatestRelease is the salient subset of the CLI's latest pushed version tag.

func FetchLatestCLIRelease added in v1.11.1

func FetchLatestCLIRelease() (LatestRelease, error)

FetchLatestCLIRelease asks GitHub for the CLI's latest pushed version tag. Failures are returned to the caller: the dashboard swallows them (the banner just stays hidden); `drift upgrade` surfaces them.

type Session

type Session struct {
	Username    string `json:"username"`
	ActiveSlice string `json:"active_slice,omitempty"`
}

type Spinner

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

Spinner renders a single-line animation next to a label by repeatedly rewriting the current line with \r. It is intentionally minimal: no nested spinners, no out-of-band logging while spinning. Callers should avoid writing to stdout between Start and Stop.

When stdout is not a terminal (or NO_COLOR is set), Spinner becomes a no-op: Start renders nothing and Stop just leaves the cursor where it is, so piped output stays clean and machine-parseable.

func StartSpinner

func StartSpinner(indent, label string) *Spinner

StartSpinner kicks off a background goroutine that animates the given label after `indent` spaces of leading whitespace. Call Stop to clear the line before printing the persistent success/failure marker.

func (*Spinner) Stop

func (s *Spinner) Stop()

Stop halts the animation and clears the spinner line so the caller can print the final persistent line in its place. Safe to call multiple times.

func (*Spinner) Update

func (s *Spinner) Update(label string)

Update changes the label of an in-flight spinner. Useful when a single step transitions through several phases (e.g. "building" → "uploading").

Jump to

Keyboard shortcuts

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