mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: Add GIT_COMMITTER information to agent env vars (#1171)
This makes setting up git a bit simpler, and users can always override these values! We'll probably add a way to disable our Git integration anyways, so these could be part of that.
This commit is contained in:
+24
-15
@@ -33,12 +33,14 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
EnvironmentVariables map[string]string
|
||||
StartupScript string
|
||||
type Metadata struct {
|
||||
OwnerEmail string `json:"owner_email"`
|
||||
OwnerUsername string `json:"owner_username"`
|
||||
EnvironmentVariables map[string]string `json:"environment_variables"`
|
||||
StartupScript string `json:"startup_script"`
|
||||
}
|
||||
|
||||
type Dialer func(ctx context.Context, logger slog.Logger) (*Options, *peerbroker.Listener, error)
|
||||
type Dialer func(ctx context.Context, logger slog.Logger) (Metadata, *peerbroker.Listener, error)
|
||||
|
||||
func New(dialer Dialer, logger slog.Logger) io.Closer {
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
@@ -62,14 +64,16 @@ type agent struct {
|
||||
closed chan struct{}
|
||||
|
||||
// Environment variables sent by Coder to inject for shell sessions.
|
||||
// This is atomic because values can change after reconnect.
|
||||
// These are atomic because values can change after reconnect.
|
||||
envVars atomic.Value
|
||||
ownerEmail atomic.String
|
||||
ownerUsername atomic.String
|
||||
startupScript atomic.Bool
|
||||
sshServer *ssh.Server
|
||||
}
|
||||
|
||||
func (a *agent) run(ctx context.Context) {
|
||||
var options *Options
|
||||
var options Metadata
|
||||
var peerListener *peerbroker.Listener
|
||||
var err error
|
||||
// An exponential back-off occurs when the connection is failing to dial.
|
||||
@@ -95,6 +99,8 @@ func (a *agent) run(ctx context.Context) {
|
||||
default:
|
||||
}
|
||||
a.envVars.Store(options.EnvironmentVariables)
|
||||
a.ownerEmail.Store(options.OwnerEmail)
|
||||
a.ownerUsername.Store(options.OwnerUsername)
|
||||
|
||||
if a.startupScript.CAS(false, true) {
|
||||
// The startup script has not ran yet!
|
||||
@@ -303,8 +309,20 @@ func (a *agent) handleSSHSession(session ssh.Session) error {
|
||||
}
|
||||
cmd := exec.CommandContext(session.Context(), shell, caller, command)
|
||||
cmd.Env = append(os.Environ(), session.Environ()...)
|
||||
executablePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting os executable: %w", err)
|
||||
}
|
||||
// Git on Windows resolves with UNIX-style paths.
|
||||
// If using backslashes, it's unable to find the executable.
|
||||
executablePath = strings.ReplaceAll(executablePath, "\\", "/")
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_SSH_COMMAND=%s gitssh --`, executablePath))
|
||||
// These prevent the user from having to specify _anything_ to successfully commit.
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_COMMITTER_EMAIL=%s`, a.ownerEmail.Load()))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_COMMITTER_NAME=%s`, a.ownerUsername.Load()))
|
||||
|
||||
// Load environment variables passed via the agent.
|
||||
// These should override all variables we manually specify.
|
||||
envVars := a.envVars.Load()
|
||||
if envVars != nil {
|
||||
envVarMap, ok := envVars.(map[string]string)
|
||||
@@ -315,15 +333,6 @@ func (a *agent) handleSSHSession(session ssh.Session) error {
|
||||
}
|
||||
}
|
||||
|
||||
executablePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting os executable: %w", err)
|
||||
}
|
||||
// Git on Windows resolves with UNIX-style paths.
|
||||
// If using backslashes, it's unable to find the executable.
|
||||
executablePath = strings.ReplaceAll(executablePath, "\\", "/")
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_SSH_COMMAND=%s gitssh --`, executablePath))
|
||||
|
||||
sshPty, windowSize, isPty := session.Pty()
|
||||
if isPty {
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", sshPty.Term))
|
||||
|
||||
+10
-13
@@ -40,7 +40,7 @@ func TestAgent(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("SessionExec", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
session := setupSSHSession(t, nil)
|
||||
session := setupSSHSession(t, agent.Metadata{})
|
||||
|
||||
command := "echo test"
|
||||
if runtime.GOOS == "windows" {
|
||||
@@ -53,7 +53,7 @@ func TestAgent(t *testing.T) {
|
||||
|
||||
t.Run("GitSSH", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
session := setupSSHSession(t, nil)
|
||||
session := setupSSHSession(t, agent.Metadata{})
|
||||
command := "sh -c 'echo $GIT_SSH_COMMAND'"
|
||||
if runtime.GOOS == "windows" {
|
||||
command = "cmd.exe /c echo %GIT_SSH_COMMAND%"
|
||||
@@ -71,7 +71,7 @@ func TestAgent(t *testing.T) {
|
||||
// it seems like it could be either.
|
||||
t.Skip("ConPTY appears to be inconsistent on Windows.")
|
||||
}
|
||||
session := setupSSHSession(t, nil)
|
||||
session := setupSSHSession(t, agent.Metadata{})
|
||||
command := "bash"
|
||||
if runtime.GOOS == "windows" {
|
||||
command = "cmd.exe"
|
||||
@@ -131,7 +131,7 @@ func TestAgent(t *testing.T) {
|
||||
|
||||
t.Run("SFTP", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
sshClient, err := setupAgent(t, nil).SSHClient()
|
||||
sshClient, err := setupAgent(t, agent.Metadata{}).SSHClient()
|
||||
require.NoError(t, err)
|
||||
client, err := sftp.NewClient(sshClient)
|
||||
require.NoError(t, err)
|
||||
@@ -148,7 +148,7 @@ func TestAgent(t *testing.T) {
|
||||
t.Parallel()
|
||||
key := "EXAMPLE"
|
||||
value := "value"
|
||||
session := setupSSHSession(t, &agent.Options{
|
||||
session := setupSSHSession(t, agent.Metadata{
|
||||
EnvironmentVariables: map[string]string{
|
||||
key: value,
|
||||
},
|
||||
@@ -166,7 +166,7 @@ func TestAgent(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempPath := filepath.Join(os.TempDir(), "content.txt")
|
||||
content := "somethingnice"
|
||||
setupAgent(t, &agent.Options{
|
||||
setupAgent(t, agent.Metadata{
|
||||
StartupScript: "echo " + content + " > " + tempPath,
|
||||
})
|
||||
var gotContent string
|
||||
@@ -191,7 +191,7 @@ func TestAgent(t *testing.T) {
|
||||
}
|
||||
|
||||
func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exec.Cmd {
|
||||
agentConn := setupAgent(t, nil)
|
||||
agentConn := setupAgent(t, agent.Metadata{})
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
go func() {
|
||||
@@ -219,7 +219,7 @@ func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exe
|
||||
return exec.Command("ssh", args...)
|
||||
}
|
||||
|
||||
func setupSSHSession(t *testing.T, options *agent.Options) *ssh.Session {
|
||||
func setupSSHSession(t *testing.T, options agent.Metadata) *ssh.Session {
|
||||
sshClient, err := setupAgent(t, options).SSHClient()
|
||||
require.NoError(t, err)
|
||||
session, err := sshClient.NewSession()
|
||||
@@ -227,12 +227,9 @@ func setupSSHSession(t *testing.T, options *agent.Options) *ssh.Session {
|
||||
return session
|
||||
}
|
||||
|
||||
func setupAgent(t *testing.T, options *agent.Options) *agent.Conn {
|
||||
if options == nil {
|
||||
options = &agent.Options{}
|
||||
}
|
||||
func setupAgent(t *testing.T, options agent.Metadata) *agent.Conn {
|
||||
client, server := provisionersdk.TransportPipe()
|
||||
closer := agent.New(func(ctx context.Context, logger slog.Logger) (*agent.Options, *peerbroker.Listener, error) {
|
||||
closer := agent.New(func(ctx context.Context, logger slog.Logger) (agent.Metadata, *peerbroker.Listener, error) {
|
||||
listener, err := peerbroker.Listen(server, nil)
|
||||
return options, listener, err
|
||||
}, slogtest.Make(t, nil).Leveled(slog.LevelDebug))
|
||||
|
||||
Reference in New Issue
Block a user