@intentius/chant-lexicon-aws 0.124.30 → 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
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apply family — the most cloud-shaped family (see docs/components/cloud-boundary).
|
|
3
|
+
* AWS leaves for this starter set: cfn-deploy, ecs-update-service,
|
|
4
|
+
* lambda-deploy, s3-sync, cdn-invalidate, run-migration.
|
|
5
|
+
*
|
|
6
|
+
* `cfn-deploy` carries declarative safety options (changeset preview,
|
|
7
|
+
* `onReplace`, `stageGsi`) — sticky per-resource knowledge (a DynamoDB GSI
|
|
8
|
+
* updates one at a time; a key-schema change forces replacement) lives inside
|
|
9
|
+
* the capability, configured by options, never scripted per component. See
|
|
10
|
+
* docs/components/capabilities.mdx#stickiness-lives-in-the-capability.
|
|
11
|
+
*
|
|
12
|
+
* `cfn-deploy` and `ecs-update-service` are real implementations (#557, epic
|
|
13
|
+
* #551) over the injectable `CloudExecutor` (./cloud-executor.ts).
|
|
14
|
+
* `lambda-deploy` gained a real implementation in #558 (epic #551) — the one
|
|
15
|
+
* new capability the fourth, genuinely different validation component
|
|
16
|
+
* (image-processor-lambda) needed; see ../SPRAWL-VALIDATION.md. `s3-sync`,
|
|
17
|
+
* `cdn-invalidate`, and `run-migration` are also real over the same executor —
|
|
18
|
+
* `run-migration` dispatches on its target transport (ECS one-off task, Lambda
|
|
19
|
+
* invoke, or SSM host command).
|
|
20
|
+
*/
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
import { readFileSync } from "node:fs";
|
|
23
|
+
import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
|
|
24
|
+
import { defaultCloudExecutor } from "./cloud-executor.js";
|
|
25
|
+
/** CloudFormation's wording when a change set has nothing to apply — the stack
|
|
26
|
+
* is already current. AWS phrases it "The submitted information didn't contain
|
|
27
|
+
* changes."; `aws cloudformation deploy` reports "No updates are to be
|
|
28
|
+
* performed."; keep both plus a bare "no changes" for other backends/Floci. */
|
|
29
|
+
const NO_CHANGE_REASON = /(did ?n['’]?t|does ?n['’]?t|did not|does not) contain changes|no updates are to be performed|no changes/i;
|
|
30
|
+
/**
|
|
31
|
+
* True when CloudFormation reports the change set has nothing to apply. AWS
|
|
32
|
+
* surfaces this as a FAILED change set whose `StatusReason` names the empty
|
|
33
|
+
* diff (executing it then errors); when no reason is available, a FAILED set
|
|
34
|
+
* with zero proposed changes is the same no-op. A FAILED set with any other
|
|
35
|
+
* reason is a genuine create failure and must NOT be treated as a no-op.
|
|
36
|
+
*/
|
|
37
|
+
export function isNoopChangeSet(changeSet) {
|
|
38
|
+
if (changeSet.status !== "FAILED")
|
|
39
|
+
return false;
|
|
40
|
+
return changeSet.statusReason
|
|
41
|
+
? NO_CHANGE_REASON.test(changeSet.statusReason)
|
|
42
|
+
: changeSet.changes.length === 0;
|
|
43
|
+
}
|
|
44
|
+
// ── cfn-deploy ───────────────────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Whether a proposed change replaces its resource. CloudFormation's
|
|
47
|
+
* `Replacement: Conditional` counts: the replacement depends on a value it
|
|
48
|
+
* resolves only while applying, so a reviewer has to read it as one (#4084).
|
|
49
|
+
*/
|
|
50
|
+
export function cfnChangeReplaces(change) {
|
|
51
|
+
return change.replacement || change.replacementMode === "Conditional";
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* One CloudFormation resource change as a change-set entry (#4084). The
|
|
55
|
+
* address is the logical id, which is also the entity name the aws
|
|
56
|
+
* lexicon's graph IR gives the resource, so the entry maps onto its node
|
|
57
|
+
* (#4022). `Add` is a create, `Modify` an update or (when it replaces) a
|
|
58
|
+
* replace, `Remove` a delete, `Import` an import that changes nothing else,
|
|
59
|
+
* and `Dynamic` an update CloudFormation could not describe further.
|
|
60
|
+
*/
|
|
61
|
+
export function cfnChangeEntry(member, change) {
|
|
62
|
+
const attributes = [...new Set(change.details ?? [])].sort().map((path) => ({ path }));
|
|
63
|
+
// A change inside a nested stack (#3997) sits under the nested stacks'
|
|
64
|
+
// logical ids, the way a terraform address sits under its module.
|
|
65
|
+
const module = change.nestedStack?.length ? change.nestedStack.join("/") : undefined;
|
|
66
|
+
const base = {
|
|
67
|
+
member,
|
|
68
|
+
lexicon: "aws",
|
|
69
|
+
planner: "chant",
|
|
70
|
+
address: module ? `${module}/${change.logicalResourceId}` : change.logicalResourceId,
|
|
71
|
+
type: change.resourceType,
|
|
72
|
+
name: change.logicalResourceId,
|
|
73
|
+
...(module ? { module } : {}),
|
|
74
|
+
attributes,
|
|
75
|
+
};
|
|
76
|
+
switch (change.action) {
|
|
77
|
+
case "Add":
|
|
78
|
+
return { ...base, action: "create" };
|
|
79
|
+
case "Remove":
|
|
80
|
+
return { ...base, action: "delete", disruption: "destroy" };
|
|
81
|
+
case "Import":
|
|
82
|
+
return { ...base, action: "no-op", importing: true };
|
|
83
|
+
case "Dynamic":
|
|
84
|
+
return { ...base, action: cfnChangeReplaces(change) ? "replace" : "update", disruption: cfnChangeReplaces(change) ? "replace" : "unknown" };
|
|
85
|
+
default:
|
|
86
|
+
return cfnChangeReplaces(change)
|
|
87
|
+
? { ...base, action: "replace", disruption: "replace" }
|
|
88
|
+
: { ...base, action: "update", disruption: "in-place" };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The changes `onReplace: "block"` refuses: those CloudFormation says replace
|
|
93
|
+
* their resource (`Replacement: True`). `run` and `plan` refuse on the same
|
|
94
|
+
* set, so a plan that passes is one `run` would not block (#3997).
|
|
95
|
+
*/
|
|
96
|
+
function blockedReplacements(changes) {
|
|
97
|
+
return changes.filter((c) => c.replacement);
|
|
98
|
+
}
|
|
99
|
+
function isCfnPlanArtifact(value) {
|
|
100
|
+
const v = value;
|
|
101
|
+
return v?.kind === "cfn-deploy-plan" && typeof v.stack === "string" && typeof v.changesDigest === "string";
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A digest over a change set's changes as entries, independent of the change
|
|
105
|
+
* set's name and of the order CloudFormation lists them in. A change set with
|
|
106
|
+
* nothing to apply digests as no entries.
|
|
107
|
+
*/
|
|
108
|
+
export function cfnChangesDigest(stack, changeSet) {
|
|
109
|
+
const entries = isNoopChangeSet(changeSet) ? [] : changeSet.changes.map((c) => cfnChangeEntry(stack, c));
|
|
110
|
+
entries.sort((a, b) => a.address.localeCompare(b.address) || a.action.localeCompare(b.action));
|
|
111
|
+
return computePlanDigest("cfn-deploy-changes", { stack, entries });
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Thrown by `run` when the change set it created proposes other changes than
|
|
115
|
+
* the change set its plan read (#3997): the stack, the template or the
|
|
116
|
+
* parameters moved between the plan and the apply. The change set is deleted
|
|
117
|
+
* unexecuted. `planMoved` is core's mark for a moved plan
|
|
118
|
+
* (`isPlanMovedError`), so the command that applied it exits 3.
|
|
119
|
+
*/
|
|
120
|
+
export class CfnPlanMovedError extends Error {
|
|
121
|
+
constructor(stack, planned, now) {
|
|
122
|
+
super(`cfn-deploy "${stack}": refusing to apply. The change set proposes other changes than the plan that was approved ` +
|
|
123
|
+
`(planned ${planned}, now ${now}); the stack, its template or its parameters changed since. Plan it again.`);
|
|
124
|
+
this.stack = stack;
|
|
125
|
+
this.planned = planned;
|
|
126
|
+
this.now = now;
|
|
127
|
+
this.planMoved = true;
|
|
128
|
+
this.name = "CfnPlanMovedError";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** The template's bytes as a digest, or `null` when it cannot be read here (the change set then reports why). */
|
|
132
|
+
function templateDigest(path) {
|
|
133
|
+
try {
|
|
134
|
+
return `sha256:${createHash("sha256").update(readFileSync(path)).digest("hex")}`;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* What a `cfn-deploy` plan binds: the environment, the stack, the template's
|
|
142
|
+
* bytes and the parameters as resolved, and the stacks whose outputs were
|
|
143
|
+
* not known yet. The proposed changes are not in it; they are the member's
|
|
144
|
+
* entries, which the change-set document's digest binds (#4084).
|
|
145
|
+
*/
|
|
146
|
+
function cfnPlanDigest(ctx, stack, input, parameters, unresolved) {
|
|
147
|
+
return computePlanDigest("cfn-deploy-plan", {
|
|
148
|
+
env: ctx.env,
|
|
149
|
+
stack,
|
|
150
|
+
template: input.template,
|
|
151
|
+
templateDigest: templateDigest(input.template),
|
|
152
|
+
parameters,
|
|
153
|
+
unresolved,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Plan a `cfn-deploy` step without changing the stack (#4084): create a
|
|
158
|
+
* change set for the template and the resolved parameters, read the changes
|
|
159
|
+
* it proposes, and delete it without executing it. A stack that does not
|
|
160
|
+
* exist yet gets a CREATE change set, so every resource reads as a create,
|
|
161
|
+
* and the empty `REVIEW_IN_PROGRESS` stack CloudFormation made to hold it is
|
|
162
|
+
* deleted too. A change set with nothing to apply (`isNoopChangeSet`) gives
|
|
163
|
+
* a member with no entries. A step whose parameters read outputs that are
|
|
164
|
+
* not known yet, from a stack the same run applies first, creates no change
|
|
165
|
+
* set: its member gets a hole saying whose outputs it waits on, and no
|
|
166
|
+
* artifact, so `run` plans it afresh.
|
|
167
|
+
*
|
|
168
|
+
* With `onReplace: "block"` (the default), a change set that replaces a
|
|
169
|
+
* resource fails the plan with {@link CfnReplacementBlockedError}, so the
|
|
170
|
+
* refusal shows in the pull request's plan rather than at apply (#3997).
|
|
171
|
+
* Otherwise the result carries a {@link CfnPlanArtifact} that `run` checks
|
|
172
|
+
* its own change set against.
|
|
173
|
+
*
|
|
174
|
+
* The plan answers the outputs the stack will have (#3999), from the
|
|
175
|
+
* outputs it has now (`DescribeStacks`) and the change set: an output keeps
|
|
176
|
+
* its value when every resource its template `Value` reads keeps its
|
|
177
|
+
* physical resource, which an in-place `Modify` does and an `Add`, a
|
|
178
|
+
* `Remove` or a replacement does not ({@link cfnPlannedOutputs}). An output
|
|
179
|
+
* that reads a parameter, an import or a condition is known only after
|
|
180
|
+
* apply. A wrong guess costs a round, never a wrong apply: a dependent whose
|
|
181
|
+
* input applies as another value is held back and planned again.
|
|
182
|
+
*/
|
|
183
|
+
async function planCfnDeploy(executor, ctx, input) {
|
|
184
|
+
const stack = input.stack ?? ctx.component;
|
|
185
|
+
const raw = { ...(input.inputs ?? {}), ...(input.imageRef !== undefined ? { ImageRef: input.imageRef } : {}) };
|
|
186
|
+
const parameters = {};
|
|
187
|
+
const missing = [];
|
|
188
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
189
|
+
if (value === undefined || value === null) {
|
|
190
|
+
parameters[key] = null;
|
|
191
|
+
missing.push(key);
|
|
192
|
+
}
|
|
193
|
+
else
|
|
194
|
+
parameters[key] = typeof value === "string" ? value : JSON.stringify(value);
|
|
195
|
+
}
|
|
196
|
+
const unresolved = [...(ctx.unresolvedInputs ?? [])].sort();
|
|
197
|
+
const planDigest = cfnPlanDigest(ctx, stack, input, parameters, unresolved);
|
|
198
|
+
const member = (holes) => ({
|
|
199
|
+
member: stack,
|
|
200
|
+
lexicon: "aws",
|
|
201
|
+
planner: "chant",
|
|
202
|
+
status: "planned",
|
|
203
|
+
planDigest,
|
|
204
|
+
holes,
|
|
205
|
+
});
|
|
206
|
+
if (unresolved.length > 0 || missing.length > 0) {
|
|
207
|
+
const reason = unresolved.length > 0
|
|
208
|
+
? `inputs from ${unresolved.join(", ")}; re-checked on apply`
|
|
209
|
+
: `inputs ${missing.sort().join(", ")} are known only at apply; re-checked on apply`;
|
|
210
|
+
return {
|
|
211
|
+
member: stack,
|
|
212
|
+
planDigest,
|
|
213
|
+
changeSet: { member: member([{ address: stack, type: "AWS::CloudFormation::Stack", reason }]), entries: [] },
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const changeSet = await executor.cloudformation.createChangeSet({
|
|
217
|
+
stackName: stack,
|
|
218
|
+
templatePath: input.template,
|
|
219
|
+
parameters: flattenCfnInputs(input),
|
|
220
|
+
});
|
|
221
|
+
try {
|
|
222
|
+
if (isNoopChangeSet(changeSet)) {
|
|
223
|
+
const artifact = { kind: "cfn-deploy-plan", stack, changesDigest: cfnChangesDigest(stack, changeSet) };
|
|
224
|
+
// Nothing to apply, so the outputs the stack has are the ones it will have (#3999).
|
|
225
|
+
const standing = await standingOutputs(executor, stack);
|
|
226
|
+
const outputs = standing ? { known: { ...standing }, unknown: [] } : undefined;
|
|
227
|
+
return { member: stack, planDigest, artifact, changeSet: { member: member([]), entries: [] }, ...(outputs ? { outputs } : {}) };
|
|
228
|
+
}
|
|
229
|
+
if (changeSet.status === "FAILED") {
|
|
230
|
+
throw new Error(`cfn-deploy "${stack}": CloudFormation could not create a change set to plan it${changeSet.statusReason ? `: ${changeSet.statusReason}` : ""}`);
|
|
231
|
+
}
|
|
232
|
+
const blocked = blockedReplacements(changeSet.changes);
|
|
233
|
+
if (blocked.length > 0 && (input.onReplace ?? "block") === "block")
|
|
234
|
+
throw new CfnReplacementBlockedError(stack, blocked);
|
|
235
|
+
const entries = changeSet.changes.map((c) => cfnChangeEntry(stack, c));
|
|
236
|
+
const artifact = { kind: "cfn-deploy-plan", stack, changesDigest: cfnChangesDigest(stack, changeSet) };
|
|
237
|
+
const outputs = changeSet.stackCreated ? undefined : await plannedStackOutputs(executor, stack, input.template, changeSet.changes);
|
|
238
|
+
return { member: stack, planDigest, artifact, changeSet: { member: member([]), entries }, ...(outputs ? { outputs } : {}) };
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
await cleanUpPlanChangeSet(executor, changeSet);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Delete a plan's change set, and the empty stack a CREATE change set made
|
|
246
|
+
* when no stack existed before. A failure to clean up does not fail the
|
|
247
|
+
* plan: the change set holds nothing that applies, and the next CREATE
|
|
248
|
+
* change set reuses an empty stack left behind.
|
|
249
|
+
*/
|
|
250
|
+
async function cleanUpPlanChangeSet(executor, changeSet) {
|
|
251
|
+
try {
|
|
252
|
+
await executor.cloudformation.deleteChangeSet({ stackName: changeSet.stackName, changeSetName: changeSet.changeSetName });
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
console.error(`cfn-deploy plan "${changeSet.stackName}": could not delete change set ${changeSet.changeSetName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
256
|
+
}
|
|
257
|
+
if (changeSet.stackCreated && executor.cloudformation.deleteStackShell) {
|
|
258
|
+
try {
|
|
259
|
+
await executor.cloudformation.deleteStackShell(changeSet.stackName);
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
console.error(`cfn-deploy plan "${changeSet.stackName}": could not delete the empty stack its CREATE change set made: ${err instanceof Error ? err.message : String(err)}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/** The stack's outputs as they stand, or `undefined` when they cannot be read. */
|
|
267
|
+
async function standingOutputs(executor, stack) {
|
|
268
|
+
try {
|
|
269
|
+
return { ...(await executor.cloudformation.describeStack(stack)).outputs };
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* What the plan says of the stack's outputs once `changes` apply: none to
|
|
277
|
+
* say for a template with no `Outputs`, which needs no `DescribeStacks`.
|
|
278
|
+
*/
|
|
279
|
+
async function plannedStackOutputs(executor, stack, templatePath, changes) {
|
|
280
|
+
const template = readTemplate(templatePath);
|
|
281
|
+
if (!template || typeof template !== "object")
|
|
282
|
+
return undefined;
|
|
283
|
+
if (Object.keys(template.Outputs ?? {}).length === 0)
|
|
284
|
+
return { known: {}, unknown: [] };
|
|
285
|
+
return cfnPlannedOutputs(template, changes, await standingOutputs(executor, stack));
|
|
286
|
+
}
|
|
287
|
+
/** The template at `path` as JSON, or `undefined` when it cannot be read or parsed here. */
|
|
288
|
+
function readTemplate(path) {
|
|
289
|
+
try {
|
|
290
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* What a template's `Value` reads (#3999): the logical ids it names through
|
|
298
|
+
* `Ref`, `Fn::GetAtt` and `Fn::Sub`, and whether it reads anything this plan
|
|
299
|
+
* cannot follow (`Fn::ImportValue`, a condition's `Fn::If`, or an intrinsic
|
|
300
|
+
* it does not know).
|
|
301
|
+
*/
|
|
302
|
+
function valueReads(value) {
|
|
303
|
+
const ids = new Set();
|
|
304
|
+
let opaque = false;
|
|
305
|
+
const sub = (text) => {
|
|
306
|
+
for (const m of text.matchAll(/\$\{([^}!][^}]*)\}/g))
|
|
307
|
+
ids.add(m[1].split(".")[0]);
|
|
308
|
+
};
|
|
309
|
+
const walk = (v) => {
|
|
310
|
+
if (Array.isArray(v))
|
|
311
|
+
return v.forEach(walk);
|
|
312
|
+
if (v === null || typeof v !== "object")
|
|
313
|
+
return;
|
|
314
|
+
const entries = Object.entries(v);
|
|
315
|
+
if (entries.length === 1) {
|
|
316
|
+
const [key, arg] = entries[0];
|
|
317
|
+
if (key === "Ref") {
|
|
318
|
+
if (typeof arg === "string")
|
|
319
|
+
ids.add(arg);
|
|
320
|
+
else
|
|
321
|
+
opaque = true;
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (key === "Fn::GetAtt") {
|
|
325
|
+
if (Array.isArray(arg) && typeof arg[0] === "string")
|
|
326
|
+
ids.add(arg[0]);
|
|
327
|
+
else if (typeof arg === "string")
|
|
328
|
+
ids.add(arg.split(".")[0]);
|
|
329
|
+
else
|
|
330
|
+
opaque = true;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (key === "Fn::Sub") {
|
|
334
|
+
if (typeof arg === "string")
|
|
335
|
+
sub(arg);
|
|
336
|
+
else if (Array.isArray(arg) && typeof arg[0] === "string") {
|
|
337
|
+
const vars = (arg[1] ?? {});
|
|
338
|
+
for (const m of arg[0].matchAll(/\$\{([^}!][^}]*)\}/g))
|
|
339
|
+
if (!(m[1] in vars))
|
|
340
|
+
ids.add(m[1].split(".")[0]);
|
|
341
|
+
walk(vars);
|
|
342
|
+
}
|
|
343
|
+
else
|
|
344
|
+
opaque = true;
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (key === "Fn::Join" || key === "Fn::Select" || key === "Fn::Split" || key === "Fn::Base64")
|
|
348
|
+
return walk(arg);
|
|
349
|
+
if (key.startsWith("Fn::") || key === "Condition") {
|
|
350
|
+
opaque = true;
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
entries.forEach(([, x]) => walk(x));
|
|
355
|
+
};
|
|
356
|
+
walk(value);
|
|
357
|
+
return { ids, opaque };
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* The outputs a stack will have once `changes` apply (#3999), from its
|
|
361
|
+
* template and the outputs it has now. An output is known, at the value it
|
|
362
|
+
* has now, when its `Value` reads only pseudo parameters and resources whose
|
|
363
|
+
* physical resource the change set keeps (no `Add`, `Remove`, `Import` or
|
|
364
|
+
* `Dynamic`, no replacement, no change in a nested stack under it). One
|
|
365
|
+
* that reads a parameter, an import or a condition, one the stack does not
|
|
366
|
+
* have yet, and every output of a template this cannot read, is unknown.
|
|
367
|
+
* `undefined` when there is no template or no standing stack to read from.
|
|
368
|
+
*/
|
|
369
|
+
export function cfnPlannedOutputs(template, changes, standing) {
|
|
370
|
+
if (!standing || template === null || typeof template !== "object")
|
|
371
|
+
return undefined;
|
|
372
|
+
const t = template;
|
|
373
|
+
const parameters = new Set(Object.keys(t.Parameters ?? {}));
|
|
374
|
+
const moved = new Set();
|
|
375
|
+
for (const c of changes) {
|
|
376
|
+
const id = c.nestedStack?.[0] ?? c.logicalResourceId;
|
|
377
|
+
const keeps = !c.nestedStack && c.action === "Modify" && !c.replacement && c.replacementMode !== "True" && c.replacementMode !== "Conditional";
|
|
378
|
+
if (!keeps)
|
|
379
|
+
moved.add(id);
|
|
380
|
+
}
|
|
381
|
+
const known = {};
|
|
382
|
+
const unknown = [];
|
|
383
|
+
for (const [name, output] of Object.entries(t.Outputs ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
|
|
384
|
+
const { ids, opaque } = valueReads(output?.Value);
|
|
385
|
+
const stays = !opaque && output?.Condition === undefined && name in standing &&
|
|
386
|
+
[...ids].every((id) => id.startsWith("AWS::") || (!parameters.has(id) && !moved.has(id)));
|
|
387
|
+
if (stays)
|
|
388
|
+
known[name] = standing[name];
|
|
389
|
+
else
|
|
390
|
+
unknown.push(name);
|
|
391
|
+
}
|
|
392
|
+
return { known, unknown };
|
|
393
|
+
}
|
|
394
|
+
/** Whether a CloudFormation error says the stack does not exist. */
|
|
395
|
+
const STACK_MISSING = /does not exist/i;
|
|
396
|
+
/**
|
|
397
|
+
* Thrown when a changeset proposes a resource replacement and `onReplace` is
|
|
398
|
+
* `"block"` (the default) — refusing to apply rather than silently destroying
|
|
399
|
+
* and recreating a resource, which for a stateful resource (a DynamoDB table,
|
|
400
|
+
* an RDS instance) means data loss. Carries the specific resources CloudFormation
|
|
401
|
+
* proposed replacing, so the caller/human reviewing the changeset knows exactly
|
|
402
|
+
* what was refused.
|
|
403
|
+
*/
|
|
404
|
+
export class CfnReplacementBlockedError extends Error {
|
|
405
|
+
constructor(stack, replacements) {
|
|
406
|
+
super(`cfn-deploy "${stack}": refusing changeset — it would replace ${replacements.length} resource(s) ` +
|
|
407
|
+
`(${replacements.map((r) => r.logicalResourceId).join(", ")}) and onReplace is "block". ` +
|
|
408
|
+
`Re-run with onReplace: "allow" or "snapshot-first" to proceed.`);
|
|
409
|
+
this.stack = stack;
|
|
410
|
+
this.replacements = replacements;
|
|
411
|
+
this.name = "CfnReplacementBlockedError";
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Deploy a CloudFormation stack: create a changeset, apply the declarative
|
|
416
|
+
* safety policy against its proposed changes, then execute (or refuse) it and
|
|
417
|
+
* wait for the stack to reach a terminal status.
|
|
418
|
+
*
|
|
419
|
+
* - `onReplace: "block"` (default) — `CfnReplacementBlockedError` if any
|
|
420
|
+
* change requires replacement; the changeset is left/deleted unexecuted.
|
|
421
|
+
* - `onReplace: "allow"` — executes regardless of replacement.
|
|
422
|
+
* - `onReplace: "snapshot-first"` — takes a `snapshot-before`-style capture
|
|
423
|
+
* (via `executor.cloudformation`'s DynamoDB/RDS-aware snapshot, when the
|
|
424
|
+
* replaced resource is a stateful type) before executing.
|
|
425
|
+
* - `stageGsi` records intent only in this phase (the real add→backfill→remove
|
|
426
|
+
* staging is DynamoDB-specific choreography layered on top of the plain
|
|
427
|
+
* changeset apply; Phase 1 surfaces the option and passes it through
|
|
428
|
+
* uninterpreted rather than half-implementing GSI staging, since staging
|
|
429
|
+
* requires a second follow-up deploy the single-step `cfn-deploy` capability
|
|
430
|
+
* does not orchestrate on its own).
|
|
431
|
+
*
|
|
432
|
+
* Rollback: trigger CloudFormation's native `rollback-stack` — the stack's own
|
|
433
|
+
* automatic-rollback mechanism restores the last known-good state.
|
|
434
|
+
*/
|
|
435
|
+
export function createCfnDeployCapability(executor = defaultCloudExecutor()) {
|
|
436
|
+
return {
|
|
437
|
+
kind: "cfn-deploy",
|
|
438
|
+
plan(ctx, input) {
|
|
439
|
+
return planCfnDeploy(executor, ctx, input);
|
|
440
|
+
},
|
|
441
|
+
async outputs(ctx, input) {
|
|
442
|
+
// The outputs the stack has now, for a dependent planned before it
|
|
443
|
+
// applies (#3183). A stack that does not exist yet has none.
|
|
444
|
+
try {
|
|
445
|
+
return (await executor.cloudformation.describeStack(input.stack ?? ctx.component)).outputs;
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
if (STACK_MISSING.test(err instanceof Error ? err.message : String(err)))
|
|
449
|
+
return {};
|
|
450
|
+
throw err;
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
async run(ctx, input) {
|
|
454
|
+
const stack = input.stack ?? ctx.component;
|
|
455
|
+
const onReplace = input.onReplace ?? "block";
|
|
456
|
+
const changeSet = await executor.cloudformation.createChangeSet({
|
|
457
|
+
stackName: stack,
|
|
458
|
+
templatePath: input.template,
|
|
459
|
+
parameters: flattenCfnInputs(input),
|
|
460
|
+
});
|
|
461
|
+
// Handed the plan the approver approved (#3997), apply only a change
|
|
462
|
+
// set that proposes the same changes. Anything else means the stack,
|
|
463
|
+
// the template or the parameters moved since the plan.
|
|
464
|
+
const planned = ctx.plans?.[stack];
|
|
465
|
+
if (isCfnPlanArtifact(planned)) {
|
|
466
|
+
const now = cfnChangesDigest(stack, changeSet);
|
|
467
|
+
if (now !== planned.changesDigest) {
|
|
468
|
+
await cleanUpPlanChangeSet(executor, changeSet);
|
|
469
|
+
throw new CfnPlanMovedError(stack, planned.changesDigest, now);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// An unchanged stack yields a change set with nothing to apply.
|
|
473
|
+
// `execute-change-set` rejects it ("The submitted information didn't
|
|
474
|
+
// contain changes"), which would fail this component and stop the whole
|
|
475
|
+
// run — so a re-run of `--components all` dies on the first already-current
|
|
476
|
+
// stack. Treat it as an idempotent no-op success instead: delete the empty
|
|
477
|
+
// change set and report the stack's current status/outputs so downstream
|
|
478
|
+
// steps still get real outputs and the run proceeds (#960).
|
|
479
|
+
if (isNoopChangeSet(changeSet)) {
|
|
480
|
+
await executor.cloudformation.deleteChangeSet({
|
|
481
|
+
stackName: stack,
|
|
482
|
+
changeSetName: changeSet.changeSetName,
|
|
483
|
+
});
|
|
484
|
+
const { stackStatus, outputs } = await executor.cloudformation.describeStack(stack);
|
|
485
|
+
return { stackStatus, outputs };
|
|
486
|
+
}
|
|
487
|
+
const replacements = blockedReplacements(changeSet.changes);
|
|
488
|
+
let snapshotId;
|
|
489
|
+
if (replacements.length > 0 && onReplace === "block") {
|
|
490
|
+
await executor.cloudformation.deleteChangeSet({
|
|
491
|
+
stackName: stack,
|
|
492
|
+
changeSetName: changeSet.changeSetName,
|
|
493
|
+
});
|
|
494
|
+
throw new CfnReplacementBlockedError(stack, replacements);
|
|
495
|
+
}
|
|
496
|
+
if (replacements.length > 0 && onReplace === "snapshot-first") {
|
|
497
|
+
// Phase 1: record which resources were replaced so the caller/audit
|
|
498
|
+
// trail has the fact captured; a dedicated `snapshot-before` capability
|
|
499
|
+
// handles resource-specific (DynamoDB/RDS/OpenSearch) capture and is
|
|
500
|
+
// composed ahead of `cfn-deploy` in the component's own phase list
|
|
501
|
+
// when the sticky resource needs a real point-in-time backup.
|
|
502
|
+
snapshotId = `pending-snapshot:${stack}:${replacements.map((r) => r.logicalResourceId).join(",")}`;
|
|
503
|
+
}
|
|
504
|
+
await executor.cloudformation.executeChangeSet({
|
|
505
|
+
stackName: stack,
|
|
506
|
+
changeSetName: changeSet.changeSetName,
|
|
507
|
+
});
|
|
508
|
+
const { stackStatus, outputs } = await executor.cloudformation.waitForStack(stack);
|
|
509
|
+
// `waitForStack` returns on any terminal (`*_COMPLETE`/`*_FAILED`) status,
|
|
510
|
+
// including the failure ones: a create that fails rolls back to
|
|
511
|
+
// `ROLLBACK_COMPLETE`, an update to `UPDATE_ROLLBACK_COMPLETE`, and hard
|
|
512
|
+
// failures end in `*_FAILED`. Treating those as success would report a
|
|
513
|
+
// failed deploy as green (and hand downstream steps an empty `outputs`),
|
|
514
|
+
// so throw — the same fail-closed contract the rest of the release model
|
|
515
|
+
// relies on.
|
|
516
|
+
// A missing status is fail-closed too: Floci's describe-stacks can omit
|
|
517
|
+
// StackStatus mid-rollback, and `undefined.includes` would throw a TypeError
|
|
518
|
+
// that masks the real deploy failure (#947).
|
|
519
|
+
if (!stackStatus || stackStatus.includes("ROLLBACK") || stackStatus.endsWith("_FAILED")) {
|
|
520
|
+
throw new Error(`cfn-deploy "${stack}": stack reached ${stackStatus ?? "an indeterminate status"} — the deploy failed and was rolled back. Inspect the stack events for the resource that failed to create/update.`);
|
|
521
|
+
}
|
|
522
|
+
return { stackStatus, outputs, ...(snapshotId ? { snapshotId } : {}) };
|
|
523
|
+
},
|
|
524
|
+
async rollback(ctx, input) {
|
|
525
|
+
const stack = input.stack ?? ctx.component;
|
|
526
|
+
try {
|
|
527
|
+
await executor.cloudformation.rollbackStack(stack);
|
|
528
|
+
}
|
|
529
|
+
catch (err) {
|
|
530
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
531
|
+
// Local emulators (Floci) don't implement RollbackStack → `UnknownAction`.
|
|
532
|
+
// The stack is already in a terminal failed state; degrade to a clear
|
|
533
|
+
// message instead of crashing the compensation phase (#947).
|
|
534
|
+
if (/UnknownAction|not supported/i.test(message)) {
|
|
535
|
+
console.error(`cfn-deploy rollback "${stack}": the target doesn't support RollbackStack (a local emulator such as Floci) — skipping automated rollback. Original: ${message}`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
throw err;
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
/** Flatten the wired `inputs` map plus `imageRef` (already resolved by the driver) to plain string CloudFormation parameters. */
|
|
544
|
+
function flattenCfnInputs(input) {
|
|
545
|
+
const params = {};
|
|
546
|
+
for (const [key, value] of Object.entries(input.inputs ?? {})) {
|
|
547
|
+
params[key] = typeof value === "string" ? value : JSON.stringify(value);
|
|
548
|
+
}
|
|
549
|
+
if (input.imageRef)
|
|
550
|
+
params.ImageRef = input.imageRef;
|
|
551
|
+
return Object.keys(params).length > 0 ? params : undefined;
|
|
552
|
+
}
|
|
553
|
+
/** Default `cfn-deploy` capability, backed by the real `CloudExecutor`. */
|
|
554
|
+
export const cfnDeployCapability = createCfnDeployCapability();
|
|
555
|
+
/**
|
|
556
|
+
* Roll a new task definition/image out to an ECS service via `UpdateService`.
|
|
557
|
+
* Before the update, `run` reads the service with `describe-services` and
|
|
558
|
+
* records the task definition it was running in its output. The rollback sends
|
|
559
|
+
* that previous task definition back (and the previous desired count, when the
|
|
560
|
+
* step changed it). The step's own `imageRef` is never the rollback target:
|
|
561
|
+
* it is the revision the step just rolled out, so re-sending it restores
|
|
562
|
+
* nothing (#2609). When the previous task definition could not be read, the
|
|
563
|
+
* rollback fails with a message naming the service rather than report a
|
|
564
|
+
* restore that changed nothing (#2605). A component that needs a specific
|
|
565
|
+
* task definition restored can still give itself an explicit rollback phase,
|
|
566
|
+
* such as `rollback-previous` with that `taskDefinition`.
|
|
567
|
+
*/
|
|
568
|
+
export function createEcsUpdateServiceCapability(executor = defaultCloudExecutor()) {
|
|
569
|
+
return {
|
|
570
|
+
kind: "ecs-update-service",
|
|
571
|
+
async run(_ctx, input) {
|
|
572
|
+
// A failed read only costs the rollback its target; the forward update
|
|
573
|
+
// still runs, and the rollback then fails saying it has nothing to restore.
|
|
574
|
+
const before = await executor.ecs.describeService(input.cluster, input.service).catch(() => undefined);
|
|
575
|
+
const { deploymentId } = await executor.ecs.updateService({
|
|
576
|
+
cluster: input.cluster,
|
|
577
|
+
service: input.service,
|
|
578
|
+
taskDefinition: input.imageRef,
|
|
579
|
+
desiredCount: input.desiredCount,
|
|
580
|
+
forceNewDeployment: input.forceNewDeployment,
|
|
581
|
+
});
|
|
582
|
+
return {
|
|
583
|
+
deploymentId,
|
|
584
|
+
...(before?.taskDefinition ? { previousTaskDefinition: before.taskDefinition } : {}),
|
|
585
|
+
...(before && input.desiredCount !== undefined ? { previousDesiredCount: before.desiredCount } : {}),
|
|
586
|
+
};
|
|
587
|
+
},
|
|
588
|
+
async rollback(_ctx, input, output) {
|
|
589
|
+
const previous = output?.previousTaskDefinition;
|
|
590
|
+
if (!previous) {
|
|
591
|
+
throw new Error(`ecs-update-service rollback: the task definition service "${input.service}" ran before the step ` +
|
|
592
|
+
`was not recorded, so there is nothing to roll back to; ` +
|
|
593
|
+
`give the component a rollback phase that names the taskDefinition`);
|
|
594
|
+
}
|
|
595
|
+
await executor.ecs.rollbackService({
|
|
596
|
+
cluster: input.cluster,
|
|
597
|
+
service: input.service,
|
|
598
|
+
taskDefinition: previous,
|
|
599
|
+
desiredCount: output?.previousDesiredCount,
|
|
600
|
+
});
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
/** Default `ecs-update-service` capability, backed by the real `CloudExecutor`. */
|
|
605
|
+
export const ecsUpdateServiceCapability = createEcsUpdateServiceCapability();
|
|
606
|
+
/**
|
|
607
|
+
* Update a Lambda function's code to a new container image, wait for the
|
|
608
|
+
* update to apply, and (by default) publish an immutable version and repoint
|
|
609
|
+
* `alias` at it — the same digest-promotion invariant every other apply verb
|
|
610
|
+
* follows (`imageRef`/`codeRef` is a resolved `publish-image` digest, never a
|
|
611
|
+
* rebuild). Rollback restores whatever version `alias` pointed at before this
|
|
612
|
+
* step ran (captured up front), repointing the alias back — mirroring
|
|
613
|
+
* `ecs-update-service`'s best-effort re-apply style rather than a native
|
|
614
|
+
* automatic rollback (Lambda has none for code updates).
|
|
615
|
+
*/
|
|
616
|
+
export function createLambdaDeployCapability(executor = defaultCloudExecutor()) {
|
|
617
|
+
const previousVersionByTarget = new Map();
|
|
618
|
+
return {
|
|
619
|
+
kind: "lambda-deploy",
|
|
620
|
+
async run(_ctx, input) {
|
|
621
|
+
const alias = input.alias ?? "live";
|
|
622
|
+
if (!previousVersionByTarget.has(input.functionName)) {
|
|
623
|
+
previousVersionByTarget.set(input.functionName, await executor.lambda.getAliasVersion(input.functionName, alias));
|
|
624
|
+
}
|
|
625
|
+
const { functionArn } = await executor.lambda.updateFunctionCode({
|
|
626
|
+
functionName: input.functionName,
|
|
627
|
+
imageUri: input.codeRef,
|
|
628
|
+
});
|
|
629
|
+
const { status } = await executor.lambda.waitForUpdate(input.functionName);
|
|
630
|
+
if (status !== "Successful") {
|
|
631
|
+
throw new Error(`lambda-deploy "${input.functionName}": code update ended "${status}"`);
|
|
632
|
+
}
|
|
633
|
+
if (input.publish === false) {
|
|
634
|
+
return { version: "$LATEST", functionArn };
|
|
635
|
+
}
|
|
636
|
+
const published = await executor.lambda.publishVersion({ functionName: input.functionName });
|
|
637
|
+
await executor.lambda.updateAlias({ functionName: input.functionName, alias, version: published.version });
|
|
638
|
+
return { version: published.version, functionArn: published.functionArn };
|
|
639
|
+
},
|
|
640
|
+
async rollback(_ctx, input) {
|
|
641
|
+
const alias = input.alias ?? "live";
|
|
642
|
+
const previousVersion = previousVersionByTarget.get(input.functionName);
|
|
643
|
+
if (!previousVersion)
|
|
644
|
+
return; // no prior alias version recorded — nothing to restore.
|
|
645
|
+
await executor.lambda.updateAlias({ functionName: input.functionName, alias, version: previousVersion });
|
|
646
|
+
},
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
/** Default `lambda-deploy` capability, backed by the real `CloudExecutor`. */
|
|
650
|
+
export const lambdaDeployCapability = createLambdaDeployCapability();
|
|
651
|
+
/** Sync a directory of static assets to an S3 bucket via `aws s3 sync` (endpoint-aware through the `CloudExecutor`). */
|
|
652
|
+
export function createS3SyncCapability(executor = defaultCloudExecutor()) {
|
|
653
|
+
return {
|
|
654
|
+
kind: "s3-sync",
|
|
655
|
+
rollbackPolicy: "needs-opt-out",
|
|
656
|
+
async run(_ctx, input) {
|
|
657
|
+
return executor.s3.sync({ from: input.from, to: input.to, delete: input.delete });
|
|
658
|
+
},
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
/** Default `s3-sync` capability, backed by the real `CloudExecutor`. */
|
|
662
|
+
export const s3SyncCapability = createS3SyncCapability();
|
|
663
|
+
/** Invalidate CDN cache paths after a content update (e.g. following `s3-sync`), via `aws cloudfront create-invalidation`. */
|
|
664
|
+
export function createCdnInvalidateCapability(executor = defaultCloudExecutor()) {
|
|
665
|
+
return {
|
|
666
|
+
kind: "cdn-invalidate",
|
|
667
|
+
rollbackPolicy: "needs-opt-out",
|
|
668
|
+
async run(_ctx, input) {
|
|
669
|
+
return executor.cloudfront.createInvalidation({
|
|
670
|
+
distributionId: input.distributionId,
|
|
671
|
+
paths: input.paths ?? ["/*"],
|
|
672
|
+
});
|
|
673
|
+
},
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
/** Default `cdn-invalidate` capability, backed by the real `CloudExecutor`. */
|
|
677
|
+
export const cdnInvalidateCapability = createCdnInvalidateCapability();
|
|
678
|
+
/**
|
|
679
|
+
* A runner that reports its outcome does so as a trailing JSON object
|
|
680
|
+
* `{"applied":bool,"version":"..."}` — on stdout (host) or in the Lambda
|
|
681
|
+
* response payload. Parse the last such line; when the runner emits nothing
|
|
682
|
+
* parseable, fall back to `applied` = "the process exited cleanly" and an empty
|
|
683
|
+
* version (an ECS task exposes only its exit code — no captured stdout — so it
|
|
684
|
+
* always takes this fallback).
|
|
685
|
+
*/
|
|
686
|
+
function parseMigrationReport(text, appliedFallback) {
|
|
687
|
+
for (const line of text.split("\n").map((l) => l.trim()).reverse()) {
|
|
688
|
+
if (!line.startsWith("{"))
|
|
689
|
+
continue;
|
|
690
|
+
try {
|
|
691
|
+
const parsed = JSON.parse(line);
|
|
692
|
+
if (typeof parsed.applied === "boolean" || typeof parsed.version === "string") {
|
|
693
|
+
return { applied: parsed.applied ?? appliedFallback, version: parsed.version ?? "" };
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
// not the report line — keep scanning older lines.
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return { applied: appliedFallback, version: "" };
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Run a database/schema migration against a target as a deploy step, dispatching
|
|
704
|
+
* on the target transport (ECS one-off task, Lambda invoke, or SSM host command)
|
|
705
|
+
* through the injectable `CloudExecutor`. A non-clean outcome (non-zero exit, a
|
|
706
|
+
* Lambda `FunctionError`) throws so the phase fails rather than proceeding on an
|
|
707
|
+
* unmigrated schema.
|
|
708
|
+
*/
|
|
709
|
+
export function createRunMigrationCapability(executor = defaultCloudExecutor()) {
|
|
710
|
+
return {
|
|
711
|
+
kind: "run-migration",
|
|
712
|
+
rollbackPolicy: "needs-opt-out",
|
|
713
|
+
async run(_ctx, input) {
|
|
714
|
+
const target = input.target;
|
|
715
|
+
switch (target.via) {
|
|
716
|
+
case "ecs-task": {
|
|
717
|
+
const { taskArn } = await executor.ecs.runTask({
|
|
718
|
+
cluster: target.cluster,
|
|
719
|
+
taskDefinition: target.taskDefinition,
|
|
720
|
+
container: target.container,
|
|
721
|
+
command: target.command,
|
|
722
|
+
launchType: target.launchType,
|
|
723
|
+
subnets: target.subnets,
|
|
724
|
+
securityGroups: target.securityGroups,
|
|
725
|
+
assignPublicIp: target.assignPublicIp,
|
|
726
|
+
});
|
|
727
|
+
const result = await executor.ecs.waitForTask(target.cluster, taskArn);
|
|
728
|
+
if (result.exitCode !== 0) {
|
|
729
|
+
throw new Error(`run-migration (ecs-task ${target.cluster}): task ${result.lastStatus} with exit ${result.exitCode ?? "none"}${result.stoppedReason ? ` (${result.stoppedReason})` : ""}`);
|
|
730
|
+
}
|
|
731
|
+
return { applied: true, version: "" };
|
|
732
|
+
}
|
|
733
|
+
case "lambda": {
|
|
734
|
+
const res = await executor.lambda.invoke({ functionName: target.function, payload: target.payload });
|
|
735
|
+
if (res.functionError) {
|
|
736
|
+
throw new Error(`run-migration (lambda ${target.function}): ${res.functionError} — ${res.payload.trim()}`);
|
|
737
|
+
}
|
|
738
|
+
return parseMigrationReport(res.payload, true);
|
|
739
|
+
}
|
|
740
|
+
case "host": {
|
|
741
|
+
const { stdout, exitCode } = await executor.host.exec({ host: target.host, command: target.command, cwd: target.cwd });
|
|
742
|
+
if (exitCode !== 0) {
|
|
743
|
+
throw new Error(`run-migration (host ${target.host}): command exited ${exitCode}`);
|
|
744
|
+
}
|
|
745
|
+
return parseMigrationReport(stdout, true);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
/** Default `run-migration` capability, backed by the real `CloudExecutor`. */
|
|
752
|
+
export const runMigrationCapability = createRunMigrationCapability();
|