From 2b1a0ee126d097581b3ca6a58975e14def4ce135 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 1 Apr 2022 14:42:36 -0500 Subject: [PATCH] chore: update v1 schema (#643) --- .golangci.yml | 1 + Makefile | 1 + cli/configssh.go | 2 +- cli/configssh_test.go | 3 +- cli/login.go | 12 +- cli/parameters.go | 28 +-- cli/root.go | 2 +- cli/ssh.go | 14 +- cli/ssh_test.go | 4 +- cli/start.go | 10 +- cli/start_test.go | 14 +- cli/workspaceagent_test.go | 5 +- cli/workspacecreate.go | 4 +- cli/workspacedelete.go | 2 +- cli/workspacelist.go | 3 +- cli/workspaces.go | 4 +- cli/workspaceshow.go | 4 +- cli/workspacestart.go | 2 +- cli/workspacestop.go | 2 +- cli/workspaceupdate.go | 2 +- cmd/templater/main.go | 8 +- coderd/coderdtest/coderdtest.go | 20 +-- coderd/coderdtest/coderdtest_test.go | 3 +- coderd/database/databasefake/databasefake.go | 25 ++- coderd/database/dump.sql | 159 +++++++++++------ coderd/database/dump/main.go | 10 +- .../database/migrations/000001_base.down.sql | 8 + coderd/database/migrations/000001_base.up.sql | 90 +++++----- .../migrations/000002_projects.up.sql | 22 ++- .../migrations/000003_workspaces.up.sql | 11 +- coderd/database/migrations/000004_jobs.up.sql | 45 +++-- coderd/database/models.go | 87 +++------ coderd/database/postgres/postgres.go | 22 ++- coderd/database/querier.go | 6 +- coderd/database/query.sql | 2 +- coderd/database/query.sql.go | 166 ++++++------------ coderd/httpmw/httpmw.go | 8 + coderd/httpmw/organizationparam.go | 15 +- coderd/httpmw/organizationparam_test.go | 35 +++- coderd/httpmw/projectparam.go | 2 +- coderd/httpmw/projectparam_test.go | 10 +- coderd/httpmw/projectversionparam.go | 2 +- coderd/httpmw/projectversionparam_test.go | 11 +- coderd/httpmw/userparam.go | 17 +- coderd/httpmw/userparam_test.go | 11 +- coderd/httpmw/workspacebuildparam_test.go | 8 +- coderd/httpmw/workspaceparam_test.go | 7 +- coderd/organizations.go | 28 +-- coderd/organizations_test.go | 2 +- coderd/parameter/compute.go | 12 +- coderd/parameter/compute_test.go | 15 +- coderd/parameters.go | 15 +- coderd/projects_test.go | 2 +- coderd/provisionerjobs_test.go | 7 +- coderd/users.go | 37 ++-- coderd/users_test.go | 76 ++++---- coderd/workspacebuilds.go | 2 +- coderd/workspacebuilds_test.go | 10 +- coderd/workspaceresourceauth_test.go | 4 +- coderd/workspaceresources_test.go | 4 +- coderd/workspaces.go | 2 +- coderd/workspaces_test.go | 18 +- codersdk/organizations.go | 66 +++++-- codersdk/parameters.go | 22 ++- codersdk/projects.go | 7 +- codersdk/users.go | 101 ++++++----- codersdk/workspacebuilds.go | 2 +- codersdk/workspaces.go | 2 +- examples/examples.go | 18 +- 69 files changed, 757 insertions(+), 624 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index c5235b204b..226d1bd78c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -187,6 +187,7 @@ linters-settings: - t - id - wg + - Me # Optional list of variable declarations that should be ignored completely. (defaults to empty list) # Entries must be in the form of " " or " *" for # variables, or "const " for constants. diff --git a/Makefile b/Makefile index 6d06a90f46..9d6e66a1b8 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ build: site/out bin # Runs migrations to output a dump of the database. coderd/database/dump.sql: $(wildcard coderd/database/migrations/*.sql) go run coderd/database/dump/main.go +.PHONY: coderd/database/dump.sql # Generates Go code for querying the database. coderd/database/generate: fmt/sql coderd/database/dump.sql coderd/database/query.sql diff --git a/cli/configssh.go b/cli/configssh.go index 0d803fd729..d5c2dca40f 100644 --- a/cli/configssh.go +++ b/cli/configssh.go @@ -52,7 +52,7 @@ func configSSH() *cobra.Command { sshConfigContent = sshConfigContent[:startIndex-1] + sshConfigContent[endIndex+len(sshEndToken):] } - workspaces, err := client.WorkspacesByUser(cmd.Context(), "") + workspaces, err := client.WorkspacesByUser(cmd.Context(), codersdk.Me) if err != nil { return err } diff --git a/cli/configssh_test.go b/cli/configssh_test.go index 140cee2dbe..ae9e95f7e5 100644 --- a/cli/configssh_test.go +++ b/cli/configssh_test.go @@ -8,6 +8,7 @@ import ( "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/codersdk" "github.com/coder/coder/pty/ptytest" ) @@ -21,7 +22,7 @@ func TestConfigSSH(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) tempFile, err := os.CreateTemp(t.TempDir(), "") require.NoError(t, err) diff --git a/cli/login.go b/cli/login.go index b941ba894b..eddf14284b 100644 --- a/cli/login.go +++ b/cli/login.go @@ -119,10 +119,10 @@ func login() *cobra.Command { } _, err = client.CreateFirstUser(cmd.Context(), codersdk.CreateFirstUserRequest{ - Email: email, - Username: username, - Organization: username, - Password: password, + Email: email, + Username: username, + OrganizationName: username, + Password: password, }) if err != nil { return xerrors.Errorf("create initial user: %w", err) @@ -167,7 +167,7 @@ func login() *cobra.Command { Secret: true, Validate: func(token string) error { client.SessionToken = token - _, err := client.User(cmd.Context(), "me") + _, err := client.User(cmd.Context(), codersdk.Me) if err != nil { return xerrors.New("That's not a valid token!") } @@ -180,7 +180,7 @@ func login() *cobra.Command { // Login to get user data - verify it is OK before persisting client.SessionToken = sessionToken - resp, err := client.User(cmd.Context(), "me") + resp, err := client.User(cmd.Context(), codersdk.Me) if err != nil { return xerrors.Errorf("get user: %w", err) } diff --git a/cli/parameters.go b/cli/parameters.go index 71690ba4b1..67434813bb 100644 --- a/cli/parameters.go +++ b/cli/parameters.go @@ -3,6 +3,7 @@ package cli import ( "context" + "github.com/google/uuid" "github.com/spf13/cobra" "golang.org/x/xerrors" @@ -20,42 +21,45 @@ func parameters() *cobra.Command { return cmd } -func parseScopeAndID(ctx context.Context, client *codersdk.Client, organization codersdk.Organization, rawScope string, name string) (codersdk.ParameterScope, string, error) { +func parseScopeAndID(ctx context.Context, client *codersdk.Client, organization codersdk.Organization, rawScope string, name string) (codersdk.ParameterScope, uuid.UUID, error) { scope, err := parseParameterScope(rawScope) if err != nil { - return scope, "", err + return scope, uuid.Nil, err } - var scopeID string + + var scopeID uuid.UUID switch scope { case codersdk.ParameterOrganization: if name == "" { scopeID = organization.ID } else { - org, err := client.OrganizationByName(ctx, "", name) + org, err := client.OrganizationByName(ctx, codersdk.Me, name) if err != nil { - return scope, "", err + return scope, uuid.Nil, err } scopeID = org.ID } case codersdk.ParameterProject: project, err := client.ProjectByName(ctx, organization.ID, name) if err != nil { - return scope, "", err + return scope, uuid.Nil, err } - scopeID = project.ID.String() + scopeID = project.ID case codersdk.ParameterUser: - user, err := client.User(ctx, name) + uid, _ := uuid.Parse(name) + user, err := client.User(ctx, uid) if err != nil { - return scope, "", err + return scope, uuid.Nil, err } scopeID = user.ID case codersdk.ParameterWorkspace: - workspace, err := client.WorkspaceByName(ctx, "", name) + workspace, err := client.WorkspaceByName(ctx, codersdk.Me, name) if err != nil { - return scope, "", err + return scope, uuid.Nil, err } - scopeID = workspace.ID.String() + scopeID = workspace.ID } + return scope, scopeID, nil } diff --git a/cli/root.go b/cli/root.go index 7da96cc67f..542cd67d30 100644 --- a/cli/root.go +++ b/cli/root.go @@ -107,7 +107,7 @@ func createClient(cmd *cobra.Command) (*codersdk.Client, error) { // currentOrganization returns the currently active organization for the authenticated user. func currentOrganization(cmd *cobra.Command, client *codersdk.Client) (codersdk.Organization, error) { - orgs, err := client.OrganizationsByUser(cmd.Context(), "me") + orgs, err := client.OrganizationsByUser(cmd.Context(), codersdk.Me) if err != nil { return codersdk.Organization{}, nil } diff --git a/cli/ssh.go b/cli/ssh.go index db8395454d..6cae395d28 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -32,26 +32,32 @@ func ssh() *cobra.Command { if err != nil { return err } - workspace, err := client.WorkspaceByName(cmd.Context(), "", args[0]) + + workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, args[0]) if err != nil { return err } + if workspace.LatestBuild.Transition != database.WorkspaceTransitionStart { return xerrors.New("workspace must be in start transition to ssh") } + if workspace.LatestBuild.Job.CompletedAt == nil { err = cliui.WorkspaceBuild(cmd.Context(), cmd.ErrOrStderr(), client, workspace.LatestBuild.ID, workspace.CreatedAt) if err != nil { return err } } + if workspace.LatestBuild.Transition == database.WorkspaceTransitionDelete { return xerrors.New("workspace is deleting...") } + resources, err := client.WorkspaceResourcesByBuild(cmd.Context(), workspace.LatestBuild.ID) if err != nil { return err } + resourceByAddress := make(map[string]codersdk.WorkspaceResource) for _, resource := range resources { if resource.Agent == nil { @@ -59,6 +65,7 @@ func ssh() *cobra.Command { } resourceByAddress[resource.Address] = resource } + var resourceAddress string if len(args) >= 2 { resourceAddress = args[1] @@ -73,6 +80,7 @@ func ssh() *cobra.Command { break } } + resource, exists := resourceByAddress[resourceAddress] if !exists { resourceKeys := make([]string, 0) @@ -100,6 +108,7 @@ func ssh() *cobra.Command { return err } defer conn.Close() + if stdio { rawSSH, err := conn.SSH() if err != nil { @@ -135,13 +144,16 @@ func ssh() *cobra.Command { if err != nil { return err } + sshSession.Stdin = cmd.InOrStdin() sshSession.Stdout = cmd.OutOrStdout() sshSession.Stderr = cmd.OutOrStdout() + err = sshSession.Shell() if err != nil { return err } + err = sshSession.Wait() if err != nil { return err diff --git a/cli/ssh_test.go b/cli/ssh_test.go index ce4dfb06b9..4b2a725e91 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -53,7 +53,7 @@ func TestSSH(t *testing.T) { }) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) go func() { // Run this async so the SSH command has to wait for // the build and agent to connect! @@ -111,7 +111,7 @@ func TestSSH(t *testing.T) { }) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) go func() { // Run this async so the SSH command has to wait for // the build and agent to connect! diff --git a/cli/start.go b/cli/start.go index a7a1481e38..87ac0f4d14 100644 --- a/cli/start.go +++ b/cli/start.go @@ -255,7 +255,7 @@ func start() *cobra.Command { _, _ = fmt.Fprintln(cmd.OutOrStdout(), "\n\n"+cliui.Styles.Bold.Render("Interrupt caught. Gracefully exiting...")) if dev { - workspaces, err := client.WorkspacesByUser(cmd.Context(), "") + workspaces, err := client.WorkspacesByUser(cmd.Context(), codersdk.Me) if err != nil { return xerrors.Errorf("get workspaces: %w", err) } @@ -343,10 +343,10 @@ func start() *cobra.Command { func createFirstUser(cmd *cobra.Command, client *codersdk.Client, cfg config.Root) error { _, err := client.CreateFirstUser(cmd.Context(), codersdk.CreateFirstUserRequest{ - Email: "admin@coder.com", - Username: "developer", - Password: "password", - Organization: "acme-corp", + Email: "admin@coder.com", + Username: "developer", + Password: "password", + OrganizationName: "acme-corp", }) if err != nil { return xerrors.Errorf("create first user: %w", err) diff --git a/cli/start_test.go b/cli/start_test.go index 23c0a9f9c5..285457a2a3 100644 --- a/cli/start_test.go +++ b/cli/start_test.go @@ -59,10 +59,10 @@ func TestStart(t *testing.T) { return true }, 15*time.Second, 25*time.Millisecond) _, err = client.CreateFirstUser(ctx, codersdk.CreateFirstUserRequest{ - Email: "some@one.com", - Username: "example", - Password: "password", - Organization: "example", + Email: "some@one.com", + Username: "example", + Password: "password", + OrganizationName: "example", }) require.NoError(t, err) cancelFunc() @@ -90,7 +90,7 @@ func TestStart(t *testing.T) { require.NoError(t, err) client := codersdk.New(parsed) client.SessionToken = token - _, err = client.User(ctx, "") + _, err = client.User(ctx, codersdk.Me) require.NoError(t, err) }) t.Run("TLSBadVersion", func(t *testing.T) { @@ -182,7 +182,7 @@ func TestStart(t *testing.T) { require.NoError(t, err) client := codersdk.New(parsed) client.SessionToken = token - orgs, err := client.OrganizationsByUser(ctx, "") + orgs, err := client.OrganizationsByUser(ctx, codersdk.Me) require.NoError(t, err) coderdtest.NewProvisionerDaemon(t, client) @@ -190,7 +190,7 @@ func TestStart(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, orgs[0].ID, nil) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, orgs[0].ID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) require.NoError(t, err) diff --git a/cli/workspaceagent_test.go b/cli/workspaceagent_test.go index 72dda966ca..cc87c95b9d 100644 --- a/cli/workspaceagent_test.go +++ b/cli/workspaceagent_test.go @@ -8,6 +8,7 @@ import ( "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/codersdk" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" ) @@ -43,7 +44,7 @@ func TestWorkspaceAgent(t *testing.T) { }) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) cmd, _ := clitest.New(t, "workspaces", "agent", "--auth", "aws-instance-identity", "--url", client.URL.String()) @@ -97,7 +98,7 @@ func TestWorkspaceAgent(t *testing.T) { }) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) cmd, _ := clitest.New(t, "workspaces", "agent", "--auth", "google-instance-identity", "--url", client.URL.String()) diff --git a/cli/workspacecreate.go b/cli/workspacecreate.go index a34bb57797..8a2e128ed8 100644 --- a/cli/workspacecreate.go +++ b/cli/workspacecreate.go @@ -74,7 +74,7 @@ func workspaceCreate() *cobra.Command { _, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"Creating with the "+cliui.Styles.Field.Render(project.Name)+" project...") workspaceName := args[0] - _, err = client.WorkspaceByName(cmd.Context(), "", workspaceName) + _, err = client.WorkspaceByName(cmd.Context(), codersdk.Me, workspaceName) if err == nil { return xerrors.Errorf("A workspace already exists named %q!", workspaceName) } @@ -137,7 +137,7 @@ func workspaceCreate() *cobra.Command { } before := time.Now() - workspace, err := client.CreateWorkspace(cmd.Context(), "", codersdk.CreateWorkspaceRequest{ + workspace, err := client.CreateWorkspace(cmd.Context(), codersdk.Me, codersdk.CreateWorkspaceRequest{ ProjectID: project.ID, Name: workspaceName, ParameterValues: parameters, diff --git a/cli/workspacedelete.go b/cli/workspacedelete.go index e5b5c7a886..e77e66bd9d 100644 --- a/cli/workspacedelete.go +++ b/cli/workspacedelete.go @@ -21,7 +21,7 @@ func workspaceDelete() *cobra.Command { if err != nil { return err } - workspace, err := client.WorkspaceByName(cmd.Context(), "", args[0]) + workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, args[0]) if err != nil { return err } diff --git a/cli/workspacelist.go b/cli/workspacelist.go index f78c2122d6..28a7adaf5e 100644 --- a/cli/workspacelist.go +++ b/cli/workspacelist.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" "github.com/coder/coder/cli/cliui" + "github.com/coder/coder/codersdk" ) func workspaceList() *cobra.Command { @@ -21,7 +22,7 @@ func workspaceList() *cobra.Command { return err } start := time.Now() - workspaces, err := client.WorkspacesByUser(cmd.Context(), "") + workspaces, err := client.WorkspacesByUser(cmd.Context(), codersdk.Me) if err != nil { return err } diff --git a/cli/workspaces.go b/cli/workspaces.go index 067d45e3d0..582c7a0041 100644 --- a/cli/workspaces.go +++ b/cli/workspaces.go @@ -4,6 +4,8 @@ import ( "strings" "github.com/spf13/cobra" + + "github.com/coder/coder/codersdk" ) func workspaces() *cobra.Command { @@ -29,7 +31,7 @@ func validArgsWorkspaceName(cmd *cobra.Command, _ []string, toComplete string) ( if err != nil { return nil, cobra.ShellCompDirectiveError } - workspaces, err := client.WorkspacesByUser(cmd.Context(), "") + workspaces, err := client.WorkspacesByUser(cmd.Context(), codersdk.Me) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cli/workspaceshow.go b/cli/workspaceshow.go index 987ce6b94c..80a2fcc3ca 100644 --- a/cli/workspaceshow.go +++ b/cli/workspaceshow.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/spf13/cobra" + + "github.com/coder/coder/codersdk" ) func workspaceShow() *cobra.Command { @@ -14,7 +16,7 @@ func workspaceShow() *cobra.Command { if err != nil { return err } - workspace, err := client.WorkspaceByName(cmd.Context(), "", args[0]) + workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, args[0]) if err != nil { return err } diff --git a/cli/workspacestart.go b/cli/workspacestart.go index a4205079f9..d9cd1cf9b4 100644 --- a/cli/workspacestart.go +++ b/cli/workspacestart.go @@ -20,7 +20,7 @@ func workspaceStart() *cobra.Command { if err != nil { return err } - workspace, err := client.WorkspaceByName(cmd.Context(), "", args[0]) + workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, args[0]) if err != nil { return err } diff --git a/cli/workspacestop.go b/cli/workspacestop.go index 222021a4f0..5e647815ee 100644 --- a/cli/workspacestop.go +++ b/cli/workspacestop.go @@ -20,7 +20,7 @@ func workspaceStop() *cobra.Command { if err != nil { return err } - workspace, err := client.WorkspaceByName(cmd.Context(), "", args[0]) + workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, args[0]) if err != nil { return err } diff --git a/cli/workspaceupdate.go b/cli/workspaceupdate.go index 36cd101422..f77f2ad72d 100644 --- a/cli/workspaceupdate.go +++ b/cli/workspaceupdate.go @@ -17,7 +17,7 @@ func workspaceUpdate() *cobra.Command { if err != nil { return err } - workspace, err := client.WorkspaceByName(cmd.Context(), "", args[0]) + workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, args[0]) if err != nil { return err } diff --git a/cmd/templater/main.go b/cmd/templater/main.go index 013ac9e931..753f6f5f1e 100644 --- a/cmd/templater/main.go +++ b/cmd/templater/main.go @@ -99,10 +99,10 @@ func parse(cmd *cobra.Command, parameters []codersdk.CreateParameterRequest) err defer daemonClose.Close() created, err := client.CreateFirstUser(cmd.Context(), codersdk.CreateFirstUserRequest{ - Email: "templater@coder.com", - Username: "templater", - Organization: "templater", - Password: "insecure", + Email: "templater@coder.com", + Username: "templater", + OrganizationName: "templater", + Password: "insecure", }) if err != nil { return err diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 958c37ef8d..88ef33639f 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -155,10 +155,10 @@ func NewProvisionerDaemon(t *testing.T, client *codersdk.Client) io.Closer { // with the passed in codersdk client. func CreateFirstUser(t *testing.T, client *codersdk.Client) codersdk.CreateFirstUserResponse { req := codersdk.CreateFirstUserRequest{ - Email: "testuser@coder.com", - Username: "testuser", - Password: "testpass", - Organization: "testorg", + Email: "testuser@coder.com", + Username: "testuser", + Password: "testpass", + OrganizationName: "testorg", } resp, err := client.CreateFirstUser(context.Background(), req) require.NoError(t, err) @@ -173,12 +173,12 @@ func CreateFirstUser(t *testing.T, client *codersdk.Client) codersdk.CreateFirst } // CreateAnotherUser creates and authenticates a new user. -func CreateAnotherUser(t *testing.T, client *codersdk.Client, organization string) *codersdk.Client { +func CreateAnotherUser(t *testing.T, client *codersdk.Client, organizationID uuid.UUID) *codersdk.Client { req := codersdk.CreateUserRequest{ Email: namesgenerator.GetRandomName(1) + "@coder.com", Username: randomUsername(), Password: "testpass", - OrganizationID: organization, + OrganizationID: organizationID, } _, err := client.CreateUser(context.Background(), req) require.NoError(t, err) @@ -197,12 +197,12 @@ func CreateAnotherUser(t *testing.T, client *codersdk.Client, organization strin // CreateProjectVersion creates a project import provisioner job // with the responses provided. It uses the "echo" provisioner for compatibility // with testing. -func CreateProjectVersion(t *testing.T, client *codersdk.Client, organization string, res *echo.Responses) codersdk.ProjectVersion { +func CreateProjectVersion(t *testing.T, client *codersdk.Client, organizationID uuid.UUID, res *echo.Responses) codersdk.ProjectVersion { data, err := echo.Tar(res) require.NoError(t, err) file, err := client.Upload(context.Background(), codersdk.ContentTypeTar, data) require.NoError(t, err) - projectVersion, err := client.CreateProjectVersion(context.Background(), organization, codersdk.CreateProjectVersionRequest{ + projectVersion, err := client.CreateProjectVersion(context.Background(), organizationID, codersdk.CreateProjectVersionRequest{ StorageSource: file.Hash, StorageMethod: database.ProvisionerStorageMethodFile, Provisioner: database.ProvisionerTypeEcho, @@ -213,7 +213,7 @@ func CreateProjectVersion(t *testing.T, client *codersdk.Client, organization st // CreateProject creates a project with the "echo" provisioner for // compatibility with testing. The name assigned is randomly generated. -func CreateProject(t *testing.T, client *codersdk.Client, organization string, version uuid.UUID) codersdk.Project { +func CreateProject(t *testing.T, client *codersdk.Client, organization uuid.UUID, version uuid.UUID) codersdk.Project { project, err := client.CreateProject(context.Background(), organization, codersdk.CreateProjectRequest{ Name: randomUsername(), VersionID: version, @@ -268,7 +268,7 @@ func AwaitWorkspaceAgents(t *testing.T, client *codersdk.Client, build uuid.UUID // CreateWorkspace creates a workspace for the user and project provided. // A random name is generated for it. -func CreateWorkspace(t *testing.T, client *codersdk.Client, user string, projectID uuid.UUID) codersdk.Workspace { +func CreateWorkspace(t *testing.T, client *codersdk.Client, user uuid.UUID, projectID uuid.UUID) codersdk.Workspace { workspace, err := client.CreateWorkspace(context.Background(), user, codersdk.CreateWorkspaceRequest{ ProjectID: projectID, Name: randomUsername(), diff --git a/coderd/coderdtest/coderdtest_test.go b/coderd/coderdtest/coderdtest_test.go index 7eb8719ea2..597850b6da 100644 --- a/coderd/coderdtest/coderdtest_test.go +++ b/coderd/coderdtest/coderdtest_test.go @@ -6,6 +6,7 @@ import ( "go.uber.org/goleak" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/codersdk" ) func TestMain(m *testing.M) { @@ -20,7 +21,7 @@ func TestNew(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID) _, _ = coderdtest.NewGoogleInstanceIdentity(t, "example", false) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 618c1c6daf..f338a05bd8 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -143,7 +143,7 @@ func (q *fakeQuerier) GetUserByEmailOrUsername(_ context.Context, arg database.G return database.User{}, sql.ErrNoRows } -func (q *fakeQuerier) GetUserByID(_ context.Context, id string) (database.User, error) { +func (q *fakeQuerier) GetUserByID(_ context.Context, id uuid.UUID) (database.User, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -217,34 +217,33 @@ func (q *fakeQuerier) GetWorkspaceOwnerCountsByProjectIDs(_ context.Context, pro q.mutex.RLock() defer q.mutex.RUnlock() - counts := map[string]map[string]struct{}{} + counts := map[uuid.UUID]map[uuid.UUID]struct{}{} for _, projectID := range projectIDs { found := false for _, workspace := range q.workspace { - if workspace.ProjectID.String() != projectID.String() { + if workspace.ProjectID != projectID { continue } if workspace.Deleted { continue } - countByOwnerID, ok := counts[projectID.String()] + countByOwnerID, ok := counts[projectID] if !ok { - countByOwnerID = map[string]struct{}{} + countByOwnerID = map[uuid.UUID]struct{}{} } countByOwnerID[workspace.OwnerID] = struct{}{} - counts[projectID.String()] = countByOwnerID + counts[projectID] = countByOwnerID found = true break } if !found { - counts[projectID.String()] = map[string]struct{}{} + counts[projectID] = map[uuid.UUID]struct{}{} } } res := make([]database.GetWorkspaceOwnerCountsByProjectIDsRow, 0) for key, value := range counts { - uid := uuid.MustParse(key) res = append(res, database.GetWorkspaceOwnerCountsByProjectIDsRow{ - ProjectID: uid, + ProjectID: key, Count: int64(len(value)), }) } @@ -364,7 +363,7 @@ func (q *fakeQuerier) GetWorkspacesByUserID(_ context.Context, req database.GetW return workspaces, nil } -func (q *fakeQuerier) GetOrganizationByID(_ context.Context, id string) (database.Organization, error) { +func (q *fakeQuerier) GetOrganizationByID(_ context.Context, id uuid.UUID) (database.Organization, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -388,7 +387,7 @@ func (q *fakeQuerier) GetOrganizationByName(_ context.Context, name string) (dat return database.Organization{}, sql.ErrNoRows } -func (q *fakeQuerier) GetOrganizationsByUserID(_ context.Context, userID string) ([]database.Organization, error) { +func (q *fakeQuerier) GetOrganizationsByUserID(_ context.Context, userID uuid.UUID) ([]database.Organization, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -483,7 +482,7 @@ func (q *fakeQuerier) GetProjectVersionByProjectIDAndName(_ context.Context, arg defer q.mutex.RUnlock() for _, projectVersion := range q.projectVersion { - if projectVersion.ProjectID.UUID.String() != arg.ProjectID.UUID.String() { + if projectVersion.ProjectID != arg.ProjectID { continue } if !strings.EqualFold(projectVersion.Name, arg.Name) { @@ -1056,7 +1055,7 @@ func (q *fakeQuerier) InsertWorkspaceBuild(_ context.Context, arg database.Inser ProjectVersionID: arg.ProjectVersionID, BeforeID: arg.BeforeID, Transition: arg.Transition, - Initiator: arg.Initiator, + InitiatorID: arg.InitiatorID, JobID: arg.JobID, ProvisionerState: arg.ProvisionerState, } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index f0150fb5b8..64f3525a3c 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -57,12 +57,6 @@ CREATE TYPE provisioner_type AS ENUM ( 'terraform' ); -CREATE TYPE userstatus AS ENUM ( - 'active', - 'dormant', - 'decommissioned' -); - CREATE TYPE workspace_transition AS ENUM ( 'start', 'stop', @@ -72,7 +66,7 @@ CREATE TYPE workspace_transition AS ENUM ( CREATE TABLE api_keys ( id text NOT NULL, hashed_secret bytea NOT NULL, - user_id text NOT NULL, + user_id uuid NOT NULL, application boolean NOT NULL, name text NOT NULL, last_used timestamp with time zone NOT NULL, @@ -90,7 +84,7 @@ CREATE TABLE api_keys ( CREATE TABLE files ( hash character varying(64) NOT NULL, created_at timestamp with time zone NOT NULL, - created_by text NOT NULL, + created_by uuid NOT NULL, mimetype character varying(64) NOT NULL, data bytea NOT NULL ); @@ -101,25 +95,30 @@ CREATE TABLE licenses ( created_at timestamp with time zone NOT NULL ); +CREATE SEQUENCE licenses_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE licenses_id_seq OWNED BY public.licenses.id; + CREATE TABLE organization_members ( - organization_id text NOT NULL, - user_id text NOT NULL, + user_id uuid NOT NULL, + organization_id uuid NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, roles text[] DEFAULT '{organization-member}'::text[] NOT NULL ); CREATE TABLE organizations ( - id text NOT NULL, + id uuid NOT NULL, name text NOT NULL, description text NOT NULL, created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - "default" boolean DEFAULT false NOT NULL, - auto_off_threshold bigint DEFAULT '28800000000000'::bigint NOT NULL, - cpu_provisioning_rate real DEFAULT 4.0 NOT NULL, - memory_provisioning_rate real DEFAULT 1.0 NOT NULL, - workspace_auto_off boolean DEFAULT false NOT NULL + updated_at timestamp with time zone NOT NULL ); CREATE TABLE parameter_schemas ( @@ -146,7 +145,7 @@ CREATE TABLE parameter_values ( created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, scope parameter_scope NOT NULL, - scope_id text NOT NULL, + scope_id uuid NOT NULL, name character varying(64) NOT NULL, source_scheme parameter_source_scheme NOT NULL, source_value text NOT NULL, @@ -156,7 +155,7 @@ CREATE TABLE parameter_values ( CREATE TABLE project_versions ( id uuid NOT NULL, project_id uuid, - organization_id text NOT NULL, + organization_id uuid NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, name character varying(64) NOT NULL, @@ -168,7 +167,7 @@ CREATE TABLE projects ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, - organization_id text NOT NULL, + organization_id uuid NOT NULL, deleted boolean DEFAULT false NOT NULL, name character varying(64) NOT NULL, provisioner provisioner_type NOT NULL, @@ -179,7 +178,7 @@ CREATE TABLE provisioner_daemons ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone, - organization_id text, + organization_id uuid, name character varying(64) NOT NULL, provisioners provisioner_type[] NOT NULL ); @@ -202,8 +201,8 @@ CREATE TABLE provisioner_jobs ( canceled_at timestamp with time zone, completed_at timestamp with time zone, error text, - organization_id text NOT NULL, - initiator_id text NOT NULL, + organization_id uuid NOT NULL, + initiator_id uuid NOT NULL, provisioner provisioner_type NOT NULL, storage_method provisioner_storage_method NOT NULL, storage_source text NOT NULL, @@ -213,7 +212,7 @@ CREATE TABLE provisioner_jobs ( ); CREATE TABLE users ( - id text NOT NULL, + id uuid NOT NULL, email text NOT NULL, name text NOT NULL, revoked boolean NOT NULL, @@ -221,17 +220,7 @@ CREATE TABLE users ( hashed_password bytea NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, - temporary_password boolean DEFAULT false NOT NULL, - avatar_hash text DEFAULT ''::text NOT NULL, - ssh_key_regenerated_at timestamp with time zone DEFAULT now() NOT NULL, - username text DEFAULT ''::text NOT NULL, - dotfiles_git_uri text DEFAULT ''::text NOT NULL, - roles text[] DEFAULT '{site-member}'::text[] NOT NULL, - status userstatus DEFAULT 'active'::public.userstatus NOT NULL, - relatime timestamp with time zone DEFAULT now() NOT NULL, - gpg_key_regenerated_at timestamp with time zone DEFAULT now() NOT NULL, - _decomissioned boolean DEFAULT false NOT NULL, - shell text DEFAULT ''::text NOT NULL + username text DEFAULT ''::text NOT NULL ); CREATE TABLE workspace_agents ( @@ -260,7 +249,7 @@ CREATE TABLE workspace_builds ( before_id uuid, after_id uuid, transition workspace_transition NOT NULL, - initiator character varying(255) NOT NULL, + initiator_id uuid NOT NULL, provisioner_state bytea, job_id uuid NOT NULL ); @@ -280,85 +269,136 @@ CREATE TABLE workspaces ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, - owner_id text NOT NULL, + owner_id uuid NOT NULL, project_id uuid NOT NULL, deleted boolean DEFAULT false NOT NULL, name character varying(64) NOT NULL ); -ALTER TABLE ONLY files - ADD CONSTRAINT files_hash_key UNIQUE (hash); +ALTER TABLE ONLY licenses ALTER COLUMN id SET DEFAULT nextval('public.licenses_id_seq'::regclass); -ALTER TABLE ONLY parameter_schemas - ADD CONSTRAINT parameter_schemas_id_key UNIQUE (id); +ALTER TABLE ONLY api_keys + ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY files + ADD CONSTRAINT files_pkey PRIMARY KEY (hash); + +ALTER TABLE ONLY licenses + ADD CONSTRAINT licenses_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY organization_members + ADD CONSTRAINT organization_members_pkey PRIMARY KEY (organization_id, user_id); + +ALTER TABLE ONLY organizations + ADD CONSTRAINT organizations_pkey PRIMARY KEY (id); ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_name_key UNIQUE (job_id, name); +ALTER TABLE ONLY parameter_schemas + ADD CONSTRAINT parameter_schemas_pkey PRIMARY KEY (id); + ALTER TABLE ONLY parameter_values - ADD CONSTRAINT parameter_values_id_key UNIQUE (id); + ADD CONSTRAINT parameter_values_pkey PRIMARY KEY (id); ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_scope_id_name_key UNIQUE (scope_id, name); ALTER TABLE ONLY project_versions - ADD CONSTRAINT project_versions_id_key UNIQUE (id); + ADD CONSTRAINT project_versions_pkey PRIMARY KEY (id); ALTER TABLE ONLY project_versions ADD CONSTRAINT project_versions_project_id_name_key UNIQUE (project_id, name); -ALTER TABLE ONLY projects - ADD CONSTRAINT projects_id_key UNIQUE (id); - ALTER TABLE ONLY projects ADD CONSTRAINT projects_organization_id_name_key UNIQUE (organization_id, name); -ALTER TABLE ONLY provisioner_daemons - ADD CONSTRAINT provisioner_daemons_id_key UNIQUE (id); +ALTER TABLE ONLY projects + ADD CONSTRAINT projects_pkey PRIMARY KEY (id); ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_name_key UNIQUE (name); +ALTER TABLE ONLY provisioner_daemons + ADD CONSTRAINT provisioner_daemons_pkey PRIMARY KEY (id); + ALTER TABLE ONLY provisioner_job_logs - ADD CONSTRAINT provisioner_job_logs_id_key UNIQUE (id); + ADD CONSTRAINT provisioner_job_logs_pkey PRIMARY KEY (id); ALTER TABLE ONLY provisioner_jobs - ADD CONSTRAINT provisioner_jobs_id_key UNIQUE (id); + ADD CONSTRAINT provisioner_jobs_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_auth_token_key UNIQUE (auth_token); ALTER TABLE ONLY workspace_agents - ADD CONSTRAINT workspace_agents_id_key UNIQUE (id); - -ALTER TABLE ONLY workspace_builds - ADD CONSTRAINT workspace_builds_id_key UNIQUE (id); + ADD CONSTRAINT workspace_agents_pkey PRIMARY KEY (id); ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_key UNIQUE (job_id); +ALTER TABLE ONLY workspace_builds + ADD CONSTRAINT workspace_builds_pkey PRIMARY KEY (id); + ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_workspace_id_name_key UNIQUE (workspace_id, name); ALTER TABLE ONLY workspace_resources - ADD CONSTRAINT workspace_resources_id_key UNIQUE (id); + ADD CONSTRAINT workspace_resources_pkey PRIMARY KEY (id); ALTER TABLE ONLY workspaces - ADD CONSTRAINT workspaces_id_key UNIQUE (id); + ADD CONSTRAINT workspaces_pkey PRIMARY KEY (id); + +CREATE INDEX idx_api_keys_user ON api_keys USING btree (user_id); + +CREATE INDEX idx_organization_member_organization_id_uuid ON organization_members USING btree (organization_id); + +CREATE INDEX idx_organization_member_user_id_uuid ON organization_members USING btree (user_id); + +CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name); + +CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lower(name)); + +CREATE UNIQUE INDEX idx_users_email ON users USING btree (email); + +CREATE UNIQUE INDEX idx_users_username ON users USING btree (username); CREATE UNIQUE INDEX projects_organization_id_name_idx ON projects USING btree (organization_id, name) WHERE (deleted = false); +CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)); + CREATE UNIQUE INDEX workspaces_owner_id_name_idx ON workspaces USING btree (owner_id, name) WHERE (deleted = false); +ALTER TABLE ONLY api_keys + ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + +ALTER TABLE ONLY organization_members + ADD CONSTRAINT organization_members_organization_id_uuid_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; + +ALTER TABLE ONLY organization_members + ADD CONSTRAINT organization_members_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; ALTER TABLE ONLY project_versions - ADD CONSTRAINT project_versions_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id); + ADD CONSTRAINT project_versions_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; + +ALTER TABLE ONLY project_versions + ADD CONSTRAINT project_versions_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; + +ALTER TABLE ONLY projects + ADD CONSTRAINT projects_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; +ALTER TABLE ONLY provisioner_jobs + ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; + ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_resource_id_fkey FOREIGN KEY (resource_id) REFERENCES workspace_resources(id) ON DELETE CASCADE; @@ -375,5 +415,8 @@ ALTER TABLE ONLY workspace_resources ADD CONSTRAINT workspace_resources_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; ALTER TABLE ONLY workspaces - ADD CONSTRAINT workspaces_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id); + ADD CONSTRAINT workspaces_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT; + +ALTER TABLE ONLY workspaces + ADD CONSTRAINT workspaces_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE RESTRICT; diff --git a/coderd/database/dump/main.go b/coderd/database/dump/main.go index b5b35f7361..802fcedc38 100644 --- a/coderd/database/dump/main.go +++ b/coderd/database/dump/main.go @@ -19,14 +19,17 @@ func main() { panic(err) } defer closeFn() + db, err := sql.Open("postgres", connection) if err != nil { panic(err) } + err = database.MigrateUp(db) if err != nil { panic(err) } + cmd := exec.Command( "pg_dump", "--schema-only", @@ -39,10 +42,11 @@ func main() { // queries executing against this table. "--exclude-table=schema_migrations", ) - cmd.Env = []string{ + + cmd.Env = append(os.Environ(), []string{ "PGTZ=UTC", "PGCLIENTENCODING=UTF8", - } + }...) var output bytes.Buffer cmd.Stdout = &output cmd.Stderr = os.Stderr @@ -57,7 +61,7 @@ func main() { // Public is implicit in the schema. "s/ public\\./ /", // Remove database settings. - "s/SET.*;//g", + "s/SET .* = .*;//g", // Remove select statements. These aren't useful // to a reader of the dump. "s/SELECT.*;//g", diff --git a/coderd/database/migrations/000001_base.down.sql b/coderd/database/migrations/000001_base.down.sql index e69de29bb2..d4540a4b5a 100644 --- a/coderd/database/migrations/000001_base.down.sql +++ b/coderd/database/migrations/000001_base.down.sql @@ -0,0 +1,8 @@ +DROP TABLE licenses; +DROP TABLE api_keys; +DROP TABLE organization_members; +DROP TABLE organizations; +DROP TABLE users; + +DROP TYPE login_type; + diff --git a/coderd/database/migrations/000001_base.up.sql b/coderd/database/migrations/000001_base.up.sql index c98a1ca0f3..65fbbf8fd4 100644 --- a/coderd/database/migrations/000001_base.up.sql +++ b/coderd/database/migrations/000001_base.up.sql @@ -4,28 +4,18 @@ -- All tables and types are stolen from: -- https://github.com/coder/m/blob/47b6fc383347b9f9fab424d829c482defd3e1fe2/product/coder/pkg/database/dump.sql -DO $$ BEGIN - CREATE TYPE login_type AS ENUM ( - 'built-in', - 'saml', - 'oidc' - ); -EXCEPTION - WHEN duplicate_object THEN null; -END $$; +-- +-- Name: users; Type: TABLE; Schema: public; Owner: coder +-- -DO $$ BEGIN - CREATE TYPE userstatus AS ENUM ( - 'active', - 'dormant', - 'decommissioned' - ); -EXCEPTION - WHEN duplicate_object THEN null; -END $$; +CREATE TYPE login_type AS ENUM ( + 'built-in', + 'saml', + 'oidc' +); CREATE TABLE IF NOT EXISTS users ( - id text NOT NULL, + id uuid NOT NULL, email text NOT NULL, name text NOT NULL, revoked boolean NOT NULL, @@ -33,44 +23,51 @@ CREATE TABLE IF NOT EXISTS users ( hashed_password bytea NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, - temporary_password boolean DEFAULT false NOT NULL, - avatar_hash text DEFAULT '' :: text NOT NULL, - ssh_key_regenerated_at timestamp with time zone DEFAULT now() NOT NULL, - username text DEFAULT '' :: text NOT NULL, - dotfiles_git_uri text DEFAULT '' :: text NOT NULL, - roles text [] DEFAULT '{site-member}' :: text [] NOT NULL, - status userstatus DEFAULT 'active' :: public.userstatus NOT NULL, - relatime timestamp with time zone DEFAULT now() NOT NULL, - gpg_key_regenerated_at timestamp with time zone DEFAULT now() NOT NULL, - _decomissioned boolean DEFAULT false NOT NULL, - shell text DEFAULT '' :: text NOT NULL + username text DEFAULT ''::text NOT NULL, + PRIMARY KEY (id) ); +CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users USING btree (email); +CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username ON users USING btree (username); +CREATE UNIQUE INDEX IF NOT EXISTS users_username_lower_idx ON users USING btree (lower(username)); + +-- +-- Name: organizations; Type: TABLE; Schema: Owner: coder +-- + CREATE TABLE IF NOT EXISTS organizations ( - id text NOT NULL, + id uuid NOT NULL, name text NOT NULL, description text NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, - "default" boolean DEFAULT false NOT NULL, - auto_off_threshold bigint DEFAULT '28800000000000' :: bigint NOT NULL, - cpu_provisioning_rate real DEFAULT 4.0 NOT NULL, - memory_provisioning_rate real DEFAULT 1.0 NOT NULL, - workspace_auto_off boolean DEFAULT false NOT NULL + PRIMARY KEY (id) ); +CREATE UNIQUE INDEX IF NOT EXISTS idx_organization_name ON organizations USING btree (name); +CREATE UNIQUE INDEX IF NOT EXISTS idx_organization_name_lower ON organizations USING btree (lower(name)); + CREATE TABLE IF NOT EXISTS organization_members ( - organization_id text NOT NULL, - user_id text NOT NULL, + user_id uuid NOT NULL, + organization_id uuid NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, - roles text [] DEFAULT '{organization-member}' :: text [] NOT NULL + roles text [] DEFAULT '{organization-member}' :: text [] NOT NULL, + PRIMARY KEY (organization_id, user_id) ); +CREATE INDEX idx_organization_member_organization_id_uuid ON organization_members USING btree (organization_id); +CREATE INDEX idx_organization_member_user_id_uuid ON organization_members USING btree (user_id); + +ALTER TABLE ONLY organization_members + ADD CONSTRAINT organization_members_organization_id_uuid_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; +ALTER TABLE ONLY organization_members + ADD CONSTRAINT organization_members_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + CREATE TABLE IF NOT EXISTS api_keys ( id text NOT NULL, hashed_secret bytea NOT NULL, - user_id text NOT NULL, + user_id uuid NOT NULL, application boolean NOT NULL, name text NOT NULL, last_used timestamp with time zone NOT NULL, @@ -82,11 +79,18 @@ CREATE TABLE IF NOT EXISTS api_keys ( oidc_refresh_token text DEFAULT ''::text NOT NULL, oidc_id_token text DEFAULT ''::text NOT NULL, oidc_expiry timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL, - devurl_token boolean DEFAULT false NOT NULL + devurl_token boolean DEFAULT false NOT NULL, + PRIMARY KEY (id) ); +CREATE INDEX IF NOT EXISTS idx_api_keys_user ON api_keys USING btree (user_id); + +ALTER TABLE ONLY api_keys + ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + CREATE TABLE IF NOT EXISTS licenses ( - id integer NOT NULL, + id serial, license jsonb NOT NULL, - created_at timestamp with time zone NOT NULL + created_at timestamptz NOT NULL, + PRIMARY KEY (id) ); diff --git a/coderd/database/migrations/000002_projects.up.sql b/coderd/database/migrations/000002_projects.up.sql index d8a9fe29ba..b9b778ea69 100644 --- a/coderd/database/migrations/000002_projects.up.sql +++ b/coderd/database/migrations/000002_projects.up.sql @@ -1,10 +1,12 @@ -- Store arbitrary data like project source code or avatars. CREATE TABLE files ( - hash varchar(64) NOT NULL UNIQUE, + hash varchar(64) NOT NULL, created_at timestamptz NOT NULL, - created_by text NOT NULL, + -- foreign key? + created_by uuid NOT NULL, mimetype varchar(64) NOT NULL, - data bytea NOT NULL + data bytea NOT NULL, + PRIMARY KEY (hash) ); CREATE TYPE provisioner_type AS ENUM ('echo', 'terraform'); @@ -12,17 +14,18 @@ CREATE TYPE provisioner_type AS ENUM ('echo', 'terraform'); -- Project defines infrastructure that your software project -- requires for development. CREATE TABLE projects ( - id uuid NOT NULL UNIQUE, + id uuid NOT NULL, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, -- Projects must be scoped to an organization. - organization_id text NOT NULL, + organization_id uuid NOT NULL REFERENCES organizations (id) ON DELETE CASCADE, deleted boolean NOT NULL DEFAULT FALSE, name varchar(64) NOT NULL, provisioner provisioner_type NOT NULL, -- Target's a Project Version to use for Workspaces. -- If a Workspace doesn't match this version, it will be prompted to rebuild. active_version_id uuid NOT NULL, + PRIMARY KEY (id), -- Disallow projects to have the same name under -- the same organization. UNIQUE(organization_id, name) @@ -35,10 +38,10 @@ CREATE UNIQUE INDEX ON projects (organization_id, name) WHERE deleted = FALSE; -- an "import" job is queued to parse parameters. A Project Version -- can only be used if the import job succeeds. CREATE TABLE project_versions ( - id uuid NOT NULL UNIQUE, - -- This should be indexed. - project_id uuid REFERENCES projects (id), - organization_id text NOT NULL, + id uuid NOT NULL, + -- This should be indexed. It is intentionally nullable. + project_id uuid REFERENCES projects (id) ON DELETE CASCADE, + organization_id uuid NOT NULL REFERENCES organizations (id) ON DELETE CASCADE, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, -- Name is generated for ease of differentiation. @@ -49,6 +52,7 @@ CREATE TABLE project_versions ( description varchar(1048576) NOT NULL, -- The job ID for building the project version. job_id uuid NOT NULL, + PRIMARY KEY (id), -- Disallow projects to have the same build name -- multiple times. UNIQUE(project_id, name) diff --git a/coderd/database/migrations/000003_workspaces.up.sql b/coderd/database/migrations/000003_workspaces.up.sql index 186434fea5..3f8393c9cb 100644 --- a/coderd/database/migrations/000003_workspaces.up.sql +++ b/coderd/database/migrations/000003_workspaces.up.sql @@ -1,11 +1,14 @@ CREATE TABLE workspaces ( - id uuid NOT NULL UNIQUE, + id uuid NOT NULL, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, - owner_id text NOT NULL, - project_id uuid NOT NULL REFERENCES projects (id), + -- Use ON DELETE RESTRICT so that we can cleanup external workspace + -- resources first. + owner_id uuid NOT NULL REFERENCES users (id) ON DELETE RESTRICT, + project_id uuid NOT NULL REFERENCES projects (id) ON DELETE RESTRICT, deleted boolean NOT NULL DEFAULT FALSE, - name varchar(64) NOT NULL + name varchar(64) NOT NULL, + PRIMARY KEY (id) ); -- Enforces no active workspaces have the same name. diff --git a/coderd/database/migrations/000004_jobs.up.sql b/coderd/database/migrations/000004_jobs.up.sql index bf9b731d4e..7c5a960804 100644 --- a/coderd/database/migrations/000004_jobs.up.sql +++ b/coderd/database/migrations/000004_jobs.up.sql @@ -1,12 +1,13 @@ CREATE TABLE IF NOT EXISTS provisioner_daemons ( - id uuid NOT NULL UNIQUE, + id uuid NOT NULL, created_at timestamptz NOT NULL, updated_at timestamptz, - organization_id text, + organization_id uuid, -- Name is generated for ease of differentiation. -- eg. WowBananas16 name varchar(64) NOT NULL UNIQUE, - provisioners provisioner_type [ ] NOT NULL + provisioners provisioner_type [ ] NOT NULL, + PRIMARY KEY (id) ); CREATE TYPE provisioner_job_type AS ENUM ( @@ -17,21 +18,23 @@ CREATE TYPE provisioner_job_type AS ENUM ( CREATE TYPE provisioner_storage_method AS ENUM ('file'); CREATE TABLE IF NOT EXISTS provisioner_jobs ( - id uuid NOT NULL UNIQUE, + id uuid NOT NULL, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, started_at timestamptz, canceled_at timestamptz, completed_at timestamptz, error text, - organization_id text NOT NULL, - initiator_id text NOT NULL, + organization_id uuid NOT NULL REFERENCES organizations (id) ON DELETE CASCADE, + -- foreign key? + initiator_id uuid NOT NULL, provisioner provisioner_type NOT NULL, storage_method provisioner_storage_method NOT NULL, storage_source text NOT NULL, type provisioner_job_type NOT NULL, input jsonb NOT NULL, - worker_id uuid + worker_id uuid, + PRIMARY KEY (id) ); CREATE TYPE log_level AS ENUM ( @@ -48,28 +51,30 @@ CREATE TYPE log_source AS ENUM ( ); CREATE TABLE IF NOT EXISTS provisioner_job_logs ( - id uuid NOT NULL UNIQUE, + id uuid NOT NULL, job_id uuid NOT NULL REFERENCES provisioner_jobs (id) ON DELETE CASCADE, created_at timestamptz NOT NULL, source log_source NOT NULL, level log_level NOT NULL, stage varchar(128) NOT NULL, - output varchar(1024) NOT NULL + output varchar(1024) NOT NULL, + PRIMARY KEY (id) ); CREATE TABLE workspace_resources ( - id uuid NOT NULL UNIQUE, + id uuid NOT NULL, created_at timestamptz NOT NULL, job_id uuid NOT NULL REFERENCES provisioner_jobs (id) ON DELETE CASCADE, transition workspace_transition NOT NULL, address varchar(256) NOT NULL, type varchar(192) NOT NULL, name varchar(64) NOT NULL, - agent_id uuid + agent_id uuid, + PRIMARY KEY (id) ); CREATE TABLE workspace_agents ( - id uuid NOT NULL UNIQUE, + id uuid NOT NULL, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, first_connected_at timestamptz, @@ -81,7 +86,8 @@ CREATE TABLE workspace_agents ( environment_variables jsonb, startup_script varchar(65534), instance_metadata jsonb, - resource_metadata jsonb + resource_metadata jsonb, + PRIMARY KEY (id) ); CREATE TYPE parameter_scope AS ENUM ( @@ -111,7 +117,7 @@ CREATE TYPE parameter_destination_scheme AS ENUM('none', 'environment_variable', -- a UI for users to enter values. -- Needs to be made consistent with the examples below. CREATE TABLE parameter_schemas ( - id uuid NOT NULL UNIQUE, + id uuid NOT NULL, created_at timestamptz NOT NULL, job_id uuid NOT NULL REFERENCES provisioner_jobs (id) ON DELETE CASCADE, name varchar(64) NOT NULL, @@ -131,26 +137,28 @@ CREATE TABLE parameter_schemas ( validation_condition varchar(512) NOT NULL, validation_type_system parameter_type_system NOT NULL, validation_value_type varchar(64) NOT NULL, + PRIMARY KEY (id), UNIQUE(job_id, name) ); -- Parameters are provided to jobs for provisioning and to workspaces. CREATE TABLE parameter_values ( - id uuid NOT NULL UNIQUE, + id uuid NOT NULL, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, scope parameter_scope NOT NULL, - scope_id text NOT NULL, + scope_id uuid NOT NULL, name varchar(64) NOT NULL, source_scheme parameter_source_scheme NOT NULL, source_value text NOT NULL, destination_scheme parameter_destination_scheme NOT NULL, + PRIMARY KEY (id), -- Prevents duplicates for parameters in the same scope. UNIQUE(scope_id, name) ); CREATE TABLE workspace_builds ( - id uuid NOT NULL UNIQUE, + id uuid NOT NULL, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, workspace_id uuid NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE, @@ -159,10 +167,11 @@ CREATE TABLE workspace_builds ( before_id uuid, after_id uuid, transition workspace_transition NOT NULL, - initiator varchar(255) NOT NULL, + initiator_id uuid NOT NULL, -- State stored by the provisioner provisioner_state bytea, -- Job ID of the action job_id uuid NOT NULL UNIQUE REFERENCES provisioner_jobs (id) ON DELETE CASCADE, + PRIMARY KEY (id), UNIQUE(workspace_id, name) ); diff --git a/coderd/database/models.go b/coderd/database/models.go index 8e4eecd5e4..7ecf5e7d0d 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -209,26 +209,6 @@ func (e *ProvisionerType) Scan(src interface{}) error { return nil } -type UserStatus string - -const ( - UserstatusActive UserStatus = "active" - UserstatusDormant UserStatus = "dormant" - UserstatusDecommissioned UserStatus = "decommissioned" -) - -func (e *UserStatus) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = UserStatus(s) - case string: - *e = UserStatus(s) - default: - return fmt.Errorf("unsupported scan type for UserStatus: %T", src) - } - return nil -} - type WorkspaceTransition string const ( @@ -252,7 +232,7 @@ func (e *WorkspaceTransition) Scan(src interface{}) error { type APIKey struct { ID string `db:"id" json:"id"` HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"` - UserID string `db:"user_id" json:"user_id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` Application bool `db:"application" json:"application"` Name string `db:"name" json:"name"` LastUsed time.Time `db:"last_used" json:"last_used"` @@ -270,7 +250,7 @@ type APIKey struct { type File struct { Hash string `db:"hash" json:"hash"` CreatedAt time.Time `db:"created_at" json:"created_at"` - CreatedBy string `db:"created_by" json:"created_by"` + CreatedBy uuid.UUID `db:"created_by" json:"created_by"` Mimetype string `db:"mimetype" json:"mimetype"` Data []byte `db:"data" json:"data"` } @@ -282,21 +262,16 @@ type License struct { } type Organization struct { - ID string `db:"id" json:"id"` - Name string `db:"name" json:"name"` - Description string `db:"description" json:"description"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - Default bool `db:"default" json:"default"` - AutoOffThreshold int64 `db:"auto_off_threshold" json:"auto_off_threshold"` - CpuProvisioningRate float32 `db:"cpu_provisioning_rate" json:"cpu_provisioning_rate"` - MemoryProvisioningRate float32 `db:"memory_provisioning_rate" json:"memory_provisioning_rate"` - WorkspaceAutoOff bool `db:"workspace_auto_off" json:"workspace_auto_off"` + ID uuid.UUID `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Description string `db:"description" json:"description"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` } type OrganizationMember struct { - OrganizationID string `db:"organization_id" json:"organization_id"` - UserID string `db:"user_id" json:"user_id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` Roles []string `db:"roles" json:"roles"` @@ -326,7 +301,7 @@ type ParameterValue struct { CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` Scope ParameterScope `db:"scope" json:"scope"` - ScopeID string `db:"scope_id" json:"scope_id"` + ScopeID uuid.UUID `db:"scope_id" json:"scope_id"` Name string `db:"name" json:"name"` SourceScheme ParameterSourceScheme `db:"source_scheme" json:"source_scheme"` SourceValue string `db:"source_value" json:"source_value"` @@ -337,7 +312,7 @@ type Project struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - OrganizationID string `db:"organization_id" json:"organization_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` Deleted bool `db:"deleted" json:"deleted"` Name string `db:"name" json:"name"` Provisioner ProvisionerType `db:"provisioner" json:"provisioner"` @@ -347,7 +322,7 @@ type Project struct { type ProjectVersion struct { ID uuid.UUID `db:"id" json:"id"` ProjectID uuid.NullUUID `db:"project_id" json:"project_id"` - OrganizationID string `db:"organization_id" json:"organization_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` Name string `db:"name" json:"name"` @@ -359,7 +334,7 @@ type ProvisionerDaemon struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt sql.NullTime `db:"updated_at" json:"updated_at"` - OrganizationID sql.NullString `db:"organization_id" json:"organization_id"` + OrganizationID uuid.NullUUID `db:"organization_id" json:"organization_id"` Name string `db:"name" json:"name"` Provisioners []ProvisionerType `db:"provisioners" json:"provisioners"` } @@ -372,8 +347,8 @@ type ProvisionerJob struct { CanceledAt sql.NullTime `db:"canceled_at" json:"canceled_at"` CompletedAt sql.NullTime `db:"completed_at" json:"completed_at"` Error sql.NullString `db:"error" json:"error"` - OrganizationID string `db:"organization_id" json:"organization_id"` - InitiatorID string `db:"initiator_id" json:"initiator_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"` Provisioner ProvisionerType `db:"provisioner" json:"provisioner"` StorageMethod ProvisionerStorageMethod `db:"storage_method" json:"storage_method"` StorageSource string `db:"storage_source" json:"storage_source"` @@ -393,32 +368,22 @@ type ProvisionerJobLog struct { } type User struct { - ID string `db:"id" json:"id"` - Email string `db:"email" json:"email"` - Name string `db:"name" json:"name"` - Revoked bool `db:"revoked" json:"revoked"` - LoginType LoginType `db:"login_type" json:"login_type"` - HashedPassword []byte `db:"hashed_password" json:"hashed_password"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - TemporaryPassword bool `db:"temporary_password" json:"temporary_password"` - AvatarHash string `db:"avatar_hash" json:"avatar_hash"` - SshKeyRegeneratedAt time.Time `db:"ssh_key_regenerated_at" json:"ssh_key_regenerated_at"` - Username string `db:"username" json:"username"` - DotfilesGitUri string `db:"dotfiles_git_uri" json:"dotfiles_git_uri"` - Roles []string `db:"roles" json:"roles"` - Status UserStatus `db:"status" json:"status"` - Relatime time.Time `db:"relatime" json:"relatime"` - GpgKeyRegeneratedAt time.Time `db:"gpg_key_regenerated_at" json:"gpg_key_regenerated_at"` - Decomissioned bool `db:"_decomissioned" json:"_decomissioned"` - Shell string `db:"shell" json:"shell"` + ID uuid.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Name string `db:"name" json:"name"` + Revoked bool `db:"revoked" json:"revoked"` + LoginType LoginType `db:"login_type" json:"login_type"` + HashedPassword []byte `db:"hashed_password" json:"hashed_password"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + Username string `db:"username" json:"username"` } type Workspace struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - OwnerID string `db:"owner_id" json:"owner_id"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` ProjectID uuid.UUID `db:"project_id" json:"project_id"` Deleted bool `db:"deleted" json:"deleted"` Name string `db:"name" json:"name"` @@ -450,7 +415,7 @@ type WorkspaceBuild struct { BeforeID uuid.NullUUID `db:"before_id" json:"before_id"` AfterID uuid.NullUUID `db:"after_id" json:"after_id"` Transition WorkspaceTransition `db:"transition" json:"transition"` - Initiator string `db:"initiator" json:"initiator"` + InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"` ProvisionerState []byte `db:"provisioner_state" json:"provisioner_state"` JobID uuid.UUID `db:"job_id" json:"job_id"` } diff --git a/coderd/database/postgres/postgres.go b/coderd/database/postgres/postgres.go index 9b3345bd1c..73306eb88b 100644 --- a/coderd/database/postgres/postgres.go +++ b/coderd/database/postgres/postgres.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "os" + "strconv" "sync" "time" @@ -31,15 +32,18 @@ func Open() (string, func(), error) { return "", nil, xerrors.Errorf("connect to ci postgres: %w", err) } defer db.Close() + dbName, err := cryptorand.StringCharset(cryptorand.Lower, 10) if err != nil { return "", nil, xerrors.Errorf("generate db name: %w", err) } + dbName = "ci" + dbName _, err = db.Exec("CREATE DATABASE " + dbName) if err != nil { return "", nil, xerrors.Errorf("create db: %w", err) } + return "postgres://postgres:postgres@127.0.0.1:5432/" + dbName + "?sslmode=disable", func() {}, nil } @@ -59,7 +63,7 @@ func Open() (string, func(), error) { port, err := getFreePort() if err != nil { openPortMutex.Unlock() - return "", nil, xerrors.Errorf("Unable to get free port: %w", err) + return "", nil, xerrors.Errorf("get free port: %w", err) } resource, err := pool.RunWithOptions(&dockertest.RunOptions{ @@ -80,7 +84,8 @@ func Open() (string, func(), error) { // https://github.com/moby/moby/issues/42442 // where the ipv4 and ipv6 ports might be _different_ and collide with other running docker containers. HostIP: "0.0.0.0", - HostPort: fmt.Sprintf("%d", port)}}, + HostPort: strconv.FormatInt(int64(port), 10), + }}, }, Mounts: []string{ // The postgres image has a VOLUME parameter in it's image. @@ -107,18 +112,23 @@ func Open() (string, func(), error) { // Docker should hard-kill the container after 120 seconds. err = resource.Expire(120) if err != nil { - return "", nil, xerrors.Errorf("could not expire resource: %w", err) + return "", nil, xerrors.Errorf("expire resource: %w", err) } pool.MaxWait = 120 * time.Second err = pool.Retry(func() error { db, err := sql.Open("postgres", dbURL) if err != nil { - return err + return xerrors.Errorf("open postgres: %w", err) } + defer db.Close() + err = db.Ping() - _ = db.Close() - return err + if err != nil { + return xerrors.Errorf("ping postgres: %w", err) + } + + return nil }) if err != nil { return "", nil, err diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 019cce2fdb..abaed68e80 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -13,10 +13,10 @@ type querier interface { DeleteParameterValueByID(ctx context.Context, id uuid.UUID) error GetAPIKeyByID(ctx context.Context, id string) (APIKey, error) GetFileByHash(ctx context.Context, hash string) (File, error) - GetOrganizationByID(ctx context.Context, id string) (Organization, error) + GetOrganizationByID(ctx context.Context, id uuid.UUID) (Organization, error) GetOrganizationByName(ctx context.Context, name string) (Organization, error) GetOrganizationMemberByUserID(ctx context.Context, arg GetOrganizationMemberByUserIDParams) (OrganizationMember, error) - GetOrganizationsByUserID(ctx context.Context, userID string) ([]Organization, error) + GetOrganizationsByUserID(ctx context.Context, userID uuid.UUID) ([]Organization, error) GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUID) ([]ParameterSchema, error) GetParameterValueByScopeAndName(ctx context.Context, arg GetParameterValueByScopeAndNameParams) (ParameterValue, error) GetParameterValuesByScope(ctx context.Context, arg GetParameterValuesByScopeParams) ([]ParameterValue, error) @@ -34,7 +34,7 @@ type querier interface { GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]ProvisionerJob, error) GetProvisionerLogsByIDBetween(ctx context.Context, arg GetProvisionerLogsByIDBetweenParams) ([]ProvisionerJobLog, error) GetUserByEmailOrUsername(ctx context.Context, arg GetUserByEmailOrUsernameParams) (User, error) - GetUserByID(ctx context.Context, id string) (User, error) + GetUserByID(ctx context.Context, id uuid.UUID) (User, error) GetUserCount(ctx context.Context) (int64, error) GetWorkspaceAgentByAuthToken(ctx context.Context, authToken uuid.UUID) (WorkspaceAgent, error) GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanceID string) (WorkspaceAgent, error) diff --git a/coderd/database/query.sql b/coderd/database/query.sql index 7bc2c5f324..bfe1dc5afa 100644 --- a/coderd/database/query.sql +++ b/coderd/database/query.sql @@ -683,7 +683,7 @@ INSERT INTO before_id, name, transition, - initiator, + initiator_id, job_id, provisioner_state ) diff --git a/coderd/database/query.sql.go b/coderd/database/query.sql.go index 38da2dddba..f0572bc561 100644 --- a/coderd/database/query.sql.go +++ b/coderd/database/query.sql.go @@ -148,14 +148,14 @@ func (q *sqlQuerier) GetFileByHash(ctx context.Context, hash string) (File, erro const getOrganizationByID = `-- name: GetOrganizationByID :one SELECT - id, name, description, created_at, updated_at, "default", auto_off_threshold, cpu_provisioning_rate, memory_provisioning_rate, workspace_auto_off + id, name, description, created_at, updated_at FROM organizations WHERE id = $1 ` -func (q *sqlQuerier) GetOrganizationByID(ctx context.Context, id string) (Organization, error) { +func (q *sqlQuerier) GetOrganizationByID(ctx context.Context, id uuid.UUID) (Organization, error) { row := q.db.QueryRowContext(ctx, getOrganizationByID, id) var i Organization err := row.Scan( @@ -164,18 +164,13 @@ func (q *sqlQuerier) GetOrganizationByID(ctx context.Context, id string) (Organi &i.Description, &i.CreatedAt, &i.UpdatedAt, - &i.Default, - &i.AutoOffThreshold, - &i.CpuProvisioningRate, - &i.MemoryProvisioningRate, - &i.WorkspaceAutoOff, ) return i, err } const getOrganizationByName = `-- name: GetOrganizationByName :one SELECT - id, name, description, created_at, updated_at, "default", auto_off_threshold, cpu_provisioning_rate, memory_provisioning_rate, workspace_auto_off + id, name, description, created_at, updated_at FROM organizations WHERE @@ -193,18 +188,13 @@ func (q *sqlQuerier) GetOrganizationByName(ctx context.Context, name string) (Or &i.Description, &i.CreatedAt, &i.UpdatedAt, - &i.Default, - &i.AutoOffThreshold, - &i.CpuProvisioningRate, - &i.MemoryProvisioningRate, - &i.WorkspaceAutoOff, ) return i, err } const getOrganizationMemberByUserID = `-- name: GetOrganizationMemberByUserID :one SELECT - organization_id, user_id, created_at, updated_at, roles + user_id, organization_id, created_at, updated_at, roles FROM organization_members WHERE @@ -215,16 +205,16 @@ LIMIT ` type GetOrganizationMemberByUserIDParams struct { - OrganizationID string `db:"organization_id" json:"organization_id"` - UserID string `db:"user_id" json:"user_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` } func (q *sqlQuerier) GetOrganizationMemberByUserID(ctx context.Context, arg GetOrganizationMemberByUserIDParams) (OrganizationMember, error) { row := q.db.QueryRowContext(ctx, getOrganizationMemberByUserID, arg.OrganizationID, arg.UserID) var i OrganizationMember err := row.Scan( - &i.OrganizationID, &i.UserID, + &i.OrganizationID, &i.CreatedAt, &i.UpdatedAt, pq.Array(&i.Roles), @@ -234,7 +224,7 @@ func (q *sqlQuerier) GetOrganizationMemberByUserID(ctx context.Context, arg GetO const getOrganizationsByUserID = `-- name: GetOrganizationsByUserID :many SELECT - id, name, description, created_at, updated_at, "default", auto_off_threshold, cpu_provisioning_rate, memory_provisioning_rate, workspace_auto_off + id, name, description, created_at, updated_at FROM organizations WHERE @@ -248,7 +238,7 @@ WHERE ) ` -func (q *sqlQuerier) GetOrganizationsByUserID(ctx context.Context, userID string) ([]Organization, error) { +func (q *sqlQuerier) GetOrganizationsByUserID(ctx context.Context, userID uuid.UUID) ([]Organization, error) { rows, err := q.db.QueryContext(ctx, getOrganizationsByUserID, userID) if err != nil { return nil, err @@ -263,11 +253,6 @@ func (q *sqlQuerier) GetOrganizationsByUserID(ctx context.Context, userID string &i.Description, &i.CreatedAt, &i.UpdatedAt, - &i.Default, - &i.AutoOffThreshold, - &i.CpuProvisioningRate, - &i.MemoryProvisioningRate, - &i.WorkspaceAutoOff, ); err != nil { return nil, err } @@ -346,7 +331,7 @@ LIMIT type GetParameterValueByScopeAndNameParams struct { Scope ParameterScope `db:"scope" json:"scope"` - ScopeID string `db:"scope_id" json:"scope_id"` + ScopeID uuid.UUID `db:"scope_id" json:"scope_id"` Name string `db:"name" json:"name"` } @@ -379,7 +364,7 @@ WHERE type GetParameterValuesByScopeParams struct { Scope ParameterScope `db:"scope" json:"scope"` - ScopeID string `db:"scope_id" json:"scope_id"` + ScopeID uuid.UUID `db:"scope_id" json:"scope_id"` } func (q *sqlQuerier) GetParameterValuesByScope(ctx context.Context, arg GetParameterValuesByScopeParams) ([]ParameterValue, error) { @@ -456,9 +441,9 @@ LIMIT ` type GetProjectByOrganizationAndNameParams struct { - OrganizationID string `db:"organization_id" json:"organization_id"` - Deleted bool `db:"deleted" json:"deleted"` - Name string `db:"name" json:"name"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + Deleted bool `db:"deleted" json:"deleted"` + Name string `db:"name" json:"name"` } func (q *sqlQuerier) GetProjectByOrganizationAndName(ctx context.Context, arg GetProjectByOrganizationAndNameParams) (Project, error) { @@ -651,8 +636,8 @@ WHERE ` type GetProjectsByOrganizationParams struct { - OrganizationID string `db:"organization_id" json:"organization_id"` - Deleted bool `db:"deleted" json:"deleted"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + Deleted bool `db:"deleted" json:"deleted"` } func (q *sqlQuerier) GetProjectsByOrganization(ctx context.Context, arg GetProjectsByOrganizationParams) ([]Project, error) { @@ -881,7 +866,7 @@ func (q *sqlQuerier) GetProvisionerLogsByIDBetween(ctx context.Context, arg GetP const getUserByEmailOrUsername = `-- name: GetUserByEmailOrUsername :one SELECT - id, email, name, revoked, login_type, hashed_password, created_at, updated_at, temporary_password, avatar_hash, ssh_key_regenerated_at, username, dotfiles_git_uri, roles, status, relatime, gpg_key_regenerated_at, _decomissioned, shell + id, email, name, revoked, login_type, hashed_password, created_at, updated_at, username FROM users WHERE @@ -908,24 +893,14 @@ func (q *sqlQuerier) GetUserByEmailOrUsername(ctx context.Context, arg GetUserBy &i.HashedPassword, &i.CreatedAt, &i.UpdatedAt, - &i.TemporaryPassword, - &i.AvatarHash, - &i.SshKeyRegeneratedAt, &i.Username, - &i.DotfilesGitUri, - pq.Array(&i.Roles), - &i.Status, - &i.Relatime, - &i.GpgKeyRegeneratedAt, - &i.Decomissioned, - &i.Shell, ) return i, err } const getUserByID = `-- name: GetUserByID :one SELECT - id, email, name, revoked, login_type, hashed_password, created_at, updated_at, temporary_password, avatar_hash, ssh_key_regenerated_at, username, dotfiles_git_uri, roles, status, relatime, gpg_key_regenerated_at, _decomissioned, shell + id, email, name, revoked, login_type, hashed_password, created_at, updated_at, username FROM users WHERE @@ -934,7 +909,7 @@ LIMIT 1 ` -func (q *sqlQuerier) GetUserByID(ctx context.Context, id string) (User, error) { +func (q *sqlQuerier) GetUserByID(ctx context.Context, id uuid.UUID) (User, error) { row := q.db.QueryRowContext(ctx, getUserByID, id) var i User err := row.Scan( @@ -946,17 +921,7 @@ func (q *sqlQuerier) GetUserByID(ctx context.Context, id string) (User, error) { &i.HashedPassword, &i.CreatedAt, &i.UpdatedAt, - &i.TemporaryPassword, - &i.AvatarHash, - &i.SshKeyRegeneratedAt, &i.Username, - &i.DotfilesGitUri, - pq.Array(&i.Roles), - &i.Status, - &i.Relatime, - &i.GpgKeyRegeneratedAt, - &i.Decomissioned, - &i.Shell, ) return i, err } @@ -1071,7 +1036,7 @@ func (q *sqlQuerier) GetWorkspaceAgentByResourceID(ctx context.Context, resource const getWorkspaceBuildByID = `-- name: GetWorkspaceBuildByID :one SELECT - id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator, provisioner_state, job_id + id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator_id, provisioner_state, job_id FROM workspace_builds WHERE @@ -1093,7 +1058,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (W &i.BeforeID, &i.AfterID, &i.Transition, - &i.Initiator, + &i.InitiatorID, &i.ProvisionerState, &i.JobID, ) @@ -1102,7 +1067,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (W const getWorkspaceBuildByJobID = `-- name: GetWorkspaceBuildByJobID :one SELECT - id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator, provisioner_state, job_id + id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator_id, provisioner_state, job_id FROM workspace_builds WHERE @@ -1124,7 +1089,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UU &i.BeforeID, &i.AfterID, &i.Transition, - &i.Initiator, + &i.InitiatorID, &i.ProvisionerState, &i.JobID, ) @@ -1133,7 +1098,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UU const getWorkspaceBuildByWorkspaceID = `-- name: GetWorkspaceBuildByWorkspaceID :many SELECT - id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator, provisioner_state, job_id + id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator_id, provisioner_state, job_id FROM workspace_builds WHERE @@ -1159,7 +1124,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceID(ctx context.Context, workspa &i.BeforeID, &i.AfterID, &i.Transition, - &i.Initiator, + &i.InitiatorID, &i.ProvisionerState, &i.JobID, ); err != nil { @@ -1178,7 +1143,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceID(ctx context.Context, workspa const getWorkspaceBuildByWorkspaceIDAndName = `-- name: GetWorkspaceBuildByWorkspaceIDAndName :one SELECT - id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator, provisioner_state, job_id + id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator_id, provisioner_state, job_id FROM workspace_builds WHERE @@ -1204,7 +1169,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDAndName(ctx context.Context, &i.BeforeID, &i.AfterID, &i.Transition, - &i.Initiator, + &i.InitiatorID, &i.ProvisionerState, &i.JobID, ) @@ -1213,7 +1178,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDAndName(ctx context.Context, const getWorkspaceBuildByWorkspaceIDWithoutAfter = `-- name: GetWorkspaceBuildByWorkspaceIDWithoutAfter :one SELECT - id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator, provisioner_state, job_id + id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator_id, provisioner_state, job_id FROM workspace_builds WHERE @@ -1236,7 +1201,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDWithoutAfter(ctx context.Cont &i.BeforeID, &i.AfterID, &i.Transition, - &i.Initiator, + &i.InitiatorID, &i.ProvisionerState, &i.JobID, ) @@ -1245,7 +1210,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDWithoutAfter(ctx context.Cont const getWorkspaceBuildsByWorkspaceIDsWithoutAfter = `-- name: GetWorkspaceBuildsByWorkspaceIDsWithoutAfter :many SELECT - id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator, provisioner_state, job_id + id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator_id, provisioner_state, job_id FROM workspace_builds WHERE @@ -1272,7 +1237,7 @@ func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceIDsWithoutAfter(ctx context.Co &i.BeforeID, &i.AfterID, &i.Transition, - &i.Initiator, + &i.InitiatorID, &i.ProvisionerState, &i.JobID, ); err != nil { @@ -1327,9 +1292,9 @@ WHERE ` type GetWorkspaceByUserIDAndNameParams struct { - OwnerID string `db:"owner_id" json:"owner_id"` - Deleted bool `db:"deleted" json:"deleted"` - Name string `db:"name" json:"name"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` + Deleted bool `db:"deleted" json:"deleted"` + Name string `db:"name" json:"name"` } func (q *sqlQuerier) GetWorkspaceByUserIDAndName(ctx context.Context, arg GetWorkspaceByUserIDAndNameParams) (Workspace, error) { @@ -1511,8 +1476,8 @@ WHERE ` type GetWorkspacesByUserIDParams struct { - OwnerID string `db:"owner_id" json:"owner_id"` - Deleted bool `db:"deleted" json:"deleted"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` + Deleted bool `db:"deleted" json:"deleted"` } func (q *sqlQuerier) GetWorkspacesByUserID(ctx context.Context, arg GetWorkspacesByUserIDParams) ([]Workspace, error) { @@ -1588,7 +1553,7 @@ VALUES type InsertAPIKeyParams struct { ID string `db:"id" json:"id"` HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"` - UserID string `db:"user_id" json:"user_id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` Application bool `db:"application" json:"application"` Name string `db:"name" json:"name"` LastUsed time.Time `db:"last_used" json:"last_used"` @@ -1652,7 +1617,7 @@ VALUES type InsertFileParams struct { Hash string `db:"hash" json:"hash"` CreatedAt time.Time `db:"created_at" json:"created_at"` - CreatedBy string `db:"created_by" json:"created_by"` + CreatedBy uuid.UUID `db:"created_by" json:"created_by"` Mimetype string `db:"mimetype" json:"mimetype"` Data []byte `db:"data" json:"data"` } @@ -1680,11 +1645,11 @@ const insertOrganization = `-- name: InsertOrganization :one INSERT INTO organizations (id, name, description, created_at, updated_at) VALUES - ($1, $2, $3, $4, $5) RETURNING id, name, description, created_at, updated_at, "default", auto_off_threshold, cpu_provisioning_rate, memory_provisioning_rate, workspace_auto_off + ($1, $2, $3, $4, $5) RETURNING id, name, description, created_at, updated_at ` type InsertOrganizationParams struct { - ID string `db:"id" json:"id"` + ID uuid.UUID `db:"id" json:"id"` Name string `db:"name" json:"name"` Description string `db:"description" json:"description"` CreatedAt time.Time `db:"created_at" json:"created_at"` @@ -1706,11 +1671,6 @@ func (q *sqlQuerier) InsertOrganization(ctx context.Context, arg InsertOrganizat &i.Description, &i.CreatedAt, &i.UpdatedAt, - &i.Default, - &i.AutoOffThreshold, - &i.CpuProvisioningRate, - &i.MemoryProvisioningRate, - &i.WorkspaceAutoOff, ) return i, err } @@ -1725,12 +1685,12 @@ INSERT INTO roles ) VALUES - ($1, $2, $3, $4, $5) RETURNING organization_id, user_id, created_at, updated_at, roles + ($1, $2, $3, $4, $5) RETURNING user_id, organization_id, created_at, updated_at, roles ` type InsertOrganizationMemberParams struct { - OrganizationID string `db:"organization_id" json:"organization_id"` - UserID string `db:"user_id" json:"user_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` Roles []string `db:"roles" json:"roles"` @@ -1746,8 +1706,8 @@ func (q *sqlQuerier) InsertOrganizationMember(ctx context.Context, arg InsertOrg ) var i OrganizationMember err := row.Scan( - &i.OrganizationID, &i.UserID, + &i.OrganizationID, &i.CreatedAt, &i.UpdatedAt, pq.Array(&i.Roles), @@ -1879,7 +1839,7 @@ type InsertParameterValueParams struct { CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` Scope ParameterScope `db:"scope" json:"scope"` - ScopeID string `db:"scope_id" json:"scope_id"` + ScopeID uuid.UUID `db:"scope_id" json:"scope_id"` SourceScheme ParameterSourceScheme `db:"source_scheme" json:"source_scheme"` SourceValue string `db:"source_value" json:"source_value"` DestinationScheme ParameterDestinationScheme `db:"destination_scheme" json:"destination_scheme"` @@ -1931,7 +1891,7 @@ type InsertProjectParams struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - OrganizationID string `db:"organization_id" json:"organization_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` Name string `db:"name" json:"name"` Provisioner ProvisionerType `db:"provisioner" json:"provisioner"` ActiveVersionID uuid.UUID `db:"active_version_id" json:"active_version_id"` @@ -1980,7 +1940,7 @@ VALUES type InsertProjectVersionParams struct { ID uuid.UUID `db:"id" json:"id"` ProjectID uuid.NullUUID `db:"project_id" json:"project_id"` - OrganizationID string `db:"organization_id" json:"organization_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` Name string `db:"name" json:"name"` @@ -2023,7 +1983,7 @@ VALUES type InsertProvisionerDaemonParams struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` - OrganizationID sql.NullString `db:"organization_id" json:"organization_id"` + OrganizationID uuid.NullUUID `db:"organization_id" json:"organization_id"` Name string `db:"name" json:"name"` Provisioners []ProvisionerType `db:"provisioners" json:"provisioners"` } @@ -2070,8 +2030,8 @@ type InsertProvisionerJobParams struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - OrganizationID string `db:"organization_id" json:"organization_id"` - InitiatorID string `db:"initiator_id" json:"initiator_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"` Provisioner ProvisionerType `db:"provisioner" json:"provisioner"` StorageMethod ProvisionerStorageMethod `db:"storage_method" json:"storage_method"` StorageSource string `db:"storage_source" json:"storage_source"` @@ -2189,11 +2149,11 @@ INSERT INTO username ) VALUES - ($1, $2, $3, $4, false, $5, $6, $7, $8) RETURNING id, email, name, revoked, login_type, hashed_password, created_at, updated_at, temporary_password, avatar_hash, ssh_key_regenerated_at, username, dotfiles_git_uri, roles, status, relatime, gpg_key_regenerated_at, _decomissioned, shell + ($1, $2, $3, $4, false, $5, $6, $7, $8) RETURNING id, email, name, revoked, login_type, hashed_password, created_at, updated_at, username ` type InsertUserParams struct { - ID string `db:"id" json:"id"` + ID uuid.UUID `db:"id" json:"id"` Email string `db:"email" json:"email"` Name string `db:"name" json:"name"` LoginType LoginType `db:"login_type" json:"login_type"` @@ -2224,17 +2184,7 @@ func (q *sqlQuerier) InsertUser(ctx context.Context, arg InsertUserParams) (User &i.HashedPassword, &i.CreatedAt, &i.UpdatedAt, - &i.TemporaryPassword, - &i.AvatarHash, - &i.SshKeyRegeneratedAt, &i.Username, - &i.DotfilesGitUri, - pq.Array(&i.Roles), - &i.Status, - &i.Relatime, - &i.GpgKeyRegeneratedAt, - &i.Decomissioned, - &i.Shell, ) return i, err } @@ -2257,7 +2207,7 @@ type InsertWorkspaceParams struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - OwnerID string `db:"owner_id" json:"owner_id"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` ProjectID uuid.UUID `db:"project_id" json:"project_id"` Name string `db:"name" json:"name"` } @@ -2358,12 +2308,12 @@ INSERT INTO before_id, name, transition, - initiator, + initiator_id, job_id, provisioner_state ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator, provisioner_state, job_id + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id, created_at, updated_at, workspace_id, project_version_id, name, before_id, after_id, transition, initiator_id, provisioner_state, job_id ` type InsertWorkspaceBuildParams struct { @@ -2375,7 +2325,7 @@ type InsertWorkspaceBuildParams struct { BeforeID uuid.NullUUID `db:"before_id" json:"before_id"` Name string `db:"name" json:"name"` Transition WorkspaceTransition `db:"transition" json:"transition"` - Initiator string `db:"initiator" json:"initiator"` + InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"` JobID uuid.UUID `db:"job_id" json:"job_id"` ProvisionerState []byte `db:"provisioner_state" json:"provisioner_state"` } @@ -2390,7 +2340,7 @@ func (q *sqlQuerier) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspa arg.BeforeID, arg.Name, arg.Transition, - arg.Initiator, + arg.InitiatorID, arg.JobID, arg.ProvisionerState, ) @@ -2405,7 +2355,7 @@ func (q *sqlQuerier) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspa &i.BeforeID, &i.AfterID, &i.Transition, - &i.Initiator, + &i.InitiatorID, &i.ProvisionerState, &i.JobID, ) diff --git a/coderd/httpmw/httpmw.go b/coderd/httpmw/httpmw.go index 0c215edab8..ce0a7955bb 100644 --- a/coderd/httpmw/httpmw.go +++ b/coderd/httpmw/httpmw.go @@ -19,6 +19,13 @@ func parseUUID(rw http.ResponseWriter, r *http.Request, param string) (uuid.UUID }) return uuid.UUID{}, false } + + // Automatically set uuid.Nil to the acting users id. + if param == UserKey && rawID == "me" { + key := APIKey(r) + return key.UserID, true + } + parsed, err := uuid.Parse(rawID) if err != nil { httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ @@ -26,5 +33,6 @@ func parseUUID(rw http.ResponseWriter, r *http.Request, param string) (uuid.UUID }) return uuid.UUID{}, false } + return parsed, true } diff --git a/coderd/httpmw/organizationparam.go b/coderd/httpmw/organizationparam.go index b59855f864..d66e492366 100644 --- a/coderd/httpmw/organizationparam.go +++ b/coderd/httpmw/organizationparam.go @@ -7,8 +7,6 @@ import ( "fmt" "net/http" - "github.com/go-chi/chi/v5" - "github.com/coder/coder/coderd/database" "github.com/coder/coder/coderd/httpapi" ) @@ -40,17 +38,15 @@ func OrganizationMemberParam(r *http.Request) database.OrganizationMember { func ExtractOrganizationParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - organizationID := chi.URLParam(r, "organization") - if organizationID == "" { - httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ - Message: "organization must be provided", - }) + orgID, ok := parseUUID(rw, r, "organization") + if !ok { return } - organization, err := db.GetOrganizationByID(r.Context(), organizationID) + + organization, err := db.GetOrganizationByID(r.Context(), orgID) if errors.Is(err, sql.ErrNoRows) { httpapi.Write(rw, http.StatusNotFound, httpapi.Response{ - Message: fmt.Sprintf("organization %q does not exist", organizationID), + Message: fmt.Sprintf("organization %q does not exist", orgID), }) return } @@ -60,6 +56,7 @@ func ExtractOrganizationParam(db database.Store) func(http.Handler) http.Handler }) return } + apiKey := APIKey(r) organizationMember, err := db.GetOrganizationMemberByUserID(r.Context(), database.GetOrganizationMemberByUserIDParams{ OrganizationID: organization.ID, diff --git a/coderd/httpmw/organizationparam_test.go b/coderd/httpmw/organizationparam_test.go index c21ddb4df5..02887260fe 100644 --- a/coderd/httpmw/organizationparam_test.go +++ b/coderd/httpmw/organizationparam_test.go @@ -32,10 +32,11 @@ func TestOrganizationParam(t *testing.T) { Name: httpmw.AuthCookie, Value: fmt.Sprintf("%s-%s", id, secret), }) - userID, err := cryptorand.String(16) - require.NoError(t, err) + + userID := uuid.New() username, err := cryptorand.String(8) require.NoError(t, err) + user, err := db.InsertUser(r.Context(), database.InsertUserParams{ ID: userID, Email: "testaccount@coder.com", @@ -86,7 +87,7 @@ func TestOrganizationParam(t *testing.T) { r, _ = setupAuthentication(db) rtr = chi.NewRouter() ) - chi.RouteContext(r.Context()).URLParams.Add("organization", "nothin") + chi.RouteContext(r.Context()).URLParams.Add("organization", uuid.NewString()) rtr.Use( httpmw.ExtractAPIKey(db, nil), httpmw.ExtractOrganizationParam(db), @@ -98,6 +99,26 @@ func TestOrganizationParam(t *testing.T) { require.Equal(t, http.StatusNotFound, res.StatusCode) }) + t.Run("InvalidUUID", func(t *testing.T) { + t.Parallel() + var ( + db = databasefake.New() + rw = httptest.NewRecorder() + r, _ = setupAuthentication(db) + rtr = chi.NewRouter() + ) + chi.RouteContext(r.Context()).URLParams.Add("organization", "not-a-uuid") + rtr.Use( + httpmw.ExtractAPIKey(db, nil), + httpmw.ExtractOrganizationParam(db), + ) + rtr.Get("/", nil) + rtr.ServeHTTP(rw, r) + res := rw.Result() + defer res.Body.Close() + require.Equal(t, http.StatusBadRequest, res.StatusCode) + }) + t.Run("NotInOrganization", func(t *testing.T) { t.Parallel() var ( @@ -107,13 +128,13 @@ func TestOrganizationParam(t *testing.T) { rtr = chi.NewRouter() ) organization, err := db.InsertOrganization(r.Context(), database.InsertOrganizationParams{ - ID: uuid.NewString(), + ID: uuid.New(), Name: "test", CreatedAt: database.Now(), UpdatedAt: database.Now(), }) require.NoError(t, err) - chi.RouteContext(r.Context()).URLParams.Add("organization", organization.ID) + chi.RouteContext(r.Context()).URLParams.Add("organization", organization.ID.String()) rtr.Use( httpmw.ExtractAPIKey(db, nil), httpmw.ExtractOrganizationParam(db), @@ -134,7 +155,7 @@ func TestOrganizationParam(t *testing.T) { rtr = chi.NewRouter() ) organization, err := db.InsertOrganization(r.Context(), database.InsertOrganizationParams{ - ID: uuid.NewString(), + ID: uuid.New(), Name: "test", CreatedAt: database.Now(), UpdatedAt: database.Now(), @@ -147,7 +168,7 @@ func TestOrganizationParam(t *testing.T) { UpdatedAt: database.Now(), }) require.NoError(t, err) - chi.RouteContext(r.Context()).URLParams.Add("organization", organization.ID) + chi.RouteContext(r.Context()).URLParams.Add("organization", organization.ID.String()) rtr.Use( httpmw.ExtractAPIKey(db, nil), httpmw.ExtractOrganizationParam(db), diff --git a/coderd/httpmw/projectparam.go b/coderd/httpmw/projectparam.go index 9239dfb996..a43e0b72ea 100644 --- a/coderd/httpmw/projectparam.go +++ b/coderd/httpmw/projectparam.go @@ -46,7 +46,7 @@ func ExtractProjectParam(db database.Store) func(http.Handler) http.Handler { } ctx := context.WithValue(r.Context(), projectParamContextKey{}, project) - chi.RouteContext(ctx).URLParams.Add("organization", project.OrganizationID) + chi.RouteContext(ctx).URLParams.Add("organization", project.OrganizationID.String()) next.ServeHTTP(rw, r.WithContext(ctx)) }) } diff --git a/coderd/httpmw/projectparam_test.go b/coderd/httpmw/projectparam_test.go index 7e84d46211..a1fc30010c 100644 --- a/coderd/httpmw/projectparam_test.go +++ b/coderd/httpmw/projectparam_test.go @@ -32,8 +32,8 @@ func TestProjectParam(t *testing.T) { Name: httpmw.AuthCookie, Value: fmt.Sprintf("%s-%s", id, secret), }) - userID, err := cryptorand.String(16) - require.NoError(t, err) + + userID := uuid.New() username, err := cryptorand.String(8) require.NoError(t, err) user, err := db.InsertUser(r.Context(), database.InsertUserParams{ @@ -47,6 +47,7 @@ func TestProjectParam(t *testing.T) { UpdatedAt: database.Now(), }) require.NoError(t, err) + _, err = db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, UserID: user.ID, @@ -55,8 +56,8 @@ func TestProjectParam(t *testing.T) { ExpiresAt: database.Now().Add(time.Minute), }) require.NoError(t, err) - orgID, err := cryptorand.String(16) - require.NoError(t, err) + + orgID := uuid.New() organization, err := db.InsertOrganization(r.Context(), database.InsertOrganizationParams{ ID: orgID, Name: "banana", @@ -65,6 +66,7 @@ func TestProjectParam(t *testing.T) { UpdatedAt: database.Now(), }) require.NoError(t, err) + _, err = db.InsertOrganizationMember(r.Context(), database.InsertOrganizationMemberParams{ OrganizationID: orgID, UserID: user.ID, diff --git a/coderd/httpmw/projectversionparam.go b/coderd/httpmw/projectversionparam.go index e2c00d37ac..a21819996e 100644 --- a/coderd/httpmw/projectversionparam.go +++ b/coderd/httpmw/projectversionparam.go @@ -47,7 +47,7 @@ func ExtractProjectVersionParam(db database.Store) func(http.Handler) http.Handl } ctx := context.WithValue(r.Context(), projectVersionParamContextKey{}, projectVersion) - chi.RouteContext(ctx).URLParams.Add("organization", projectVersion.OrganizationID) + chi.RouteContext(ctx).URLParams.Add("organization", projectVersion.OrganizationID.String()) next.ServeHTTP(rw, r.WithContext(ctx)) }) } diff --git a/coderd/httpmw/projectversionparam_test.go b/coderd/httpmw/projectversionparam_test.go index bf6c683c6a..aab718a7ff 100644 --- a/coderd/httpmw/projectversionparam_test.go +++ b/coderd/httpmw/projectversionparam_test.go @@ -32,8 +32,8 @@ func TestProjectVersionParam(t *testing.T) { Name: httpmw.AuthCookie, Value: fmt.Sprintf("%s-%s", id, secret), }) - userID, err := cryptorand.String(16) - require.NoError(t, err) + + userID := uuid.New() username, err := cryptorand.String(8) require.NoError(t, err) user, err := db.InsertUser(r.Context(), database.InsertUserParams{ @@ -47,6 +47,7 @@ func TestProjectVersionParam(t *testing.T) { UpdatedAt: database.Now(), }) require.NoError(t, err) + _, err = db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, UserID: user.ID, @@ -55,8 +56,8 @@ func TestProjectVersionParam(t *testing.T) { ExpiresAt: database.Now().Add(time.Minute), }) require.NoError(t, err) - orgID, err := cryptorand.String(16) - require.NoError(t, err) + + orgID := uuid.New() organization, err := db.InsertOrganization(r.Context(), database.InsertOrganizationParams{ ID: orgID, Name: "banana", @@ -65,6 +66,7 @@ func TestProjectVersionParam(t *testing.T) { UpdatedAt: database.Now(), }) require.NoError(t, err) + _, err = db.InsertOrganizationMember(r.Context(), database.InsertOrganizationMemberParams{ OrganizationID: orgID, UserID: user.ID, @@ -72,6 +74,7 @@ func TestProjectVersionParam(t *testing.T) { UpdatedAt: database.Now(), }) require.NoError(t, err) + project, err := db.InsertProject(context.Background(), database.InsertProjectParams{ ID: uuid.New(), OrganizationID: organization.ID, diff --git a/coderd/httpmw/userparam.go b/coderd/httpmw/userparam.go index 22fd223f3e..965e545cb0 100644 --- a/coderd/httpmw/userparam.go +++ b/coderd/httpmw/userparam.go @@ -5,12 +5,12 @@ import ( "fmt" "net/http" - "github.com/go-chi/chi/v5" - "github.com/coder/coder/coderd/database" "github.com/coder/coder/coderd/httpapi" ) +const UserKey = "user" + type userParamContextKey struct{} // UserParam returns the user from the ExtractUserParam handler. @@ -26,21 +26,20 @@ func UserParam(r *http.Request) database.User { func ExtractUserParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - userID := chi.URLParam(r, "user") - if userID == "" { - httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ - Message: "user id or name must be provided", - }) + userID, ok := parseUUID(rw, r, UserKey) + if !ok { return } + apiKey := APIKey(r) - if apiKey.UserID != userID && userID != "me" { + if apiKey.UserID != userID { httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ Message: "getting non-personal users isn't supported yet", }) return } - user, err := db.GetUserByID(r.Context(), apiKey.UserID) + + user, err := db.GetUserByID(r.Context(), userID) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: fmt.Sprintf("get user: %s", err.Error()), diff --git a/coderd/httpmw/userparam_test.go b/coderd/httpmw/userparam_test.go index 99fd0f0407..6a7ccd6928 100644 --- a/coderd/httpmw/userparam_test.go +++ b/coderd/httpmw/userparam_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/go-chi/chi/v5" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/coder/coder/coderd/database" @@ -32,18 +33,20 @@ func TestUserParam(t *testing.T) { Value: fmt.Sprintf("%s-%s", id, secret), }) - _, err := db.InsertUser(r.Context(), database.InsertUserParams{ - ID: "bananas", + user, err := db.InsertUser(r.Context(), database.InsertUserParams{ + ID: uuid.New(), }) require.NoError(t, err) + _, err = db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, - UserID: "bananas", + UserID: user.ID, HashedSecret: hashed[:], LastUsed: database.Now(), ExpiresAt: database.Now().Add(time.Minute), }) require.NoError(t, err) + return db, rw, r } @@ -82,7 +85,7 @@ func TestUserParam(t *testing.T) { require.Equal(t, http.StatusBadRequest, res.StatusCode) }) - t.Run("Me", func(t *testing.T) { + t.Run("me", func(t *testing.T) { t.Parallel() db, rw, r := setup(t) diff --git a/coderd/httpmw/workspacebuildparam_test.go b/coderd/httpmw/workspacebuildparam_test.go index d4a7ae1e59..ad9b6c63ed 100644 --- a/coderd/httpmw/workspacebuildparam_test.go +++ b/coderd/httpmw/workspacebuildparam_test.go @@ -32,8 +32,8 @@ func TestWorkspaceBuildParam(t *testing.T) { Name: httpmw.AuthCookie, Value: fmt.Sprintf("%s-%s", id, secret), }) - userID, err := cryptorand.String(16) - require.NoError(t, err) + + userID := uuid.New() username, err := cryptorand.String(8) require.NoError(t, err) user, err := db.InsertUser(r.Context(), database.InsertUserParams{ @@ -47,6 +47,7 @@ func TestWorkspaceBuildParam(t *testing.T) { UpdatedAt: database.Now(), }) require.NoError(t, err) + _, err = db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, UserID: user.ID, @@ -55,6 +56,7 @@ func TestWorkspaceBuildParam(t *testing.T) { ExpiresAt: database.Now().Add(time.Minute), }) require.NoError(t, err) + workspace, err := db.InsertWorkspace(context.Background(), database.InsertWorkspaceParams{ ID: uuid.New(), ProjectID: uuid.New(), @@ -64,7 +66,7 @@ func TestWorkspaceBuildParam(t *testing.T) { require.NoError(t, err) ctx := chi.NewRouteContext() - ctx.URLParams.Add("user", userID) + ctx.URLParams.Add("user", userID.String()) ctx.URLParams.Add("workspace", workspace.Name) r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, ctx)) return r, workspace diff --git a/coderd/httpmw/workspaceparam_test.go b/coderd/httpmw/workspaceparam_test.go index 3c528ae44e..5c169a0d10 100644 --- a/coderd/httpmw/workspaceparam_test.go +++ b/coderd/httpmw/workspaceparam_test.go @@ -32,8 +32,8 @@ func TestWorkspaceParam(t *testing.T) { Name: httpmw.AuthCookie, Value: fmt.Sprintf("%s-%s", id, secret), }) - userID, err := cryptorand.String(16) - require.NoError(t, err) + + userID := uuid.New() username, err := cryptorand.String(8) require.NoError(t, err) user, err := db.InsertUser(r.Context(), database.InsertUserParams{ @@ -47,6 +47,7 @@ func TestWorkspaceParam(t *testing.T) { UpdatedAt: database.Now(), }) require.NoError(t, err) + _, err = db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ ID: id, UserID: user.ID, @@ -105,7 +106,7 @@ func TestWorkspaceParam(t *testing.T) { r, _ := setup(db) workspace, err := db.InsertWorkspace(context.Background(), database.InsertWorkspaceParams{ ID: uuid.New(), - OwnerID: "not-me", + OwnerID: uuid.New(), Name: "hello", }) require.NoError(t, err) diff --git a/coderd/organizations.go b/coderd/organizations.go index 8dde91903b..305b132317 100644 --- a/coderd/organizations.go +++ b/coderd/organizations.go @@ -91,7 +91,7 @@ func (api *api) postProjectVersionsByOrganization(rw http.ResponseWriter, r *htt CreatedAt: database.Now(), UpdatedAt: database.Now(), Scope: database.ParameterScopeImportJob, - ScopeID: jobID.String(), + ScopeID: jobID, SourceScheme: parameterValue.SourceScheme, SourceValue: parameterValue.SourceValue, DestinationScheme: parameterValue.DestinationScheme, @@ -200,10 +200,11 @@ func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Reque var project codersdk.Project err = api.Database.InTx(func(db database.Store) error { + now := database.Now() dbProject, err := db.InsertProject(r.Context(), database.InsertProjectParams{ ID: uuid.New(), - CreatedAt: database.Now(), - UpdatedAt: database.Now(), + CreatedAt: now, + UpdatedAt: now, OrganizationID: organization.ID, Name: createProject.Name, Provisioner: importJob.Provisioner, @@ -212,6 +213,7 @@ func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Reque if err != nil { return xerrors.Errorf("insert project: %s", err) } + err = db.UpdateProjectVersionByID(r.Context(), database.UpdateProjectVersionByIDParams{ ID: projectVersion.ID, ProjectID: uuid.NullUUID{ @@ -222,6 +224,7 @@ func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Reque if err != nil { return xerrors.Errorf("insert project version: %s", err) } + for _, parameterValue := range createProject.ParameterValues { _, err = db.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ ID: uuid.New(), @@ -229,7 +232,7 @@ func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Reque CreatedAt: database.Now(), UpdatedAt: database.Now(), Scope: database.ParameterScopeProject, - ScopeID: dbProject.ID.String(), + ScopeID: dbProject.ID, SourceScheme: parameterValue.SourceScheme, SourceValue: parameterValue.SourceValue, DestinationScheme: parameterValue.DestinationScheme, @@ -238,6 +241,7 @@ func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Reque return xerrors.Errorf("insert parameter value: %w", err) } } + project = convertProject(dbProject, 0) return nil }) @@ -291,18 +295,20 @@ func (api *api) projectByOrganizationAndName(rw http.ResponseWriter, r *http.Req OrganizationID: organization.ID, Name: projectName, }) - if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, httpapi.Response{ - Message: fmt.Sprintf("no project found by name %q in the %q organization", projectName, organization.Name), - }) - return - } if err != nil { + if errors.Is(err, sql.ErrNoRows) { + httpapi.Write(rw, http.StatusNotFound, httpapi.Response{ + Message: fmt.Sprintf("no project found by name %q in the %q organization", projectName, organization.Name), + }) + return + } + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: fmt.Sprintf("get project by organization and name: %s", err), }) return } + workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByProjectIDs(r.Context(), []uuid.UUID{project.ID}) if errors.Is(err, sql.ErrNoRows) { err = nil @@ -313,10 +319,12 @@ func (api *api) projectByOrganizationAndName(rw http.ResponseWriter, r *http.Req }) return } + count := uint32(0) if len(workspaceCounts) > 0 { count = uint32(workspaceCounts[0].Count) } + render.Status(r, http.StatusOK) render.JSON(rw, r, convertProject(project, count)) } diff --git a/coderd/organizations_test.go b/coderd/organizations_test.go index d405e88b3a..eb5ea00cb5 100644 --- a/coderd/organizations_test.go +++ b/coderd/organizations_test.go @@ -19,7 +19,7 @@ func TestProvisionerDaemonsByOrganization(t *testing.T) { t.Run("NoAuth", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) - _, err := client.ProvisionerDaemonsByOrganization(context.Background(), "someorg") + _, err := client.ProvisionerDaemonsByOrganization(context.Background(), uuid.New()) require.Error(t, err) }) diff --git a/coderd/parameter/compute.go b/coderd/parameter/compute.go index f5960a3585..ad58990fc6 100644 --- a/coderd/parameter/compute.go +++ b/coderd/parameter/compute.go @@ -14,8 +14,8 @@ import ( // ComputeScope targets identifiers to pull parameters from. type ComputeScope struct { ProjectImportJobID uuid.UUID - OrganizationID string - UserID string + OrganizationID uuid.UUID + UserID uuid.UUID ProjectID uuid.NullUUID WorkspaceID uuid.NullUUID } @@ -71,7 +71,7 @@ func Compute(ctx context.Context, db database.Store, scope ComputeScope, options // Job parameters come second! err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ Scope: database.ParameterScopeImportJob, - ScopeID: scope.ProjectImportJobID.String(), + ScopeID: scope.ProjectImportJobID, }) if err != nil { return nil, err @@ -101,7 +101,7 @@ func Compute(ctx context.Context, db database.Store, scope ComputeScope, options DestinationScheme: parameterSchema.DefaultDestinationScheme, SourceValue: parameterSchema.DefaultSourceValue, Scope: database.ParameterScopeImportJob, - ScopeID: scope.ProjectImportJobID.String(), + ScopeID: scope.ProjectImportJobID, }, true) if err != nil { return nil, xerrors.Errorf("insert default value: %w", err) @@ -115,7 +115,7 @@ func Compute(ctx context.Context, db database.Store, scope ComputeScope, options // Project parameters come third! err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ Scope: database.ParameterScopeProject, - ScopeID: scope.ProjectID.UUID.String(), + ScopeID: scope.ProjectID.UUID, }) if err != nil { return nil, err @@ -135,7 +135,7 @@ func Compute(ctx context.Context, db database.Store, scope ComputeScope, options // Workspace parameters come last! err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ Scope: database.ParameterScopeWorkspace, - ScopeID: scope.WorkspaceID.UUID.String(), + ScopeID: scope.WorkspaceID.UUID, }) if err != nil { return nil, err diff --git a/coderd/parameter/compute_test.go b/coderd/parameter/compute_test.go index 3ad01d4f5f..9beacc7321 100644 --- a/coderd/parameter/compute_test.go +++ b/coderd/parameter/compute_test.go @@ -18,7 +18,7 @@ func TestCompute(t *testing.T) { generateScope := func() parameter.ComputeScope { return parameter.ComputeScope{ ProjectImportJobID: uuid.New(), - OrganizationID: uuid.NewString(), + OrganizationID: uuid.New(), ProjectID: uuid.NullUUID{ UUID: uuid.New(), Valid: true, @@ -27,7 +27,7 @@ func TestCompute(t *testing.T) { UUID: uuid.New(), Valid: true, }, - UserID: uuid.NewString(), + UserID: uuid.New(), } } type parameterOptions struct { @@ -88,7 +88,7 @@ func TestCompute(t *testing.T) { computedValue := computed[0] require.True(t, computedValue.DefaultSourceValue) require.Equal(t, database.ParameterScopeImportJob, computedValue.Scope) - require.Equal(t, scope.ProjectImportJobID.String(), computedValue.ScopeID) + require.Equal(t, scope.ProjectImportJobID, computedValue.ScopeID) require.Equal(t, computedValue.SourceValue, parameterSchema.DefaultSourceValue) }) @@ -114,7 +114,7 @@ func TestCompute(t *testing.T) { ID: uuid.New(), Name: parameterSchema.Name, Scope: database.ParameterScopeImportJob, - ScopeID: scope.ProjectImportJobID.String(), + ScopeID: scope.ProjectImportJobID, SourceScheme: database.ParameterSourceSchemeData, SourceValue: "secondnop", DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, @@ -139,7 +139,7 @@ func TestCompute(t *testing.T) { ID: uuid.New(), Name: parameterSchema.Name, Scope: database.ParameterScopeProject, - ScopeID: scope.ProjectID.UUID.String(), + ScopeID: scope.ProjectID.UUID, SourceScheme: database.ParameterSourceSchemeData, SourceValue: "nop", DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, @@ -160,11 +160,12 @@ func TestCompute(t *testing.T) { parameterSchema := generateParameter(t, db, parameterOptions{ ProjectImportJobID: scope.ProjectImportJobID, }) + _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ ID: uuid.New(), Name: parameterSchema.Name, Scope: database.ParameterScopeWorkspace, - ScopeID: scope.WorkspaceID.UUID.String(), + ScopeID: scope.WorkspaceID.UUID, SourceScheme: database.ParameterSourceSchemeData, SourceValue: "nop", DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, @@ -189,7 +190,7 @@ func TestCompute(t *testing.T) { ID: uuid.New(), Name: parameterSchema.Name, Scope: database.ParameterScopeWorkspace, - ScopeID: scope.WorkspaceID.UUID.String(), + ScopeID: scope.WorkspaceID.UUID, SourceScheme: database.ParameterSourceSchemeData, SourceValue: "nop", DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, diff --git a/coderd/parameters.go b/coderd/parameters.go index e7eb7fb389..3d9b7d5eb2 100644 --- a/coderd/parameters.go +++ b/coderd/parameters.go @@ -138,7 +138,7 @@ func convertParameterValue(parameterValue database.ParameterValue) codersdk.Para } } -func readScopeAndID(rw http.ResponseWriter, r *http.Request) (database.ParameterScope, string, bool) { +func readScopeAndID(rw http.ResponseWriter, r *http.Request) (database.ParameterScope, uuid.UUID, bool) { var scope database.ParameterScope switch chi.URLParam(r, "scope") { case string(codersdk.ParameterOrganization): @@ -153,8 +153,17 @@ func readScopeAndID(rw http.ResponseWriter, r *http.Request) (database.Parameter httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ Message: fmt.Sprintf("invalid scope %q", scope), }) - return scope, "", false + return scope, uuid.Nil, false } - return scope, chi.URLParam(r, "id"), true + id := chi.URLParam(r, "id") + uid, err := uuid.Parse(id) + if err != nil { + httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ + Message: fmt.Sprintf("invalid uuid %q: %s", id, err), + }) + return scope, uuid.Nil, false + } + + return scope, uid, true } diff --git a/coderd/projects_test.go b/coderd/projects_test.go index d819243adf..782e2457f1 100644 --- a/coderd/projects_test.go +++ b/coderd/projects_test.go @@ -47,7 +47,7 @@ func TestDeleteProject(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - coderdtest.CreateWorkspace(t, client, "me", project.ID) + coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) err := client.DeleteProject(context.Background(), project.ID) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) diff --git a/coderd/provisionerjobs_test.go b/coderd/provisionerjobs_test.go index 70afdbf609..8b363174a4 100644 --- a/coderd/provisionerjobs_test.go +++ b/coderd/provisionerjobs_test.go @@ -9,6 +9,7 @@ import ( "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/coderd/database" + "github.com/coder/coder/codersdk" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" ) @@ -37,7 +38,7 @@ func TestProvisionerJobLogs(t *testing.T) { }) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) before := time.Now().UTC() coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) @@ -75,7 +76,7 @@ func TestProvisionerJobLogs(t *testing.T) { }) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) before := database.Now() ctx, cancelFunc := context.WithCancel(context.Background()) t.Cleanup(cancelFunc) @@ -111,7 +112,7 @@ func TestProvisionerJobLogs(t *testing.T) { }) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) logs, err := client.WorkspaceBuildLogsBefore(context.Background(), workspace.LatestBuild.ID, time.Now()) require.NoError(t, err) diff --git a/coderd/users.go b/coderd/users.go index 8d413b9cdc..336d9091ce 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -32,12 +32,14 @@ func (api *api) firstUser(rw http.ResponseWriter, r *http.Request) { }) return } + if userCount == 0 { httpapi.Write(rw, http.StatusNotFound, httpapi.Response{ Message: "The initial user has not been created!", }) return } + httpapi.Write(rw, http.StatusOK, httpapi.Response{ Message: "The initial user has already been created!", }) @@ -49,6 +51,7 @@ func (api *api) postFirstUser(rw http.ResponseWriter, r *http.Request) { if !httpapi.Read(rw, r, &createUser) { return } + // This should only function for the first user. userCount, err := api.Database.GetUserCount(r.Context()) if err != nil { @@ -57,6 +60,7 @@ func (api *api) postFirstUser(rw http.ResponseWriter, r *http.Request) { }) return } + // If a user already exists, the initial admin user no longer can be created. if userCount != 0 { httpapi.Write(rw, http.StatusConflict, httpapi.Response{ @@ -64,6 +68,7 @@ func (api *api) postFirstUser(rw http.ResponseWriter, r *http.Request) { }) return } + hashedPassword, err := userpassword.Hash(createUser.Password) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ @@ -77,7 +82,7 @@ func (api *api) postFirstUser(rw http.ResponseWriter, r *http.Request) { var organization database.Organization err = api.Database.InTx(func(s database.Store) error { user, err = api.Database.InsertUser(r.Context(), database.InsertUserParams{ - ID: uuid.NewString(), + ID: uuid.New(), Email: createUser.Email, HashedPassword: []byte(hashedPassword), Username: createUser.Username, @@ -89,8 +94,8 @@ func (api *api) postFirstUser(rw http.ResponseWriter, r *http.Request) { return xerrors.Errorf("create user: %w", err) } organization, err = api.Database.InsertOrganization(r.Context(), database.InsertOrganizationParams{ - ID: uuid.NewString(), - Name: createUser.Organization, + ID: uuid.New(), + Name: createUser.OrganizationName, CreatedAt: database.Now(), UpdatedAt: database.Now(), }) @@ -190,7 +195,7 @@ func (api *api) postUsers(rw http.ResponseWriter, r *http.Request) { var user database.User err = api.Database.InTx(func(db database.Store) error { user, err = db.InsertUser(r.Context(), database.InsertUserParams{ - ID: uuid.NewString(), + ID: uuid.New(), Email: createUser.Email, HashedPassword: []byte(hashedPassword), Username: createUser.Username, @@ -317,7 +322,7 @@ func (api *api) postOrganizationsByUser(rw http.ResponseWriter, r *http.Request) var organization database.Organization err = api.Database.InTx(func(db database.Store) error { organization, err = api.Database.InsertOrganization(r.Context(), database.InsertOrganizationParams{ - ID: uuid.NewString(), + ID: uuid.New(), Name: req.Name, CreatedAt: database.Now(), UpdatedAt: database.Now(), @@ -611,7 +616,7 @@ func (api *api) postWorkspacesByUser(rw http.ResponseWriter, r *http.Request) { CreatedAt: database.Now(), UpdatedAt: database.Now(), Scope: database.ParameterScopeWorkspace, - ScopeID: workspace.ID.String(), + ScopeID: workspace.ID, SourceScheme: parameterValue.SourceScheme, SourceValue: parameterValue.SourceValue, DestinationScheme: parameterValue.DestinationScheme, @@ -649,7 +654,7 @@ func (api *api) postWorkspacesByUser(rw http.ResponseWriter, r *http.Request) { WorkspaceID: workspace.ID, ProjectVersionID: projectVersion.ID, Name: namesgenerator.GetRandomName(1), - Initiator: apiKey.UserID, + InitiatorID: apiKey.UserID, Transition: database.WorkspaceTransitionStart, JobID: provisionerJob.ID, }) @@ -725,35 +730,35 @@ func (api *api) workspacesByUser(rw http.ResponseWriter, r *http.Request) { return } - buildByWorkspaceID := map[string]database.WorkspaceBuild{} + buildByWorkspaceID := map[uuid.UUID]database.WorkspaceBuild{} for _, workspaceBuild := range workspaceBuilds { - buildByWorkspaceID[workspaceBuild.WorkspaceID.String()] = workspaceBuild + buildByWorkspaceID[workspaceBuild.WorkspaceID] = workspaceBuild } - projectByID := map[string]database.Project{} + projectByID := map[uuid.UUID]database.Project{} for _, project := range projects { - projectByID[project.ID.String()] = project + projectByID[project.ID] = project } - jobByID := map[string]database.ProvisionerJob{} + jobByID := map[uuid.UUID]database.ProvisionerJob{} for _, job := range jobs { - jobByID[job.ID.String()] = job + jobByID[job.ID] = job } apiWorkspaces := make([]codersdk.Workspace, 0, len(workspaces)) for _, workspace := range workspaces { - build, exists := buildByWorkspaceID[workspace.ID.String()] + build, exists := buildByWorkspaceID[workspace.ID] if !exists { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: fmt.Sprintf("build not found for workspace %q", workspace.Name), }) return } - project, exists := projectByID[workspace.ProjectID.String()] + project, exists := projectByID[workspace.ProjectID] if !exists { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: fmt.Sprintf("project not found for workspace %q", workspace.Name), }) return } - job, exists := jobByID[build.JobID.String()] + job, exists := jobByID[build.JobID] if !exists { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ Message: fmt.Sprintf("build job not found for workspace %q", workspace.Name), diff --git a/coderd/users_test.go b/coderd/users_test.go index 116fb1d4a2..bfedfbc2d0 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -27,10 +27,10 @@ func TestFirstUser(t *testing.T) { client := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, client) _, err := client.CreateFirstUser(context.Background(), codersdk.CreateFirstUserRequest{ - Email: "some@email.com", - Username: "exampleuser", - Password: "password", - Organization: "someorg", + Email: "some@email.com", + Username: "exampleuser", + Password: "password", + OrganizationName: "someorg", }) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) @@ -62,10 +62,10 @@ func TestPostLogin(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) req := codersdk.CreateFirstUserRequest{ - Email: "testuser@coder.com", - Username: "testuser", - Password: "testpass", - Organization: "testorg", + Email: "testuser@coder.com", + Username: "testuser", + Password: "testpass", + OrganizationName: "testorg", } _, err := client.CreateFirstUser(context.Background(), req) require.NoError(t, err) @@ -82,10 +82,10 @@ func TestPostLogin(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) req := codersdk.CreateFirstUserRequest{ - Email: "testuser@coder.com", - Username: "testuser", - Password: "testpass", - Organization: "testorg", + Email: "testuser@coder.com", + Username: "testuser", + Password: "testpass", + OrganizationName: "testorg", } _, err := client.CreateFirstUser(context.Background(), req) require.NoError(t, err) @@ -137,13 +137,13 @@ func TestPostUsers(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) - me, err := client.User(context.Background(), "") + me, err := client.User(context.Background(), codersdk.Me) require.NoError(t, err) _, err = client.CreateUser(context.Background(), codersdk.CreateUserRequest{ Email: me.Email, Username: me.Username, Password: "password", - OrganizationID: "someorg", + OrganizationID: uuid.New(), }) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) @@ -155,7 +155,7 @@ func TestPostUsers(t *testing.T) { client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) _, err := client.CreateUser(context.Background(), codersdk.CreateUserRequest{ - OrganizationID: "not-exists", + OrganizationID: uuid.New(), Email: "another@user.org", Username: "someone-else", Password: "testing", @@ -170,7 +170,7 @@ func TestPostUsers(t *testing.T) { client := coderdtest.New(t, nil) first := coderdtest.CreateFirstUser(t, client) other := coderdtest.CreateAnotherUser(t, client, first.OrganizationID) - org, err := other.CreateOrganization(context.Background(), "", codersdk.CreateOrganizationRequest{ + org, err := other.CreateOrganization(context.Background(), codersdk.Me, codersdk.CreateOrganizationRequest{ Name: "another", }) require.NoError(t, err) @@ -204,7 +204,7 @@ func TestUserByName(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, client) - _, err := client.User(context.Background(), "") + _, err := client.User(context.Background(), codersdk.Me) require.NoError(t, err) } @@ -212,7 +212,7 @@ func TestOrganizationsByUser(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, client) - orgs, err := client.OrganizationsByUser(context.Background(), "") + orgs, err := client.OrganizationsByUser(context.Background(), codersdk.Me) require.NoError(t, err) require.NotNil(t, orgs) require.Len(t, orgs, 1) @@ -224,7 +224,7 @@ func TestOrganizationByUserAndName(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) - _, err := client.OrganizationByName(context.Background(), "", "nothing") + _, err := client.OrganizationByName(context.Background(), codersdk.Me, "nothing") var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusNotFound, apiErr.StatusCode()) @@ -235,11 +235,11 @@ func TestOrganizationByUserAndName(t *testing.T) { client := coderdtest.New(t, nil) first := coderdtest.CreateFirstUser(t, client) other := coderdtest.CreateAnotherUser(t, client, first.OrganizationID) - org, err := other.CreateOrganization(context.Background(), "", codersdk.CreateOrganizationRequest{ + org, err := other.CreateOrganization(context.Background(), codersdk.Me, codersdk.CreateOrganizationRequest{ Name: "another", }) require.NoError(t, err) - _, err = client.OrganizationByName(context.Background(), "", org.Name) + _, err = client.OrganizationByName(context.Background(), codersdk.Me, org.Name) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode()) @@ -251,7 +251,7 @@ func TestOrganizationByUserAndName(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) org, err := client.Organization(context.Background(), user.OrganizationID) require.NoError(t, err) - _, err = client.OrganizationByName(context.Background(), "", org.Name) + _, err = client.OrganizationByName(context.Background(), codersdk.Me, org.Name) require.NoError(t, err) }) } @@ -264,7 +264,7 @@ func TestPostOrganizationsByUser(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) org, err := client.Organization(context.Background(), user.OrganizationID) require.NoError(t, err) - _, err = client.CreateOrganization(context.Background(), "", codersdk.CreateOrganizationRequest{ + _, err = client.CreateOrganization(context.Background(), codersdk.Me, codersdk.CreateOrganizationRequest{ Name: org.Name, }) var apiErr *codersdk.Error @@ -276,7 +276,7 @@ func TestPostOrganizationsByUser(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, client) - _, err := client.CreateOrganization(context.Background(), "", codersdk.CreateOrganizationRequest{ + _, err := client.CreateOrganization(context.Background(), codersdk.Me, codersdk.CreateOrganizationRequest{ Name: "new", }) require.NoError(t, err) @@ -291,7 +291,7 @@ func TestPostAPIKey(t *testing.T) { _ = coderdtest.CreateFirstUser(t, client) client.SessionToken = "" - _, err := client.CreateAPIKey(context.Background(), "") + _, err := client.CreateAPIKey(context.Background(), codersdk.Me) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode()) @@ -301,7 +301,7 @@ func TestPostAPIKey(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, client) - apiKey, err := client.CreateAPIKey(context.Background(), "") + apiKey, err := client.CreateAPIKey(context.Background(), codersdk.Me) require.NotNil(t, apiKey) require.GreaterOrEqual(t, len(apiKey.Key), 2) require.NoError(t, err) @@ -314,7 +314,7 @@ func TestPostWorkspacesByUser(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, client) - _, err := client.CreateWorkspace(context.Background(), "", codersdk.CreateWorkspaceRequest{ + _, err := client.CreateWorkspace(context.Background(), codersdk.Me, codersdk.CreateWorkspaceRequest{ ProjectID: uuid.New(), Name: "workspace", }) @@ -330,14 +330,14 @@ func TestPostWorkspacesByUser(t *testing.T) { first := coderdtest.CreateFirstUser(t, client) other := coderdtest.CreateAnotherUser(t, client, first.OrganizationID) - org, err := other.CreateOrganization(context.Background(), "", codersdk.CreateOrganizationRequest{ + org, err := other.CreateOrganization(context.Background(), codersdk.Me, codersdk.CreateOrganizationRequest{ Name: "another", }) require.NoError(t, err) version := coderdtest.CreateProjectVersion(t, other, org.ID, nil) project := coderdtest.CreateProject(t, other, org.ID, version.ID) - _, err = client.CreateWorkspace(context.Background(), "", codersdk.CreateWorkspaceRequest{ + _, err = client.CreateWorkspace(context.Background(), codersdk.Me, codersdk.CreateWorkspaceRequest{ ProjectID: project.ID, Name: "workspace", }) @@ -355,8 +355,8 @@ func TestPostWorkspacesByUser(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) - _, err := client.CreateWorkspace(context.Background(), "", codersdk.CreateWorkspaceRequest{ + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) + _, err := client.CreateWorkspace(context.Background(), codersdk.Me, codersdk.CreateWorkspaceRequest{ ProjectID: project.ID, Name: workspace.Name, }) @@ -374,7 +374,7 @@ func TestPostWorkspacesByUser(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - _ = coderdtest.CreateWorkspace(t, client, "", project.ID) + _ = coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) }) } @@ -384,7 +384,7 @@ func TestWorkspacesByUser(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) - _, err := client.WorkspacesByUser(context.Background(), "") + _, err := client.WorkspacesByUser(context.Background(), codersdk.Me) require.NoError(t, err) }) t.Run("List", func(t *testing.T) { @@ -395,8 +395,8 @@ func TestWorkspacesByUser(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - _ = coderdtest.CreateWorkspace(t, client, "", project.ID) - workspaces, err := client.WorkspacesByUser(context.Background(), "") + _ = coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) + workspaces, err := client.WorkspacesByUser(context.Background(), codersdk.Me) require.NoError(t, err) require.Len(t, workspaces, 1) }) @@ -408,7 +408,7 @@ func TestWorkspaceByUserAndName(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) coderdtest.CreateFirstUser(t, client) - _, err := client.WorkspaceByName(context.Background(), "", "something") + _, err := client.WorkspaceByName(context.Background(), codersdk.Me, "something") var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusNotFound, apiErr.StatusCode()) @@ -421,8 +421,8 @@ func TestWorkspaceByUserAndName(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) - _, err := client.WorkspaceByName(context.Background(), "", workspace.Name) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) + _, err := client.WorkspaceByName(context.Background(), codersdk.Me, workspace.Name) require.NoError(t, err) }) } diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 489cb1d7a0..37f0cfecf6 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -101,7 +101,7 @@ func convertWorkspaceBuild(workspaceBuild database.WorkspaceBuild, job codersdk. AfterID: workspaceBuild.AfterID.UUID, Name: workspaceBuild.Name, Transition: workspaceBuild.Transition, - Initiator: workspaceBuild.Initiator, + InitiatorID: workspaceBuild.InitiatorID, Job: job, } } diff --git a/coderd/workspacebuilds_test.go b/coderd/workspacebuilds_test.go index a5b1c520db..fb210f6597 100644 --- a/coderd/workspacebuilds_test.go +++ b/coderd/workspacebuilds_test.go @@ -22,7 +22,7 @@ func TestWorkspaceBuild(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) _, err := client.WorkspaceBuild(context.Background(), workspace.LatestBuild.ID) require.NoError(t, err) } @@ -43,7 +43,7 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) { }) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) var build codersdk.WorkspaceBuild require.Eventually(t, func() bool { var err error @@ -72,7 +72,7 @@ func TestWorkspaceBuildResources(t *testing.T) { coderdtest.AwaitProjectVersionJob(t, client, version.ID) closeDaemon.Close() project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) _, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) @@ -105,7 +105,7 @@ func TestWorkspaceBuildResources(t *testing.T) { }) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) resources, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID) require.NoError(t, err) @@ -152,7 +152,7 @@ func TestWorkspaceBuildLogs(t *testing.T) { }) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) ctx, cancelFunc := context.WithCancel(context.Background()) t.Cleanup(cancelFunc) logs, err := client.WorkspaceBuildLogsAfter(ctx, workspace.LatestBuild.ID, before) diff --git a/coderd/workspaceresourceauth_test.go b/coderd/workspaceresourceauth_test.go index 5051161948..41426028f6 100644 --- a/coderd/workspaceresourceauth_test.go +++ b/coderd/workspaceresourceauth_test.go @@ -44,7 +44,7 @@ func TestPostWorkspaceAuthAWSInstanceIdentity(t *testing.T) { }) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) client.HTTPClient = metadataClient @@ -110,7 +110,7 @@ func TestPostWorkspaceAuthGoogleInstanceIdentity(t *testing.T) { }) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) _, err := client.AuthWorkspaceGoogleInstanceIdentity(context.Background(), "", metadata) diff --git a/coderd/workspaceresources_test.go b/coderd/workspaceresources_test.go index 493422af83..ed6c8d7c68 100644 --- a/coderd/workspaceresources_test.go +++ b/coderd/workspaceresources_test.go @@ -44,7 +44,7 @@ func TestWorkspaceResource(t *testing.T) { }) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) resources, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID) require.NoError(t, err) @@ -81,7 +81,7 @@ func TestWorkspaceAgentListen(t *testing.T) { }) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) daemonCloser.Close() diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 19fd48187c..4a2b3d6d8b 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -222,7 +222,7 @@ func (api *api) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { BeforeID: priorHistoryID, Name: namesgenerator.GetRandomName(1), ProvisionerState: priorHistory.ProvisionerState, - Initiator: apiKey.UserID, + InitiatorID: apiKey.UserID, Transition: createBuild.Transition, JobID: provisionerJob.ID, }) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 84de75af91..5787e18ae7 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -23,7 +23,7 @@ func TestWorkspace(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) _, err := client.Workspace(context.Background(), workspace.ID) require.NoError(t, err) } @@ -38,7 +38,7 @@ func TestWorkspaceBuilds(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) _, err := client.WorkspaceBuilds(context.Background(), workspace.ID) require.NoError(t, err) }) @@ -54,7 +54,7 @@ func TestPostWorkspaceBuild(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) _, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{ ProjectVersionID: uuid.New(), Transition: database.WorkspaceTransitionStart, @@ -75,7 +75,7 @@ func TestPostWorkspaceBuild(t *testing.T) { }) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - _, err := client.CreateWorkspace(context.Background(), "", codersdk.CreateWorkspaceRequest{ + _, err := client.CreateWorkspace(context.Background(), codersdk.Me, codersdk.CreateWorkspaceRequest{ ProjectID: project.ID, Name: "workspace", }) @@ -94,7 +94,7 @@ func TestPostWorkspaceBuild(t *testing.T) { coderdtest.AwaitProjectVersionJob(t, client, version.ID) // Close here so workspace build doesn't process! closeDaemon.Close() - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) _, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{ ProjectVersionID: project.ActiveVersionID, Transition: database.WorkspaceTransitionStart, @@ -113,7 +113,7 @@ func TestPostWorkspaceBuild(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) build, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{ ProjectVersionID: project.ActiveVersionID, @@ -135,7 +135,7 @@ func TestPostWorkspaceBuild(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) build, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{ Transition: database.WorkspaceTransitionDelete, @@ -160,7 +160,7 @@ func TestWorkspaceBuildByName(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) coderdtest.AwaitProjectVersionJob(t, client, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) _, err := client.WorkspaceBuildByName(context.Background(), workspace.ID, "something") var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) @@ -175,7 +175,7 @@ func TestWorkspaceBuildByName(t *testing.T) { version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil) coderdtest.AwaitProjectVersionJob(t, client, version.ID) project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + workspace := coderdtest.CreateWorkspace(t, client, codersdk.Me, project.ID) build, err := client.WorkspaceBuild(context.Background(), workspace.LatestBuild.ID) require.NoError(t, err) _, err = client.WorkspaceBuildByName(context.Background(), workspace.ID, build.Name) diff --git a/codersdk/organizations.go b/codersdk/organizations.go index c21314937b..a20fd31c37 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -8,13 +8,14 @@ import ( "time" "github.com/google/uuid" + "golang.org/x/xerrors" "github.com/coder/coder/coderd/database" ) // Organization is the JSON representation of a Coder organization. type Organization struct { - ID string `json:"id" validate:"required"` + ID uuid.UUID `json:"id" validate:"required"` Name string `json:"name" validate:"required"` CreatedAt time.Time `json:"created_at" validate:"required"` UpdatedAt time.Time `json:"updated_at" validate:"required"` @@ -47,86 +48,113 @@ type CreateProjectRequest struct { ParameterValues []CreateParameterRequest `json:"parameter_values"` } -func (c *Client) Organization(ctx context.Context, id string) (Organization, error) { - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s", id), nil) +func (c *Client) Organization(ctx context.Context, id uuid.UUID) (Organization, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s", id.String()), nil) if err != nil { - return Organization{}, err + return Organization{}, xerrors.Errorf("execute request: %w", err) } defer res.Body.Close() + if res.StatusCode != http.StatusOK { return Organization{}, readBodyAsError(res) } + var organization Organization return organization, json.NewDecoder(res.Body).Decode(&organization) } // ProvisionerDaemonsByOrganization returns provisioner daemons available for an organization. -func (c *Client) ProvisionerDaemonsByOrganization(ctx context.Context, organization string) ([]ProvisionerDaemon, error) { - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/provisionerdaemons", organization), nil) +func (c *Client) ProvisionerDaemonsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerDaemon, error) { + res, err := c.request(ctx, http.MethodGet, + fmt.Sprintf("/api/v2/organizations/%s/provisionerdaemons", organizationID.String()), + nil, + ) if err != nil { - return nil, err + return nil, xerrors.Errorf("execute request: %w", err) } defer res.Body.Close() + if res.StatusCode != http.StatusOK { return nil, readBodyAsError(res) } + var daemons []ProvisionerDaemon return daemons, json.NewDecoder(res.Body).Decode(&daemons) } // CreateProjectVersion processes source-code and optionally associates the version with a project. // Executing without a project is useful for validating source-code. -func (c *Client) CreateProjectVersion(ctx context.Context, organization string, req CreateProjectVersionRequest) (ProjectVersion, error) { - res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/organizations/%s/projectversions", organization), req) +func (c *Client) CreateProjectVersion(ctx context.Context, organizationID uuid.UUID, req CreateProjectVersionRequest) (ProjectVersion, error) { + res, err := c.request(ctx, http.MethodPost, + fmt.Sprintf("/api/v2/organizations/%s/projectversions", organizationID.String()), + req, + ) if err != nil { - return ProjectVersion{}, err + return ProjectVersion{}, xerrors.Errorf("execute request: %w", err) } defer res.Body.Close() + if res.StatusCode != http.StatusCreated { return ProjectVersion{}, readBodyAsError(res) } + var projectVersion ProjectVersion return projectVersion, json.NewDecoder(res.Body).Decode(&projectVersion) } // CreateProject creates a new project inside an organization. -func (c *Client) CreateProject(ctx context.Context, organization string, request CreateProjectRequest) (Project, error) { - res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/organizations/%s/projects", organization), request) +func (c *Client) CreateProject(ctx context.Context, organizationID uuid.UUID, request CreateProjectRequest) (Project, error) { + res, err := c.request(ctx, http.MethodPost, + fmt.Sprintf("/api/v2/organizations/%s/projects", organizationID.String()), + request, + ) if err != nil { - return Project{}, err + return Project{}, xerrors.Errorf("execute request: %w", err) } defer res.Body.Close() + if res.StatusCode != http.StatusCreated { return Project{}, readBodyAsError(res) } + var project Project return project, json.NewDecoder(res.Body).Decode(&project) } // ProjectsByOrganization lists all projects inside of an organization. -func (c *Client) ProjectsByOrganization(ctx context.Context, organization string) ([]Project, error) { - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/projects", organization), nil) +func (c *Client) ProjectsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]Project, error) { + res, err := c.request(ctx, http.MethodGet, + fmt.Sprintf("/api/v2/organizations/%s/projects", organizationID.String()), + nil, + ) if err != nil { - return nil, err + return nil, xerrors.Errorf("execute request: %w", err) } defer res.Body.Close() + if res.StatusCode != http.StatusOK { return nil, readBodyAsError(res) } + var projects []Project return projects, json.NewDecoder(res.Body).Decode(&projects) } // ProjectByName finds a project inside the organization provided with a case-insensitive name. -func (c *Client) ProjectByName(ctx context.Context, organization, name string) (Project, error) { - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/projects/%s", organization, name), nil) +func (c *Client) ProjectByName(ctx context.Context, organizationID uuid.UUID, name string) (Project, error) { + res, err := c.request(ctx, http.MethodGet, + fmt.Sprintf("/api/v2/organizations/%s/projects/%s", organizationID.String(), name), + nil, + ) if err != nil { - return Project{}, err + return Project{}, xerrors.Errorf("execute request: %w", err) } defer res.Body.Close() + if res.StatusCode != http.StatusOK { return Project{}, readBodyAsError(res) } + var project Project return project, json.NewDecoder(res.Body).Decode(&project) } diff --git a/codersdk/parameters.go b/codersdk/parameters.go index e2a2992d5c..4d7ac34038 100644 --- a/codersdk/parameters.go +++ b/codersdk/parameters.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "net/http" "time" @@ -27,7 +28,7 @@ type Parameter struct { CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` Scope ParameterScope `db:"scope" json:"scope"` - ScopeID string `db:"scope_id" json:"scope_id"` + ScopeID uuid.UUID `db:"scope_id" json:"scope_id"` Name string `db:"name" json:"name"` SourceScheme database.ParameterSourceScheme `db:"source_scheme" json:"source_scheme"` DestinationScheme database.ParameterDestinationScheme `db:"destination_scheme" json:"destination_scheme"` @@ -41,40 +42,47 @@ type CreateParameterRequest struct { DestinationScheme database.ParameterDestinationScheme `json:"destination_scheme" validate:"oneof=environment_variable provisioner_variable,required"` } -func (c *Client) CreateParameter(ctx context.Context, scope ParameterScope, id string, req CreateParameterRequest) (Parameter, error) { - res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/parameters/%s/%s", scope, id), req) +func (c *Client) CreateParameter(ctx context.Context, scope ParameterScope, id uuid.UUID, req CreateParameterRequest) (Parameter, error) { + res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/parameters/%s/%s", scope, id.String()), req) if err != nil { return Parameter{}, err } defer res.Body.Close() + if res.StatusCode != http.StatusCreated { return Parameter{}, readBodyAsError(res) } + var param Parameter return param, json.NewDecoder(res.Body).Decode(¶m) } -func (c *Client) DeleteParameter(ctx context.Context, scope ParameterScope, id, name string) error { - res, err := c.request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/parameters/%s/%s/%s", scope, id, name), nil) +func (c *Client) DeleteParameter(ctx context.Context, scope ParameterScope, id uuid.UUID, name string) error { + res, err := c.request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/parameters/%s/%s/%s", scope, id.String(), name), nil) if err != nil { return err } defer res.Body.Close() + if res.StatusCode != http.StatusOK { return readBodyAsError(res) } + + _, _ = io.Copy(io.Discard, res.Body) return nil } -func (c *Client) Parameters(ctx context.Context, scope ParameterScope, id string) ([]Parameter, error) { - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/parameters/%s/%s", scope, id), nil) +func (c *Client) Parameters(ctx context.Context, scope ParameterScope, id uuid.UUID) ([]Parameter, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/parameters/%s/%s", scope, id.String()), nil) if err != nil { return nil, err } defer res.Body.Close() + if res.StatusCode != http.StatusOK { return nil, readBodyAsError(res) } + var parameters []Parameter return parameters, json.NewDecoder(res.Body).Decode(¶meters) } diff --git a/codersdk/projects.go b/codersdk/projects.go index 03d5bc2ee0..0884fb4fc1 100644 --- a/codersdk/projects.go +++ b/codersdk/projects.go @@ -12,14 +12,13 @@ import ( "github.com/coder/coder/coderd/database" ) -// Project is the JSON representation of a Coder project. -// This type matches the database object for now, but is -// abstracted for ease of change later on. +// Project is the JSON representation of a Coder project. This type matches the +// database object for now, but is abstracted for ease of change later on. type Project struct { ID uuid.UUID `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - OrganizationID string `json:"organization_id"` + OrganizationID uuid.UUID `json:"organization_id"` Name string `json:"name"` Provisioner database.ProvisionerType `json:"provisioner"` ActiveVersionID uuid.UUID `json:"active_version_id"` diff --git a/codersdk/users.go b/codersdk/users.go index bdee84f598..dfcbd78125 100644 --- a/codersdk/users.go +++ b/codersdk/users.go @@ -10,32 +10,35 @@ import ( "github.com/google/uuid" ) +// Me is used as a replacement for your own ID. +var Me = uuid.Nil + // User represents a user in Coder. type User struct { - ID string `json:"id" validate:"required"` + ID uuid.UUID `json:"id" validate:"required"` Email string `json:"email" validate:"required"` CreatedAt time.Time `json:"created_at" validate:"required"` Username string `json:"username" validate:"required"` } type CreateFirstUserRequest struct { - Email string `json:"email" validate:"required,email"` - Username string `json:"username" validate:"required,username"` - Password string `json:"password" validate:"required"` - Organization string `json:"organization" validate:"required,username"` + Email string `json:"email" validate:"required,email"` + Username string `json:"username" validate:"required,username"` + Password string `json:"password" validate:"required"` + OrganizationName string `json:"organization" validate:"required,username"` } // CreateFirstUserResponse contains IDs for newly created user info. type CreateFirstUserResponse struct { - UserID string `json:"user_id"` - OrganizationID string `json:"organization_id"` + UserID uuid.UUID `json:"user_id"` + OrganizationID uuid.UUID `json:"organization_id"` } type CreateUserRequest struct { - Email string `json:"email" validate:"required,email"` - Username string `json:"username" validate:"required,username"` - Password string `json:"password" validate:"required"` - OrganizationID string `json:"organization_id" validate:"required"` + Email string `json:"email" validate:"required,email"` + Username string `json:"username" validate:"required,username"` + Password string `json:"password" validate:"required"` + OrganizationID uuid.UUID `json:"organization_id" validate:"required"` } // LoginWithPasswordRequest enables callers to authenticate with email and password. @@ -113,11 +116,8 @@ func (c *Client) CreateUser(ctx context.Context, req CreateUserRequest) (User, e } // CreateAPIKey generates an API key for the user ID provided. -func (c *Client) CreateAPIKey(ctx context.Context, id string) (*GenerateAPIKeyResponse, error) { - if id == "" { - id = "me" - } - res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys", id), nil) +func (c *Client) CreateAPIKey(ctx context.Context, userID uuid.UUID) (*GenerateAPIKeyResponse, error) { + res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys", uuidOrMe(userID)), nil) if err != nil { return nil, err } @@ -162,12 +162,9 @@ func (c *Client) Logout(ctx context.Context) error { } // User returns a user for the ID provided. -// If the ID string is empty, the current user will be returned. -func (c *Client) User(ctx context.Context, id string) (User, error) { - if id == "" { - id = "me" - } - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s", id), nil) +// If the uuid is nil, the current user will be returned. +func (c *Client) User(ctx context.Context, id uuid.UUID) (User, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s", uuidOrMe(id)), nil) if err != nil { return User{}, err } @@ -180,11 +177,8 @@ func (c *Client) User(ctx context.Context, id string) (User, error) { } // OrganizationsByUser returns all organizations the user is a member of. -func (c *Client) OrganizationsByUser(ctx context.Context, id string) ([]Organization, error) { - if id == "" { - id = "me" - } - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/organizations", id), nil) +func (c *Client) OrganizationsByUser(ctx context.Context, userID uuid.UUID) ([]Organization, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/organizations", uuidOrMe(userID)), nil) if err != nil { return nil, err } @@ -196,11 +190,8 @@ func (c *Client) OrganizationsByUser(ctx context.Context, id string) ([]Organiza return orgs, json.NewDecoder(res.Body).Decode(&orgs) } -func (c *Client) OrganizationByName(ctx context.Context, user, name string) (Organization, error) { - if user == "" { - user = "me" - } - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/organizations/%s", user, name), nil) +func (c *Client) OrganizationByName(ctx context.Context, userID uuid.UUID, name string) (Organization, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/organizations/%s", uuidOrMe(userID), name), nil) if err != nil { return Organization{}, err } @@ -213,68 +204,74 @@ func (c *Client) OrganizationByName(ctx context.Context, user, name string) (Org } // CreateOrganization creates an organization and adds the provided user as an admin. -func (c *Client) CreateOrganization(ctx context.Context, user string, req CreateOrganizationRequest) (Organization, error) { - if user == "" { - user = "me" - } - res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/organizations", user), req) +func (c *Client) CreateOrganization(ctx context.Context, userID uuid.UUID, req CreateOrganizationRequest) (Organization, error) { + res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/organizations", uuidOrMe(userID)), req) if err != nil { return Organization{}, err } defer res.Body.Close() + if res.StatusCode != http.StatusCreated { return Organization{}, readBodyAsError(res) } + var org Organization return org, json.NewDecoder(res.Body).Decode(&org) } // CreateWorkspace creates a new workspace for the project specified. -func (c *Client) CreateWorkspace(ctx context.Context, user string, request CreateWorkspaceRequest) (Workspace, error) { - if user == "" { - user = "me" - } - res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/workspaces", user), request) +func (c *Client) CreateWorkspace(ctx context.Context, userID uuid.UUID, request CreateWorkspaceRequest) (Workspace, error) { + res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/workspaces", uuidOrMe(userID)), request) if err != nil { return Workspace{}, err } defer res.Body.Close() + if res.StatusCode != http.StatusCreated { return Workspace{}, readBodyAsError(res) } + var workspace Workspace return workspace, json.NewDecoder(res.Body).Decode(&workspace) } // WorkspacesByUser returns all workspaces the specified user has access to. -func (c *Client) WorkspacesByUser(ctx context.Context, user string) ([]Workspace, error) { - if user == "" { - user = "me" - } - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/workspaces", user), nil) +func (c *Client) WorkspacesByUser(ctx context.Context, userID uuid.UUID) ([]Workspace, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/workspaces", uuidOrMe(userID)), nil) if err != nil { return nil, err } defer res.Body.Close() + if res.StatusCode != http.StatusOK { return nil, readBodyAsError(res) } + var workspaces []Workspace return workspaces, json.NewDecoder(res.Body).Decode(&workspaces) } -func (c *Client) WorkspaceByName(ctx context.Context, user, name string) (Workspace, error) { - if user == "" { - user = "me" - } - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/workspaces/%s", user, name), nil) +func (c *Client) WorkspaceByName(ctx context.Context, userID uuid.UUID, name string) (Workspace, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/workspaces/%s", uuidOrMe(userID), name), nil) if err != nil { return Workspace{}, err } defer res.Body.Close() + if res.StatusCode != http.StatusOK { return Workspace{}, readBodyAsError(res) } + var workspace Workspace return workspace, json.NewDecoder(res.Body).Decode(&workspace) } + +// uuidOrMe returns the provided uuid as a string if it's valid, ortherwise +// `me`. +func uuidOrMe(id uuid.UUID) string { + if id == Me { + return "me" + } + + return id.String() +} diff --git a/codersdk/workspacebuilds.go b/codersdk/workspacebuilds.go index b71e1d52a6..94d5c8f1e0 100644 --- a/codersdk/workspacebuilds.go +++ b/codersdk/workspacebuilds.go @@ -24,7 +24,7 @@ type WorkspaceBuild struct { AfterID uuid.UUID `json:"after_id"` Name string `json:"name"` Transition database.WorkspaceTransition `json:"transition"` - Initiator string `json:"initiator"` + InitiatorID uuid.UUID `json:"initiator_id"` Job ProvisionerJob `json:"job"` } diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index 0724b735bb..c065cf5b6d 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -18,7 +18,7 @@ type Workspace struct { ID uuid.UUID `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - OwnerID string `json:"owner_id"` + OwnerID uuid.UUID `json:"owner_id"` ProjectID uuid.UUID `json:"project_id"` ProjectName string `json:"project_name"` LatestBuild WorkspaceBuild `json:"latest_build"` diff --git a/examples/examples.go b/examples/examples.go index 7b1da9e9eb..a66ad9587f 100644 --- a/examples/examples.go +++ b/examples/examples.go @@ -38,6 +38,7 @@ func List() ([]Example, error) { returnError = xerrors.Errorf("read dir: %w", err) return } + for _, dir := range dirs { exampleID := dir.Name() // Each one of these is a example! @@ -46,31 +47,37 @@ func List() ([]Example, error) { returnError = xerrors.Errorf("example %q does not contain README.md", exampleID) return } + frontMatter, err := pageparser.ParseFrontMatterAndContent(bytes.NewReader(readme)) if err != nil { returnError = xerrors.Errorf("parse example %q front matter: %w", exampleID, err) return } + nameRaw, exists := frontMatter.FrontMatter["name"] if !exists { returnError = xerrors.Errorf("example %q front matter does not contain name", exampleID) return } + name, valid := nameRaw.(string) if !valid { returnError = xerrors.Errorf("example %q name isn't a string", exampleID) return } + descriptionRaw, exists := frontMatter.FrontMatter["description"] if !exists { returnError = xerrors.Errorf("example %q front matter does not contain name", exampleID) return } + description, valid := descriptionRaw.(string) if !valid { returnError = xerrors.Errorf("example %q description isn't a string", exampleID) return } + examples = append(examples, Example{ ID: exampleID, Name: name, @@ -87,8 +94,9 @@ func Archive(exampleID string) ([]byte, error) { rawData, err, _ := archives.Do(exampleID, func() (interface{}, error) { examples, err := List() if err != nil { - return nil, err + return nil, xerrors.Errorf("list: %w", err) } + var selected Example for _, example := range examples { if example.ID != exampleID { @@ -97,6 +105,7 @@ func Archive(exampleID string) ([]byte, error) { selected = example break } + if selected.ID == "" { return nil, xerrors.Errorf("example with id %q not found", exampleID) } @@ -114,27 +123,33 @@ func Archive(exampleID string) ([]byte, error) { if err != nil { return nil, xerrors.Errorf("open file: %w", err) } + info, err := file.Stat() if err != nil { return nil, xerrors.Errorf("stat file: %w", err) } + if info.IsDir() { continue } + data := make([]byte, info.Size()) _, err = file.Read(data) if err != nil { return nil, xerrors.Errorf("read data: %w", err) } + header, err := tar.FileInfoHeader(info, entry.Name()) if err != nil { return nil, xerrors.Errorf("get file header: %w", err) } header.Mode = 0644 + err = tarWriter.WriteHeader(header) if err != nil { return nil, xerrors.Errorf("write file: %w", err) } + _, err = tarWriter.Write(data) if err != nil { return nil, xerrors.Errorf("write: %w", err) @@ -144,6 +159,7 @@ func Archive(exampleID string) ([]byte, error) { if err != nil { return nil, xerrors.Errorf("flush archive: %w", err) } + return buffer.Bytes(), nil }) if err != nil {