| 69 | } |
| 70 | |
| 71 | func (w *WebsocketDialer) Dial(ctx context.Context, r tailnet.ResumeTokenController, |
| 72 | ) ( |
| 73 | tailnet.ControlProtocolClients, error, |
| 74 | ) { |
| 75 | w.logger.Debug(ctx, "dialing Coder tailnet v2+ API") |
| 76 | |
| 77 | u := new(url.URL) |
| 78 | *u = *w.url |
| 79 | q := u.Query() |
| 80 | if r != nil && !w.resumeTokenFailed { |
| 81 | if token, ok := r.Token(); ok { |
| 82 | q.Set("resume_token", token) |
| 83 | w.logger.Debug(ctx, "using resume token on dial") |
| 84 | } |
| 85 | } |
| 86 | // The current version includes additions |
| 87 | // |
| 88 | // 2.1 GetAnnouncementBanners on the Agent API (version locked to Tailnet API) |
| 89 | // 2.2 PostTelemetry on the Tailnet API |
| 90 | // 2.3 RefreshResumeToken, WorkspaceUpdates |
| 91 | // |
| 92 | // Resume tokens and telemetry are optional, and fail gracefully. So we use version 2.0 for |
| 93 | // maximum compatibility if we don't need WorkspaceUpdates. If we do, we use 2.3. |
| 94 | if w.workspaceUpdatesReq != nil { |
| 95 | q.Add("version", "2.3") |
| 96 | } else { |
| 97 | q.Add("version", "2.0") |
| 98 | } |
| 99 | u.RawQuery = q.Encode() |
| 100 | |
| 101 | // nolint:bodyclose |
| 102 | ws, res, err := websocket.Dial(ctx, u.String(), w.dialOptions) |
| 103 | if w.isFirst { |
| 104 | if res != nil && slices.Contains(permanentErrorStatuses, res.StatusCode) { |
| 105 | err = codersdk.ReadBodyAsError(res) |
| 106 | var sdkErr *codersdk.Error |
| 107 | if xerrors.As(err, &sdkErr) { |
| 108 | // Check for resume token failure first |
| 109 | if w.checkResumeTokenFailure(ctx, sdkErr) { |
| 110 | return tailnet.ControlProtocolClients{}, err |
| 111 | } |
| 112 | |
| 113 | // A bit more human-readable help in the case the API version was rejected |
| 114 | if sdkErr.Message == AgentAPIMismatchMessage && |
| 115 | sdkErr.StatusCode() == http.StatusBadRequest { |
| 116 | sdkErr.Helper = fmt.Sprintf( |
| 117 | "Ensure your client release version (%s, different than the API version) matches the server release version", |
| 118 | buildinfo.Version()) |
| 119 | } |
| 120 | |
| 121 | if sdkErr.Message == codersdk.DatabaseNotReachable && |
| 122 | sdkErr.StatusCode() == http.StatusInternalServerError { |
| 123 | err = xerrors.Errorf("%w: %v", codersdk.ErrDatabaseNotReachable, err) |
| 124 | } |
| 125 | } |
| 126 | w.connected <- err |
| 127 | return tailnet.ControlProtocolClients{}, err |
| 128 | } |