2022-03-07 17:40:54 +00:00
package cli
import (
2022-03-25 16:34:45 +00:00
"context"
2022-06-17 05:54:45 +00:00
"fmt"
2022-03-28 19:31:03 +00:00
"net/http"
2022-06-06 13:38:33 +00:00
_ "net/http/pprof" //nolint: gosec
2022-03-07 17:40:54 +00:00
"net/url"
2022-05-02 16:36:51 +00:00
"os"
"path/filepath"
2022-06-17 16:51:46 +00:00
"runtime"
2022-03-25 16:34:45 +00:00
"time"
2022-03-07 17:40:54 +00:00
2022-03-25 19:48:08 +00:00
"cloud.google.com/go/compute/metadata"
2022-03-07 17:40:54 +00:00
"github.com/spf13/cobra"
"golang.org/x/xerrors"
2022-06-24 15:25:01 +00:00
"gopkg.in/natefinch/lumberjack.v2"
2022-03-07 17:40:54 +00:00
2022-03-22 19:17:50 +00:00
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
2022-03-07 17:40:54 +00:00
"github.com/coder/coder/agent"
2022-06-17 16:51:46 +00:00
"github.com/coder/coder/agent/reaper"
2022-08-31 15:33:50 +00:00
"github.com/coder/coder/buildinfo"
2022-03-28 19:26:41 +00:00
"github.com/coder/coder/cli/cliflag"
2022-03-07 17:40:54 +00:00
"github.com/coder/coder/codersdk"
)
func workspaceAgent ( ) * cobra . Command {
2022-03-25 19:48:08 +00:00
var (
2022-06-06 13:38:33 +00:00
auth string
pprofEnabled bool
pprofAddress string
2022-06-21 23:01:34 +00:00
noReap bool
2022-03-25 19:48:08 +00:00
)
cmd := & cobra . Command {
2022-03-07 17:40:54 +00:00
Use : "agent" ,
2022-03-25 16:34:45 +00:00
// This command isn't useful to manually execute.
2022-03-07 17:40:54 +00:00
Hidden : true ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2022-04-30 16:40:30 +00:00
rawURL , err := cmd . Flags ( ) . GetString ( varAgentURL )
if err != nil {
return xerrors . Errorf ( "CODER_AGENT_URL must be set: %w" , err )
2022-03-07 17:40:54 +00:00
}
2022-03-25 19:48:08 +00:00
coderURL , err := url . Parse ( rawURL )
2022-03-07 17:40:54 +00:00
if err != nil {
2022-03-25 19:48:08 +00:00
return xerrors . Errorf ( "parse %q: %w" , rawURL , err )
2022-03-07 17:40:54 +00:00
}
2022-05-02 16:36:51 +00:00
logWriter := & lumberjack . Logger {
Filename : filepath . Join ( os . TempDir ( ) , "coder-agent.log" ) ,
MaxSize : 5 , // MB
}
defer logWriter . Close ( )
logger := slog . Make ( sloghuman . Sink ( cmd . ErrOrStderr ( ) ) , sloghuman . Sink ( logWriter ) ) . Leveled ( slog . LevelDebug )
2022-06-17 16:51:46 +00:00
isLinux := runtime . GOOS == "linux"
// Spawn a reaper so that we don't accumulate a ton
// of zombie processes.
2022-06-21 23:01:34 +00:00
if reaper . IsInitProcess ( ) && ! noReap && isLinux {
2022-06-17 16:51:46 +00:00
logger . Info ( cmd . Context ( ) , "spawning reaper process" )
2022-06-21 23:01:34 +00:00
// Do not start a reaper on the child process. It's important
// to do this else we fork bomb ourselves.
args := append ( os . Args , "--no-reap" )
err := reaper . ForkReap ( reaper . WithExecArgs ( args ... ) )
2022-06-17 16:51:46 +00:00
if err != nil {
logger . Error ( cmd . Context ( ) , "failed to reap" , slog . Error ( err ) )
return xerrors . Errorf ( "fork reap: %w" , err )
}
logger . Info ( cmd . Context ( ) , "reaper process exiting" )
return nil
}
2022-08-31 15:33:50 +00:00
version := buildinfo . Version ( )
logger . Info ( cmd . Context ( ) , "starting agent" ,
slog . F ( "url" , coderURL ) ,
slog . F ( "auth" , auth ) ,
slog . F ( "version" , version ) ,
)
2022-03-07 17:40:54 +00:00
client := codersdk . New ( coderURL )
2022-10-24 03:35:08 +00:00
// Set a reasonable timeout so requests can't hang forever!
client . HTTPClient . Timeout = 10 * time . Second
2022-03-28 19:31:03 +00:00
2022-06-06 13:38:33 +00:00
if pprofEnabled {
srvClose := serveHandler ( cmd . Context ( ) , logger , nil , pprofAddress , "pprof" )
defer srvClose ( )
} else {
// If pprof wasn't enabled at startup, allow a
// `kill -USR1 $agent_pid` to start it (on Unix).
srvClose := agentStartPPROFOnUSR1 ( cmd . Context ( ) , logger , pprofAddress )
defer srvClose ( )
}
2022-03-28 19:31:03 +00:00
// exchangeToken returns a session token.
// This is abstracted to allow for the same looping condition
// regardless of instance identity auth type.
var exchangeToken func ( context . Context ) ( codersdk . WorkspaceAgentAuthenticateResponse , error )
2022-03-25 16:34:45 +00:00
switch auth {
case "token" :
2022-04-30 16:40:30 +00:00
token , err := cmd . Flags ( ) . GetString ( varAgentToken )
if err != nil {
return xerrors . Errorf ( "CODER_AGENT_TOKEN must be set for token auth: %w" , err )
2022-03-25 16:34:45 +00:00
}
2022-03-28 19:26:41 +00:00
client . SessionToken = token
2022-03-25 16:34:45 +00:00
case "google-instance-identity" :
2022-03-25 19:48:08 +00:00
// This is *only* done for testing to mock client authentication.
// This will never be set in a production scenario.
var gcpClient * metadata . Client
gcpClientRaw := cmd . Context ( ) . Value ( "gcp-client" )
if gcpClientRaw != nil {
gcpClient , _ = gcpClientRaw . ( * metadata . Client )
}
2022-03-28 19:31:03 +00:00
exchangeToken = func ( ctx context . Context ) ( codersdk . WorkspaceAgentAuthenticateResponse , error ) {
return client . AuthWorkspaceGoogleInstanceIdentity ( ctx , "" , gcpClient )
}
case "aws-instance-identity" :
// This is *only* done for testing to mock client authentication.
// This will never be set in a production scenario.
var awsClient * http . Client
awsClientRaw := cmd . Context ( ) . Value ( "aws-client" )
if awsClientRaw != nil {
awsClient , _ = awsClientRaw . ( * http . Client )
if awsClient != nil {
client . HTTPClient = awsClient
}
}
exchangeToken = func ( ctx context . Context ) ( codersdk . WorkspaceAgentAuthenticateResponse , error ) {
return client . AuthWorkspaceAWSInstanceIdentity ( ctx )
}
case "azure-instance-identity" :
2022-04-19 13:48:13 +00:00
// This is *only* done for testing to mock client authentication.
// This will never be set in a production scenario.
var azureClient * http . Client
azureClientRaw := cmd . Context ( ) . Value ( "azure-client" )
if azureClientRaw != nil {
azureClient , _ = azureClientRaw . ( * http . Client )
if azureClient != nil {
client . HTTPClient = azureClient
}
}
exchangeToken = func ( ctx context . Context ) ( codersdk . WorkspaceAgentAuthenticateResponse , error ) {
return client . AuthWorkspaceAzureInstanceIdentity ( ctx )
}
2022-03-28 19:31:03 +00:00
}
2022-03-25 19:48:08 +00:00
2022-06-17 05:54:45 +00:00
executablePath , err := os . Executable ( )
if err != nil {
return xerrors . Errorf ( "getting os executable: %w" , err )
}
err = os . Setenv ( "PATH" , fmt . Sprintf ( "%s%c%s" , os . Getenv ( "PATH" ) , filepath . ListSeparator , filepath . Dir ( executablePath ) ) )
if err != nil {
return xerrors . Errorf ( "add executable to $PATH: %w" , err )
}
2022-09-01 01:09:44 +00:00
closer := agent . New ( agent . Options {
2022-10-24 03:35:08 +00:00
Client : client ,
Logger : logger ,
2022-11-04 16:44:36 +00:00
ExchangeToken : func ( ctx context . Context ) ( string , error ) {
2022-10-24 03:35:08 +00:00
if exchangeToken == nil {
2022-11-04 16:44:36 +00:00
return client . SessionToken , nil
2022-10-24 03:35:08 +00:00
}
resp , err := exchangeToken ( ctx )
if err != nil {
2022-11-04 16:44:36 +00:00
return "" , err
2022-10-24 03:35:08 +00:00
}
client . SessionToken = resp . SessionToken
2022-11-06 22:46:51 +00:00
return resp . SessionToken , nil
2022-10-24 03:35:08 +00:00
} ,
2022-04-30 16:40:30 +00:00
EnvironmentVariables : map [ string ] string {
2022-11-04 16:44:36 +00:00
"GIT_ASKPASS" : executablePath ,
2022-04-30 16:40:30 +00:00
} ,
2022-04-29 22:30:10 +00:00
} )
2022-03-07 17:40:54 +00:00
<- cmd . Context ( ) . Done ( )
return closer . Close ( )
} ,
}
2022-03-28 19:26:41 +00:00
2022-04-30 16:40:30 +00:00
cliflag . StringVarP ( cmd . Flags ( ) , & auth , "auth" , "" , "CODER_AGENT_AUTH" , "token" , "Specify the authentication type to use for the agent" )
2022-06-06 13:38:33 +00:00
cliflag . BoolVarP ( cmd . Flags ( ) , & pprofEnabled , "pprof-enable" , "" , "CODER_AGENT_PPROF_ENABLE" , false , "Enable serving pprof metrics on the address defined by --pprof-address." )
2022-06-21 23:01:34 +00:00
cliflag . BoolVarP ( cmd . Flags ( ) , & noReap , "no-reap" , "" , "" , false , "Do not start a process reaper." )
2022-06-06 13:38:33 +00:00
cliflag . StringVarP ( cmd . Flags ( ) , & pprofAddress , "pprof-address" , "" , "CODER_AGENT_PPROF_ADDRESS" , "127.0.0.1:6060" , "The address to serve pprof." )
2022-03-25 19:48:08 +00:00
return cmd
2022-03-07 17:40:54 +00:00
}