Skip to content
github-actions[bot] edited this page Oct 11, 2026 · 4 revisions

Errors

APIError

Product API failures return *vngcloud.APIError. Login failures use the login error types below:

type APIError struct {
	Operation  string
	StatusCode int
	Code       string
	Message    string
	Retryable  bool
	Err        error
}

Code accepts only JSON strings and numbers. Other code types use the status-derived fallback. Code is the API's own error code when the response carries one. When it does not, including a null code or one that is just the HTTP status repeated, Code falls back to a status-derived value, so it is never empty for a 4xx or 5xx response:

Status Code
400 BadRequest
401 Unauthorized
403 Forbidden
404 NotFound
409 Conflict
429 Throttled
5xx ServerError
Other 4xx ClientError

The SDK reads three error body shapes into Code and Message: an object {"code","message"}, an array [{"code","message"}], and the IAM accounts API wrapper {"errors":[{"code","message"}]}. In the wrapper, the first object entry gives Code and the messages of all object entries are joined with ; . An empty list or a list with only null or non-object entries leaves the status text as Message and the status-derived Code. No other body field reaches the error. Echoed access tokens and vCDN API keys are replaced with [redacted] in Code and Message, including errors carried in HTTP 2xx envelopes. Envelope errors redact the final sent credential even when another request refreshes the shared token. Redirect refusal messages redact raw host names before quoting them.

Err wraps a sentinel matching the failure, so errors.Is works without inspecting Code or StatusCode directly:

Sentinel Meaning
vngcloud.ErrAuth 401, or a login failure (see below)
vngcloud.ErrPermission 403
vngcloud.ErrNotFound 404
vngcloud.ErrRateLimited 429

Helper functions cover the common checks:

vngcloud.IsNotFound(err)
vngcloud.IsPermissionDenied(err)
vngcloud.IsRateLimited(err)
vngcloud.IsRetryable(err)   // true if the SDK would retry this call itself
vngcloud.ErrorCode(err)     // APIError.Code, or "" for a non-APIError

A service can map its own error shape onto NotFound; see the billing wiki page for an example. That mapping always wins over the status-derived fallback above. Classification uses the message before credential redaction.

Network failure messages use fixed descriptions, such as canceled, timed out, or the network operation that failed. Unknown causes use network request failed. Certificate failures name the class: unknown authority, hostname mismatch, or invalid certificate. Other TLS certificate verification failures use TLS certificate verification failed. Certificate subjects and details are withheld. Errors never include request URLs, redirect paths, or query values. Cross-host redirect errors name only the source and target hosts. DNS lookup failures use no such host, prefixed by the network operation when available. DNS names, servers, and raw details are withheld. Cancellation and deadlines still match their context sentinels with errors.Is. Network error chains expose fixed text and safe sentinels, never the original URL error or arbitrary transport cause. Timeouts retain a net.Error whose Timeout() returns true. Connection resets use connection reset, prefixed by the network operation when available.

JSON decode failures use response failed to decode. Syntax failures keep a safe *json.SyntaxError and its offset without exposing the response text.

LoginError

Every IAM User login failure returns *vngcloud.LoginError:

type LoginError struct {
	Status           int
	CaptchaSuspected bool
	Reason           string
	Err              error
}

Reason is fixed text naming the step that failed (loading the sign-in page, submitting it, exchanging the authorization code, and so on). Status is the HTTP status observed at that step, when there is one. CaptchaSuspected is true when the sign-in form was redisplayed after a submit, which the console does both for wrong credentials and for a required captcha.

Err is vngcloud.ErrAuth, so errors.Is(err, vngcloud.ErrAuth) is true for a login failure, or the context's own error when the attempt was canceled or timed out, so errors.Is(err, context.Canceled) is true instead. Neither LoginError nor anything it wraps ever holds a password, TOTP secret or code, token, cookie, authorization code, root email, or username: its message is built only from Status, CaptchaSuspected, and Reason, never from the failing HTTP call's own response.

err := cfg.Authenticate(ctx)
var loginErr *vngcloud.LoginError
if errors.As(err, &loginErr) {
	log.Printf("login failed: %s", loginErr.Reason)
	if loginErr.CaptchaSuspected {
		log.Print("a captcha may be required; sign in with a static token instead")
	}
}

ServiceAccountLoginError

Service-account exchange failures return *vngcloud.ServiceAccountLoginError:

type ServiceAccountLoginError struct {
    Status int
    Reason string
    Err    error
}

errors.As identifies the type. Status records the HTTP status, or zero when no response exists. Reason uses only these fixed strings:

  • token request failed
  • token endpoint redirected request
  • token endpoint rejected request
  • invalid token response

Err holds only vngcloud.ErrAuth, or the caller's context error when the caller cancels or reaches its deadline. An internal HTTP timeout still matches ErrAuth. No body, server code or message, header, URL, transport cause, or decoder cause enters the error or its unwrap chain. Invalid selected credentials fail before requests with ErrInvalidConfig.

vngcloud.ErrTokenCache has the fixed text vngcloud: token cache unavailable. It identifies service-account cache directory, read, and lock failures and matches neither ErrAuth nor ErrInvalidConfig. The SDK returns the sentinel alone, without a path, hash, credential, or filesystem cause in its text or unwrap chain. An ended caller context takes precedence and returns ctx.Err() directly. Missing or malformed cache content is a miss; a post-exchange write failure does not turn successful login into an error. Other authentication methods retain their existing cache errors.

CLI codes and exits

Failure CLI code Exit
Service-account exchange ServiceAccountLoginFailed 3
Exchange wrapping caller cancellation or deadline ServiceAccountLoginFailed 1
Service-account cache directory, read, or lock failure TokenCacheFailed 1
Bare context error at the cache boundary RequestFailed 1
Partial LoadConfig credentials or refused credentials file NoCredentials 3
Partial NewConfig credentials, invalid shape, or misplaced config key InvalidConfig 2
Product 401 after recovery Existing API code 3
Product 403 Existing API code 1

Cache preflight follows SDK auth selection and sends no request. Other auth kinds retain their existing cache preflight errors. Fixed exchange errors never contain server text. Generic product errors with service-account secret-key variants withhold the affected text.

Logging

vngcloud.WithLogger(logger *slog.Logger) is the only way to get logs from the SDK; without it, nothing is logged. With it, at Debug level:

  • Every HTTP attempt logs one request record with method, path (the URL path, with no query string and with credentials and explicit secrets redacted), status (omitted when no response was received), and duration.
  • An IAM User login logs login started before the attempt and login finished with ok (true or false) after. Login's own HTTP requests are not logged as request records.
  • A service-account exchange logs exchange started and exchange finished with ok. Its HTTP request never enters logs or response capture.

Nothing else is logged: no body, header, cookie, token, root email, authorization code, or credential.

cfg, err := vngcloud.NewConfig(
	vngcloud.WithRegion("hcm-3"),
	vngcloud.WithIAMUser(iamUser),
	vngcloud.WithLogger(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))),
)

Clone this wiki locally