diff --git a/cli/login_test.go b/cli/login_test.go index 8150dc5d94..1fb6576c3e 100644 --- a/cli/login_test.go +++ b/cli/login_test.go @@ -16,6 +16,7 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/pty/ptytest" ) @@ -58,7 +59,7 @@ func TestLogin(t *testing.T) { t.Parallel() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-Coder-Build-Version", "something") + w.Header().Set(codersdk.BuildVersionHeader, "something") w.WriteHeader(http.StatusNotFound) w.Write([]byte("Not Found")) })) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index ee1277d3bc..2fd38b9bb2 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -10052,6 +10052,9 @@ const docTemplate = `{ "codersdk.ProvisionerDaemon": { "type": "object", "properties": { + "api_version": { + "type": "string" + }, "created_at": { "type": "string", "format": "date-time" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 83689a31c6..ff124c7ad7 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -9036,6 +9036,9 @@ "codersdk.ProvisionerDaemon": { "type": "object", "properties": { + "api_version": { + "type": "string" + }, "created_at": { "type": "string", "format": "date-time" diff --git a/coderd/coderd.go b/coderd/coderd.go index ae861d5687..4dc62e6761 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -564,7 +564,7 @@ func New(options *Options) *API { // Build-Version is helpful for debugging. func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("X-Coder-Build-Version", buildinfo.Version()) + w.Header().Add(codersdk.BuildVersionHeader, buildinfo.Version()) next.ServeHTTP(w, r) }) }, @@ -1194,7 +1194,7 @@ func (api *API) CreateInMemoryProvisionerDaemon(ctx context.Context, name string Tags: provisionersdk.MutateTags(uuid.Nil, nil), LastSeenAt: sql.NullTime{Time: dbtime.Now(), Valid: true}, Version: buildinfo.Version(), - APIVersion: "1.0", + APIVersion: provisionersdk.APIVersionCurrent, }) if err != nil { return nil, xerrors.Errorf("failed to create in-memory provisioner daemon: %w", err) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index e9fdd47987..0ff444ca4a 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -7279,6 +7279,7 @@ func (q *FakeQuerier) UpsertProvisionerDaemon(_ context.Context, arg database.Up ReplicaID: uuid.NullUUID{}, LastSeenAt: arg.LastSeenAt, Version: arg.Version, + APIVersion: arg.APIVersion, } q.provisionerDaemons = append(q.provisionerDaemons, d) return d, nil diff --git a/coderd/database/dbpurge/dbpurge_test.go b/coderd/database/dbpurge/dbpurge_test.go index 59000e4638..8fe9953d6a 100644 --- a/coderd/database/dbpurge/dbpurge_test.go +++ b/coderd/database/dbpurge/dbpurge_test.go @@ -218,7 +218,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) { CreatedAt: now.Add(-14 * 24 * time.Hour), LastSeenAt: sql.NullTime{Valid: true, Time: now.Add(-7 * 24 * time.Hour).Add(time.Minute)}, Version: "1.0.0", - APIVersion: "1.0", + APIVersion: provisionersdk.APIVersionCurrent, }) require.NoError(t, err) _, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{ @@ -229,7 +229,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) { CreatedAt: now.Add(-8 * 24 * time.Hour), LastSeenAt: sql.NullTime{Valid: true, Time: now.Add(-8 * 24 * time.Hour).Add(time.Hour)}, Version: "1.0.0", - APIVersion: "1.0", + APIVersion: provisionersdk.APIVersionCurrent, }) require.NoError(t, err) _, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{ @@ -242,7 +242,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) { }, CreatedAt: now.Add(-9 * 24 * time.Hour), Version: "1.0.0", - APIVersion: "1.0", + APIVersion: provisionersdk.APIVersionCurrent, }) require.NoError(t, err) _, err = db.UpsertProvisionerDaemon(ctx, database.UpsertProvisionerDaemonParams{ @@ -256,7 +256,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) { CreatedAt: now.Add(-6 * 24 * time.Hour), LastSeenAt: sql.NullTime{Valid: true, Time: now.Add(-6 * 24 * time.Hour)}, Version: "1.0.0", - APIVersion: "1.0", + APIVersion: provisionersdk.APIVersionCurrent, }) require.NoError(t, err) diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index c2e8c6a836..d89ade60b6 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -24,6 +24,7 @@ import ( "golang.org/x/oauth2" "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/v2/buildinfo" "github.com/coder/coder/v2/cli/clibase" "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/database" @@ -1784,8 +1785,8 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi Provisioners: []database.ProvisionerType{database.ProvisionerTypeEcho}, Tags: database.StringMap{}, LastSeenAt: sql.NullTime{}, - Version: "", - APIVersion: "1.0", + Version: buildinfo.Version(), + APIVersion: provisionersdk.APIVersionCurrent, }) require.NoError(t, err) diff --git a/codersdk/client.go b/codersdk/client.go index b95af72ab3..05ca69fba6 100644 --- a/codersdk/client.go +++ b/codersdk/client.go @@ -78,6 +78,9 @@ const ( // ProvisionerDaemonPSK contains the authentication pre-shared key for an external provisioner daemon ProvisionerDaemonPSK = "Coder-Provisioner-Daemon-PSK" + + // BuildVersionHeader contains build information of Coder. + BuildVersionHeader = "X-Coder-Build-Version" ) // loggableMimeTypes is a list of MIME types that are safe to log diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index 6894f60d4d..5457ba6991 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -15,6 +15,7 @@ import ( "golang.org/x/xerrors" "nhooyr.io/websocket" + "github.com/coder/coder/v2/buildinfo" "github.com/coder/coder/v2/codersdk/drpc" "github.com/coder/coder/v2/provisionerd/proto" "github.com/coder/coder/v2/provisionerd/runner" @@ -41,6 +42,7 @@ type ProvisionerDaemon struct { LastSeenAt NullTime `json:"last_seen_at,omitempty" format:"date-time"` Name string `json:"name"` Version string `json:"version"` + APIVersion string `json:"api_version"` Provisioners []ProvisionerType `json:"provisioners"` Tags map[string]string `json:"tags"` } @@ -212,6 +214,7 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione } headers := http.Header{} + headers.Set(BuildVersionHeader, buildinfo.Version()) if req.PreSharedKey == "" { // use session token if we don't have a PSK. jar, err := cookiejar.New(nil) diff --git a/codersdk/users.go b/codersdk/users.go index fa3aed72b1..7b6492c811 100644 --- a/codersdk/users.go +++ b/codersdk/users.go @@ -203,7 +203,7 @@ func (c *Client) HasFirstUser(ctx context.Context) (bool, error) { if res.StatusCode == http.StatusNotFound { // ensure we are talking to coder and not // some other service that returns 404 - v := res.Header.Get("X-Coder-Build-Version") + v := res.Header.Get(BuildVersionHeader) if v == "" { return false, xerrors.Errorf("missing build version header, not a coder instance") } diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index a567cb0ba8..d3729c22d7 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -1051,6 +1051,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi ```json [ { + "api_version": "string", "created_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", @@ -1078,6 +1079,7 @@ Status Code **200** | Name | Type | Required | Restrictions | Description | | ------------------- | ----------------- | -------- | ------------ | ----------- | | `[array item]` | array | false | | | +| `» api_version` | string | false | | | | `» created_at` | string(date-time) | false | | | | `» id` | string(uuid) | false | | | | `» last_seen_at` | string(date-time) | false | | | diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 407bb4e34e..6d0eae941d 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -3902,6 +3902,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { + "api_version": "string", "created_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "last_seen_at": "2019-08-24T14:15:22Z", @@ -3919,6 +3920,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | Name | Type | Required | Restrictions | Description | | ------------------ | --------------- | -------- | ------------ | ----------- | +| `api_version` | string | false | | | | `created_at` | string | false | | | | `id` | string | false | | | | `last_seen_at` | string | false | | | diff --git a/enterprise/cli/provisionerdaemons_test.go b/enterprise/cli/provisionerdaemons_test.go index 2b4d0ab117..3c0d377214 100644 --- a/enterprise/cli/provisionerdaemons_test.go +++ b/enterprise/cli/provisionerdaemons_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/coder/coder/v2/buildinfo" "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/rbac" @@ -49,6 +50,8 @@ func TestProvisionerDaemon_PSK(t *testing.T) { }, testutil.WaitShort, testutil.IntervalSlow) require.Equal(t, "matt-daemon", daemons[0].Name) require.Equal(t, provisionersdk.ScopeOrganization, daemons[0].Tags[provisionersdk.TagScope]) + require.Equal(t, buildinfo.Version(), daemons[0].Version) + require.Equal(t, provisionersdk.APIVersionCurrent, daemons[0].APIVersion) } func TestProvisionerDaemon_SessionToken(t *testing.T) { @@ -84,6 +87,8 @@ func TestProvisionerDaemon_SessionToken(t *testing.T) { assert.Equal(t, "my-daemon", daemons[0].Name) assert.Equal(t, provisionersdk.ScopeUser, daemons[0].Tags[provisionersdk.TagScope]) assert.Equal(t, anotherUser.ID.String(), daemons[0].Tags[provisionersdk.TagOwner]) + assert.Equal(t, buildinfo.Version(), daemons[0].Version) + assert.Equal(t, provisionersdk.APIVersionCurrent, daemons[0].APIVersion) }) t.Run("ScopeAnotherUser", func(t *testing.T) { @@ -118,6 +123,8 @@ func TestProvisionerDaemon_SessionToken(t *testing.T) { assert.Equal(t, provisionersdk.ScopeUser, daemons[0].Tags[provisionersdk.TagScope]) // This should get clobbered to the user who started the daemon. assert.Equal(t, anotherUser.ID.String(), daemons[0].Tags[provisionersdk.TagOwner]) + assert.Equal(t, buildinfo.Version(), daemons[0].Version) + assert.Equal(t, provisionersdk.APIVersionCurrent, daemons[0].APIVersion) }) t.Run("ScopeOrg", func(t *testing.T) { @@ -150,5 +157,7 @@ func TestProvisionerDaemon_SessionToken(t *testing.T) { }, testutil.WaitShort, testutil.IntervalSlow) assert.Equal(t, "org-daemon", daemons[0].Name) assert.Equal(t, provisionersdk.ScopeOrganization, daemons[0].Tags[provisionersdk.TagScope]) + assert.Equal(t, buildinfo.Version(), daemons[0].Version) + assert.Equal(t, provisionersdk.APIVersionCurrent, daemons[0].APIVersion) }) } diff --git a/enterprise/cli/server_dbcrypt_test.go b/enterprise/cli/server_dbcrypt_test.go index 8b1bbffa52..e9e88c49d2 100644 --- a/enterprise/cli/server_dbcrypt_test.go +++ b/enterprise/cli/server_dbcrypt_test.go @@ -15,9 +15,9 @@ import ( "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/database/postgres" - "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/enterprise/dbcrypt" "github.com/coder/coder/v2/pty/ptytest" + "github.com/coder/coder/v2/testutil" ) // TestServerDBCrypt tests end-to-end encryption, decryption, and deletion @@ -50,7 +50,7 @@ func TestServerDBCrypt(t *testing.T) { users := genData(t, db) // Setup an initial cipher A - keyA := mustString(t, 32) + keyA := testutil.MustRandString(t, 32) cipherA, err := dbcrypt.NewCiphers([]byte(keyA)) require.NoError(t, err) @@ -87,7 +87,7 @@ func TestServerDBCrypt(t *testing.T) { } // Re-encrypt all existing data with a new cipher. - keyB := mustString(t, 32) + keyB := testutil.MustRandString(t, 32) cipherBA, err := dbcrypt.NewCiphers([]byte(keyB), []byte(keyA)) require.NoError(t, err) @@ -160,7 +160,7 @@ func TestServerDBCrypt(t *testing.T) { } // Re-encrypt all existing data with a new cipher. - keyC := mustString(t, 32) + keyC := testutil.MustRandString(t, 32) cipherC, err := dbcrypt.NewCiphers([]byte(keyC)) require.NoError(t, err) @@ -222,7 +222,7 @@ func genData(t *testing.T, db database.Store) []database.User { for _, status := range database.AllUserStatusValues() { for _, loginType := range database.AllLoginTypeValues() { for _, deleted := range []bool{false, true} { - randName := mustString(t, 32) + randName := testutil.MustRandString(t, 32) usr := dbgen.User(t, db, database.User{ Username: randName, Email: randName + "@notcoder.com", @@ -252,13 +252,6 @@ func genData(t *testing.T, db database.Store) []database.User { return users } -func mustString(t *testing.T, n int) string { - t.Helper() - s, err := cryptorand.String(n) - require.NoError(t, err) - return s -} - func requireEncryptedEquals(t *testing.T, c dbcrypt.Cipher, expected, actual string) { t.Helper() var decodedVal []byte diff --git a/enterprise/coderd/provisionerdaemons.go b/enterprise/coderd/provisionerdaemons.go index 874c8cb501..ffd3af57ac 100644 --- a/enterprise/coderd/provisionerdaemons.go +++ b/enterprise/coderd/provisionerdaemons.go @@ -233,6 +233,8 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) authCtx = dbauthz.AsSystemRestricted(ctx) } + versionHdrVal := r.Header.Get(codersdk.BuildVersionHeader) + // Create the daemon in the database. now := dbtime.Now() daemon, err := api.Database.UpsertProvisionerDaemon(authCtx, database.UpsertProvisionerDaemonParams{ @@ -241,8 +243,8 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) Tags: tags, CreatedAt: now, LastSeenAt: sql.NullTime{Time: now, Valid: true}, - Version: "", // TODO: provisionerd needs to send version - APIVersion: "1.0", + Version: versionHdrVal, + APIVersion: provisionersdk.APIVersionCurrent, }) if err != nil { if !xerrors.Is(err, context.Canceled) { @@ -361,6 +363,7 @@ func convertProvisionerDaemon(daemon database.ProvisionerDaemon) codersdk.Provis Name: daemon.Name, Tags: daemon.Tags, Version: daemon.Version, + APIVersion: daemon.APIVersion, } for _, provisionerType := range daemon.Provisioners { result.Provisioners = append(result.Provisioners, codersdk.ProvisionerType(provisionerType)) diff --git a/enterprise/coderd/provisionerdaemons_test.go b/enterprise/coderd/provisionerdaemons_test.go index 2e19aa3168..1cce042447 100644 --- a/enterprise/coderd/provisionerdaemons_test.go +++ b/enterprise/coderd/provisionerdaemons_test.go @@ -12,6 +12,7 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/v2/buildinfo" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/rbac" @@ -40,9 +41,10 @@ func TestProvisionerDaemonServe(t *testing.T) { templateAdminClient, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleTemplateAdmin()) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() + daemonName := testutil.MustRandString(t, 63) srv, err := templateAdminClient.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ ID: uuid.New(), - Name: t.Name(), + Name: daemonName, Organization: user.OrganizationID, Provisioners: []codersdk.ProvisionerType{ codersdk.ProvisionerTypeEcho, @@ -54,7 +56,11 @@ func TestProvisionerDaemonServe(t *testing.T) { daemons, err := client.ProvisionerDaemons(ctx) //nolint:gocritic // Test assertion. require.NoError(t, err) - require.Len(t, daemons, 1) + if assert.Len(t, daemons, 1) { + assert.Equal(t, daemonName, daemons[0].Name) + assert.Equal(t, buildinfo.Version(), daemons[0].Version) + assert.Equal(t, provisionersdk.APIVersionCurrent, daemons[0].APIVersion) + } }) t.Run("NoLicense", func(t *testing.T) { @@ -63,9 +69,10 @@ func TestProvisionerDaemonServe(t *testing.T) { templateAdminClient, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleTemplateAdmin()) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() + daemonName := testutil.MustRandString(t, 63) _, err := templateAdminClient.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ ID: uuid.New(), - Name: t.Name(), + Name: daemonName, Organization: user.OrganizationID, Provisioners: []codersdk.ProvisionerType{ codersdk.ProvisionerTypeEcho, @@ -90,7 +97,7 @@ func TestProvisionerDaemonServe(t *testing.T) { defer cancel() _, err := another.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ ID: uuid.New(), - Name: t.Name(), + Name: testutil.MustRandString(t, 63), Organization: user.OrganizationID, Provisioners: []codersdk.ProvisionerType{ codersdk.ProvisionerTypeEcho, @@ -117,7 +124,7 @@ func TestProvisionerDaemonServe(t *testing.T) { defer cancel() _, err := another.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ ID: uuid.New(), - Name: t.Name(), + Name: testutil.MustRandString(t, 63), Organization: user.OrganizationID, Provisioners: []codersdk.ProvisionerType{ codersdk.ProvisionerTypeEcho, @@ -212,7 +219,9 @@ func TestProvisionerDaemonServe(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() another := codersdk.New(client.URL) + daemonName := testutil.MustRandString(t, 63) srv, err := another.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ + Name: daemonName, Organization: user.OrganizationID, Provisioners: []codersdk.ProvisionerType{ codersdk.ProvisionerTypeEcho, @@ -229,6 +238,7 @@ func TestProvisionerDaemonServe(t *testing.T) { daemons, err := client.ProvisionerDaemons(ctx) //nolint:gocritic // Test assertion. require.NoError(t, err) if assert.Len(t, daemons, 1) { + assert.Equal(t, daemonName, daemons[0].Name) assert.Equal(t, provisionersdk.ScopeOrganization, daemons[0].Tags[provisionersdk.TagScope]) } }) @@ -274,7 +284,7 @@ func TestProvisionerDaemonServe(t *testing.T) { pd := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) { return another.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ ID: uuid.New(), - Name: t.Name(), + Name: testutil.MustRandString(t, 63), Organization: user.OrganizationID, Provisioners: []codersdk.ProvisionerType{ codersdk.ProvisionerTypeEcho, @@ -352,7 +362,7 @@ func TestProvisionerDaemonServe(t *testing.T) { defer cancel() _, err := another.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ ID: uuid.New(), - Name: t.Name(), + Name: testutil.MustRandString(t, 32), Organization: user.OrganizationID, Provisioners: []codersdk.ProvisionerType{ codersdk.ProvisionerTypeEcho, @@ -387,7 +397,7 @@ func TestProvisionerDaemonServe(t *testing.T) { another := codersdk.New(client.URL) _, err := another.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ ID: uuid.New(), - Name: t.Name(), + Name: testutil.MustRandString(t, 63), Organization: user.OrganizationID, Provisioners: []codersdk.ProvisionerType{ codersdk.ProvisionerTypeEcho, @@ -420,7 +430,7 @@ func TestProvisionerDaemonServe(t *testing.T) { another := codersdk.New(client.URL) _, err := another.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ ID: uuid.New(), - Name: t.Name(), + Name: testutil.MustRandString(t, 63), Organization: user.OrganizationID, Provisioners: []codersdk.ProvisionerType{ codersdk.ProvisionerTypeEcho, diff --git a/enterprise/wsproxy/wsproxy.go b/enterprise/wsproxy/wsproxy.go index d626a7ea51..c2fb30f7b4 100644 --- a/enterprise/wsproxy/wsproxy.go +++ b/enterprise/wsproxy/wsproxy.go @@ -323,7 +323,7 @@ func New(ctx context.Context, opts *Options) (*Server, error) { // Build-Version is helpful for debugging. func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("X-Coder-Build-Version", buildinfo.Version()) + w.Header().Add(codersdk.BuildVersionHeader, buildinfo.Version()) next.ServeHTTP(w, r) }) }, diff --git a/provisionersdk/serve.go b/provisionersdk/serve.go index baa3cc1412..b2d8f19d5e 100644 --- a/provisionersdk/serve.go +++ b/provisionersdk/serve.go @@ -20,6 +20,13 @@ import ( "github.com/coder/coder/v2/provisionersdk/proto" ) +const ( + // APIVersionCurrent is the current provisionerd API version. + // Breaking changes to the provisionerd API **MUST** increment + // the major version below. + APIVersionCurrent = "1.0" +) + // ServeOptions are configurations to serve a provisioner. type ServeOptions struct { // Listener serves multiple connections. Cannot be combined with Conn. diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 3b11fd6929..0f13aa4249 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -79,6 +79,7 @@ export const provisioners: TypesGen.ProvisionerDaemon[] = [ provisioners: [], tags: {}, version: "v2.34.5", + api_version: "1.0", }, { id: "cdr-basic", @@ -87,6 +88,7 @@ export const provisioners: TypesGen.ProvisionerDaemon[] = [ provisioners: [], tags: {}, version: "v2.34.5", + api_version: "1.0", }, ]; diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 98def777d9..060b01e904 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -809,6 +809,7 @@ export interface ProvisionerDaemon { readonly last_seen_at?: string; readonly name: string; readonly version: string; + readonly api_version: string; readonly provisioners: ProvisionerType[]; readonly tags: Record; } diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 822eed72df..f12cd6763a 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -336,6 +336,7 @@ export const MockProvisioner: TypesGen.ProvisionerDaemon = { provisioners: ["echo"], tags: { scope: "organization" }, version: "v2.34.5", + api_version: "1.0", }; export const MockUserProvisioner: TypesGen.ProvisionerDaemon = { @@ -345,6 +346,7 @@ export const MockUserProvisioner: TypesGen.ProvisionerDaemon = { provisioners: ["echo"], tags: { scope: "user", owner: "12345678-abcd-1234-abcd-1234567890abcd" }, version: "v2.34.5", + api_version: "1.0", }; export const MockProvisionerJob: TypesGen.ProvisionerJob = { diff --git a/testutil/rand.go b/testutil/rand.go new file mode 100644 index 0000000000..b20cb9b057 --- /dev/null +++ b/testutil/rand.go @@ -0,0 +1,17 @@ +package testutil + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/cryptorand" +) + +// MustRandString returns a random string of length n. +func MustRandString(t *testing.T, n int) string { + t.Helper() + s, err := cryptorand.String(n) + require.NoError(t, err) + return s +}