Skip to content

feat: add endpoint for resolving autostart status #10507

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 9 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
43 changes: 43 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,7 @@ func New(options *Options) *API {
r.Put("/extend", api.putExtendWorkspace)
r.Put("/dormant", api.putWorkspaceDormant)
r.Put("/autoupdates", api.putWorkspaceAutoupdates)
r.Get("/resolve-autostart", api.resolveAutostart)
})
})
r.Route("/workspacebuilds/{workspacebuild}", func(r chi.Router) {
Expand Down
14 changes: 14 additions & 0 deletions coderd/database/db2sdk/db2sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/google/uuid"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"

"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/parameter"
Expand All @@ -30,6 +31,19 @@ func WorkspaceBuildParameter(p database.WorkspaceBuildParameter) codersdk.Worksp
}
}

func TemplateVersionParameters(params []database.TemplateVersionParameter) ([]codersdk.TemplateVersionParameter, error) {
out := make([]codersdk.TemplateVersionParameter, len(params))
var err error
for i, p := range params {
out[i], err = TemplateVersionParameter(p)
if err != nil {
return nil, xerrors.Errorf("convert template version parameter %q: %w", p.Name, err)
}
}

return out, nil
}

func TemplateVersionParameter(param database.TemplateVersionParameter) (codersdk.TemplateVersionParameter, error) {
options, err := templateVersionParameterOptions(param.Options)
if err != nil {
Expand Down
95 changes: 95 additions & 0 deletions coderd/workspaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"cdr.dev/slog"
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/db2sdk"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
Expand Down Expand Up @@ -1059,6 +1060,100 @@ func (api *API) putWorkspaceAutoupdates(rw http.ResponseWriter, r *http.Request)
rw.WriteHeader(http.StatusNoContent)
}

// @Summary Resolve workspace autostart by id.
// @ID resolve-workspace-autostart-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Workspaces
// @Param workspace path string true "Workspace ID" format(uuid)
// @Success 200 {object} codersdk.ResolveAutostartResponse
// @Router /workspaces/{workspace}/resolve-autostart [get]
func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
workspace = httpmw.WorkspaceParam(r)
)

template, err := api.Database.GetTemplateByID(ctx, workspace.TemplateID)
if err != nil {
httpapi.InternalServerError(rw, err)
return
}

templateAccessControl := (*(api.AccessControlStore.Load())).GetTemplateAccessControl(template)
useActiveVersion := templateAccessControl.RequireActiveVersion || workspace.AutomaticUpdates == database.AutomaticUpdatesAlways
if !useActiveVersion {
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ResolveAutostartResponse{})
return
}

build, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching latest workspace build.",
Detail: err.Error(),
})
return
}

if build.TemplateVersionID == template.ActiveVersionID {
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ResolveAutostartResponse{})
return
}

version, err := api.Database.GetTemplateVersionByID(ctx, template.ActiveVersionID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching template version.",
Detail: err.Error(),
})
return
}

dbVersionParams, err := api.Database.GetTemplateVersionParameters(ctx, version.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching template version parameters.",
Detail: err.Error(),
})
return
}

dbBuildParams, err := api.Database.GetWorkspaceBuildParameters(ctx, build.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching latest workspace build parameters.",
Detail: err.Error(),
})
return
}

versionParams, err := db2sdk.TemplateVersionParameters(dbVersionParams)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error converting template version parameters.",
Detail: err.Error(),
})
return
}

resolver := codersdk.ParameterResolver{
Rich: db2sdk.WorkspaceBuildParameters(dbBuildParams),
}

var response codersdk.ResolveAutostartResponse
for _, param := range versionParams {
_, err := resolver.ValidateResolve(param, nil)
// There's a parameter mismatch if we get an error back from the
// resolver.
response.ParameterMismatch = err != nil
if response.ParameterMismatch {
break
}
}
httpapi.Write(ctx, rw, http.StatusOK, response)
}

// @Summary Watch workspace by ID
// @ID watch-workspace-by-id
// @Security CoderSessionToken
Expand Down
93 changes: 93 additions & 0 deletions coderd/workspaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,99 @@ func TestWorkspace(t *testing.T) {
})
}

func TestResolveAutostart(t *testing.T) {
t.Parallel()

t.Run("OK", func(t *testing.T) {
t.Parallel()
ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you do this without a ProvisionerDaemon following the new dbfake paradigm introduced here: #10426 ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I can make this change but it's not as trivial as it sounds especially because of parameters. Once I finish up this feature I'm planning on helping refactor existing tests using dbfake. Do you care if we push it for now?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wary of shipping a feature with a "chore" task leftover that we pinky-promise to fix up soon. Once the feature is shipped it's real easy to just put off the chore indefinitely. Even if you created a GitHub ticket, we auto-close stale issues! I'm not questioning your sincerity, but the road to hell is paved with best intentions.

I think debt is a good analogy in this case. You're saying we should take out a loan here. What's so important about shipping this change now such that we want to take the risk of not fixing the tests? Flaky tests are like, the biggest thing on developer's mind judging by our conversation at the offsite.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah agreed, per our offline conversation I'm going to refactor these in a separate PR.

owner := coderdtest.CreateFirstUser(t, ownerClient)
version1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version1.ID)
template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, version1.ID)

params := &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Response{
{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
Parameters: []*proto.RichParameter{
{
Name: "param",
Description: "param",
Required: true,
Mutable: true,
},
},
},
},
},
},
ProvisionApply: echo.ApplyComplete,
}
version2 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, params, func(ctvr *codersdk.CreateTemplateVersionRequest) {
ctvr.TemplateID = template.ID
})
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version2.ID)

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()

client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
workspace := coderdtest.CreateWorkspace(t, client, owner.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.AutomaticUpdates = codersdk.AutomaticUpdatesAlways
})
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)

err := ownerClient.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
ID: version2.ID,
})
require.NoError(t, err)

// Autostart shouldn't be possible if parameters do not match.
resp, err := client.ResolveAutostart(ctx, workspace.ID.String())
require.NoError(t, err)
require.True(t, resp.ParameterMismatch)

update, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
TemplateVersionID: version2.ID,
Transition: codersdk.WorkspaceTransitionStart,
RichParameterValues: []codersdk.WorkspaceBuildParameter{
{
Name: "param",
Value: "Hello",
},
},
})
require.NoError(t, err)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, update.ID)

// We should be able to autostart since parameters are updated.
resp, err = client.ResolveAutostart(ctx, workspace.ID.String())
require.NoError(t, err)
require.False(t, resp.ParameterMismatch)

// Create one last version where the parameters are the same as the previous
// version.
version3 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, params, func(ctvr *codersdk.CreateTemplateVersionRequest) {
ctvr.TemplateID = template.ID
})
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version3.ID)

err = ownerClient.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
ID: version3.ID,
})
require.NoError(t, err)

// Even though we're out of date we should still be able to autostart
// since parameters resolve.
resp, err = client.ResolveAutostart(ctx, workspace.ID.String())
require.NoError(t, err)
require.False(t, resp.ParameterMismatch)
})
}

func TestAdminViewAllWorkspaces(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
Expand Down
17 changes: 17 additions & 0 deletions codersdk/workspaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,23 @@ func (c *Client) WorkspaceQuota(ctx context.Context, userID string) (WorkspaceQu
return quota, json.NewDecoder(res.Body).Decode(&quota)
}

type ResolveAutostartResponse struct {
ParameterMismatch bool `json:"parameter_mismatch"`
}

func (c *Client) ResolveAutostart(ctx context.Context, workspaceID string) (ResolveAutostartResponse, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s/resolve-autostart", workspaceID), nil)
if err != nil {
return ResolveAutostartResponse{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return ResolveAutostartResponse{}, ReadBodyAsError(res)
}
var response ResolveAutostartResponse
return response, json.NewDecoder(res.Body).Decode(&response)
}

// WorkspaceNotifyChannel is the PostgreSQL NOTIFY
// channel to listen for updates on. The payload is empty,
// because the size of a workspace payload can be very large.
Expand Down
14 changes: 14 additions & 0 deletions docs/api/schemas.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading