Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit68b72ed

Browse files
AbhineetJainkylecarbs
authored andcommitted
feat: update build url to @username/workspace/builds/buildnumber (#2234)
* update build url to @username/workspace/builds/buildnumber* update errors thrown from the API* add unit tests for the new API* add t.parallel* get username and workspace name from params
1 parent118cc4c commit68b72ed

File tree

16 files changed

+305
-48
lines changed

16 files changed

+305
-48
lines changed

‎coderd/coderd.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,10 @@ func New(options *Options) *API {
270270
r.Get("/",api.organizationsByUser)
271271
r.Get("/{organizationname}",api.organizationByUserAndName)
272272
})
273-
r.Get("/workspace/{workspacename}",api.workspaceByOwnerAndName)
273+
r.Route("/workspace/{workspacename}",func(r chi.Router) {
274+
r.Get("/",api.workspaceByOwnerAndName)
275+
r.Get("/builds/{buildnumber}",api.workspaceBuildByBuildNumber)
276+
})
274277
r.Get("/gitsshkey",api.gitSSHKey)
275278
r.Put("/gitsshkey",api.regenerateGitSSHKey)
276279
})

‎coderd/coderd_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"io"
66
"net/http"
7+
"strconv"
78
"strings"
89
"testing"
910
"time"
@@ -163,6 +164,10 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
163164
AssertObject:rbac.ResourceWorkspace,
164165
AssertAction:rbac.ActionRead,
165166
},
167+
"GET:/api/v2/users/me/workspace/{workspacename}/builds/{buildnumber}": {
168+
AssertObject:rbac.ResourceWorkspace,
169+
AssertAction:rbac.ActionRead,
170+
},
166171
"GET:/api/v2/workspaces/{workspace}/builds/{workspacebuildname}": {
167172
AssertAction:rbac.ActionRead,
168173
AssertObject:workspaceRBACObj,
@@ -388,6 +393,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
388393
route=strings.ReplaceAll(route,"{workspacename}",workspace.Name)
389394
route=strings.ReplaceAll(route,"{workspacebuildname}",workspace.LatestBuild.Name)
390395
route=strings.ReplaceAll(route,"{workspaceagent}",workspaceResources[0].Agents[0].ID.String())
396+
route=strings.ReplaceAll(route,"{buildnumber}",strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber),10))
391397
route=strings.ReplaceAll(route,"{template}",template.ID.String())
392398
route=strings.ReplaceAll(route,"{hash}",file.Hash)
393399
route=strings.ReplaceAll(route,"{workspaceresource}",workspaceResources[0].ID.String())

‎coderd/database/databasefake/databasefake.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,22 @@ func (q *fakeQuerier) GetWorkspaceBuildByWorkspaceIDAndName(_ context.Context, a
625625
return database.WorkspaceBuild{},sql.ErrNoRows
626626
}
627627

628+
func (q*fakeQuerier)GetWorkspaceBuildByWorkspaceIDAndBuildNumber(_ context.Context,arg database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (database.WorkspaceBuild,error) {
629+
q.mutex.RLock()
630+
deferq.mutex.RUnlock()
631+
632+
for_,workspaceBuild:=rangeq.workspaceBuilds {
633+
ifworkspaceBuild.WorkspaceID.String()!=arg.WorkspaceID.String() {
634+
continue
635+
}
636+
ifworkspaceBuild.BuildNumber!=arg.BuildNumber {
637+
continue
638+
}
639+
returnworkspaceBuild,nil
640+
}
641+
return database.WorkspaceBuild{},sql.ErrNoRows
642+
}
643+
628644
func (q*fakeQuerier)GetWorkspacesByOrganizationIDs(_ context.Context,req database.GetWorkspacesByOrganizationIDsParams) ([]database.Workspace,error) {
629645
q.mutex.RLock()
630646
deferq.mutex.RUnlock()

‎coderd/database/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/queries.sql.go

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/queries/workspacebuilds.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ WHERE
2727
workspace_id= $1
2828
AND"name"= $2;
2929

30+
-- name: GetWorkspaceBuildByWorkspaceIDAndBuildNumber :one
31+
SELECT
32+
*
33+
FROM
34+
workspace_builds
35+
WHERE
36+
workspace_id= $1
37+
AND build_number= $2;
38+
3039
-- name: GetWorkspaceBuildByWorkspaceID :many
3140
SELECT
3241
*

‎coderd/workspacebuilds.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"net/http"
9+
"strconv"
910

1011
"github.com/go-chi/chi/v5"
1112
"github.com/google/uuid"
@@ -160,6 +161,82 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) {
160161
httpapi.Write(rw,http.StatusOK,apiBuilds)
161162
}
162163

164+
func (api*API)workspaceBuildByBuildNumber(rw http.ResponseWriter,r*http.Request) {
165+
owner:=httpmw.UserParam(r)
166+
workspaceName:=chi.URLParam(r,"workspacename")
167+
buildNumber,err:=strconv.ParseInt(chi.URLParam(r,"buildnumber"),10,32)
168+
iferr!=nil {
169+
httpapi.Write(rw,http.StatusBadRequest, httpapi.Response{
170+
Message:"Failed to parse build number as integer.",
171+
Detail:err.Error(),
172+
})
173+
return
174+
}
175+
176+
workspace,err:=api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
177+
OwnerID:owner.ID,
178+
Name:workspaceName,
179+
})
180+
iferrors.Is(err,sql.ErrNoRows) {
181+
httpapi.Write(rw,http.StatusNotFound, httpapi.Response{
182+
Message:fmt.Sprintf("Workspace %q does not exist.",workspaceName),
183+
})
184+
return
185+
}
186+
iferr!=nil {
187+
httpapi.Write(rw,http.StatusInternalServerError, httpapi.Response{
188+
Message:"Internal error fetching workspace by name.",
189+
Detail:err.Error(),
190+
})
191+
return
192+
}
193+
194+
if!api.Authorize(rw,r,rbac.ActionRead,rbac.ResourceWorkspace.
195+
InOrg(workspace.OrganizationID).WithOwner(workspace.OwnerID.String()).WithID(workspace.ID.String())) {
196+
return
197+
}
198+
199+
workspaceBuild,err:=api.Database.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(r.Context(), database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{
200+
WorkspaceID:workspace.ID,
201+
BuildNumber:int32(buildNumber),
202+
})
203+
iferrors.Is(err,sql.ErrNoRows) {
204+
httpapi.Write(rw,http.StatusNotFound, httpapi.Response{
205+
Message:fmt.Sprintf("Workspace %q Build %d does not exist.",workspaceName,buildNumber),
206+
})
207+
return
208+
}
209+
iferr!=nil {
210+
httpapi.Write(rw,http.StatusInternalServerError, httpapi.Response{
211+
Message:"Internal error fetching workspace build.",
212+
Detail:err.Error(),
213+
})
214+
return
215+
}
216+
217+
job,err:=api.Database.GetProvisionerJobByID(r.Context(),workspaceBuild.JobID)
218+
iferr!=nil {
219+
httpapi.Write(rw,http.StatusInternalServerError, httpapi.Response{
220+
Message:"Internal error fetching provisioner job.",
221+
Detail:err.Error(),
222+
})
223+
return
224+
}
225+
226+
users,err:=api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{workspace.OwnerID,workspaceBuild.InitiatorID})
227+
iferr!=nil {
228+
httpapi.Write(rw,http.StatusInternalServerError, httpapi.Response{
229+
Message:"Internal error fetching user.",
230+
Detail:err.Error(),
231+
})
232+
return
233+
}
234+
235+
httpapi.Write(rw,http.StatusOK,
236+
convertWorkspaceBuild(findUser(workspace.OwnerID,users),findUser(workspaceBuild.InitiatorID,users),
237+
workspace,workspaceBuild,job))
238+
}
239+
163240
func (api*API)workspaceBuildByName(rw http.ResponseWriter,r*http.Request) {
164241
workspace:=httpmw.WorkspaceParam(r)
165242
if!api.Authorize(rw,r,rbac.ActionRead,rbac.ResourceWorkspace.

‎coderd/workspacebuilds_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package coderd_test
22

33
import (
44
"context"
5+
"fmt"
56
"net/http"
7+
"strconv"
68
"testing"
79
"time"
810

@@ -28,6 +30,94 @@ func TestWorkspaceBuild(t *testing.T) {
2830
require.NoError(t,err)
2931
}
3032

33+
funcTestWorkspaceBuildByBuildNumber(t*testing.T) {
34+
t.Parallel()
35+
t.Run("Successful",func(t*testing.T) {
36+
t.Parallel()
37+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
38+
first:=coderdtest.CreateFirstUser(t,client)
39+
user,err:=client.User(context.Background(),codersdk.Me)
40+
require.NoError(t,err,"fetch me")
41+
version:=coderdtest.CreateTemplateVersion(t,client,first.OrganizationID,nil)
42+
template:=coderdtest.CreateTemplate(t,client,first.OrganizationID,version.ID)
43+
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
44+
workspace:=coderdtest.CreateWorkspace(t,client,first.OrganizationID,template.ID)
45+
_,err=client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
46+
context.Background(),
47+
user.Username,
48+
workspace.Name,
49+
strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber),10),
50+
)
51+
require.NoError(t,err)
52+
})
53+
54+
t.Run("BuildNumberNotInt",func(t*testing.T) {
55+
t.Parallel()
56+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
57+
first:=coderdtest.CreateFirstUser(t,client)
58+
user,err:=client.User(context.Background(),codersdk.Me)
59+
require.NoError(t,err,"fetch me")
60+
version:=coderdtest.CreateTemplateVersion(t,client,first.OrganizationID,nil)
61+
template:=coderdtest.CreateTemplate(t,client,first.OrganizationID,version.ID)
62+
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
63+
workspace:=coderdtest.CreateWorkspace(t,client,first.OrganizationID,template.ID)
64+
_,err=client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
65+
context.Background(),
66+
user.Username,
67+
workspace.Name,
68+
"buildNumber",
69+
)
70+
varapiError*codersdk.Error
71+
require.ErrorAs(t,err,&apiError)
72+
require.Equal(t,http.StatusBadRequest,apiError.StatusCode())
73+
require.ErrorContains(t,apiError,"Failed to parse build number as integer.")
74+
})
75+
76+
t.Run("WorkspaceNotFound",func(t*testing.T) {
77+
t.Parallel()
78+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
79+
first:=coderdtest.CreateFirstUser(t,client)
80+
user,err:=client.User(context.Background(),codersdk.Me)
81+
require.NoError(t,err,"fetch me")
82+
version:=coderdtest.CreateTemplateVersion(t,client,first.OrganizationID,nil)
83+
template:=coderdtest.CreateTemplate(t,client,first.OrganizationID,version.ID)
84+
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
85+
workspace:=coderdtest.CreateWorkspace(t,client,first.OrganizationID,template.ID)
86+
_,err=client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
87+
context.Background(),
88+
user.Username,
89+
"workspaceName",
90+
strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber),10),
91+
)
92+
varapiError*codersdk.Error
93+
require.ErrorAs(t,err,&apiError)
94+
require.Equal(t,http.StatusNotFound,apiError.StatusCode())
95+
require.ErrorContains(t,apiError,"Workspace\"workspaceName\" does not exist.")
96+
})
97+
98+
t.Run("WorkspaceBuildNotFound",func(t*testing.T) {
99+
t.Parallel()
100+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
101+
first:=coderdtest.CreateFirstUser(t,client)
102+
user,err:=client.User(context.Background(),codersdk.Me)
103+
require.NoError(t,err,"fetch me")
104+
version:=coderdtest.CreateTemplateVersion(t,client,first.OrganizationID,nil)
105+
template:=coderdtest.CreateTemplate(t,client,first.OrganizationID,version.ID)
106+
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
107+
workspace:=coderdtest.CreateWorkspace(t,client,first.OrganizationID,template.ID)
108+
_,err=client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
109+
context.Background(),
110+
user.Username,
111+
workspace.Name,
112+
"200",
113+
)
114+
varapiError*codersdk.Error
115+
require.ErrorAs(t,err,&apiError)
116+
require.Equal(t,http.StatusNotFound,apiError.StatusCode())
117+
require.ErrorContains(t,apiError,fmt.Sprintf("Workspace %q Build 200 does not exist.",workspace.Name))
118+
})
119+
}
120+
31121
funcTestWorkspaceBuilds(t*testing.T) {
32122
t.Parallel()
33123
t.Run("Single",func(t*testing.T) {

‎codersdk/workspacebuilds.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,16 @@ func (c *Client) WorkspaceBuildState(ctx context.Context, build uuid.UUID) ([]by
103103
}
104104
returnio.ReadAll(res.Body)
105105
}
106+
107+
func (c*Client)WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(ctx context.Context,usernamestring,workspaceNamestring,buildNumberstring) (WorkspaceBuild,error) {
108+
res,err:=c.Request(ctx,http.MethodGet,fmt.Sprintf("/api/v2/users/%s/workspace/%s/builds/%s",username,workspaceName,buildNumber),nil)
109+
iferr!=nil {
110+
returnWorkspaceBuild{},err
111+
}
112+
deferres.Body.Close()
113+
ifres.StatusCode!=http.StatusOK {
114+
returnWorkspaceBuild{},readBodyAsError(res)
115+
}
116+
varworkspaceBuildWorkspaceBuild
117+
returnworkspaceBuild,json.NewDecoder(res.Body).Decode(&workspaceBuild)
118+
}

‎site/src/AppRouter.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,6 @@ export const AppRouter: FC = () => (
113113
<Routepath="ssh-keys"element={<SSHKeysPage/>}/>
114114
</Route>
115115

116-
<Route
117-
path="builds/:buildId"
118-
element={
119-
<AuthAndFrame>
120-
<WorkspaceBuildPage/>
121-
</AuthAndFrame>
122-
}
123-
/>
124-
125116
<Routepath="/@:username">
126117
<Routepath=":workspace">
127118
<Route
@@ -160,6 +151,15 @@ export const AppRouter: FC = () => (
160151
}
161152
/>
162153
</Route>
154+
155+
<Route
156+
path="builds/:buildNumber"
157+
element={
158+
<AuthAndFrame>
159+
<WorkspaceBuildPage/>
160+
</AuthAndFrame>
161+
}
162+
/>
163163
</Route>
164164
</Route>
165165

‎site/src/api/api.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,14 @@ export const getWorkspaceBuilds = async (workspaceId: string): Promise<TypesGen.
268268
returnresponse.data
269269
}
270270

271-
exportconstgetWorkspaceBuild=async(workspaceId:string):Promise<TypesGen.WorkspaceBuild>=>{
272-
constresponse=awaitaxios.get<TypesGen.WorkspaceBuild>(`/api/v2/workspacebuilds/${workspaceId}`)
271+
exportconstgetWorkspaceBuildByNumber=async(
272+
username="me",
273+
workspaceName:string,
274+
buildNumber:string,
275+
):Promise<TypesGen.WorkspaceBuild>=>{
276+
constresponse=awaitaxios.get<TypesGen.WorkspaceBuild>(
277+
`/api/v2/users/${username}/workspace/${workspaceName}/builds/${buildNumber}`,
278+
)
273279
returnresponse.data
274280
}
275281

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp