mirror of
https://github.com/larksuite/cli.git
synced 2026-08-03 08:32:46 +08:00
refactor(sidecar): return typed errors from the demo servers
Both demo entry points reported every startup failure with fmt.Errorf, so the self-proxy rejection was indistinguishable from a missing key file except by reading the sentence. Classify them instead: a poisoned environment and an unreadable config are ConfigError, flag validation is ValidationError, local key and log file work is InternalError, and listen or serve failures are NetworkError. Causes are attached rather than folded into the message. The self-proxy test asserts the type with errors.As and keeps the check that the message names the variable, so renaming the variable still fails the test while rewording the sentence no longer does.
This commit is contained in:
@@ -8,6 +8,7 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/larksuite/cli/envnames"
|
||||
"github.com/larksuite/cli/errs"
|
||||
extcred "github.com/larksuite/cli/extension/credential"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/credential"
|
||||
@@ -424,8 +426,12 @@ func TestRun_RejectsSelfProxy(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("expected error when AUTH_PROXY is set")
|
||||
}
|
||||
if !strings.Contains(err.Error(), envnames.CliAuthProxy) {
|
||||
t.Errorf("error should mention %s, got: %v", envnames.CliAuthProxy, err)
|
||||
var configErr *errs.ConfigError
|
||||
if !errors.As(err, &configErr) {
|
||||
t.Fatalf("self-proxy rejection = %T, want *errs.ConfigError so callers can branch on the type", err)
|
||||
}
|
||||
if !strings.Contains(configErr.Error(), envnames.CliAuthProxy) {
|
||||
t.Errorf("error should mention %s, got: %v", envnames.CliAuthProxy, configErr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/larksuite/cli/envnames"
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
@@ -63,25 +64,25 @@ func run(ctx context.Context, listen, keyFile, logFile, profile string) error {
|
||||
// credential provider would activate and return sentinel tokens instead
|
||||
// of real ones, breaking the "trusted side holds real credentials" premise.
|
||||
if v := os.Getenv(envnames.CliAuthProxy); v != "" {
|
||||
return fmt.Errorf("%s is set in this environment (%s); unset it before starting the sidecar server", envnames.CliAuthProxy, v)
|
||||
return errs.NewConfigError(errs.SubtypeInvalidConfig, "%s is set in this environment (%s); unset it before starting the sidecar server", envnames.CliAuthProxy, v)
|
||||
}
|
||||
if listen == "" {
|
||||
return fmt.Errorf("invalid --listen address: empty")
|
||||
return errs.NewValidationError(errs.SubtypeInvalidArgument, "invalid --listen address: empty")
|
||||
}
|
||||
|
||||
// Generate HMAC key (32 bytes = 256 bits) and write it to disk (0600).
|
||||
keyBytes := make([]byte, 32)
|
||||
if _, err := rand.Read(keyBytes); err != nil {
|
||||
return fmt.Errorf("failed to generate HMAC key: %v", err)
|
||||
return errs.NewInternalError(errs.SubtypeStorage, "failed to generate HMAC key: %v", err).WithCause(err)
|
||||
}
|
||||
keyHex := hex.EncodeToString(keyBytes)
|
||||
|
||||
keyDir := filepath.Dir(keyFile)
|
||||
if err := vfs.MkdirAll(keyDir, 0700); err != nil {
|
||||
return fmt.Errorf("failed to create key directory: %v", err)
|
||||
return errs.NewInternalError(errs.SubtypeStorage, "failed to create key directory: %v", err).WithCause(err)
|
||||
}
|
||||
if err := vfs.WriteFile(keyFile, []byte(keyHex), 0600); err != nil {
|
||||
return fmt.Errorf("failed to write key file: %v", err)
|
||||
return errs.NewInternalError(errs.SubtypeStorage, "failed to write key file: %v", err).WithCause(err)
|
||||
}
|
||||
|
||||
// Audit logger: file or stderr.
|
||||
@@ -89,7 +90,7 @@ func run(ctx context.Context, listen, keyFile, logFile, profile string) error {
|
||||
if logFile != "" {
|
||||
f, err := vfs.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open log file: %v", err)
|
||||
return errs.NewInternalError(errs.SubtypeStorage, "failed to open log file: %v", err).WithCause(err)
|
||||
}
|
||||
defer f.Close()
|
||||
auditLogger = log.New(f, "", log.LstdFlags)
|
||||
@@ -102,12 +103,12 @@ func run(ctx context.Context, listen, keyFile, logFile, profile string) error {
|
||||
factory := cmdutil.NewDefault(nil, cmdutil.InvocationContext{Profile: profile})
|
||||
cfg, err := factory.Config()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load config: %v", err)
|
||||
return errs.NewConfigError(errs.SubtypeInvalidConfig, "failed to load config: %v", err).WithCause(err)
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", listen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on %s: %v", listen, err)
|
||||
return errs.NewNetworkError(errs.SubtypeNetworkTransport, "failed to listen on %s: %v", listen, err).WithCause(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
@@ -161,7 +162,7 @@ func run(ctx context.Context, listen, keyFile, logFile, profile string) error {
|
||||
fmt.Fprintf(os.Stderr, " export %s=%q\n", envnames.CliBrand, string(cfg.Brand))
|
||||
|
||||
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
return fmt.Errorf("sidecar server exited unexpectedly: %v", err)
|
||||
return errs.NewNetworkError(errs.SubtypeNetworkTransport, "sidecar server exited unexpectedly: %v", err).WithCause(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/larksuite/cli/envnames"
|
||||
"github.com/larksuite/cli/errs"
|
||||
extcred "github.com/larksuite/cli/extension/credential"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/credential"
|
||||
@@ -420,8 +422,12 @@ func TestRun_RejectsSelfProxy(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("expected error when AUTH_PROXY is set")
|
||||
}
|
||||
if !strings.Contains(err.Error(), envnames.CliAuthProxy) {
|
||||
t.Errorf("error should mention %s, got: %v", envnames.CliAuthProxy, err)
|
||||
var configErr *errs.ConfigError
|
||||
if !errors.As(err, &configErr) {
|
||||
t.Fatalf("self-proxy rejection = %T, want *errs.ConfigError so callers can branch on the type", err)
|
||||
}
|
||||
if !strings.Contains(configErr.Error(), envnames.CliAuthProxy) {
|
||||
t.Errorf("error should mention %s, got: %v", envnames.CliAuthProxy, configErr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/larksuite/cli/envnames"
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
@@ -63,30 +64,30 @@ func defaultKeyFile() string {
|
||||
|
||||
func run(ctx context.Context, listen, keyFile, keysDir, logFile, profile string) error {
|
||||
if v := os.Getenv(envnames.CliAuthProxy); v != "" {
|
||||
return fmt.Errorf("%s is set in this environment (%s); unset it before starting the sidecar server", envnames.CliAuthProxy, v)
|
||||
return errs.NewConfigError(errs.SubtypeInvalidConfig, "%s is set in this environment (%s); unset it before starting the sidecar server", envnames.CliAuthProxy, v)
|
||||
}
|
||||
if listen == "" {
|
||||
return fmt.Errorf("invalid --listen address: empty")
|
||||
return errs.NewValidationError(errs.SubtypeInvalidArgument, "invalid --listen address: empty")
|
||||
}
|
||||
|
||||
if _, err := validate.SafeInputPath(keyFile); err != nil {
|
||||
return fmt.Errorf("invalid --key-file path: %w", err)
|
||||
return errs.NewValidationError(errs.SubtypeInvalidArgument, "invalid --key-file path: %v", err).WithCause(err)
|
||||
}
|
||||
if logFile != "" {
|
||||
if _, err := validate.SafeInputPath(logFile); err != nil {
|
||||
return fmt.Errorf("invalid --log-file path: %w", err)
|
||||
return errs.NewValidationError(errs.SubtypeInvalidArgument, "invalid --log-file path: %v", err).WithCause(err)
|
||||
}
|
||||
}
|
||||
if keysDir != "" {
|
||||
if _, err := validate.SafeInputPath(keysDir); err != nil {
|
||||
return fmt.Errorf("invalid --keys-dir path: %w", err)
|
||||
return errs.NewValidationError(errs.SubtypeInvalidArgument, "invalid --keys-dir path: %v", err).WithCause(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Reuse existing key if present; generate a new one only on first run.
|
||||
keyDir := filepath.Dir(keyFile)
|
||||
if err := vfs.MkdirAll(keyDir, 0700); err != nil {
|
||||
return fmt.Errorf("failed to create key directory: %v", err)
|
||||
return errs.NewInternalError(errs.SubtypeStorage, "failed to create key directory: %v", err).WithCause(err)
|
||||
}
|
||||
|
||||
var keyHex string
|
||||
@@ -95,11 +96,11 @@ func run(ctx context.Context, listen, keyFile, keysDir, logFile, profile string)
|
||||
} else {
|
||||
keyBytes := make([]byte, 32)
|
||||
if _, err := rand.Read(keyBytes); err != nil {
|
||||
return fmt.Errorf("failed to generate HMAC key: %v", err)
|
||||
return errs.NewInternalError(errs.SubtypeStorage, "failed to generate HMAC key: %v", err).WithCause(err)
|
||||
}
|
||||
keyHex = hex.EncodeToString(keyBytes)
|
||||
if err := vfs.WriteFile(keyFile, []byte(keyHex), 0600); err != nil {
|
||||
return fmt.Errorf("failed to write key file: %v", err)
|
||||
return errs.NewInternalError(errs.SubtypeStorage, "failed to write key file: %v", err).WithCause(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +114,7 @@ func run(ctx context.Context, listen, keyFile, keysDir, logFile, profile string)
|
||||
if logFile != "" {
|
||||
f, err := vfs.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open log file: %v", err)
|
||||
return errs.NewInternalError(errs.SubtypeStorage, "failed to open log file: %v", err).WithCause(err)
|
||||
}
|
||||
defer f.Close()
|
||||
auditLogger = log.New(f, "", log.LstdFlags)
|
||||
@@ -124,12 +125,12 @@ func run(ctx context.Context, listen, keyFile, keysDir, logFile, profile string)
|
||||
factory := cmdutil.NewDefault(nil, cmdutil.InvocationContext{Profile: profile})
|
||||
cfg, err := factory.Config()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load config: %v", err)
|
||||
return errs.NewConfigError(errs.SubtypeInvalidConfig, "failed to load config: %v", err).WithCause(err)
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", listen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on %s: %v", listen, err)
|
||||
return errs.NewNetworkError(errs.SubtypeNetworkTransport, "failed to listen on %s: %v", listen, err).WithCause(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
@@ -189,7 +190,7 @@ func run(ctx context.Context, listen, keyFile, keysDir, logFile, profile string)
|
||||
fmt.Fprintf(os.Stderr, " export %s=%q\n", envnames.CliBrand, string(cfg.Brand))
|
||||
|
||||
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
return fmt.Errorf("sidecar server exited unexpectedly: %v", err)
|
||||
return errs.NewNetworkError(errs.SubtypeNetworkTransport, "sidecar server exited unexpectedly: %v", err).WithCause(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user