@kensio/yulin 1.21.34 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/service/cloudformation/registry/sim-cfn-attribute-reads.d.ts +11 -0
- package/dist/service/cloudformation/registry/sim-cfn-attribute-reads.js +64 -0
- package/dist/service/cloudformation/registry/sim-cfn-read-handler-authorizer.d.ts +45 -0
- package/dist/service/cloudformation/registry/sim-cfn-read-handler-authorizer.js +72 -0
- package/dist/service/cloudformation/registry/sim-cfn-read-handler.d.ts +33 -0
- package/dist/service/cloudformation/registry/sim-cfn-read-handler.fixture.d.ts +24 -0
- package/dist/service/cloudformation/registry/sim-cfn-read-handler.fixture.js +58 -0
- package/dist/service/cloudformation/registry/sim-cfn-read-handler.js +36 -0
- package/dist/service/cloudformation/registry/sim-cfn-read-handlers.d.ts +10 -0
- package/dist/service/cloudformation/registry/sim-cfn-read-handlers.js +23 -0
- package/dist/service/cloudformation/registry/sim-cfn-read-handlers.json +7400 -0
- package/dist/service/cloudformation/registry/sim-cfn-resource-arn.d.ts +46 -0
- package/dist/service/cloudformation/registry/sim-cfn-resource-arn.js +119 -0
- package/dist/service/cloudformation/stack/sim-cfn-stack-deploy-context.d.ts +6 -1
- package/dist/service/cloudformation/stack/sim-cfn-stack-deploy-context.js +3 -0
- package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.d.ts +3 -2
- package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.js +2 -1
- package/dist/service/cloudformation/stack/sim-cfn-stack.js +9 -0
- package/dist/service/cloudformation/stack/update/sim-cfn-stack-update-plan.d.ts +11 -0
- package/dist/service/cloudformation/stack/update/sim-cfn-stack-update-plan.js +22 -0
- package/dist/service/cloudformation/stack/update/sim-cfn-stack-updater.d.ts +3 -1
- package/dist/service/cloudformation/stack/update/sim-cfn-stack-updater.js +2 -1
- package/dist/service/stepfunctions/command/execution/sim-states-execution-start.js +1 -0
- package/dist/service/stepfunctions/execution/sim-states-run-task.js +1 -0
- package/dist/service/stepfunctions/execution/sim-states-state-outcome.d.ts +1 -0
- package/dist/service/stepfunctions/execution/sim-states-walks.d.ts +1 -0
- package/dist/service/stepfunctions/execution/sim-states-walks.js +1 -0
- package/dist/service/stepfunctions/task/sim-aws-states-task-targets.js +6 -1
- package/dist/service/stepfunctions/task/sim-states-execution-role.d.ts +2 -1
- package/dist/service/stepfunctions/task/sim-states-execution-role.js +2 -1
- package/dist/service/stepfunctions/task/sim-states-task-invocation.d.ts +1 -0
- package/docs/services/cloudformation/README.md +106 -0
- package/package.json +5 -4
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SimCfnResource } from "../resource/sim-cfn-resource.js";
|
|
2
|
+
import type { SimCfnTemplateValueRecord } from "../template/value/sim-cfn-template-value.js";
|
|
3
|
+
interface SimCfnResourceArnProperties {
|
|
4
|
+
readonly resource: SimCfnResource;
|
|
5
|
+
readonly arnFormat: string;
|
|
6
|
+
readonly resolvedProperties: SimCfnTemplateValueRecord;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The ARN a read handler's calls on one Resource are authorized on.
|
|
10
|
+
*
|
|
11
|
+
* The ARN format's variables are filled from the Stack's scope and from the
|
|
12
|
+
* Resource's own properties, which is the whole of it for a Resource whose
|
|
13
|
+
* template names it (`DomainName` and `RepositoryName` on a CodeArtifact
|
|
14
|
+
* repository). A Resource whose name CloudFormation generated holds no such
|
|
15
|
+
* property. Its ARN comes from its `Arn` attribute, from a Ref that is an ARN, or
|
|
16
|
+
* from its Ref where the format has one variable left, all of which a service answers once it has
|
|
17
|
+
* created the Resource.
|
|
18
|
+
*
|
|
19
|
+
* An ARN that cannot be built is refused. Authorizing the calls on a guessed
|
|
20
|
+
* ARN would let a policy scoped to the real one pass or fail by accident.
|
|
21
|
+
*/
|
|
22
|
+
export declare class SimCfnResourceArn {
|
|
23
|
+
private readonly resource;
|
|
24
|
+
private readonly arnFormat;
|
|
25
|
+
private readonly resolvedProperties;
|
|
26
|
+
constructor(properties: SimCfnResourceArnProperties);
|
|
27
|
+
/** The Resource's ARN, or a refusal saying which variable could not be filled. */
|
|
28
|
+
value(): string;
|
|
29
|
+
private filled;
|
|
30
|
+
private unfilledVariables;
|
|
31
|
+
private variable;
|
|
32
|
+
private property;
|
|
33
|
+
/**
|
|
34
|
+
* The property an ARN variable is held in under another name. The Service
|
|
35
|
+
* Authorization Reference names a CodeBuild project's `${ProjectName}`,
|
|
36
|
+
* which `AWS::CodeBuild::Project` calls `Name`.
|
|
37
|
+
*/
|
|
38
|
+
private namePropertyFor;
|
|
39
|
+
/**
|
|
40
|
+
* A value the Resource answers once a service has created it. A Resource
|
|
41
|
+
* that was skipped answers with a stand-in naming itself, which is no part
|
|
42
|
+
* of an ARN.
|
|
43
|
+
*/
|
|
44
|
+
private deployedValue;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const variablePattern = /\$\{(?<name>[^}]+)\}/gu;
|
|
2
|
+
/**
|
|
3
|
+
* The ARN a read handler's calls on one Resource are authorized on.
|
|
4
|
+
*
|
|
5
|
+
* The ARN format's variables are filled from the Stack's scope and from the
|
|
6
|
+
* Resource's own properties, which is the whole of it for a Resource whose
|
|
7
|
+
* template names it (`DomainName` and `RepositoryName` on a CodeArtifact
|
|
8
|
+
* repository). A Resource whose name CloudFormation generated holds no such
|
|
9
|
+
* property. Its ARN comes from its `Arn` attribute, from a Ref that is an ARN, or
|
|
10
|
+
* from its Ref where the format has one variable left, all of which a service answers once it has
|
|
11
|
+
* created the Resource.
|
|
12
|
+
*
|
|
13
|
+
* An ARN that cannot be built is refused. Authorizing the calls on a guessed
|
|
14
|
+
* ARN would let a policy scoped to the real one pass or fail by accident.
|
|
15
|
+
*/
|
|
16
|
+
export class SimCfnResourceArn {
|
|
17
|
+
resource;
|
|
18
|
+
arnFormat;
|
|
19
|
+
resolvedProperties;
|
|
20
|
+
constructor(properties) {
|
|
21
|
+
this.resource = properties.resource;
|
|
22
|
+
this.arnFormat = properties.arnFormat;
|
|
23
|
+
this.resolvedProperties = new Map(Object.entries(properties.resolvedProperties));
|
|
24
|
+
}
|
|
25
|
+
/** The Resource's ARN, or a refusal saying which variable could not be filled. */
|
|
26
|
+
value() {
|
|
27
|
+
const unfilled = this.unfilledVariables();
|
|
28
|
+
if (unfilled.length === 0) {
|
|
29
|
+
return this.filled();
|
|
30
|
+
}
|
|
31
|
+
const arnAttribute = this.deployedValue(() => {
|
|
32
|
+
return this.resource.attributeValue("Arn");
|
|
33
|
+
});
|
|
34
|
+
if (arnAttribute?.startsWith("arn:") === true) {
|
|
35
|
+
return arnAttribute;
|
|
36
|
+
}
|
|
37
|
+
const refValue = this.deployedValue(() => this.resource.refValue);
|
|
38
|
+
// An SNS topic's Ref is its whole ARN, where a hosted zone's is the one
|
|
39
|
+
// identifier its ARN format lacks.
|
|
40
|
+
if (refValue?.startsWith("arn:") === true) {
|
|
41
|
+
return refValue;
|
|
42
|
+
}
|
|
43
|
+
const [onlyUnfilled] = unfilled;
|
|
44
|
+
if (refValue !== undefined && unfilled.length === 1) {
|
|
45
|
+
return this.filled(new Map([[onlyUnfilled, refValue]]));
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`Sim CloudFormation cannot authorize the read handler of Resource ${this.resource.logicalId} (${String(this.resource.type)}): its ARN format ${this.arnFormat} needs ${unfilled.join(", ")}, which neither its properties nor its attributes give`);
|
|
48
|
+
}
|
|
49
|
+
filled(extra = new Map()) {
|
|
50
|
+
return this.arnFormat.replaceAll(variablePattern, (_match, name) => {
|
|
51
|
+
return extra.get(name) ?? this.variable(name) ?? "";
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
unfilledVariables() {
|
|
55
|
+
return this.arnFormat
|
|
56
|
+
.matchAll(variablePattern)
|
|
57
|
+
.map((match) => match.groups?.["name"] ?? "")
|
|
58
|
+
.filter((name) => this.variable(name) === undefined)
|
|
59
|
+
.toArray();
|
|
60
|
+
}
|
|
61
|
+
variable(name) {
|
|
62
|
+
const { accountId, regionName } = this.resource.accountRegionScope;
|
|
63
|
+
switch (name) {
|
|
64
|
+
case "Partition": {
|
|
65
|
+
return "aws";
|
|
66
|
+
}
|
|
67
|
+
case "Region": {
|
|
68
|
+
return regionName;
|
|
69
|
+
}
|
|
70
|
+
case "Account": {
|
|
71
|
+
return accountId;
|
|
72
|
+
}
|
|
73
|
+
default: {
|
|
74
|
+
return this.property(name) ?? this.property(this.namePropertyFor(name));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
property(name) {
|
|
79
|
+
return stringValue(this.resolvedProperties.get(name));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The property an ARN variable is held in under another name. The Service
|
|
83
|
+
* Authorization Reference names a CodeBuild project's `${ProjectName}`,
|
|
84
|
+
* which `AWS::CodeBuild::Project` calls `Name`.
|
|
85
|
+
*/
|
|
86
|
+
namePropertyFor(variable) {
|
|
87
|
+
const typeName = this.resource.type?.split("::").at(-1);
|
|
88
|
+
if (variable === `${String(typeName)}Name`) {
|
|
89
|
+
return "Name";
|
|
90
|
+
}
|
|
91
|
+
return variable;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A value the Resource answers once a service has created it. A Resource
|
|
95
|
+
* that was skipped answers with a stand-in naming itself, which is no part
|
|
96
|
+
* of an ARN.
|
|
97
|
+
*/
|
|
98
|
+
deployedValue(read) {
|
|
99
|
+
const { createComplete, skipped, inert } = this.resource;
|
|
100
|
+
if (!createComplete || skipped || inert) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
// A Resource type with no Arn attribute refuses to answer for one, which
|
|
104
|
+
// here only means the ARN has to come from somewhere else.
|
|
105
|
+
try {
|
|
106
|
+
return stringValue(read());
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** The value where it is a string, which is all an ARN can be made of. */
|
|
114
|
+
function stringValue(value) {
|
|
115
|
+
if (typeof value === "string") {
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SimAws } from "../../aws/sim-aws.js";
|
|
1
2
|
import type { SimAwsCaller } from "../../aws/caller/sim-aws-caller.js";
|
|
2
3
|
import type { SimCdkOutContext } from "../cdk/sim-cdk-out-context.js";
|
|
3
4
|
/**
|
|
@@ -18,13 +19,17 @@ export interface SimCfnStackUpdateContext {
|
|
|
18
19
|
* values the deployment started with.
|
|
19
20
|
*/
|
|
20
21
|
export declare class SimCfnStackDeployContext {
|
|
22
|
+
/** The simulation the Stack deploys into, which an update never moves. */
|
|
23
|
+
readonly simAws: SimAws;
|
|
21
24
|
/** The CDK cloud assembly the Stack's template was synthesized into. */
|
|
22
25
|
cdkOutContext: SimCdkOutContext | undefined;
|
|
23
26
|
/** The principal the Stack's Resource work is authorized as. */
|
|
24
27
|
caller: SimAwsCaller | undefined;
|
|
25
28
|
/** The principal the Stack's CDK file assets are published as. */
|
|
26
29
|
assetsCaller: SimAwsCaller | undefined;
|
|
27
|
-
constructor(properties: SimCfnStackUpdateContext
|
|
30
|
+
constructor(properties: SimCfnStackUpdateContext & {
|
|
31
|
+
readonly simAws: SimAws;
|
|
32
|
+
});
|
|
28
33
|
/**
|
|
29
34
|
* Take on what an update brings with it, for this and every later operation.
|
|
30
35
|
*
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* values the deployment started with.
|
|
8
8
|
*/
|
|
9
9
|
export class SimCfnStackDeployContext {
|
|
10
|
+
/** The simulation the Stack deploys into, which an update never moves. */
|
|
11
|
+
simAws;
|
|
10
12
|
/** The CDK cloud assembly the Stack's template was synthesized into. */
|
|
11
13
|
cdkOutContext;
|
|
12
14
|
/** The principal the Stack's Resource work is authorized as. */
|
|
@@ -14,6 +16,7 @@ export class SimCfnStackDeployContext {
|
|
|
14
16
|
/** The principal the Stack's CDK file assets are published as. */
|
|
15
17
|
assetsCaller;
|
|
16
18
|
constructor(properties) {
|
|
19
|
+
this.simAws = properties.simAws;
|
|
17
20
|
this.cdkOutContext = properties.cdkOutContext;
|
|
18
21
|
this.caller = properties.caller;
|
|
19
22
|
this.assetsCaller = properties.assetsCaller;
|
|
@@ -4,7 +4,7 @@ import type { SimAwsCaller } from "../../aws/caller/sim-aws-caller.js";
|
|
|
4
4
|
import type { SimAwsAccountRegionScope } from "../../aws/sim-aws-account-region-scope.js";
|
|
5
5
|
import type { SimCfnBinding } from "../bind/sim-cfn-binding.js";
|
|
6
6
|
import type { SimCdkOutContext } from "../cdk/sim-cdk-out-context.js";
|
|
7
|
-
import { type SimCfnStackUpdateContext } from "./sim-cfn-stack-deploy-context.js";
|
|
7
|
+
import { SimCfnStackDeployContext, type SimCfnStackUpdateContext } from "./sim-cfn-stack-deploy-context.js";
|
|
8
8
|
import type { SimCfnResource } from "../resource/sim-cfn-resource.js";
|
|
9
9
|
import { SimCfnResourceRetention } from "../resource/delete/sim-cfn-resource-retention.js";
|
|
10
10
|
import type { SimCfnTemplate } from "../template/sim-cfn-template.js";
|
|
@@ -53,12 +53,13 @@ interface SimCfnStackResourceOperationsProperties {
|
|
|
53
53
|
* ordering of the work itself.
|
|
54
54
|
*/
|
|
55
55
|
export declare class SimCfnStackResourceOperations {
|
|
56
|
+
/** What the Stack deploys from, into and as. */
|
|
57
|
+
readonly context: SimCfnStackDeployContext;
|
|
56
58
|
private readonly simAws;
|
|
57
59
|
private readonly accountRegionScope;
|
|
58
60
|
private readonly stackName;
|
|
59
61
|
private readonly bindings;
|
|
60
62
|
private readonly resourceOrder;
|
|
61
|
-
private readonly context;
|
|
62
63
|
/**
|
|
63
64
|
* Resources an update kept in simulated AWS.
|
|
64
65
|
*
|
|
@@ -20,12 +20,13 @@ import { SimCfnStackResourceUpdates } from "./update/sim-cfn-stack-resource-upda
|
|
|
20
20
|
* ordering of the work itself.
|
|
21
21
|
*/
|
|
22
22
|
export class SimCfnStackResourceOperations {
|
|
23
|
+
/** What the Stack deploys from, into and as. */
|
|
24
|
+
context;
|
|
23
25
|
simAws;
|
|
24
26
|
accountRegionScope;
|
|
25
27
|
stackName;
|
|
26
28
|
bindings;
|
|
27
29
|
resourceOrder;
|
|
28
|
-
context;
|
|
29
30
|
/**
|
|
30
31
|
* Resources an update kept in simulated AWS.
|
|
31
32
|
*
|
|
@@ -3,6 +3,7 @@ import { makeSimCfnStackResourceMap } from "./resource-map/sim-cfn-stack-resourc
|
|
|
3
3
|
import { SimCfnStackResourceLookup } from "./resource-map/sim-cfn-stack-resource-lookup.js";
|
|
4
4
|
import { SimCfnStackDeploymentLifecycle } from "./deploy/sim-cfn-stack-deployment-lifecycle.js";
|
|
5
5
|
import { SimCfnStackResourceOperations } from "./sim-cfn-stack-resource-operations.js";
|
|
6
|
+
import { SimCfnReadHandlerAuthorizer } from "../registry/sim-cfn-read-handler-authorizer.js";
|
|
6
7
|
import { SimCfnStackUpdateLifecycle } from "./update/sim-cfn-stack-update-lifecycle.js";
|
|
7
8
|
import { SimCfnStackOutputs } from "./output/sim-cfn-stack-outputs.js";
|
|
8
9
|
import { SimCfnStackOutputLookup } from "./output/sim-cfn-stack-output-lookup.js";
|
|
@@ -148,6 +149,14 @@ export class SimCfnStack {
|
|
|
148
149
|
await this.updating.update({
|
|
149
150
|
apply: async () => {
|
|
150
151
|
await updater.apply();
|
|
152
|
+
// Resolving an output reads an untouched Resource through its read
|
|
153
|
+
// handler. A rollback makes no such call, so it has no step here.
|
|
154
|
+
await new SimCfnReadHandlerAuthorizer({
|
|
155
|
+
context: this.operations.context,
|
|
156
|
+
}).authorizeUpdate(updater.plan, {
|
|
157
|
+
updated: template,
|
|
158
|
+
resources: this.resourceMap,
|
|
159
|
+
});
|
|
151
160
|
this.resolveOutputs();
|
|
152
161
|
},
|
|
153
162
|
rollBack: async () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SimCfnResource } from "../../resource/sim-cfn-resource.js";
|
|
2
2
|
import { SimCfnResourceRetention } from "../../resource/delete/sim-cfn-resource-retention.js";
|
|
3
|
+
import type { CfnTemplateBodyRecord } from "../../template/sim-cfn-template.js";
|
|
3
4
|
import { type SimCfnStackInPlaceUpdates } from "./sim-cfn-stack-resource-changes.js";
|
|
4
5
|
import { type SimCfnStackResourceChange } from "./sim-cfn-stack-resource-change-pairs.js";
|
|
5
6
|
interface SimCfnStackUpdatePlanProperties {
|
|
@@ -39,6 +40,11 @@ export declare class SimCfnStackUpdatePlan {
|
|
|
39
40
|
*/
|
|
40
41
|
readonly creations: readonly SimCfnResource[];
|
|
41
42
|
readonly updated: ReadonlyMap<string, SimCfnResource>;
|
|
43
|
+
/**
|
|
44
|
+
* The deployed Resources the new template describes unchanged, which the
|
|
45
|
+
* update leaves where they are.
|
|
46
|
+
*/
|
|
47
|
+
readonly untouched: readonly SimCfnResource[];
|
|
42
48
|
constructor(properties: SimCfnStackUpdatePlanProperties);
|
|
43
49
|
/**
|
|
44
50
|
* What the update's deletions are to leave in simulated AWS.
|
|
@@ -49,6 +55,11 @@ export declare class SimCfnStackUpdatePlan {
|
|
|
49
55
|
* deletes that Resource.
|
|
50
56
|
*/
|
|
51
57
|
retention(): SimCfnResourceRetention;
|
|
58
|
+
/**
|
|
59
|
+
* The untouched Resources whose attributes the template reads, through
|
|
60
|
+
* `Fn::GetAtt` or `Fn::Sub`.
|
|
61
|
+
*/
|
|
62
|
+
untouchedReadBy(template: CfnTemplateBodyRecord): SimCfnResource[];
|
|
52
63
|
/**
|
|
53
64
|
* Whether the update changes any Resource at all.
|
|
54
65
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SimCfnResourceRetention } from "../../resource/delete/sim-cfn-resource-retention.js";
|
|
2
|
+
import { simCfnAttributeReads } from "../../registry/sim-cfn-attribute-reads.js";
|
|
2
3
|
import { simCfnStackResourceChanges, } from "./sim-cfn-stack-resource-changes.js";
|
|
3
4
|
import { simCfnStackResourceChangePairs, } from "./sim-cfn-stack-resource-change-pairs.js";
|
|
4
5
|
/**
|
|
@@ -33,6 +34,11 @@ export class SimCfnStackUpdatePlan {
|
|
|
33
34
|
*/
|
|
34
35
|
creations;
|
|
35
36
|
updated;
|
|
37
|
+
/**
|
|
38
|
+
* The deployed Resources the new template describes unchanged, which the
|
|
39
|
+
* update leaves where they are.
|
|
40
|
+
*/
|
|
41
|
+
untouched;
|
|
36
42
|
constructor(properties) {
|
|
37
43
|
const { current, updated, inPlaceUpdates } = properties;
|
|
38
44
|
const { replaced, updatedInPlace } = simCfnStackResourceChanges({
|
|
@@ -57,6 +63,14 @@ export class SimCfnStackUpdatePlan {
|
|
|
57
63
|
return (!current.has(resource.logicalId) || replaced.has(resource.logicalId));
|
|
58
64
|
})
|
|
59
65
|
.toArray();
|
|
66
|
+
this.untouched = current
|
|
67
|
+
.values()
|
|
68
|
+
.filter(({ logicalId }) => {
|
|
69
|
+
return (updated.has(logicalId) &&
|
|
70
|
+
!replaced.has(logicalId) &&
|
|
71
|
+
!updatedInPlace.has(logicalId));
|
|
72
|
+
})
|
|
73
|
+
.toArray();
|
|
60
74
|
}
|
|
61
75
|
/**
|
|
62
76
|
* What the update's deletions are to leave in simulated AWS.
|
|
@@ -74,6 +88,14 @@ export class SimCfnStackUpdatePlan {
|
|
|
74
88
|
])),
|
|
75
89
|
});
|
|
76
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* The untouched Resources whose attributes the template reads, through
|
|
93
|
+
* `Fn::GetAtt` or `Fn::Sub`.
|
|
94
|
+
*/
|
|
95
|
+
untouchedReadBy(template) {
|
|
96
|
+
const reads = simCfnAttributeReads(template);
|
|
97
|
+
return this.untouched.filter(({ logicalId }) => reads.has(logicalId));
|
|
98
|
+
}
|
|
77
99
|
/**
|
|
78
100
|
* Whether the update changes any Resource at all.
|
|
79
101
|
*
|
|
@@ -3,6 +3,7 @@ import type { SimAwsAccountRegionScope } from "../../../aws/sim-aws-account-regi
|
|
|
3
3
|
import type { SimCfnResource } from "../../resource/sim-cfn-resource.js";
|
|
4
4
|
import type { SimCfnTemplate } from "../../template/sim-cfn-template.js";
|
|
5
5
|
import type { SimCfnStackResourceOperations } from "../sim-cfn-stack-resource-operations.js";
|
|
6
|
+
import { SimCfnStackUpdatePlan } from "./sim-cfn-stack-update-plan.js";
|
|
6
7
|
/**
|
|
7
8
|
* What CloudFormation answers an update that would change nothing.
|
|
8
9
|
*
|
|
@@ -34,8 +35,9 @@ interface SimCfnStackUpdaterProperties {
|
|
|
34
35
|
* SimCfnStackUpdateLifecycle owns the first and SimCfnStack the second.
|
|
35
36
|
*/
|
|
36
37
|
export declare class SimCfnStackUpdater {
|
|
38
|
+
/** What the update does to the Stack's Resources. */
|
|
39
|
+
readonly plan: SimCfnStackUpdatePlan;
|
|
37
40
|
private readonly properties;
|
|
38
|
-
private readonly plan;
|
|
39
41
|
constructor(properties: SimCfnStackUpdaterProperties);
|
|
40
42
|
/**
|
|
41
43
|
* Refuse an update that would do nothing, the way CloudFormation refuses one.
|
|
@@ -25,8 +25,9 @@ export const simCfnNoUpdatesMessage = "No updates are to be performed.";
|
|
|
25
25
|
* SimCfnStackUpdateLifecycle owns the first and SimCfnStack the second.
|
|
26
26
|
*/
|
|
27
27
|
export class SimCfnStackUpdater {
|
|
28
|
-
|
|
28
|
+
/** What the update does to the Stack's Resources. */
|
|
29
29
|
plan;
|
|
30
|
+
properties;
|
|
30
31
|
constructor(properties) {
|
|
31
32
|
const { accountRegionScope, background, resources, updated } = properties;
|
|
32
33
|
this.properties = properties;
|
|
@@ -14,6 +14,7 @@ export async function runSimStatesTask(state, input, context) {
|
|
|
14
14
|
target: state.target,
|
|
15
15
|
payload: simStatesEffectiveInput(input, state, context.contextObject),
|
|
16
16
|
roleArn: context.roleArn,
|
|
17
|
+
stateMachineArn: context.stateMachineArn,
|
|
17
18
|
});
|
|
18
19
|
const output = simStatesEffectiveOutput(input, result, state, context.contextObject);
|
|
19
20
|
return state.Next === undefined
|
|
@@ -71,6 +71,7 @@ export interface SimStatesWalkContext {
|
|
|
71
71
|
* The state machine's execution role, which a task assumes.
|
|
72
72
|
*/
|
|
73
73
|
readonly roleArn: string;
|
|
74
|
+
readonly stateMachineArn: string;
|
|
74
75
|
/**
|
|
75
76
|
* What the walk is recording itself on, which a `Parallel` state's branch
|
|
76
77
|
* hangs off.
|
|
@@ -11,6 +11,7 @@ interface SimStatesWalksProperties {
|
|
|
11
11
|
* The state machine's execution role, which a task assumes.
|
|
12
12
|
*/
|
|
13
13
|
readonly roleArn: string;
|
|
14
|
+
readonly stateMachineArn: string;
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* How one execution walks a set of states, its `Parallel` branches included.
|
|
@@ -16,6 +16,7 @@ export function simStatesWalks(properties) {
|
|
|
16
16
|
contextObject: child.contextObject,
|
|
17
17
|
tasks: properties.tasks,
|
|
18
18
|
roleArn: properties.roleArn,
|
|
19
|
+
stateMachineArn: properties.stateMachineArn,
|
|
19
20
|
walkChild: walks,
|
|
20
21
|
},
|
|
21
22
|
...(child.onSettled !== undefined && { onSettled: child.onSettled }),
|
|
@@ -30,7 +30,12 @@ export class SimAwsStatesTaskTargets {
|
|
|
30
30
|
const { stateName, target } = invocation;
|
|
31
31
|
const role = simStatesExecutionRoleTarget(invocation.roleArn, stateName);
|
|
32
32
|
const roleScope = this.#simAws.accountRegionScope(role.accountId, this.#accountRegionScope.regionName);
|
|
33
|
-
|
|
33
|
+
// Step Functions supplies the state machine as the trust policy source.
|
|
34
|
+
// https://docs.aws.amazon.com/step-functions/latest/dg/procedure-create-iam-role.html
|
|
35
|
+
const caller = await assumeSimStatesExecutionRole(role, roleScope, {
|
|
36
|
+
arn: invocation.stateMachineArn,
|
|
37
|
+
accountId: this.#accountRegionScope.accountId,
|
|
38
|
+
});
|
|
34
39
|
return await target.run({
|
|
35
40
|
stateName,
|
|
36
41
|
payload: invocation.payload,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SimAwsCaller } from "../../aws/caller/sim-aws-caller.js";
|
|
2
2
|
import type { SimAwsAccountRegionContainer } from "../../aws/sim-aws-account-region-scope.js";
|
|
3
3
|
import { type SimServiceRoleTarget } from "../../sts/service-role/sim-service-role-target.js";
|
|
4
|
+
import type { SimServiceRoleSource } from "../../sts/service-role/sim-service-role-source.js";
|
|
4
5
|
/**
|
|
5
6
|
* The service principal a state machine's execution role trusts.
|
|
6
7
|
*/
|
|
@@ -21,4 +22,4 @@ export declare function simStatesExecutionRoleTarget(roleArn: string, stateName:
|
|
|
21
22
|
* rule, which reaches a function as a service principal and is admitted by the
|
|
22
23
|
* function's resource policy.
|
|
23
24
|
*/
|
|
24
|
-
export declare function assumeSimStatesExecutionRole(target: SimServiceRoleTarget, scope: SimAwsAccountRegionContainer): Promise<SimAwsCaller>;
|
|
25
|
+
export declare function assumeSimStatesExecutionRole(target: SimServiceRoleTarget, scope: SimAwsAccountRegionContainer, source: SimServiceRoleSource): Promise<SimAwsCaller>;
|
|
@@ -46,12 +46,13 @@ const refusals = {
|
|
|
46
46
|
* rule, which reaches a function as a service principal and is admitted by the
|
|
47
47
|
* function's resource policy.
|
|
48
48
|
*/
|
|
49
|
-
export async function assumeSimStatesExecutionRole(target, scope) {
|
|
49
|
+
export async function assumeSimStatesExecutionRole(target, scope, source) {
|
|
50
50
|
return await assumeSimServiceRole({
|
|
51
51
|
target,
|
|
52
52
|
servicePrincipal: simStatesServicePrincipal,
|
|
53
53
|
sessionName,
|
|
54
54
|
scope,
|
|
55
55
|
refusals,
|
|
56
|
+
source,
|
|
56
57
|
});
|
|
57
58
|
}
|
|
@@ -2165,6 +2165,100 @@ console.log(stacks.size);
|
|
|
2165
2165
|
See [simulated IAM](https://yulinsim.dev/services/iam/ "Simulated IAM usage docs") for the documents
|
|
2166
2166
|
it takes.
|
|
2167
2167
|
|
|
2168
|
+
### The read calls an update makes
|
|
2169
|
+
|
|
2170
|
+
CloudFormation calls a resource's read handler when it needs the resource's attributes and holds no
|
|
2171
|
+
fresh copy of them. A stack update that leaves a resource unchanged, while the new template reads one
|
|
2172
|
+
of its attributes through `Fn::GetAtt` or `Fn::Sub`, is one such case. The handler runs as the
|
|
2173
|
+
stack's execution role. Yulin authorizes each of its calls as the deployment's principal, after the
|
|
2174
|
+
update's own changes and before its outputs are resolved. A denied call fails the update, which is
|
|
2175
|
+
rolled back.
|
|
2176
|
+
|
|
2177
|
+
The calls come from the resource type's entry in the CloudFormation registry. Yulin keeps the ones in
|
|
2178
|
+
the type's own service and authorizes them on the resource's ARN, built from the stack's account and
|
|
2179
|
+
Region and the resource's properties. A resource yulin skips is authorized the same way, since the
|
|
2180
|
+
snapshot and the properties are all this takes. A call IAM scopes to no resource type is
|
|
2181
|
+
authorized on `*`.
|
|
2182
|
+
|
|
2183
|
+
```typescript sim-cloudformation-read-handler
|
|
2184
|
+
/**
|
|
2185
|
+
* An update refused because the deploy Role may not read an unchanged
|
|
2186
|
+
* resource whose attribute an output reads.
|
|
2187
|
+
*/
|
|
2188
|
+
|
|
2189
|
+
import { SimAws } from "@kensio/yulin";
|
|
2190
|
+
|
|
2191
|
+
const simAws = new SimAws({
|
|
2192
|
+
defaultAccountId: "123456789012",
|
|
2193
|
+
defaultRegionName: "eu-west-2",
|
|
2194
|
+
});
|
|
2195
|
+
|
|
2196
|
+
const deployer = await simAws.iam().makeDeployRole({
|
|
2197
|
+
roleName: "cdk-exec",
|
|
2198
|
+
policyDocument: {
|
|
2199
|
+
Version: "2012-10-17",
|
|
2200
|
+
Statement: {
|
|
2201
|
+
Effect: "Allow",
|
|
2202
|
+
Action: [
|
|
2203
|
+
"s3:*",
|
|
2204
|
+
"codeartifact:CreateDomain",
|
|
2205
|
+
"codeartifact:CreateRepository",
|
|
2206
|
+
"codeartifact:DescribeRepository",
|
|
2207
|
+
"codeartifact:ListTagsForResource",
|
|
2208
|
+
],
|
|
2209
|
+
Resource: "*",
|
|
2210
|
+
},
|
|
2211
|
+
},
|
|
2212
|
+
});
|
|
2213
|
+
|
|
2214
|
+
const mirror = {
|
|
2215
|
+
Domain: {
|
|
2216
|
+
Type: "AWS::CodeArtifact::Domain",
|
|
2217
|
+
Properties: { DomainName: "example" },
|
|
2218
|
+
},
|
|
2219
|
+
Repository: {
|
|
2220
|
+
Type: "AWS::CodeArtifact::Repository",
|
|
2221
|
+
Properties: { DomainName: "example", RepositoryName: "npm" },
|
|
2222
|
+
DependsOn: "Domain",
|
|
2223
|
+
},
|
|
2224
|
+
};
|
|
2225
|
+
const outputs = {
|
|
2226
|
+
RepositoryArn: { Value: { "Fn::GetAtt": ["Repository", "Arn"] } },
|
|
2227
|
+
};
|
|
2228
|
+
|
|
2229
|
+
const cloudFormation = simAws.cloudFormation();
|
|
2230
|
+
|
|
2231
|
+
await cloudFormation.deployTemplate({
|
|
2232
|
+
stackName: "artifacts",
|
|
2233
|
+
template: { Resources: mirror, Outputs: outputs },
|
|
2234
|
+
caller: deployer,
|
|
2235
|
+
});
|
|
2236
|
+
|
|
2237
|
+
await cloudFormation.updateStack({
|
|
2238
|
+
input: {
|
|
2239
|
+
StackName: "artifacts",
|
|
2240
|
+
TemplateBody: JSON.stringify({
|
|
2241
|
+
Resources: { ...mirror, Sources: { Type: "AWS::S3::Bucket" } },
|
|
2242
|
+
Outputs: outputs,
|
|
2243
|
+
}),
|
|
2244
|
+
},
|
|
2245
|
+
});
|
|
2246
|
+
|
|
2247
|
+
try {
|
|
2248
|
+
await cloudFormation.waitForStackUpdateComplete("artifacts");
|
|
2249
|
+
} catch (error) {
|
|
2250
|
+
// "... is not authorized to perform:
|
|
2251
|
+
// codeartifact:GetRepositoryPermissionsPolicy on resource:
|
|
2252
|
+
// arn:aws:codeartifact:eu-west-2:123456789012:repository/example/npm"
|
|
2253
|
+
console.log((error as Error).message);
|
|
2254
|
+
}
|
|
2255
|
+
```
|
|
2256
|
+
|
|
2257
|
+
A resource the update creates or changes needs no read, because its own handler returned its
|
|
2258
|
+
attributes. A resource whose ARN cannot be built fails the update with the ARN variable it was
|
|
2259
|
+
missing. That happens to a resource yulin skips whose ARN holds an identifier its service would have
|
|
2260
|
+
generated, since only a created resource can give one.
|
|
2261
|
+
|
|
2168
2262
|
### One caller for an assembly, and one per Stack
|
|
2169
2263
|
|
|
2170
2264
|
`deployCdkOut(...)` takes a caller for every Stack in the assembly. A Stack deployed by a different
|
|
@@ -4579,4 +4673,16 @@ Each service's own docs describe what its resource types support.
|
|
|
4579
4673
|
`InvokeRole` and `AddDefaultAuthorizerToCorsPreflight` fail the transform by name.
|
|
4580
4674
|
`AutoPublishAlias` and `DeploymentPreference` are left out, because the simulator has one version
|
|
4581
4675
|
of a function and nothing to shift traffic between.
|
|
4676
|
+
- A stack update authorizes the read handler calls of an unchanged resource whose attributes the
|
|
4677
|
+
new template reads, and no other handler calls. The create, update and delete calls are the ones
|
|
4678
|
+
each service makes itself. Creating a resource of a type yulin skips authorizes nothing. Of the read
|
|
4679
|
+
calls, only those in the resource type's own service are authorized. The registry also lists calls
|
|
4680
|
+
into other services (a KMS key, a role a property names), and a handler makes those only when a
|
|
4681
|
+
property leads to them. See [the read calls an update makes](#the-read-calls-an-update-makes).
|
|
4682
|
+
- The read handler calls come from a snapshot of the registry and the Service Authorization
|
|
4683
|
+
Reference, which `pnpm run cfn:read-handlers` refreshes. A resource type the snapshot leaves out is
|
|
4684
|
+
not authorized at all. It leaves out a type whose read handler is unpublished, and one with no
|
|
4685
|
+
resource type in the Service Authorization Reference named after it.
|
|
4686
|
+
- An `Fn::GetAtt` inside an `Fn::If` branch the conditions rule out still counts as reading the
|
|
4687
|
+
resource.
|
|
4582
4688
|
- Many advanced CloudFormation features are outside the simulation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kensio/yulin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "AWS system behaviour simulation for isolated unit testing",
|
|
5
5
|
"repository": "https://github.com/KensioSoftware/yulin",
|
|
6
6
|
"homepage": "https://yulinsim.dev/",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"build": "pnpm clean && tsc -p tsconfig.build.json && pnpm oxlint:config && pnpm docs:index",
|
|
16
16
|
"oxlint:config": "pnpm tsx scripts/mts/write-oxlint-config.mts",
|
|
17
17
|
"docs:index": "pnpm tsx scripts/mts/write-llms-index.mts",
|
|
18
|
+
"cfn:read-handlers": "pnpm tsx scripts/mts/write-cfn-read-handlers.mts",
|
|
18
19
|
"build:check": "tsc -p tsconfig.json --noEmit",
|
|
19
20
|
"prepack": "pnpm build",
|
|
20
21
|
"verify:pack": "pnpm tsx scripts/mts/verify-pack.mts",
|
|
@@ -290,16 +291,16 @@
|
|
|
290
291
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
291
292
|
"esbuild": "^0.28.2",
|
|
292
293
|
"eslint": "^10.8.0",
|
|
293
|
-
"eslint-plugin-jsdoc": "^
|
|
294
|
+
"eslint-plugin-jsdoc": "^65.0.0",
|
|
294
295
|
"eslint-plugin-no-secrets": "^2.3.3",
|
|
295
296
|
"eslint-plugin-security": "^4.0.1",
|
|
296
297
|
"eslint-plugin-unicorn": "^76.0.0",
|
|
297
298
|
"execa": "^10.0.0",
|
|
298
299
|
"fta-cli": "^3.0.0",
|
|
299
300
|
"hyparquet": "^1.29.2",
|
|
300
|
-
"hyparquet-writer": "^0.
|
|
301
|
+
"hyparquet-writer": "^0.17.0",
|
|
301
302
|
"node-sql-parser": "^5.4.0",
|
|
302
|
-
"oxfmt": "^0.
|
|
303
|
+
"oxfmt": "^0.74.0",
|
|
303
304
|
"oxlint": "^1.77.0",
|
|
304
305
|
"oxlint-tsgolint": "^7.0.2001",
|
|
305
306
|
"semantic-release": "25.0.9",
|