diff --git a/components/gitpod-protocol/go/gitpod-service.go b/components/gitpod-protocol/go/gitpod-service.go index c3b18a1aac2a92..ba2770fb96c1d1 100644 --- a/components/gitpod-protocol/go/gitpod-service.go +++ b/components/gitpod-protocol/go/gitpod-service.go @@ -1951,7 +1951,6 @@ type PortConfig struct { Visibility string `json:"visibility,omitempty"` Description string `json:"description,omitempty"` Name string `json:"name,omitempty"` - Sort uint32 `json:"sort,omitempty"` } // TaskConfig is the TaskConfig message type diff --git a/components/supervisor/pkg/ports/ports-config.go b/components/supervisor/pkg/ports/ports-config.go index 9118e1e803dbb5..10b1f605c6c3f4 100644 --- a/components/supervisor/pkg/ports/ports-config.go +++ b/components/supervisor/pkg/ports/ports-config.go @@ -6,7 +6,6 @@ package ports import ( "context" - "errors" "fmt" "reflect" "regexp" @@ -16,36 +15,41 @@ import ( "github.com/gitpod-io/gitpod/supervisor/pkg/config" ) +const NON_CONFIGED_BASIC_SCORE = 100000 + // RangeConfig is a port range config. type RangeConfig struct { - *gitpod.PortsItems + gitpod.PortsItems Start uint32 End uint32 Sort uint32 } +// SortConfig is a port with a sort field +type SortConfig struct { + gitpod.PortConfig + Sort uint32 +} + // Configs provides access to port configurations. type Configs struct { - workspaceConfigs map[uint32]*gitpod.PortConfig - instancePortConfigs map[uint32]*gitpod.PortConfig + instancePortConfigs map[uint32]*SortConfig instanceRangeConfigs []*RangeConfig } // ForEach iterates over all configured ports. -func (configs *Configs) ForEach(callback func(port uint32, config *gitpod.PortConfig)) { +func (configs *Configs) ForEach(callback func(port uint32, config *SortConfig)) { if configs == nil { return } visited := make(map[uint32]struct{}) - for _, configs := range []map[uint32]*gitpod.PortConfig{configs.instancePortConfigs, configs.workspaceConfigs} { - for port, config := range configs { - _, exists := visited[port] - if exists { - continue - } - visited[port] = struct{}{} - callback(port, config) + for port, config := range configs.instancePortConfigs { + _, exists := visited[port] + if exists { + continue } + visited[port] = struct{}{} + callback(port, config) } } @@ -60,7 +64,7 @@ var ( ) // Get returns the config for the give port. -func (configs *Configs) Get(port uint32) (*gitpod.PortConfig, ConfigKind, bool) { +func (configs *Configs) Get(port uint32) (*SortConfig, ConfigKind, bool) { if configs == nil { return nil, PortConfigKind, false } @@ -68,19 +72,17 @@ func (configs *Configs) Get(port uint32) (*gitpod.PortConfig, ConfigKind, bool) if exists { return config, PortConfigKind, true } - config, exists = configs.workspaceConfigs[port] - if exists { - return config, PortConfigKind, true - } for _, rangeConfig := range configs.instanceRangeConfigs { if rangeConfig.Start <= port && port <= rangeConfig.End { - return &gitpod.PortConfig{ - Port: float64(port), - OnOpen: rangeConfig.OnOpen, - Visibility: rangeConfig.Visibility, - Description: rangeConfig.Description, - Name: rangeConfig.Name, - Sort: rangeConfig.Sort, + return &SortConfig{ + PortConfig: gitpod.PortConfig{ + Port: float64(port), + OnOpen: rangeConfig.OnOpen, + Visibility: rangeConfig.Visibility, + Description: rangeConfig.Description, + Name: rangeConfig.Name, + }, + Sort: rangeConfig.Sort, }, RangeConfigKind, true } } @@ -121,17 +123,6 @@ func (service *ConfigService) Observe(ctx context.Context) (<-chan *Configs, <-c configs := service.configService.Observe(ctx) current := &Configs{} - if service.gitpodAPI != nil { - info, err := service.gitpodAPI.GetWorkspace(ctx, service.workspaceID) - if err != nil { - errorsChan <- err - } else { - current.workspaceConfigs = parseWorkspaceConfigs(info.Workspace.Config.Ports) - updatesChan <- &Configs{workspaceConfigs: current.workspaceConfigs} - } - } else { - errorsChan <- errors.New("could not connect to Gitpod API to fetch workspace port configs") - } for { select { @@ -146,7 +137,6 @@ func (service *ConfigService) Observe(ctx context.Context) (<-chan *Configs, <-c continue } updatesChan <- &Configs{ - workspaceConfigs: current.workspaceConfigs, instancePortConfigs: current.instancePortConfigs, instanceRangeConfigs: current.instanceRangeConfigs, } @@ -170,22 +160,7 @@ func (service *ConfigService) update(config *gitpod.GitpodConfig, current *Confi var portRangeRegexp = regexp.MustCompile(`^(\d+)[-:](\d+)$`) -func parseWorkspaceConfigs(ports []*gitpod.PortConfig) (portConfigs map[uint32]*gitpod.PortConfig) { - if len(ports) == 0 { - return nil - } - portConfigs = make(map[uint32]*gitpod.PortConfig) - for _, config := range ports { - port := uint32(config.Port) - _, exists := portConfigs[port] - if !exists { - portConfigs[port] = config - } - } - return portConfigs -} - -func parseInstanceConfigs(ports []*gitpod.PortsItems) (portConfigs map[uint32]*gitpod.PortConfig, rangeConfigs []*RangeConfig) { +func parseInstanceConfigs(ports []*gitpod.PortsItems) (portConfigs map[uint32]*SortConfig, rangeConfigs []*RangeConfig) { for index, config := range ports { if config == nil { continue @@ -195,18 +170,20 @@ func parseInstanceConfigs(ports []*gitpod.PortsItems) (portConfigs map[uint32]*g Port, err := strconv.ParseUint(rawPort, 10, 16) if err == nil { if portConfigs == nil { - portConfigs = make(map[uint32]*gitpod.PortConfig) + portConfigs = make(map[uint32]*SortConfig) } port := uint32(Port) _, exists := portConfigs[port] if !exists { - portConfigs[port] = &gitpod.PortConfig{ - OnOpen: config.OnOpen, - Port: float64(Port), - Visibility: config.Visibility, - Description: config.Description, - Name: config.Name, - Sort: uint32(index), + portConfigs[port] = &SortConfig{ + PortConfig: gitpod.PortConfig{ + OnOpen: config.OnOpen, + Port: float64(Port), + Visibility: config.Visibility, + Description: config.Description, + Name: config.Name, + }, + Sort: uint32(index), } } continue @@ -224,7 +201,7 @@ func parseInstanceConfigs(ports []*gitpod.PortsItems) (portConfigs map[uint32]*g continue } rangeConfigs = append(rangeConfigs, &RangeConfig{ - PortsItems: config, + PortsItems: *config, Start: uint32(start), End: uint32(end), Sort: uint32(index), diff --git a/components/supervisor/pkg/ports/ports-config_test.go b/components/supervisor/pkg/ports/ports-config_test.go index 39c88d7490741d..632bd849320649 100644 --- a/components/supervisor/pkg/ports/ports-config_test.go +++ b/components/supervisor/pkg/ports/ports-config_test.go @@ -16,38 +16,14 @@ import ( func TestPortsConfig(t *testing.T) { tests := []struct { - Desc string - WorkspacePorts []*gitpod.PortConfig - GitpodConfig *gitpod.GitpodConfig - Expectation *PortConfigTestExpectations + Desc string + GitpodConfig *gitpod.GitpodConfig + Expectation *PortConfigTestExpectations }{ { Desc: "no configs", Expectation: &PortConfigTestExpectations{}, }, - { - Desc: "workspace port config", - WorkspacePorts: []*gitpod.PortConfig{ - { - Port: 9229, - OnOpen: "ignore", - Visibility: "public", - Name: "Nice Port Name", - Description: "Nice Port Description", - }, - }, - Expectation: &PortConfigTestExpectations{ - WorkspaceConfigs: []*gitpod.PortConfig{ - { - Port: 9229, - OnOpen: "ignore", - Visibility: "public", - Name: "Nice Port Name", - Description: "Nice Port Description", - }, - }, - }, - }, { Desc: "instance port config", GitpodConfig: &gitpod.GitpodConfig{ @@ -89,7 +65,7 @@ func TestPortsConfig(t *testing.T) { Expectation: &PortConfigTestExpectations{ InstanceRangeConfigs: []*RangeConfig{ { - PortsItems: &gitpod.PortsItems{ + PortsItems: gitpod.PortsItems{ Port: "9229-9339", OnOpen: "ignore", Visibility: "public", @@ -119,26 +95,11 @@ func TestPortsConfig(t *testing.T) { defer ctrl.Finish() gitpodAPI := gitpod.NewMockAPIInterface(ctrl) - gitpodAPI.EXPECT().GetWorkspace(context, workspaceID).Times(1).Return(&gitpod.WorkspaceInfo{ - Workspace: &gitpod.Workspace{ - Config: &gitpod.WorkspaceConfig{ - Ports: test.WorkspacePorts, - }, - }, - }, nil) service := NewConfigService(workspaceID, configService, gitpodAPI) updates, errors := service.Observe(context) actual := &PortConfigTestExpectations{} - select { - case err := <-errors: - t.Fatal(err) - case change := <-updates: - for _, config := range change.workspaceConfigs { - actual.WorkspaceConfigs = append(actual.WorkspaceConfigs, config) - } - } if test.GitpodConfig != nil { go func() { @@ -150,7 +111,7 @@ func TestPortsConfig(t *testing.T) { case change := <-updates: actual.InstanceRangeConfigs = change.instanceRangeConfigs for _, config := range change.instancePortConfigs { - actual.InstancePortConfigs = append(actual.InstancePortConfigs, config) + actual.InstancePortConfigs = append(actual.InstancePortConfigs, &config.PortConfig) } } } @@ -163,7 +124,6 @@ func TestPortsConfig(t *testing.T) { } type PortConfigTestExpectations struct { - WorkspaceConfigs []*gitpod.PortConfig InstancePortConfigs []*gitpod.PortConfig InstanceRangeConfigs []*RangeConfig } diff --git a/components/supervisor/pkg/ports/ports.go b/components/supervisor/pkg/ports/ports.go index 1570f51af7eaae..d7bd4a96a3f643 100644 --- a/components/supervisor/pkg/ports/ports.go +++ b/components/supervisor/pkg/ports/ports.go @@ -291,7 +291,7 @@ func (pm *Manager) updateState(ctx context.Context, exposed []ExposedPort, serve stateChanged := !reflect.DeepEqual(newState, pm.state) pm.state = newState - if !stateChanged { + if !stateChanged && configured == nil { return } @@ -315,10 +315,14 @@ func (pm *Manager) nextState(ctx context.Context) map[uint32]*managedPort { return mp } config, _, exists := pm.configs.Get(port) + var portConfig *gitpod.PortConfig + if exists && config != nil { + portConfig = &config.PortConfig + } mp := &managedPort{ LocalhostPort: port, - OnExposed: getOnExposedAction(config, port), - OnOpen: getOnOpenAction(config, port), + OnExposed: getOnExposedAction(portConfig, port), + OnOpen: getOnOpenAction(portConfig, port), } if exists { mp.Name = config.Name @@ -358,7 +362,7 @@ func (pm *Manager) nextState(ctx context.Context) map[uint32]*managedPort { // 2. second capture configured since we don't want to auto expose already exposed ports if pm.configs != nil { - pm.configs.ForEach(func(port uint32, config *gitpod.PortConfig) { + pm.configs.ForEach(func(port uint32, config *SortConfig) { if pm.boundInternally(port) { return } @@ -740,17 +744,12 @@ func (pm *Manager) Subscribe() (*Subscription, error) { func (pm *Manager) getStatus() []*api.PortsStatus { res := make([]*api.PortsStatus, 0, len(pm.state)) for port := range pm.state { - portStatus := pm.getPortStatus(port) - // Filter out ports that not served and not inside `.gitpod.yml` - if _, _, ok := pm.configs.Get(port); !ok && !portStatus.Served { - continue - } - res = append(res, portStatus) + res = append(res, pm.getPortStatus(port)) } sort.SliceStable(res, func(i, j int) bool { // Max number of port 65536 - score1 := 100000 + res[i].LocalPort - score2 := 100000 + res[j].LocalPort + score1 := NON_CONFIGED_BASIC_SCORE + res[i].LocalPort + score2 := NON_CONFIGED_BASIC_SCORE + res[j].LocalPort if c, _, ok := pm.configs.Get(res[i].LocalPort); ok { score1 = c.Sort } diff --git a/components/supervisor/pkg/ports/ports_test.go b/components/supervisor/pkg/ports/ports_test.go index 7156b236ec8686..c5a5d37ff4f79d 100644 --- a/components/supervisor/pkg/ports/ports_test.go +++ b/components/supervisor/pkg/ports/ports_test.go @@ -25,8 +25,7 @@ func TestPortsUpdateState(t *testing.T) { type ExposureExpectation []ExposedPort type UpdateExpectation [][]*api.PortsStatus type ConfigChange struct { - workspace []*gitpod.PortConfig - instance []*gitpod.PortsItems + instance []*gitpod.PortsItems } type Change struct { Config *ConfigChange @@ -63,8 +62,8 @@ func TestPortsUpdateState(t *testing.T) { []*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private}}, []*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}}, []*api.PortsStatus{{LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}, {LocalPort: 60000, Served: true}}, - []*api.PortsStatus{{LocalPort: 60000, Served: true}}, - {}, + []*api.PortsStatus{{LocalPort: 8080, Served: false, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}, {LocalPort: 60000, Served: true}}, + []*api.PortsStatus{{LocalPort: 8080, Served: false, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{OnExposed: api.OnPortExposedAction_notify_private, Visibility: api.PortVisibility_private, Url: "foobar"}}}, }, }, { @@ -109,47 +108,6 @@ func TestPortsUpdateState(t *testing.T) { ExpectedExposure: ExposureExpectation(nil), ExpectedUpdates: UpdateExpectation{{}}, }, - { - Desc: "serving configured workspace port", - Changes: []Change{ - {Config: &ConfigChange{ - workspace: []*gitpod.PortConfig{ - {Port: 8080, OnOpen: "open-browser"}, - {Port: 9229, OnOpen: "ignore", Visibility: "private"}, - }, - }}, - { - Exposed: []ExposedPort{ - {LocalPort: 8080, Public: true, URL: "8080-foobar"}, - {LocalPort: 9229, Public: false, URL: "9229-foobar"}, - }, - }, - { - Served: []ServedPort{ - {net.IPv4zero, 8080, false}, - {net.IPv4(127, 0, 0, 1), 9229, true}, - }, - }, - }, - ExpectedExposure: []ExposedPort{ - {LocalPort: 8080}, - {LocalPort: 9229}, - }, - ExpectedUpdates: UpdateExpectation{ - {}, - []*api.PortsStatus{ - {LocalPort: 8080, OnOpen: api.PortsStatus_open_browser}, - {LocalPort: 9229, OnOpen: api.PortsStatus_ignore}}, - []*api.PortsStatus{ - {LocalPort: 8080, OnOpen: api.PortsStatus_open_browser, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, Url: "8080-foobar", OnExposed: api.OnPortExposedAction_open_browser}}, - {LocalPort: 9229, OnOpen: api.PortsStatus_ignore, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, Url: "9229-foobar", OnExposed: api.OnPortExposedAction_ignore}}, - }, - []*api.PortsStatus{ - {LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_open_browser, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, Url: "8080-foobar", OnExposed: api.OnPortExposedAction_open_browser}}, - {LocalPort: 9229, Served: true, OnOpen: api.PortsStatus_ignore, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, Url: "9229-foobar", OnExposed: api.OnPortExposedAction_ignore}}, - }, - }, - }, { Desc: "serving port from the configured port range", Changes: []Change{ @@ -168,6 +126,7 @@ func TestPortsUpdateState(t *testing.T) { {LocalPort: 60000}, }, ExpectedUpdates: UpdateExpectation{ + {}, {}, []*api.PortsStatus{{LocalPort: 4040, Served: true, OnOpen: api.PortsStatus_open_browser}}, []*api.PortsStatus{{LocalPort: 4040, Served: true, OnOpen: api.PortsStatus_open_browser, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_public, Url: "4040-foobar", OnExposed: api.OnPortExposedAction_open_browser}}}, @@ -181,7 +140,7 @@ func TestPortsUpdateState(t *testing.T) { Desc: "auto expose configured ports", Changes: []Change{ { - Config: &ConfigChange{workspace: []*gitpod.PortConfig{ + Config: &ConfigChange{instance: []*gitpod.PortsItems{ {Port: 8080, Visibility: "private"}, }}, }, @@ -234,8 +193,8 @@ func TestPortsUpdateState(t *testing.T) { ExpectedUpdates: UpdateExpectation{ {}, { - {LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private}, {LocalPort: 3000, Served: true, OnOpen: api.PortsStatus_notify_private}, + {LocalPort: 8080, Served: true, OnOpen: api.PortsStatus_notify_private}, }, }, }, @@ -243,7 +202,7 @@ func TestPortsUpdateState(t *testing.T) { Desc: "served between auto exposing configured and exposed update", Changes: []Change{ { - Config: &ConfigChange{workspace: []*gitpod.PortConfig{ + Config: &ConfigChange{instance: []*gitpod.PortsItems{ {Port: 8080, Visibility: "private"}, }}, }, @@ -450,7 +409,7 @@ func TestPortsUpdateState(t *testing.T) { Desc: "port status has description set as soon as the port gets exposed, if there was a description configured", Changes: []Change{ { - Config: &ConfigChange{workspace: []*gitpod.PortConfig{ + Config: &ConfigChange{instance: []*gitpod.PortsItems{ {Port: 8080, Visibility: "private", Description: "Development server"}, }}, }, @@ -475,7 +434,7 @@ func TestPortsUpdateState(t *testing.T) { Desc: "port status has the name attribute set as soon as the port gets exposed, if there was a name configured in Gitpod's Workspace", Changes: []Change{ { - Config: &ConfigChange{workspace: []*gitpod.PortConfig{ + Config: &ConfigChange{instance: []*gitpod.PortsItems{ {Port: 3000, Visibility: "private", Name: "react"}, }}, }, @@ -496,6 +455,193 @@ func TestPortsUpdateState(t *testing.T) { {{LocalPort: 3000, Name: "react", Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}}, }, }, + { + Desc: "change configed ports order", + Changes: []Change{ + { + Config: &ConfigChange{instance: []*gitpod.PortsItems{ + {Port: 3001, Visibility: "private", Name: "react"}, + {Port: 3000, Visibility: "private", Name: "react"}, + }}, + }, + { + Config: &ConfigChange{instance: []*gitpod.PortsItems{ + {Port: "5000-5999", Visibility: "private", Name: "react"}, + {Port: 3001, Visibility: "private", Name: "react"}, + {Port: 3000, Visibility: "private", Name: "react"}, + }}, + }, + { + Served: []ServedPort{{net.IPv4zero, 5002, false}}, + }, + { + Served: []ServedPort{{net.IPv4zero, 5002, false}, {net.IPv4zero, 5001, false}}, + }, + { + Config: &ConfigChange{instance: []*gitpod.PortsItems{ + {Port: 3000, Visibility: "private", Name: "react"}, + {Port: 3001, Visibility: "private", Name: "react"}, + }}, + }, + { + Served: []ServedPort{{net.IPv4zero, 5001, false}, {net.IPv4zero, 3000, false}}, + }, + { + Exposed: []ExposedPort{{LocalPort: 3000, Public: false, URL: "foobar"}}, + }, + }, + ExpectedExposure: []ExposedPort{ + {LocalPort: 5002}, + {LocalPort: 5001}, + {LocalPort: 3000}, + {LocalPort: 3001}, + }, + ExpectedUpdates: UpdateExpectation{ + {}, + { + {LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify}, + }, + { + {LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify}, + }, + { + {LocalPort: 5002, Name: "react", Served: true, OnOpen: api.PortsStatus_notify}, + {LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify}, + }, + { + {LocalPort: 5001, Name: "react", Served: true, OnOpen: api.PortsStatus_notify}, + {LocalPort: 5002, Name: "react", Served: true, OnOpen: api.PortsStatus_notify}, + {LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify}, + }, + { + {LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 5001, Served: true, OnOpen: api.PortsStatus_notify_private}, + {LocalPort: 5002, Served: true, OnOpen: api.PortsStatus_notify_private}, + }, + { + {LocalPort: 3000, Name: "react", Served: true, OnOpen: api.PortsStatus_notify}, + {LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 5001, Served: true, OnOpen: api.PortsStatus_notify_private}, + }, + { + {LocalPort: 3000, Name: "react", Served: true, OnOpen: api.PortsStatus_notify, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify, Url: "foobar"}}, + {LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 5001, Served: true, OnOpen: api.PortsStatus_notify_private}, + }, + }, + }, + { + Desc: "change configed ports order with ranged covered not ranged", + Changes: []Change{ + { + Config: &ConfigChange{ + instance: []*gitpod.PortsItems{ + {Port: 3001, Visibility: "private", Name: "react"}, + {Port: 3000, Visibility: "private", Name: "react"}, + }, + }, + }, + { + Config: &ConfigChange{ + instance: []*gitpod.PortsItems{ + {Port: 3003, Visibility: "private", Name: "react"}, + {Port: 3001, Visibility: "private", Name: "react"}, + {Port: "3001-3005", Visibility: "private", Name: "react"}, + {Port: 3000, Visibility: "private", Name: "react"}, + }, + }, + }, + { + Served: []ServedPort{{net.IPv4zero, 3000, false}}, + }, + { + Served: []ServedPort{{net.IPv4zero, 3000, false}, {net.IPv4zero, 3001, false}, {net.IPv4zero, 3002, false}}, + }, + { + Config: &ConfigChange{ + instance: []*gitpod.PortsItems{ + {Port: 3003, Visibility: "private", Name: "react"}, + {Port: 3000, Visibility: "private", Name: "react"}, + }, + }, + }, + { + Config: &ConfigChange{ + instance: []*gitpod.PortsItems{ + {Port: "3001-3005", Visibility: "private", Name: "react"}, + {Port: 3003, Visibility: "private", Name: "react"}, + {Port: 3000, Visibility: "private", Name: "react"}, + }, + }, + }, + }, + ExpectedExposure: []ExposedPort{ + {LocalPort: 3000}, + {LocalPort: 3001}, + {LocalPort: 3002}, + {LocalPort: 3003}, + }, + ExpectedUpdates: UpdateExpectation{ + {}, + { + {LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify}, + }, + { + {LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3000, Name: "react", OnOpen: api.PortsStatus_notify}, + }, + { + {LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3001, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify}, + }, + { + {LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3001, Served: true, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3002, Served: true, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify}, + }, + { + {LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3001, Served: true, OnOpen: api.PortsStatus_notify_private}, + {LocalPort: 3002, Served: true, OnOpen: api.PortsStatus_notify_private}, + }, + { + {LocalPort: 3001, Name: "react", Served: true, OnOpen: api.PortsStatus_notify}, + {LocalPort: 3002, Name: "react", Served: true, OnOpen: api.PortsStatus_notify}, + {LocalPort: 3003, Name: "react", OnOpen: api.PortsStatus_notify}, + {LocalPort: 3000, Served: true, Name: "react", OnOpen: api.PortsStatus_notify}, + }, + }, + }, + { + // Please make sure this test pass for code browser resolveExternalPort + // see also https://github.com/gitpod-io/openvscode-server/blob/5ab7644a8bbf37d28e23212bc6f1529cafd8bf7b/extensions/gitpod-web/src/extension.ts#L310-L339 + Desc: "expose port without served, port should be responded for use case of openvscode-server", + Changes: []Change{ + { + Exposed: []ExposedPort{{LocalPort: 3000, Public: false, URL: "foobar"}}, + }, + }, + // this will not exposed because test manager didn't implement it properly + // ExpectedExposure: []ExposedPort{ + // {LocalPort: 3000}, + // }, + ExpectedUpdates: UpdateExpectation{ + {}, + { + {LocalPort: 3000, OnOpen: api.PortsStatus_notify_private, Exposed: &api.ExposedPortInfo{Visibility: api.PortVisibility_private, OnExposed: api.OnPortExposedAction_notify_private, Url: "foobar"}}, + }, + }, + }, } log.Log.Logger.SetLevel(logrus.FatalLevel) @@ -558,7 +704,6 @@ func TestPortsUpdateState(t *testing.T) { for _, c := range test.Changes { if c.Config != nil { change := &Configs{} - change.workspaceConfigs = parseWorkspaceConfigs(c.Config.workspace) portConfigs, rangeConfigs := parseInstanceConfigs(c.Config.instance) change.instancePortConfigs = portConfigs change.instanceRangeConfigs = rangeConfigs @@ -584,8 +729,6 @@ func TestPortsUpdateState(t *testing.T) { wg.Wait() var ( - sorPorts = cmpopts.SortSlices(func(x, y uint32) bool { return x < y }) - sortPortStatus = cmpopts.SortSlices(func(x, y *api.PortsStatus) bool { return x.LocalPort < y.LocalPort }) sortExposed = cmpopts.SortSlices(func(x, y ExposedPort) bool { return x.LocalPort < y.LocalPort }) ignoreUnexported = cmpopts.IgnoreUnexported( api.PortsStatus{}, @@ -596,7 +739,7 @@ func TestPortsUpdateState(t *testing.T) { t.Errorf("unexpected exposures (-want +got):\n%s", diff) } - if diff := cmp.Diff(test.ExpectedUpdates, UpdateExpectation(updts), sorPorts, sortPortStatus, ignoreUnexported); diff != "" { + if diff := cmp.Diff(test.ExpectedUpdates, UpdateExpectation(updts), ignoreUnexported); diff != "" { t.Errorf("unexpected updates (-want +got):\n%s", diff) } }) @@ -750,9 +893,13 @@ func TestPortsConcurrentSubscribe(t *testing.T) { } func TestManager_getStatus(t *testing.T) { + type portState struct { + port uint32 + notServed bool + } type fields struct { orderInYaml []any - state []uint32 + state []portState } tests := []struct { name string @@ -764,7 +911,7 @@ func TestManager_getStatus(t *testing.T) { fields: fields{ // The port number (e.g. 1337) or range (e.g. 3000-3999) to expose. orderInYaml: []any{1002, 1000, "3000-3999", 1001}, - state: []uint32{1000, 1001, 1002, 3003, 3001, 3002, 4002, 4000, 5000, 5005}, + state: []portState{{port: 1000}, {port: 1001}, {port: 1002}, {port: 3003}, {port: 3001}, {port: 3002}, {port: 4002}, {port: 4000}, {port: 5000}, {port: 5005}}, }, want: []uint32{1002, 1000, 3001, 3002, 3003, 1001, 4000, 4002, 5000, 5005}, }, @@ -772,7 +919,7 @@ func TestManager_getStatus(t *testing.T) { name: "order for ranged ports and inside ranged order by number ASC", fields: fields{ orderInYaml: []any{1002, "3000-3999", 1009, "4000-4999"}, - state: []uint32{5000, 1000, 1009, 4000, 4001, 3000, 3009}, + state: []portState{{port: 5000}, {port: 1000}, {port: 1009}, {port: 4000}, {port: 4001}, {port: 3000}, {port: 3009}}, }, want: []uint32{3000, 3009, 1009, 4000, 4001, 1000, 5000}, }, @@ -780,11 +927,20 @@ func TestManager_getStatus(t *testing.T) { name: "served ports order by number ASC", fields: fields{ orderInYaml: []any{}, - state: []uint32{4000, 4003, 4007, 4001, 4006}, + state: []portState{{port: 4000}, {port: 4003}, {port: 4007}, {port: 4001}, {port: 4006}}, }, want: []uint32{4000, 4001, 4003, 4006, 4007}, }, - + { + // Please make sure this test pass for code browser resolveExternalPort + // see also https://github.com/gitpod-io/openvscode-server/blob/5ab7644a8bbf37d28e23212bc6f1529cafd8bf7b/extensions/gitpod-web/src/extension.ts#L310-L339 + name: "expose not served ports should respond their status", + fields: fields{ + orderInYaml: []any{}, + state: []portState{{port: 4000, notServed: true}}, + }, + want: []uint32{4000}, + }, // It will not works because we do not `Run` ports Manger // As ports Manger will autoExpose those ports (but not ranged port) in yaml // and they will exists in state @@ -800,11 +956,11 @@ func TestManager_getStatus(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { state := make(map[uint32]*managedPort) - for _, port := range tt.fields.state { - state[port] = &managedPort{ - Served: true, - LocalhostPort: port, - TunneledTargetPort: port, + for _, s := range tt.fields.state { + state[s.port] = &managedPort{ + Served: !s.notServed, + LocalhostPort: s.port, + TunneledTargetPort: s.port, TunneledClients: map[string]uint32{}, } }