Skip to content

Commit ff13c0b

Browse files
committed
return import failure message as object
1 parent 28068f8 commit ff13c0b

6 files changed

Lines changed: 119 additions & 79 deletions

File tree

src/autocomplete/ResourceStateCompletionProvider.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ export class ResourceStateCompletionProvider implements CompletionProvider {
5555
log.info(`Retrieving resource details from AWS account with id: ${identifier} and type: ${resource.Type}`);
5656
let properties: string;
5757
try {
58-
const resourceState = await this.resourceStateManager.getResource(resource.Type, identifier);
59-
if (!resourceState) {
58+
const result = await this.resourceStateManager.getResource(resource.Type, identifier);
59+
if (!result.resource) {
6060
return [];
6161
}
62-
properties = resourceState.properties;
62+
properties = result.resource.properties;
6363
} catch {
6464
log.info(`No resource found for id: ${identifier} and type: ${resource.Type}`);
6565
return [];

src/resourceState/ResourceStateImporter.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export class ResourceStateImporter {
157157
parentResourceType?: string,
158158
): Promise<{ fetchedResourceStates: ResourceTemplateFormat[]; importResult: ResourceStateResult }> {
159159
const fetchedResourceStates: ResourceTemplateFormat[] = [];
160+
const failureReasons: Record<string, Record<string, string>> = {};
160161
const importResult: ResourceStateResult = {
161162
completionItem: undefined,
162163
failedImports: {},
@@ -176,9 +177,11 @@ export class ResourceStateImporter {
176177
}
177178
for (const resourceIdentifier of resourceSelection.resourceIdentifiers) {
178179
try {
179-
const resourceState = await this.resourceStateManager.getResource(resourceType, resourceIdentifier);
180-
if (!resourceState) {
180+
const result = await this.resourceStateManager.getResource(resourceType, resourceIdentifier);
181+
if (!result.resource) {
181182
this.getOrCreate(importResult.failedImports, resourceType, []).push(resourceIdentifier);
183+
failureReasons[resourceType] ??= {};
184+
failureReasons[resourceType][resourceIdentifier] = result.error ?? 'Unknown error';
182185
continue;
183186
}
184187
this.getOrCreate(importResult.successfulImports, resourceType, []).push(resourceIdentifier);
@@ -195,20 +198,27 @@ export class ResourceStateImporter {
195198
Type: resourceType,
196199
DeletionPolicy:
197200
purpose === ResourceStatePurpose.IMPORT ? DeletionPolicyOnImport : undefined,
198-
Properties: this.applyTransformations(resourceState.properties, schema, purpose, logicalId),
201+
Properties: this.applyTransformations(
202+
result.resource.properties,
203+
schema,
204+
purpose,
205+
logicalId,
206+
),
199207
Metadata: await this.createMetadata(resourceIdentifier, purpose),
200208
},
201209
});
202210
} catch (error) {
203211
log.error(error, `Error importing resource state for ${resourceType} id: ${resourceIdentifier}`);
204212
this.getOrCreate(importResult.failedImports, resourceType, []).push(resourceIdentifier);
205213
const errorMessage = extractErrorMessage(error);
206-
importResult.failureReasons ??= {};
207-
importResult.failureReasons[resourceType] ??= {};
208-
importResult.failureReasons[resourceType][resourceIdentifier] = errorMessage;
214+
failureReasons[resourceType] ??= {};
215+
failureReasons[resourceType][resourceIdentifier] = errorMessage;
209216
}
210217
}
211218
}
219+
if (Object.keys(failureReasons).length > 0) {
220+
importResult.failureReasons = failureReasons;
221+
}
212222
return { fetchedResourceStates, importResult };
213223
}
214224

src/resourceState/ResourceStateManager.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { ScopedTelemetry } from '../telemetry/ScopedTelemetry';
1515
import { Telemetry, Measure, Count } from '../telemetry/TelemetryDecorator';
1616
import { isClientError } from '../utils/AwsErrorMapper';
1717
import { Closeable } from '../utils/Closeable';
18+
import { extractErrorMessage } from '../utils/Errors';
1819
import { NO_LIST_SUPPORT, REQUIRES_RESOURCE_MODEL } from './ListResourcesExclusionTypes';
1920
import { ListResourcesResult, RefreshResourcesResult } from './ResourceStateTypes';
2021

@@ -60,11 +61,14 @@ export class ResourceStateManager implements SettingsConfigurable, Closeable {
6061
}
6162

6263
@Measure({ name: 'getResource', captureErrorType: true })
63-
public async getResource(typeName: ResourceType, identifier: ResourceId): Promise<ResourceState | undefined> {
64+
public async getResource(
65+
typeName: ResourceType,
66+
identifier: ResourceId,
67+
): Promise<{ resource?: ResourceState; error?: string }> {
6468
const cachedResources = this.getResourceState(typeName, identifier);
6569
if (cachedResources) {
6670
this.telemetry.count('state.hit', 1);
67-
return cachedResources;
71+
return { resource: cachedResources };
6872
}
6973

7074
this.telemetry.count('state.miss', 1);
@@ -76,19 +80,20 @@ export class ResourceStateManager implements SettingsConfigurable, Closeable {
7680
} catch (error) {
7781
if (error instanceof ResourceNotFoundException) {
7882
log.info(`No resource found for type ${typeName} and identifier "${identifier}"`);
83+
return {};
7984
} else if (isClientError(error)) {
8085
log.info(`Client error for type ${typeName} and identifier "${identifier}"`);
81-
} else {
82-
log.error(error, `CCAPI GetResource failed for type ${typeName} and identifier "${identifier}"`);
86+
return { error: extractErrorMessage(error) };
8387
}
88+
log.error(error, `CCAPI GetResource failed for type ${typeName} and identifier "${identifier}"`);
8489
throw error;
8590
}
8691

8792
if (!output?.TypeName || !output?.ResourceDescription?.Identifier || !output?.ResourceDescription?.Properties) {
8893
log.error(
8994
`GetResource output is missing required fields for type ${typeName} with identifier "${identifier}"`,
9095
);
91-
return;
96+
return { error: 'GetResource output is missing required fields' };
9297
}
9398

9499
const value: ResourceState = {
@@ -99,7 +104,7 @@ export class ResourceStateManager implements SettingsConfigurable, Closeable {
99104
};
100105

101106
this.storeResourceState(typeName, identifier, value);
102-
return value;
107+
return { resource: value };
103108
}
104109

105110
@Measure({ name: 'listResources' })
@@ -136,12 +141,17 @@ export class ResourceStateManager implements SettingsConfigurable, Closeable {
136141
typeName: string,
137142
identifier: string,
138143
): Promise<{ found: boolean; resourceList?: ResourceList }> {
144+
let result: { resource?: ResourceState; error?: string };
139145
try {
140-
await this.getResource(typeName, identifier);
146+
result = await this.getResource(typeName, identifier);
141147
} catch {
142148
return { found: false };
143149
}
144150

151+
if (!result.resource) {
152+
return { found: false };
153+
}
154+
145155
// Add to cache
146156
const cached = this.resourceListMap.get(typeName);
147157
if (cached && !cached.resourceIdentifiers.includes(identifier)) {

tst/unit/autocomplete/ResourceStateCompletionProvider.test.ts

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ describe('ResourceStateCompletionProvider', () => {
115115
});
116116

117117
mockComponents.schemaRetriever.getDefault.returns(s3Schemas);
118-
mockComponents.resourceStateManager.getResource.rejects(new Error('Resource not found'));
118+
mockComponents.resourceStateManager.getResource.resolves({ error: 'Resource not found' });
119119

120120
const result = await provider.getCompletions(context, mockYamlParams);
121121

@@ -152,14 +152,16 @@ describe('ResourceStateCompletionProvider', () => {
152152
emptySchemas.schemas.set('Custom::Type', customTypeSchema);
153153
mockComponents.schemaRetriever.getDefault.returns(emptySchemas);
154154
mockComponents.resourceStateManager.getResource.resolves({
155-
typeName: 'Custom::Type',
156-
identifier: 'test',
157-
properties: JSON.stringify({
158-
BucketName: 'test',
159-
VersioningConfiguration: { Status: 'Enabled' },
160-
ExistingProp: 'value',
161-
}),
162-
createdTimestamp: new Date() as any,
155+
resource: {
156+
typeName: 'Custom::Type',
157+
identifier: 'test',
158+
properties: JSON.stringify({
159+
BucketName: 'test',
160+
VersioningConfiguration: { Status: 'Enabled' },
161+
ExistingProp: 'value',
162+
}),
163+
createdTimestamp: new Date() as any,
164+
},
163165
});
164166

165167
const result = await provider.getCompletions(context, mockYamlParams);
@@ -187,13 +189,15 @@ describe('ResourceStateCompletionProvider', () => {
187189
emptySchemas.schemas.set('Custom::Type', customTypeSchema);
188190
mockComponents.schemaRetriever.getDefault.returns(emptySchemas);
189191
mockComponents.resourceStateManager.getResource.resolves({
190-
typeName: 'Custom::Type',
191-
identifier: 'test',
192-
properties: JSON.stringify({
193-
BucketName: 'test',
194-
VersioningConfiguration: { Status: 'Enabled' },
195-
}),
196-
createdTimestamp: new Date() as any,
192+
resource: {
193+
typeName: 'Custom::Type',
194+
identifier: 'test',
195+
properties: JSON.stringify({
196+
BucketName: 'test',
197+
VersioningConfiguration: { Status: 'Enabled' },
198+
}),
199+
createdTimestamp: new Date() as any,
200+
},
197201
});
198202

199203
const result = await provider.getCompletions(context, mockYamlParams);
@@ -268,10 +272,12 @@ describe('ResourceStateCompletionProvider', () => {
268272
});
269273

270274
mockComponents.resourceStateManager.getResource.resolves({
271-
typeName: 'AWS::IAM::Role',
272-
identifier: 'Admin',
273-
properties: `{"Path":"/","ManagedPolicyArns":["arn:aws:iam::aws:policy/AdministratorAccess"],"MaxSessionDuration":43200,"RoleName":"Admin","AssumeRolePolicyDocument":{"Version":"2012-10-17","Statement":[{"Condition":{"StringEquals":{"sts:ExternalId":"IsengardExternalIdAKj8duTfSqL6"}},"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::727820809195:root"},"Sid":""}]},"Arn":"arn:aws:iam::783764615233:role/Admin","RoleId":"AROA3M7AC6BAWIZG2LLQY"}`,
274-
createdTimestamp: DateTime.now(),
275+
resource: {
276+
typeName: 'AWS::IAM::Role',
277+
identifier: 'Admin',
278+
properties: `{"Path":"/","ManagedPolicyArns":["arn:aws:iam::aws:policy/AdministratorAccess"],"MaxSessionDuration":43200,"RoleName":"Admin","AssumeRolePolicyDocument":{"Version":"2012-10-17","Statement":[{"Condition":{"StringEquals":{"sts:ExternalId":"IsengardExternalIdAKj8duTfSqL6"}},"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::727820809195:root"},"Sid":""}]},"Arn":"arn:aws:iam::783764615233:role/Admin","RoleId":"AROA3M7AC6BAWIZG2LLQY"}`,
279+
createdTimestamp: DateTime.now(),
280+
},
275281
});
276282

277283
const result = await provider.getCompletions(context, mockJsonParams);
@@ -307,10 +313,12 @@ describe('ResourceStateCompletionProvider', () => {
307313
});
308314

309315
mockComponents.resourceStateManager.getResource.resolves({
310-
typeName: 'AWS::IAM::Role',
311-
identifier: 'Admin',
312-
properties: `{"Path":"/","ManagedPolicyArns":["arn:aws:iam::aws:policy/AdministratorAccess"],"MaxSessionDuration":43200,"RoleName":"Admin","AssumeRolePolicyDocument":{"Version":"2012-10-17","Statement":[{"Condition":{"StringEquals":{"sts:ExternalId":"IsengardExternalIdAKj8duTfSqL6"}},"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::727820809195:root"},"Sid":""}]},"Arn":"arn:aws:iam::783764615233:role/Admin","RoleId":"AROA3M7AC6BAWIZG2LLQY"}`,
313-
createdTimestamp: DateTime.now(),
316+
resource: {
317+
typeName: 'AWS::IAM::Role',
318+
identifier: 'Admin',
319+
properties: `{"Path":"/","ManagedPolicyArns":["arn:aws:iam::aws:policy/AdministratorAccess"],"MaxSessionDuration":43200,"RoleName":"Admin","AssumeRolePolicyDocument":{"Version":"2012-10-17","Statement":[{"Condition":{"StringEquals":{"sts:ExternalId":"IsengardExternalIdAKj8duTfSqL6"}},"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::727820809195:root"},"Sid":""}]},"Arn":"arn:aws:iam::783764615233:role/Admin","RoleId":"AROA3M7AC6BAWIZG2LLQY"}`,
320+
createdTimestamp: DateTime.now(),
321+
},
314322
});
315323

316324
const result = await provider.getCompletions(context, mockYamlParams);
@@ -459,13 +467,15 @@ describe('ResourceStateCompletionProvider', () => {
459467
emptySchemas.schemas.set('Custom::Type', customTypeSchema);
460468
mockComponents.schemaRetriever.getDefault.returns(emptySchemas);
461469
mockComponents.resourceStateManager.getResource.resolves({
462-
typeName: 'Custom::Type',
463-
identifier: 'test',
464-
properties: JSON.stringify({
465-
BucketName: 'test',
466-
VersioningConfiguration: { Status: 'Enabled' },
467-
}),
468-
createdTimestamp: new Date() as any,
470+
resource: {
471+
typeName: 'Custom::Type',
472+
identifier: 'test',
473+
properties: JSON.stringify({
474+
BucketName: 'test',
475+
VersioningConfiguration: { Status: 'Enabled' },
476+
}),
477+
createdTimestamp: new Date() as any,
478+
},
469479
});
470480

471481
const result = await provider.getCompletions(context, mockYamlParams);
@@ -490,13 +500,15 @@ describe('ResourceStateCompletionProvider', () => {
490500
mockComponents.schemaRetriever.getDefault.returns(emptySchemas);
491501
mockComponents.documentManager.getLine.returns('"",');
492502
mockComponents.resourceStateManager.getResource.resolves({
493-
typeName: 'Custom::Type',
494-
identifier: 'test',
495-
properties: JSON.stringify({
496-
BucketName: 'test',
497-
VersioningConfiguration: { Status: 'Enabled' },
498-
}),
499-
createdTimestamp: new Date() as any,
503+
resource: {
504+
typeName: 'Custom::Type',
505+
identifier: 'test',
506+
properties: JSON.stringify({
507+
BucketName: 'test',
508+
VersioningConfiguration: { Status: 'Enabled' },
509+
}),
510+
createdTimestamp: new Date() as any,
511+
},
500512
});
501513

502514
const result = await provider.getCompletions(context, mockJsonParams);

tst/unit/resourceState/ResourceStateImporter.test.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ describe('ResourceStateImporter', () => {
9292
},
9393
];
9494

95-
mockResourceStateManager.getResource.mockResolvedValue(mockResource);
95+
mockResourceStateManager.getResource.mockResolvedValue({ resource: mockResource });
9696

9797
const params: ResourceStateParams = {
9898
resourceSelections,
@@ -136,7 +136,7 @@ describe('ResourceStateImporter', () => {
136136
},
137137
];
138138

139-
mockResourceStateManager.getResource.mockResolvedValue(mockResource);
139+
mockResourceStateManager.getResource.mockResolvedValue({ resource: mockResource });
140140

141141
const params: ResourceStateParams = {
142142
resourceSelections,
@@ -225,7 +225,7 @@ describe('ResourceStateImporter', () => {
225225

226226
createAndRegisterDocument(uri, scenario.initialContent, scenario.documentType);
227227

228-
mockResourceStateManager.getResource.mockRejectedValue(new Error('Access denied'));
228+
mockResourceStateManager.getResource.mockResolvedValue({ error: 'Access denied' });
229229

230230
const params: ResourceStateParams = {
231231
resourceSelections: [{ resourceType: 'AWS::S3::Bucket', resourceIdentifiers: ['my-bucket'] }],
@@ -246,7 +246,7 @@ describe('ResourceStateImporter', () => {
246246
createAndRegisterDocument(uri, scenario.initialContent, scenario.documentType);
247247

248248
const mockResource = createMockResourceState('AWS::S3::Bucket');
249-
mockResourceStateManager.getResource.mockResolvedValue(mockResource);
249+
mockResourceStateManager.getResource.mockResolvedValue({ resource: mockResource });
250250

251251
const params: ResourceStateParams = {
252252
resourceSelections: [
@@ -269,9 +269,9 @@ describe('ResourceStateImporter', () => {
269269

270270
const mockResource = createMockResourceState('AWS::S3::Bucket');
271271
mockResourceStateManager.getResource
272-
.mockResolvedValueOnce(mockResource)
273-
.mockRejectedValueOnce(new Error('Not found'))
274-
.mockRejectedValueOnce(new Error('Timeout'));
272+
.mockResolvedValueOnce({ resource: mockResource })
273+
.mockResolvedValueOnce({ error: 'Not found' })
274+
.mockResolvedValueOnce({ error: 'Timeout' });
275275

276276
const params: ResourceStateParams = {
277277
resourceSelections: [
@@ -306,7 +306,7 @@ describe('ResourceStateImporter', () => {
306306
});
307307

308308
const mockResource = createMockResourceState('AWS::S3::Bucket');
309-
mockResourceStateManager.getResource.mockResolvedValue(mockResource);
309+
mockResourceStateManager.getResource.mockResolvedValue({ resource: mockResource });
310310

311311
const params: ResourceStateParams = {
312312
resourceSelections: [
@@ -340,7 +340,7 @@ describe('ResourceStateImporter', () => {
340340
});
341341

342342
const mockResource = createMockResourceState('AWS::S3::Bucket');
343-
mockResourceStateManager.getResource.mockResolvedValue(mockResource);
343+
mockResourceStateManager.getResource.mockResolvedValue({ resource: mockResource });
344344

345345
const params: ResourceStateParams = {
346346
resourceSelections: [
@@ -370,7 +370,7 @@ describe('ResourceStateImporter', () => {
370370
});
371371

372372
const mockResource = createMockResourceState('AWS::S3::Bucket');
373-
mockResourceStateManager.getResource.mockResolvedValue(mockResource);
373+
mockResourceStateManager.getResource.mockResolvedValue({ resource: mockResource });
374374

375375
const params: ResourceStateParams = {
376376
resourceSelections: [
@@ -420,7 +420,7 @@ describe('ResourceStateImporter', () => {
420420
},
421421
];
422422

423-
mockResourceStateManager.getResource.mockResolvedValue(mockResource);
423+
mockResourceStateManager.getResource.mockResolvedValue({ resource: mockResource });
424424

425425
const params: ResourceStateParams = {
426426
resourceSelections,
@@ -460,9 +460,9 @@ describe('ResourceStateImporter', () => {
460460
];
461461

462462
mockResourceStateManager.getResource
463-
.mockResolvedValueOnce(mockResource1)
464-
.mockResolvedValueOnce(mockResource2)
465-
.mockResolvedValueOnce(mockResource3);
463+
.mockResolvedValueOnce({ resource: mockResource1 })
464+
.mockResolvedValueOnce({ resource: mockResource2 })
465+
.mockResolvedValueOnce({ resource: mockResource3 });
466466

467467
const params: ResourceStateParams = {
468468
resourceSelections,

0 commit comments

Comments
 (0)