mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
feat: Enable workspace debug logging (#6838)
* feat: Enable workspace debug logging * Fix * Fix * Fix * fix * fix * Enable RBAC * unit tests * Fix * fix * fix * fix * more tests * fix: workspacebuild_test use roles * fix: swagger comment * fix: ctx.Done * fix: address PR comments * break loop
This commit is contained in:
20
coderd/apidoc/docs.go
generated
20
coderd/apidoc/docs.go
generated
@ -6519,6 +6519,17 @@ const docTemplate = `{
|
||||
"dry_run": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"log_level": {
|
||||
"description": "Log level changes the default logging verbosity of a provider (\"info\" if empty).",
|
||||
"enum": [
|
||||
"debug"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.ProvisionerLogLevel"
|
||||
}
|
||||
]
|
||||
},
|
||||
"orphan": {
|
||||
"description": "Orphan may be set for the Destroy transition.",
|
||||
"type": "boolean"
|
||||
@ -7776,6 +7787,15 @@ const docTemplate = `{
|
||||
"ProvisionerJobFailed"
|
||||
]
|
||||
},
|
||||
"codersdk.ProvisionerLogLevel": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"debug"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ProvisionerLogLevelDebug"
|
||||
]
|
||||
},
|
||||
"codersdk.ProvisionerStorageMethod": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
14
coderd/apidoc/swagger.json
generated
14
coderd/apidoc/swagger.json
generated
@ -5803,6 +5803,15 @@
|
||||
"dry_run": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"log_level": {
|
||||
"description": "Log level changes the default logging verbosity of a provider (\"info\" if empty).",
|
||||
"enum": ["debug"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.ProvisionerLogLevel"
|
||||
}
|
||||
]
|
||||
},
|
||||
"orphan": {
|
||||
"description": "Orphan may be set for the Destroy transition.",
|
||||
"type": "boolean"
|
||||
@ -6971,6 +6980,11 @@
|
||||
"ProvisionerJobFailed"
|
||||
]
|
||||
},
|
||||
"codersdk.ProvisionerLogLevel": {
|
||||
"type": "string",
|
||||
"enum": ["debug"],
|
||||
"x-enum-varnames": ["ProvisionerLogLevelDebug"]
|
||||
},
|
||||
"codersdk.ProvisionerStorageMethod": {
|
||||
"type": "string",
|
||||
"enum": ["file"],
|
||||
|
@ -273,6 +273,7 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
|
||||
TemplateName: template.Name,
|
||||
TemplateVersion: templateVersion.Name,
|
||||
},
|
||||
LogLevel: input.LogLevel,
|
||||
},
|
||||
}
|
||||
case database.ProvisionerJobTypeTemplateVersionDryRun:
|
||||
@ -1550,6 +1551,7 @@ type TemplateVersionImportJob struct {
|
||||
type WorkspaceProvisionJob struct {
|
||||
WorkspaceBuildID uuid.UUID `json:"workspace_build_id"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
LogLevel string `json:"log_level,omitempty"`
|
||||
}
|
||||
|
||||
// TemplateVersionDryRunJob is the payload for the "template_version_dry_run" job type.
|
||||
|
@ -545,6 +545,13 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if createBuild.LogLevel != "" && !api.Authorize(r, rbac.ActionUpdate, template) {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Workspace builds with a custom log level are restricted to template authors only.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var workspaceBuild database.WorkspaceBuild
|
||||
var provisionerJob database.ProvisionerJob
|
||||
// This must happen in a transaction to ensure history can be inserted, and
|
||||
@ -582,6 +589,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
workspaceBuildID := uuid.New()
|
||||
input, err := json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
||||
WorkspaceBuildID: workspaceBuildID,
|
||||
LogLevel: string(createBuild.LogLevel),
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("marshal provision job: %w", err)
|
||||
|
@ -12,10 +12,12 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/audit"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
@ -1151,3 +1153,133 @@ func TestMigrateLegacyToRichParameters(t *testing.T) {
|
||||
require.Len(t, buildParameters, 1)
|
||||
require.Equal(t, "carrot", buildParameters[0].Value)
|
||||
}
|
||||
|
||||
func TestWorkspaceBuildDebugMode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("AsRegularUser", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create users
|
||||
templateAuthorClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
templateAuthor := coderdtest.CreateFirstUser(t, templateAuthorClient)
|
||||
regularUserClient, _ := coderdtest.CreateAnotherUser(t, templateAuthorClient, templateAuthor.OrganizationID)
|
||||
|
||||
// Template owner: create a template
|
||||
version := coderdtest.CreateTemplateVersion(t, templateAuthorClient, templateAuthor.OrganizationID, nil)
|
||||
template := coderdtest.CreateTemplate(t, templateAuthorClient, templateAuthor.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJob(t, templateAuthorClient, version.ID)
|
||||
|
||||
// Regular user: create a workspace
|
||||
workspace := coderdtest.CreateWorkspace(t, regularUserClient, templateAuthor.OrganizationID, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, regularUserClient, workspace.LatestBuild.ID)
|
||||
|
||||
// Regular user: try to start a workspace build in debug mode
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
_, err := regularUserClient.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
TemplateVersionID: workspace.LatestBuild.TemplateVersionID,
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
LogLevel: "debug",
|
||||
})
|
||||
|
||||
// Regular user: expect an error
|
||||
require.NotNil(t, err)
|
||||
var sdkError *codersdk.Error
|
||||
isSdkError := xerrors.As(err, &sdkError)
|
||||
require.True(t, isSdkError)
|
||||
require.Contains(t, sdkError.Message, "Workspace builds with a custom log level are restricted to template authors only.")
|
||||
})
|
||||
t.Run("AsTemplateAuthor", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create users
|
||||
adminClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
admin := coderdtest.CreateFirstUser(t, adminClient)
|
||||
templateAdminClient, _ := coderdtest.CreateAnotherUser(t, adminClient, admin.OrganizationID, rbac.RoleTemplateAdmin())
|
||||
|
||||
// Interact as template admin
|
||||
echoResponses := &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.ProvisionComplete,
|
||||
ProvisionApply: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_DEBUG,
|
||||
Output: "want-it",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_TRACE,
|
||||
Output: "dont-want-it",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_DEBUG,
|
||||
Output: "done",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{},
|
||||
},
|
||||
}},
|
||||
}
|
||||
version := coderdtest.CreateTemplateVersion(t, templateAdminClient, admin.OrganizationID, echoResponses)
|
||||
template := coderdtest.CreateTemplate(t, templateAdminClient, admin.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJob(t, templateAdminClient, version.ID)
|
||||
|
||||
// Create workspace
|
||||
workspace := coderdtest.CreateWorkspace(t, templateAdminClient, admin.OrganizationID, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, templateAdminClient, workspace.LatestBuild.ID)
|
||||
|
||||
// Create workspace build
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
build, err := templateAdminClient.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
TemplateVersionID: workspace.LatestBuild.TemplateVersionID,
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
ProvisionerState: []byte(" "),
|
||||
LogLevel: "debug",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
build = coderdtest.AwaitWorkspaceBuildJob(t, templateAdminClient, build.ID)
|
||||
|
||||
// Watch for incoming logs
|
||||
logs, closer, err := templateAdminClient.WorkspaceBuildLogsAfter(ctx, build.ID, 0)
|
||||
require.NoError(t, err)
|
||||
defer closer.Close()
|
||||
|
||||
var logsProcessed int
|
||||
|
||||
processingLogs:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
require.Fail(t, "timeout occurred while processing logs")
|
||||
return
|
||||
case log, ok := <-logs:
|
||||
if !ok {
|
||||
break processingLogs
|
||||
}
|
||||
|
||||
logsProcessed++
|
||||
|
||||
require.NotEqual(t, "dont-want-it", log.Output, "unexpected log message", "%s log message shouldn't be logged: %s")
|
||||
|
||||
if log.Output == "done" {
|
||||
break processingLogs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require.Len(t, echoResponses.ProvisionApply, logsProcessed)
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user