Skip to content

Commit e5c73d4

Browse files
committed
fix(context): add validation and HTTP timeout to prevent hangs
- Add 30-second HTTP timeout to API client to prevent infinite hangs - Add connection validation during context create - Verify server connectivity and token validity before saving context - Verify gateway group exists if specified - Add --skip-validation flag for advanced users - Provide clear error messages for auth failures and connection issues Fixes issue where invalid credentials were silently accepted and subsequent commands would hang indefinitely when connecting to unreachable servers.
1 parent a1626ac commit e5c73d4

2 files changed

Lines changed: 62 additions & 8 deletions

File tree

pkg/api/client.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"net/http"
10+
"time"
1011
)
1112

1213
// Client is a thin wrapper around net/http for the API7 EE Admin API.
@@ -30,7 +31,10 @@ func NewAuthenticatedClient(apiKey string, tlsSkipVerify bool, caCert string) *h
3031
apiKey: apiKey,
3132
base: defaultTransport(tlsSkipVerify, caCert),
3233
}
33-
return &http.Client{Transport: transport}
34+
return &http.Client{
35+
Transport: transport,
36+
Timeout: 30 * time.Second,
37+
}
3438
}
3539

3640
// apiKeyTransport injects the X-API-KEY header into every request.

pkg/cmd/context/create/create.go

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package create
22

33
import (
4+
"errors"
45
"fmt"
6+
"net/http"
57

68
"github.com/spf13/cobra"
79

810
"github.com/api7/a7/internal/config"
11+
"github.com/api7/a7/pkg/api"
912
cmd "github.com/api7/a7/pkg/cmd"
1013
"github.com/api7/a7/pkg/cmdutil"
1114
)
@@ -14,13 +17,14 @@ import (
1417
type Options struct {
1518
Config func() (config.Config, error)
1619

17-
Name string
18-
Server string
19-
Token string
20-
GatewayGroup string
21-
TLSSkipVerify bool
22-
CACert string
23-
Use bool
20+
Name string
21+
Server string
22+
Token string
23+
GatewayGroup string
24+
TLSSkipVerify bool
25+
CACert string
26+
Use bool
27+
SkipValidation bool
2428
}
2529

2630
// NewCmd creates the "context create" command.
@@ -45,12 +49,50 @@ func NewCmd(f *cmd.Factory) *cobra.Command {
4549
c.Flags().BoolVar(&opts.TLSSkipVerify, "tls-skip-verify", false, "Skip TLS certificate verification")
4650
c.Flags().StringVar(&opts.CACert, "ca-cert", "", "Path to CA certificate")
4751
c.Flags().BoolVar(&opts.Use, "use", false, "Set as current context after creation")
52+
c.Flags().BoolVar(&opts.SkipValidation, "skip-validation", false, "Skip connectivity validation")
4853

4954
_ = c.MarkFlagRequired("server")
5055

5156
return c
5257
}
5358

59+
func validateContext(ctx config.Context) error {
60+
httpClient := api.NewAuthenticatedClient(ctx.Token, ctx.TLSSkipVerify, ctx.CACert)
61+
client := api.NewClient(httpClient, ctx.Server)
62+
63+
// Test connectivity with lightweight API call
64+
_, err := client.Get("/api/gateway_groups?page_size=1", nil)
65+
if err != nil {
66+
var apiErr *api.APIError
67+
if errors.As(err, &apiErr) {
68+
switch apiErr.StatusCode {
69+
case 401:
70+
return fmt.Errorf("authentication failed: invalid token")
71+
case 403:
72+
return fmt.Errorf("permission denied: check token permissions")
73+
default:
74+
return fmt.Errorf("server error: %s", cmdutil.FormatAPIError(err))
75+
}
76+
}
77+
// Network error (DNS, connection refused, timeout, etc.)
78+
return fmt.Errorf("cannot connect to server: %w", err)
79+
}
80+
81+
// If gateway-group is specified, verify it exists
82+
if ctx.GatewayGroup != "" {
83+
_, err := client.Get("/api/gateway_groups/"+ctx.GatewayGroup, nil)
84+
if err != nil {
85+
var apiErr *api.APIError
86+
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
87+
return fmt.Errorf("gateway group %q not found", ctx.GatewayGroup)
88+
}
89+
return fmt.Errorf("failed to verify gateway group: %w", err)
90+
}
91+
}
92+
93+
return nil
94+
}
95+
5496
func createRun(opts *Options, f *cmd.Factory) error {
5597
cfg, err := opts.Config()
5698
if err != nil {
@@ -66,6 +108,14 @@ func createRun(opts *Options, f *cmd.Factory) error {
66108
CACert: opts.CACert,
67109
}
68110

111+
// Validate context before saving (unless --skip-validation is set)
112+
if !opts.SkipValidation {
113+
fmt.Fprintf(f.IOStreams.Out, "Validating connection to %s...\n", ctx.Server)
114+
if err := validateContext(ctx); err != nil {
115+
return fmt.Errorf("validation failed: %w\nUse --skip-validation to bypass this check", err)
116+
}
117+
}
118+
69119
if err := cfg.AddContext(ctx); err != nil {
70120
return &cmdutil.FlagError{Err: err}
71121
}

0 commit comments

Comments
 (0)