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:
Marcin Tojek
2023-03-30 16:00:33 +02:00
committed by GitHub
parent 665b84de0d
commit 0ba200c2a1
18 changed files with 610 additions and 288 deletions

20
coderd/apidoc/docs.go generated
View File

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

View File

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

View 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.

View File

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

View File

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