Skip to content

Commit f40e565

Browse files
committed
[network-check] Implement "local" mode
Tool: gitpod/catfood.gitpod.cloud
1 parent 28a648d commit f40e565

File tree

11 files changed

+359
-45
lines changed

11 files changed

+359
-45
lines changed

gitpod-network-check/cmd/checks.go

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,6 @@ import (
1212
testrunner "github.com/gitpod-io/enterprise-deployment-toolkit/gitpod-network-check/pkg/runner"
1313
)
1414

15-
type Mode string
16-
17-
const (
18-
ModeEC2 Mode = "ec2"
19-
ModeLambda Mode = "lambda"
20-
)
21-
22-
var validModes = map[string]bool{
23-
string(ModeLambda): true,
24-
string(ModeEC2): true,
25-
}
26-
27-
var flags = struct {
28-
// Variable to store the testsets flag value
29-
SelectedTestsets []string
30-
31-
// Variable to store the mode flag value
32-
ModeVar string
33-
34-
Mode Mode
35-
}{}
36-
3715
var checkCommand = &cobra.Command{ // nolint:gochecknoglobals
3816
PersistentPreRunE: validateArguments,
3917
Use: "diagnose",
@@ -42,15 +20,17 @@ var checkCommand = &cobra.Command{ // nolint:gochecknoglobals
4220
RunE: func(cmd *cobra.Command, args []string) error {
4321
ctx := cmd.Context()
4422

45-
var runner testrunner.TestRunner
46-
if flags.Mode == ModeEC2 {
47-
ec2Runner, err := testrunner.NewEC2TestRunner(ctx, &networkConfig)
48-
if err != nil {
49-
return fmt.Errorf("❌ failed to create EC2 test runner: %v", err)
50-
}
51-
runner = ec2Runner
23+
runner, err := testrunner.NewRunner(ctx, flags.Mode, &networkConfig)
24+
if err != nil {
25+
return fmt.Errorf("❌ failed to create test runner: %v", err)
5226
}
27+
5328
defer (func() {
29+
// Ensure runner was actually assigned before trying to clean up
30+
if runner == nil {
31+
log.Info("ℹ️ No runner initialized, skipping cleanup.")
32+
return
33+
}
5434
log.Infof("ℹ️ Running cleanup")
5535
terr := runner.Cleanup(ctx)
5636
if terr != nil {
@@ -60,7 +40,7 @@ var checkCommand = &cobra.Command{ // nolint:gochecknoglobals
6040
})()
6141

6242
// Prepare
63-
err := runner.Prepare(ctx)
43+
err = runner.Prepare(ctx)
6444
if err != nil {
6545
return fmt.Errorf("❌ failed to prepare: %v", err)
6646
}
@@ -92,12 +72,6 @@ var checkCommand = &cobra.Command{ // nolint:gochecknoglobals
9272
}
9373

9474
func validateArguments(cmd *cobra.Command, args []string) error {
95-
// Validate mode
96-
if !validModes[flags.ModeVar] {
97-
return fmt.Errorf("invalid mode: %s, must be one of: %v", flags.ModeVar, maps.Keys(validModes))
98-
}
99-
flags.Mode = Mode(flags.ModeVar)
100-
10175
// Validate testsets if specified
10276
if len(flags.SelectedTestsets) > 0 {
10377
for _, testset := range flags.SelectedTestsets {

gitpod-network-check/cmd/cleanup.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,16 @@ import (
1010
)
1111

1212
var cleanCommand = &cobra.Command{ // nolint:gochecknoglobals
13-
PersistentPreRunE: validateSubnets,
1413
Use: "clean",
1514
Short: "Explicitly cleans up after the network check diagnosis",
1615
SilenceUsage: false,
1716
RunE: func(cmd *cobra.Command, args []string) error {
1817
ctx := cmd.Context()
1918

2019
log.Infof("ℹ️ Running cleanup")
21-
runner, err := runner.LoadEC2RunnerFromTags(ctx, &networkConfig)
20+
runner, err := runner.NewRunner(ctx, flags.Mode, &networkConfig)
2221
if err != nil {
23-
log.WithError(err).Fatal("Failed to load EC2 runner")
22+
return fmt.Errorf("❌ failed to create test runner: %v", err)
2423
}
2524

2625
err = runner.Cleanup(ctx)

gitpod-network-check/cmd/root.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,21 @@ import (
1313
"github.com/spf13/viper"
1414

1515
"github.com/gitpod-io/enterprise-deployment-toolkit/gitpod-network-check/pkg/checks"
16+
"github.com/gitpod-io/enterprise-deployment-toolkit/gitpod-network-check/pkg/runner"
1617
)
1718

1819
var networkConfig = checks.NetworkConfig{LogLevel: "INFO"}
1920

21+
var flags = struct {
22+
// Variable to store the testsets flag value
23+
SelectedTestsets []string
24+
25+
// Variable to store the mode flag value
26+
ModeVar string
27+
28+
Mode runner.Mode
29+
}{}
30+
2031
var networkCheckCmd = &cobra.Command{ // nolint:gochecknoglobals
2132
PersistentPreRunE: preRunE,
2233
Use: "gitpod-network-check",
@@ -28,16 +39,24 @@ func preRunE(cmd *cobra.Command, args []string) error {
2839
// setup logger
2940
lvl, err := log.ParseLevel(networkConfig.LogLevel)
3041
if err != nil {
31-
log.WithField("log-level", networkConfig.CfgFile).Fatal("incorrect log level")
32-
33-
return fmt.Errorf("incorrect log level")
42+
return fmt.Errorf("❌ incorrect log level: %v", err)
3443
}
3544

3645
log.SetLevel(lvl)
3746
log.WithField("log-level", networkConfig.CfgFile).Debug("log level configured")
3847

3948
// validate the config
40-
return validateSubnets(cmd, args)
49+
err = validateSubnets(cmd, args)
50+
if err != nil {
51+
return fmt.Errorf("❌ incorrect subnets: %v", err)
52+
}
53+
54+
err = validateMode(cmd, args)
55+
if err != nil {
56+
return fmt.Errorf("❌ incorrect mode: %v", err)
57+
}
58+
59+
return nil
4160
}
4261

4362
func validateSubnets(cmd *cobra.Command, args []string) error {
@@ -53,6 +72,17 @@ func validateSubnets(cmd *cobra.Command, args []string) error {
5372
return nil
5473
}
5574

75+
func validateMode(cmd *cobra.Command, args []string) error {
76+
// Validate mode
77+
mode, err := runner.VaildateMode(flags.ModeVar)
78+
if err != nil {
79+
return err
80+
}
81+
flags.Mode = mode
82+
83+
return nil
84+
}
85+
5686
func bindFlags(cmd *cobra.Command, v *viper.Viper) {
5787
cmd.PersistentFlags().VisitAll(func(f *pflag.Flag) {
5888
// Environment variables can't have dashes in them, so bind them to their equivalent
@@ -95,8 +125,8 @@ func init() {
95125
networkCheckCmd.PersistentFlags().StringSliceVar(&networkConfig.HttpsHosts, "https-hosts", []string{}, "Hosts to test for outbound HTTPS connectivity")
96126
networkCheckCmd.PersistentFlags().StringVar(&networkConfig.InstanceAMI, "instance-ami", "", "Custom ec2 instance AMI id, if not set will use latest ubuntu")
97127
networkCheckCmd.PersistentFlags().StringVar(&networkConfig.ApiEndpoint, "api-endpoint", "", "The Gitpod Enterprise control plane's regional API endpoint subdomain")
98-
networkCheckCmd.Flags().StringSliceVar(&flags.SelectedTestsets, "testsets", []string{"aws-services-pod-subnet", "aws-services-main-subnet", "https-hosts-main-subnet"}, "List of testsets to run (options: aws-services-pod-subnet, aws-services-main-subnet, https-hosts-main-subnet)")
99-
networkCheckCmd.Flags().StringVar(&flags.ModeVar, "mode", string(ModeEC2), "How to run the tests (default: ec2, options: ec2, lamda)")
128+
networkCheckCmd.PersistentFlags().StringSliceVar(&flags.SelectedTestsets, "testsets", []string{"aws-services-pod-subnet", "aws-services-main-subnet", "https-hosts-main-subnet"}, "List of testsets to run (options: aws-services-pod-subnet, aws-services-main-subnet, https-hosts-main-subnet)")
129+
networkCheckCmd.PersistentFlags().StringVar(&flags.ModeVar, "mode", string(runner.ModeEC2), "How to run the tests (default: ec2, options: ec2, lambda, local)")
100130
bindFlags(networkCheckCmd, v)
101131
log.Infof("ℹ️ Running with region `%s`, main subnet `%v`, pod subnet `%v`, hosts `%v`, ami `%v`, and API endpoint `%v`", networkConfig.AwsRegion, networkConfig.MainSubnets, networkConfig.PodSubnets, networkConfig.HttpsHosts, networkConfig.InstanceAMI, networkConfig.ApiEndpoint)
102132
}

gitpod-network-check/pkg/runner/common.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,55 @@ package runner
22

33
import (
44
"context"
5+
"fmt"
6+
"maps"
7+
"slices"
58

69
"github.com/aws/aws-sdk-go-v2/aws"
710
"github.com/aws/aws-sdk-go-v2/config"
811

912
"github.com/gitpod-io/enterprise-deployment-toolkit/gitpod-network-check/pkg/checks"
1013
)
1114

15+
type Mode string
16+
17+
const (
18+
ModeEC2 Mode = "ec2"
19+
ModeLambda Mode = "lambda"
20+
ModeLocal Mode = "local"
21+
)
22+
23+
var validModes = map[string]bool{
24+
string(ModeLambda): true,
25+
string(ModeEC2): true,
26+
string(ModeLocal): true,
27+
}
28+
29+
func VaildateMode(modeStr string) (Mode, error) {
30+
if _, ok := validModes[modeStr]; ok {
31+
return Mode(modeStr), nil
32+
}
33+
return "", fmt.Errorf("invalid mode: %s, must be one of: %v", modeStr, slices.Collect(maps.Keys(validModes)))
34+
}
35+
1236
type TestRunner interface {
1337
Prepare(ctx context.Context) error
1438
TestService(ctx context.Context, subnets []checks.Subnet, serviceEndpoints map[string]string) (bool, error)
1539
Cleanup(ctx context.Context) error
1640
}
1741

42+
func NewRunner(ctx context.Context, mode Mode, config *checks.NetworkConfig) (TestRunner, error) {
43+
switch mode {
44+
case ModeEC2:
45+
return NewEC2TestRunner(context.Background(), config)
46+
case ModeLocal:
47+
return NewLocalTestRunner(), nil
48+
default:
49+
return nil, fmt.Errorf("invalid mode: %s, must be one of: %v", mode, slices.Collect(maps.Keys(validModes)))
50+
}
51+
// TODO: Add logic for ModeLambda if/when implemented
52+
}
53+
1854
func initAwsConfig(ctx context.Context, region string) (aws.Config, error) {
1955
return config.LoadDefaultConfig(ctx, config.WithRegion(region))
2056
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package runner
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"net/url"
8+
"time"
9+
10+
log "github.com/sirupsen/logrus"
11+
12+
"github.com/gitpod-io/enterprise-deployment-toolkit/gitpod-network-check/pkg/checks"
13+
)
14+
15+
// LocalTestRunner executes network checks directly from the local machine.
16+
type LocalTestRunner struct {
17+
// No fields needed for local runner currently
18+
}
19+
20+
// NewLocalTestRunner creates a new instance of LocalTestRunner.
21+
func NewLocalTestRunner() *LocalTestRunner {
22+
log.Info("ℹ️ Using local test runner")
23+
return &LocalTestRunner{}
24+
}
25+
26+
// Prepare performs any setup required for the local runner. Currently a no-op.
27+
func (r *LocalTestRunner) Prepare(ctx context.Context) error {
28+
log.Debug("Local runner Prepare: No preparation needed.")
29+
return nil // No setup needed for local execution
30+
}
31+
32+
// TestService runs connectivity tests to the specified service endpoints from the local machine.
33+
// The subnets parameter is ignored in local mode.
34+
func (r *LocalTestRunner) TestService(ctx context.Context, subnets []checks.Subnet, serviceEndpoints map[string]string) (bool, error) {
35+
log.Debugf("Local runner TestService: Ignoring subnets (%d provided)", len(subnets))
36+
overallSuccess := true
37+
38+
httpClient := &http.Client{
39+
Timeout: 15 * time.Second, // Sensible default timeout
40+
Transport: &http.Transport{
41+
// Consider adding proxy support if needed later
42+
// Proxy: http.ProxyFromEnvironment,
43+
DisableKeepAlives: true, // Avoid reusing connections for distinct tests
44+
},
45+
}
46+
47+
for name, endpointURL := range serviceEndpoints {
48+
log.Infof("ℹ️ Testing connectivity to %s (%s) locally...", name, endpointURL)
49+
50+
// Ensure URL includes scheme
51+
parsedURL, err := url.Parse(endpointURL)
52+
if err != nil {
53+
log.Errorf("❌ Failed to parse URL for %s (%s): %v", name, endpointURL, err)
54+
overallSuccess = false
55+
continue
56+
}
57+
if parsedURL.Scheme == "" {
58+
// Default to HTTPS if no scheme is provided
59+
parsedURL.Scheme = "https"
60+
endpointURL = parsedURL.String()
61+
log.Debugf("Assuming HTTPS for %s: %s", name, endpointURL)
62+
}
63+
64+
req, err := http.NewRequestWithContext(ctx, http.MethodHead, endpointURL, nil)
65+
if err != nil {
66+
log.Errorf("❌ Failed to create request for %s (%s): %v", name, endpointURL, err)
67+
overallSuccess = false
68+
continue
69+
}
70+
71+
// Add a user-agent?
72+
// req.Header.Set("User-Agent", "gitpod-network-check/local")
73+
74+
resp, err := httpClient.Do(req)
75+
if err != nil {
76+
log.Errorf("❌ Failed to connect to %s (%s): %v", name, endpointURL, err)
77+
overallSuccess = false
78+
continue
79+
}
80+
resp.Body.Close() // Ensure body is closed even if not read
81+
82+
// Consider any 2xx or 3xx status code as success for a HEAD request.
83+
// Some services might return 403 Forbidden for HEAD but are still reachable.
84+
// Let's be lenient for now and accept anything < 500.
85+
if resp.StatusCode >= 500 {
86+
log.Errorf("❌ Connection test failed for %s (%s): Received status code %d", name, endpointURL, resp.StatusCode)
87+
overallSuccess = false
88+
} else {
89+
log.Infof("✅ Successfully connected to %s (%s) - Status: %d", name, endpointURL, resp.StatusCode)
90+
}
91+
}
92+
93+
if !overallSuccess {
94+
return false, fmt.Errorf("one or more local connectivity tests failed")
95+
}
96+
97+
log.Info("✅ All local connectivity tests passed.")
98+
return true, nil
99+
}
100+
101+
// Cleanup performs any teardown required for the local runner. Currently a no-op.
102+
func (r *LocalTestRunner) Cleanup(ctx context.Context) error {
103+
log.Debug("Local runner Cleanup: No cleanup needed.")
104+
return nil // No cleanup needed for local execution
105+
}

memory-bank/activeContext.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Active Context: gitpod-network-check (2025-04-03)
2+
3+
**Current Focus:**
4+
5+
Implement a new "local" execution mode for the `gitpod-network-check diagnose` command.
6+
7+
**Recent Changes:**
8+
9+
* Memory Bank initialized (`projectbrief.md`, `productContext.md` created).
10+
* Analysis of existing code (`cmd/checks.go`, `pkg/runner/common.go`, `pkg/runner/ec2-runner.go`, `cmd/root.go`) completed in PLAN MODE.
11+
* Plan developed and approved for adding the `local` mode.
12+
13+
**Next Steps:**
14+
15+
1. Create remaining core Memory Bank files (`systemPatterns.md`, `techContext.md`, `progress.md`).
16+
2. Create `gitpod-network-check/pkg/runner/local-runner.go`.
17+
3. Implement the `TestRunner` interface within `local-runner.go` using `net/http` for checks.
18+
4. Update `gitpod-network-check/cmd/checks.go` to add the `local` mode constant, update `validModes`, and add instantiation logic for `LocalTestRunner`.
19+
5. Update `gitpod-network-check/cmd/root.go` to modify the help text for the `--mode` flag.

memory-bank/productContext.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Product Context: gitpod-network-check
2+
3+
**Problem Solved:**
4+
5+
Deploying Gitpod requires specific network configurations to allow communication between its components and various external services (AWS APIs, container registries, identity providers, etc.). Misconfigurations are common and can be difficult to diagnose, leading to deployment failures or runtime issues. This tool aims to proactively identify these network connectivity problems before or during Gitpod installation/updates.
6+
7+
**How it Should Work:**
8+
9+
The tool executes predefined sets of network connectivity tests (`TestSets`) targeting specific endpoints required by Gitpod. These tests are run from environments that simulate where Gitpod components would run (e.g., within specific AWS subnets).
10+
11+
* **Modes:** The tool supports different execution modes:
12+
* `ec2`: (Existing) Launches temporary EC2 instances in specified subnets to run tests. Requires AWS credentials and permissions.
13+
* `lambda`: (Planned/Partially Implemented?) Uses AWS Lambda functions for testing.
14+
* `local`: (Current Task) Runs tests directly from the machine executing the CLI using standard Go libraries. Useful for basic outbound checks or when AWS resources aren't desired/available.
15+
* **Test Sets:** Groups of related checks (e.g., connectivity to core AWS services from pod subnets).
16+
* **Configuration:** Network details (subnets, region) and test parameters (hosts) are provided via CLI flags or a configuration file.
17+
* **Output:** Logs detailed information about each check, clearly indicating success or failure.
18+
* **Cleanup:** Automatically removes any temporary resources created during the `ec2` mode run.
19+
20+
**User Experience Goals:**
21+
22+
* **Simplicity:** Easy to run with sensible defaults.
23+
* **Clarity:** Provide clear pass/fail results and informative error messages.
24+
* **Flexibility:** Allow users to select specific test sets and execution modes.
25+
* **Reliability:** Accurately reflect the network connectivity status relevant to Gitpod.

0 commit comments

Comments
 (0)