feat: added include_deleted to getWorkspaceByOwnerAndName (#2164)

* feat: added include_deleted

relates to #1955

* Update coderd/workspaces.go

defining vars in the scope of conditional

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>

* Update coderd/workspaces.go

avoid newline

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>

* Update coderd/workspaces.go

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>

* PR feedback

* wrote test, added type

* Update coderd/workspaces_test.go

shortening test name

Co-authored-by: Cian Johnston <cian@coder.com>

* taking out api.ts change for now

* casing

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
Co-authored-by: Cian Johnston <cian@coder.com>
This commit is contained in:
Kira Pilot
2022-06-08 14:04:05 -04:00
committed by GitHub
parent 85821568a9
commit 3bc122b7d5
7 changed files with 72 additions and 7 deletions
+1
View File
@@ -83,6 +83,7 @@
"workspaceapp",
"workspaceapps",
"workspacebuilds",
"workspacename",
"wsconncache",
"xerrors",
"xstate",
+2 -2
View File
@@ -49,7 +49,7 @@ func create() *cobra.Command {
workspaceName, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: "Specify a name for your workspace:",
Validate: func(workspaceName string) error {
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName)
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceByOwnerAndNameParams{})
if err == nil {
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
}
@@ -61,7 +61,7 @@ func create() *cobra.Command {
}
}
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName)
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceByOwnerAndNameParams{})
if err == nil {
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
}
+1 -1
View File
@@ -214,7 +214,7 @@ func namedWorkspace(cmd *cobra.Command, client *codersdk.Client, identifier stri
return codersdk.Workspace{}, xerrors.Errorf("invalid workspace name: %q", identifier)
}
return client.WorkspaceByOwnerAndName(cmd.Context(), owner, name)
return client.WorkspaceByOwnerAndName(cmd.Context(), owner, name, codersdk.WorkspaceByOwnerAndNameParams{})
}
// createConfig consumes the global configuration flag to produce a config root.
+22
View File
@@ -165,10 +165,32 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
owner := httpmw.UserParam(r)
workspaceName := chi.URLParam(r, "workspacename")
includeDeleted := false
if s := r.URL.Query().Get("include_deleted"); s != "" {
var err error
includeDeleted, err = strconv.ParseBool(s)
if err != nil {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: fmt.Sprintf("Invalid boolean value %q for \"include_deleted\" query param.", s),
Validations: []httpapi.Error{
{Field: "include_deleted", Detail: "Must be a valid boolean"},
},
})
return
}
}
workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
OwnerID: owner.ID,
Name: workspaceName,
})
if includeDeleted && errors.Is(err, sql.ErrNoRows) {
workspace, err = api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
OwnerID: owner.ID,
Name: workspaceName,
Deleted: includeDeleted,
})
}
if errors.Is(err, sql.ErrNoRows) {
// Do not leak information if the workspace exists or not
httpapi.Forbidden(rw)
+31 -2
View File
@@ -258,7 +258,7 @@ func TestWorkspaceByOwnerAndName(t *testing.T) {
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
_, err := client.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, "something")
_, err := client.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, "something", codersdk.WorkspaceByOwnerAndNameParams{})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
@@ -271,9 +271,38 @@ func TestWorkspaceByOwnerAndName(t *testing.T) {
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
_, err := client.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, workspace.Name)
_, err := client.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, workspace.Name, codersdk.WorkspaceByOwnerAndNameParams{})
require.NoError(t, err)
})
t.Run("Deleted", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
// Given:
// We delete the workspace
build, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
Transition: codersdk.WorkspaceTransitionDelete,
})
require.NoError(t, err, "delete the workspace")
coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
// Then:
// When we call without includes_deleted, we don't expect to get the workspace back
_, err = client.WorkspaceByOwnerAndName(context.Background(), workspace.OwnerName, workspace.Name, codersdk.WorkspaceByOwnerAndNameParams{})
require.ErrorContains(t, err, "403")
// Then:
// When we call with includes_deleted, we should get the workspace back
workspaceNew, err := client.WorkspaceByOwnerAndName(context.Background(), workspace.OwnerName, workspace.Name, codersdk.WorkspaceByOwnerAndNameParams{IncludeDeleted: true})
require.NoError(t, err)
require.Equal(t, workspace.ID, workspaceNew.ID)
})
}
func TestWorkspaceFilter(t *testing.T) {
+10 -2
View File
@@ -258,9 +258,17 @@ func (c *Client) Workspaces(ctx context.Context, filter WorkspaceFilter) ([]Work
return workspaces, json.NewDecoder(res.Body).Decode(&workspaces)
}
type WorkspaceByOwnerAndNameParams struct {
IncludeDeleted bool `json:"include_deleted,omitempty"`
}
// WorkspaceByOwnerAndName returns a workspace by the owner's UUID and the workspace's name.
func (c *Client) WorkspaceByOwnerAndName(ctx context.Context, owner string, name string) (Workspace, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/workspace/%s", owner, name), nil)
func (c *Client) WorkspaceByOwnerAndName(ctx context.Context, owner string, name string, params WorkspaceByOwnerAndNameParams) (Workspace, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/workspace/%s", owner, name), nil, func(r *http.Request) {
q := r.URL.Query()
q.Set("include_deleted", fmt.Sprintf("%t", params.IncludeDeleted))
r.URL.RawQuery = q.Encode()
})
if err != nil {
return Workspace{}, err
}
+5
View File
@@ -463,6 +463,11 @@ export interface WorkspaceBuildsRequest extends Pagination {
readonly WorkspaceID: string
}
// From codersdk/workspaces.go:261:6
export interface WorkspaceByOwnerAndNameParams {
readonly include_deleted?: boolean
}
// From codersdk/workspaces.go:219:6
export interface WorkspaceFilter {
readonly organization_id?: string