Skip to content

Commit db491de

Browse files
authored
refactor(apprunner): migrate to sdkv3. (aws#6764)
## Problem app runner still uses sdkv2. ## Solution - Refactor `getServiceSummaries` in `AppRunnerNode` to use SDK native pagination. This includes updates to the tests for `AppRunnerNode`. - As is usual with these migrations, build types to account for optional props. - Fix bug where wizard displayed node12 as label for node16 runtime. ## Verification - Followed the AppRunner wizard to create an AppRunner deployment through the toolkit using public ECR (https://gallery.ecr.aws/aws-containers/hello-app-runner). Followed similar steps through the console to verify it worked the same way. - Pause and resume service via explorer. - Delete service via explorer. - Click in links via explorer. - used the cwl integration to search log streams associated with services. - used the live tail feature on the service (very cool!) --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent e7b7307 commit db491de

17 files changed

+730
-258
lines changed

package-lock.json

Lines changed: 505 additions & 75 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@
510510
"@aws-sdk/client-ssm": "<3.696.0",
511511
"@aws-sdk/client-sso": "<3.696.0",
512512
"@aws-sdk/client-sso-oidc": "<3.696.0",
513+
"@aws-sdk/client-apprunner": "<3.696.0",
513514
"@aws-sdk/credential-provider-env": "<3.696.0",
514515
"@aws-sdk/credential-provider-process": "<3.696.0",
515516
"@aws-sdk/credential-provider-sso": "<3.696.0",

packages/core/src/awsService/apprunner/commands/createServiceFromEcr.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import { EcrRepositoryNode } from '../../../awsService/ecr/explorer/ecrRepositor
88
import { EcrTagNode } from '../../../awsService/ecr/explorer/ecrTagNode'
99

1010
import { CreateAppRunnerServiceWizard } from '../wizards/apprunnerCreateServiceWizard'
11-
import { DefaultAppRunnerClient } from '../../../shared/clients/apprunnerClient'
11+
import { AppRunnerClient } from '../../../shared/clients/apprunner'
1212
import { telemetry } from '../../../shared/telemetry/telemetry'
1313
import { Result } from '../../../shared/telemetry/telemetry'
1414

1515
export async function createFromEcr(
1616
node: EcrTagNode | EcrRepositoryNode,
17-
client = new DefaultAppRunnerClient(node.regionCode)
17+
client = new AppRunnerClient(node.regionCode)
1818
): Promise<void> {
1919
let telemetryResult: Result = 'Failed'
2020

packages/core/src/awsService/apprunner/explorer/apprunnerNode.ts

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@ import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase'
88
import { AppRunnerServiceNode } from './apprunnerServiceNode'
99
import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode'
1010
import * as nls from 'vscode-nls'
11-
import { AppRunnerClient } from '../../../shared/clients/apprunnerClient'
12-
import { getPaginatedAwsCallIter } from '../../../shared/utilities/collectionUtils'
13-
import { AppRunner } from 'aws-sdk'
11+
import { AppRunnerClient, CreateServiceRequest, ServiceSummary } from '../../../shared/clients/apprunner'
1412
import { PollingSet } from '../../../shared/utilities/pollingSet'
1513

1614
const localize = nls.loadMessageBundle()
1715

1816
export class AppRunnerNode extends AWSTreeNodeBase {
19-
private readonly serviceNodes: Map<AppRunner.ServiceId, AppRunnerServiceNode> = new Map()
17+
private readonly serviceNodes: Map<string, AppRunnerServiceNode> = new Map()
2018
private readonly pollingSet: PollingSet<string> = new PollingSet(20000, this.refresh.bind(this))
2119

2220
public constructor(
@@ -43,30 +41,10 @@ export class AppRunnerNode extends AWSTreeNodeBase {
4341
})
4442
}
4543

46-
private async getServiceSummaries(request: AppRunner.ListServicesRequest = {}): Promise<AppRunner.Service[]> {
47-
const iterator = getPaginatedAwsCallIter({
48-
awsCall: async (request) => await this.client.listServices(request),
49-
nextTokenNames: {
50-
request: 'NextToken',
51-
response: 'NextToken',
52-
},
53-
request,
54-
})
55-
56-
const services: AppRunner.Service[] = []
57-
58-
while (true) {
59-
const next = await iterator.next()
60-
61-
// eslint-disable-next-line unicorn/no-array-for-each
62-
next.value.ServiceSummaryList.forEach((summary: AppRunner.Service) => services.push(summary))
63-
64-
if (next.done) {
65-
break
66-
}
67-
}
68-
69-
return services
44+
private async getServiceSummaries(): Promise<ServiceSummary[]> {
45+
// TODO: avoid resolving all services at once.
46+
const serviceCollection = this.client.paginateServices({})
47+
return await serviceCollection.flatten().promise()
7048
}
7149

7250
public async updateChildren(): Promise<void> {
@@ -107,7 +85,7 @@ export class AppRunnerNode extends AWSTreeNodeBase {
10785
this.pollingSet.delete(id)
10886
}
10987

110-
public async createService(request: AppRunner.CreateServiceRequest): Promise<void> {
88+
public async createService(request: CreateServiceRequest): Promise<void> {
11189
await this.client.createService(request)
11290
this.refresh()
11391
}

packages/core/src/awsService/apprunner/explorer/apprunnerServiceNode.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
*/
55

66
import AsyncLock from 'async-lock'
7-
import { AppRunnerClient } from '../../../shared/clients/apprunnerClient'
8-
import { AppRunner } from 'aws-sdk'
7+
import { AppRunnerClient, ServiceSummary } from '../../../shared/clients/apprunner'
98
import { AppRunnerNode } from './apprunnerNode'
109

1110
import { toArrayAsync, toMap } from '../../../shared/utilities/collectionUtils'
@@ -15,6 +14,7 @@ import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode'
1514
import * as nls from 'vscode-nls'
1615
import { getLogger } from '../../../shared/logger/logger'
1716
import { getIcon } from '../../../shared/icons'
17+
import * as AppRunner from '@aws-sdk/client-apprunner'
1818
import { CloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogs'
1919
import { LogGroup } from '@aws-sdk/client-cloudwatch-logs'
2020
const localize = nls.loadMessageBundle()
@@ -41,7 +41,7 @@ export class AppRunnerServiceNode extends CloudWatchLogsBase implements AWSResou
4141
constructor(
4242
public readonly parent: AppRunnerNode,
4343
private readonly client: AppRunnerClient,
44-
private _info: AppRunner.Service,
44+
private _info: ServiceSummary,
4545
private currentOperation: AppRunner.OperationSummary & { Type?: ServiceOperation } = {},
4646
cloudwatchClient = new CloudWatchLogsClient(client.regionCode)
4747
) {
@@ -55,7 +55,7 @@ export class AppRunnerServiceNode extends CloudWatchLogsBase implements AWSResou
5555
this.update(_info)
5656
}
5757

58-
public get info(): Readonly<AppRunner.Service> {
58+
public get info(): Readonly<ServiceSummary> {
5959
return this._info
6060
}
6161

@@ -81,7 +81,7 @@ export class AppRunnerServiceNode extends CloudWatchLogsBase implements AWSResou
8181
this.label = `${this._info.ServiceName} [${displayStatus}]`
8282
}
8383

84-
public update(info: AppRunner.ServiceSummary | AppRunner.Service): void {
84+
public update(info: ServiceSummary): void {
8585
// update can be called multiple times during an event loop
8686
// this would rarely cause the node's status to appear as 'Operation in progress'
8787
this.lock
@@ -135,7 +135,7 @@ export class AppRunnerServiceNode extends CloudWatchLogsBase implements AWSResou
135135
})
136136
}
137137

138-
private updateInfo(info: AppRunner.ServiceSummary | AppRunner.Service): void {
138+
private updateInfo(info: ServiceSummary): void {
139139
if (info.Status === 'OPERATION_IN_PROGRESS' && this.currentOperation.Type === undefined) {
140140
// Asynchronous since it is not currently possible for race-conditions to occur with updating operations
141141
void this.updateOperation()
@@ -171,13 +171,14 @@ export class AppRunnerServiceNode extends CloudWatchLogsBase implements AWSResou
171171
this.setOperation(this._info, resp.OperationId, 'START_DEPLOYMENT')
172172
}
173173

174-
public setOperation(info: AppRunner.Service, id?: string, type?: ServiceOperation): void {
174+
// eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents
175+
public setOperation(info: ServiceSummary, id?: string, type?: ServiceOperation): void {
175176
this.currentOperation.Id = id
176177
this.currentOperation.Type = type
177178
this.update(info)
178179
}
179180

180-
public async describe(): Promise<AppRunner.Service> {
181+
public async describe(): Promise<ServiceSummary> {
181182
const resp = await this.client.describeService({ ServiceArn: this.arn })
182183
this.update(resp.Service)
183184
return this._info

packages/core/src/awsService/apprunner/wizards/apprunnerCreateServiceWizard.ts

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

6-
import { AppRunner } from 'aws-sdk'
76
import * as nls from 'vscode-nls'
87
import { createCommonButtons, QuickInputToggleButton } from '../../../shared/ui/buttons'
98
import * as input from '../../../shared/ui/inputPrompter'
@@ -17,8 +16,9 @@ import { makeDeploymentButton } from './deploymentButton'
1716
import { createExitPrompter } from '../../../shared/ui/common/exitPrompter'
1817
import { IamClient } from '../../../shared/clients/iam'
1918
import { DefaultEcrClient } from '../../../shared/clients/ecrClient'
20-
import { DefaultAppRunnerClient } from '../../../shared/clients/apprunnerClient'
19+
import { AppRunnerClient, CreateServiceRequest, SourceConfiguration } from '../../../shared/clients/apprunner'
2120
import { getAppRunnerCreateServiceDocUrl } from '../../../shared/extensionUtilities'
21+
import * as AppRunner from '@aws-sdk/client-apprunner'
2222

2323
const localize = nls.loadMessageBundle()
2424

@@ -74,10 +74,10 @@ function createInstanceStep(): Prompter<AppRunner.InstanceConfiguration> {
7474

7575
function createSourcePrompter(
7676
autoDeployButton: QuickInputToggleButton
77-
): Prompter<AppRunner.CreateServiceRequest['SourceConfiguration']> {
77+
): Prompter<CreateServiceRequest['SourceConfiguration']> {
7878
const ecrPath = {
7979
label: 'ECR',
80-
data: { ImageRepository: {} } as AppRunner.SourceConfiguration,
80+
data: { ImageRepository: {} } as SourceConfiguration,
8181
detail: localize(
8282
'AWS.apprunner.createService.ecr.detail',
8383
'Create a service from a public or private Elastic Container Registry repository'
@@ -86,7 +86,7 @@ function createSourcePrompter(
8686

8787
const repositoryPath = {
8888
label: 'Repository',
89-
data: { CodeRepository: {} } as AppRunner.SourceConfiguration,
89+
data: { CodeRepository: {} } as SourceConfiguration,
9090
detail: localize('AWS.apprunner.createService.repository.detail', 'Create a service from a GitHub repository'),
9191
}
9292

@@ -96,15 +96,15 @@ function createSourcePrompter(
9696
})
9797
}
9898

99-
export class CreateAppRunnerServiceWizard extends Wizard<AppRunner.CreateServiceRequest> {
99+
export class CreateAppRunnerServiceWizard extends Wizard<CreateServiceRequest> {
100100
public constructor(
101101
region: string,
102-
initState: WizardState<AppRunner.CreateServiceRequest> = {},
103-
implicitState: WizardState<AppRunner.CreateServiceRequest> = {},
102+
initState: WizardState<CreateServiceRequest> = {},
103+
implicitState: WizardState<CreateServiceRequest> = {},
104104
clients = {
105105
iam: new IamClient(region),
106106
ecr: new DefaultEcrClient(region),
107-
apprunner: new DefaultAppRunnerClient(region),
107+
apprunner: new AppRunnerClient(region),
108108
}
109109
) {
110110
super({

packages/core/src/awsService/apprunner/wizards/codeRepositoryWizard.ts

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

6-
import { AppRunner } from 'aws-sdk'
76
import * as nls from 'vscode-nls'
87
import { createCommonButtons, createRefreshButton, QuickInputToggleButton } from '../../../shared/ui/buttons'
98
import { Remote } from '../../../../types/git.d'
109
import { GitExtension } from '../../../shared/extensions/git'
1110
import * as vscode from 'vscode'
1211
import { WizardForm } from '../../../shared/wizards/wizardForm'
1312
import { createVariablesPrompter } from '../../../shared/ui/common/variablesPrompter'
14-
import { AppRunnerClient } from '../../../shared/clients/apprunnerClient'
13+
import {
14+
AppRunnerClient,
15+
SourceConfiguration,
16+
CodeRepository,
17+
CodeConfigurationValues,
18+
} from '../../../shared/clients/apprunner'
1519
import { makeDeploymentButton } from './deploymentButton'
16-
import { createLabelQuickPick, createQuickPick, QuickPickPrompter } from '../../../shared/ui/pickerPrompter'
20+
import {
21+
createLabelQuickPick,
22+
createQuickPick,
23+
DataQuickPickItem,
24+
QuickPickPrompter,
25+
} from '../../../shared/ui/pickerPrompter'
1726
import { createInputBox, InputBoxPrompter } from '../../../shared/ui/inputPrompter'
18-
import { apprunnerConnectionHelpUrl, apprunnerConfigHelpUrl, apprunnerRuntimeHelpUrl } from '../../../shared/constants'
27+
import { apprunnerConfigHelpUrl, apprunnerConnectionHelpUrl, apprunnerRuntimeHelpUrl } from '../../../shared/constants'
1928
import { Wizard, WIZARD_BACK } from '../../../shared/wizards/wizard'
2029
import { openUrl } from '../../../shared/utilities/vsCodeUtils'
2130
import { getAppRunnerCreateServiceDocUrl } from '../../../shared/extensionUtilities'
31+
import * as AppRunner from '@aws-sdk/client-apprunner'
2232

2333
const localize = nls.loadMessageBundle()
2434

@@ -82,9 +92,9 @@ function createBranchPrompter(
8292
}
8393

8494
function createRuntimePrompter(): QuickPickPrompter<AppRunner.Runtime> {
85-
const items = [
86-
{ label: 'python3', data: 'PYTHON_3' },
87-
{ label: 'nodejs12', data: 'NODEJS_16' },
95+
const items: DataQuickPickItem<AppRunner.Runtime>[] = [
96+
{ label: 'python3', data: AppRunner.Runtime.PYTHON_3 },
97+
{ label: 'nodejs16', data: AppRunner.Runtime.NODEJS_16 },
8898
]
8999

90100
return createQuickPick(items, {
@@ -158,10 +168,12 @@ export function createConnectionPrompter(client: AppRunnerClient) {
158168
const getItems = async () => {
159169
const resp = await client.listConnections()
160170

161-
return resp.ConnectionSummaryList.filter((conn) => conn.Status === 'AVAILABLE').map((conn) => ({
162-
label: conn.ConnectionName!,
163-
data: conn,
164-
}))
171+
return resp
172+
.filter((conn) => conn.Status === 'AVAILABLE')
173+
.map((conn) => ({
174+
label: conn.ConnectionName!,
175+
data: conn,
176+
}))
165177
}
166178

167179
const refreshButton = createRefreshButton()
@@ -184,21 +196,24 @@ function createSourcePrompter(): QuickPickPrompter<AppRunner.ConfigurationSource
184196
)
185197
const apiLabel = localize('AWS.apprunner.createService.configSource.apiLabel', 'Configure all settings here')
186198
const repoLabel = localize('AWS.apprunner.createService.configSource.repoLabel', 'Use configuration file')
199+
const apiItem: DataQuickPickItem<AppRunner.ConfigurationSource> = {
200+
label: apiLabel,
201+
data: AppRunner.ConfigurationSource.API,
202+
}
203+
const repoItem: DataQuickPickItem<AppRunner.ConfigurationSource> = {
204+
label: repoLabel,
205+
data: AppRunner.ConfigurationSource.REPOSITORY,
206+
detail: configDetail,
207+
}
187208

188-
return createQuickPick(
189-
[
190-
{ label: apiLabel, data: 'API' },
191-
{ label: repoLabel, data: 'REPOSITORY', detail: configDetail },
192-
],
193-
{
194-
title: localize('AWS.apprunner.createService.configSource.title', 'Choose configuration source'),
195-
buttons: createCommonButtons(apprunnerConfigHelpUrl),
196-
}
197-
)
209+
return createQuickPick([apiItem, repoItem], {
210+
title: localize('AWS.apprunner.createService.configSource.title', 'Choose configuration source'),
211+
buttons: createCommonButtons(apprunnerConfigHelpUrl),
212+
})
198213
}
199214

200-
function createCodeRepositorySubForm(git: GitExtension): WizardForm<AppRunner.CodeRepository> {
201-
const subform = new WizardForm<AppRunner.CodeRepository>()
215+
function createCodeRepositorySubForm(git: GitExtension): WizardForm<CodeRepository> {
216+
const subform = new WizardForm<CodeRepository>()
202217
const form = subform.body
203218

204219
form.RepositoryUrl.bindPrompter(() => createRepoPrompter(git).transform((r) => r.fetchUrl!))
@@ -210,7 +225,7 @@ function createCodeRepositorySubForm(git: GitExtension): WizardForm<AppRunner.Co
210225
form.CodeConfiguration.ConfigurationSource.bindPrompter(createSourcePrompter)
211226
form.SourceCodeVersion.Type.setDefault(() => 'BRANCH')
212227

213-
const codeConfigForm = new WizardForm<AppRunner.CodeConfigurationValues>()
228+
const codeConfigForm = new WizardForm<CodeConfigurationValues>()
214229
codeConfigForm.body.Runtime.bindPrompter(createRuntimePrompter)
215230
codeConfigForm.body.BuildCommand.bindPrompter((state) => createBuildCommandPrompter(state.Runtime!))
216231
codeConfigForm.body.StartCommand.bindPrompter((state) => createStartCommandPrompter(state.Runtime!))
@@ -227,7 +242,7 @@ function createCodeRepositorySubForm(git: GitExtension): WizardForm<AppRunner.Co
227242
return subform
228243
}
229244

230-
export class AppRunnerCodeRepositoryWizard extends Wizard<AppRunner.SourceConfiguration> {
245+
export class AppRunnerCodeRepositoryWizard extends Wizard<SourceConfiguration> {
231246
constructor(
232247
client: AppRunnerClient,
233248
git: GitExtension,

packages/core/src/awsService/apprunner/wizards/imageRepositoryWizard.ts

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

6-
import { AppRunner } from 'aws-sdk'
76
import { createCommonButtons, QuickInputButton, QuickInputToggleButton } from '../../../shared/ui/buttons'
87
import { toArrayAsync } from '../../../shared/utilities/collectionUtils'
98
import { EcrClient, EcrRepository } from '../../../shared/clients/ecrClient'
@@ -22,6 +21,7 @@ import { createRolePrompter } from '../../../shared/ui/common/roles'
2221
import { getLogger } from '../../../shared/logger/logger'
2322
import { getAppRunnerCreateServiceDocUrl, isCloud9 } from '../../../shared/extensionUtilities'
2423
import { createExitPrompter } from '../../../shared/ui/common/exitPrompter'
24+
import { ImageRepository, SourceConfiguration } from '../../../shared/clients/apprunner'
2525

2626
const localize = nls.loadMessageBundle()
2727

@@ -223,8 +223,8 @@ export class ImageIdentifierForm extends WizardForm<{ repo: TaggedEcrRepository
223223
function createImageRepositorySubForm(
224224
ecrClient: EcrClient,
225225
autoDeployButton: QuickInputToggleButton
226-
): WizardForm<AppRunner.ImageRepository> {
227-
const subform = new WizardForm<AppRunner.ImageRepository>()
226+
): WizardForm<ImageRepository> {
227+
const subform = new WizardForm<ImageRepository>()
228228
const form = subform.body
229229

230230
// note: this is intentionally initialized only once to preserve caches
@@ -253,7 +253,7 @@ function createImageRepositorySubForm(
253253
return subform
254254
}
255255

256-
export class AppRunnerImageRepositoryWizard extends Wizard<AppRunner.SourceConfiguration> {
256+
export class AppRunnerImageRepositoryWizard extends Wizard<SourceConfiguration> {
257257
constructor(ecrClient: EcrClient, iamClient: IamClient, autoDeployButton = makeDeploymentButton()) {
258258
super()
259259
const form = this.form

packages/core/src/awsexplorer/regionNode.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { ResourcesNode } from '../dynamicResources/explorer/nodes/resourcesNode'
2222
import { AppRunnerNode } from '../awsService/apprunner/explorer/apprunnerNode'
2323
import { DocumentDBNode } from '../docdb/explorer/docdbNode'
2424
import { DefaultDocumentDBClient } from '../shared/clients/docdbClient'
25-
import { DefaultAppRunnerClient } from '../shared/clients/apprunnerClient'
25+
import { AppRunnerClient } from '../shared/clients/apprunner'
2626
import { DefaultEcrClient } from '../shared/clients/ecrClient'
2727
import { DefaultRedshiftClient } from '../shared/clients/redshiftClient'
2828
import { DefaultIotClient } from '../shared/clients/iotClient'
@@ -53,7 +53,7 @@ const serviceCandidates: ServiceNode[] = [
5353
},
5454
{
5555
serviceId: 'apprunner',
56-
createFn: (regionCode: string) => new AppRunnerNode(regionCode, new DefaultAppRunnerClient(regionCode)),
56+
createFn: (regionCode: string) => new AppRunnerNode(regionCode, new AppRunnerClient(regionCode)),
5757
},
5858
{
5959
serviceId: 'cloudformation',

0 commit comments

Comments
 (0)