@intentius/chant-lexicon-aws 0.124.31 → 0.124.32
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/actions/dynamodb.js +41 -0
- package/dist/actions/ecr.js +9 -0
- package/dist/actions/ecs.js +5 -0
- package/dist/actions/iam.js +3 -0
- package/dist/actions/index.js +9 -0
- package/dist/actions/lambda.js +11 -0
- package/dist/actions/logs.js +4 -0
- package/dist/actions/s3.js +40 -0
- package/dist/actions/sns.js +5 -0
- package/dist/actions/sqs.js +15 -0
- package/dist/agentcore/trace-fetch.d.ts.map +1 -1
- package/dist/agentcore/trace-fetch.js +472 -0
- package/dist/agentcore/trace-render.js +359 -0
- package/dist/ambient.js +148 -0
- package/dist/api/credential-chain.d.ts +30 -3
- package/dist/api/credential-chain.d.ts.map +1 -1
- package/dist/api/credential-chain.js +509 -0
- package/dist/api/read-client.d.ts +16 -43
- package/dist/api/read-client.d.ts.map +1 -1
- package/dist/api/read-client.js +458 -0
- package/dist/api/sigv4.d.ts +4 -4
- package/dist/api/sigv4.js +185 -0
- package/dist/behaviour-kinds.js +114 -0
- package/dist/caller-identity.d.ts +3 -5
- package/dist/caller-identity.d.ts.map +1 -1
- package/dist/caller-identity.js +146 -0
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +120 -0
- package/dist/codegen/enum-overlay.js +162 -0
- package/dist/codegen/extensions.js +139 -0
- package/dist/codegen/fallback.js +32 -0
- package/dist/codegen/generate-cli.js +15 -0
- package/dist/codegen/generate-lexicon.js +70 -0
- package/dist/codegen/generate-typescript.js +226 -0
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/codegen/generate.js +234 -0
- package/dist/codegen/naming.js +135 -0
- package/dist/codegen/package.js +67 -0
- package/dist/codegen/patches.d.ts +26 -1
- package/dist/codegen/patches.d.ts.map +1 -1
- package/dist/codegen/patches.js +90 -0
- package/dist/codegen/property-overlay.js +85 -0
- package/dist/codegen/property-overlay.json +1 -16
- package/dist/codegen/sam.js +402 -0
- package/dist/codegen/typecheck.js +4 -0
- package/dist/codegen/versions.js +31 -0
- package/dist/components/__tests__/mock-cloud-executor.js +326 -0
- package/dist/components/apply.d.ts +44 -1
- package/dist/components/apply.d.ts.map +1 -1
- package/dist/components/apply.js +752 -0
- package/dist/components/builders.js +37 -0
- package/dist/components/capability-plugin.js +66 -0
- package/dist/components/cloud-executor.d.ts +35 -0
- package/dist/components/cloud-executor.d.ts.map +1 -1
- package/dist/components/cloud-executor.js +576 -0
- package/dist/components/config-bom.js +207 -0
- package/dist/components/host-delivery.js +117 -0
- package/dist/components/index.js +18 -0
- package/dist/components/job-submission.js +77 -0
- package/dist/components/publish.js +234 -0
- package/dist/components/safety.js +62 -0
- package/dist/components/wait-aws.js +68 -0
- package/dist/composites/agentcore-agent.js +187 -0
- package/dist/composites/agentcore-trust-policy.js +13 -0
- package/dist/composites/alb-shared.js +84 -0
- package/dist/composites/bucket-deployment.js +93 -0
- package/dist/composites/catalog.js +2714 -0
- package/dist/composites/dynamodb-table.js +66 -0
- package/dist/composites/ec2-instance-bundle.js +75 -0
- package/dist/composites/ec2-instance-role.js +26 -0
- package/dist/composites/ecr-repository.js +64 -0
- package/dist/composites/ecs-trust-policy.js +10 -0
- package/dist/composites/efs-with-access-point.js +44 -0
- package/dist/composites/eks-cluster.js +118 -0
- package/dist/composites/fargate-alb.js +216 -0
- package/dist/composites/fargate-service.js +222 -0
- package/dist/composites/index.js +28 -0
- package/dist/composites/lambda-api.js +15 -0
- package/dist/composites/lambda-dynamodb.js +77 -0
- package/dist/composites/lambda-eventbridge.js +27 -0
- package/dist/composites/lambda-function.js +54 -0
- package/dist/composites/lambda-s3.js +93 -0
- package/dist/composites/lambda-sns.js +23 -0
- package/dist/composites/lambda-sqs.js +34 -0
- package/dist/composites/landing-zone.js +82 -0
- package/dist/composites/microvm-app.js +293 -0
- package/dist/composites/minimal-vpc.js +40 -0
- package/dist/composites/monitoring-stack.js +105 -0
- package/dist/composites/nlb-service.js +61 -0
- package/dist/composites/rds-instance.js +98 -0
- package/dist/composites/scheduled-lambda.js +28 -0
- package/dist/composites/solr-fargate-service.js +33 -0
- package/dist/composites/step-functions-workflow.js +78 -0
- package/dist/composites/vpc-default.js +132 -0
- package/dist/condition.js +43 -0
- package/dist/coverage.js +4 -0
- package/dist/deep-observe.d.ts.map +1 -1
- package/dist/deep-observe.js +709 -0
- package/dist/deep-topology.js +226 -0
- package/dist/default-tags.js +50 -0
- package/dist/defaults.js +85 -0
- package/dist/dependencies.js +269 -0
- package/dist/detect.js +21 -0
- package/dist/disruption.d.ts.map +1 -1
- package/dist/disruption.js +129 -0
- package/dist/effect-receipt-row.js +107 -0
- package/dist/generated/index.d.ts +1297 -82
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/generated/index.js +12382 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/governance.js +131 -0
- package/dist/identity-observe.d.ts.map +1 -1
- package/dist/identity-observe.js +127 -0
- package/dist/import/generator.js +757 -0
- package/dist/import/live-export.js +34 -0
- package/dist/import/parser.js +394 -0
- package/dist/index.js +69 -0
- package/dist/integrity.json +6 -6
- package/dist/intrinsics.js +393 -0
- package/dist/lint/audit-catalog.js +87 -0
- package/dist/lint/audit-lineage.js +150 -0
- package/dist/lint/post-synth/cf-refs.js +247 -0
- package/dist/lint/post-synth/cor020.js +60 -0
- package/dist/lint/post-synth/ext001.d.ts.map +1 -1
- package/dist/lint/post-synth/ext001.js +197 -0
- package/dist/lint/post-synth/index.js +124 -0
- package/dist/lint/post-synth/waw010.js +41 -0
- package/dist/lint/post-synth/waw011.js +41 -0
- package/dist/lint/post-synth/waw013.js +38 -0
- package/dist/lint/post-synth/waw014.js +42 -0
- package/dist/lint/post-synth/waw015.js +88 -0
- package/dist/lint/post-synth/waw016.d.ts.map +1 -1
- package/dist/lint/post-synth/waw016.js +116 -0
- package/dist/lint/post-synth/waw017.js +43 -0
- package/dist/lint/post-synth/waw018.js +61 -0
- package/dist/lint/post-synth/waw019.js +64 -0
- package/dist/lint/post-synth/waw020.js +56 -0
- package/dist/lint/post-synth/waw021.js +44 -0
- package/dist/lint/post-synth/waw022.js +36 -0
- package/dist/lint/post-synth/waw023.js +40 -0
- package/dist/lint/post-synth/waw024.js +46 -0
- package/dist/lint/post-synth/waw025.js +36 -0
- package/dist/lint/post-synth/waw026.js +38 -0
- package/dist/lint/post-synth/waw027.js +41 -0
- package/dist/lint/post-synth/waw028.js +39 -0
- package/dist/lint/post-synth/waw029.js +54 -0
- package/dist/lint/post-synth/waw030.js +282 -0
- package/dist/lint/post-synth/waw031.js +58 -0
- package/dist/lint/post-synth/waw032.js +45 -0
- package/dist/lint/post-synth/waw033.js +75 -0
- package/dist/lint/post-synth/waw034.js +52 -0
- package/dist/lint/post-synth/waw035.js +56 -0
- package/dist/lint/post-synth/waw036.js +79 -0
- package/dist/lint/post-synth/waw037.js +67 -0
- package/dist/lint/post-synth/waw038.js +42 -0
- package/dist/lint/post-synth/waw039.js +42 -0
- package/dist/lint/post-synth/waw040.js +45 -0
- package/dist/lint/post-synth/waw041.js +40 -0
- package/dist/lint/post-synth/waw042.js +99 -0
- package/dist/lint/post-synth/waw043.js +51 -0
- package/dist/lint/post-synth/waw044.js +64 -0
- package/dist/lint/post-synth/waw045.js +49 -0
- package/dist/lint/post-synth/waw046.js +52 -0
- package/dist/lint/post-synth/waw047.js +43 -0
- package/dist/lint/post-synth/waw048.js +40 -0
- package/dist/lint/post-synth/waw049.js +78 -0
- package/dist/lint/post-synth/waw050.js +44 -0
- package/dist/lint/post-synth/waw051.js +41 -0
- package/dist/lint/post-synth/waw052.js +43 -0
- package/dist/lint/post-synth/waw053.js +43 -0
- package/dist/lint/post-synth/waw054.js +41 -0
- package/dist/lint/post-synth/waw055.js +41 -0
- package/dist/lint/post-synth/waw056.js +65 -0
- package/dist/lint/post-synth/waw057.js +43 -0
- package/dist/lint/post-synth/waw058.js +61 -0
- package/dist/lint/post-synth/waw059.js +338 -0
- package/dist/lint/post-synth/waw060.js +83 -0
- package/dist/lint/post-synth/waw061.js +60 -0
- package/dist/lint/post-synth/waw062.js +0 -0
- package/dist/lint/post-synth/waw063.js +0 -0
- package/dist/lint/post-synth/waw064.js +45 -0
- package/dist/lint/post-synth/waw065.js +104 -0
- package/dist/lint/post-synth/waw066.js +145 -0
- package/dist/lint/post-synth/waw067.js +115 -0
- package/dist/lint/post-synth/waw068.js +69 -0
- package/dist/lint/post-synth/waw069.js +0 -0
- package/dist/lint/rules/hardcoded-region.js +38 -0
- package/dist/lint/rules/iam-wildcard.js +60 -0
- package/dist/lint/rules/index.js +6 -0
- package/dist/lint/rules/s3-encryption.js +60 -0
- package/dist/live-attrs.js +42 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +66 -0
- package/dist/manifest.json +1 -1
- package/dist/mapping.js +33 -0
- package/dist/meta.json +17359 -784
- package/dist/nested-stack.js +95 -0
- package/dist/okf/index.md +203 -0
- package/dist/okf/rules/EXT001.md +1 -0
- package/dist/okf/rules/WAW016.md +2 -0
- package/dist/okf/rules/WAW019.md +1 -0
- package/dist/okf/rules/WAW028.md +1 -0
- package/dist/okf/rules/WAW030.md +2 -0
- package/dist/okf/rules/WAW039.md +1 -0
- package/dist/okf/rules/WAW049.md +1 -0
- package/dist/okf/rules/WAW056.md +1 -0
- package/dist/okf/rules/WAW058.md +1 -0
- package/dist/okf/rules/WAW062.md +1 -0
- package/dist/okf/rules/WAW064.md +1 -0
- package/dist/okf/rules/WAW065.md +1 -0
- package/dist/okf/rules/WAW066.md +1 -0
- package/dist/okf/rules/WAW067.md +1 -0
- package/dist/okf/rules/WAW068.md +1 -0
- package/dist/okf/rules/WAW069.md +4 -0
- package/dist/okf/types/AIWorkloadConfig.md +23 -0
- package/dist/okf/types/ARCRegionSwitchPlan.md +1 -0
- package/dist/okf/types/AccountAccessApplication.md +28 -0
- package/dist/okf/types/AccountAccessEntitlement.md +22 -0
- package/dist/okf/types/ActionTarget.md +20 -0
- package/dist/okf/types/AdConfiguration.md +21 -0
- package/dist/okf/types/Adapter.md +26 -0
- package/dist/okf/types/AgentContext.md +35 -0
- package/dist/okf/types/AgentGoal.md +31 -0
- package/dist/okf/types/AgentProfile.md +34 -0
- package/dist/okf/types/Alarm.md +3 -1
- package/dist/okf/types/AlarmModel.md +14 -9
- package/dist/okf/types/AmplifyWebhook.md +25 -0
- package/dist/okf/types/ApiCache.md +8 -8
- package/dist/okf/types/ApiGatewayManagedOverrides.md +5 -5
- package/dist/okf/types/AppAuthorization.md +35 -0
- package/dist/okf/types/AppBlockBuilder.md +1 -0
- package/dist/okf/types/AppInstance.md +2 -2
- package/dist/okf/types/AppInstanceBot.md +2 -2
- package/dist/okf/types/AppStreamFleet.md +3 -2
- package/dist/okf/types/AppSyncApiKey.md +6 -6
- package/dist/okf/types/AppSyncType.md +22 -0
- package/dist/okf/types/ApplicationStatusCheck.md +36 -0
- package/dist/okf/types/ApplicationVersion.md +4 -1
- package/dist/okf/types/ApplicationsList.md +26 -0
- package/dist/okf/types/ApprovalPolicy.md +28 -0
- package/dist/okf/types/ArchiveRule.md +24 -0
- package/dist/okf/types/Asset.md +30 -0
- package/dist/okf/types/Backup.md +31 -0
- package/dist/okf/types/BedrockAgentCoreDataset.md +1 -1
- package/dist/okf/types/BedrockAgentCoreGateway.md +3 -0
- package/dist/okf/types/BedrockSession.md +28 -0
- package/dist/okf/types/BillEstimate.md +35 -0
- package/dist/okf/types/Brand.md +32 -0
- package/dist/okf/types/CallAnalyticsCategory.md +25 -0
- package/dist/okf/types/Campaign.md +28 -0
- package/dist/okf/types/CapacityProvider.md +30 -0
- package/dist/okf/types/Case.md +24 -0
- package/dist/okf/types/CertificateAssociation.md +13 -0
- package/dist/okf/types/ChangeSet.md +36 -0
- package/dist/okf/types/ChannelFlow.md +29 -0
- package/dist/okf/types/ChimeChannel.md +39 -0
- package/dist/okf/types/Classifier.md +6 -6
- package/dist/okf/types/ClientVpnAuthorizationRule.md +6 -6
- package/dist/okf/types/ClientVpnEndpoint.md +2 -0
- package/dist/okf/types/ClientVpnRoute.md +5 -5
- package/dist/okf/types/CloudConnector.md +29 -0
- package/dist/okf/types/Cluster.md +39 -0
- package/dist/okf/types/ClusterSchedulerConfig.md +30 -0
- package/dist/okf/types/CodeCommitRepository.md +12 -12
- package/dist/okf/types/CodeConnectionsHost.md +26 -0
- package/dist/okf/types/CodeRepository.md +5 -5
- package/dist/okf/types/CompositeAlarm.md +1 -0
- package/dist/okf/types/ComputeEnvironment.md +2 -0
- package/dist/okf/types/ComputeNodeGroup.md +1 -1
- package/dist/okf/types/ComputeQuota.md +34 -0
- package/dist/okf/types/ConfigRule.md +1 -1
- package/dist/okf/types/ConfigurationRecorder.md +7 -6
- package/dist/okf/types/ConnectAttachment.md +2 -0
- package/dist/okf/types/ConnectQueue.md +5 -1
- package/dist/okf/types/ConnectVocabulary.md +28 -0
- package/dist/okf/types/ConnectionType.md +24 -0
- package/dist/okf/types/ConsentPortal.md +33 -0
- package/dist/okf/types/ContactMethod.md +29 -0
- package/dist/okf/types/ContainerAssociation.md +26 -0
- package/dist/okf/types/ContainerFleet.md +1 -1
- package/dist/okf/types/ContainerGroupDefinition.md +1 -1
- package/dist/okf/types/Content.md +40 -0
- package/dist/okf/types/ContentAssociation.md +28 -0
- package/dist/okf/types/CustomDetectionRuleAssociation.md +28 -0
- package/dist/okf/types/CustomEntityType.md +4 -9
- package/dist/okf/types/Customization.md +22 -0
- package/dist/okf/types/CwIntegration.md +31 -0
- package/dist/okf/types/DAXParameterGroup.md +3 -8
- package/dist/okf/types/DAXSubnetGroup.md +0 -5
- package/dist/okf/types/DBClusterEndpoint.md +31 -0
- package/dist/okf/types/DBProxyTargetGroup.md +1 -0
- package/dist/okf/types/DBSnapshot.md +62 -0
- package/dist/okf/types/DLMLifecyclePolicy.md +12 -12
- package/dist/okf/types/DLPSetting.md +31 -0
- package/dist/okf/types/DMSInstanceProfile.md +1 -1
- package/dist/okf/types/DRSLaunchConfigurationTemplate.md +28 -0
- package/dist/okf/types/Daemon.md +1 -0
- package/dist/okf/types/DataExchangeDataSet.md +29 -0
- package/dist/okf/types/DataIntegrationFlow.md +27 -0
- package/dist/okf/types/DataMigration.md +1 -1
- package/dist/okf/types/DataProvider.md +2 -1
- package/dist/okf/types/DataQualityRuleset.md +6 -11
- package/dist/okf/types/DataSource.md +23 -0
- package/dist/okf/types/Dataset.md +20 -0
- package/dist/okf/types/DatasetIntegration.md +23 -0
- package/dist/okf/types/DbCluster.md +1 -1
- package/dist/okf/types/DbInstance.md +8 -2
- package/dist/okf/types/DeadlineBudget.md +29 -0
- package/dist/okf/types/DelegationSet.md +23 -0
- package/dist/okf/types/DeliveryChannel.md +6 -11
- package/dist/okf/types/DevOpsAgentAgentSpace.md +4 -0
- package/dist/okf/types/DevOpsAgentService.md +1 -0
- package/dist/okf/types/DevOpsAgentTrigger.md +28 -0
- package/dist/okf/types/DirectConnectGatewayAttachment.md +2 -0
- package/dist/okf/types/Directory.md +60 -0
- package/dist/okf/types/DocDBDBClusterParameterGroup.md +5 -10
- package/dist/okf/types/DocDBDBSubnetGroup.md +4 -9
- package/dist/okf/types/DocDBElasticClusterSnapshot.md +36 -0
- package/dist/okf/types/DocumentClassifierEndpoint.md +27 -0
- package/dist/okf/types/Domain.md +27 -0
- package/dist/okf/types/EKSCertificateAuthority.md +42 -0
- package/dist/okf/types/EKSCluster.md +8 -0
- package/dist/okf/types/EcSnapshot.md +66 -0
- package/dist/okf/types/EmailIdentityCertificate.md +15 -0
- package/dist/okf/types/EndpointConfig.md +14 -13
- package/dist/okf/types/EntityRecognizer.md +27 -0
- package/dist/okf/types/EntityRecognizerEndpoint.md +31 -0
- package/dist/okf/types/EnvironmentEC2.md +13 -13
- package/dist/okf/types/EvaluationForm.md +1 -0
- package/dist/okf/types/Evaluator.md +6 -2
- package/dist/okf/types/EventAction.md +26 -0
- package/dist/okf/types/EventRule.md +2 -0
- package/dist/okf/types/EventSource.md +30 -0
- package/dist/okf/types/EventTracker.md +22 -0
- package/dist/okf/types/EventsV2EventBus.md +28 -0
- package/dist/okf/types/EventsV2ResourcePolicy.md +19 -0
- package/dist/okf/types/EventsV2Subscriber.md +39 -0
- package/dist/okf/types/ExperimentDefinition.md +39 -0
- package/dist/okf/types/ExperimentRun.md +33 -0
- package/dist/okf/types/ExperimentTrialComponent.md +13 -11
- package/dist/okf/types/FHIRDatastore.md +3 -0
- package/dist/okf/types/FSxSnapshot.md +5 -5
- package/dist/okf/types/FSxVolume.md +9 -9
- package/dist/okf/types/FeaturedResultsSet.md +26 -0
- package/dist/okf/types/Feed.md +1 -0
- package/dist/okf/types/FieldLevelEncryptionConfig.md +25 -0
- package/dist/okf/types/FieldLevelEncryptionProfile.md +22 -0
- package/dist/okf/types/FileCache.md +45 -0
- package/dist/okf/types/Filter.md +27 -0
- package/dist/okf/types/Firewall.md +6 -0
- package/dist/okf/types/FlowMediaStream.md +28 -0
- package/dist/okf/types/FlowOutput.md +1 -0
- package/dist/okf/types/FpgaImage.md +38 -0
- package/dist/okf/types/Function.md +2 -2
- package/dist/okf/types/Gateway.md +41 -0
- package/dist/okf/types/GatewayClusterAssociations.md +23 -0
- package/dist/okf/types/GatewayRateLimit.md +26 -0
- package/dist/okf/types/GatewayRule.md +32 -0
- package/dist/okf/types/GlobalReplicationGroup.md +3 -0
- package/dist/okf/types/GlobalResolver.md +1 -1
- package/dist/okf/types/GlobalTable.md +1 -0
- package/dist/okf/types/GlueConnection.md +5 -4
- package/dist/okf/types/GlueSecurityConfiguration.md +1 -6
- package/dist/okf/types/GlueSession.md +38 -0
- package/dist/okf/types/GlueTable.md +5 -10
- package/dist/okf/types/GlueWorkflow.md +5 -10
- package/dist/okf/types/Group.md +27 -0
- package/dist/okf/types/Harness.md +1 -0
- package/dist/okf/types/HostKey.md +33 -0
- package/dist/okf/types/HostedZone.md +1 -0
- package/dist/okf/types/HttpApi.md +2 -0
- package/dist/okf/types/HttpNamespace.md +5 -5
- package/dist/okf/types/HubV2.md +1 -0
- package/dist/okf/types/HumanTaskUi.md +22 -0
- package/dist/okf/types/IPv4Pool.md +28 -0
- package/dist/okf/types/ImageBuilder.md +1 -0
- package/dist/okf/types/ImageBuilderImage.md +1 -1
- package/dist/okf/types/ImagePipeline.md +1 -1
- package/dist/okf/types/InferenceComponent.md +5 -1
- package/dist/okf/types/InfluxDBBackup.md +65 -0
- package/dist/okf/types/InfluxDBCluster.md +3 -0
- package/dist/okf/types/InfrastructureConfiguration.md +2 -0
- package/dist/okf/types/IngestionDestination.md +28 -0
- package/dist/okf/types/InputSecurityGroup.md +6 -4
- package/dist/okf/types/InspectorV2Connector.md +38 -0
- package/dist/okf/types/InstanceEventWindow.md +25 -0
- package/dist/okf/types/InstanceFleetConfig.md +10 -10
- package/dist/okf/types/InstanceGroupConfig.md +3 -3
- package/dist/okf/types/InstrumentationConfig.md +31 -0
- package/dist/okf/types/IntegrationAssociation.md +3 -0
- package/dist/okf/types/IoTSiteWiseApplication.md +34 -0
- package/dist/okf/types/IoTSiteWiseDataset.md +5 -2
- package/dist/okf/types/IoTSiteWiseTask.md +24 -0
- package/dist/okf/types/IpamExternalResourceVerificationToken.md +35 -0
- package/dist/okf/types/IpamInternetRegistryAssociation.md +32 -0
- package/dist/okf/types/Job.md +35 -0
- package/dist/okf/types/JobTemplate.md +26 -0
- package/dist/okf/types/KinesisChannel.md +31 -0
- package/dist/okf/types/KinesisStream.md +1 -0
- package/dist/okf/types/LambdaResourcePolicy.md +14 -0
- package/dist/okf/types/Lexicon.md +34 -0
- package/dist/okf/types/LicenseAssetGroup.md +23 -0
- package/dist/okf/types/LicenseConfiguration.md +33 -0
- package/dist/okf/types/LicenseServerEndpoint.md +32 -0
- package/dist/okf/types/LightsailKeyPair.md +29 -0
- package/dist/okf/types/LimitsProfile.md +27 -0
- package/dist/okf/types/LocationAzureBlob.md +2 -1
- package/dist/okf/types/LocationObjectStorage.md +1 -0
- package/dist/okf/types/LogAlarm.md +2 -0
- package/dist/okf/types/MGNConnector.md +23 -0
- package/dist/okf/types/MGNLaunchConfigurationTemplate.md +37 -0
- package/dist/okf/types/MLTransform.md +15 -15
- package/dist/okf/types/MWAAServerlessWorkflow.md +3 -0
- package/dist/okf/types/ManagedNotificationAccountContactAssociation.md +1 -0
- package/dist/okf/types/ManagedNotificationAdditionalChannelAssociation.md +1 -0
- package/dist/okf/types/MediaConvertJobTemplate.md +11 -13
- package/dist/okf/types/MediaConvertQueue.md +8 -10
- package/dist/okf/types/MediaInsightsPipelineConfiguration.md +28 -0
- package/dist/okf/types/MediaLiveInput.md +41 -18
- package/dist/okf/types/MediaPackageV2Channel.md +3 -0
- package/dist/okf/types/MediaPackageV2OriginEndpoint.md +1 -0
- package/dist/okf/types/MediaPipelineKinesisVideoStreamPool.md +28 -0
- package/dist/okf/types/MediaTailorFunction.md +27 -0
- package/dist/okf/types/MedicalVocabulary.md +21 -0
- package/dist/okf/types/Member.md +26 -0
- package/dist/okf/types/Memory.md +6 -0
- package/dist/okf/types/MemoryDBSnapshot.md +29 -0
- package/dist/okf/types/Metric.md +59 -0
- package/dist/okf/types/MetricAttribution.md +23 -0
- package/dist/okf/types/MicrosoftAD.md +11 -11
- package/dist/okf/types/MigrationProject.md +8 -1
- package/dist/okf/types/Monitor.md +26 -0
- package/dist/okf/types/NFSFileShare.md +44 -0
- package/dist/okf/types/NeptuneGraphGraph.md +1 -0
- package/dist/okf/types/NetworkConnector.md +4 -0
- package/dist/okf/types/NetworkFlowMonitorScope.md +23 -0
- package/dist/okf/types/NetworkMigrationDefinition.md +31 -0
- package/dist/okf/types/NetworkSecurityManagerDeployment.md +31 -0
- package/dist/okf/types/NetworkSecurityManagerPolicy.md +32 -0
- package/dist/okf/types/NetworkSecurityManagerScope.md +29 -0
- package/dist/okf/types/NotebookInstance.md +18 -18
- package/dist/okf/types/NotebookInstanceLifecycleConfig.md +6 -5
- package/dist/okf/types/NotifyConfiguration.md +35 -0
- package/dist/okf/types/OTelEnrichment.md +2 -0
- package/dist/okf/types/OnlineEvaluationConfig.md +0 -1
- package/dist/okf/types/OrganizationConfigRule.md +7 -7
- package/dist/okf/types/Outpost.md +32 -0
- package/dist/okf/types/ParallelData.md +40 -0
- package/dist/okf/types/Partition.md +5 -5
- package/dist/okf/types/PaymentConnector.md +4 -1
- package/dist/okf/types/Pentest.md +1 -0
- package/dist/okf/types/PerfReports.md +28 -0
- package/dist/okf/types/PermissionAssociation.md +30 -0
- package/dist/okf/types/PersonalizeDataset.md +1 -0
- package/dist/okf/types/PersonalizeDatasetGroup.md +1 -0
- package/dist/okf/types/PersonalizeSchema.md +1 -0
- package/dist/okf/types/Pipeline.md +25 -0
- package/dist/okf/types/PlaybackConfiguration.md +1 -0
- package/dist/okf/types/PolicyAssociation.md +1 -1
- package/dist/okf/types/PolicyGrant.md +2 -2
- package/dist/okf/types/PolicyPrincipalAttachment.md +2 -7
- package/dist/okf/types/PortalProduct.md +24 -0
- package/dist/okf/types/PrefetchSchedule.md +25 -0
- package/dist/okf/types/PricingPlanManagerSubscription.md +31 -0
- package/dist/okf/types/PrincipalAssociation.md +27 -0
- package/dist/okf/types/PrivateDnsNamespace.md +8 -8
- package/dist/okf/types/PrivateVirtualInterface.md +1 -0
- package/dist/okf/types/ProcurementPortalPreference.md +45 -0
- package/dist/okf/types/Program.md +33 -0
- package/dist/okf/types/ProjectMembership.md +1 -1
- package/dist/okf/types/ProtocolsList.md +26 -0
- package/dist/okf/types/PublicDnsNamespace.md +7 -7
- package/dist/okf/types/PublicVirtualInterface.md +1 -0
- package/dist/okf/types/QEV2IdcApplication.md +25 -0
- package/dist/okf/types/QueryLoggingConfig.md +21 -0
- package/dist/okf/types/QuerySuggestionsBlockList.md +25 -0
- package/dist/okf/types/QuickSightAssignment.md +24 -0
- package/dist/okf/types/RAMResourceAssociation.md +27 -0
- package/dist/okf/types/RDSClusterSnapshot.md +54 -0
- package/dist/okf/types/RDSDBParameterGroup.md +5 -0
- package/dist/okf/types/RDSDeployment.md +41 -0
- package/dist/okf/types/RcsAgent.md +38 -0
- package/dist/okf/types/ReceiptRule.md +5 -5
- package/dist/okf/types/ReceiptRuleSet.md +1 -6
- package/dist/okf/types/RecommenderFilter.md +27 -0
- package/dist/okf/types/RecommenderSchema.md +25 -0
- package/dist/okf/types/RecordSet.md +16 -22
- package/dist/okf/types/RecoveryPlan.md +28 -0
- package/dist/okf/types/RedshiftIdcApplication.md +31 -0
- package/dist/okf/types/RedshiftSnapshot.md +59 -0
- package/dist/okf/types/RegistrationAttachment.md +28 -0
- package/dist/okf/types/Registry.md +41 -0
- package/dist/okf/types/RegistryRecord.md +38 -0
- package/dist/okf/types/RelatedItem.md +24 -0
- package/dist/okf/types/ReplicationConfigurationTemplate.md +35 -0
- package/dist/okf/types/ReplicationSubnetGroup.md +4 -9
- package/dist/okf/types/ReplicationTask.md +14 -14
- package/dist/okf/types/ReportGenerator.md +29 -0
- package/dist/okf/types/ReportGroup.md +1 -3
- package/dist/okf/types/ResilienceHubV2Policy.md +1 -0
- package/dist/okf/types/ResilienceHubV2Service.md +1 -0
- package/dist/okf/types/ResourceConfiguration.md +1 -1
- package/dist/okf/types/ResourceMetricsConfiguration.md +21 -0
- package/dist/okf/types/RestApi.md +1 -0
- package/dist/okf/types/RetrievalInstruction.md +32 -0
- package/dist/okf/types/RouterOutput.md +1 -0
- package/dist/okf/types/Rule.md +31 -0
- package/dist/okf/types/RunCache.md +29 -0
- package/dist/okf/types/Runtime.md +2 -1
- package/dist/okf/types/SCNInstance.md +33 -0
- package/dist/okf/types/SDBDomain.md +2 -2
- package/dist/okf/types/SMBFileShare.md +49 -0
- package/dist/okf/types/SageMakerArtifact.md +27 -0
- package/dist/okf/types/SageMakerEndpoint.md +0 -1
- package/dist/okf/types/ScalableTarget.md +1 -0
- package/dist/okf/types/ScalingPlan.md +6 -6
- package/dist/okf/types/ScheduledReport.md +31 -0
- package/dist/okf/types/SecondaryNetwork.md +26 -0
- package/dist/okf/types/SecurityAgentArtifact.md +25 -0
- package/dist/okf/types/SecurityAgentIntegration.md +31 -0
- package/dist/okf/types/Server.md +1 -0
- package/dist/okf/types/ServerlessCache.md +2 -0
- package/dist/okf/types/ServerlessCacheSnapshot.md +31 -0
- package/dist/okf/types/ServerlessRepoApplication.md +32 -0
- package/dist/okf/types/ServiceDiscoveryInstance.md +3 -7
- package/dist/okf/types/SipMediaApplication.md +27 -0
- package/dist/okf/types/SiteToSiteVpnAttachment.md +2 -0
- package/dist/okf/types/SnapshotCopyGrant.md +20 -0
- package/dist/okf/types/Solution.md +1 -0
- package/dist/okf/types/SourceAssociation.md +25 -0
- package/dist/okf/types/SourceCredential.md +6 -6
- package/dist/okf/types/SourceNetwork.md +23 -0
- package/dist/okf/types/StackFleetAssociation.md +2 -7
- package/dist/okf/types/StorageTierPolicy.md +20 -0
- package/dist/okf/types/StorageVirtualMachine.md +9 -9
- package/dist/okf/types/Stream.md +28 -0
- package/dist/okf/types/SubnetCidrReservation.md +26 -0
- package/dist/okf/types/SubscriptionProvider.md +26 -0
- package/dist/okf/types/SupportPermit.md +29 -0
- package/dist/okf/types/Table.md +2 -1
- package/dist/okf/types/TableOptimizer.md +5 -10
- package/dist/okf/types/Tape.md +31 -0
- package/dist/okf/types/Template.md +36 -0
- package/dist/okf/types/Tenant.md +2 -0
- package/dist/okf/types/Terminology.md +39 -0
- package/dist/okf/types/Thesaurus.md +25 -0
- package/dist/okf/types/ThingPrincipalAttachment.md +3 -8
- package/dist/okf/types/Topic.md +1 -0
- package/dist/okf/types/TopicRuleDestination.md +1 -0
- package/dist/okf/types/TrafficPolicy.md +26 -0
- package/dist/okf/types/TrafficPolicyInstance.md +28 -0
- package/dist/okf/types/TranscribeVocabulary.md +27 -0
- package/dist/okf/types/TransitGatewayRouteTableAttachment.md +2 -0
- package/dist/okf/types/TransitVirtualInterface.md +1 -0
- package/dist/okf/types/Tunnel.md +25 -0
- package/dist/okf/types/UsageLimit.md +26 -0
- package/dist/okf/types/UseCase.md +23 -0
- package/dist/okf/types/User.md +47 -0
- package/dist/okf/types/VPCEndpoint.md +1 -1
- package/dist/okf/types/VPCEndpointService.md +6 -0
- package/dist/okf/types/VPCPeeringConnection.md +2 -2
- package/dist/okf/types/VPNConcentrator.md +1 -1
- package/dist/okf/types/VPNConnection.md +1 -1
- package/dist/okf/types/VerifiedDestinationNumber.md +25 -0
- package/dist/okf/types/View.md +27 -0
- package/dist/okf/types/VoiceConnector.md +30 -0
- package/dist/okf/types/Volume.md +44 -0
- package/dist/okf/types/Vpc.md +1 -1
- package/dist/okf/types/VpcAttachment.md +2 -0
- package/dist/okf/types/WebFunction.md +27 -0
- package/dist/okf/types/WebFunctionEndpoint.md +44 -0
- package/dist/okf/types/WebFunctionRevision.md +32 -0
- package/dist/okf/types/WickrNetwork.md +27 -0
- package/dist/okf/types/Worker.md +29 -0
- package/dist/okf/types/Workforce.md +26 -0
- package/dist/okf/types/Workload.md +35 -0
- package/dist/okf/types/WorkloadEstimate.md +35 -0
- package/dist/okf/types/Workspace.md +28 -0
- package/dist/okf/types/Workteam.md +8 -8
- package/dist/op/activities/aws-apply.js +214 -0
- package/dist/op/activities/floci.js +97 -0
- package/dist/op/activities/index.js +32 -0
- package/dist/op/builders.js +50 -0
- package/dist/ownership.js +52 -0
- package/dist/package-cli.js +25 -0
- package/dist/parameter.js +19 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +1006 -0
- package/dist/properties.js +166 -0
- package/dist/pseudo.js +25 -0
- package/dist/receipt-store.d.ts.map +1 -1
- package/dist/receipt-store.js +233 -0
- package/dist/reference-catalog.js +106 -0
- package/dist/rules/ext001.ts +3 -3
- package/dist/rules/waw016.ts +2 -2
- package/dist/serializer.js +529 -0
- package/dist/spec/fetch.js +193 -0
- package/dist/spec/parse.js +259 -0
- package/dist/spec/pin.js +185 -0
- package/dist/spec/pinned-types.json +203 -0
- package/dist/stack-errors.js +14 -0
- package/dist/taggable.d.ts.map +1 -1
- package/dist/taggable.js +33 -0
- package/dist/teardown.js +199 -0
- package/dist/template-sections.js +53 -0
- package/dist/template-transform.js +50 -0
- package/dist/types/index.d.ts +18847 -1124
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +33 -0
- package/package.json +9 -9
- package/src/agentcore/trace-fetch.ts +2 -1
- package/src/api/credential-chain.ts +119 -9
- package/src/api/credential-isolation.test.ts +72 -0
- package/src/api/read-client-chain.test.ts +159 -0
- package/src/api/read-client.ts +42 -6
- package/src/api/sigv4.ts +4 -4
- package/src/caller-identity.ts +3 -5
- package/src/codegen/any-budget.test.ts +12 -2
- package/src/codegen/generate.ts +9 -0
- package/src/codegen/package.ts +2 -2
- package/src/codegen/patches.test.ts +95 -0
- package/src/codegen/patches.ts +53 -9
- package/src/codegen/property-overlay.json +1 -16
- package/src/components/apply.ts +218 -6
- package/src/components/cfn-plan-floci.e2e.test.ts +76 -0
- package/src/components/cfn-plan.test.ts +225 -16
- package/src/components/cloud-executor.ts +73 -25
- package/src/deep-observe.ts +2 -1
- package/src/disruption.ts +2 -1
- package/src/generated/index.d.ts +18847 -1124
- package/src/generated/index.ts +1376 -161
- package/src/generated/lexicon-aws.json +17359 -784
- package/src/identity-observe.ts +2 -1
- package/src/lint/post-synth/ext001.ts +3 -3
- package/src/lint/post-synth/waw016.ts +2 -2
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/plugin.ts +2 -1
- package/src/receipt-store.ts +2 -1
- package/src/spec/pin.ts +3 -3
- package/src/spec/pinned-types.json +203 -0
- package/src/taggable.ts +2 -2
package/src/components/apply.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import { createHash } from "node:crypto";
|
|
23
23
|
import { readFileSync } from "node:fs";
|
|
24
|
-
import type { Capability, CapabilityPlan, DeployContext } from "@intentius/chant/components/capability";
|
|
24
|
+
import type { Capability, CapabilityPlan, DeployContext, PlannedOutputs } from "@intentius/chant/components/capability";
|
|
25
25
|
import type { ChangeSetEntry, ChangeSetHole, ChangeSetPart } from "@intentius/chant/change-set";
|
|
26
26
|
import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
|
|
27
27
|
import { defaultCloudExecutor, type CfnChange, type CfnChangeSet, type CloudExecutor } from "./cloud-executor";
|
|
@@ -67,13 +67,17 @@ export function cfnChangeReplaces(change: CfnChange): boolean {
|
|
|
67
67
|
*/
|
|
68
68
|
export function cfnChangeEntry(member: string, change: CfnChange): ChangeSetEntry {
|
|
69
69
|
const attributes = [...new Set(change.details ?? [])].sort().map((path) => ({ path }));
|
|
70
|
+
// A change inside a nested stack (#3997) sits under the nested stacks'
|
|
71
|
+
// logical ids, the way a terraform address sits under its module.
|
|
72
|
+
const module = change.nestedStack?.length ? change.nestedStack.join("/") : undefined;
|
|
70
73
|
const base = {
|
|
71
74
|
member,
|
|
72
75
|
lexicon: "aws",
|
|
73
76
|
planner: "chant" as const,
|
|
74
|
-
address: change.logicalResourceId,
|
|
77
|
+
address: module ? `${module}/${change.logicalResourceId}` : change.logicalResourceId,
|
|
75
78
|
type: change.resourceType,
|
|
76
79
|
name: change.logicalResourceId,
|
|
80
|
+
...(module ? { module } : {}),
|
|
77
81
|
attributes,
|
|
78
82
|
};
|
|
79
83
|
switch (change.action) {
|
|
@@ -92,6 +96,66 @@ export function cfnChangeEntry(member: string, change: CfnChange): ChangeSetEntr
|
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
/**
|
|
100
|
+
* The changes `onReplace: "block"` refuses: those CloudFormation says replace
|
|
101
|
+
* their resource (`Replacement: True`). `run` and `plan` refuse on the same
|
|
102
|
+
* set, so a plan that passes is one `run` would not block (#3997).
|
|
103
|
+
*/
|
|
104
|
+
function blockedReplacements(changes: CfnChange[]): CfnChange[] {
|
|
105
|
+
return changes.filter((c) => c.replacement);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* What a `cfn-deploy` plan hands `run` through `DeployContext.plans[stack]`
|
|
110
|
+
* (#3997): the stack and a digest over the changes the plan's change set
|
|
111
|
+
* proposed. `run` creates its change set, and executes it only when its
|
|
112
|
+
* changes have the same digest.
|
|
113
|
+
*/
|
|
114
|
+
export interface CfnPlanArtifact {
|
|
115
|
+
kind: "cfn-deploy-plan";
|
|
116
|
+
stack: string;
|
|
117
|
+
/** {@link cfnChangesDigest} of the planned change set's changes. */
|
|
118
|
+
changesDigest: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isCfnPlanArtifact(value: unknown): value is CfnPlanArtifact {
|
|
122
|
+
const v = value as Partial<CfnPlanArtifact> | undefined;
|
|
123
|
+
return v?.kind === "cfn-deploy-plan" && typeof v.stack === "string" && typeof v.changesDigest === "string";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* A digest over a change set's changes as entries, independent of the change
|
|
128
|
+
* set's name and of the order CloudFormation lists them in. A change set with
|
|
129
|
+
* nothing to apply digests as no entries.
|
|
130
|
+
*/
|
|
131
|
+
export function cfnChangesDigest(stack: string, changeSet: CfnChangeSet): string {
|
|
132
|
+
const entries = isNoopChangeSet(changeSet) ? [] : changeSet.changes.map((c) => cfnChangeEntry(stack, c));
|
|
133
|
+
entries.sort((a, b) => a.address.localeCompare(b.address) || a.action.localeCompare(b.action));
|
|
134
|
+
return computePlanDigest("cfn-deploy-changes", { stack, entries });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Thrown by `run` when the change set it created proposes other changes than
|
|
139
|
+
* the change set its plan read (#3997): the stack, the template or the
|
|
140
|
+
* parameters moved between the plan and the apply. The change set is deleted
|
|
141
|
+
* unexecuted. `planMoved` is core's mark for a moved plan
|
|
142
|
+
* (`isPlanMovedError`), so the command that applied it exits 3.
|
|
143
|
+
*/
|
|
144
|
+
export class CfnPlanMovedError extends Error {
|
|
145
|
+
readonly planMoved = true;
|
|
146
|
+
constructor(
|
|
147
|
+
public readonly stack: string,
|
|
148
|
+
public readonly planned: string,
|
|
149
|
+
public readonly now: string,
|
|
150
|
+
) {
|
|
151
|
+
super(
|
|
152
|
+
`cfn-deploy "${stack}": refusing to apply. The change set proposes other changes than the plan that was approved ` +
|
|
153
|
+
`(planned ${planned}, now ${now}); the stack, its template or its parameters changed since. Plan it again.`,
|
|
154
|
+
);
|
|
155
|
+
this.name = "CfnPlanMovedError";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
95
159
|
/** The template's bytes as a digest, or `null` when it cannot be read here (the change set then reports why). */
|
|
96
160
|
function templateDigest(path: string): string | null {
|
|
97
161
|
try {
|
|
@@ -133,7 +197,23 @@ function cfnPlanDigest(
|
|
|
133
197
|
* deleted too. A change set with nothing to apply (`isNoopChangeSet`) gives
|
|
134
198
|
* a member with no entries. A step whose parameters read outputs that are
|
|
135
199
|
* not known yet, from a stack the same run applies first, creates no change
|
|
136
|
-
* set: its member gets a hole saying whose outputs it waits on
|
|
200
|
+
* set: its member gets a hole saying whose outputs it waits on, and no
|
|
201
|
+
* artifact, so `run` plans it afresh.
|
|
202
|
+
*
|
|
203
|
+
* With `onReplace: "block"` (the default), a change set that replaces a
|
|
204
|
+
* resource fails the plan with {@link CfnReplacementBlockedError}, so the
|
|
205
|
+
* refusal shows in the pull request's plan rather than at apply (#3997).
|
|
206
|
+
* Otherwise the result carries a {@link CfnPlanArtifact} that `run` checks
|
|
207
|
+
* its own change set against.
|
|
208
|
+
*
|
|
209
|
+
* The plan answers the outputs the stack will have (#3999), from the
|
|
210
|
+
* outputs it has now (`DescribeStacks`) and the change set: an output keeps
|
|
211
|
+
* its value when every resource its template `Value` reads keeps its
|
|
212
|
+
* physical resource, which an in-place `Modify` does and an `Add`, a
|
|
213
|
+
* `Remove` or a replacement does not ({@link cfnPlannedOutputs}). An output
|
|
214
|
+
* that reads a parameter, an import or a condition is known only after
|
|
215
|
+
* apply. A wrong guess costs a round, never a wrong apply: a dependent whose
|
|
216
|
+
* input applies as another value is held back and planned again.
|
|
137
217
|
*/
|
|
138
218
|
async function planCfnDeploy(executor: CloudExecutor, ctx: DeployContext, input: CfnDeployInput): Promise<CapabilityPlan> {
|
|
139
219
|
const stack = input.stack ?? ctx.component;
|
|
@@ -176,15 +256,23 @@ async function planCfnDeploy(executor: CloudExecutor, ctx: DeployContext, input:
|
|
|
176
256
|
});
|
|
177
257
|
try {
|
|
178
258
|
if (isNoopChangeSet(changeSet)) {
|
|
179
|
-
|
|
259
|
+
const artifact: CfnPlanArtifact = { kind: "cfn-deploy-plan", stack, changesDigest: cfnChangesDigest(stack, changeSet) };
|
|
260
|
+
// Nothing to apply, so the outputs the stack has are the ones it will have (#3999).
|
|
261
|
+
const standing = await standingOutputs(executor, stack);
|
|
262
|
+
const outputs = standing ? { known: { ...standing }, unknown: [] } : undefined;
|
|
263
|
+
return { member: stack, planDigest, artifact, changeSet: { member: member([]), entries: [] }, ...(outputs ? { outputs } : {}) };
|
|
180
264
|
}
|
|
181
265
|
if (changeSet.status === "FAILED") {
|
|
182
266
|
throw new Error(
|
|
183
267
|
`cfn-deploy "${stack}": CloudFormation could not create a change set to plan it${changeSet.statusReason ? `: ${changeSet.statusReason}` : ""}`,
|
|
184
268
|
);
|
|
185
269
|
}
|
|
270
|
+
const blocked = blockedReplacements(changeSet.changes);
|
|
271
|
+
if (blocked.length > 0 && (input.onReplace ?? "block") === "block") throw new CfnReplacementBlockedError(stack, blocked);
|
|
186
272
|
const entries = changeSet.changes.map((c) => cfnChangeEntry(stack, c));
|
|
187
|
-
|
|
273
|
+
const artifact: CfnPlanArtifact = { kind: "cfn-deploy-plan", stack, changesDigest: cfnChangesDigest(stack, changeSet) };
|
|
274
|
+
const outputs = changeSet.stackCreated ? undefined : await plannedStackOutputs(executor, stack, input.template, changeSet.changes);
|
|
275
|
+
return { member: stack, planDigest, artifact, changeSet: { member: member([]), entries }, ...(outputs ? { outputs } : {}) };
|
|
188
276
|
} finally {
|
|
189
277
|
await cleanUpPlanChangeSet(executor, changeSet);
|
|
190
278
|
}
|
|
@@ -211,6 +299,118 @@ async function cleanUpPlanChangeSet(executor: CloudExecutor, changeSet: CfnChang
|
|
|
211
299
|
}
|
|
212
300
|
}
|
|
213
301
|
|
|
302
|
+
/** The stack's outputs as they stand, or `undefined` when they cannot be read. */
|
|
303
|
+
async function standingOutputs(executor: CloudExecutor, stack: string): Promise<Record<string, string> | undefined> {
|
|
304
|
+
try {
|
|
305
|
+
return { ...(await executor.cloudformation.describeStack(stack)).outputs };
|
|
306
|
+
} catch {
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* What the plan says of the stack's outputs once `changes` apply: none to
|
|
313
|
+
* say for a template with no `Outputs`, which needs no `DescribeStacks`.
|
|
314
|
+
*/
|
|
315
|
+
async function plannedStackOutputs(executor: CloudExecutor, stack: string, templatePath: string, changes: readonly CfnChange[]): Promise<PlannedOutputs | undefined> {
|
|
316
|
+
const template = readTemplate(templatePath) as { Outputs?: Record<string, unknown> } | undefined;
|
|
317
|
+
if (!template || typeof template !== "object") return undefined;
|
|
318
|
+
if (Object.keys(template.Outputs ?? {}).length === 0) return { known: {}, unknown: [] };
|
|
319
|
+
return cfnPlannedOutputs(template, changes, await standingOutputs(executor, stack));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** The template at `path` as JSON, or `undefined` when it cannot be read or parsed here. */
|
|
323
|
+
function readTemplate(path: string): unknown {
|
|
324
|
+
try {
|
|
325
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
326
|
+
} catch {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* What a template's `Value` reads (#3999): the logical ids it names through
|
|
333
|
+
* `Ref`, `Fn::GetAtt` and `Fn::Sub`, and whether it reads anything this plan
|
|
334
|
+
* cannot follow (`Fn::ImportValue`, a condition's `Fn::If`, or an intrinsic
|
|
335
|
+
* it does not know).
|
|
336
|
+
*/
|
|
337
|
+
function valueReads(value: unknown): { ids: Set<string>; opaque: boolean } {
|
|
338
|
+
const ids = new Set<string>();
|
|
339
|
+
let opaque = false;
|
|
340
|
+
const sub = (text: string): void => {
|
|
341
|
+
for (const m of text.matchAll(/\$\{([^}!][^}]*)\}/g)) ids.add(m[1]!.split(".")[0]!);
|
|
342
|
+
};
|
|
343
|
+
const walk = (v: unknown): void => {
|
|
344
|
+
if (Array.isArray(v)) return v.forEach(walk);
|
|
345
|
+
if (v === null || typeof v !== "object") return;
|
|
346
|
+
const entries = Object.entries(v as Record<string, unknown>);
|
|
347
|
+
if (entries.length === 1) {
|
|
348
|
+
const [key, arg] = entries[0]!;
|
|
349
|
+
if (key === "Ref") {
|
|
350
|
+
if (typeof arg === "string") ids.add(arg);
|
|
351
|
+
else opaque = true;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (key === "Fn::GetAtt") {
|
|
355
|
+
if (Array.isArray(arg) && typeof arg[0] === "string") ids.add(arg[0]);
|
|
356
|
+
else if (typeof arg === "string") ids.add(arg.split(".")[0]!);
|
|
357
|
+
else opaque = true;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (key === "Fn::Sub") {
|
|
361
|
+
if (typeof arg === "string") sub(arg);
|
|
362
|
+
else if (Array.isArray(arg) && typeof arg[0] === "string") {
|
|
363
|
+
const vars = (arg[1] ?? {}) as Record<string, unknown>;
|
|
364
|
+
for (const m of arg[0].matchAll(/\$\{([^}!][^}]*)\}/g)) if (!(m[1]! in vars)) ids.add(m[1]!.split(".")[0]!);
|
|
365
|
+
walk(vars);
|
|
366
|
+
} else opaque = true;
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (key === "Fn::Join" || key === "Fn::Select" || key === "Fn::Split" || key === "Fn::Base64") return walk(arg);
|
|
370
|
+
if (key.startsWith("Fn::") || key === "Condition") {
|
|
371
|
+
opaque = true;
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
entries.forEach(([, x]) => walk(x));
|
|
376
|
+
};
|
|
377
|
+
walk(value);
|
|
378
|
+
return { ids, opaque };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* The outputs a stack will have once `changes` apply (#3999), from its
|
|
383
|
+
* template and the outputs it has now. An output is known, at the value it
|
|
384
|
+
* has now, when its `Value` reads only pseudo parameters and resources whose
|
|
385
|
+
* physical resource the change set keeps (no `Add`, `Remove`, `Import` or
|
|
386
|
+
* `Dynamic`, no replacement, no change in a nested stack under it). One
|
|
387
|
+
* that reads a parameter, an import or a condition, one the stack does not
|
|
388
|
+
* have yet, and every output of a template this cannot read, is unknown.
|
|
389
|
+
* `undefined` when there is no template or no standing stack to read from.
|
|
390
|
+
*/
|
|
391
|
+
export function cfnPlannedOutputs(template: unknown, changes: readonly CfnChange[], standing: Record<string, string> | undefined): PlannedOutputs | undefined {
|
|
392
|
+
if (!standing || template === null || typeof template !== "object") return undefined;
|
|
393
|
+
const t = template as { Outputs?: Record<string, { Value?: unknown; Condition?: unknown }>; Parameters?: Record<string, unknown> };
|
|
394
|
+
const parameters = new Set(Object.keys(t.Parameters ?? {}));
|
|
395
|
+
const moved = new Set<string>();
|
|
396
|
+
for (const c of changes) {
|
|
397
|
+
const id = c.nestedStack?.[0] ?? c.logicalResourceId;
|
|
398
|
+
const keeps = !c.nestedStack && c.action === "Modify" && !c.replacement && c.replacementMode !== "True" && c.replacementMode !== "Conditional";
|
|
399
|
+
if (!keeps) moved.add(id);
|
|
400
|
+
}
|
|
401
|
+
const known: Record<string, unknown> = {};
|
|
402
|
+
const unknown: string[] = [];
|
|
403
|
+
for (const [name, output] of Object.entries(t.Outputs ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
|
|
404
|
+
const { ids, opaque } = valueReads(output?.Value);
|
|
405
|
+
const stays =
|
|
406
|
+
!opaque && output?.Condition === undefined && name in standing &&
|
|
407
|
+
[...ids].every((id) => id.startsWith("AWS::") || (!parameters.has(id) && !moved.has(id)));
|
|
408
|
+
if (stays) known[name] = standing[name];
|
|
409
|
+
else unknown.push(name);
|
|
410
|
+
}
|
|
411
|
+
return { known, unknown };
|
|
412
|
+
}
|
|
413
|
+
|
|
214
414
|
/** Whether a CloudFormation error says the stack does not exist. */
|
|
215
415
|
const STACK_MISSING = /does not exist/i;
|
|
216
416
|
|
|
@@ -324,6 +524,18 @@ export function createCfnDeployCapability(
|
|
|
324
524
|
parameters: flattenCfnInputs(input),
|
|
325
525
|
});
|
|
326
526
|
|
|
527
|
+
// Handed the plan the approver approved (#3997), apply only a change
|
|
528
|
+
// set that proposes the same changes. Anything else means the stack,
|
|
529
|
+
// the template or the parameters moved since the plan.
|
|
530
|
+
const planned = ctx.plans?.[stack];
|
|
531
|
+
if (isCfnPlanArtifact(planned)) {
|
|
532
|
+
const now = cfnChangesDigest(stack, changeSet);
|
|
533
|
+
if (now !== planned.changesDigest) {
|
|
534
|
+
await cleanUpPlanChangeSet(executor, changeSet);
|
|
535
|
+
throw new CfnPlanMovedError(stack, planned.changesDigest, now);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
327
539
|
// An unchanged stack yields a change set with nothing to apply.
|
|
328
540
|
// `execute-change-set` rejects it ("The submitted information didn't
|
|
329
541
|
// contain changes"), which would fail this component and stop the whole
|
|
@@ -340,7 +552,7 @@ export function createCfnDeployCapability(
|
|
|
340
552
|
return { stackStatus, outputs };
|
|
341
553
|
}
|
|
342
554
|
|
|
343
|
-
const replacements = changeSet.changes
|
|
555
|
+
const replacements = blockedReplacements(changeSet.changes);
|
|
344
556
|
let snapshotId: string | undefined;
|
|
345
557
|
|
|
346
558
|
if (replacements.length > 0 && onReplace === "block") {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cfn-deploy`'s plan and apply against Floci (#3997), through the real
|
|
3
|
+
* executor (`aws` CLI). Set CHANT_FLOCI_ENDPOINT to a running Floci, for
|
|
4
|
+
* example `docker run -d -p 4611:4566 floci/floci` and
|
|
5
|
+
* `CHANT_FLOCI_ENDPOINT=http://localhost:4611`. Skipped without it.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { CfnPlanMovedError, createCfnDeployCapability } from "./apply";
|
|
14
|
+
import { defaultCloudExecutor } from "./cloud-executor";
|
|
15
|
+
|
|
16
|
+
const endpoint = process.env.CHANT_FLOCI_ENDPOINT;
|
|
17
|
+
|
|
18
|
+
describe.skipIf(!endpoint)("cfn-deploy plan against Floci (#3997)", () => {
|
|
19
|
+
const stack = `chant-plan-${process.pid}-${Date.now()}`;
|
|
20
|
+
const ctx = { env: "local", component: stack };
|
|
21
|
+
const saved = { ...process.env };
|
|
22
|
+
let dir: string;
|
|
23
|
+
let template: string;
|
|
24
|
+
const aws = (...args: string[]): string => execFileSync("aws", ["--endpoint-url", endpoint!, ...args], { encoding: "utf8" });
|
|
25
|
+
// The change sets still open on the stack. Floci also lists executed ones, as EXECUTE_COMPLETE.
|
|
26
|
+
const changeSets = (): string[] =>
|
|
27
|
+
((JSON.parse(aws("cloudformation", "list-change-sets", "--stack-name", stack)) as { Summaries?: Array<{ ChangeSetName: string; ExecutionStatus?: string }> }).Summaries ?? [])
|
|
28
|
+
.filter((c) => c.ExecutionStatus !== "EXECUTE_COMPLETE")
|
|
29
|
+
.map((c) => c.ChangeSetName);
|
|
30
|
+
const write = (resources: Record<string, unknown>): void => writeFileSync(template, JSON.stringify({ Resources: resources }));
|
|
31
|
+
const topic = (name: string) => ({ Type: "AWS::SNS::Topic", Properties: { DisplayName: name } });
|
|
32
|
+
|
|
33
|
+
beforeAll(() => {
|
|
34
|
+
Object.assign(process.env, { AWS_ENDPOINT_URL: endpoint, AWS_ACCESS_KEY_ID: "test", AWS_SECRET_ACCESS_KEY: "test", AWS_REGION: "us-east-1" });
|
|
35
|
+
dir = mkdtempSync(join(tmpdir(), "cfn-plan-floci-"));
|
|
36
|
+
template = join(dir, "stack.template.json");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterAll(() => {
|
|
40
|
+
try {
|
|
41
|
+
aws("cloudformation", "delete-stack", "--stack-name", stack);
|
|
42
|
+
} catch { /* never created */ }
|
|
43
|
+
process.env = saved;
|
|
44
|
+
rmSync(dir, { recursive: true, force: true });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("plans a create, an update and no change, applies what it planned, and refuses a plan the template moved past", async () => {
|
|
48
|
+
const capability = createCfnDeployCapability(defaultCloudExecutor());
|
|
49
|
+
|
|
50
|
+
write({ Topic: topic("a") });
|
|
51
|
+
const created = await capability.plan!(ctx, { stack, template });
|
|
52
|
+
expect(created.changeSet!.entries.map((e) => [e.address, e.action])).toEqual([["Topic", "create"]]);
|
|
53
|
+
// The plan left no stack behind: the empty one its CREATE change set made is gone.
|
|
54
|
+
await expect(capability.outputs!(ctx, { stack, template })).resolves.toEqual({});
|
|
55
|
+
const first = await capability.run({ ...ctx, plans: { [stack]: created.artifact } }, { stack, template });
|
|
56
|
+
expect(first.stackStatus).toBe("CREATE_COMPLETE");
|
|
57
|
+
|
|
58
|
+
write({ Topic: topic("b"), Queue: { Type: "AWS::SQS::Queue" } });
|
|
59
|
+
const updated = await capability.plan!(ctx, { stack, template });
|
|
60
|
+
expect(updated.changeSet!.entries.map((e) => [e.address, e.action]).sort()).toEqual([["Queue", "create"], ["Topic", "update"]]);
|
|
61
|
+
expect(changeSets()).toEqual([]);
|
|
62
|
+
|
|
63
|
+
// The template moves after the plan: run refuses and leaves no change set.
|
|
64
|
+
write({ Topic: topic("b"), Queue: { Type: "AWS::SQS::Queue" }, Dlq: { Type: "AWS::SQS::Queue" } });
|
|
65
|
+
await expect(capability.run({ ...ctx, plans: { [stack]: updated.artifact } }, { stack, template })).rejects.toBeInstanceOf(CfnPlanMovedError);
|
|
66
|
+
expect(changeSets()).toEqual([]);
|
|
67
|
+
|
|
68
|
+
write({ Topic: topic("b"), Queue: { Type: "AWS::SQS::Queue" } });
|
|
69
|
+
const second = await capability.run({ ...ctx, plans: { [stack]: updated.artifact } }, { stack, template });
|
|
70
|
+
expect(second.stackStatus).toBe("UPDATE_COMPLETE");
|
|
71
|
+
|
|
72
|
+
const same = await capability.plan!(ctx, { stack, template });
|
|
73
|
+
expect(same.changeSet!.entries).toEqual([]);
|
|
74
|
+
expect(changeSets()).toEqual([]);
|
|
75
|
+
}, 600_000); // each `aws` call takes seconds, and a plan or a run makes several
|
|
76
|
+
});
|
|
@@ -11,11 +11,14 @@ import { join } from "node:path";
|
|
|
11
11
|
import { CapabilityRegistry } from "@intentius/chant/components/capability";
|
|
12
12
|
import type { DriverComponent } from "@intentius/chant/components/driver";
|
|
13
13
|
import { planFanOut } from "@intentius/chant/components/fan-out";
|
|
14
|
-
import { planPrSet, prReport } from "@intentius/chant/components/pr-run";
|
|
14
|
+
import { applyPrSet, planPrSet, prReport } from "@intentius/chant/components/pr-run";
|
|
15
15
|
import { composeChangeSet } from "@intentius/chant/change-set";
|
|
16
16
|
import { groupChangeSet, planSummaryHeadline } from "@intentius/chant/plan-summary";
|
|
17
17
|
import { memberCounts, prStatusDescription, renderPrNote } from "@intentius/chant/pr-loop";
|
|
18
|
-
import { createCfnDeployCapability } from "./apply";
|
|
18
|
+
import { CfnPlanMovedError, CfnReplacementBlockedError, cfnChangeEntry, createCfnDeployCapability, type CfnDeployInput } from "./apply";
|
|
19
|
+
import { isPlanMovedError } from "@intentius/chant/components/capability";
|
|
20
|
+
import { planMovedIn } from "@intentius/chant/components/driver";
|
|
21
|
+
import { readCfnChanges, templateHasNestedStacks, type DescribedCfnChangeSet } from "./cloud-executor";
|
|
19
22
|
import { createMockCloudExecutor, type FakeStackConfig } from "./__tests__/mock-cloud-executor";
|
|
20
23
|
|
|
21
24
|
let dir: string;
|
|
@@ -31,10 +34,10 @@ afterEach(() => rmSync(dir, { recursive: true, force: true }));
|
|
|
31
34
|
|
|
32
35
|
const ctx = { env: "local", component: "network" };
|
|
33
36
|
|
|
34
|
-
async function planOne(config: FakeStackConfig) {
|
|
37
|
+
async function planOne(config: FakeStackConfig, extra: Partial<CfnDeployInput> = {}) {
|
|
35
38
|
const mock = createMockCloudExecutor({ stacks: { network: config } });
|
|
36
39
|
const capability = createCfnDeployCapability(mock.executor);
|
|
37
|
-
const planned = await capability.plan!(ctx, { stack: "network", template });
|
|
40
|
+
const planned = await capability.plan!(ctx, { stack: "network", template, ...extra });
|
|
38
41
|
const doc = composeChangeSet([planned.changeSet!]);
|
|
39
42
|
return { mock, planned, doc, counts: memberCounts(doc, "network") };
|
|
40
43
|
}
|
|
@@ -77,17 +80,51 @@ describe("cfn-deploy plan (#4084)", () => {
|
|
|
77
80
|
{ action: "Modify", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false, replacementMode: "Conditional" },
|
|
78
81
|
{ action: "Remove", logicalResourceId: "Old", resourceType: "AWS::SQS::Queue", replacement: false },
|
|
79
82
|
],
|
|
80
|
-
});
|
|
83
|
+
}, { onReplace: "allow" });
|
|
81
84
|
expect(counts).toEqual({ create: 0, update: 0, replace: 2, delete: 1 });
|
|
82
85
|
expect(planSummaryHeadline(groupChangeSet(doc))).toContain("3 destroys or replacements");
|
|
83
86
|
});
|
|
84
87
|
|
|
85
|
-
it("a change set with nothing to apply is a member with no entries and no holes", async () => {
|
|
86
|
-
const { counts, doc, mock } = await planOne({ noChanges: true });
|
|
88
|
+
it("a change set with nothing to apply is a member with no entries and no holes, whose outputs are the stack's own (#3999)", async () => {
|
|
89
|
+
const { counts, doc, mock, planned } = await planOne({ noChanges: true, outputs: { VpcId: "vpc-1" } });
|
|
87
90
|
expect(counts).toEqual({ create: 0, update: 0, replace: 0, delete: 0 });
|
|
88
91
|
expect(doc.members[0]).toMatchObject({ status: "planned", holes: [] });
|
|
89
92
|
expect(groupChangeSet(doc, { unit: "member" }).groups[0].noChanges).toBe(true);
|
|
90
|
-
expect(
|
|
93
|
+
expect(planned.outputs).toEqual({ known: { VpcId: "vpc-1" }, unknown: [] });
|
|
94
|
+
expect(methods(mock)).toEqual(["createChangeSet", "describeStack", "deleteChangeSet"]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("a change set with changes keeps the outputs of resources it keeps, and not of the ones it adds or replaces (#3999)", async () => {
|
|
98
|
+
writeFileSync(
|
|
99
|
+
template,
|
|
100
|
+
JSON.stringify({
|
|
101
|
+
Parameters: { Env: { Type: "String" } },
|
|
102
|
+
Resources: { Topic: { Type: "AWS::SNS::Topic" }, Queue: { Type: "AWS::SQS::Queue" }, Table: { Type: "AWS::DynamoDB::Table" } },
|
|
103
|
+
Outputs: {
|
|
104
|
+
TopicArn: { Value: { Ref: "Topic" } },
|
|
105
|
+
QueueUrl: { Value: { "Fn::GetAtt": ["Queue", "QueueUrl"] } },
|
|
106
|
+
TableArn: { Value: { "Fn::Sub": "${Table.Arn}" } },
|
|
107
|
+
Region: { Value: { "Fn::Sub": "${AWS::Region}" } },
|
|
108
|
+
EnvName: { Value: { Ref: "Env" } },
|
|
109
|
+
Imported: { Value: { "Fn::ImportValue": "x" } },
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
const standing = { TopicArn: "arn:topic", QueueUrl: "https://q", TableArn: "arn:table", Region: "us-east-1", EnvName: "prod", Imported: "x" };
|
|
114
|
+
const { planned } = await planOne({
|
|
115
|
+
outputs: standing,
|
|
116
|
+
changes: [
|
|
117
|
+
{ action: "Modify", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false, replacementMode: "False" },
|
|
118
|
+
{ action: "Modify", logicalResourceId: "Queue", resourceType: "AWS::SQS::Queue", replacement: true, replacementMode: "True" },
|
|
119
|
+
{ action: "Modify", logicalResourceId: "Table", resourceType: "AWS::DynamoDB::Table", replacement: false, replacementMode: "Conditional" },
|
|
120
|
+
],
|
|
121
|
+
}, { onReplace: "allow" });
|
|
122
|
+
expect(planned.outputs).toEqual({ known: { Region: "us-east-1", TopicArn: "arn:topic" }, unknown: ["EnvName", "Imported", "QueueUrl", "TableArn"] });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("a stack the change set creates says nothing of its outputs (#3999)", async () => {
|
|
126
|
+
const { planned } = await planOne({ missing: true, changes: [{ action: "Add", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false }] });
|
|
127
|
+
expect(planned.outputs).toBeUndefined();
|
|
91
128
|
});
|
|
92
129
|
|
|
93
130
|
it("a stack that does not exist yet counts every resource as a create, and the empty stack the CREATE change set made is deleted", async () => {
|
|
@@ -175,21 +212,51 @@ describe("pr-plan over cfn-deploy members (#4084)", () => {
|
|
|
175
212
|
|
|
176
213
|
it("counts each member's changes from its change set, and the note and status line show them", async () => {
|
|
177
214
|
const { set, report } = await planBoth({
|
|
178
|
-
network: {
|
|
179
|
-
outputs: { TopicArn: "arn:topic" },
|
|
180
|
-
changes: [{ action: "Modify", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false, details: ["DisplayName"] }],
|
|
181
|
-
},
|
|
215
|
+
network: { outputs: { TopicArn: "arn:topic" }, noChanges: true },
|
|
182
216
|
"cluster-a": { changes: [{ action: "Add", logicalResourceId: "clusterAQueue", resourceType: "AWS::SQS::Queue", replacement: false }] },
|
|
183
217
|
});
|
|
184
218
|
expect(set.members.map((m) => [m.member, m.counts])).toEqual([
|
|
185
219
|
["cluster-a", { create: 1, update: 0, replace: 0, delete: 0 }],
|
|
186
|
-
["network", { create: 0, update:
|
|
220
|
+
["network", { create: 0, update: 0, replace: 0, delete: 0 }],
|
|
187
221
|
]);
|
|
222
|
+
// network changes nothing, so its plan knows its outputs: the ones it has (#3999).
|
|
223
|
+
expect(set.plannedOutputs).toEqual({ network: ["TopicArn"] });
|
|
188
224
|
const note = renderPrNote(report);
|
|
189
|
-
expect(note).toContain("| `network` | network | planned | 0 |
|
|
225
|
+
expect(note).toContain("| `network` | network | planned | 0 | 0 | 0 | 0 |");
|
|
190
226
|
expect(note).toContain("| `cluster-a` | cluster-a | planned | 1 | 0 | 0 | 0 |");
|
|
191
|
-
expect(
|
|
192
|
-
|
|
227
|
+
expect(prStatusDescription(report)).toMatch(/^2 members: 1 to change, 0 to destroy or replace;/);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("a stack whose upstream changes in the same pull request reads its outputs as known only after apply (#3999)", async () => {
|
|
231
|
+
const { set, report, mock } = await planBoth({
|
|
232
|
+
network: {
|
|
233
|
+
outputs: { TopicArn: "arn:topic" },
|
|
234
|
+
changes: [{ action: "Modify", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false, details: ["DisplayName"] }],
|
|
235
|
+
},
|
|
236
|
+
"cluster-a": { changes: [{ action: "Add", logicalResourceId: "clusterAQueue", resourceType: "AWS::SQS::Queue", replacement: false }] },
|
|
237
|
+
});
|
|
238
|
+
// network's template has no Outputs to say TopicArn stays, so it is not the TopicArn standing now.
|
|
239
|
+
expect(mock.calls.filter((c) => c.method === "describeStack")).toEqual([]);
|
|
240
|
+
expect(set.plannedOutputs).toEqual({ network: [] });
|
|
241
|
+
expect(set.members.find((m) => m.member === "cluster-a")?.unknownInputs).toEqual(["network.TopicArn"]);
|
|
242
|
+
const note = renderPrNote(report);
|
|
243
|
+
expect(note).toContain("| `cluster-a` | cluster-a | planned; inputs from network; re-checked on apply | ? | ? | ? | ? |");
|
|
244
|
+
expect(note).toContain("Known only after apply: `cluster-a` reads `network.TopicArn`.");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("a stack whose upstream changes a resource in place reads the output that resource keeps, at its value now (#3999)", async () => {
|
|
248
|
+
writeFileSync(template, JSON.stringify({ Resources: { Topic: { Type: "AWS::SNS::Topic" } }, Outputs: { TopicArn: { Value: { Ref: "Topic" } } } }));
|
|
249
|
+
const { set, mock } = await planBoth({
|
|
250
|
+
network: {
|
|
251
|
+
outputs: { TopicArn: "arn:topic" },
|
|
252
|
+
changes: [{ action: "Modify", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false, details: ["DisplayName"] }],
|
|
253
|
+
},
|
|
254
|
+
"cluster-a": { outputs: {}, changes: [{ action: "Add", logicalResourceId: "clusterAQueue", resourceType: "AWS::SQS::Queue", replacement: false }] },
|
|
255
|
+
});
|
|
256
|
+
expect(set.plannedOutputs).toEqual({ network: ["TopicArn"] });
|
|
257
|
+
expect(set.members.find((m) => m.member === "cluster-a")?.unknownInputs).toBeUndefined();
|
|
258
|
+
const created = mock.calls.filter((c) => c.method === "createChangeSet").map((c) => c.args as { stackName: string; parameters?: Record<string, string> });
|
|
259
|
+
expect(created.find((c) => c.stackName === "cluster-a")?.parameters).toEqual({ TopicArn: "arn:topic" });
|
|
193
260
|
});
|
|
194
261
|
|
|
195
262
|
it("a member whose inputs come from a stack the run creates first says so, with no counts and no change set", async () => {
|
|
@@ -222,3 +289,145 @@ describe("pr-plan over cfn-deploy members (#4084)", () => {
|
|
|
222
289
|
expect(replaced.set.doc.digest).not.toBe(first.set.doc.digest);
|
|
223
290
|
});
|
|
224
291
|
});
|
|
292
|
+
|
|
293
|
+
describe("cfn-deploy applies the change set it planned (#3997)", () => {
|
|
294
|
+
const topicUpdate = { action: "Modify" as const, logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false, replacementMode: "False" as const, details: ["DisplayName"] };
|
|
295
|
+
const queueAdd = { action: "Add" as const, logicalResourceId: "Queue", resourceType: "AWS::SQS::Queue", replacement: false };
|
|
296
|
+
|
|
297
|
+
it("a plan hands run an artifact over its changes, and run executes a change set with the same changes", async () => {
|
|
298
|
+
const mock = createMockCloudExecutor({ stacks: { network: { changes: [topicUpdate] } } });
|
|
299
|
+
const capability = createCfnDeployCapability(mock.executor);
|
|
300
|
+
const planned = await capability.plan!(ctx, { stack: "network", template });
|
|
301
|
+
expect(planned.artifact).toMatchObject({ kind: "cfn-deploy-plan", stack: "network" });
|
|
302
|
+
const out = await capability.run({ ...ctx, plans: { network: planned.artifact } }, { stack: "network", template });
|
|
303
|
+
expect(out.stackStatus).toBe("UPDATE_COMPLETE");
|
|
304
|
+
expect(methods(mock)).toEqual(["createChangeSet", "deleteChangeSet", "createChangeSet", "executeChangeSet", "waitForStack"]);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("run refuses, deletes its change set unexecuted and marks the error as a moved plan when the changes differ from the plan's", async () => {
|
|
308
|
+
const mock = createMockCloudExecutor({ stacks: { network: { changes: [topicUpdate] } } });
|
|
309
|
+
const capability = createCfnDeployCapability(mock.executor);
|
|
310
|
+
const planned = await capability.plan!(ctx, { stack: "network", template });
|
|
311
|
+
mock.setStack("network", { changes: [topicUpdate, queueAdd] });
|
|
312
|
+
const err = await capability.run({ ...ctx, plans: { network: planned.artifact } }, { stack: "network", template }).catch((e: unknown) => e);
|
|
313
|
+
expect(err).toBeInstanceOf(CfnPlanMovedError);
|
|
314
|
+
expect(isPlanMovedError(err)).toBe(true);
|
|
315
|
+
expect((err as Error).message).toMatch(/refusing to apply.*Plan it again/);
|
|
316
|
+
expect(methods(mock).slice(2)).toEqual(["createChangeSet", "deleteChangeSet"]);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("a plan of no changes refuses a run whose change set now has some, and accepts one that still has none", async () => {
|
|
320
|
+
const mock = createMockCloudExecutor({ stacks: { network: { noChanges: true } } });
|
|
321
|
+
const capability = createCfnDeployCapability(mock.executor);
|
|
322
|
+
const planned = await capability.plan!(ctx, { stack: "network", template });
|
|
323
|
+
await expect(capability.run({ ...ctx, plans: { network: planned.artifact } }, { stack: "network", template })).resolves.toMatchObject({ stackStatus: "UPDATE_COMPLETE" });
|
|
324
|
+
mock.setStack("network", { noChanges: false, changes: [queueAdd] });
|
|
325
|
+
await expect(capability.run({ ...ctx, plans: { network: planned.artifact } }, { stack: "network", template })).rejects.toBeInstanceOf(CfnPlanMovedError);
|
|
326
|
+
expect(methods(mock)).not.toContain("executeChangeSet");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("a run whose CREATE change set moved deletes the empty stack it made, too", async () => {
|
|
330
|
+
const mock = createMockCloudExecutor({ stacks: { network: { missing: true, changes: [queueAdd] } } });
|
|
331
|
+
const capability = createCfnDeployCapability(mock.executor);
|
|
332
|
+
const planned = await capability.plan!(ctx, { stack: "network", template });
|
|
333
|
+
mock.setStack("network", { changes: [queueAdd, { ...queueAdd, logicalResourceId: "Dlq" }] });
|
|
334
|
+
await expect(capability.run({ ...ctx, plans: { network: planned.artifact } }, { stack: "network", template })).rejects.toBeInstanceOf(CfnPlanMovedError);
|
|
335
|
+
expect(methods(mock).slice(3)).toEqual(["createChangeSet", "deleteChangeSet", "deleteStackShell"]);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("a member with holes hands run no artifact, so run plans it afresh", async () => {
|
|
339
|
+
const mock = createMockCloudExecutor({ stacks: { network: { changes: [queueAdd] } } });
|
|
340
|
+
const capability = createCfnDeployCapability(mock.executor);
|
|
341
|
+
const planned = await capability.plan!({ ...ctx, unresolvedInputs: ["upstream"] }, { stack: "network", template });
|
|
342
|
+
expect(planned.artifact).toBeUndefined();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('onReplace "block" refuses at plan time, naming the resources, and the change set is still deleted', async () => {
|
|
346
|
+
const mock = createMockCloudExecutor({
|
|
347
|
+
stacks: { network: { changes: [{ action: "Modify", logicalResourceId: "Table", resourceType: "AWS::DynamoDB::Table", replacement: true, replacementMode: "True" }] } },
|
|
348
|
+
});
|
|
349
|
+
const capability = createCfnDeployCapability(mock.executor);
|
|
350
|
+
const err = await capability.plan!(ctx, { stack: "network", template }).catch((e: unknown) => e);
|
|
351
|
+
expect(err).toBeInstanceOf(CfnReplacementBlockedError);
|
|
352
|
+
expect((err as Error).message).toContain("(Table)");
|
|
353
|
+
expect(methods(mock)).toEqual(["createChangeSet", "deleteChangeSet"]);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('onReplace "block" lets a Conditional replacement plan, as run does', async () => {
|
|
357
|
+
const { counts } = await planOne({
|
|
358
|
+
changes: [{ action: "Modify", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false, replacementMode: "Conditional" }],
|
|
359
|
+
});
|
|
360
|
+
expect(counts.replace).toBe(1);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("pr-apply over a moved stack fails the member with the refusal, executes nothing, and the run reads as a moved plan", async () => {
|
|
364
|
+
const mock = createMockCloudExecutor({ stacks: { network: { changes: [topicUpdate] } } });
|
|
365
|
+
const registry = new CapabilityRegistry();
|
|
366
|
+
registry.register(createCfnDeployCapability(mock.executor));
|
|
367
|
+
const components: DriverComponent[] = [
|
|
368
|
+
{ name: "network", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy", stack: "network", template }] }] },
|
|
369
|
+
];
|
|
370
|
+
const plan = planFanOut({ components, changed: ["network"] });
|
|
371
|
+
const set = await planPrSet({ components, plan, registry, env: "local" });
|
|
372
|
+
expect(set.plans.network?.network).toMatchObject({ kind: "cfn-deploy-plan" });
|
|
373
|
+
|
|
374
|
+
mock.setStack("network", { changes: [queueAdd] });
|
|
375
|
+
const moved = await applyPrSet({ components, plan, registry, env: "local", set });
|
|
376
|
+
expect(moved.status).toBe("failed");
|
|
377
|
+
expect(planMovedIn(moved.run.results)).toBe(true);
|
|
378
|
+
expect(moved.members[0]).toMatchObject({ member: "network", status: "failed" });
|
|
379
|
+
expect(methods(mock)).not.toContain("executeChangeSet");
|
|
380
|
+
|
|
381
|
+
mock.setStack("network", { changes: [topicUpdate] });
|
|
382
|
+
const applied = await applyPrSet({ components, plan, registry, env: "local", set });
|
|
383
|
+
expect(applied.status).toBe("applied");
|
|
384
|
+
expect(planMovedIn(applied.run.results)).toBe(false);
|
|
385
|
+
expect(methods(mock)).toContain("executeChangeSet");
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe("cfn-deploy plans nested stacks (#3997)", () => {
|
|
390
|
+
it("reads a nested change set's changes under the nested stack's logical id", async () => {
|
|
391
|
+
const root: DescribedCfnChangeSet = {
|
|
392
|
+
Status: "CREATE_COMPLETE",
|
|
393
|
+
Changes: [
|
|
394
|
+
{ ResourceChange: { Action: "Modify", LogicalResourceId: "Child", ResourceType: "AWS::CloudFormation::Stack", Replacement: "False", ChangeSetId: "arn:child" } },
|
|
395
|
+
{ ResourceChange: { Action: "Add", LogicalResourceId: "Queue", ResourceType: "AWS::SQS::Queue" } },
|
|
396
|
+
],
|
|
397
|
+
};
|
|
398
|
+
const nested: Record<string, DescribedCfnChangeSet> = {
|
|
399
|
+
"arn:child": {
|
|
400
|
+
Status: "CREATE_COMPLETE",
|
|
401
|
+
Changes: [
|
|
402
|
+
{ ResourceChange: { Action: "Modify", LogicalResourceId: "Grand", ResourceType: "AWS::CloudFormation::Stack", Replacement: "False", ChangeSetId: "arn:grand" } },
|
|
403
|
+
{ ResourceChange: { Action: "Modify", LogicalResourceId: "Topic", ResourceType: "AWS::SNS::Topic", Replacement: "True", Details: [{ Target: { Name: "TopicName" } }] } },
|
|
404
|
+
],
|
|
405
|
+
},
|
|
406
|
+
"arn:grand": { Status: "CREATE_COMPLETE", Changes: [{ ResourceChange: { Action: "Remove", LogicalResourceId: "Old", ResourceType: "AWS::SQS::Queue" } }] },
|
|
407
|
+
};
|
|
408
|
+
const described: string[] = [];
|
|
409
|
+
const changes = await readCfnChanges(root, async (id) => {
|
|
410
|
+
described.push(id);
|
|
411
|
+
return nested[id]!;
|
|
412
|
+
});
|
|
413
|
+
expect(described).toEqual(["arn:child", "arn:grand"]);
|
|
414
|
+
expect(changes.map((c) => [c.logicalResourceId, c.nestedStack ?? []])).toEqual([
|
|
415
|
+
["Child", []],
|
|
416
|
+
["Grand", ["Child"]],
|
|
417
|
+
["Old", ["Child", "Grand"]],
|
|
418
|
+
["Topic", ["Child"]],
|
|
419
|
+
["Queue", []],
|
|
420
|
+
]);
|
|
421
|
+
const entries = changes.map((c) => cfnChangeEntry("app", c));
|
|
422
|
+
expect(entries.find((e) => e.name === "Topic")).toMatchObject({ address: "Child/Topic", module: "Child", action: "replace" });
|
|
423
|
+
expect(entries.find((e) => e.name === "Old")).toMatchObject({ address: "Child/Grand/Old", module: "Child/Grand", action: "delete" });
|
|
424
|
+
expect(entries.find((e) => e.name === "Queue")).toMatchObject({ address: "Queue", action: "create" });
|
|
425
|
+
expect(entries.find((e) => e.name === "Queue")).not.toHaveProperty("module");
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it("asks for nested change sets only for a template that declares a nested stack", () => {
|
|
429
|
+
expect(templateHasNestedStacks(JSON.stringify({ Resources: { Child: { Type: "AWS::CloudFormation::Stack" } } }))).toBe(true);
|
|
430
|
+
expect(templateHasNestedStacks(JSON.stringify({ Resources: { Topic: { Type: "AWS::SNS::Topic" } } }))).toBe(false);
|
|
431
|
+
expect(templateHasNestedStacks("not json")).toBe(false);
|
|
432
|
+
});
|
|
433
|
+
});
|