Skip to content

Commit ecb46b4

Browse files
committed
Merge branch 'feature/sdkv3' into sdkv3/httpClient
2 parents a4e6d6c + 80c4367 commit ecb46b4

File tree

16 files changed

+341
-391
lines changed

16 files changed

+341
-391
lines changed

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

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
*/
55
import { Ec2InstanceNode } from './explorer/ec2InstanceNode'
66
import { Ec2Node } from './explorer/ec2ParentNode'
7-
import { SafeEc2Instance, Ec2Client } from '../../shared/clients/ec2'
7+
import { Ec2Instance, Ec2Client } from '../../shared/clients/ec2'
88
import { copyToClipboard } from '../../shared/utilities/messages'
99
import { ec2LogSchema } from './ec2LogDocumentProvider'
1010
import { getAwsConsoleUrl } from '../../shared/awsConsole'
1111
import { showRegionPrompter } from '../../auth/utils'
1212
import { openUrl } from '../../shared/utilities/vsCodeUtils'
1313
import { showFile } from '../../shared/utilities/textDocumentUtilities'
1414
import { Ec2ConnecterMap } from './connectionManagerMap'
15-
import { Ec2Prompter, Ec2Selection, instanceFilter } from './prompter'
15+
import { getSelection } from './prompter'
1616

1717
export async function openTerminal(connectionManagers: Ec2ConnecterMap, node?: Ec2Node) {
1818
const selection = await getSelection(node)
@@ -27,14 +27,14 @@ export async function openRemoteConnection(connectionManagers: Ec2ConnecterMap,
2727
}
2828

2929
export async function startInstance(node?: Ec2Node) {
30-
const prompterFilter = (instance: SafeEc2Instance) => instance.LastSeenStatus !== 'running'
30+
const prompterFilter = (instance: Ec2Instance) => instance.LastSeenStatus !== 'running'
3131
const selection = await getSelection(node, prompterFilter)
3232
const client = new Ec2Client(selection.region)
3333
await client.startInstanceWithCancel(selection.instanceId)
3434
}
3535

3636
export async function stopInstance(node?: Ec2Node) {
37-
const prompterFilter = (instance: SafeEc2Instance) => instance.LastSeenStatus !== 'stopped'
37+
const prompterFilter = (instance: Ec2Instance) => instance.LastSeenStatus !== 'stopped'
3838
const selection = await getSelection(node, prompterFilter)
3939
const client = new Ec2Client(selection.region)
4040
await client.stopInstanceWithCancel(selection.instanceId)
@@ -52,12 +52,6 @@ export async function linkToLaunchInstance(node?: Ec2Node) {
5252
await openUrl(url)
5353
}
5454

55-
async function getSelection(node?: Ec2Node, filter?: instanceFilter): Promise<Ec2Selection> {
56-
const prompter = new Ec2Prompter(filter)
57-
const selection = node && node instanceof Ec2InstanceNode ? node.toSelection() : await prompter.promptUser()
58-
return selection
59-
}
60-
6155
export async function copyInstanceId(instanceId: string): Promise<void> {
6256
await copyToClipboard(instanceId, 'Id')
6357
}

packages/core/src/awsService/ec2/explorer/ec2InstanceNode.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as vscode from 'vscode'
66
import { Ec2Client, getNameOfInstance } from '../../../shared/clients/ec2'
77
import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode'
88
import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase'
9-
import { SafeEc2Instance } from '../../../shared/clients/ec2'
9+
import { Ec2Instance } from '../../../shared/clients/ec2'
1010
import globals from '../../../shared/extensionGlobals'
1111
import { getIconCode } from '../utils'
1212
import { Ec2Selection } from '../prompter'
@@ -27,15 +27,15 @@ export class Ec2InstanceNode extends AWSTreeNodeBase implements AWSResourceNode
2727
public override readonly regionCode: string,
2828
private readonly partitionId: string,
2929
// XXX: this variable is marked as readonly, but the 'status' attribute is updated when polling the nodes.
30-
public readonly instance: SafeEc2Instance
30+
public readonly instance: Ec2Instance
3131
) {
3232
super('')
3333
this.parent.addChild(this)
3434
this.updateInstance(instance)
3535
this.id = this.InstanceId
3636
}
3737

38-
public updateInstance(newInstance: SafeEc2Instance) {
38+
public updateInstance(newInstance: Ec2Instance) {
3939
this.setInstanceStatus(newInstance.LastSeenStatus)
4040
this.label = `${this.name} (${this.InstanceId}) ${this.instance.LastSeenStatus.toUpperCase()}`
4141
this.contextValue = this.getContext()

packages/core/src/awsService/ec2/explorer/ec2ParentNode.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,18 @@ export class Ec2ParentNode extends AWSTreeNodeBase {
4848
}
4949
this.pollingSet.add(instanceId)
5050
}
51-
51+
// TODO: make use of childNodeLoader to avoid loading all of this at once.
5252
public async updateChildren(): Promise<void> {
53-
const ec2Instances = await (await this.ec2Client.getInstances()).toMap((instance) => instance.InstanceId)
53+
const instanceMap = await this.ec2Client
54+
.getInstances()
55+
.flatten()
56+
.toMap((instance) => instance.InstanceId)
57+
5458
updateInPlace(
5559
this.ec2InstanceNodes,
56-
ec2Instances.keys(),
57-
(key) => this.ec2InstanceNodes.get(key)!.updateInstance(ec2Instances.get(key)!),
58-
(key) =>
59-
new Ec2InstanceNode(this, this.ec2Client, this.regionCode, this.partitionId, ec2Instances.get(key)!)
60+
instanceMap.keys(),
61+
(key) => this.ec2InstanceNodes.get(key)!.updateInstance(instanceMap.get(key)!),
62+
(key) => new Ec2InstanceNode(this, this.ec2Client, this.regionCode, this.partitionId, instanceMap.get(key)!)
6063
)
6164
}
6265

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

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,49 @@
55

66
import { RegionSubmenu, RegionSubmenuResponse } from '../../shared/ui/common/regionSubmenu'
77
import { DataQuickPickItem } from '../../shared/ui/pickerPrompter'
8-
import { Ec2Client, SafeEc2Instance } from '../../shared/clients/ec2'
8+
import { Ec2Client, Ec2Instance } from '../../shared/clients/ec2'
99
import { isValidResponse } from '../../shared/wizards/wizard'
1010
import { CancellationError } from '../../shared/utilities/timeoutUtils'
1111
import { getIconCode } from './utils'
1212
import { Ec2Node } from './explorer/ec2ParentNode'
1313
import { Ec2InstanceNode } from './explorer/ec2InstanceNode'
1414
import { AsyncCollection } from '../../shared/utilities/asyncCollection'
1515

16-
export type instanceFilter = (instance: SafeEc2Instance) => boolean
16+
export type InstanceFilter = (instance: Ec2Instance) => boolean
1717
export interface Ec2Selection {
1818
instanceId: string
1919
region: string
2020
}
2121

22+
interface Ec2PrompterOptions {
23+
instanceFilter: InstanceFilter
24+
getInstancesFromRegion: (regionCode: string) => AsyncCollection<Ec2Instance[]>
25+
}
26+
2227
export class Ec2Prompter {
23-
public constructor(protected filter?: instanceFilter) {}
28+
protected instanceFilter: InstanceFilter
29+
protected getInstancesFromRegion: (regionCode: string) => AsyncCollection<Ec2Instance[]>
30+
31+
public constructor(options?: Partial<Ec2PrompterOptions>) {
32+
this.instanceFilter = options?.instanceFilter ?? ((_) => true)
33+
this.getInstancesFromRegion =
34+
options?.getInstancesFromRegion ?? ((regionCode: string) => new Ec2Client(regionCode).getInstances())
35+
}
2436

25-
public static getLabel(instance: SafeEc2Instance) {
37+
public static getLabel(instance: Ec2Instance) {
2638
const icon = `$(${getIconCode(instance)})`
2739
return `${instance.Name ?? '(no name)'} \t ${icon} ${instance.LastSeenStatus.toUpperCase()}`
2840
}
2941

30-
protected static asQuickPickItem(instance: SafeEc2Instance): DataQuickPickItem<string> {
42+
public static asQuickPickItem(instance: Ec2Instance): DataQuickPickItem<string> {
3143
return {
3244
label: Ec2Prompter.getLabel(instance),
3345
detail: instance.InstanceId,
3446
data: instance.InstanceId,
3547
}
3648
}
3749

38-
protected static getSelectionFromResponse(response: RegionSubmenuResponse<string>): Ec2Selection {
50+
public static getSelectionFromResponse(response: RegionSubmenuResponse<string>): Ec2Selection {
3951
return {
4052
instanceId: response.data,
4153
region: response.region,
@@ -53,33 +65,24 @@ export class Ec2Prompter {
5365
}
5466
}
5567

56-
protected async getInstancesFromRegion(regionCode: string): Promise<AsyncCollection<SafeEc2Instance>> {
57-
const client = new Ec2Client(regionCode)
58-
return await client.getInstances()
59-
}
60-
61-
// TODO: implement a batched generator to avoid loading all instances into UI.
62-
protected async getInstancesAsQuickPickItems(region: string): Promise<DataQuickPickItem<string>[]> {
63-
return await (
64-
await this.getInstancesFromRegion(region)
68+
public getInstancesAsQuickPickItems(region: string): AsyncIterable<DataQuickPickItem<string>[]> {
69+
return this.getInstancesFromRegion(region).map((instancePage: Ec2Instance[]) =>
70+
instancePage.filter(this.instanceFilter).map((i) => Ec2Prompter.asQuickPickItem(i))
6571
)
66-
.filter(this.filter ? (instance) => this.filter!(instance) : (instance) => true)
67-
.map((instance) => Ec2Prompter.asQuickPickItem(instance))
68-
.promise()
6972
}
7073

7174
private createEc2ConnectPrompter(): RegionSubmenu<string> {
7275
return new RegionSubmenu(
73-
async (region) => await this.getInstancesAsQuickPickItems(region),
76+
(region) => this.getInstancesAsQuickPickItems(region),
7477
{ title: 'Select EC2 Instance', matchOnDetail: true },
7578
{ title: 'Select Region for EC2 Instance' },
7679
'Instances'
7780
)
7881
}
7982
}
8083

81-
export async function getSelection(node?: Ec2Node, filter?: instanceFilter): Promise<Ec2Selection> {
82-
const prompter = new Ec2Prompter(filter)
84+
export async function getSelection(node?: Ec2Node, instanceFilter?: InstanceFilter): Promise<Ec2Selection> {
85+
const prompter = new Ec2Prompter({ instanceFilter })
8386
const selection = node && node instanceof Ec2InstanceNode ? node.toSelection() : await prompter.promptUser()
8487
return selection
8588
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { SafeEc2Instance } from '../../shared/clients/ec2'
6+
import { Ec2Instance } from '../../shared/clients/ec2'
77
import { copyToClipboard } from '../../shared/utilities/messages'
88
import { Ec2Selection } from './prompter'
99
import { sshLogFileLocation } from '../../shared/sshConfig'
1010
import { SSM } from 'aws-sdk'
1111
import { getLogger } from '../../shared/logger/logger'
1212

13-
export function getIconCode(instance: SafeEc2Instance) {
13+
export function getIconCode(instance: Ec2Instance) {
1414
if (instance.LastSeenStatus === 'running') {
1515
return 'pass'
1616
}

packages/core/src/shared/awsClientBuilderV3.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,19 +109,19 @@ export class AWSClientBuilderV3 {
109109
].join(':')
110110
}
111111

112-
public async getAwsService<C extends AwsClient>(serviceOptions: AwsServiceOptions<C>): Promise<C> {
112+
public getAwsService<C extends AwsClient>(serviceOptions: AwsServiceOptions<C>): C {
113113
const key = this.keyAwsService(serviceOptions)
114114
const cached = this.serviceCache.get(key)
115115
if (cached) {
116116
return cached as C
117117
}
118118

119-
const service = await this.createAwsService(serviceOptions)
119+
const service = this.createAwsService(serviceOptions)
120120
this.serviceCache.set(key, service)
121121
return service as C
122122
}
123123

124-
public async createAwsService<C extends AwsClient>(serviceOptions: AwsServiceOptions<C>): Promise<C> {
124+
public createAwsService<C extends AwsClient>(serviceOptions: AwsServiceOptions<C>): C {
125125
const shim = this.getShim()
126126
const opt = (serviceOptions.clientOptions ?? {}) as AwsClientOptions
127127
const userAgent = serviceOptions.userAgent ?? true

packages/core/src/shared/clients/clientWrapper.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,43 +21,48 @@ export abstract class ClientWrapper<C extends AwsClient> implements vscode.Dispo
2121
private readonly clientType: AwsClientConstructor<C>
2222
) {}
2323

24-
protected async getClient(ignoreCache: boolean = false) {
24+
protected getClient(ignoreCache: boolean = false) {
2525
const args = { serviceClient: this.clientType, region: this.regionCode }
2626
return ignoreCache
27-
? await globals.sdkClientBuilderV3.createAwsService(args)
28-
: await globals.sdkClientBuilderV3.getAwsService(args)
27+
? globals.sdkClientBuilderV3.createAwsService(args)
28+
: globals.sdkClientBuilderV3.getAwsService(args)
2929
}
3030

3131
protected async makeRequest<CommandInput extends object, Command extends AwsCommand>(
3232
command: new (o: CommandInput) => Command,
3333
commandOptions: CommandInput
3434
) {
35-
const client = await this.getClient()
35+
const client = this.getClient()
3636
return await client.send(new command(commandOptions))
3737
}
3838

39-
protected async makePaginatedRequest<
40-
CommandInput extends object,
41-
CommandOutput extends object,
42-
Output extends object,
43-
>(
39+
protected makePaginatedRequest<CommandInput extends object, CommandOutput extends object, Output extends object>(
4440
paginator: SDKPaginator<C, CommandInput, CommandOutput>,
4541
input: CommandInput,
4642
extractPage: (page: CommandOutput) => Output[] | undefined
47-
): Promise<AsyncCollection<Output>> {
48-
const p = paginator({ client: await this.getClient() }, input)
43+
): AsyncCollection<Output[]> {
44+
const p = paginator({ client: this.getClient() }, input)
4945
const collection = toCollection(() => p)
5046
.map(extractPage)
51-
.flatten()
5247
.filter(isDefined)
48+
.map((o) => o.filter(isDefined))
5349

5450
return collection
5551

56-
function isDefined(i: Output | undefined): i is Output {
52+
function isDefined<T>(i: T | undefined): i is T {
5753
return i !== undefined
5854
}
5955
}
6056

57+
protected async getFirst<CommandInput extends object, CommandOutput extends object, Output extends object>(
58+
paginator: SDKPaginator<C, CommandInput, CommandOutput>,
59+
input: CommandInput,
60+
extractPage: (page: CommandOutput) => Output[] | undefined
61+
): Promise<Output> {
62+
const results = await this.makePaginatedRequest(paginator, input, extractPage).flatten().promise()
63+
return results[0]
64+
}
65+
6166
public dispose() {
6267
this.client?.destroy()
6368
}

0 commit comments

Comments
 (0)