(options: {
coderService: CoderService;
getArchiveBehavior: () => CoderWorkspaceArchiveBehavior;
timeoutMs?: number;
stoppingWaitTimeoutMs?: number;
stoppingPollIntervalMs?: number;
})
| 126 | } |
| 127 | |
| 128 | export function createCoderUnarchiveHook(options: { |
| 129 | coderService: CoderService; |
| 130 | getArchiveBehavior: () => CoderWorkspaceArchiveBehavior; |
| 131 | timeoutMs?: number; |
| 132 | stoppingWaitTimeoutMs?: number; |
| 133 | stoppingPollIntervalMs?: number; |
| 134 | }): AfterUnarchiveHook { |
| 135 | const timeoutMs = options.timeoutMs ?? DEFAULT_START_TIMEOUT_MS; |
| 136 | |
| 137 | return async ({ workspaceId, workspaceMetadata }): Promise<Result<void>> => { |
| 138 | const runtimeConfig = workspaceMetadata.runtimeConfig; |
| 139 | if (!isSSHRuntime(runtimeConfig) || !runtimeConfig.coder) { |
| 140 | return Ok(undefined); |
| 141 | } |
| 142 | |
| 143 | const coder = runtimeConfig.coder; |
| 144 | |
| 145 | // Important safety invariant: |
| 146 | // Only start Coder workspaces that mux created (dedicated workspaces). If the user connected |
| 147 | // mux to an existing Coder workspace, unarchiving in mux should *not* start their environment. |
| 148 | if (coder.existingWorkspace === true) { |
| 149 | return Ok(undefined); |
| 150 | } |
| 151 | |
| 152 | const workspaceName = coder.workspaceName?.trim(); |
| 153 | if (!workspaceName) { |
| 154 | return Ok(undefined); |
| 155 | } |
| 156 | |
| 157 | if (options.getArchiveBehavior() !== "stop") { |
| 158 | return Ok(undefined); |
| 159 | } |
| 160 | |
| 161 | let status = await options.coderService.getWorkspaceStatus(workspaceName, { |
| 162 | timeoutMs: DEFAULT_STATUS_TIMEOUT_MS, |
| 163 | }); |
| 164 | |
| 165 | // Unarchive can happen immediately after archive, while the Coder workspace is still |
| 166 | // transitioning through "stopping". Starting during that transition can fail, so we |
| 167 | // best-effort poll briefly until it reaches a terminal state. |
| 168 | if (status.kind === "ok" && status.status === "stopping") { |
| 169 | const waitTimeoutMs = options.stoppingWaitTimeoutMs ?? DEFAULT_STOPPING_WAIT_TIMEOUT_MS; |
| 170 | const pollIntervalMs = options.stoppingPollIntervalMs ?? DEFAULT_STOPPING_POLL_INTERVAL_MS; |
| 171 | const deadlineMs = Date.now() + waitTimeoutMs; |
| 172 | |
| 173 | log.debug( |
| 174 | "Coder workspace is still stopping after mux unarchive; waiting briefly before starting", |
| 175 | { |
| 176 | workspaceId, |
| 177 | coderWorkspaceName: workspaceName, |
| 178 | waitTimeoutMs, |
| 179 | pollIntervalMs, |
| 180 | } |
| 181 | ); |
| 182 | |
| 183 | while (status.kind === "ok" && status.status === "stopping") { |
| 184 | const remainingMs = deadlineMs - Date.now(); |
| 185 | if (remainingMs <= 0) { |
no test coverage detected