diff --git a/agent/agent.go b/agent/agent.go index b87cf1827218b..e71affaf13d6c 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -60,6 +60,7 @@ const ( type Options struct { Filesystem afero.Fs + LogDir string TempDir string ExchangeToken func(ctx context.Context) (string, error) Client Client @@ -87,6 +88,12 @@ func New(options Options) io.Closer { if options.TempDir == "" { options.TempDir = os.TempDir() } + if options.LogDir == "" { + if options.TempDir != os.TempDir() { + options.Logger.Debug(context.Background(), "log dir not set, using temp dir", slog.F("temp_dir", options.TempDir)) + } + options.LogDir = options.TempDir + } if options.ExchangeToken == nil { options.ExchangeToken = func(ctx context.Context) (string, error) { return "", nil @@ -102,6 +109,7 @@ func New(options Options) io.Closer { client: options.Client, exchangeToken: options.ExchangeToken, filesystem: options.Filesystem, + logDir: options.LogDir, tempDir: options.TempDir, lifecycleUpdate: make(chan struct{}, 1), } @@ -114,6 +122,7 @@ type agent struct { client Client exchangeToken func(ctx context.Context) (string, error) filesystem afero.Fs + logDir string tempDir string reconnectingPTYs sync.Map @@ -582,7 +591,7 @@ func (a *agent) runStartupScript(ctx context.Context, script string) error { } a.logger.Info(ctx, "running startup script", slog.F("script", script)) - writer, err := a.filesystem.OpenFile(filepath.Join(a.tempDir, "coder-startup-script.log"), os.O_CREATE|os.O_RDWR, 0o600) + writer, err := a.filesystem.OpenFile(filepath.Join(a.logDir, "coder-startup-script.log"), os.O_CREATE|os.O_RDWR, 0o600) if err != nil { return xerrors.Errorf("open startup script log file: %w", err) } diff --git a/cli/agent.go b/cli/agent.go index 89a103464029d..312dc71cfe8cb 100644 --- a/cli/agent.go +++ b/cli/agent.go @@ -29,6 +29,7 @@ import ( func workspaceAgent() *cobra.Command { var ( auth string + logDir string pprofAddress string noReap bool ) @@ -55,7 +56,7 @@ func workspaceAgent() *cobra.Command { // of zombie processes. if reaper.IsInitProcess() && !noReap && isLinux { logWriter := &lumberjack.Logger{ - Filename: filepath.Join(os.TempDir(), "coder-agent-init.log"), + Filename: filepath.Join(logDir, "coder-agent-init.log"), MaxSize: 5, // MB } defer logWriter.Close() @@ -91,7 +92,7 @@ func workspaceAgent() *cobra.Command { go dumpHandler(ctx) logWriter := &lumberjack.Logger{ - Filename: filepath.Join(os.TempDir(), "coder-agent.log"), + Filename: filepath.Join(logDir, "coder-agent.log"), MaxSize: 5, // MB } defer logWriter.Close() @@ -178,6 +179,7 @@ func workspaceAgent() *cobra.Command { closer := agent.New(agent.Options{ Client: client, Logger: logger, + LogDir: logDir, ExchangeToken: func(ctx context.Context) (string, error) { if exchangeToken == nil { return client.SDK.SessionToken(), nil @@ -199,8 +201,9 @@ func workspaceAgent() *cobra.Command { } cliflag.StringVarP(cmd.Flags(), &auth, "auth", "", "CODER_AGENT_AUTH", "token", "Specify the authentication type to use for the agent") - cliflag.BoolVarP(cmd.Flags(), &noReap, "no-reap", "", "", false, "Do not start a process reaper.") + cliflag.StringVarP(cmd.Flags(), &logDir, "log-dir", "", "CODER_AGENT_LOG_DIR", os.TempDir(), "Specify the location for the agent log files") cliflag.StringVarP(cmd.Flags(), &pprofAddress, "pprof-address", "", "CODER_AGENT_PPROF_ADDRESS", "127.0.0.1:6060", "The address to serve pprof.") + cliflag.BoolVarP(cmd.Flags(), &noReap, "no-reap", "", "", false, "Do not start a process reaper.") return cmd } diff --git a/cli/agent_test.go b/cli/agent_test.go index edcab3ac4a0e5..9f5537aca9d2a 100644 --- a/cli/agent_test.go +++ b/cli/agent_test.go @@ -2,6 +2,8 @@ package cli_test import ( "context" + "os" + "path/filepath" "runtime" "strings" "testing" @@ -14,10 +16,70 @@ import ( "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" + "github.com/coder/coder/testutil" ) func TestWorkspaceAgent(t *testing.T) { t.Parallel() + + t.Run("LogDirectory", func(t *testing.T) { + t.Parallel() + + authToken := uuid.NewString() + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionApply: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "somename", + Type: "someinstance", + Agents: []*proto.Agent{{ + Id: uuid.NewString(), + Name: "someagent", + Auth: &proto.Agent_Token{ + Token: authToken, + }, + }}, + }}, + }, + }, + }}, + }) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + logDir := t.TempDir() + cmd, _ := clitest.New(t, + "agent", + "--auth", "token", + "--agent-token", authToken, + "--agent-url", client.URL.String(), + "--log-dir", logDir, + ) + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium) + defer cancel() + errC := make(chan error, 1) + go func() { + errC <- cmd.ExecuteContext(ctx) + }() + coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) + + cancel() + err := <-errC + require.NoError(t, err) + + info, err := os.Stat(filepath.Join(logDir, "coder-agent.log")) + require.NoError(t, err) + require.Greater(t, info.Size(), int64(0)) + }) + t.Run("Azure", func(t *testing.T) { t.Parallel() instanceID := "instanceidentifier" diff --git a/cli/root_test.go b/cli/root_test.go index 39e6ed78cabf1..7f5442233b830 100644 --- a/cli/root_test.go +++ b/cli/root_test.go @@ -53,6 +53,13 @@ func TestCommandHelp(t *testing.T) { "CODER_CACHE_DIRECTORY": "/tmp/coder-cli-test-cache", }, }, + { + name: "coder agent --help", + cmd: []string{"agent", "--help"}, + env: map[string]string{ + "CODER_AGENT_LOG_DIR": "/tmp", + }, + }, } root := cli.Root(cli.AGPL()) diff --git a/cli/testdata/coder_agent_--help.golden b/cli/testdata/coder_agent_--help.golden new file mode 100644 index 0000000000000..ec5e1ab8273f4 --- /dev/null +++ b/cli/testdata/coder_agent_--help.golden @@ -0,0 +1,29 @@ +Usage: + coder agent [flags] + +Flags: + --auth string Specify the authentication type to use for the agent. + Consumes $CODER_AGENT_AUTH (default "token") + -h, --help help for agent + --log-dir string Specify the location for the agent log files. + Consumes $CODER_AGENT_LOG_DIR (default "/tmp") + --no-reap Do not start a process reaper. + --pprof-address string The address to serve pprof. + Consumes $CODER_AGENT_PPROF_ADDRESS (default "127.0.0.1:6060") + +Global Flags: + --global-config coder Path to the global coder config directory. + Consumes $CODER_CONFIG_DIR (default "/tmp/coder-cli-test-config") + --header stringArray HTTP headers added to all requests. Provide as "Key=Value". + Consumes $CODER_HEADER + --no-feature-warning Suppress warnings about unlicensed features. + Consumes $CODER_NO_FEATURE_WARNING + --no-version-warning Suppress warning when client and server versions do not match. + Consumes $CODER_NO_VERSION_WARNING + --token string Specify an authentication token. For security reasons setting + CODER_SESSION_TOKEN is preferred. + Consumes $CODER_SESSION_TOKEN + --url string URL to a deployment. + Consumes $CODER_URL + -v, --verbose Enable verbose output. + Consumes $CODER_VERBOSE