Skip to content

Commit 79be78b

Browse files
Merge master into feature/stepfunctions-workflow
2 parents df1bc98 + f75a2be commit 79be78b

File tree

3 files changed

+38
-14
lines changed

3 files changed

+38
-14
lines changed

packages/core/src/awsService/ec2/model.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ import {
2929
testSshConnection,
3030
} from '../../shared/extensions/ssh'
3131
import { getLogger } from '../../shared/logger/logger'
32-
import { CancellationError, Timeout } from '../../shared/utilities/timeoutUtils'
32+
import { CancellationError, Timeout, waitUntil } from '../../shared/utilities/timeoutUtils'
3333
import { showMessageWithCancel } from '../../shared/utilities/messages'
3434
import { SshConfig } from '../../shared/sshConfig'
3535
import { SshKeyPair } from './sshKeyPair'
3636
import { Ec2SessionTracker } from './remoteSessionManager'
3737
import { getEc2SsmEnv } from './utils'
3838

39-
export type Ec2ConnectErrorCode = 'EC2SSMStatus' | 'EC2SSMPermission' | 'EC2SSMConnect' | 'EC2SSMAgentStatus'
39+
export type Ec2ConnectErrorCode = 'EC2SSMStatus' | 'EC2SSMPermission' | 'EC2SSMTestConnect' | 'EC2SSMAgentStatus'
4040

4141
export interface Ec2RemoteEnv extends VscodeRemoteConnection {
4242
selection: Ec2Selection
@@ -150,8 +150,14 @@ export class Ec2Connecter implements vscode.Disposable {
150150
}
151151
}
152152

153-
private async checkForInstanceSsmError(selection: Ec2Selection): Promise<void> {
154-
const isSsmAgentRunning = (await this.ssmClient.getInstanceAgentPingStatus(selection.instanceId)) === 'Online'
153+
public async checkForInstanceSsmError(
154+
selection: Ec2Selection,
155+
options?: Partial<{ interval: number; timeout: number }>
156+
): Promise<void> {
157+
const isSsmAgentRunning = await waitUntil(
158+
async () => (await this.ssmClient.getInstanceAgentPingStatus(selection.instanceId)) === 'Online',
159+
{ interval: options?.interval ?? 500, timeout: options?.timeout ?? 5000 }
160+
)
155161

156162
if (!isSsmAgentRunning) {
157163
this.throwConnectionError('Is SSM Agent running on the target instance?', selection, {
@@ -215,8 +221,8 @@ export class Ec2Connecter implements vscode.Disposable {
215221
remoteUser.name
216222
)
217223
} catch (err) {
218-
const message = err instanceof SshError ? 'Testing SSH connection to instance failed' : ''
219-
this.throwConnectionError(message, selection, err as Error)
224+
const message = err instanceof SshError ? `Testing SSM connection to instance failed: ${err.message}` : ''
225+
this.throwConnectionError(message, selection, { ...(err as Error), code: 'EC2SSMTestConnect' })
220226
} finally {
221227
await this.ssmClient.terminateSession(testSession)
222228
}

packages/core/src/shared/extensions/ssh.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,27 +130,34 @@ export class RemoteSshSettings extends Settings.define('remote.SSH', remoteSshTy
130130
}
131131
}
132132

133+
/**
134+
* Test a SSH connection over SSM.
135+
* @param ProcessClass given process to test the connection within.
136+
* @param hostname
137+
* @param sshPath
138+
* @param user
139+
* @param session SSM session credentials. These cannot be reused, so it may be required to create a seperate session for the test connection.
140+
* @returns
141+
*/
133142
export async function testSshConnection(
134143
ProcessClass: typeof ChildProcess,
135144
hostname: string,
136145
sshPath: string,
137146
user: string,
138147
session: SSM.StartSessionResponse
139148
): Promise<ChildProcessResult | never> {
149+
const env = { SESSION_ID: session.SessionId, STREAM_URL: session.StreamUrl, TOKEN: session.TokenValue }
150+
const process = new ProcessClass(sshPath, ['-T', `${user}@${hostname}`, 'echo "test connection succeeded" && exit'])
140151
try {
141-
const env = { SESSION_ID: session.SessionId, STREAM_URL: session.StreamUrl, TOKEN: session.TokenValue }
142-
const result = await new ProcessClass(sshPath, [
143-
'-T',
144-
`${user}@${hostname}`,
145-
'echo "test connection succeeded" && exit',
146-
]).run({
152+
return await process.run({
147153
spawnOptions: {
148154
env,
149155
},
150156
})
151-
return result
152157
} catch (error) {
153-
throw new SshError('SSH connection test failed', { cause: error as Error })
158+
throw new SshError(process.result()?.stderr ?? 'An unknown error occurred when testing the connection', {
159+
cause: error as Error,
160+
})
154161
}
155162
}
156163

packages/core/src/test/awsService/ec2/model.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,17 @@ describe('Ec2ConnectClient', function () {
125125
}
126126
})
127127

128+
it('retries if agent status is not online', async function () {
129+
const instanceAgentStatus = sinon.stub(SsmClient.prototype, 'getInstanceAgentPingStatus')
130+
instanceAgentStatus.onFirstCall().resolves('Offline')
131+
instanceAgentStatus.onSecondCall().resolves('Online')
132+
try {
133+
await client.checkForInstanceSsmError(instanceSelection, { interval: 10, timeout: 100 })
134+
} catch (err) {
135+
assert.ok(false, `checkForInstanceSsmError failed with error '${err}'`)
136+
}
137+
})
138+
128139
it('does not throw an error if all checks pass', async function () {
129140
sinon.stub(Ec2Connecter.prototype, 'isInstanceRunning').resolves(true)
130141
sinon.stub(Ec2Connecter.prototype, 'getAttachedIamRole').resolves({ Arn: 'testRole' } as IAM.Role)

0 commit comments

Comments
 (0)