Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d37eb42e7 | |||
| e7033b34dc | |||
| 01c6266e3e | |||
| f9011dcba2 | |||
| ae1be27ba6 | |||
| c4a01a42ce | |||
| c0aeb2fc2e | |||
| 908d236a19 | |||
| f519db88fb | |||
| e996e8b7e8 | |||
| da60671b33 | |||
| 963a1404c0 | |||
| 002110228c |
Generated
+82
@@ -397,6 +397,66 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/debug/derp/traffic": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Debug"
|
||||
],
|
||||
"summary": "Debug DERP traffic",
|
||||
"operationId": "debug-derp-traffic",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/derp.BytesSentRecv"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-apidocgen": {
|
||||
"skip": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/debug/expvar": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Debug"
|
||||
],
|
||||
"summary": "Debug expvar",
|
||||
"operationId": "debug-expvar",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-apidocgen": {
|
||||
"skip": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/debug/health": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -13019,6 +13079,25 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"derp.BytesSentRecv": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "Key is the public key of the client which sent/received these bytes.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/key.NodePublic"
|
||||
}
|
||||
]
|
||||
},
|
||||
"recv": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sent": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"derp.ServerInfoMessage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13524,6 +13603,9 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"key.NodePublic": {
|
||||
"type": "object"
|
||||
},
|
||||
"netcheck.Report": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Generated
+74
@@ -337,6 +337,58 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/debug/derp/traffic": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Debug"],
|
||||
"summary": "Debug DERP traffic",
|
||||
"operationId": "debug-derp-traffic",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/derp.BytesSentRecv"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-apidocgen": {
|
||||
"skip": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/debug/expvar": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Debug"],
|
||||
"summary": "Debug expvar",
|
||||
"operationId": "debug-expvar",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-apidocgen": {
|
||||
"skip": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/debug/health": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -11852,6 +11904,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"derp.BytesSentRecv": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "Key is the public key of the client which sent/received these bytes.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/key.NodePublic"
|
||||
}
|
||||
]
|
||||
},
|
||||
"recv": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sent": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"derp.ServerInfoMessage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -12321,6 +12392,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"key.NodePublic": {
|
||||
"type": "object"
|
||||
},
|
||||
"netcheck.Report": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
"expvar"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -84,6 +85,8 @@ func init() {
|
||||
globalHTTPSwaggerHandler = httpSwagger.Handler(httpSwagger.URL("/swagger/doc.json"))
|
||||
}
|
||||
|
||||
var expDERPOnce = sync.Once{}
|
||||
|
||||
// Options are requires parameters for Coder to start.
|
||||
type Options struct {
|
||||
AccessURL *url.URL
|
||||
@@ -559,6 +562,16 @@ func New(options *Options) *API {
|
||||
|
||||
derpHandler := derphttp.Handler(api.DERPServer)
|
||||
derpHandler, api.derpCloseFunc = tailnet.WithWebsocketSupport(api.DERPServer, derpHandler)
|
||||
// Register DERP on expvar HTTP handler, which we serve below in the router, c.f. expvar.Handler()
|
||||
// These are the metrics the DERP server exposes.
|
||||
// TODO: export via prometheus
|
||||
expDERPOnce.Do(func() {
|
||||
// We need to do this via a global Once because expvar registry is global and panics if we
|
||||
// register multiple times. In production there is only one Coderd and one DERP server per
|
||||
// process, but in testing, we create multiple of both, so the Once protects us from
|
||||
// panicking.
|
||||
expvar.Publish("derp", api.DERPServer.ExpVar())
|
||||
})
|
||||
cors := httpmw.Cors(options.DeploymentValues.Dangerous.AllowAllCors.Value())
|
||||
prometheusMW := httpmw.Prometheus(options.PrometheusRegistry)
|
||||
|
||||
@@ -1028,6 +1041,10 @@ func New(options *Options) *API {
|
||||
r.Use(httpmw.ExtractUserParam(options.Database))
|
||||
r.Get("/debug-link", api.userDebugOIDC)
|
||||
})
|
||||
r.Route("/derp", func(r chi.Router) {
|
||||
r.Get("/traffic", options.DERPServer.ServeDebugTraffic)
|
||||
})
|
||||
r.Method("GET", "/expvar", expvar.Handler()) // contains DERP metrics as well as cmdline and memstats
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
+21
-1
@@ -276,7 +276,7 @@ func validateHealthSettings(settings codersdk.HealthSettings) error {
|
||||
}
|
||||
|
||||
// For some reason the swagger docs need to be attached to a function.
|
||||
//
|
||||
|
||||
// @Summary Debug Info Websocket Test
|
||||
// @ID debug-info-websocket-test
|
||||
// @Security CoderSessionToken
|
||||
@@ -287,6 +287,26 @@ func validateHealthSettings(settings codersdk.HealthSettings) error {
|
||||
// @x-apidocgen {"skip": true}
|
||||
func _debugws(http.ResponseWriter, *http.Request) {} //nolint:unused
|
||||
|
||||
// @Summary Debug DERP traffic
|
||||
// @ID debug-derp-traffic
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Success 200 {array} derp.BytesSentRecv
|
||||
// @Tags Debug
|
||||
// @Router /debug/derp/traffic [get]
|
||||
// @x-apidocgen {"skip": true}
|
||||
func _debugDERPTraffic(http.ResponseWriter, *http.Request) {} //nolint:unused
|
||||
|
||||
// @Summary Debug expvar
|
||||
// @ID debug-expvar
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Debug
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Router /debug/expvar [get]
|
||||
// @x-apidocgen {"skip": true}
|
||||
func _debugExpVar(http.ResponseWriter, *http.Request) {} //nolint:unused
|
||||
|
||||
func loadDismissedHealthchecks(ctx context.Context, db database.Store, logger slog.Logger) []codersdk.HealthSection {
|
||||
dismissedHealthchecks := []codersdk.HealthSection{}
|
||||
settingsJSON, err := db.GetHealthSettings(ctx)
|
||||
|
||||
+17
-22
@@ -136,28 +136,8 @@ func NewServerTailnet(
|
||||
return nil, xerrors.Errorf("get initial multi agent: %w", err)
|
||||
}
|
||||
tn.agentConn.Store(&agentConn)
|
||||
|
||||
pn, err := tailnet.NodeToProto(conn.Node())
|
||||
if err != nil {
|
||||
tn.logger.Critical(context.Background(), "failed to convert node", slog.Error(err))
|
||||
} else {
|
||||
err = tn.getAgentConn().UpdateSelf(pn)
|
||||
if err != nil {
|
||||
tn.logger.Warn(context.Background(), "server tailnet update self", slog.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
conn.SetNodeCallback(func(node *tailnet.Node) {
|
||||
pn, err := tailnet.NodeToProto(node)
|
||||
if err != nil {
|
||||
tn.logger.Critical(context.Background(), "failed to convert node", slog.Error(err))
|
||||
return
|
||||
}
|
||||
err = tn.getAgentConn().UpdateSelf(pn)
|
||||
if err != nil {
|
||||
tn.logger.Warn(context.Background(), "broadcast server node to agents", slog.Error(err))
|
||||
}
|
||||
})
|
||||
// registering the callback also triggers send of the initial node
|
||||
tn.coordinatee.SetNodeCallback(tn.nodeCallback)
|
||||
|
||||
// This is set to allow local DERP traffic to be proxied through memory
|
||||
// instead of needing to hit the external access URL. Don't use the ctx
|
||||
@@ -183,6 +163,18 @@ func NewServerTailnet(
|
||||
return tn, nil
|
||||
}
|
||||
|
||||
func (s *ServerTailnet) nodeCallback(node *tailnet.Node) {
|
||||
pn, err := tailnet.NodeToProto(node)
|
||||
if err != nil {
|
||||
s.logger.Critical(context.Background(), "failed to convert node", slog.Error(err))
|
||||
return
|
||||
}
|
||||
err = s.getAgentConn().UpdateSelf(pn)
|
||||
if err != nil {
|
||||
s.logger.Warn(context.Background(), "broadcast server node to agents", slog.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerTailnet) Describe(descs chan<- *prometheus.Desc) {
|
||||
s.connsPerAgent.Describe(descs)
|
||||
s.totalConns.Describe(descs)
|
||||
@@ -285,6 +277,9 @@ func (s *ServerTailnet) reinitCoordinator() {
|
||||
continue
|
||||
}
|
||||
s.agentConn.Store(&agentConn)
|
||||
// reset the Node callback, which triggers the conn to send the node immediately, and also
|
||||
// register for updates
|
||||
s.coordinatee.SetNodeCallback(s.nodeCallback)
|
||||
|
||||
// Resubscribe to all of the agents we're tracking.
|
||||
for agentID := range s.agentConnectionTimes {
|
||||
|
||||
@@ -48,6 +48,7 @@ func TestServerTailnet_Reconnect(t *testing.T) {
|
||||
agentConnectionTimes: make(map[uuid.UUID]time.Time),
|
||||
}
|
||||
// reinit the Coordinator once, to load mMultiAgent0
|
||||
mCoord.EXPECT().SetNodeCallback(gomock.Any()).Times(1)
|
||||
uut.reinitCoordinator()
|
||||
|
||||
mMultiAgent0.EXPECT().NextUpdate(gomock.Any()).
|
||||
@@ -57,6 +58,7 @@ func TestServerTailnet_Reconnect(t *testing.T) {
|
||||
Times(1).
|
||||
Return(true) // this triggers reconnect
|
||||
setLost := mCoord.EXPECT().SetAllPeersLost().Times(1).After(closed0)
|
||||
mCoord.EXPECT().SetNodeCallback(gomock.Any()).Times(1).After(closed0)
|
||||
mMultiAgent1.EXPECT().NextUpdate(gomock.Any()).
|
||||
Times(1).
|
||||
After(setLost).
|
||||
|
||||
+10
-2
@@ -928,15 +928,23 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if len(api.OIDCConfig.EmailDomain) > 0 {
|
||||
ok = false
|
||||
emailSp := strings.Split(email, "@")
|
||||
if len(emailSp) == 1 {
|
||||
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
|
||||
Message: fmt.Sprintf("Your email %q is not in domains %q!", email, api.OIDCConfig.EmailDomain),
|
||||
})
|
||||
return
|
||||
}
|
||||
userEmailDomain := emailSp[len(emailSp)-1]
|
||||
for _, domain := range api.OIDCConfig.EmailDomain {
|
||||
if strings.HasSuffix(strings.ToLower(email), strings.ToLower(domain)) {
|
||||
if strings.EqualFold(userEmailDomain, domain) {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
|
||||
Message: fmt.Sprintf("Your email %q is not in domains %q !", email, api.OIDCConfig.EmailDomain),
|
||||
Message: fmt.Sprintf("Your email %q is not in domains %q!", email, api.OIDCConfig.EmailDomain),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -757,6 +757,17 @@ func TestUserOIDC(t *testing.T) {
|
||||
"kwc.io",
|
||||
},
|
||||
StatusCode: http.StatusOK,
|
||||
}, {
|
||||
Name: "EmailDomainSubset",
|
||||
IDTokenClaims: jwt.MapClaims{
|
||||
"email": "colin@gmail.com",
|
||||
"email_verified": true,
|
||||
},
|
||||
AllowSignups: true,
|
||||
EmailDomain: []string{
|
||||
"mail.com",
|
||||
},
|
||||
StatusCode: http.StatusForbidden,
|
||||
}, {
|
||||
Name: "EmptyClaims",
|
||||
IDTokenClaims: jwt.MapClaims{},
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package coderd_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbfake"
|
||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
func TestAgentAPI_LargeManifest(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
client, store := coderdtest.NewWithDatabase(t, nil)
|
||||
adminUser := coderdtest.CreateFirstUser(t, client)
|
||||
n := 512000
|
||||
longScript := make([]byte, n)
|
||||
for i := range longScript {
|
||||
longScript[i] = 'q'
|
||||
}
|
||||
r := dbfake.WorkspaceBuild(t, store, database.Workspace{
|
||||
OrganizationID: adminUser.OrganizationID,
|
||||
OwnerID: adminUser.UserID,
|
||||
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
|
||||
agents[0].Scripts = []*proto.Script{
|
||||
{
|
||||
Script: string(longScript),
|
||||
},
|
||||
}
|
||||
return agents
|
||||
}).Do()
|
||||
ac := agentsdk.New(client.URL)
|
||||
ac.SetSessionToken(r.AgentToken)
|
||||
conn, err := ac.ConnectRPC(ctx)
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
}()
|
||||
require.NoError(t, err)
|
||||
agentAPI := agentproto.NewDRPCAgentClient(conn)
|
||||
manifest, err := agentAPI.GetManifest(ctx, &agentproto.GetManifestRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, manifest.Scripts, 1)
|
||||
require.Len(t, manifest.Scripts[0].Script, n)
|
||||
}
|
||||
@@ -688,6 +688,9 @@ func (c *wsNetConn) Close() error {
|
||||
// during read or write will cancel the context, but not close the
|
||||
// conn. Close should be called to release context resources.
|
||||
func websocketNetConn(ctx context.Context, conn *websocket.Conn, msgType websocket.MessageType) (context.Context, net.Conn) {
|
||||
// Set the read limit to 4 MiB -- about the limit for protobufs. This needs to be larger than
|
||||
// the default because some of our protocols can include large messages like startup scripts.
|
||||
conn.SetReadLimit(1 << 22)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
nc := websocket.NetConn(ctx, conn, msgType)
|
||||
return ctx, &wsNetConn{
|
||||
|
||||
@@ -286,8 +286,11 @@ func ProtoFromAppHealthsRequest(req PostAppHealthsRequest) (*proto.BatchUpdateAp
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("unknown app health: %s", h)
|
||||
}
|
||||
|
||||
var idCopy uuid.UUID
|
||||
copy(idCopy[:], id[:])
|
||||
pReq.Updates = append(pReq.Updates, &proto.BatchUpdateAppHealthRequest_HealthUpdate{
|
||||
Id: id[:],
|
||||
Id: idCopy[:],
|
||||
Health: proto.AppHealth(hp),
|
||||
})
|
||||
}
|
||||
|
||||
Generated
+28
@@ -7419,6 +7419,24 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
| `count` | integer | false | | |
|
||||
| `workspaces` | array of [codersdk.Workspace](#codersdkworkspace) | false | | |
|
||||
|
||||
## derp.BytesSentRecv
|
||||
|
||||
```json
|
||||
{
|
||||
"key": {},
|
||||
"recv": 0,
|
||||
"sent": 0
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------ | -------------------------------- | -------- | ------------ | -------------------------------------------------------------------- |
|
||||
| `key` | [key.NodePublic](#keynodepublic) | false | | Key is the public key of the client which sent/received these bytes. |
|
||||
| `recv` | integer | false | | |
|
||||
| `sent` | integer | false | | |
|
||||
|
||||
## derp.ServerInfoMessage
|
||||
|
||||
```json
|
||||
@@ -8532,6 +8550,16 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
| `warnings` | array of [health.Message](#healthmessage) | false | | |
|
||||
| `workspace_proxies` | [codersdk.RegionsResponse-codersdk_WorkspaceProxy](#codersdkregionsresponse-codersdk_workspaceproxy) | false | | |
|
||||
|
||||
## key.NodePublic
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
_None_
|
||||
|
||||
## netcheck.Report
|
||||
|
||||
```json
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -0,0 +1,107 @@
|
||||
## Changelog
|
||||
|
||||
### Features
|
||||
|
||||
- Coder will now autofill parameters in the "create workspace" page based on the previous value (#11731) (@ammario)
|
||||

|
||||
- Change default Terraform version to v1.6 and relax max version constraint (#12027) (@johnstcn)
|
||||
- Show workspace name suggestions below the name field (#12001) (@aslilac)
|
||||
- Add favorite/unfavorite commands (@johnstcn)
|
||||
- Add .zip support for Coder templates (#11839) (#12032) (@mtojek)
|
||||
- Add support for `--wait` and scripts that block login (#10473) (@mafredri)
|
||||
- Users can mark workspaces as favorite to show up at the top of their list (#11875) (#11793) (#11878) (#11791) (@johnstcn)
|
||||

|
||||
- Add Prometheus metrics to servertailnet (#11988) (@coadler)
|
||||
- Add support for concurrent scenarios (#11753) (@mafredri)
|
||||
- Display user avatar on workspace page (#11893) (@BrunoQuaresma)
|
||||
- Do not show popover on update deadline (#11921) (@BrunoQuaresma)
|
||||
- Simplify "Create Template" form by removing advanced settings (#11918) (@BrunoQuaresma)
|
||||
- Show deprecation message on template page (#11996) (@BrunoQuaresma)
|
||||
- Check agent API version on connection (#11696)
|
||||
- Add "updated" search param to workspaces (#11714)
|
||||
- Add option to speedtest to dump a pcap of network traffic (#11848) (@coadler)
|
||||
- Add logging to client tailnet yamux (#11908) (@spikecurtis)
|
||||
- Add customizable upgrade message on client/server version mismatch (#11587) (@sreya)
|
||||
- Add logging to pgcoord subscribe/unsubscribe (#11952) (@spikecurtis)
|
||||
- Add logging to pgPubsub (#11953) (@spikecurtis)
|
||||
- Add logging to agent yamux session (#11912) (@spikecurtis)
|
||||
- Move agent v2 API connection monitoring to yamux layer (#11910) (@spikecurtis)
|
||||
- Add statsReporter for reporting stats on agent v2 API (#11920) (@spikecurtis)
|
||||
- Add metrics to PGPubsub (#11971) (@spikecurtis)
|
||||
- Add custom error message on signups disabled page (#11959) (@mtojek)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Make ServerTailnet set peers lost when it reconnects to the coordinator (#11682)
|
||||
- Properly auto-update workspace on SSH if template policy is set (#11773)
|
||||
- Allow template name length of 32 in template push and create (#11915) (@mafredri)
|
||||
- Alter return signature of convertWorkspace, add check for requesterID (#11796) (@johnstcn)
|
||||
- Fix limit in `GetUserWorkspaceBuildParameters` (#11954) (@mafredri)
|
||||
- Fix test flake in TestHeartbeat (#11808) (@johnstcn)
|
||||
- Do not cache context cancellation errors (#11840) (@johnstcn)
|
||||
- Fix startup script on workspace creation (#11958) (@matifali)
|
||||
- Fix startup script looping (#11972) (@code-asher)
|
||||
- Add ID to default columns in licenses list output (#11823) (@johnstcn)
|
||||
- Handle query canceled error in sendBeat() (#11794) (@johnstcn)
|
||||
- Fix proxy settings link (#11817) (@BrunoQuaresma)
|
||||
- Disable autostart and autostop according to template settings (#11809) (@Kira-Pilot)
|
||||
- Fix capitalized username (#11891) (@BrunoQuaresma)
|
||||
- Fix parameters' request upon template variables update (#11898) (@BrunoQuaresma)
|
||||
- Fix text overflow on batch ws deletion (#11981) (@BrunoQuaresma)
|
||||
- Fix parameter input icon shrink (#11995) (@BrunoQuaresma)
|
||||
- Use TSMP ping for reachability, not latency (#11749)
|
||||
- Display error when fetching OAuth2 provider apps (#11713)
|
||||
- Fix code-server path based forwarding, defer to code-server (#11759)
|
||||
- Use correct logger for lifecycle_executor (#11763)
|
||||
- Disable keepalives in workspaceapps transport (#11789) (@coadler)
|
||||
- Accept agent RPC connection without version query parameter (#11790) (@spikecurtis)
|
||||
- Stop spamming DERP map updates for equivalent maps (#11792) (@spikecurtis)
|
||||
- Check update permission to start workspace (#11798) (@mtojek)
|
||||
- Always attempt external auth refresh when fetching (#11762) (@Emyrk)
|
||||
- Stop running tests that exec sh scripts in parallel (#11834) (@spikecurtis)
|
||||
- Fix type error from theme update (#11844) (@aslilac)
|
||||
- Use new context after t.Parallel in TestOAuthAppSecrets (@spikecurtis)
|
||||
- Always attempt external auth refresh when fetching (#11762) (#11830) (@Emyrk)
|
||||
- Wait for new template version before promoting (#11874) (@spikecurtis)
|
||||
- Respect wait flag on ping (#11896) (@f0ssel)
|
||||
- Fix cliui prompt styling (#11899) (@aslilac)
|
||||
- Fix prevent agent_test.go from failing on error logs (#11909) (@spikecurtis)
|
||||
- Add timeout to listening ports request (#11935) (@coadler)
|
||||
- Only delete expired agents on success (#11940) (@coadler)
|
||||
- Close MultiAgentConn when coordinator closes (#11941) (@spikecurtis)
|
||||
- Strip timezone information from a date in dau response (#11962) (@Emyrk)
|
||||
- Improve click UX and styling for Auth Token page (#11863) (@Parkreiner)
|
||||
- Rewrite url to agent ip in single tailnet (#11810) (@coadler)
|
||||
- Avoid race in TestPGPubsub_Metrics by using Eventually (#11973) (@spikecurtis)
|
||||
- Always return a clean http client for promoauth (#11963) (@Emyrk)
|
||||
- Change build status colors (#11985) (@aslilac)
|
||||
- Only display xray results if vulns > 0 (#11989) (@sreya)
|
||||
- Use dark background in terminal, even when a light theme is selected (#12004) (@aslilac)
|
||||
- Fix graceful disconnect in DialWorkspaceAgent (#11993) (@spikecurtis)
|
||||
- Stop logging error on query canceled (#12017) (@spikecurtis)
|
||||
- Only display quota if it is higher than 0 (#11979) (@BrunoQuaresma)
|
||||
|
||||
### Documentation
|
||||
|
||||
- Using coder modules in offline deployments (#11788) (@matifali)
|
||||
- Simplify JFrog integration docs (#11787) (@matifali)
|
||||
- Add guide for azure federation (#11864) (@ericpaulsen)
|
||||
- Fix example template README 404s and semantics (#11903) (@ericpaulsen)
|
||||
- Update remote docker host docs (#11919) (@matifali)
|
||||
- Add FAQ for gateway reconnects (#12007) (@ericpaulsen)
|
||||
|
||||
### Code refactoring
|
||||
|
||||
- Apply cosmetic changes and remove ExternalAuth from settings page (#11756)
|
||||
- Minor improvements create workspace form (#11771)
|
||||
- Verify external auth before displaying workspace form (#11777) (@BrunoQuaresma)
|
||||
|
||||
Compare: [`v2.7.2...v2.7.3`](https://github.com/coder/coder/compare/v2.7.2...v2.7.3)
|
||||
|
||||
## Container image
|
||||
|
||||
- `docker pull ghcr.io/coder/coder:v2.7.3`
|
||||
|
||||
## Install/upgrade
|
||||
|
||||
Refer to our docs to [install](https://coder.com/docs/v2/latest/install) or [upgrade](https://coder.com/docs/v2/latest/admin/upgrade) Coder, or use a release asset below.
|
||||
@@ -0,0 +1,15 @@
|
||||
## Changelog
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fixed an issue where workspace apps may never be marked as healthy.
|
||||
|
||||
Compare: [`v2.8.1...v2.8.2`](https://github.com/coder/coder/compare/v2.8.1...v2.8.2)
|
||||
|
||||
## Container image
|
||||
|
||||
- `docker pull ghcr.io/coder/coder:v2.8.2`
|
||||
|
||||
## Install/upgrade
|
||||
|
||||
Refer to our docs to [install](https://coder.com/docs/v2/latest/install) or [upgrade](https://coder.com/docs/v2/latest/admin/upgrade) Coder, or use a release asset below.
|
||||
@@ -0,0 +1,15 @@
|
||||
## Changelog
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fixed an issue where some workspace apps were inaccessible.
|
||||
|
||||
Compare: [`v2.8.2...v2.8.3`](https://github.com/coder/coder/compare/v2.8.1...v2.8.2)
|
||||
|
||||
## Container image
|
||||
|
||||
- `docker pull ghcr.io/coder/coder:v2.8.3`
|
||||
|
||||
## Install/upgrade
|
||||
|
||||
Refer to our docs to [install](https://coder.com/docs/v2/latest/install) or [upgrade](https://coder.com/docs/v2/latest/admin/upgrade) Coder, or use a release asset below.
|
||||
@@ -0,0 +1,20 @@
|
||||
## Changelog
|
||||
|
||||
All users are recommended to upgrade to a version that patches
|
||||
[GHSA-7cc2-r658-7xpf](https://github.com/coder/coder/security/advisories/GHSA-7cc2-r658-7xpf)
|
||||
as soon as possible if they are using OIDC authentication with the
|
||||
`CODER_OIDC_EMAIL_DOMAIN` setting.
|
||||
|
||||
### Security
|
||||
|
||||
- Fixes [GHSA-7cc2-r658-7xpf](https://github.com/coder/coder/security/advisories/GHSA-7cc2-r658-7xpf)
|
||||
|
||||
Compare: [`v2.8.3...v2.8.4`](https://github.com/coder/coder/compare/v2.8.3...v2.8.4)
|
||||
|
||||
## Container image
|
||||
|
||||
- `docker pull ghcr.io/coder/coder:v2.8.4`
|
||||
|
||||
## Install/upgrade
|
||||
|
||||
Refer to our docs to [install](https://coder.com/docs/v2/latest/install) or [upgrade](https://coder.com/docs/v2/latest/admin/upgrade) Coder, or use a release asset below.
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { PropsWithChildren, FC } from "react";
|
||||
import {
|
||||
type PropsWithChildren,
|
||||
type FC,
|
||||
forwardRef,
|
||||
HTMLAttributes,
|
||||
} from "react";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { type Interpolation, type Theme } from "@emotion/react";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
@@ -74,9 +79,14 @@ export const NotReachableBadge: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const DisabledBadge: FC = () => {
|
||||
export const DisabledBadge: FC = forwardRef<
|
||||
HTMLSpanElement,
|
||||
HTMLAttributes<HTMLSpanElement>
|
||||
>((props, ref) => {
|
||||
return (
|
||||
<span
|
||||
{...props}
|
||||
ref={ref}
|
||||
css={[
|
||||
styles.badge,
|
||||
(theme) => ({
|
||||
@@ -89,7 +99,7 @@ export const DisabledBadge: FC = () => {
|
||||
Disabled
|
||||
</span>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export const EnterpriseBadge: FC = () => {
|
||||
return (
|
||||
|
||||
+10
@@ -105,3 +105,13 @@ export const Loading: Story = {
|
||||
isLoading: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const AutoStopAndStartOff: Story = {
|
||||
args: {
|
||||
initialValues: {
|
||||
...defaultInitialValues,
|
||||
autostartEnabled: false,
|
||||
autostopEnabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
+34
@@ -379,3 +379,37 @@ describe("templateInheritance", () => {
|
||||
expect(ttlInput).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
test("form should be enabled when both auto stop and auto start features are disabled, given that the template permits these actions", async () => {
|
||||
jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate);
|
||||
render(
|
||||
<WorkspaceScheduleForm
|
||||
{...defaultFormProps}
|
||||
initialValues={{
|
||||
...defaultFormProps.initialValues,
|
||||
autostopEnabled: false,
|
||||
autostartEnabled: false,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const submitButton = await screen.findByRole("button", { name: "Submit" });
|
||||
expect(submitButton).toBeEnabled();
|
||||
});
|
||||
|
||||
test("form should be disabled when both auto stop and auto start features are disabled at template level", async () => {
|
||||
jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate);
|
||||
render(
|
||||
<WorkspaceScheduleForm
|
||||
{...defaultFormProps}
|
||||
allowTemplateAutoStart={false}
|
||||
allowTemplateAutoStop={false}
|
||||
initialValues={{
|
||||
...defaultFormProps.initialValues,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const submitButton = await screen.findByRole("button", { name: "Submit" });
|
||||
expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
+3
-4
@@ -441,10 +441,9 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
|
||||
<FormFooter
|
||||
onCancel={onCancel}
|
||||
isLoading={isLoading}
|
||||
submitDisabled={
|
||||
(!allowTemplateAutoStart && !allowTemplateAutoStop) ||
|
||||
(!form.values.autostartEnabled && !form.values.autostopEnabled)
|
||||
}
|
||||
// If both options, autostart and autostop, are disabled at the template
|
||||
// level, the form is disabled.
|
||||
submitDisabled={!allowTemplateAutoStart && !allowTemplateAutoStop}
|
||||
/>
|
||||
</HorizontalForm>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user