88"fmt"
99"hash/fnv"
1010"io"
11+ "maps"
1112"net"
1213"net/http"
1314"net/netip"
@@ -70,16 +71,21 @@ const (
7071)
7172
7273type Options struct {
73- Filesystem afero.Fs
74- LogDir string
75- TempDir string
76- ScriptDataDir string
77- Client Client
78- ReconnectingPTYTimeout time.Duration
79- EnvironmentVariables map [string ]string
80- Logger slog.Logger
81- IgnorePorts map [int ]string
82- PortCacheDuration time.Duration
74+ Filesystem afero.Fs
75+ LogDir string
76+ TempDir string
77+ ScriptDataDir string
78+ Client Client
79+ ReconnectingPTYTimeout time.Duration
80+ EnvironmentVariables map [string ]string
81+ Logger slog.Logger
82+ // IgnorePorts tells the api handler which ports to ignore when
83+ // listing all listening ports. This is helpful to hide ports that
84+ // are used by the agent, that the user does not care about.
85+ IgnorePorts map [int ]string
86+ // ListeningPortsGetter is used to get the list of listening ports. Only
87+ // tests should set this. If unset, a default that queries the OS will be used.
88+ ListeningPortsGetter ListeningPortsGetter
8389SSHMaxTimeout time.Duration
8490TailnetListenPort uint16
8591Subsystems []codersdk.AgentSubsystem
@@ -137,9 +143,7 @@ func New(options Options) Agent {
137143if options .ServiceBannerRefreshInterval == 0 {
138144options .ServiceBannerRefreshInterval = 2 * time .Minute
139145}
140- if options .PortCacheDuration == 0 {
141- options .PortCacheDuration = 1 * time .Second
142- }
146+
143147if options .Clock == nil {
144148options .Clock = quartz .NewReal ()
145149}
@@ -153,30 +157,38 @@ func New(options Options) Agent {
153157options .Execer = agentexec .DefaultExecer
154158}
155159
160+ if options .ListeningPortsGetter == nil {
161+ options .ListeningPortsGetter = & osListeningPortsGetter {
162+ cacheDuration :1 * time .Second ,
163+ }
164+ }
165+
156166hardCtx ,hardCancel := context .WithCancel (context .Background ())
157167gracefulCtx ,gracefulCancel := context .WithCancel (hardCtx )
158168a := & agent {
159- clock :options .Clock ,
160- tailnetListenPort :options .TailnetListenPort ,
161- reconnectingPTYTimeout :options .ReconnectingPTYTimeout ,
162- logger :options .Logger ,
163- gracefulCtx :gracefulCtx ,
164- gracefulCancel :gracefulCancel ,
165- hardCtx :hardCtx ,
166- hardCancel :hardCancel ,
167- coordDisconnected :make (chan struct {}),
168- environmentVariables :options .EnvironmentVariables ,
169- client :options .Client ,
170- filesystem :options .Filesystem ,
171- logDir :options .LogDir ,
172- tempDir :options .TempDir ,
173- scriptDataDir :options .ScriptDataDir ,
174- lifecycleUpdate :make (chan struct {},1 ),
175- lifecycleReported :make (chan codersdk.WorkspaceAgentLifecycle ,1 ),
176- lifecycleStates : []agentsdk.PostLifecycleRequest {{State :codersdk .WorkspaceAgentLifecycleCreated }},
177- reportConnectionsUpdate :make (chan struct {},1 ),
178- ignorePorts :options .IgnorePorts ,
179- portCacheDuration :options .PortCacheDuration ,
169+ clock :options .Clock ,
170+ tailnetListenPort :options .TailnetListenPort ,
171+ reconnectingPTYTimeout :options .ReconnectingPTYTimeout ,
172+ logger :options .Logger ,
173+ gracefulCtx :gracefulCtx ,
174+ gracefulCancel :gracefulCancel ,
175+ hardCtx :hardCtx ,
176+ hardCancel :hardCancel ,
177+ coordDisconnected :make (chan struct {}),
178+ environmentVariables :options .EnvironmentVariables ,
179+ client :options .Client ,
180+ filesystem :options .Filesystem ,
181+ logDir :options .LogDir ,
182+ tempDir :options .TempDir ,
183+ scriptDataDir :options .ScriptDataDir ,
184+ lifecycleUpdate :make (chan struct {},1 ),
185+ lifecycleReported :make (chan codersdk.WorkspaceAgentLifecycle ,1 ),
186+ lifecycleStates : []agentsdk.PostLifecycleRequest {{State :codersdk .WorkspaceAgentLifecycleCreated }},
187+ reportConnectionsUpdate :make (chan struct {},1 ),
188+ listeningPortsHandler :listeningPortsHandler {
189+ getter :options .ListeningPortsGetter ,
190+ ignorePorts :maps .Clone (options .IgnorePorts ),
191+ },
180192reportMetadataInterval :options .ReportMetadataInterval ,
181193announcementBannersRefreshInterval :options .ServiceBannerRefreshInterval ,
182194sshMaxTimeout :options .SSHMaxTimeout ,
@@ -202,20 +214,16 @@ func New(options Options) Agent {
202214}
203215
204216type agent struct {
205- clock quartz.Clock
206- logger slog.Logger
207- client Client
208- tailnetListenPort uint16
209- filesystem afero.Fs
210- logDir string
211- tempDir string
212- scriptDataDir string
213- // ignorePorts tells the api handler which ports to ignore when
214- // listing all listening ports. This is helpful to hide ports that
215- // are used by the agent, that the user does not care about.
216- ignorePorts map [int ]string
217- portCacheDuration time.Duration
218- subsystems []codersdk.AgentSubsystem
217+ clock quartz.Clock
218+ logger slog.Logger
219+ client Client
220+ tailnetListenPort uint16
221+ filesystem afero.Fs
222+ logDir string
223+ tempDir string
224+ scriptDataDir string
225+ listeningPortsHandler listeningPortsHandler
226+ subsystems []codersdk.AgentSubsystem
219227
220228reconnectingPTYTimeout time.Duration
221229reconnectingPTYServer * reconnectingpty.Server