@@ -6,6 +6,8 @@ package ports
6
6
7
7
import (
8
8
"context"
9
+ "fmt"
10
+ "net/url"
9
11
"time"
10
12
11
13
backoff "github.com/cenkalti/backoff/v4"
@@ -57,9 +59,14 @@ func (*NoopExposedPorts) Expose(ctx context.Context, local uint32, public bool)
57
59
// GitpodExposedPorts uses a connection to the Gitpod server to implement
58
60
// the ExposedPortsInterface.
59
61
type GitpodExposedPorts struct {
60
- WorkspaceID string
61
- InstanceID string
62
- C gitpod.APIInterface
62
+ WorkspaceID string
63
+ InstanceID string
64
+ WorkspaceUrl string
65
+ C gitpod.APIInterface
66
+
67
+ localExposedPort []uint32
68
+ localExposedNotice chan struct {}
69
+ lastServerExposed []* gitpod.WorkspaceInstancePort
63
70
64
71
requests chan * exposePortRequest
65
72
}
@@ -71,15 +78,35 @@ type exposePortRequest struct {
71
78
}
72
79
73
80
// NewGitpodExposedPorts creates a new instance of GitpodExposedPorts
74
- func NewGitpodExposedPorts (workspaceID string , instanceID string , gitpodService gitpod.APIInterface ) * GitpodExposedPorts {
81
+ func NewGitpodExposedPorts (workspaceID string , instanceID string , workspaceUrl string , gitpodService gitpod.APIInterface ) * GitpodExposedPorts {
75
82
return & GitpodExposedPorts {
76
- WorkspaceID : workspaceID ,
77
- InstanceID : instanceID ,
78
- C : gitpodService ,
83
+ WorkspaceID : workspaceID ,
84
+ InstanceID : instanceID ,
85
+ WorkspaceUrl : workspaceUrl ,
86
+ C : gitpodService ,
79
87
80
88
// allow clients to submit 30 expose requests without blocking
81
- requests : make (chan * exposePortRequest , 30 ),
89
+ requests : make (chan * exposePortRequest , 30 ),
90
+ localExposedNotice : make (chan struct {}, 30 ),
91
+ }
92
+ }
93
+
94
+ func (g * GitpodExposedPorts ) getPortUrl (port uint32 ) string {
95
+ u , err := url .Parse (g .WorkspaceUrl )
96
+ if err != nil {
97
+ return ""
98
+ }
99
+ u .Host = fmt .Sprintf ("%d-%s" , port , u .Host )
100
+ return u .String ()
101
+ }
102
+
103
+ func (g * GitpodExposedPorts ) existInLocalExposed (port uint32 ) bool {
104
+ for _ , p := range g .localExposedPort {
105
+ if p == port {
106
+ return true
107
+ }
82
108
}
109
+ return false
83
110
}
84
111
85
112
// Observe starts observing the exposed ports until the context is canceled.
@@ -98,22 +125,41 @@ func (g *GitpodExposedPorts) Observe(ctx context.Context) (<-chan []ExposedPort,
98
125
errchan <- err
99
126
return
100
127
}
128
+ mixin := func (localExposedPort []uint32 , serverExposePort []* gitpod.WorkspaceInstancePort ) []ExposedPort {
129
+ res := make (map [uint32 ]ExposedPort )
130
+ for _ , port := range g .localExposedPort {
131
+ res [port ] = ExposedPort {
132
+ LocalPort : port ,
133
+ Public : false ,
134
+ URL : g .getPortUrl (port ),
135
+ }
136
+ }
137
+
138
+ for _ , p := range serverExposePort {
139
+ res [uint32 (p .Port )] = ExposedPort {
140
+ LocalPort : uint32 (p .Port ),
141
+ Public : p .Visibility == "public" ,
142
+ URL : p .URL ,
143
+ }
144
+ }
145
+ exposedPort := make ([]ExposedPort , 0 , len (res ))
146
+ for _ , p := range res {
147
+ exposedPort = append (exposedPort , p )
148
+ }
149
+ return exposedPort
150
+ }
101
151
for {
102
152
select {
103
153
case u := <- updates :
104
154
if u == nil {
105
155
return
106
156
}
157
+ g .lastServerExposed = u .Status .ExposedPorts
107
158
108
- res := make ([]ExposedPort , len (u .Status .ExposedPorts ))
109
- for i , p := range u .Status .ExposedPorts {
110
- res [i ] = ExposedPort {
111
- LocalPort : uint32 (p .Port ),
112
- Public : p .Visibility == "public" ,
113
- URL : p .URL ,
114
- }
115
- }
116
-
159
+ res := mixin (g .localExposedPort , g .lastServerExposed )
160
+ reschan <- res
161
+ case <- g .localExposedNotice :
162
+ res := mixin (g .localExposedPort , g .lastServerExposed )
117
163
reschan <- res
118
164
case <- ctx .Done ():
119
165
return
@@ -180,14 +226,19 @@ func (g *GitpodExposedPorts) doExpose(req *exposePortRequest) {
180
226
181
227
// Expose exposes a port to the internet. Upon successful execution any Observer will be updated.
182
228
func (g * GitpodExposedPorts ) Expose (ctx context.Context , local uint32 , public bool ) <- chan error {
183
- v := "private"
184
- if public {
185
- v = "public"
229
+ if ! public {
230
+ if ! g .existInLocalExposed (local ) {
231
+ g .localExposedPort = append (g .localExposedPort , local )
232
+ g .localExposedNotice <- struct {}{}
233
+ }
234
+ c := make (chan error )
235
+ close (c )
236
+ return c
186
237
}
187
238
req := & exposePortRequest {
188
239
port : & gitpod.WorkspaceInstancePort {
189
240
Port : float64 (local ),
190
- Visibility : v ,
241
+ Visibility : "public" ,
191
242
},
192
243
ctx : ctx ,
193
244
done : make (chan error ),
0 commit comments