chore: refactor to directly create Client in Command Handlers (#19760)

Refactors the CLI to create the `*codersdk.Client` in the handlers. This is groundwork for changing the `rootCmd.InitClient()` to use the new `ClientOption`​s.

It also improves variable locality, scoping the Client to the handler. This makes misuse less likely and reduces the memory allocations to just the command being executed, rather than allocating a Client for every command regardless of whether it is executed.
This commit is contained in:
Spike Curtis
2025-09-22 17:14:07 +04:00
committed by GitHub
parent 12496830d6
commit 606ae897b7
67 changed files with 605 additions and 431 deletions
+92 -100
View File
@@ -494,110 +494,106 @@ type RootCmd struct {
noFeatureWarning bool
}
// InitClient authenticates the client with files from disk
// and injects header middlewares for telemetry, authentication,
// InitClient creates and configures a new client with authentication, telemetry,
// and version checks.
func (r *RootCmd) InitClient(client *codersdk.Client) serpent.MiddlewareFunc {
return func(next serpent.HandlerFunc) serpent.HandlerFunc {
return func(inv *serpent.Invocation) error {
conf := r.createConfig()
var err error
// Read the client URL stored on disk.
if r.clientURL == nil || r.clientURL.String() == "" {
rawURL, err := conf.URL().Read()
// If the configuration files are absent, the user is logged out
if os.IsNotExist(err) {
binPath, err := os.Executable()
if err != nil {
binPath = "coder"
}
return xerrors.Errorf(notLoggedInMessage, binPath)
}
if err != nil {
return err
}
r.clientURL, err = url.Parse(strings.TrimSpace(rawURL))
if err != nil {
return err
}
}
// Read the token stored on disk.
if r.token == "" {
r.token, err = conf.Session().Read()
// Even if there isn't a token, we don't care.
// Some API routes can be unauthenticated.
if err != nil && !os.IsNotExist(err) {
return err
}
}
err = r.configureClient(inv.Context(), client, r.clientURL, inv)
func (r *RootCmd) InitClient(inv *serpent.Invocation) (*codersdk.Client, error) {
conf := r.createConfig()
var err error
// Read the client URL stored on disk.
if r.clientURL == nil || r.clientURL.String() == "" {
rawURL, err := conf.URL().Read()
// If the configuration files are absent, the user is logged out
if os.IsNotExist(err) {
binPath, err := os.Executable()
if err != nil {
return err
binPath = "coder"
}
client.SetSessionToken(r.token)
return nil, xerrors.Errorf(notLoggedInMessage, binPath)
}
if err != nil {
return nil, err
}
if r.debugHTTP {
client.PlainLogger = os.Stderr
client.SetLogBodies(true)
}
client.DisableDirectConnections = r.disableDirect
return next(inv)
r.clientURL, err = url.Parse(strings.TrimSpace(rawURL))
if err != nil {
return nil, err
}
}
// Read the token stored on disk.
if r.token == "" {
r.token, err = conf.Session().Read()
// Even if there isn't a token, we don't care.
// Some API routes can be unauthenticated.
if err != nil && !os.IsNotExist(err) {
return nil, err
}
}
client := &codersdk.Client{}
err = r.configureClient(inv.Context(), client, r.clientURL, inv)
if err != nil {
return nil, err
}
client.SetSessionToken(r.token)
if r.debugHTTP {
client.PlainLogger = os.Stderr
client.SetLogBodies(true)
}
client.DisableDirectConnections = r.disableDirect
return client, nil
}
// TryInitClient is similar to InitClient but doesn't error when credentials are missing.
// This allows commands to run without requiring authentication, but still use auth if available.
func (r *RootCmd) TryInitClient(client *codersdk.Client) serpent.MiddlewareFunc {
return func(next serpent.HandlerFunc) serpent.HandlerFunc {
return func(inv *serpent.Invocation) error {
conf := r.createConfig()
var err error
// Read the client URL stored on disk.
if r.clientURL == nil || r.clientURL.String() == "" {
rawURL, err := conf.URL().Read()
// If the configuration files are absent, just continue without URL
if err != nil {
// Continue with a nil or empty URL
if !os.IsNotExist(err) {
return err
}
} else {
r.clientURL, err = url.Parse(strings.TrimSpace(rawURL))
if err != nil {
return err
}
}
func (r *RootCmd) TryInitClient(inv *serpent.Invocation) (*codersdk.Client, error) {
conf := r.createConfig()
var err error
// Read the client URL stored on disk.
if r.clientURL == nil || r.clientURL.String() == "" {
rawURL, err := conf.URL().Read()
// If the configuration files are absent, just continue without URL
if err != nil {
// Continue with a nil or empty URL
if !os.IsNotExist(err) {
return nil, err
}
// Read the token stored on disk.
if r.token == "" {
r.token, err = conf.Session().Read()
// Even if there isn't a token, we don't care.
// Some API routes can be unauthenticated.
if err != nil && !os.IsNotExist(err) {
return err
}
} else {
r.clientURL, err = url.Parse(strings.TrimSpace(rawURL))
if err != nil {
return nil, err
}
// Only configure the client if we have a URL
if r.clientURL != nil && r.clientURL.String() != "" {
err = r.configureClient(inv.Context(), client, r.clientURL, inv)
if err != nil {
return err
}
client.SetSessionToken(r.token)
if r.debugHTTP {
client.PlainLogger = os.Stderr
client.SetLogBodies(true)
}
client.DisableDirectConnections = r.disableDirect
}
return next(inv)
}
}
// Read the token stored on disk.
if r.token == "" {
r.token, err = conf.Session().Read()
// Even if there isn't a token, we don't care.
// Some API routes can be unauthenticated.
if err != nil && !os.IsNotExist(err) {
return nil, err
}
}
// Only configure the client if we have a URL
if r.clientURL != nil && r.clientURL.String() != "" {
client := &codersdk.Client{}
err = r.configureClient(inv.Context(), client, r.clientURL, inv)
if err != nil {
return nil, err
}
client.SetSessionToken(r.token)
if r.debugHTTP {
client.PlainLogger = os.Stderr
client.SetLogBodies(true)
}
client.DisableDirectConnections = r.disableDirect
return client, nil
}
// Return a minimal client if no URL is available
return &codersdk.Client{}, nil
}
// HeaderTransport creates a new transport that executes `--header-command`
@@ -817,17 +813,13 @@ func namedWorkspace(ctx context.Context, client *codersdk.Client, identifier str
return client.WorkspaceByOwnerAndName(ctx, owner, name, codersdk.WorkspaceOptions{})
}
func initAppearance(client *codersdk.Client, outConfig *codersdk.AppearanceConfig) serpent.MiddlewareFunc {
return func(next serpent.HandlerFunc) serpent.HandlerFunc {
return func(inv *serpent.Invocation) error {
cfg, _ := client.Appearance(inv.Context())
if cfg.DocsURL == "" {
cfg.DocsURL = codersdk.DefaultDocsURL()
}
*outConfig = cfg
return next(inv)
}
func initAppearance(ctx context.Context, client *codersdk.Client) codersdk.AppearanceConfig {
// best effort
cfg, _ := client.Appearance(ctx)
if cfg.DocsURL == "" {
cfg.DocsURL = codersdk.DefaultDocsURL()
}
return cfg
}
// createConfig consumes the global configuration flag to produce a config root.