feat: add endpoint for resolving autostart status (#10507)

This commit is contained in:
Jon Ayers
2023-11-08 23:24:56 -06:00
committed by GitHub
parent cf8ee78547
commit e23873ff8f
11 changed files with 425 additions and 0 deletions

43
coderd/apidoc/docs.go generated
View File

@ -6480,6 +6480,41 @@ const docTemplate = `{
}
}
},
"/workspaces/{workspace}/resolve-autostart": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Workspaces"
],
"summary": "Resolve workspace autostart by id.",
"operationId": "resolve-workspace-autostart-by-id",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Workspace ID",
"name": "workspace",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.ResolveAutostartResponse"
}
}
}
}
},
"/workspaces/{workspace}/ttl": {
"put": {
"security": [
@ -9720,6 +9755,14 @@ const docTemplate = `{
}
}
},
"codersdk.ResolveAutostartResponse": {
"type": "object",
"properties": {
"parameter_mismatch": {
"type": "boolean"
}
}
},
"codersdk.ResourceType": {
"type": "string",
"enum": [

View File

@ -5718,6 +5718,37 @@
}
}
},
"/workspaces/{workspace}/resolve-autostart": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Workspaces"],
"summary": "Resolve workspace autostart by id.",
"operationId": "resolve-workspace-autostart-by-id",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Workspace ID",
"name": "workspace",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.ResolveAutostartResponse"
}
}
}
}
},
"/workspaces/{workspace}/ttl": {
"put": {
"security": [
@ -8756,6 +8787,14 @@
}
}
},
"codersdk.ResolveAutostartResponse": {
"type": "object",
"properties": {
"parameter_mismatch": {
"type": "boolean"
}
}
},
"codersdk.ResourceType": {
"type": "string",
"enum": [

View File

@ -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) {

View File

@ -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"
@ -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 {

View File

@ -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"
@ -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

View File

@ -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})
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})