Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commiteec7378

Browse files
committed
feat(cli): add capability for SSH command to connect to a running container
1 parentcccdf1e commiteec7378

File tree

4 files changed

+153
-12
lines changed

4 files changed

+153
-12
lines changed

‎agent/agent.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,8 @@ func (a *agent) init() {
307307

308308
returna.reportConnection(id,connectionType,ip)
309309
},
310+
311+
ExperimentalContainersEnabled:a.experimentalDevcontainersEnabled,
310312
})
311313
iferr!=nil {
312314
panic(err)

‎agent/agentssh/agentssh.go

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929

3030
"cdr.dev/slog"
3131

32+
"github.com/coder/coder/v2/agent/agentcontainers"
3233
"github.com/coder/coder/v2/agent/agentexec"
3334
"github.com/coder/coder/v2/agent/agentrsa"
3435
"github.com/coder/coder/v2/agent/usershell"
@@ -104,6 +105,9 @@ type Config struct {
104105
BlockFileTransferbool
105106
// ReportConnection.
106107
ReportConnectionreportConnectionFunc
108+
// Experimental: allow connecting to running containers if
109+
// CODER_AGENT_DEVCONTAINERS_ENABLE=true.
110+
ExperimentalContainersEnabledbool
107111
}
108112

109113
typeServerstruct {
@@ -324,6 +328,22 @@ func (s *sessionCloseTracker) Close() error {
324328
returns.Session.Close()
325329
}
326330

331+
funcextractContainerInfo(env []string) (container,containerUserstring,filteredEnv []string) {
332+
for_,kv:=rangeenv {
333+
ifstrings.HasPrefix(kv,"CODER_CONTAINER=") {
334+
container=strings.TrimPrefix(kv,"CODER_CONTAINER=")
335+
}
336+
337+
ifstrings.HasPrefix(kv,"CODER_CONTAINER_USER=") {
338+
containerUser=strings.TrimPrefix(kv,"CODER_CONTAINER_USER=")
339+
}
340+
}
341+
342+
returncontainer,containerUser,slices.DeleteFunc(env,func(kvstring)bool {
343+
returnstrings.HasPrefix(kv,"CODER_CONTAINER=")||strings.HasPrefix(kv,"CODER_CONTAINER_USER=")
344+
})
345+
}
346+
327347
func (s*Server)sessionHandler(session ssh.Session) {
328348
ctx:=session.Context()
329349
id:=uuid.New()
@@ -353,6 +373,13 @@ func (s *Server) sessionHandler(session ssh.Session) {
353373
defers.trackSession(session,false)
354374

355375
reportSession:=true
376+
377+
container,containerUser,env:=extractContainerInfo(env)
378+
s.logger.Debug(ctx,"container info",
379+
slog.F("container",container),
380+
slog.F("container_user",containerUser),
381+
)
382+
356383
switchmagicType {
357384
caseMagicSessionTypeVSCode:
358385
s.connCountVSCode.Add(1)
@@ -398,6 +425,10 @@ func (s *Server) sessionHandler(session ssh.Session) {
398425
switchss:=session.Subsystem();ss {
399426
case"":
400427
case"sftp":
428+
ifs.config.ExperimentalContainersEnabled&&container!="" {
429+
closeCause("sftp not yet supported with containers")
430+
return
431+
}
401432
err:=s.sftpHandler(logger,session)
402433
iferr!=nil {
403434
closeCause(err.Error())
@@ -422,7 +453,7 @@ func (s *Server) sessionHandler(session ssh.Session) {
422453
env=append(env,fmt.Sprintf("DISPLAY=localhost:%d.%d",display,x11.ScreenNumber))
423454
}
424455

425-
err:=s.sessionStart(logger,session,env,magicType)
456+
err:=s.sessionStart(logger,session,env,magicType,container,containerUser)
426457
varexitError*exec.ExitError
427458
ifxerrors.As(err,&exitError) {
428459
code:=exitError.ExitCode()
@@ -495,30 +526,35 @@ func (s *Server) fileTransferBlocked(session ssh.Session) bool {
495526
returnfalse
496527
}
497528

498-
func (s*Server)sessionStart(logger slog.Logger,session ssh.Session,env []string,magicTypeMagicSessionType) (retErrerror) {
529+
func (s*Server)sessionStart(logger slog.Logger,session ssh.Session,env []string,magicTypeMagicSessionType,container,containerUserstring) (retErrerror) {
499530
ctx:=session.Context()
500531

501532
magicTypeLabel:=magicTypeMetricLabel(magicType)
502533
sshPty,windowSize,isPty:=session.Pty()
534+
ptyLabel:="no"
535+
ifisPty {
536+
ptyLabel="yes"
537+
}
503538

504-
cmd,err:=s.CreateCommand(ctx,session.RawCommand(),env,nil)
505-
iferr!=nil {
506-
ptyLabel:="no"
507-
ifisPty {
508-
ptyLabel="yes"
539+
// plumb in envinfoer here to modify command for container exec?
540+
varei usershell.EnvInfoer
541+
varerrerror
542+
ifs.config.ExperimentalContainersEnabled&&container!="" {
543+
ei,err=agentcontainers.EnvInfo(ctx,s.Execer,container,containerUser)
544+
iferr!=nil {
545+
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel,ptyLabel,"container_env_info").Add(1)
546+
returnerr
509547
}
548+
}
549+
cmd,err:=s.CreateCommand(ctx,session.RawCommand(),env,ei)
550+
iferr!=nil {
510551
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel,ptyLabel,"create_command").Add(1)
511552
returnerr
512553
}
513554

514555
ifssh.AgentRequested(session) {
515556
l,err:=ssh.NewAgentListener()
516557
iferr!=nil {
517-
ptyLabel:="no"
518-
ifisPty {
519-
ptyLabel="yes"
520-
}
521-
522558
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel,ptyLabel,"listener").Add(1)
523559
returnxerrors.Errorf("new agent listener: %w",err)
524560
}

‎cli/ssh.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ func (r *RootCmd) ssh() *serpent.Command {
7676
appearanceConfig codersdk.AppearanceConfig
7777
networkInfoDirstring
7878
networkInfoInterval time.Duration
79+
80+
containerstring
81+
containerUserstring
7982
)
8083
client:=new(codersdk.Client)
8184
cmd:=&serpent.Command{
@@ -454,6 +457,17 @@ func (r *RootCmd) ssh() *serpent.Command {
454457
}
455458
}
456459

460+
ifcontainer!="" {
461+
fork,v:=rangemap[string]string{
462+
"CODER_CONTAINER":container,
463+
"CODER_CONTAINER_USER":containerUser,
464+
} {
465+
iferr:=sshSession.Setenv(k,v);err!=nil {
466+
returnxerrors.Errorf("setenv: %w",err)
467+
}
468+
}
469+
}
470+
457471
err=sshSession.RequestPty("xterm-256color",128,128, gossh.TerminalModes{})
458472
iferr!=nil {
459473
returnxerrors.Errorf("request pty: %w",err)
@@ -594,6 +608,20 @@ func (r *RootCmd) ssh() *serpent.Command {
594608
Default:"5s",
595609
Value:serpent.DurationOf(&networkInfoInterval),
596610
},
611+
{
612+
Flag:"container",
613+
FlagShorthand:"c",
614+
Description:"Specifies a container inside the workspace to connect to.",
615+
Value:serpent.StringOf(&container),
616+
Hidden:true,// Hidden until this features is at least in beta.
617+
},
618+
{
619+
Flag:"container-user",
620+
FlagShorthand:"u",
621+
Description:"When connecting to a container, specifies the user to connect as.",
622+
Value:serpent.StringOf(&containerUser),
623+
Hidden:true,// Hidden until this features is at least in beta.
624+
},
597625
sshDisableAutostartOption(serpent.BoolOf(&disableAutostart)),
598626
}
599627
returncmd

‎cli/ssh_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"time"
2525

2626
"github.com/google/uuid"
27+
"github.com/ory/dockertest/v3"
28+
"github.com/ory/dockertest/v3/docker"
2729
"github.com/spf13/afero"
2830
"github.com/stretchr/testify/assert"
2931
"github.com/stretchr/testify/require"
@@ -1924,6 +1926,79 @@ Expire-Date: 0
19241926
<-cmdDone
19251927
}
19261928

1929+
funcTestSSH_Container(t*testing.T) {
1930+
t.Parallel()
1931+
1932+
t.Run("OK",func(t*testing.T) {
1933+
t.Parallel()
1934+
ifruntime.GOOS!="linux" {
1935+
t.Skip("Skipping test on non-Linux platform")
1936+
}
1937+
1938+
client,workspace,agentToken:=setupWorkspaceForAgent(t)
1939+
ctx:=testutil.Context(t,testutil.WaitLong)
1940+
pool,err:=dockertest.NewPool("")
1941+
require.NoError(t,err,"Could not connect to docker")
1942+
ct,err:=pool.RunWithOptions(&dockertest.RunOptions{
1943+
Repository:"busybox",
1944+
Tag:"latest",
1945+
Cmd: []string{"sleep","infnity"},
1946+
},func(config*docker.HostConfig) {
1947+
config.AutoRemove=true
1948+
config.RestartPolicy= docker.RestartPolicy{Name:"no"}
1949+
})
1950+
require.NoError(t,err,"Could not start container")
1951+
// Wait for container to start
1952+
require.Eventually(t,func()bool {
1953+
ct,ok:=pool.ContainerByName(ct.Container.Name)
1954+
returnok&&ct.Container.State.Running
1955+
},testutil.WaitShort,testutil.IntervalSlow,"Container did not start in time")
1956+
t.Cleanup(func() {
1957+
err:=pool.Purge(ct)
1958+
require.NoError(t,err,"Could not stop container")
1959+
})
1960+
1961+
inv,root:=clitest.New(t,"ssh",workspace.Name,"-c",ct.Container.ID)
1962+
clitest.SetupConfig(t,client,root)
1963+
ptty:=ptytest.New(t).Attach(inv)
1964+
1965+
cmdDone:=tGo(t,func() {
1966+
err:=inv.WithContext(ctx).Run()
1967+
assert.NoError(t,err)
1968+
})
1969+
1970+
_=agenttest.New(t,client.URL,agentToken,func(o*agent.Options) {
1971+
o.ExperimentalContainersEnabled=true
1972+
})
1973+
_=coderdtest.NewWorkspaceAgentWaiter(t,client,workspace.ID).Wait()
1974+
1975+
ptty.ExpectMatch(" #")
1976+
ptty.WriteLine("hostname")
1977+
ptty.ExpectMatch(ct.Container.Config.Hostname)
1978+
ptty.WriteLine("exit")
1979+
<-cmdDone
1980+
})
1981+
1982+
t.Run("NotFound",func(t*testing.T) {
1983+
t.Parallel()
1984+
ifruntime.GOOS!="linux" {
1985+
t.Skip("Skipping test on non-Linux platform")
1986+
}
1987+
1988+
ctx:=testutil.Context(t,testutil.WaitShort)
1989+
client,workspace,agentToken:=setupWorkspaceForAgent(t)
1990+
_=agenttest.New(t,client.URL,agentToken,func(o*agent.Options) {
1991+
o.ExperimentalContainersEnabled=true
1992+
})
1993+
_=coderdtest.NewWorkspaceAgentWaiter(t,client,workspace.ID).Wait()
1994+
1995+
inv,root:=clitest.New(t,"ssh",workspace.Name,"-c",uuid.NewString())
1996+
clitest.SetupConfig(t,client,root)
1997+
err:=inv.WithContext(ctx).Run()
1998+
require.Error(t,err)// TODO(Cian): nicer error message?
1999+
})
2000+
}
2001+
19272002
// tGoContext runs fn in a goroutine passing a context that will be
19282003
// canceled on test completion and wait until fn has finished executing.
19292004
// Done and cancel are returned for optionally waiting until completion

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp