Skip to content

refactor(sdkv3): migrate ssm client #6137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 42 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f464309
move in main file
Hweinstock Nov 22, 2024
afc0a26
add test file
Hweinstock Nov 22, 2024
be3c42a
add more detail to telemetry emitted
Hweinstock Nov 27, 2024
89ef745
add a default retry strategy
Hweinstock Nov 27, 2024
4890f73
use class type instead of interface
Hweinstock Dec 2, 2024
ccfd462
give up type info so that it compiles
Hweinstock Dec 2, 2024
0675afe
refactor types to have any middleware
Hweinstock Dec 2, 2024
3e8caf6
remove interface
Hweinstock Dec 2, 2024
67784e1
remove unused types
Hweinstock Dec 2, 2024
1d29b18
simplify naming
Hweinstock Dec 2, 2024
4500cdc
rename test file
Hweinstock Dec 2, 2024
4e2dffe
change name to temp
Hweinstock Dec 2, 2024
470e0f5
switch name to lower case
Hweinstock Dec 2, 2024
fb605f1
implement general wrapper
Hweinstock Dec 3, 2024
239b597
add new ssm client
Hweinstock Dec 3, 2024
f02c897
migrate ssm use cases
Hweinstock Dec 3, 2024
3b123af
try manually adjusting versions in the streaming client
Hweinstock Dec 6, 2024
2ba6bf0
bump smithy-client version
Hweinstock Dec 6, 2024
6fc31e5
Merge branch 'feature/sdkv3' into sdkv3/startMigration
Hweinstock Jan 8, 2025
8512617
merge in upstream changes
Hweinstock Jan 8, 2025
a63255f
undo font char changes
Hweinstock Jan 8, 2025
80efaa3
undo workaround
Hweinstock Jan 8, 2025
6a04c98
add link to relevant issue
Hweinstock Jan 9, 2025
58809be
move dep to core module
Hweinstock Jan 14, 2025
3e3a01e
merge in upstream changes
Hweinstock Jan 14, 2025
4880c16
improve middleware typing
Hweinstock Jan 14, 2025
c312676
merge in upstream changes
Hweinstock Jan 14, 2025
6dc2405
remove redundant type
Hweinstock Jan 14, 2025
9cd4afa
make types more specific
Hweinstock Jan 14, 2025
35d1b36
Merge branch 'sdkv3/startMigration' into sdkv3/migrateSSM
Hweinstock Jan 14, 2025
13f4d03
improve types further
Hweinstock Jan 14, 2025
222ff04
improve typing
Hweinstock Jan 14, 2025
32a7230
Merge branch 'sdkv3/startMigration' into sdkv3/migrateSSM
Hweinstock Jan 14, 2025
3a37405
loosen type to fix error
Hweinstock Jan 14, 2025
829ef71
merge: resolve conflicts
Hweinstock Jan 27, 2025
1cdbd87
types: slightly improve types
Hweinstock Jan 27, 2025
3b50424
refactor: small cleanup
Hweinstock Jan 27, 2025
5904a28
refactor: rename wrappers to clients
Hweinstock Jan 28, 2025
efea2a2
refactor: improving typing constraints
Hweinstock Jan 28, 2025
8f736e1
merge: resolve conflicts
Hweinstock Feb 11, 2025
c04f2f9
fix: avoid index.ts imports
Hweinstock Feb 11, 2025
c10dc63
merge: fix import conflict
Hweinstock Feb 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,834 changes: 1,367 additions & 467 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@
"@aws-sdk/property-provider": "3.46.0",
"@aws-sdk/smithy-client": "^3.46.0",
"@aws-sdk/util-arn-parser": "^3.46.0",
"@aws-sdk/client-ssm": "^3.699.0",
"@aws/mynah-ui": "^4.22.1",
"@gerhobbelt/gitignore-parser": "^0.2.0-9",
"@iarna/toml": "^2.2.5",
Expand Down
34 changes: 17 additions & 17 deletions packages/core/src/awsService/ec2/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
* SPDX-License-Identifier: Apache-2.0
*/
import * as vscode from 'vscode'
import { Session } from 'aws-sdk/clients/ssm'
import { EC2, IAM, SSM } from 'aws-sdk'
import { EC2, IAM } from 'aws-sdk'
import { Ec2Selection } from './prompter'
import { getOrInstallCli } from '../../shared/utilities/cliUtils'
import { isCloud9 } from '../../shared/extensionUtilities'
import { ToolkitError } from '../../shared/errors'
import { SsmClient } from '../../shared/clients/ssmClient'
import { SsmClient } from '../../shared/clients/ssm'
import { Ec2Client } from '../../shared/clients/ec2Client'
import {
VscodeRemoteConnection,
Expand All @@ -35,13 +34,14 @@ import { SshConfig } from '../../shared/sshConfig'
import { SshKeyPair } from './sshKeyPair'
import { Ec2SessionTracker } from './remoteSessionManager'
import { getEc2SsmEnv } from './utils'
import { Session, StartSessionResponse } from '@aws-sdk/client-ssm'

export type Ec2ConnectErrorCode = 'EC2SSMStatus' | 'EC2SSMPermission' | 'EC2SSMConnect' | 'EC2SSMAgentStatus'

export interface Ec2RemoteEnv extends VscodeRemoteConnection {
selection: Ec2Selection
keyPair: SshKeyPair
ssmSession: SSM.StartSessionResponse
ssmSession: StartSessionResponse
}

export type Ec2OS = 'Amazon Linux' | 'Ubuntu' | 'macOS'
Expand All @@ -51,7 +51,7 @@ interface RemoteUser {
}

export class Ec2Connecter implements vscode.Disposable {
protected ssmClient: SsmClient
protected ssm: SsmClient
protected ec2Client: Ec2Client
protected iamClient: DefaultIamClient
protected sessionManager: Ec2SessionTracker
Expand All @@ -65,10 +65,10 @@ export class Ec2Connecter implements vscode.Disposable {
)

public constructor(readonly regionCode: string) {
this.ssmClient = this.createSsmSdkClient()
this.ssm = this.createSsmSdkClient()
this.ec2Client = this.createEc2SdkClient()
this.iamClient = this.createIamSdkClient()
this.sessionManager = new Ec2SessionTracker(regionCode, this.ssmClient)
this.sessionManager = new Ec2SessionTracker(regionCode, this.ssm)
}

protected createSsmSdkClient(): SsmClient {
Expand All @@ -83,7 +83,7 @@ export class Ec2Connecter implements vscode.Disposable {
return new DefaultIamClient(this.regionCode)
}

public async addActiveSession(sessionId: SSM.SessionId, instanceId: EC2.InstanceId): Promise<void> {
public async addActiveSession(sessionId: string, instanceId: EC2.InstanceId): Promise<void> {
await this.sessionManager.addSession(instanceId, sessionId)
}

Expand Down Expand Up @@ -151,7 +151,7 @@ export class Ec2Connecter implements vscode.Disposable {
}

private async checkForInstanceSsmError(selection: Ec2Selection): Promise<void> {
const isSsmAgentRunning = (await this.ssmClient.getInstanceAgentPingStatus(selection.instanceId)) === 'Online'
const isSsmAgentRunning = (await this.ssm.getInstanceAgentPingStatus(selection.instanceId)) === 'Online'

if (!isSsmAgentRunning) {
this.throwConnectionError('Is SSM Agent running on the target instance?', selection, {
Expand All @@ -178,15 +178,15 @@ export class Ec2Connecter implements vscode.Disposable {
shellArgs: shellArgs,
}

await openRemoteTerminal(terminalOptions, () => this.ssmClient.terminateSession(session)).catch((err) => {
await openRemoteTerminal(terminalOptions, () => this.ssm.terminateSession(session)).catch((err) => {
throw ToolkitError.chain(err, 'Failed to open ec2 instance.')
})
}

public async attemptToOpenEc2Terminal(selection: Ec2Selection): Promise<void> {
await this.checkForStartSessionError(selection)
try {
const response = await this.ssmClient.startSession(selection.instanceId)
const response = await this.ssm.startSession(selection.instanceId)
await this.openSessionInTerminal(response, selection)
} catch (err: unknown) {
this.throwConnectionError('', selection, err as Error)
Expand All @@ -198,7 +198,7 @@ export class Ec2Connecter implements vscode.Disposable {

const remoteUser = await this.getRemoteUser(selection.instanceId)
const remoteEnv = await this.prepareEc2RemoteEnvWithProgress(selection, remoteUser)
const testSession = await this.ssmClient.startSession(selection.instanceId, 'AWS-StartSSHSession')
const testSession = await this.ssm.startSession(selection.instanceId, 'AWS-StartSSHSession')
try {
await testSshConnection(
remoteEnv.SessionProcess,
Expand All @@ -218,7 +218,7 @@ export class Ec2Connecter implements vscode.Disposable {
const message = err instanceof SshError ? 'Testing SSH connection to instance failed' : ''
this.throwConnectionError(message, selection, err as Error)
} finally {
await this.ssmClient.terminateSession(testSession)
await this.ssm.terminateSession(testSession)
}
}

Expand All @@ -232,8 +232,8 @@ export class Ec2Connecter implements vscode.Disposable {
return remoteEnv
}

private async startSSMSession(instanceId: string): Promise<SSM.StartSessionResponse> {
const ssmSession = await this.ssmClient.startSession(instanceId, 'AWS-StartSSHSession')
private async startSSMSession(instanceId: string): Promise<StartSessionResponse> {
const ssmSession = await this.ssm.startSession(instanceId, 'AWS-StartSSHSession')
await this.addActiveSession(instanceId, ssmSession.SessionId!)
return ssmSession
}
Expand Down Expand Up @@ -308,7 +308,7 @@ export class Ec2Connecter implements vscode.Disposable {
}

private async sendCommandAndWait(instanceId: string, command: string) {
return await this.ssmClient.sendCommandAndWait(instanceId, 'AWS-RunShellScript', {
return await this.ssm.sendCommandAndWait(instanceId, 'AWS-RunShellScript', {
commands: [command],
})
}
Expand All @@ -331,7 +331,7 @@ export class Ec2Connecter implements vscode.Disposable {
}

public async getRemoteUser(instanceId: string): Promise<RemoteUser> {
const os = await this.ssmClient.getTargetPlatformName(instanceId)
const os = await this.ssm.getTargetPlatformName(instanceId)
if (os === 'Amazon Linux') {
return { name: 'ec2-user', os }
}
Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/awsService/ec2/remoteSessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,30 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { EC2, SSM } from 'aws-sdk'
import { SsmClient } from '../../shared/clients/ssmClient'
import { EC2 } from 'aws-sdk'
import { SsmClient } from '../../shared/clients/ssm'
import { Disposable } from 'vscode'

export class Ec2SessionTracker extends Map<EC2.InstanceId, SSM.SessionId> implements Disposable {
export class Ec2SessionTracker extends Map<EC2.InstanceId, string> implements Disposable {
public constructor(
readonly regionCode: string,
protected ssmClient: SsmClient
protected ssm: SsmClient
) {
super()
}

public async addSession(instanceId: EC2.InstanceId, sessionId: SSM.SessionId): Promise<void> {
public async addSession(instanceId: EC2.InstanceId, sessionId: string): Promise<void> {
if (this.isConnectedTo(instanceId)) {
const existingSessionId = this.get(instanceId)!
await this.ssmClient.terminateSessionFromId(existingSessionId)
await this.ssm.terminateSessionFromId(existingSessionId)
this.set(instanceId, sessionId)
} else {
this.set(instanceId, sessionId)
}
}

private async disconnectEnv(instanceId: EC2.InstanceId): Promise<void> {
await this.ssmClient.terminateSessionFromId(this.get(instanceId)!)
await this.ssm.terminateSessionFromId(this.get(instanceId)!)
this.delete(instanceId)
}

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { registerCommands } from './commands'
// The following imports the endpoints file, which causes webpack to bundle it in the final output file
import endpoints from '../resources/endpoints.json'
import { showViewLogsMessage } from './shared/utilities/messages'
import { AWSClientBuilderV3 } from './shared/awsClientBuilderV3'
import { setupUninstallHandler } from './shared/handleUninstall'
import { maybeShowMinVscodeWarning } from './shared/extensionStartup'
import { getLogger } from './shared/logger/logger'
Expand Down Expand Up @@ -104,6 +105,7 @@ export async function activateCommon(
globals.machineId = await getMachineId()
globals.awsContext = new DefaultAwsContext()
globals.sdkClientBuilder = new DefaultAWSClientBuilder(globals.awsContext)
globals.sdkClientBuilderV3 = new AWSClientBuilderV3(globals.awsContext)
globals.loginManager = new LoginManager(globals.awsContext, new CredentialsStore())

// order matters here
Expand Down
11 changes: 10 additions & 1 deletion packages/core/src/shared/awsClientBuilderV3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
BuildMiddleware,
DeserializeHandler,
DeserializeMiddleware,
Handler,
FinalizeHandler,
FinalizeRequestMiddleware,
HandlerExecutionContext,
Expand All @@ -37,10 +38,18 @@ export type AwsClientConstructor<C> = new (o: AwsClientOptions) => C

// AWS-SDKv3 does not export generic types for clients so we need to build them as needed
// https://github.com/aws/aws-sdk-js-v3/issues/5856#issuecomment-2096950979
interface AwsClient {
export interface AwsClient {
middlewareStack: {
add: MiddlewareStack<any, MetadataBearer>['add']
}
send: (command: AwsCommand, options?: any) => Promise<any>
destroy: () => void
}

export interface AwsCommand {
input: object
middlewareStack: any
resolveMiddleware: (stack: any, configuration: any, options: any) => Handler<any, any>
}

interface AwsClientOptions {
Expand Down
57 changes: 57 additions & 0 deletions packages/core/src/shared/clients/clientWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import * as vscode from 'vscode'
import globals from '../extensionGlobals'
import { AwsClient, AwsClientConstructor, AwsCommand } from '../awsClientBuilderV3'
import { pageableToCollection } from '../utilities/collectionUtils'

export abstract class ClientWrapper<C extends AwsClient> implements vscode.Disposable {
protected client?: C

public constructor(
public readonly regionCode: string,
private readonly clientType: AwsClientConstructor<C>
) {}

protected async getClient() {
if (this.client) {
return this.client
}
this.client = await globals.sdkClientBuilderV3.createAwsService(this.clientType, undefined, this.regionCode)
return this.client!
}

protected async makeRequest<CommandInput extends object, Command extends AwsCommand>(
command: new (o: CommandInput) => Command,
commandOptions: CommandInput
) {
const client = await this.getClient()
return await client.send(new command(commandOptions))
}

protected makePaginatedRequest<
CommandInput extends object,
CommandOutput extends object,
Command extends AwsCommand,
>(
command: new (o: CommandInput) => Command,
commandOptions: CommandInput,
collectKey: keyof CommandOutput & string,
nextTokenKey?: keyof CommandOutput & keyof CommandInput & string
) {
const requester = async (req: CommandInput) => await this.makeRequest(command, req)
const response = pageableToCollection(
requester,
commandOptions,
nextTokenKey ?? ('NextToken' as never),
collectKey
)
return response
}

public dispose() {
this.client?.destroy()
}
}
Loading