@kensio/yulin 1.21.33 → 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.
Files changed (35) hide show
  1. package/dist/sdk/wire/sim-sdk-wire-response.d.ts +5 -0
  2. package/dist/sdk/wire/sim-sdk-wire-response.js +20 -0
  3. package/dist/service/cloudformation/registry/sim-cfn-attribute-reads.d.ts +11 -0
  4. package/dist/service/cloudformation/registry/sim-cfn-attribute-reads.js +64 -0
  5. package/dist/service/cloudformation/registry/sim-cfn-read-handler-authorizer.d.ts +45 -0
  6. package/dist/service/cloudformation/registry/sim-cfn-read-handler-authorizer.js +72 -0
  7. package/dist/service/cloudformation/registry/sim-cfn-read-handler.d.ts +33 -0
  8. package/dist/service/cloudformation/registry/sim-cfn-read-handler.fixture.d.ts +24 -0
  9. package/dist/service/cloudformation/registry/sim-cfn-read-handler.fixture.js +58 -0
  10. package/dist/service/cloudformation/registry/sim-cfn-read-handler.js +36 -0
  11. package/dist/service/cloudformation/registry/sim-cfn-read-handlers.d.ts +10 -0
  12. package/dist/service/cloudformation/registry/sim-cfn-read-handlers.js +23 -0
  13. package/dist/service/cloudformation/registry/sim-cfn-read-handlers.json +7400 -0
  14. package/dist/service/cloudformation/registry/sim-cfn-resource-arn.d.ts +46 -0
  15. package/dist/service/cloudformation/registry/sim-cfn-resource-arn.js +119 -0
  16. package/dist/service/cloudformation/stack/sim-cfn-stack-deploy-context.d.ts +6 -1
  17. package/dist/service/cloudformation/stack/sim-cfn-stack-deploy-context.js +3 -0
  18. package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.d.ts +3 -2
  19. package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.js +2 -1
  20. package/dist/service/cloudformation/stack/sim-cfn-stack.js +9 -0
  21. package/dist/service/cloudformation/stack/update/sim-cfn-stack-update-plan.d.ts +11 -0
  22. package/dist/service/cloudformation/stack/update/sim-cfn-stack-update-plan.js +22 -0
  23. package/dist/service/cloudformation/stack/update/sim-cfn-stack-updater.d.ts +3 -1
  24. package/dist/service/cloudformation/stack/update/sim-cfn-stack-updater.js +2 -1
  25. package/dist/service/stepfunctions/command/execution/sim-states-execution-start.js +1 -0
  26. package/dist/service/stepfunctions/execution/sim-states-run-task.js +1 -0
  27. package/dist/service/stepfunctions/execution/sim-states-state-outcome.d.ts +1 -0
  28. package/dist/service/stepfunctions/execution/sim-states-walks.d.ts +1 -0
  29. package/dist/service/stepfunctions/execution/sim-states-walks.js +1 -0
  30. package/dist/service/stepfunctions/task/sim-aws-states-task-targets.js +6 -1
  31. package/dist/service/stepfunctions/task/sim-states-execution-role.d.ts +2 -1
  32. package/dist/service/stepfunctions/task/sim-states-execution-role.js +2 -1
  33. package/dist/service/stepfunctions/task/sim-states-task-invocation.d.ts +1 -0
  34. package/docs/services/cloudformation/README.md +106 -0
  35. 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
- properties;
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;
@@ -56,6 +56,7 @@ export class SimStatesExecutionStart {
56
56
  background: this.#background,
57
57
  tasks: this.#tasks,
58
58
  roleArn: stateMachine.roleArn,
59
+ stateMachineArn: stateMachine.arn,
59
60
  })({
60
61
  definition: stateMachine.parsedDefinition,
61
62
  record: execution,
@@ -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
- const caller = await assumeSimStatesExecutionRole(role, roleScope);
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
  }
@@ -15,6 +15,7 @@ export interface SimStatesTaskInvocation {
15
15
  * work.
16
16
  */
17
17
  readonly roleArn: string;
18
+ readonly stateMachineArn: string;
18
19
  }
19
20
  /**
20
21
  * Where a `Task` state's work is done.
@@ -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.21.33",
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": "^64.0.1",
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.16.9",
301
+ "hyparquet-writer": "^0.17.0",
301
302
  "node-sql-parser": "^5.4.0",
302
- "oxfmt": "^0.73.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",