Skip to content

fix(amazonq): Handle developer profile migration more gracefully #7362

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 1 commit into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AuthUtil, RegionProfile, RegionProfileManager, defaultServiceConfig } f
import { globals } from 'aws-core-vscode/shared'
import { constants } from 'aws-core-vscode/auth'
import { createTestAuthUtil } from 'aws-core-vscode/test'
import { randomUUID } from 'crypto'

const enterpriseSsoStartUrl = 'https://enterprise.awsapps.com/start'
const region = 'us-east-1'
Expand Down Expand Up @@ -158,7 +159,7 @@ describe('RegionProfileManager', async function () {
})
})

describe('persistence', function () {
describe('persistSelectedRegionProfile', function () {
it('persistSelectedRegionProfile', async function () {
await setupConnection('idc')
await regionProfileManager.switchRegionProfile(profileFoo, 'user')
Expand All @@ -177,14 +178,13 @@ describe('RegionProfileManager', async function () {

assert.strictEqual(state[AuthUtil.instance.profileName], profileFoo)
})
})

it(`restoreRegionProfile`, async function () {
sinon.stub(regionProfileManager, 'listRegionProfile').resolves([profileFoo])
describe('restoreRegionProfile', function () {
beforeEach(async function () {
await setupConnection('idc')
if (!AuthUtil.instance.isConnected()) {
fail('connection should not be undefined')
}

})
it('restores region profile if profile name matches', async function () {
const state = {} as any
state[AuthUtil.instance.profileName] = profileFoo

Expand All @@ -194,6 +194,51 @@ describe('RegionProfileManager', async function () {

assert.strictEqual(regionProfileManager.activeRegionProfile, profileFoo)
})

it('returns early when no profiles exist', async function () {
const state = {} as any
state[AuthUtil.instance.profileName] = undefined

await globals.globalState.update('aws.amazonq.regionProfiles', state)

await regionProfileManager.restoreRegionProfile()
assert.strictEqual(regionProfileManager.activeRegionProfile, undefined)
})

it('returns early when no profile name matches, and multiple profiles exist', async function () {
const state = {} as any
state[AuthUtil.instance.profileName] = undefined
state[randomUUID()] = profileFoo

await globals.globalState.update('aws.amazonq.regionProfiles', state)

await regionProfileManager.restoreRegionProfile()
assert.strictEqual(regionProfileManager.activeRegionProfile, undefined)
})

it('uses single profile when no profile name matches', async function () {
const state = {} as any
state[randomUUID()] = profileFoo

await globals.globalState.update('aws.amazonq.regionProfiles', state)

await regionProfileManager.restoreRegionProfile()

assert.strictEqual(regionProfileManager.activeRegionProfile, profileFoo)
})

it('handles cross-validation failure', async function () {
const state = {
[AuthUtil.instance.profileName]: profileFoo,
}
sinon.stub(regionProfileManager, 'loadPersistedRegionProfiles').returns(state)
sinon.stub(regionProfileManager, 'getProfiles').resolves([]) // No matching profile
const invalidateStub = sinon.stub(regionProfileManager, 'invalidateProfile')

await regionProfileManager.restoreRegionProfile()

assert.ok(invalidateStub.calledWith(profileFoo.arn))
})
})

describe('invalidate', function () {
Expand Down
31 changes: 22 additions & 9 deletions packages/core/src/codewhisperer/region/regionProfileManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const endpoints = createConstantMap({
'eu-central-1': 'https://q.eu-central-1.amazonaws.com/',
})

const getRegionProfile = () =>
const getRegionProfiles = () =>
globals.globalState.tryGet<{ [label: string]: RegionProfile }>('aws.amazonq.regionProfiles', Object, {})

/**
Expand Down Expand Up @@ -86,9 +86,9 @@ export class RegionProfileManager {
// This is a poller that handles synchornization of selected region profiles between different IDE windows.
// It checks for changes in global state of region profile, invoking the change handler to switch profiles
public globalStatePoller = GlobalStatePoller.create({
getState: getRegionProfile,
getState: getRegionProfiles,
changeHandler: async () => {
const profile = this.loadPersistedRegionProfle()
const profile = this.loadPersistedRegionProfiles()
void this._switchRegionProfile(profile[this.authProvider.profileName], 'reload')
},
pollIntervalInMs: 2000,
Expand Down Expand Up @@ -285,10 +285,23 @@ export class RegionProfileManager {

// Note: should be called after [this.authProvider.isConnected()] returns non null
async restoreRegionProfile() {
const previousSelected = this.loadPersistedRegionProfle()[this.authProvider.profileName] || undefined
if (!previousSelected) {
const profiles = this.loadPersistedRegionProfiles()
if (!profiles || Object.keys(profiles).length === 0) {
return
}

let previousSelected = profiles[this.authProvider.profileName]

// If no profile matches auth profileName and there are multiple profiles, return so user can select
if (!previousSelected && Object.keys(profiles).length > 1) {
return
}

// If no profile matches auth profileName but there's only one profile, use that one
if (!previousSelected && Object.keys(profiles).length === 1) {
previousSelected = Object.values(profiles)[0]
}

// cross-validation
this.getProfiles()
.then(async (profiles) => {
Expand Down Expand Up @@ -319,8 +332,8 @@ export class RegionProfileManager {
await this.switchRegionProfile(previousSelected, 'reload')
}

private loadPersistedRegionProfle(): { [label: string]: RegionProfile } {
return getRegionProfile()
public loadPersistedRegionProfiles(): { [label: string]: RegionProfile } {
return getRegionProfiles()
}

async persistSelectRegionProfile() {
Expand All @@ -330,7 +343,7 @@ export class RegionProfileManager {
}

// persist connectionId to profileArn
const previousPersistedState = getRegionProfile()
const previousPersistedState = getRegionProfiles()

previousPersistedState[this.authProvider.profileName] = this.activeRegionProfile
await globals.globalState.update('aws.amazonq.regionProfiles', previousPersistedState)
Expand Down Expand Up @@ -379,7 +392,7 @@ export class RegionProfileManager {
this._activeRegionProfile = undefined
}

const profiles = this.loadPersistedRegionProfle()
const profiles = this.loadPersistedRegionProfiles()
const updatedProfiles = Object.fromEntries(
Object.entries(profiles).filter(([connId, profile]) => profile.arn !== arn)
)
Expand Down
Loading