-
Notifications
You must be signed in to change notification settings - Fork 7.4k
Update microsoft dev-tunnels to v0.1.13 #11205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ import ( | |
| "net/http/httptest" | ||
| "net/url" | ||
| "regexp" | ||
| "strconv" | ||
| "strings" | ||
| "sync" | ||
| "time" | ||
|
|
@@ -25,7 +26,28 @@ import ( | |
| "golang.org/x/crypto/ssh" | ||
| ) | ||
|
|
||
| func NewMockHttpClient() (*http.Client, error) { | ||
| type mockClientOpts struct { | ||
| ports map[int]tunnels.TunnelPort // Port number to protocol | ||
| } | ||
|
|
||
| type mockClientOpt func(*mockClientOpts) | ||
|
|
||
| // WithSpecificPorts allows you to specify a map of ports to TunnelPorts that will be returned by the mock HTTP client. | ||
| // Note that this does not take a copy of the map, so you should not modify the map after passing it to this function. | ||
| func WithSpecificPorts(ports map[int]tunnels.TunnelPort) mockClientOpt { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The majority of changes in this file relate to this. Previous behaviour was maintained when this is not provided to avoid a lot of test churn. If you provide these ports, they will be used when working with the http server tunnel port endpoints. |
||
| return func(opts *mockClientOpts) { | ||
| opts.ports = ports | ||
| } | ||
| } | ||
|
|
||
| func NewMockHttpClient(opts ...mockClientOpt) (*http.Client, error) { | ||
| mockClientOpts := &mockClientOpts{} | ||
| for _, opt := range opts { | ||
| opt(mockClientOpts) | ||
| } | ||
|
|
||
| specifiedPorts := mockClientOpts.ports | ||
|
|
||
| accessToken := "tunnel access-token" | ||
| relayServer, err := newMockrelayServer(withAccessToken(accessToken)) | ||
| if err != nil { | ||
|
|
@@ -35,7 +57,7 @@ func NewMockHttpClient() (*http.Client, error) { | |
| hostURL := strings.Replace(relayServer.URL(), "http://", "ws://", 1) | ||
| mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| var response []byte | ||
| if r.URL.Path == "/api/v1/tunnels/tunnel-id" { | ||
| if r.URL.Path == "/tunnels/tunnel-id" { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consequence of dropping of |
||
| tunnel := &tunnels.Tunnel{ | ||
| AccessTokens: map[tunnels.TunnelAccessScope]string{ | ||
| tunnels.TunnelAccessScopeConnect: accessToken, | ||
|
|
@@ -54,54 +76,141 @@ func NewMockHttpClient() (*http.Client, error) { | |
| if err != nil { | ||
| log.Fatalf("json.Marshal returned an error: %v", err) | ||
| } | ||
| } else if strings.HasPrefix(r.URL.Path, "/api/v1/tunnels/tunnel-id/ports") { | ||
| // Use regex to check if the path ends with a number | ||
| match, err := regexp.MatchString(`\/\d+$`, r.URL.Path) | ||
| if err != nil { | ||
| log.Fatalf("regexp.MatchString returned an error: %v", err) | ||
| } | ||
|
|
||
| // If the path ends with a number, it's a request for a specific port | ||
| if match || r.Method == http.MethodPost { | ||
| _, _ = w.Write(response) | ||
| return | ||
| } else if strings.HasPrefix(r.URL.Path, "/tunnels/tunnel-id/ports") { | ||
| // Use regex to capture the port number from the end of the path | ||
| re := regexp.MustCompile(`\/(\d+)$`) | ||
| matches := re.FindStringSubmatch(r.URL.Path) | ||
| targetingSpecificPort := len(matches) > 0 | ||
|
|
||
| if targetingSpecificPort { | ||
| if r.Method == http.MethodDelete { | ||
| w.WriteHeader(http.StatusOK) | ||
| return | ||
| } | ||
|
|
||
| tunnelPort := &tunnels.TunnelPort{ | ||
| if r.Method == http.MethodGet { | ||
| // If no ports were configured, then we assume that every request for a port is valid. | ||
| if specifiedPorts == nil { | ||
| response, err := json.Marshal(tunnels.TunnelPort{ | ||
| AccessControl: &tunnels.TunnelAccessControl{ | ||
| Entries: []tunnels.TunnelAccessControlEntry{}, | ||
| }, | ||
| }) | ||
|
|
||
| if err != nil { | ||
| log.Fatalf("json.Marshal returned an error: %v", err) | ||
| } | ||
|
|
||
| _, _ = w.Write(response) | ||
| return | ||
| } else { | ||
| // Otherwise we'll fetch the port from our configured ports and include the protocol in the response. | ||
| port, err := strconv.Atoi(matches[1]) | ||
| if err != nil { | ||
| log.Fatalf("strconv.Atoi returned an error: %v", err) | ||
| } | ||
|
|
||
| tunnelPort, ok := specifiedPorts[port] | ||
| if !ok { | ||
| w.WriteHeader(http.StatusNotFound) | ||
| return | ||
| } | ||
|
|
||
| response, err := json.Marshal(tunnelPort) | ||
|
|
||
| if err != nil { | ||
| log.Fatalf("json.Marshal returned an error: %v", err) | ||
| } | ||
|
|
||
| _, _ = w.Write(response) | ||
| return | ||
| } | ||
| } | ||
|
|
||
| // Else this is an unexpected request, fall through to 404 at the bottom | ||
| } | ||
|
|
||
| // If it's a PUT request, we assume it's for creating a new port so we'll do some validation | ||
| // and then return a stub. | ||
| if r.Method == http.MethodPut { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now I did change this to a PUT. This is now a PUT in https://github.com/microsoft/dev-tunnels/blob/06ba8571935cd11a5384cb7d597fa1b796c59282/go/tunnels/manager.go#L465 But previously was a POST: https://github.com/microsoft/dev-tunnels/blob/97233d20448e1c3cb0e0fd9114acf68c7e5c0249/go/tunnels/manager.go#L370 |
||
| // If a port was already configured with this number, and the protocol has changed, return a 400 Bad Request. | ||
| if specifiedPorts != nil { | ||
| port, err := strconv.Atoi(matches[1]) | ||
| if err != nil { | ||
| log.Fatalf("strconv.Atoi returned an error: %v", err) | ||
| } | ||
|
|
||
| var portRequest tunnels.TunnelPort | ||
| if err := json.NewDecoder(r.Body).Decode(&portRequest); err != nil { | ||
| log.Fatalf("json.NewDecoder returned an error: %v", err) | ||
| } | ||
|
|
||
| tunnelPort, ok := specifiedPorts[port] | ||
| if ok { | ||
| if tunnelPort.Protocol != portRequest.Protocol { | ||
| w.WriteHeader(http.StatusBadRequest) | ||
| return | ||
| } | ||
| } | ||
|
|
||
| // Create or update the new port entry. | ||
| specifiedPorts[port] = portRequest | ||
| } | ||
|
|
||
| response, err := json.Marshal(tunnels.TunnelPort{ | ||
| AccessControl: &tunnels.TunnelAccessControl{ | ||
| Entries: []tunnels.TunnelAccessControlEntry{}, | ||
| }, | ||
| } | ||
| }) | ||
|
|
||
| // Convert the tunnel to JSON and write it to the response | ||
| response, err = json.Marshal(*tunnelPort) | ||
| if err != nil { | ||
| log.Fatalf("json.Marshal returned an error: %v", err) | ||
| } | ||
| } else { | ||
| // If the path doesn't end with a number and we aren't making a POST request, return an array of ports | ||
| tunnelPorts := []tunnels.TunnelPort{ | ||
| { | ||
| AccessControl: &tunnels.TunnelAccessControl{ | ||
| Entries: []tunnels.TunnelAccessControlEntry{}, | ||
|
|
||
| _, _ = w.Write(response) | ||
| return | ||
| } | ||
|
|
||
| // Finally, if it's not targeting a specific port or a POST request, we return a list of ports, either | ||
| // totally stubbed, or whatever was configured in the mock client options. | ||
| if specifiedPorts == nil { | ||
| response, err := json.Marshal(tunnels.TunnelPortListResponse{ | ||
| Value: []tunnels.TunnelPort{ | ||
| { | ||
| AccessControl: &tunnels.TunnelAccessControl{ | ||
| Entries: []tunnels.TunnelAccessControlEntry{}, | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
| if err != nil { | ||
| log.Fatalf("json.Marshal returned an error: %v", err) | ||
| } | ||
|
|
||
| response, err = json.Marshal(tunnelPorts) | ||
| _, _ = w.Write(response) | ||
| return | ||
| } else { | ||
| var ports []tunnels.TunnelPort | ||
| for _, tunnelPort := range specifiedPorts { | ||
| ports = append(ports, tunnelPort) | ||
| } | ||
| response, err := json.Marshal(tunnels.TunnelPortListResponse{ | ||
| Value: ports, | ||
| }) | ||
| if err != nil { | ||
| log.Fatalf("json.Marshal returned an error: %v", err) | ||
| } | ||
| } | ||
|
|
||
| _, _ = w.Write(response) | ||
| return | ||
| } | ||
| } else { | ||
| w.WriteHeader(http.StatusNotFound) | ||
| return | ||
| } | ||
|
|
||
| // Write the response | ||
| _, _ = w.Write(response) | ||
| })) | ||
|
|
||
| url, err := url.Parse(mockServer.URL) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,9 +12,9 @@ import ( | |
| ) | ||
|
|
||
| const ( | ||
| githubSubjectId = "1" | ||
| InternalPortTag = "InternalPort" | ||
| UserForwardedPortTag = "UserForwardedPort" | ||
| githubSubjectId = "1" | ||
| InternalPortLabel = "InternalPort" | ||
| UserForwardedPortLabel = "UserForwardedPort" | ||
| ) | ||
|
|
||
| const ( | ||
|
|
@@ -108,7 +108,26 @@ func (fwd *CodespacesPortForwarder) ForwardPort(ctx context.Context, opts Forwar | |
| return fmt.Errorf("error converting port: %w", err) | ||
| } | ||
|
|
||
| tunnelPort := tunnels.NewTunnelPort(port, "", "", tunnels.TunnelProtocolHttp) | ||
| // In v0.0.25 of dev-tunnels, the dev-tunnel manager `CreateTunnelPort` would "accept" requests that | ||
| // change the port protocol but they would not result in any actual change. This has changed, resulting in | ||
| // an error `Invalid arguments. The tunnel port protocol cannot be changed.`. It's not clear why the previous | ||
| // behaviour existed, whether it was truly the API version, or whether the `If-Not-Match` header being set inside | ||
| // `CreateTunnelPort` avoided the server accepting the request to change the protocol and that has since regressed. | ||
| // | ||
| // In any case, now we check whether a port exists with the given port number, if it does, we use the existing protocol. | ||
| // If it doesn't exist, we default to HTTP, which was the previous behaviour for all ports. | ||
| protocol := tunnels.TunnelProtocolHttp | ||
|
|
||
| existingPort, err := fwd.connection.TunnelManager.GetTunnelPort(ctx, fwd.connection.Tunnel, opts.Port, fwd.connection.Options) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably could do a cached |
||
| if err != nil && !strings.Contains(err.Error(), "404") { | ||
| return fmt.Errorf("error checking whether tunnel port already exists: %v", err) | ||
| } | ||
|
|
||
| if existingPort != nil { | ||
| protocol = tunnels.TunnelProtocol(existingPort.Protocol) | ||
| } | ||
|
|
||
| tunnelPort := tunnels.NewTunnelPort(port, "", "", protocol) | ||
|
|
||
| // If no visibility is provided, Dev Tunnels will use the default (private) | ||
| if opts.Visibility != "" { | ||
|
|
@@ -136,9 +155,9 @@ func (fwd *CodespacesPortForwarder) ForwardPort(ctx context.Context, opts Forwar | |
|
|
||
| // Tag the port as internal or user forwarded so we know if it needs to be shown in the UI | ||
| if opts.Internal { | ||
| tunnelPort.Tags = []string{InternalPortTag} | ||
| tunnelPort.Labels = []string{InternalPortLabel} | ||
| } else { | ||
| tunnelPort.Tags = []string{UserForwardedPortTag} | ||
| tunnelPort.Labels = []string{UserForwardedPortLabel} | ||
| } | ||
|
|
||
| // Create the tunnel port | ||
|
|
@@ -362,8 +381,8 @@ func visibilityToAccessControlEntries(visibility string) []tunnels.TunnelAccessC | |
|
|
||
| // IsInternalPort returns true if the port is internal. | ||
| func IsInternalPort(port *tunnels.TunnelPort) bool { | ||
| for _, tag := range port.Tags { | ||
| if strings.EqualFold(tag, InternalPortTag) { | ||
| for _, label := range port.Labels { | ||
| if strings.EqualFold(label, InternalPortLabel) { | ||
| return true | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consequence of now requiring api version as per description