Skip to content

Commit 5adfed8

Browse files
authored
feat(auth): support for IAM connections (#2975)
## Problem New auth UX doesn't interact with IAM credentials ## Solution Implement support by wrapping around existing logic. This removes the `showAuthNode` flag, moving most things over to `Auth`.
1 parent b7d444f commit 5adfed8

21 files changed

+649
-328
lines changed

package.json

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
"onStartupFinished",
4141
"onDebugResolve:aws-sam",
4242
"onCommand:aws.login",
43-
"onCommand:aws.login2",
4443
"onCommand:aws.credentials.profile.create",
4544
"onCommand:aws.credentials.edit",
4645
"onCommand:aws.logout",
@@ -106,7 +105,7 @@
106105
"properties": {
107106
"aws.profile": {
108107
"type": "string",
109-
"default": "",
108+
"deprecationMessage": "The current profile is now stored internally by the Toolkit.",
110109
"description": "%AWS.configuration.profileDescription%"
111110
},
112111
"aws.ecs.openTerminalCommand": {
@@ -739,6 +738,13 @@
739738
}
740739
]
741740
},
741+
"submenus": [
742+
{
743+
"id": "aws.auth",
744+
"label": "%AWS.submenu.auth.title%",
745+
"icon": "$(ellipsis)"
746+
}
747+
],
742748
"menus": {
743749
"commandPalette": [
744750
{
@@ -1069,6 +1075,22 @@
10691075
"command": "aws.renderStateMachineGraph",
10701076
"when": "false"
10711077
},
1078+
{
1079+
"command": "aws.auth.addConnection",
1080+
"when": "false"
1081+
},
1082+
{
1083+
"command": "aws.auth.switchConnections",
1084+
"when": "false"
1085+
},
1086+
{
1087+
"command": "aws.auth.signout",
1088+
"when": "false"
1089+
},
1090+
{
1091+
"command": "aws.auth.help",
1092+
"when": "false"
1093+
},
10721094
{
10731095
"command": "aws.dev.openMenu",
10741096
"when": "aws.isDevMode || isCloud9"
@@ -1719,9 +1741,43 @@
17191741
"group": "0@2"
17201742
},
17211743
{
1722-
"command": "aws.auth.logout",
1744+
"command": "aws.auth.addConnection",
1745+
"when": "viewItem == awsAuthNode",
1746+
"group": "0@1"
1747+
},
1748+
{
1749+
"command": "aws.auth.switchConnections",
1750+
"when": "viewItem == awsAuthNode",
1751+
"group": "0@2"
1752+
},
1753+
{
1754+
"command": "aws.auth.signout",
1755+
"when": "viewItem == awsAuthNode",
1756+
"group": "0@3"
1757+
},
1758+
{
1759+
"command": "aws.auth.help",
17231760
"when": "viewItem == awsAuthNode",
17241761
"group": "inline@1"
1762+
},
1763+
{
1764+
"submenu": "aws.auth",
1765+
"when": "viewItem == awsAuthNode",
1766+
"group": "inline@2"
1767+
}
1768+
],
1769+
"aws.auth": [
1770+
{
1771+
"command": "aws.auth.addConnection",
1772+
"group": "0@1"
1773+
},
1774+
{
1775+
"command": "aws.auth.switchConnections",
1776+
"group": "0@2"
1777+
},
1778+
{
1779+
"command": "aws.auth.signout",
1780+
"group": "0@3"
17251781
}
17261782
]
17271783
},
@@ -1778,17 +1834,6 @@
17781834
}
17791835
}
17801836
},
1781-
{
1782-
"command": "aws.login2",
1783-
"title": "%AWS.command.login2%",
1784-
"category": "%AWS.title%",
1785-
"cloud9": {
1786-
"cn": {
1787-
"title": "%AWS.command.login2.cn%",
1788-
"category": "%AWS.title.cn%"
1789-
}
1790-
}
1791-
},
17921837
{
17931838
"command": "aws.credentials.profile.create",
17941839
"title": "%AWS.command.credentials.profile.create%",
@@ -1820,10 +1865,26 @@
18201865
}
18211866
},
18221867
{
1823-
"command": "aws.auth.logout",
1824-
"title": "%AWS.command.logout%",
1868+
"command": "aws.auth.addConnection",
1869+
"title": "%AWS.command.auth.addConnection%",
1870+
"category": "%AWS.title%"
1871+
},
1872+
{
1873+
"command": "aws.auth.switchConnections",
1874+
"title": "%AWS.command.auth.switchConnections%",
18251875
"category": "%AWS.title%"
18261876
},
1877+
{
1878+
"command": "aws.auth.signout",
1879+
"title": "%AWS.command.auth.signout%",
1880+
"category": "%AWS.title%"
1881+
},
1882+
{
1883+
"command": "aws.auth.help",
1884+
"title": "%AWS.generic.viewDocs%",
1885+
"category": "%AWS.title%",
1886+
"icon": "$(question)"
1887+
},
18271888
{
18281889
"command": "aws.createIssueOnGitHub",
18291890
"title": "%AWS.command.createIssueOnGitHub%",

package.nls.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,13 @@
7373
"AWS.command.apig.copyUrl": "Copy URL",
7474
"AWS.command.apig.invokeRemoteRestApi": "Invoke on AWS",
7575
"AWS.command.apig.invokeRemoteRestApi.cn": "Invoke on Amazon",
76+
"AWS.command.auth.addConnection": "Add new Connection",
77+
"AWS.command.auth.switchConnections": "Switch Connections",
78+
"AWS.command.auth.signout": "Sign out",
7679
"AWS.command.github": "View Source on GitHub",
7780
"AWS.command.help": "View Toolkit Documentation",
78-
"AWS.command.login": "Choose AWS Profile...",
79-
"AWS.command.login.cn": "Choose Amazon Profile...",
80-
"AWS.command.login2": "Connect to AWS",
81-
"AWS.command.login2.cn": "Connect to Amazon",
81+
"AWS.command.login": "Connect to AWS",
82+
"AWS.command.login.cn": "Connect to Amazon",
8283
"AWS.command.logout": "Sign out",
8384
"AWS.command.createIssueOnGitHub": "Report Toolkit Issue",
8485
"AWS.command.createNewSamApp": "Create Lambda SAM Application",
@@ -175,6 +176,7 @@
175176
"AWS.developerTools.explorerTitle": "Developer Tools",
176177
"AWS.cloudWatchLogs.limit.desc": "Maximum amount of log entries pulled per request from CloudWatch Logs (max 10000)",
177178
"AWS.samcli.deploy.bucket.recentlyUsed": "Buckets recently used for SAM deployments",
179+
"AWS.submenu.auth.title": "Authentication",
178180
"AWS.generic.create": "Create...",
179181
"AWS.generic.save": "Save",
180182
"AWS.generic.close": "Close",

resources/walkthrough/setup-connect.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ To interact with Amazon Web Services \(AWS\) through the AWS Toolkit for Visual
1111

1212
1. [Click here](command:aws.login) to open the configuration wizard to connect to AWS.
1313

14-
> This command can also be accessed through the [Command Palette](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/toolkit-navigation.html#command-locations) by choosing **AWS: >Choose AWS Profile...**\.
14+
> This command can also be accessed through the [Command Palette](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/toolkit-navigation.html#command-locations) by choosing **AWS: >Connect to AWS**\.
1515
>
16-
> ![AWS Toolkit Command palette, Choose AWS Profile](./images/aws-toolkit-commandpalette.png)
16+
> ![AWS Toolkit Command palette, Connect to AWS](./images/aws-toolkit-commandpalette.png)
1717
1818
2. Choose a profile from the list\.
1919

src/awsexplorer/activation.ts

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

66
import * as vscode from 'vscode'
7-
import { LoginManager } from '../credentials/loginManager'
87
import { submitFeedback } from '../feedback/vue/submitFeedback'
98
import { deleteCloudFormation } from '../lambda/commands/deleteCloudFormation'
109
import { CloudFormationStackNode } from '../lambda/explorer/cloudFormationNodes'
@@ -29,7 +28,6 @@ import { cdkNode, CdkRootNode } from '../cdk/explorer/rootNode'
2928
import { codewhispererNode } from '../codewhisperer/explorer/codewhispererNode'
3029
import { once } from '../shared/utilities/functionUtils'
3130
import { Auth, AuthNode } from '../credentials/auth'
32-
import { DevSettings } from '../shared/settings'
3331

3432
/**
3533
* Activates the AWS Explorer UI and related functionality.
@@ -40,7 +38,7 @@ export async function activate(args: {
4038
toolkitOutputChannel: vscode.OutputChannel
4139
remoteInvokeOutputChannel: vscode.OutputChannel
4240
}): Promise<void> {
43-
const awsExplorer = new AwsExplorer(globals.context, args.context.awsContext, args.regionProvider)
41+
const awsExplorer = new AwsExplorer(globals.context, args.regionProvider)
4442

4543
const view = vscode.window.createTreeView(awsExplorer.viewProviderId, {
4644
treeDataProvider: awsExplorer,
@@ -50,14 +48,6 @@ export async function activate(args: {
5048

5149
await registerAwsExplorerCommands(args.context, awsExplorer, args.toolkitOutputChannel)
5250

53-
globals.context.subscriptions.push(
54-
view.onDidChangeVisibility(async e => {
55-
if (e.visible) {
56-
await LoginManager.tryAutoConnect(args.context.awsContext)
57-
}
58-
})
59-
)
60-
6151
telemetry.vscode_activeRegions.emit({ value: args.regionProvider.getExplorerRegions().length })
6252

6353
args.context.extensionContext.subscriptions.push(
@@ -75,10 +65,7 @@ export async function activate(args: {
7565
})
7666
)
7767

78-
const nodes = DevSettings.instance.get('showAuthNode', false)
79-
? [new AuthNode(Auth.instance), cdkNode, codewhispererNode]
80-
: [cdkNode, codewhispererNode]
81-
68+
const nodes = [new AuthNode(Auth.instance), cdkNode, codewhispererNode]
8269
const developerTools = createLocalExplorerView(nodes)
8370
args.context.extensionContext.subscriptions.push(developerTools)
8471

src/awsexplorer/awsExplorer.ts

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@
44
*/
55

66
import * as vscode from 'vscode'
7-
import { AwsContext } from '../shared/awsContext'
7+
import { Auth, AuthNode, useIamCredentials } from '../credentials/auth'
88
import { getIdeProperties } from '../shared/extensionUtilities'
9+
import { getIcon } from '../shared/icons'
910
import { getLogger, Logger } from '../shared/logger'
1011
import { RegionProvider } from '../shared/regions/regionProvider'
1112
import { RefreshableAwsTreeProvider } from '../shared/treeview/awsTreeProvider'
1213
import { AWSCommandTreeNode } from '../shared/treeview/nodes/awsCommandTreeNode'
1314
import { AWSTreeNodeBase } from '../shared/treeview/nodes/awsTreeNodeBase'
14-
import { makeChildrenNodes } from '../shared/treeview/utils'
15+
import { makeChildrenNodes, TreeShim } from '../shared/treeview/utils'
1516
import { intersection, toMap, updateInPlace } from '../shared/utilities/collectionUtils'
17+
import { once } from '../shared/utilities/functionUtils'
1618
import { localize } from '../shared/utilities/vsCodeUtils'
1719
import { RegionNode } from './regionNode'
1820

@@ -23,14 +25,6 @@ export class AwsExplorer implements vscode.TreeDataProvider<AWSTreeNodeBase>, Re
2325
private readonly _onDidChangeTreeData: vscode.EventEmitter<AWSTreeNodeBase | undefined>
2426
private readonly regionNodes: Map<string, RegionNode>
2527

26-
private readonly ROOT_NODE_SIGN_IN = new AWSCommandTreeNode(
27-
undefined,
28-
localize('AWS.explorerNode.connecting.label', 'Connecting...'),
29-
'aws.login',
30-
undefined,
31-
localize('AWS.explorerNode.connecting.tooltip', 'Connecting...')
32-
)
33-
3428
private readonly ROOT_NODE_ADD_REGION = new AWSCommandTreeNode(
3529
undefined,
3630
localize('AWS.explorerNode.addRegion', 'Add regions to {0} Explorer...', getIdeProperties().company),
@@ -45,33 +39,19 @@ export class AwsExplorer implements vscode.TreeDataProvider<AWSTreeNodeBase>, Re
4539

4640
public constructor(
4741
private readonly extContext: vscode.ExtensionContext,
48-
private readonly awsContext: AwsContext,
49-
private readonly regionProvider: RegionProvider
42+
private readonly regionProvider: RegionProvider,
43+
private readonly auth = Auth.instance
5044
) {
5145
this._onDidChangeTreeData = new vscode.EventEmitter<AWSTreeNodeBase | undefined>()
5246
this.onDidChangeTreeData = this._onDidChangeTreeData.event
5347
this.regionNodes = new Map<string, RegionNode>()
54-
this.extContext.subscriptions.push(
55-
this.awsContext.onDidChangeContext(e => {
56-
if (!e.accountId) {
57-
this.ROOT_NODE_SIGN_IN.label = localize(
58-
'AWS.explorerNode.signIn',
59-
'Connect to {0}...',
60-
getIdeProperties().company
61-
)
62-
this.ROOT_NODE_SIGN_IN.tooltip = localize(
63-
'AWS.explorerNode.signIn.tooltip',
64-
'Click here to select credentials for the {0} Toolkit',
65-
getIdeProperties().company
66-
)
67-
}
68-
})
69-
)
48+
7049
this.extContext.subscriptions.push(
7150
this.regionProvider.onDidChange(() => {
7251
this.logger.verbose('Refreshing AWS Explorer due to Region Provider updates')
7352
this.refresh()
74-
})
53+
}),
54+
this.auth.onDidChangeActiveConnection(() => this.refresh())
7555
)
7656
}
7757

@@ -120,28 +100,42 @@ export class AwsExplorer implements vscode.TreeDataProvider<AWSTreeNodeBase>, Re
120100
this._onDidChangeTreeData.fire(node)
121101
}
122102

103+
private readonly getAuthNode = once(() => new TreeShim(new AuthNode(this.auth)))
123104
private async getRootNodes(): Promise<AWSTreeNodeBase[]> {
124-
if (!(await this.awsContext.getCredentials())) {
125-
return [this.ROOT_NODE_SIGN_IN]
105+
const conn = this.auth.activeConnection
106+
if (conn !== undefined && conn.type !== 'iam') {
107+
// TODO: this should show up as a child node?
108+
const selectIamNode = useIamCredentials.build(this.auth).asTreeNode({
109+
// label: `No IAM credentials linked to ${conn.label}`,
110+
// iconPath: getIcon('vscode-circle-slash'),
111+
label: 'Select IAM Credentials to View Resources',
112+
iconPath: getIcon('vscode-sync'),
113+
})
114+
115+
return [this.getAuthNode(), new TreeShim(selectIamNode)]
116+
} else if (conn === undefined || conn.state !== 'valid') {
117+
return [this.getAuthNode()]
126118
}
127119

128120
const partitionRegions = this.regionProvider.getRegions()
129121
const userVisibleRegionCodes = this.regionProvider.getExplorerRegions()
130122
const regionMap = toMap(partitionRegions, r => r.id)
131123

132-
return await makeChildrenNodes({
133-
getChildNodes: async () => {
134-
updateInPlace(
135-
this.regionNodes,
136-
intersection(regionMap.keys(), userVisibleRegionCodes),
137-
key => this.regionNodes.get(key)!.update(regionMap.get(key)!),
138-
key => new RegionNode(regionMap.get(key)!, this.regionProvider)
139-
)
124+
updateInPlace(
125+
this.regionNodes,
126+
intersection(regionMap.keys(), userVisibleRegionCodes),
127+
key => this.regionNodes.get(key)!.update(regionMap.get(key)!),
128+
key => new RegionNode(regionMap.get(key)!, this.regionProvider)
129+
)
130+
131+
if (this.regionNodes.size === 0) {
132+
return [this.getAuthNode(), this.ROOT_NODE_ADD_REGION]
133+
}
140134

141-
return [...this.regionNodes.values()]
142-
},
143-
getNoChildrenPlaceholderNode: async () => this.ROOT_NODE_ADD_REGION,
144-
sort: (nodeA, nodeB) => nodeA.regionName.localeCompare(nodeB.regionName),
135+
return await makeChildrenNodes({
136+
getChildNodes: async () => [this.getAuthNode(), ...this.regionNodes.values()],
137+
sort: (a, b) =>
138+
a instanceof TreeShim ? -1 : b instanceof TreeShim ? 1 : a.regionName.localeCompare(b.regionName),
145139
})
146140
}
147141
}

0 commit comments

Comments
 (0)