Skip to content
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

deps(sdk): barebones general public client builder. #5940

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
624407d
create barebone sdk builder
Hweinstock Nov 5, 2024
d721869
migrate ec2 (working)
Hweinstock Nov 5, 2024
31fcd52
solve typing problem
Hweinstock Nov 6, 2024
8a0c77c
work towards changing type system
Hweinstock Nov 6, 2024
013677d
more progress, without compile errors
Hweinstock Nov 6, 2024
82e92c3
remove event listener tests
Hweinstock Nov 6, 2024
4dc8f7a
passing basic tests
Hweinstock Nov 6, 2024
6a32dff
find workaround to add middlestack
Hweinstock Nov 6, 2024
36c2c75
add test case for region
Hweinstock Nov 6, 2024
f506b47
add comments and remove half-supported features
Hweinstock Nov 6, 2024
f9fed75
add more tests
Hweinstock Nov 6, 2024
666bfc8
add comment explaining test
Hweinstock Nov 6, 2024
f24eb45
undo change to .eslintrc.js
Hweinstock Nov 6, 2024
f94d505
remove console.log
Hweinstock Nov 6, 2024
d806de9
rename to ec2Wrapper to avoid confusion
Hweinstock Nov 7, 2024
93f7ae2
move utility function to util file
Hweinstock Nov 7, 2024
642487b
Merge branch 'master' into migrateEc2
Hweinstock Nov 12, 2024
ab817b1
fix merge conflicts
Hweinstock Nov 12, 2024
7a3b885
try different type
Hweinstock Nov 12, 2024
4f46c54
merge master, resolve conflicts
Hweinstock Nov 14, 2024
7d3cdf6
Merge branch 'master' into migrateEc2
Hweinstock Nov 19, 2024
12ded34
merge in master (step1)
Hweinstock Nov 22, 2024
9b45344
update package.json/package-lock.json
Hweinstock Nov 22, 2024
fcf13d3
update package/package-lock.json
Hweinstock Nov 22, 2024
8ca643f
update package.json/package-lock again
Hweinstock Nov 22, 2024
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
2,294 changes: 1,708 additions & 586 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@
"webpack-merge": "^5.10.0"
},
"dependencies": {
"@types/node": "^22.7.5",
"@aws-sdk/client-ec2": "^3.683.0",
"@aws-sdk/protocol-http": "^3.370.0",
"@aws-toolkits/telemetry": "^1.0.242",
"@smithy/types": "^3.6.0",
"@types/node": "^22.7.5",
"vscode-nls": "^5.2.0",
"vscode-nls-dev": "^4.0.4"
}
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/awsService/ec2/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
import { Ec2InstanceNode } from './explorer/ec2InstanceNode'
import { Ec2Node } from './explorer/ec2ParentNode'
import { SafeEc2Instance, Ec2Client } from '../../shared/clients/ec2Client'
import { SafeEc2Instance, Ec2Wrapper } from '../../shared/clients/ec2Wrapper'
import { copyToClipboard } from '../../shared/utilities/messages'
import { ec2LogSchema } from './ec2LogDocumentProvider'
import { getAwsConsoleUrl } from '../../shared/awsConsole'
Expand All @@ -29,20 +29,20 @@ export async function openRemoteConnection(connectionManagers: Ec2ConnecterMap,
export async function startInstance(node?: Ec2Node) {
const prompterFilter = (instance: SafeEc2Instance) => instance.LastSeenStatus !== 'running'
const selection = await getSelection(node, prompterFilter)
const client = new Ec2Client(selection.region)
const client = new Ec2Wrapper(selection.region)
await client.startInstanceWithCancel(selection.instanceId)
}

export async function stopInstance(node?: Ec2Node) {
const prompterFilter = (instance: SafeEc2Instance) => instance.LastSeenStatus !== 'stopped'
const selection = await getSelection(node, prompterFilter)
const client = new Ec2Client(selection.region)
const client = new Ec2Wrapper(selection.region)
await client.stopInstanceWithCancel(selection.instanceId)
}

export async function rebootInstance(node?: Ec2Node) {
const selection = await getSelection(node)
const client = new Ec2Client(selection.region)
const client = new Ec2Wrapper(selection.region)
await client.rebootInstanceWithCancel(selection.instanceId)
}

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/awsService/ec2/ec2LogDocumentProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
import * as vscode from 'vscode'
import { Ec2Selection } from './prompter'
import { Ec2Client } from '../../shared/clients/ec2Client'
import { Ec2Wrapper } from '../../shared/clients/ec2Wrapper'
import { ec2LogsScheme } from '../../shared/constants'
import { UriSchema } from '../../shared/utilities/uriUtils'

Expand All @@ -16,7 +16,7 @@ export class Ec2LogDocumentProvider implements vscode.TextDocumentContentProvide
throw new Error(`Invalid EC2 Logs URI: ${uri.toString()}`)
}
const ec2Selection = ec2LogSchema.parse(uri)
const ec2Client = new Ec2Client(ec2Selection.region)
const ec2Client = new Ec2Wrapper(ec2Selection.region)
const consoleOutput = await ec2Client.getConsoleOutput(ec2Selection.instanceId, false)
return consoleOutput.Output
}
Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/awsService/ec2/explorer/ec2InstanceNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
* SPDX-License-Identifier: Apache-2.0
*/
import * as vscode from 'vscode'
import { Ec2Client, getNameOfInstance } from '../../../shared/clients/ec2Client'
import { Ec2Wrapper, getNameOfInstance } from '../../../shared/clients/ec2Wrapper'
import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode'
import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase'
import { SafeEc2Instance } from '../../../shared/clients/ec2Client'
import { SafeEc2Instance } from '../../../shared/clients/ec2Wrapper'
import globals from '../../../shared/extensionGlobals'
import { getIconCode } from '../utils'
import { Ec2Selection } from '../prompter'
import { Ec2Node, Ec2ParentNode } from './ec2ParentNode'
import { EC2 } from 'aws-sdk'
import { getLogger } from '../../../shared'
import { InstanceStateName } from '@aws-sdk/client-ec2'

export const Ec2InstanceRunningContext = 'awsEc2RunningNode'
export const Ec2InstanceStoppedContext = 'awsEc2StoppedNode'
Expand All @@ -23,7 +23,7 @@ type Ec2InstanceNodeContext = 'awsEc2RunningNode' | 'awsEc2StoppedNode' | 'awsEc
export class Ec2InstanceNode extends AWSTreeNodeBase implements AWSResourceNode {
public constructor(
public readonly parent: Ec2ParentNode,
public readonly client: Ec2Client,
public readonly client: Ec2Wrapper,
public override readonly regionCode: string,
private readonly partitionId: string,
// XXX: this variable is marked as readonly, but the 'status' attribute is updated when polling the nodes.
Expand Down Expand Up @@ -68,7 +68,7 @@ export class Ec2InstanceNode extends AWSTreeNodeBase implements AWSResourceNode
return Ec2InstancePendingContext
}

public setInstanceStatus(instanceStatus: string) {
public setInstanceStatus(instanceStatus: InstanceStateName) {
this.instance.LastSeenStatus = instanceStatus
}

Expand All @@ -79,7 +79,7 @@ export class Ec2InstanceNode extends AWSTreeNodeBase implements AWSResourceNode
}
}

public getStatus(): EC2.InstanceStateName {
public getStatus(): InstanceStateName {
return this.instance.LastSeenStatus
}

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/awsService/ec2/explorer/ec2ParentNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase'
import { makeChildrenNodes } from '../../../shared/treeview/utils'
import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode'
import { Ec2InstanceNode } from './ec2InstanceNode'
import { Ec2Client } from '../../../shared/clients/ec2Client'
import { Ec2Wrapper } from '../../../shared/clients/ec2Wrapper'
import { updateInPlace } from '../../../shared/utilities/collectionUtils'
import { PollingSet } from '../../../shared/utilities/pollingSet'

Expand All @@ -23,7 +23,7 @@ export class Ec2ParentNode extends AWSTreeNodeBase {
public constructor(
public override readonly regionCode: string,
public readonly partitionId: string,
protected readonly ec2Client: Ec2Client
protected readonly ec2Client: Ec2Wrapper
) {
super('EC2', vscode.TreeItemCollapsibleState.Collapsed)
this.ec2InstanceNodes = new Map<string, Ec2InstanceNode>()
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/awsService/ec2/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getOrInstallCli } from '../../shared/utilities/cliUtils'
import { isCloud9 } from '../../shared/extensionUtilities'
import { ToolkitError } from '../../shared/errors'
import { SsmClient } from '../../shared/clients/ssmClient'
import { Ec2Client } from '../../shared/clients/ec2Client'
import { Ec2Wrapper } from '../../shared/clients/ec2Wrapper'
import {
VscodeRemoteConnection,
ensureDependencies,
Expand Down Expand Up @@ -40,7 +40,7 @@ export interface Ec2RemoteEnv extends VscodeRemoteConnection {

export class Ec2Connecter implements vscode.Disposable {
protected ssmClient: SsmClient
protected ec2Client: Ec2Client
protected ec2Client: Ec2Wrapper
protected iamClient: DefaultIamClient
protected sessionManager: Ec2SessionTracker

Expand All @@ -63,8 +63,8 @@ export class Ec2Connecter implements vscode.Disposable {
return new SsmClient(this.regionCode)
}

protected createEc2SdkClient(): Ec2Client {
return new Ec2Client(this.regionCode)
protected createEc2SdkClient(): Ec2Wrapper {
return new Ec2Wrapper(this.regionCode)
}

protected createIamSdkClient(): DefaultIamClient {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/awsService/ec2/prompter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { RegionSubmenu, RegionSubmenuResponse } from '../../shared/ui/common/regionSubmenu'
import { DataQuickPickItem } from '../../shared/ui/pickerPrompter'
import { Ec2Client, SafeEc2Instance } from '../../shared/clients/ec2Client'
import { Ec2Wrapper, SafeEc2Instance } from '../../shared/clients/ec2Wrapper'
import { isValidResponse } from '../../shared/wizards/wizard'
import { CancellationError } from '../../shared/utilities/timeoutUtils'
import { AsyncCollection } from '../../shared/utilities/asyncCollection'
Expand Down Expand Up @@ -54,7 +54,7 @@ export class Ec2Prompter {
}

protected async getInstancesFromRegion(regionCode: string): Promise<AsyncCollection<SafeEc2Instance>> {
const client = new Ec2Client(regionCode)
const client = new Ec2Wrapper(regionCode)
return await client.getInstances()
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/awsService/ec2/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { SafeEc2Instance } from '../../shared/clients/ec2Client'
import { SafeEc2Instance } from '../../shared/clients/ec2Wrapper'
import { copyToClipboard } from '../../shared/utilities/messages'
import { Ec2Selection } from './prompter'
import { sshLogFileLocation } from '../../shared/sshConfig'
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/awsexplorer/regionNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { getEcsRootNode } from '../awsService/ecs/model'
import { compareTreeItems, TreeShim } from '../shared/treeview/utils'
import { Ec2ParentNode } from '../awsService/ec2/explorer/ec2ParentNode'
import { DevSettings } from '../shared/settings'
import { Ec2Client } from '../shared/clients/ec2Client'
import { Ec2Wrapper } from '../shared/clients/ec2Wrapper'
import { isCloud9 } from '../shared/extensionUtilities'

interface ServiceNode {
Expand Down Expand Up @@ -67,7 +67,7 @@ const serviceCandidates: ServiceNode[] = [
serviceId: 'ec2',
when: () => DevSettings.instance.isDevMode(),
createFn: (regionCode: string, partitionId: string) =>
new Ec2ParentNode(regionCode, partitionId, new Ec2Client(regionCode)),
new Ec2ParentNode(regionCode, partitionId, new Ec2Wrapper(regionCode)),
},
{
serviceId: 'ecr',
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 @@ -57,6 +57,7 @@ import { registerCommands } from './commands'
import endpoints from '../resources/endpoints.json'
import { getLogger, maybeShowMinVscodeWarning, setupUninstallHandler } from './shared'
import { showViewLogsMessage } from './shared/utilities/messages'
import { DefaultAWSClientBuilderV3 } from './shared/awsClientBuilderV3'

disableAwsSdkWarning()

Expand Down Expand Up @@ -121,6 +122,7 @@ export async function activateCommon(
globals.machineId = await getMachineId()
globals.awsContext = new DefaultAwsContext()
globals.sdkClientBuilder = new DefaultAWSClientBuilder(globals.awsContext)
globals.sdkClientBuilderV3 = new DefaultAWSClientBuilderV3(globals.awsContext)
globals.loginManager = new LoginManager(globals.awsContext, new CredentialsStore())

// order matters here
Expand Down
150 changes: 150 additions & 0 deletions packages/core/src/shared/awsClientBuilderV3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import { CredentialsShim } from '../auth/deprecated/loginManager'
import { AwsContext } from './awsContext'
import { AwsCredentialIdentityProvider } from '@smithy/types'
import { Client as IClient } from '@smithy/types'
import { getUserAgent } from './telemetry/util'
import { DevSettings } from './settings'
import {
DeserializeHandler,
DeserializeHandlerOptions,
DeserializeMiddleware,
HandlerExecutionContext,
Provider,
UserAgent,
} from '@aws-sdk/types'
import { HttpResponse } from '@aws-sdk/protocol-http'
import { telemetry } from './telemetry'
import { getRequestId } from './errors'
import { extensionVersion } from '.'
import { getLogger } from './logger'

export type AwsClient = IClient<any, any, any>
interface AwsConfigOptions {
credentials: AwsCredentialIdentityProvider
region: string | Provider<string>
customUserAgent: UserAgent
requestHandler: any
apiVersion: string
endpoint: string
}
export type AwsClientOptions = AwsConfigOptions

export interface AWSClientBuilderV3 {
createAwsService<C extends AwsClient>(
type: new (o: AwsClientOptions) => C,
options?: Partial<AwsClientOptions>,
region?: string,
userAgent?: boolean,
settings?: DevSettings
): Promise<C>
}

export class DefaultAWSClientBuilderV3 implements AWSClientBuilderV3 {
public constructor(private readonly context: AwsContext) {}

private getShim(): CredentialsShim {
const shim = this.context.credentialsShim
if (!shim) {
throw new Error('Toolkit is not logged-in.')
}
return shim
}

public async createAwsService<C extends AwsClient>(
type: new (o: AwsClientOptions) => C,
options?: Partial<AwsClientOptions>,
region?: string,
userAgent: boolean = true,
settings?: DevSettings
): Promise<C> {
const shim = this.getShim()
const opt = (options ?? {}) as AwsClientOptions

if (!opt.region && region) {
opt.region = region
}

if (!opt.customUserAgent && userAgent) {
opt.customUserAgent = [[getUserAgent({ includePlatform: true, includeClientId: true }), extensionVersion]]
}
// TODO: add tests for refresh logic.
opt.credentials = async () => {
const creds = await shim.get()
if (creds.expiration && creds.expiration.getTime() < Date.now()) {
return shim.refresh()
}
return creds
}

const service = new type(opt)
// TODO: add middleware for logging, telemetry, endpoints.
service.middlewareStack.add(telemetryMiddleware, { step: 'deserialize' } as DeserializeHandlerOptions)
return service
}
}

export function getServiceId(context: { clientName?: string; commandName?: string }): string {
return context.clientName?.toLowerCase().replace(/client$/, '') ?? 'unknown-service'
}

/**
* Record request IDs to the current context, potentially overriding the field if
* multiple API calls are made in the same context. We only do failures as successes are generally uninteresting and noisy.
*/
export function recordErrorTelemetry(err: Error, serviceName?: string) {
interface RequestData {
requestId?: string
requestServiceType?: string
}

telemetry.record({
requestId: getRequestId(err),
requestServiceType: serviceName,
} satisfies RequestData as any)
}

export function omitIfPresent<T extends Record<string, unknown>>(obj: T, keys: string[]): T {
Hweinstock marked this conversation as resolved.
Show resolved Hide resolved
const objCopy = { ...obj }
for (const key of keys) {
if (key in objCopy) {
;(objCopy as any)[key] = '[omitted]'
}
}
return objCopy
}

function logAndThrow(e: any, serviceId: string, errorMessageAppend: string): never {
if (e instanceof Error) {
recordErrorTelemetry(e, serviceId)
const err = { ...e }
delete err['stack']
getLogger().error('API Response %s: %O', errorMessageAppend, err)
}
throw e
}
/**
* Telemetry logic to be added to all created clients. Adds logging and emitting metric on errors.
*/

const telemetryMiddleware: DeserializeMiddleware<any, any> =
(next: DeserializeHandler<any, any>, context: HandlerExecutionContext) => async (args: any) => {
if (!HttpResponse.isInstance(args.request)) {
return next(args)
}
const serviceId = getServiceId(context as object)
const { hostname, path } = args.request
const logTail = `(${hostname} ${path})`
const result = await next(args).catch((e: any) => logAndThrow(e, serviceId, logTail))
if (HttpResponse.isInstance(result.response)) {
// TODO: omit credentials / sensitive info from the logs / telemetry.
const output = omitIfPresent(result.output, [])
getLogger().debug('API Response %s: %O', logTail, output)
}

return result
}
Loading
Loading