Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: Refactor API routes to use UUIDs instead of friendly names #401

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Mar 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix querying
  • Loading branch information
kylecarbs committed Mar 6, 2022
commit 1726eadca554a80f8fa90b685deed8f882d25ed7
2 changes: 1 addition & 1 deletion cli/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func projects() *cobra.Command {
return cmd
}

func displayProjectImportInfo(cmd *cobra.Command, parameterSchemas []coderd.ParameterSchema, parameterValues []coderd.ComputedParameterValue, resources []coderd.ProvisionerJobResource) error {
func displayProjectImportInfo(cmd *cobra.Command, parameterSchemas []coderd.ParameterSchema, parameterValues []coderd.ComputedParameterValue, resources []coderd.WorkspaceResource) error {
schemaByID := map[string]coderd.ParameterSchema{}
for _, schema := range parameterSchemas {
schemaByID[schema.ID.String()] = schema
Expand Down
2 changes: 1 addition & 1 deletion cli/workspacecreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func workspaceCreate() *cobra.Command {
return err
}

logs, err := client.WorkspaceProvisionJobLogsAfter(cmd.Context(), organization.Name, history.ProvisionJobID, time.Time{})
logs, err := client.WorkspaceBuildJobLogsAfter(cmd.Context(), organization.Name, history.ProvisionJobID, time.Time{})
if err != nil {
return err
}
Expand Down
88 changes: 57 additions & 31 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,28 +51,35 @@ func New(options *Options) (http.Handler, func()) {
httpmw.ExtractAPIKey(options.Database, nil),
httpmw.ExtractOrganizationParam(options.Database),
)
r.Get("/", api.organization)
r.Get("/provisionerdaemons", api.provisionerDaemonsByOrganization)
r.Post("/projectversions", api.postProjectVersionsByOrganization)
r.Route("/projects", func(r chi.Router) {
r.Post("/", api.postProjectsByOrganization)
r.Get("/", api.projectsByOrganization)
r.Get("/{projectname}", api.projectByOrganizationAndName)
})
r.Get("/parameters", nil)
r.Post("/parameters", nil)
r.Patch("/parameters/{name}", nil)
r.Delete("/parameters/{name}", nil)
})
r.Route("/projects/{project}", func(r chi.Router) {
r.Use(
httpmw.ExtractAPIKey(options.Database, nil),
httpmw.ExtractProjectParam(options.Database),
httpmw.ExtractOrganizationParam(options.Database),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it doesn't seem like this would be used here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ensures the caller has access to the organization the project resides in. Otherwise, users could access projects inside organizations they aren't in.

)
r.Get("/", api.projectByOrganization)
r.Delete("/", nil)
r.Get("/workspaces", api.workspacesByProject)
r.Get("/", api.project)
r.Get("/parameters", api.parametersByProject)
r.Post("/parameters", api.postParametersByProject)
r.Get("/versions", api.projectVersionsByOrganization)
r.Get("/versions/latest", nil)
r.Patch("/versions", nil)
r.Patch("/parameters/{name}", nil)
r.Delete("/parameters/{name}", nil)
r.Route("/versions", func(r chi.Router) {
r.Get("/", api.projectVersionsByProject)
r.Patch("/versions", nil)
r.Get("/{projectversionname}", api.projectVersionByName)
})
})
r.Route("/projectversions/{projectversion}", func(r chi.Router) {
r.Use(
Expand All @@ -81,65 +88,84 @@ func New(options *Options) (http.Handler, func()) {
httpmw.ExtractOrganizationParam(options.Database),
)

r.Get("/", nil)
r.Get("/schema", nil)
r.Get("/parameters", nil)
r.Get("/logs", nil)
r.Get("/resources", nil)
r.Get("/", api.projectVersion)
r.Get("/schema", api.projectVersionSchema)
r.Get("/parameters", api.projectVersionParameters)
r.Get("/resources", api.projectVersionResources)
r.Get("/logs", api.projectVersionLogs)
})
r.Route("/provisionerdaemons", func(r chi.Router) {
r.Route("/me", func(r chi.Router) {
r.Get("/listen", api.provisionerDaemonsServe)
r.Get("/listen", api.provisionerDaemonsListen)
})
})
r.Route("/users", func(r chi.Router) {
r.Post("/login", api.postLogin)
r.Post("/logout", api.postLogout)
r.Get("/first", api.firstUser)
r.Post("/first", api.postFirstUser)
r.Post("/login", api.postLogin)
r.Post("/logout", api.postLogout)
r.Group(func(r chi.Router) {
r.Use(httpmw.ExtractAPIKey(options.Database, nil))
r.Post("/", nil)
r.Post("/", api.postUsers)
r.Route("/{user}", func(r chi.Router) {
r.Use(httpmw.ExtractUserParam(options.Database))
r.Get("/", api.userByName)
r.Get("/organizations", api.organizationsByUser)
r.Post("/keys", api.postKeyForUser)
r.Post("/organizations", api.postOrganizationsByUser)
r.Post("/keys", api.postAPIKey)
r.Route("/organizations", func(r chi.Router) {
r.Post("/", api.postOrganizationsByUser)
r.Get("/", api.organizationsByUser)
r.Get("/{organizationname}", api.organizationByUserAndName)
})
r.Route("/workspaces", func(r chi.Router) {
r.Post("/", api.postWorkspacesByUser)
r.Get("/", api.workspacesByUser)
r.Get("/{workspacename}", api.workspaceByUserAndName)
})
})
})
})
r.Route("/workspaceagents", func(r chi.Router) {
r.Route("/workspaceresources", func(r chi.Router) {
r.Route("/auth", func(r chi.Router) {
r.Post("/google-instance-identity", api.postAuthenticateWorkspaceAgentUsingGoogleInstanceIdentity)
r.Post("/google-instance-identity", api.postWorkspaceAuthGoogleInstanceIdentity)
})
r.Route("/me", func(r chi.Router) {
r.Route("/agent", func(r chi.Router) {
r.Use(httpmw.ExtractWorkspaceAgent(options.Database))
r.Get("/listen", nil)
r.Get("/", api.workspaceAgentListen)
})
r.Route("/{workspaceresource}", func(r chi.Router) {
r.Use(
httpmw.ExtractAPIKey(options.Database, nil),
httpmw.ExtractWorkspaceResourceParam(options.Database),
httpmw.ExtractWorkspaceParam(options.Database),
)
r.Get("/", api.workspaceResource)
r.Get("/dial", api.workspaceResourceDial)
})
})
r.Route("/workspaces/{workspace}", func(r chi.Router) {
r.Use(
httpmw.ExtractAPIKey(options.Database, nil),
httpmw.ExtractWorkspaceParam(options.Database),
httpmw.ExtractUserParam(options.Database),
)
r.Get("/", nil)
r.Get("/builds", nil)
r.Post("/builds", nil)
r.Get("/", api.workspace)
r.Route("/builds", func(r chi.Router) {
r.Get("/", api.workspaceBuilds)
r.Post("/", api.postWorkspaceBuilds)
r.Get("/latest", api.workspaceBuildLatest)
r.Get("/{workspacebuildname}", api.workspaceBuildByName)
})
})
r.Route("/workspacebuilds/{workspacebuild}", func(r chi.Router) {
r.Use(
httpmw.ExtractAPIKey(options.Database, nil),
httpmw.ExtractWorkspaceBuildParam(options.Database),
httpmw.ExtractWorkspaceParam(options.Database),
)
r.Get("/logs", nil)
r.Get("/resources", nil)
r.Route("/resources/{workspaceresource}", func(r chi.Router) {
r.Use(httpmw.ExtractWorkspaceResourceParam(options.Database))
r.Get("/", nil)
r.Get("/dial", nil)
})
r.Get("/", api.workspaceBuild)
r.Get("/logs", api.workspaceBuildLogs)
r.Get("/resources", api.workspaceBuildResources)
})
})
r.NotFound(site.Handler(options.Logger).ServeHTTP)
Expand Down
76 changes: 59 additions & 17 deletions coderd/coderdtest/coderdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ func NewProvisionerDaemon(t *testing.T, client *codersdk.Client) io.Closer {

// CreateFirstUser creates a user with preset credentials and authenticates
// with the passed in codersdk client.
func CreateFirstUser(t *testing.T, client *codersdk.Client) coderd.CreateUserResponse {
req := coderd.CreateUserRequest{
func CreateFirstUser(t *testing.T, client *codersdk.Client) coderd.CreateFirstUserResponse {
req := coderd.CreateFirstUserRequest{
Email: "[email protected]",
Username: "testuser",
Password: "testpass",
Expand All @@ -161,6 +161,28 @@ func CreateFirstUser(t *testing.T, client *codersdk.Client) coderd.CreateUserRes
return resp
}

// CreateAnotherUser creates and authenticates a new user.
func CreateAnotherUser(t *testing.T, client *codersdk.Client, organization string) *codersdk.Client {
req := coderd.CreateUserRequest{
Email: namesgenerator.GetRandomName(1) + "@coder.com",
Username: randomUsername(),
Password: "testpass",
OrganizationID: organization,
}
_, err := client.CreateUser(context.Background(), req)
require.NoError(t, err)

login, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
Email: req.Email,
Password: req.Password,
})
require.NoError(t, err)

other := codersdk.New(client.URL)
other.SessionToken = login.SessionToken
return other
}

// CreateProjectVersion creates a project import provisioner job
// with the responses provided. It uses the "echo" provisioner for compatibility
// with testing.
Expand Down Expand Up @@ -190,27 +212,47 @@ func CreateProject(t *testing.T, client *codersdk.Client, organization string, v
}

// AwaitProjectImportJob awaits for an import job to reach completed status.
func AwaitProjectImportJob(t *testing.T, client *codersdk.Client, organization string, job uuid.UUID) coderd.ProvisionerJob {
var provisionerJob coderd.ProvisionerJob
// require.Eventually(t, func() bool {
// var err error
// provisionerJob, err = client.ProjectImportJob(context.Background(), organization, job)
// require.NoError(t, err)
// return provisionerJob.Status.Completed()
// }, 5*time.Second, 25*time.Millisecond)
return provisionerJob
func AwaitProjectVersionJob(t *testing.T, client *codersdk.Client, version uuid.UUID) coderd.ProjectVersion {
var projectVersion coderd.ProjectVersion
require.Eventually(t, func() bool {
var err error
projectVersion, err = client.ProjectVersion(context.Background(), version)
require.NoError(t, err)
return projectVersion.Job.CompletedAt != nil
}, 5*time.Second, 25*time.Millisecond)
return projectVersion
}

// AwaitWorkspaceBuildJob waits for a workspace provision job to reach completed status.
func AwaitWorkspaceBuildJob(t *testing.T, client *codersdk.Client, build uuid.UUID) coderd.WorkspaceBuild {
var workspaceBuild coderd.WorkspaceBuild
require.Eventually(t, func() bool {
var err error
workspaceBuild, err = client.WorkspaceBuild(context.Background(), build)
require.NoError(t, err)
return workspaceBuild.Job.CompletedAt != nil
}, 5*time.Second, 25*time.Millisecond)
return workspaceBuild
}

// AwaitWorkspaceProvisionJob awaits for a workspace provision job to reach completed status.
func AwaitWorkspaceProvisionJob(t *testing.T, client *codersdk.Client, organization string, job uuid.UUID) coderd.ProvisionerJob {
var provisionerJob coderd.ProvisionerJob
// AwaitWorkspaceAgents waits for all resources with agents to be connected.
func AwaitWorkspaceAgents(t *testing.T, client *codersdk.Client, build uuid.UUID) []coderd.WorkspaceResource {
var resources []coderd.WorkspaceResource
require.Eventually(t, func() bool {
var err error
provisionerJob, err = client.WorkspaceProvisionJob(context.Background(), organization, job)
resources, err = client.WorkspaceResourcesByBuild(context.Background(), build)
require.NoError(t, err)
return provisionerJob.Status.Completed()
for _, resource := range resources {
if resource.Agent == nil {
continue
}
if resource.Agent.UpdatedAt.IsZero() {
return false
}
}
return true
}, 5*time.Second, 25*time.Millisecond)
return provisionerJob
return resources
}

// CreateWorkspace creates a workspace for the user and project provided.
Expand Down
11 changes: 6 additions & 5 deletions coderd/coderdtest/coderdtest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ func TestNew(t *testing.T) {
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
closer := coderdtest.NewProvisionerDaemon(t, client)
job := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitProjectImportJob(t, client, user.OrganizationID, job.ID)
project := coderdtest.CreateProject(t, client, user.OrganizationID, job.ID)
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)
history, err := client.CreateWorkspaceBuild(context.Background(), "me", workspace.Name, coderd.CreateWorkspaceBuildRequest{
build, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, coderd.CreateWorkspaceBuildRequest{
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.NoError(t, err)
coderdtest.AwaitWorkspaceProvisionJob(t, client, user.OrganizationID, history.ProvisionJobID)
coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
coderdtest.AwaitWorkspaceAgents(t, client, build.ID)
closer.Close()
}
24 changes: 13 additions & 11 deletions coderd/organizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ type CreateProjectRequest struct {
VersionID uuid.UUID `json:"project_version_id" validate:"required"`
}

func (api *api) organization(rw http.ResponseWriter, r *http.Request) {
organization := httpmw.OrganizationParam(r)
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertOrganization(organization))
}

func (api *api) provisionerDaemonsByOrganization(rw http.ResponseWriter, r *http.Request) {
daemons, err := api.Database.GetProvisionerDaemons(r.Context())
if errors.Is(err, sql.ErrNoRows) {
Expand Down Expand Up @@ -109,6 +115,7 @@ func (api *api) postProjectVersionsByOrganization(rw http.ResponseWriter, r *htt
}

var projectVersion database.ProjectVersion
var provisionerJob database.ProvisionerJob
err = api.Database.InTx(func(db database.Store) error {
jobID := uuid.New()
for _, parameterValue := range req.ParameterValues {
Expand All @@ -128,7 +135,7 @@ func (api *api) postProjectVersionsByOrganization(rw http.ResponseWriter, r *htt
}
}

job, err := api.Database.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{
provisionerJob, err = api.Database.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{
ID: jobID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
Expand Down Expand Up @@ -160,7 +167,7 @@ func (api *api) postProjectVersionsByOrganization(rw http.ResponseWriter, r *htt
UpdatedAt: database.Now(),
Name: namesgenerator.GetRandomName(1),
Description: "",
JobID: job.ID,
JobID: provisionerJob.ID,
})
if err != nil {
return xerrors.Errorf("insert project version: %w", err)
Expand All @@ -175,7 +182,7 @@ func (api *api) postProjectVersionsByOrganization(rw http.ResponseWriter, r *htt
}

render.Status(r, http.StatusCreated)
render.JSON(rw, r, convertProjectVersion(projectVersion))
render.JSON(rw, r, convertProjectVersion(projectVersion, convertProvisionerJob(provisionerJob)))
}

// Create a new project in an organization.
Expand Down Expand Up @@ -227,29 +234,24 @@ func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Reque

var project Project
err = api.Database.InTx(func(db database.Store) error {
projectVersionID := uuid.New()
dbProject, err := db.InsertProject(r.Context(), database.InsertProjectParams{
ID: uuid.New(),
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
OrganizationID: organization.ID,
Name: createProject.Name,
Provisioner: importJob.Provisioner,
ActiveVersionID: projectVersionID,
ActiveVersionID: projectVersion.ID,
})
if err != nil {
return xerrors.Errorf("insert project: %s", err)
}
_, err = db.InsertProjectVersion(r.Context(), database.InsertProjectVersionParams{
ID: projectVersionID,
err = db.UpdateProjectVersionByID(r.Context(), database.UpdateProjectVersionByIDParams{
ID: projectVersion.ID,
ProjectID: uuid.NullUUID{
UUID: dbProject.ID,
Valid: true,
},
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
Name: namesgenerator.GetRandomName(1),
JobID: importJob.ID,
})
if err != nil {
return xerrors.Errorf("insert project version: %s", err)
Expand Down
5 changes: 3 additions & 2 deletions coderd/organizations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import (
"net/http"
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/require"

"github.com/coder/coder/coderd"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/database"
"github.com/coder/coder/provisioner/echo"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)

func TestProvisionerDaemonsByOrganization(t *testing.T) {
Expand Down
Loading