Skip to content

Commit a82f214

Browse files
committed
detached-construct
1 parent 24d2adf commit a82f214

File tree

7 files changed

+97
-54
lines changed

7 files changed

+97
-54
lines changed

packages/aws-cdk-lib/aws-cloudfront/lib/cache-policy.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import { Construct, Node } from 'constructs';
1+
import { Construct } from 'constructs';
22
import { CachePolicyReference, CfnCachePolicy, ICachePolicyRef } from './cloudfront.generated';
33
import {
44
Duration,
55
Names,
66
Resource,
7-
ResourceEnvironment,
87
Stack,
98
Token,
109
UnscopedValidationError,
1110
ValidationError,
1211
withResolved,
1312
} from '../../core';
1413
import { addConstructMetadata } from '../../core/lib/metadata-resource';
14+
import { DetachedConstruct } from '../../core/lib/private/detached-construct';
1515
import { propertyInjectable } from '../../core/lib/prop-injectable';
1616

1717
/**
@@ -148,19 +148,14 @@ export class CachePolicy extends Resource implements ICachePolicy {
148148

149149
/** Use an existing managed cache policy. */
150150
private static fromManagedCachePolicy(managedCachePolicyId: string): ICachePolicy {
151-
return new class implements ICachePolicy {
152-
public get node(): Node {
153-
throw new UnscopedValidationError('The result of fromManagedCachePolicy can not be used in this API');
154-
}
155-
156-
public get env(): ResourceEnvironment {
157-
throw new UnscopedValidationError('The result of fromManagedCachePolicy can not be used in this API');
158-
}
159-
151+
return new class extends DetachedConstruct implements ICachePolicy {
160152
public readonly cachePolicyId = managedCachePolicyId;
161153
public readonly cachePolicyRef = {
162154
cachePolicyId: managedCachePolicyId,
163155
};
156+
constructor() {
157+
super('The result of fromManagedCachePolicy can not be used in this API');
158+
}
164159
}();
165160
}
166161

packages/aws-cdk-lib/aws-cloudfront/lib/origin-request-policy.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { Construct, Node } from 'constructs';
1+
import { Construct } from 'constructs';
22
import { CfnOriginRequestPolicy, IOriginRequestPolicyRef, OriginRequestPolicyReference } from './cloudfront.generated';
3-
import { Names, Resource, ResourceEnvironment, Token, UnscopedValidationError, ValidationError } from '../../core';
3+
import { Names, Resource, Token, UnscopedValidationError, ValidationError } from '../../core';
44
import { addConstructMetadata } from '../../core/lib/metadata-resource';
5+
import { DetachedConstruct } from '../../core/lib/private/detached-construct';
56
import { propertyInjectable } from '../../core/lib/prop-injectable';
67

78
/**
@@ -87,19 +88,14 @@ export class OriginRequestPolicy extends Resource implements IOriginRequestPolic
8788

8889
/** Use an existing managed origin request policy. */
8990
private static fromManagedOriginRequestPolicy(managedOriginRequestPolicyId: string): IOriginRequestPolicy {
90-
return new class implements IOriginRequestPolicy {
91-
public get node(): Node {
92-
throw new UnscopedValidationError('The result of fromManagedOriginRequestPolicy can not be used in this API');
93-
}
94-
95-
public get env(): ResourceEnvironment {
96-
throw new UnscopedValidationError('The result of fromManagedOriginRequestPolicy can not be used in this API');
97-
}
98-
91+
return new class extends DetachedConstruct implements IOriginRequestPolicy {
9992
public readonly originRequestPolicyId = managedOriginRequestPolicyId;
10093
public readonly originRequestPolicyRef = {
10194
originRequestPolicyId: managedOriginRequestPolicyId,
10295
};
96+
constructor() {
97+
super('The result of fromManagedOriginRequestPolicy can not be used in this API');
98+
}
10399
}();
104100
}
105101

packages/aws-cdk-lib/aws-cloudfront/lib/response-headers-policy.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { Construct, Node } from 'constructs';
1+
import { Construct } from 'constructs';
22
import {
33
CfnResponseHeadersPolicy,
44
IResponseHeadersPolicyRef,
55
ResponseHeadersPolicyReference,
66
} from './cloudfront.generated';
7-
import { Duration, Names, Resource, ResourceEnvironment, Token, UnscopedValidationError, ValidationError, withResolved } from '../../core';
7+
import { Duration, Names, Resource, Token, ValidationError, withResolved } from '../../core';
88
import { addConstructMetadata } from '../../core/lib/metadata-resource';
9+
import { DetachedConstruct } from '../../core/lib/private/detached-construct';
910
import { propertyInjectable } from '../../core/lib/prop-injectable';
1011

1112
/**
@@ -109,19 +110,14 @@ export class ResponseHeadersPolicy extends Resource implements IResponseHeadersP
109110
}
110111

111112
private static fromManagedResponseHeadersPolicy(managedResponseHeadersPolicyId: string): IResponseHeadersPolicy {
112-
return new class implements IResponseHeadersPolicy {
113-
public get node(): Node {
114-
throw new UnscopedValidationError('The result of fromManagedResponseHeadersPolicy can not be used in this API');
115-
}
116-
117-
public get env(): ResourceEnvironment {
118-
throw new UnscopedValidationError('The result of fromManagedResponseHeadersPolicy can not be used in this API');
119-
}
120-
113+
return new class extends DetachedConstruct implements IResponseHeadersPolicy {
121114
public readonly responseHeadersPolicyId = managedResponseHeadersPolicyId;
122115
public readonly responseHeadersPolicyRef = {
123116
responseHeadersPolicyId: managedResponseHeadersPolicyId,
124117
};
118+
constructor() {
119+
super('The result of fromManagedResponseHeadersPolicy can not be used in this API');
120+
}
125121
};
126122
}
127123

packages/aws-cdk-lib/aws-codepipeline-actions/lib/elastic-beanstalk/deploy-action.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { Construct, Node } from 'constructs';
1+
import { Construct } from 'constructs';
22
import * as codepipeline from '../../../aws-codepipeline';
3-
import { Aws, ResourceEnvironment, UnscopedValidationError } from '../../../core';
3+
import { Aws } from '../../../core';
4+
import { DetachedConstruct } from '../../../core/lib/private/detached-construct';
45
import { Action } from '../action';
56
import { deployArtifactBounds } from '../common';
67

@@ -53,16 +54,13 @@ export class ElasticBeanstalkDeployAction extends Action {
5354
// it doesn't seem we can scope this down further for the codepipeline action.
5455

5556
const policyArn = `arn:${Aws.PARTITION}:iam::aws:policy/AdministratorAccess-AWSElasticBeanstalk`;
56-
options.role.addManagedPolicy({
57-
get node(): Node {
58-
throw new UnscopedValidationError('This object can not be used in this API');
59-
},
60-
get env(): ResourceEnvironment {
61-
throw new UnscopedValidationError('This object can not be used in this API');
62-
},
63-
managedPolicyArn: policyArn,
64-
managedPolicyRef: { policyArn },
65-
});
57+
options.role.addManagedPolicy(new class extends DetachedConstruct {
58+
managedPolicyArn = policyArn;
59+
managedPolicyRef = { policyArn };
60+
constructor() {
61+
super('This object can not be used in this API');
62+
}
63+
}());
6664

6765
// the Action's Role needs to read from the Bucket to get artifacts
6866
options.bucket.grantRead(options.role);

packages/aws-cdk-lib/aws-iam/lib/managed-policy.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Construct, Node } from 'constructs';
1+
import { Construct } from 'constructs';
22
import {
33
CfnManagedPolicy,
44
IGroupRef,
@@ -13,9 +13,10 @@ import { AddToPrincipalPolicyResult, ArnPrincipal, IGrantable, IPrincipal, Princ
1313
import { undefinedIfEmpty } from './private/util';
1414
import { IRole } from './role';
1515
import { IUser } from './user';
16-
import { Arn, ArnFormat, Aws, Resource, ResourceEnvironment, Stack, UnscopedValidationError, ValidationError, Lazy } from '../../core';
16+
import { Arn, ArnFormat, Aws, Resource, Stack, ValidationError, Lazy } from '../../core';
1717
import { getCustomizeRolesConfig, PolicySynthesizer } from '../../core/lib/helpers-internal';
1818
import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource';
19+
import { DetachedConstruct } from '../../core/lib/private/detached-construct';
1920
import { propertyInjectable } from '../../core/lib/prop-injectable';
2021

2122
/**
@@ -179,7 +180,7 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl
179180
* prefix when constructing this object.
180181
*/
181182
public static fromAwsManagedPolicyName(managedPolicyName: string): IManagedPolicy {
182-
class AwsManagedPolicy implements IManagedPolicy {
183+
class AwsManagedPolicy extends DetachedConstruct implements IManagedPolicy {
183184
public readonly managedPolicyArn = Arn.format({
184185
partition: Aws.PARTITION,
185186
service: 'iam',
@@ -188,17 +189,14 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl
188189
resource: 'policy',
189190
resourceName: managedPolicyName,
190191
});
192+
constructor() {
193+
super('The result of fromAwsManagedPolicyName can not be used in this API');
194+
}
191195
public get managedPolicyRef(): ManagedPolicyReference {
192196
return {
193197
policyArn: this.managedPolicyArn,
194198
};
195199
}
196-
public get node(): Node {
197-
throw new UnscopedValidationError('The result of fromAwsManagedPolicyName can not be used in this API');
198-
}
199-
public get env(): ResourceEnvironment {
200-
throw new UnscopedValidationError('The result of fromAwsManagedPolicyName can not be used in this API');
201-
}
202200
}
203201
return new AwsManagedPolicy();
204202
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Construct, IConstruct } from 'constructs';
2+
import type { ResourceEnvironment } from '../environment';
3+
import { UnscopedValidationError } from '../errors';
4+
5+
/**
6+
* Base class for detached constructs that throw UnscopedValidationError
7+
* when accessing node, env, or with() methods.
8+
*
9+
* This is used by legacy APIs like ManagedPolicy.fromAwsManagedPolicyName() and
10+
* CloudFront policy imports that return construct-like objects without requiring
11+
* a scope parameter. These APIs predate modern CDK patterns and cannot be changed
12+
* without breaking existing customer code.
13+
*
14+
* DO NOT USE for new code. New APIs should require a scope parameter.
15+
*
16+
* @internal
17+
*/
18+
export abstract class DetachedConstruct extends Construct implements IConstruct {
19+
private readonly errorMessage: string;
20+
21+
constructor(errorMessage: string) {
22+
super(null as any, undefined as any);
23+
24+
this.errorMessage = errorMessage;
25+
26+
// Use Object.defineProperty to override 'node' property instead of a getter
27+
// to avoid TS2611 error (property vs accessor conflict with base class)
28+
Object.defineProperty(this, 'node', {
29+
get() { throw new UnscopedValidationError(errorMessage); },
30+
});
31+
}
32+
33+
public get env(): ResourceEnvironment {
34+
throw new UnscopedValidationError(this.errorMessage);
35+
}
36+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { UnscopedValidationError } from '../../lib/errors';
2+
import { DetachedConstruct } from '../../lib/private/detached-construct';
3+
4+
class TestDetachedConstruct extends DetachedConstruct {
5+
constructor(message: string) {
6+
super(message);
7+
}
8+
}
9+
10+
describe('DetachedConstruct', () => {
11+
test('throws UnscopedValidationError when accessing node', () => {
12+
const construct = new TestDetachedConstruct('test error message');
13+
14+
expect(() => construct.node).toThrow(UnscopedValidationError);
15+
expect(() => construct.node).toThrow('test error message');
16+
});
17+
18+
test('throws UnscopedValidationError when accessing env', () => {
19+
const construct = new TestDetachedConstruct('test error message');
20+
21+
expect(() => construct.env).toThrow(UnscopedValidationError);
22+
expect(() => construct.env).toThrow('test error message');
23+
});
24+
});

0 commit comments

Comments
 (0)