@intentius/chant-lexicon-gcp 0.41.20 → 0.44.1
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/composites/index.d.ts +2 -0
- package/dist/composites/index.d.ts.map +1 -1
- package/dist/composites/landing-zone.d.ts +67 -0
- package/dist/composites/landing-zone.d.ts.map +1 -0
- package/dist/deep-observe-hooks.d.ts +16 -3
- package/dist/deep-observe-hooks.d.ts.map +1 -1
- package/dist/deep-observe.d.ts +68 -139
- package/dist/deep-observe.d.ts.map +1 -1
- package/dist/describe-resources.d.ts +27 -3
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/governance.d.ts +101 -0
- package/dist/governance.d.ts.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +5 -2
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/wgc501.d.ts +18 -0
- package/dist/lint/post-synth/wgc501.d.ts.map +1 -0
- package/dist/lint/post-synth/wgc502.d.ts +10 -0
- package/dist/lint/post-synth/wgc502.d.ts.map +1 -0
- package/dist/lint/post-synth/wgc503.d.ts +11 -0
- package/dist/lint/post-synth/wgc503.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +519 -0
- package/dist/okf/rules/WGC001.md +15 -0
- package/dist/okf/rules/WGC002.md +16 -0
- package/dist/okf/rules/WGC003.md +15 -0
- package/dist/okf/rules/WGC101.md +16 -0
- package/dist/okf/rules/WGC102.md +15 -0
- package/dist/okf/rules/WGC103.md +15 -0
- package/dist/okf/rules/WGC104.md +15 -0
- package/dist/okf/rules/WGC105.md +15 -0
- package/dist/okf/rules/WGC106.md +15 -0
- package/dist/okf/rules/WGC107.md +15 -0
- package/dist/okf/rules/WGC108.md +15 -0
- package/dist/okf/rules/WGC109.md +11 -0
- package/dist/okf/rules/WGC110.md +15 -0
- package/dist/okf/rules/WGC111.md +15 -0
- package/dist/okf/rules/WGC112.md +15 -0
- package/dist/okf/rules/WGC113.md +16 -0
- package/dist/okf/rules/WGC201.md +15 -0
- package/dist/okf/rules/WGC202.md +11 -0
- package/dist/okf/rules/WGC203.md +11 -0
- package/dist/okf/rules/WGC204.md +15 -0
- package/dist/okf/rules/WGC301.md +15 -0
- package/dist/okf/rules/WGC302.md +15 -0
- package/dist/okf/rules/WGC303.md +15 -0
- package/dist/okf/rules/WGC401.md +15 -0
- package/dist/okf/rules/WGC402.md +15 -0
- package/dist/okf/rules/WGC403.md +15 -0
- package/dist/okf/rules/WGC501.md +15 -0
- package/dist/okf/rules/WGC502.md +11 -0
- package/dist/okf/rules/WGC503.md +15 -0
- package/dist/okf/types/API.md +21 -0
- package/dist/okf/types/APIConfig.md +15 -0
- package/dist/okf/types/APIQuotaAdjusterSettings.md +15 -0
- package/dist/okf/types/APIQuotaPreference.md +15 -0
- package/dist/okf/types/AccessBoundaryPolicy.md +15 -0
- package/dist/okf/types/AccessLevel.md +15 -0
- package/dist/okf/types/AccessLevelCondition.md +15 -0
- package/dist/okf/types/AccessPolicy.md +15 -0
- package/dist/okf/types/AddonsConfig.md +15 -0
- package/dist/okf/types/AgentPool.md +15 -0
- package/dist/okf/types/AiplatformModel.md +15 -0
- package/dist/okf/types/AistreamsCluster.md +15 -0
- package/dist/okf/types/AlertPolicy.md +15 -0
- package/dist/okf/types/AlloydbBackup.md +15 -0
- package/dist/okf/types/AlloydbCluster.md +15 -0
- package/dist/okf/types/AlloydbInstance.md +15 -0
- package/dist/okf/types/AnalyticsAccount.md +15 -0
- package/dist/okf/types/AndroidApp.md +15 -0
- package/dist/okf/types/AnywhereCache.md +15 -0
- package/dist/okf/types/ApigatewayGateway.md +15 -0
- package/dist/okf/types/ApigeeEnvironment.md +15 -0
- package/dist/okf/types/ApigeeInstance.md +15 -0
- package/dist/okf/types/ApihubDeployment.md +15 -0
- package/dist/okf/types/ApikeysKey.md +15 -0
- package/dist/okf/types/AppConnection.md +15 -0
- package/dist/okf/types/AppConnector.md +15 -0
- package/dist/okf/types/AppGateway.md +15 -0
- package/dist/okf/types/AppProfile.md +15 -0
- package/dist/okf/types/Application.md +15 -0
- package/dist/okf/types/ArtifactRegistryRepository.md +15 -0
- package/dist/okf/types/Assignment.md +15 -0
- package/dist/okf/types/Attestor.md +15 -0
- package/dist/okf/types/AuthorizationPolicy.md +15 -0
- package/dist/okf/types/AuthorizedView.md +15 -0
- package/dist/okf/types/AutokeyConfig.md +15 -0
- package/dist/okf/types/Automation.md +15 -0
- package/dist/okf/types/Autoscaler.md +15 -0
- package/dist/okf/types/AutoscalingPolicy.md +15 -0
- package/dist/okf/types/BQJob.md +15 -0
- package/dist/okf/types/BackendBucket.md +15 -0
- package/dist/okf/types/BackendBucketSignedURLKey.md +15 -0
- package/dist/okf/types/BackendService.md +15 -0
- package/dist/okf/types/BackendServiceSignedURLKey.md +15 -0
- package/dist/okf/types/BackupPlanAssociation.md +15 -0
- package/dist/okf/types/BackupPolicy.md +15 -0
- package/dist/okf/types/BackupdrBackupPlan.md +15 -0
- package/dist/okf/types/BackupdrBackupVault.md +15 -0
- package/dist/okf/types/Batch.md +15 -0
- package/dist/okf/types/BatchJob.md +15 -0
- package/dist/okf/types/BatchTask.md +15 -0
- package/dist/okf/types/BigLakeCatalog.md +15 -0
- package/dist/okf/types/BigLakeDatabase.md +15 -0
- package/dist/okf/types/BigLakeTable.md +15 -0
- package/dist/okf/types/BigQueryDataPolicy.md +15 -0
- package/dist/okf/types/BigQueryDataset.md +15 -0
- package/dist/okf/types/BigQueryExport.md +15 -0
- package/dist/okf/types/BigQueryTable.md +15 -0
- package/dist/okf/types/BigqueryconnectionConnection.md +15 -0
- package/dist/okf/types/BigquerydatatransferConfig.md +15 -0
- package/dist/okf/types/BigqueryreservationReservation.md +15 -0
- package/dist/okf/types/BigtableInstance.md +15 -0
- package/dist/okf/types/BigtableTable.md +15 -0
- package/dist/okf/types/BillingAccount.md +15 -0
- package/dist/okf/types/BinaryauthorizationPolicy.md +15 -0
- package/dist/okf/types/Brand.md +15 -0
- package/dist/okf/types/BtBackup.md +15 -0
- package/dist/okf/types/BtCluster.md +15 -0
- package/dist/okf/types/Bucket.md +15 -0
- package/dist/okf/types/BucketAccessControl.md +15 -0
- package/dist/okf/types/Budget.md +15 -0
- package/dist/okf/types/CAPool.md +15 -0
- package/dist/okf/types/CapacityCommitment.md +15 -0
- package/dist/okf/types/CertificateAuthority.md +15 -0
- package/dist/okf/types/CertificateIssuanceConfig.md +15 -0
- package/dist/okf/types/CertificateMap.md +15 -0
- package/dist/okf/types/CertificateMapEntry.md +15 -0
- package/dist/okf/types/CertificateTemplate.md +15 -0
- package/dist/okf/types/CertificatemanagerCertificate.md +15 -0
- package/dist/okf/types/ClientConnectorService.md +15 -0
- package/dist/okf/types/ClientTLSPolicy.md +15 -0
- package/dist/okf/types/CloudBatchResourceAllowance.md +15 -0
- package/dist/okf/types/CloudFunction.md +15 -0
- package/dist/okf/types/CloudRunJob.md +15 -0
- package/dist/okf/types/CloudRunService.md +15 -0
- package/dist/okf/types/CloudbuildTrigger.md +15 -0
- package/dist/okf/types/ClouddmsPrivateConnection.md +15 -0
- package/dist/okf/types/CloudidentityGroup.md +15 -0
- package/dist/okf/types/CloudidentityMembership.md +15 -0
- package/dist/okf/types/CloudidsEndpoint.md +15 -0
- package/dist/okf/types/CloudschedulerJob.md +15 -0
- package/dist/okf/types/ComposerEnvironment.md +15 -0
- package/dist/okf/types/ComputeAddress.md +15 -0
- package/dist/okf/types/ComputeDisk.md +15 -0
- package/dist/okf/types/ComputeFirewallPolicy.md +15 -0
- package/dist/okf/types/ComputeHealthCheck.md +15 -0
- package/dist/okf/types/ComputeInstance.md +19 -0
- package/dist/okf/types/ComputeNetworkPeering.md +15 -0
- package/dist/okf/types/ComputeNodeGroup.md +15 -0
- package/dist/okf/types/ComputeReservation.md +15 -0
- package/dist/okf/types/ComputeRoute.md +15 -0
- package/dist/okf/types/ComputeSnapshot.md +15 -0
- package/dist/okf/types/ConfigcontrollerInstance.md +15 -0
- package/dist/okf/types/ConnectionProfile.md +15 -0
- package/dist/okf/types/ConnectivityTest.md +15 -0
- package/dist/okf/types/Connector.md +28 -0
- package/dist/okf/types/ConsentStore.md +15 -0
- package/dist/okf/types/ConsumerGroup.md +15 -0
- package/dist/okf/types/ConsumerQuotaOverride.md +15 -0
- package/dist/okf/types/Contact.md +15 -0
- package/dist/okf/types/ContainerattachedCluster.md +15 -0
- package/dist/okf/types/ConversionWorkspace.md +15 -0
- package/dist/okf/types/CryptoKeyVersion.md +15 -0
- package/dist/okf/types/CustomClass.md +15 -0
- package/dist/okf/types/CustomConstraint.md +15 -0
- package/dist/okf/types/CustomRole.md +15 -0
- package/dist/okf/types/CustomTargetType.md +15 -0
- package/dist/okf/types/DICOMStore.md +15 -0
- package/dist/okf/types/DNSAuthorization.md +15 -0
- package/dist/okf/types/DNSManagedZone.md +15 -0
- package/dist/okf/types/DNSRecordSet.md +15 -0
- package/dist/okf/types/Dashboard.md +15 -0
- package/dist/okf/types/DataExchange.md +15 -0
- package/dist/okf/types/DataLabelingJob.md +15 -0
- package/dist/okf/types/DataPolicy.md +15 -0
- package/dist/okf/types/DataStore.md +15 -0
- package/dist/okf/types/DataStoreTargetSite.md +15 -0
- package/dist/okf/types/Database.md +15 -0
- package/dist/okf/types/DatacatalogEntryGroup.md +15 -0
- package/dist/okf/types/DataflowJob.md +15 -0
- package/dist/okf/types/DataformRepository.md +15 -0
- package/dist/okf/types/DatafusionInstance.md +15 -0
- package/dist/okf/types/DataplexEntryGroup.md +15 -0
- package/dist/okf/types/DataplexTask.md +15 -0
- package/dist/okf/types/DataprocCluster.md +15 -0
- package/dist/okf/types/DataprocJob.md +15 -0
- package/dist/okf/types/DataprocNodeGroup.md +15 -0
- package/dist/okf/types/DataprocSession.md +15 -0
- package/dist/okf/types/DatasetAccess.md +15 -0
- package/dist/okf/types/DatastoreIndex.md +15 -0
- package/dist/okf/types/DatastreamPrivateConnection.md +15 -0
- package/dist/okf/types/DatastreamRoute.md +15 -0
- package/dist/okf/types/DefaultObjectAccessControl.md +15 -0
- package/dist/okf/types/DefaultSupportedIDPConfig.md +15 -0
- package/dist/okf/types/DeidentifyTemplate.md +15 -0
- package/dist/okf/types/DeliveryPipeline.md +15 -0
- package/dist/okf/types/DenyPolicy.md +15 -0
- package/dist/okf/types/DeployPolicy.md +15 -0
- package/dist/okf/types/DeploymentResourcePool.md +15 -0
- package/dist/okf/types/DeploymentmanagerDeployment.md +15 -0
- package/dist/okf/types/Device.md +15 -0
- package/dist/okf/types/DeviceRegistry.md +15 -0
- package/dist/okf/types/DevicestreamingSession.md +15 -0
- package/dist/okf/types/DialogflowAgent.md +15 -0
- package/dist/okf/types/DialogflowEntityType.md +15 -0
- package/dist/okf/types/DialogflowIntent.md +15 -0
- package/dist/okf/types/DialogflowcxAgent.md +15 -0
- package/dist/okf/types/DialogflowcxEntityType.md +15 -0
- package/dist/okf/types/DialogflowcxIntent.md +15 -0
- package/dist/okf/types/DiscoveredService.md +15 -0
- package/dist/okf/types/DiscoveredWorkload.md +15 -0
- package/dist/okf/types/DiskResourcePolicyAttachment.md +15 -0
- package/dist/okf/types/DnsPolicy.md +15 -0
- package/dist/okf/types/Document.md +15 -0
- package/dist/okf/types/DomainMapping.md +15 -0
- package/dist/okf/types/EdgeCacheKeyset.md +15 -0
- package/dist/okf/types/EdgeCacheOrigin.md +15 -0
- package/dist/okf/types/EdgeCacheService.md +15 -0
- package/dist/okf/types/EdgecontainerCluster.md +15 -0
- package/dist/okf/types/EdgecontainerNodePool.md +15 -0
- package/dist/okf/types/EdgenetworkNetwork.md +15 -0
- package/dist/okf/types/EndpointAttachment.md +15 -0
- package/dist/okf/types/EndpointPolicy.md +15 -0
- package/dist/okf/types/Engine.md +15 -0
- package/dist/okf/types/Enrollment.md +15 -0
- package/dist/okf/types/Entitlement.md +15 -0
- package/dist/okf/types/Entry.md +15 -0
- package/dist/okf/types/EntryType.md +15 -0
- package/dist/okf/types/Envgroup.md +15 -0
- package/dist/okf/types/EnvgroupAttachment.md +15 -0
- package/dist/okf/types/EventarcChannel.md +15 -0
- package/dist/okf/types/EventarcTrigger.md +15 -0
- package/dist/okf/types/ExampleStore.md +15 -0
- package/dist/okf/types/ExternalAccessRule.md +15 -0
- package/dist/okf/types/ExternalAddress.md +15 -0
- package/dist/okf/types/ExternalVPNGateway.md +15 -0
- package/dist/okf/types/FHIRStore.md +15 -0
- package/dist/okf/types/Feature.md +15 -0
- package/dist/okf/types/FeatureMembership.md +15 -0
- package/dist/okf/types/Featurestore.md +15 -0
- package/dist/okf/types/FeaturestoreEntityType.md +15 -0
- package/dist/okf/types/FeaturestoreEntityTypeFeature.md +15 -0
- package/dist/okf/types/Federation.md +15 -0
- package/dist/okf/types/Feed.md +15 -0
- package/dist/okf/types/Field.md +15 -0
- package/dist/okf/types/FilestoreBackup.md +15 -0
- package/dist/okf/types/FilestoreInstance.md +15 -0
- package/dist/okf/types/FilestoreSnapshot.md +15 -0
- package/dist/okf/types/FirebaseProject.md +15 -0
- package/dist/okf/types/FirebasedatabaseInstance.md +15 -0
- package/dist/okf/types/FirebasehostingChannel.md +15 -0
- package/dist/okf/types/FirestoreBackupSchedule.md +15 -0
- package/dist/okf/types/FirestoreIndex.md +15 -0
- package/dist/okf/types/Firewall.md +15 -0
- package/dist/okf/types/FirewallPolicyAssociation.md +15 -0
- package/dist/okf/types/FirewallPolicyRule.md +15 -0
- package/dist/okf/types/FirewallRule.md +15 -0
- package/dist/okf/types/FlexTemplateJob.md +15 -0
- package/dist/okf/types/FlexibleAppVersion.md +15 -0
- package/dist/okf/types/Flow.md +15 -0
- package/dist/okf/types/FolderFeed.md +15 -0
- package/dist/okf/types/ForwardingRule.md +15 -0
- package/dist/okf/types/Fulfillment.md +15 -0
- package/dist/okf/types/Function.md +15 -0
- package/dist/okf/types/FutureReservation.md +15 -0
- package/dist/okf/types/GCPServiceAccount.md +15 -0
- package/dist/okf/types/GCPUserAccessBinding.md +15 -0
- package/dist/okf/types/GCPolicy.md +15 -0
- package/dist/okf/types/GKECluster.md +15 -0
- package/dist/okf/types/GRPCRoute.md +15 -0
- package/dist/okf/types/GcsFolder.md +15 -0
- package/dist/okf/types/GkebackupBackup.md +15 -0
- package/dist/okf/types/GkebackupBackupPlan.md +15 -0
- package/dist/okf/types/GkehubMembership.md +15 -0
- package/dist/okf/types/GkehubNamespace.md +15 -0
- package/dist/okf/types/GlobalNetworkEndpoint.md +15 -0
- package/dist/okf/types/GlobalNetworkEndpointGroup.md +15 -0
- package/dist/okf/types/GoogleChannelConfig.md +15 -0
- package/dist/okf/types/GuestPolicy.md +15 -0
- package/dist/okf/types/HL7V2Store.md +15 -0
- package/dist/okf/types/HMACKey.md +15 -0
- package/dist/okf/types/HTTPHealthCheck.md +15 -0
- package/dist/okf/types/HTTPRoute.md +15 -0
- package/dist/okf/types/HTTPSHealthCheck.md +15 -0
- package/dist/okf/types/HealthcareDataset.md +15 -0
- package/dist/okf/types/Hub.md +15 -0
- package/dist/okf/types/IAMAuditConfig.md +20 -0
- package/dist/okf/types/IAMPolicy.md +15 -0
- package/dist/okf/types/IAMPolicyMember.md +15 -0
- package/dist/okf/types/IdentityAwareProxyClient.md +15 -0
- package/dist/okf/types/IdentityplatformConfig.md +15 -0
- package/dist/okf/types/Image.md +15 -0
- package/dist/okf/types/ImportJob.md +15 -0
- package/dist/okf/types/InboundSAMLConfig.md +15 -0
- package/dist/okf/types/IndexEndpoint.md +15 -0
- package/dist/okf/types/InspectTemplate.md +15 -0
- package/dist/okf/types/InstanceAttachment.md +15 -0
- package/dist/okf/types/InstanceConfig.md +15 -0
- package/dist/okf/types/InstanceEndpoint.md +15 -0
- package/dist/okf/types/InstanceGroup.md +15 -0
- package/dist/okf/types/InstanceGroupManager.md +15 -0
- package/dist/okf/types/InstanceGroupNamedPort.md +15 -0
- package/dist/okf/types/InstanceTemplate.md +15 -0
- package/dist/okf/types/InterceptDeployment.md +15 -0
- package/dist/okf/types/InterceptEndpointGroup.md +15 -0
- package/dist/okf/types/Interconnect.md +15 -0
- package/dist/okf/types/InterconnectAttachment.md +15 -0
- package/dist/okf/types/InternalRange.md +15 -0
- package/dist/okf/types/JobTrigger.md +15 -0
- package/dist/okf/types/KMSCryptoKey.md +19 -0
- package/dist/okf/types/KMSKeyRing.md +15 -0
- package/dist/okf/types/KeyHandle.md +15 -0
- package/dist/okf/types/KeyRingImportJob.md +15 -0
- package/dist/okf/types/LBRouteExtension.md +15 -0
- package/dist/okf/types/Lake.md +15 -0
- package/dist/okf/types/Lien.md +15 -0
- package/dist/okf/types/Link.md +15 -0
- package/dist/okf/types/Listing.md +15 -0
- package/dist/okf/types/LocationTagBinding.md +15 -0
- package/dist/okf/types/LogBucket.md +15 -0
- package/dist/okf/types/LogExclusion.md +15 -0
- package/dist/okf/types/LogMetric.md +15 -0
- package/dist/okf/types/LogSink.md +15 -0
- package/dist/okf/types/LogView.md +15 -0
- package/dist/okf/types/LogicalView.md +15 -0
- package/dist/okf/types/Machine.md +15 -0
- package/dist/okf/types/MachineImage.md +15 -0
- package/dist/okf/types/ManagedFolder.md +15 -0
- package/dist/okf/types/ManagedSSLCertificate.md +15 -0
- package/dist/okf/types/ManagedkafkaCluster.md +15 -0
- package/dist/okf/types/ManagedkafkaTopic.md +15 -0
- package/dist/okf/types/ManagementServer.md +15 -0
- package/dist/okf/types/MaterializedView.md +15 -0
- package/dist/okf/types/MembershipBinding.md +15 -0
- package/dist/okf/types/MemcacheInstance.md +15 -0
- package/dist/okf/types/MemorystoreInstance.md +15 -0
- package/dist/okf/types/Mesh.md +15 -0
- package/dist/okf/types/MetadataStore.md +15 -0
- package/dist/okf/types/MetastoreBackup.md +15 -0
- package/dist/okf/types/MetastoreService.md +15 -0
- package/dist/okf/types/MetricDescriptor.md +15 -0
- package/dist/okf/types/MigrationJob.md +15 -0
- package/dist/okf/types/MlengineModel.md +15 -0
- package/dist/okf/types/MonGroup.md +15 -0
- package/dist/okf/types/MonService.md +15 -0
- package/dist/okf/types/MonitoredProject.md +15 -0
- package/dist/okf/types/MuteConfig.md +15 -0
- package/dist/okf/types/NATAddress.md +15 -0
- package/dist/okf/types/NetappBackupVault.md +15 -0
- package/dist/okf/types/NetworkAttachment.md +15 -0
- package/dist/okf/types/NetworkEdgeSecurityService.md +15 -0
- package/dist/okf/types/NetworkEndpoint.md +15 -0
- package/dist/okf/types/NetworkEndpointGroup.md +15 -0
- package/dist/okf/types/NetworkFirewallPolicy.md +15 -0
- package/dist/okf/types/NetworkFirewallPolicyAssociation.md +15 -0
- package/dist/okf/types/NetworkFirewallPolicyRule.md +15 -0
- package/dist/okf/types/NetworkPeeringRoutesConfig.md +15 -0
- package/dist/okf/types/NetworkPolicy.md +15 -0
- package/dist/okf/types/NetworkservicesGateway.md +15 -0
- package/dist/okf/types/Node.md +21 -0
- package/dist/okf/types/NodePool.md +15 -0
- package/dist/okf/types/NodeTemplate.md +15 -0
- package/dist/okf/types/Note.md +15 -0
- package/dist/okf/types/NotebookInstance.md +15 -0
- package/dist/okf/types/NotebooksEnvironment.md +15 -0
- package/dist/okf/types/Notification.md +15 -0
- package/dist/okf/types/NotificationChannel.md +15 -0
- package/dist/okf/types/NotificationConfig.md +15 -0
- package/dist/okf/types/OAuthIDPConfig.md +15 -0
- package/dist/okf/types/OSPolicyAssignment.md +15 -0
- package/dist/okf/types/Occurrence.md +15 -0
- package/dist/okf/types/Organization.md +15 -0
- package/dist/okf/types/OrganizationFeed.md +15 -0
- package/dist/okf/types/OrganizationSecurityPolicy.md +15 -0
- package/dist/okf/types/OrganizationSecurityPolicyAssociation.md +15 -0
- package/dist/okf/types/OrganizationSecurityPolicyRule.md +15 -0
- package/dist/okf/types/OrgpolicyPolicy.md +15 -0
- package/dist/okf/types/PacketMirroring.md +15 -0
- package/dist/okf/types/Page.md +15 -0
- package/dist/okf/types/Parameter.md +15 -0
- package/dist/okf/types/ParameterVersion.md +15 -0
- package/dist/okf/types/PartialPolicy.md +15 -0
- package/dist/okf/types/PatchDeployment.md +15 -0
- package/dist/okf/types/PeeredDNSDomain.md +15 -0
- package/dist/okf/types/PerInstanceConfig.md +15 -0
- package/dist/okf/types/PhraseSet.md +15 -0
- package/dist/okf/types/PlatformPolicy.md +15 -0
- package/dist/okf/types/PolicyTag.md +15 -0
- package/dist/okf/types/PrivateCloud.md +15 -0
- package/dist/okf/types/PrivatecaCertificate.md +15 -0
- package/dist/okf/types/Processor.md +15 -0
- package/dist/okf/types/ProcessorDefaultVersion.md +15 -0
- package/dist/okf/types/ProcessorVersion.md +15 -0
- package/dist/okf/types/ProjectDefaultConfig.md +15 -0
- package/dist/okf/types/ProjectFeed.md +15 -0
- package/dist/okf/types/ProjectMetadata.md +15 -0
- package/dist/okf/types/PubSubSnapshot.md +15 -0
- package/dist/okf/types/PubSubSubscription.md +15 -0
- package/dist/okf/types/PubSubTopic.md +15 -0
- package/dist/okf/types/PubsubliteReservation.md +15 -0
- package/dist/okf/types/PubsubliteTopic.md +15 -0
- package/dist/okf/types/RecaptchaenterpriseFirewallPolicy.md +15 -0
- package/dist/okf/types/RecaptchaenterpriseKey.md +15 -0
- package/dist/okf/types/Recognizer.md +15 -0
- package/dist/okf/types/RedisCluster.md +15 -0
- package/dist/okf/types/RedisInstance.md +15 -0
- package/dist/okf/types/RegionAutoscaler.md +15 -0
- package/dist/okf/types/RegionDiskResourcePolicyAttachment.md +15 -0
- package/dist/okf/types/RegionNetworkEndpointGroup.md +15 -0
- package/dist/okf/types/RegionPerInstanceConfig.md +15 -0
- package/dist/okf/types/RegionSSLPolicy.md +15 -0
- package/dist/okf/types/RegionalEndpoint.md +15 -0
- package/dist/okf/types/ResourceBundle.md +15 -0
- package/dist/okf/types/ResourcePolicy.md +15 -0
- package/dist/okf/types/ResourcemanagerFolder.md +15 -0
- package/dist/okf/types/ResourcemanagerPolicy.md +15 -0
- package/dist/okf/types/ResourcemanagerProject.md +15 -0
- package/dist/okf/types/ResponsePolicy.md +15 -0
- package/dist/okf/types/ResponsePolicyRule.md +15 -0
- package/dist/okf/types/Restore.md +15 -0
- package/dist/okf/types/RestorePlan.md +15 -0
- package/dist/okf/types/Router.md +15 -0
- package/dist/okf/types/RouterInterface.md +15 -0
- package/dist/okf/types/RouterNAT.md +15 -0
- package/dist/okf/types/RouterPeer.md +15 -0
- package/dist/okf/types/Routine.md +15 -0
- package/dist/okf/types/Runtime.md +15 -0
- package/dist/okf/types/RuntimeTemplate.md +15 -0
- package/dist/okf/types/SQLDatabase.md +15 -0
- package/dist/okf/types/SQLInstance.md +21 -0
- package/dist/okf/types/SQLUser.md +15 -0
- package/dist/okf/types/SSHPublicKey.md +15 -0
- package/dist/okf/types/SSLCert.md +15 -0
- package/dist/okf/types/SSLCertificate.md +15 -0
- package/dist/okf/types/SSLPolicy.md +15 -0
- package/dist/okf/types/SavedQuery.md +15 -0
- package/dist/okf/types/Schema.md +15 -0
- package/dist/okf/types/Scope.md +15 -0
- package/dist/okf/types/ScopeRBACRoleBinding.md +15 -0
- package/dist/okf/types/SecretCiphertext.md +15 -0
- package/dist/okf/types/SecretManagerSecret.md +15 -0
- package/dist/okf/types/SecretManagerSecretVersion.md +15 -0
- package/dist/okf/types/SecuresourcemanagerInstance.md +15 -0
- package/dist/okf/types/SecuresourcemanagerRepository.md +15 -0
- package/dist/okf/types/SecurityPolicy.md +15 -0
- package/dist/okf/types/ServerTLSPolicy.md +15 -0
- package/dist/okf/types/ServiceAccountKey.md +15 -0
- package/dist/okf/types/ServiceAttachment.md +15 -0
- package/dist/okf/types/ServiceBinding.md +15 -0
- package/dist/okf/types/ServiceConnectionPolicy.md +15 -0
- package/dist/okf/types/ServiceIdentity.md +15 -0
- package/dist/okf/types/ServiceLevelObjective.md +15 -0
- package/dist/okf/types/ServicePerimeter.md +19 -0
- package/dist/okf/types/ServicePerimeterResource.md +15 -0
- package/dist/okf/types/ServiceProjectAttachment.md +15 -0
- package/dist/okf/types/ServiceSplitTraffic.md +15 -0
- package/dist/okf/types/ServicedirectoryEndpoint.md +15 -0
- package/dist/okf/types/ServicedirectoryNamespace.md +15 -0
- package/dist/okf/types/ServicedirectoryService.md +15 -0
- package/dist/okf/types/ServicenetworkingConnection.md +15 -0
- package/dist/okf/types/ServiceusageService.md +15 -0
- package/dist/okf/types/Settings.md +15 -0
- package/dist/okf/types/SharedVPCHostProject.md +15 -0
- package/dist/okf/types/SharedVPCServiceProject.md +15 -0
- package/dist/okf/types/Site.md +15 -0
- package/dist/okf/types/Source.md +15 -0
- package/dist/okf/types/SourcerepoRepository.md +15 -0
- package/dist/okf/types/SpannerBackupSchedule.md +15 -0
- package/dist/okf/types/SpannerDatabase.md +15 -0
- package/dist/okf/types/SpannerInstance.md +15 -0
- package/dist/okf/types/Spoke.md +15 -0
- package/dist/okf/types/StandardAppVersion.md +15 -0
- package/dist/okf/types/StorageBucket.md +21 -0
- package/dist/okf/types/StoragetransferJob.md +15 -0
- package/dist/okf/types/StoredInfoType.md +15 -0
- package/dist/okf/types/Stream.md +15 -0
- package/dist/okf/types/Subnet.md +15 -0
- package/dist/okf/types/Subnetwork.md +15 -0
- package/dist/okf/types/Subscription.md +15 -0
- package/dist/okf/types/SyncAuthorization.md +15 -0
- package/dist/okf/types/TCPRoute.md +15 -0
- package/dist/okf/types/TLSRoute.md +15 -0
- package/dist/okf/types/Tag.md +15 -0
- package/dist/okf/types/TagBinding.md +15 -0
- package/dist/okf/types/TagKey.md +15 -0
- package/dist/okf/types/TagTemplate.md +15 -0
- package/dist/okf/types/TagValue.md +15 -0
- package/dist/okf/types/Target.md +15 -0
- package/dist/okf/types/TargetGRPCProxy.md +15 -0
- package/dist/okf/types/TargetHTTPProxy.md +15 -0
- package/dist/okf/types/TargetHTTPSProxy.md +15 -0
- package/dist/okf/types/TargetInstance.md +15 -0
- package/dist/okf/types/TargetPool.md +15 -0
- package/dist/okf/types/TargetSSLProxy.md +15 -0
- package/dist/okf/types/TargetTCPProxy.md +15 -0
- package/dist/okf/types/TargetVPNGateway.md +15 -0
- package/dist/okf/types/TasksQueue.md +15 -0
- package/dist/okf/types/Taxonomy.md +15 -0
- package/dist/okf/types/Tenant.md +15 -0
- package/dist/okf/types/TenantDefaultSupportedIDPConfig.md +15 -0
- package/dist/okf/types/TenantInboundSAMLConfig.md +15 -0
- package/dist/okf/types/TenantOAuthIDPConfig.md +15 -0
- package/dist/okf/types/Tensorboard.md +15 -0
- package/dist/okf/types/URLMap.md +15 -0
- package/dist/okf/types/UptimeCheckConfig.md +15 -0
- package/dist/okf/types/User.md +15 -0
- package/dist/okf/types/VPCNetwork.md +15 -0
- package/dist/okf/types/VPNGateway.md +15 -0
- package/dist/okf/types/VPNTunnel.md +15 -0
- package/dist/okf/types/VertexaiDataset.md +15 -0
- package/dist/okf/types/VertexaiEndpoint.md +15 -0
- package/dist/okf/types/VertexaiIndex.md +15 -0
- package/dist/okf/types/VirtualMachine.md +15 -0
- package/dist/okf/types/VmwareengineNetwork.md +15 -0
- package/dist/okf/types/VmwareengineNetworkPeering.md +15 -0
- package/dist/okf/types/VpnConnection.md +15 -0
- package/dist/okf/types/WasmPlugin.md +15 -0
- package/dist/okf/types/WebApp.md +15 -0
- package/dist/okf/types/Webhook.md +15 -0
- package/dist/okf/types/WorkerPool.md +15 -0
- package/dist/okf/types/Workflow.md +15 -0
- package/dist/okf/types/WorkflowTemplate.md +15 -0
- package/dist/okf/types/WorkflowsExecution.md +15 -0
- package/dist/okf/types/WorkforcePool.md +15 -0
- package/dist/okf/types/WorkforcePoolProvider.md +15 -0
- package/dist/okf/types/Workload.md +15 -0
- package/dist/okf/types/WorkloadIdentityPool.md +15 -0
- package/dist/okf/types/WorkloadIdentityPoolProvider.md +15 -0
- package/dist/okf/types/Workstation.md +15 -0
- package/dist/okf/types/WorkstationCluster.md +15 -0
- package/dist/okf/types/WorkstationConfig.md +15 -0
- package/dist/okf/types/Zone.md +19 -0
- package/dist/rules/wgc501.ts +73 -0
- package/dist/rules/wgc502.ts +41 -0
- package/dist/rules/wgc503.ts +56 -0
- package/dist/serializer.d.ts +12 -0
- package/dist/serializer.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/composites/index.ts +6 -0
- package/src/composites/landing-zone.test.ts +67 -0
- package/src/composites/landing-zone.ts +173 -0
- package/src/deep-observe-hooks.ts +87 -21
- package/src/deep-observe.test.ts +158 -13
- package/src/deep-observe.ts +143 -142
- package/src/describe-resources.test.ts +46 -1
- package/src/describe-resources.ts +64 -13
- package/src/governance.test.ts +59 -0
- package/src/governance.ts +179 -0
- package/src/index.ts +10 -0
- package/src/lifecycle-integration.test.ts +3 -3
- package/src/lint/audit-catalog.ts +5 -0
- package/src/lint/post-synth/index.ts +6 -0
- package/src/lint/post-synth/wgc501.test.ts +87 -0
- package/src/lint/post-synth/wgc501.ts +73 -0
- package/src/lint/post-synth/wgc502.test.ts +64 -0
- package/src/lint/post-synth/wgc502.ts +41 -0
- package/src/lint/post-synth/wgc503.test.ts +77 -0
- package/src/lint/post-synth/wgc503.ts +56 -0
- package/src/plugin.test.ts +4 -1
- package/src/serializer.ts +6 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Landing-zone bootstrap composites (#791, epic #787 C1) — the GCP slice.
|
|
3
|
+
*
|
|
4
|
+
* Fixed-shape resources for greenfield creation of the recommended
|
|
5
|
+
* governance foundation through a Config Connector management stack. The
|
|
6
|
+
* evaluability rules (EVL002/004) require composites to declare a fixed set
|
|
7
|
+
* of resources, so these carry the foundation only; arbitrary folder/project
|
|
8
|
+
* trees are authored as config with `landingZoneConfig()` (../governance.ts)
|
|
9
|
+
* and reconciled by the GCP cloud warden, and further folders/projects are
|
|
10
|
+
* declared as ordinary resources.
|
|
11
|
+
*
|
|
12
|
+
* The pieces compose:
|
|
13
|
+
* const lz = GovernanceFoundation({ orgId: "123456789012" });
|
|
14
|
+
* const lr = LocationRestriction({ orgId: "123456789012", locations: ["in:eu-locations"] });
|
|
15
|
+
* const ac = OrganizationAuditConfig({ orgId: "123456789012" });
|
|
16
|
+
* There is no OrganizationRoot analog — GCP organizations come from Cloud
|
|
17
|
+
* Identity / Workspace, not the resource API, so greenfield and brownfield
|
|
18
|
+
* both start from an existing org id.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { Composite } from "@intentius/chant";
|
|
22
|
+
import { IAMAuditConfig, OrgpolicyPolicy, ResourcemanagerFolder } from "../generated";
|
|
23
|
+
import {
|
|
24
|
+
AUDIT_ALL_SERVICES,
|
|
25
|
+
DISABLE_SA_KEY_CREATION,
|
|
26
|
+
SKIP_DEFAULT_NETWORK,
|
|
27
|
+
resourceLocationRestriction,
|
|
28
|
+
type OrgPolicyConfig,
|
|
29
|
+
} from "../governance";
|
|
30
|
+
|
|
31
|
+
/** "organizations/123" and "123" are both accepted everywhere an org id appears. */
|
|
32
|
+
function orgNumber(orgId: string): string {
|
|
33
|
+
return orgId.replace(/^organizations\//, "");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function labels(component: string): Record<string, string> {
|
|
37
|
+
return {
|
|
38
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
39
|
+
"app.kubernetes.io/component": component,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function orgPolicy(
|
|
44
|
+
metaName: string,
|
|
45
|
+
policy: OrgPolicyConfig,
|
|
46
|
+
orgId: string,
|
|
47
|
+
namespace?: string,
|
|
48
|
+
): InstanceType<typeof OrgpolicyPolicy> {
|
|
49
|
+
return new OrgpolicyPolicy({
|
|
50
|
+
metadata: {
|
|
51
|
+
name: metaName,
|
|
52
|
+
...(namespace && { namespace }),
|
|
53
|
+
labels: labels("org-policy"),
|
|
54
|
+
},
|
|
55
|
+
resourceID: policy.constraint,
|
|
56
|
+
organizationRef: { external: orgNumber(orgId) },
|
|
57
|
+
spec: { rules: policy.rules },
|
|
58
|
+
} as Record<string, unknown>);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// GovernanceFoundation — the recommended foundation
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
export interface GovernanceFoundationProps {
|
|
66
|
+
/** The organization to hang the foundation off ("123456789012" or "organizations/123456789012"). */
|
|
67
|
+
orgId: string;
|
|
68
|
+
/** Namespace for all resources. */
|
|
69
|
+
namespace?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type GovernanceFoundationResult = {
|
|
73
|
+
folderSecurity: InstanceType<typeof ResourcemanagerFolder>;
|
|
74
|
+
folderInfrastructure: InstanceType<typeof ResourcemanagerFolder>;
|
|
75
|
+
folderSandbox: InstanceType<typeof ResourcemanagerFolder>;
|
|
76
|
+
folderWorkloads: InstanceType<typeof ResourcemanagerFolder>;
|
|
77
|
+
policyDisableSaKeyCreation: InstanceType<typeof OrgpolicyPolicy>;
|
|
78
|
+
policySkipDefaultNetwork: InstanceType<typeof OrgpolicyPolicy>;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Security / Infrastructure / Sandbox / Workloads folders with
|
|
83
|
+
* `disable-sa-key-creation` and `skip-default-network` enforced at the
|
|
84
|
+
* organization root — the same baseline `landingZoneConfig()` declares.
|
|
85
|
+
*/
|
|
86
|
+
export const GovernanceFoundation = Composite<GovernanceFoundationProps, GovernanceFoundationResult>((props) => {
|
|
87
|
+
const { orgId, namespace } = props;
|
|
88
|
+
const folder = (metaName: string, displayName: string): InstanceType<typeof ResourcemanagerFolder> =>
|
|
89
|
+
new ResourcemanagerFolder({
|
|
90
|
+
metadata: {
|
|
91
|
+
name: metaName,
|
|
92
|
+
...(namespace && { namespace }),
|
|
93
|
+
labels: labels("folder"),
|
|
94
|
+
},
|
|
95
|
+
displayName,
|
|
96
|
+
organizationRef: { external: orgNumber(orgId) },
|
|
97
|
+
} as Record<string, unknown>);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
folderSecurity: folder("security", "Security"),
|
|
101
|
+
folderInfrastructure: folder("infrastructure", "Infrastructure"),
|
|
102
|
+
folderSandbox: folder("sandbox", "Sandbox"),
|
|
103
|
+
folderWorkloads: folder("workloads", "Workloads"),
|
|
104
|
+
policyDisableSaKeyCreation: orgPolicy("disable-sa-key-creation", DISABLE_SA_KEY_CREATION, orgId, namespace),
|
|
105
|
+
policySkipDefaultNetwork: orgPolicy("skip-default-network", SKIP_DEFAULT_NETWORK, orgId, namespace),
|
|
106
|
+
};
|
|
107
|
+
}, "GovernanceFoundation");
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// LocationRestriction — optional root guardrail
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
export interface LocationRestrictionProps {
|
|
114
|
+
/** The organization the policy binds to. */
|
|
115
|
+
orgId: string;
|
|
116
|
+
/** The only locations resources may be created in (value groups like "in:eu-locations" work). */
|
|
117
|
+
locations: string[];
|
|
118
|
+
/** Namespace for all resources. */
|
|
119
|
+
namespace?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export type LocationRestrictionResult = {
|
|
123
|
+
policyResourceLocations: InstanceType<typeof OrgpolicyPolicy>;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const LocationRestriction = Composite<LocationRestrictionProps, LocationRestrictionResult>((props) => {
|
|
127
|
+
const policyResourceLocations = orgPolicy(
|
|
128
|
+
"resource-location-restriction",
|
|
129
|
+
resourceLocationRestriction(props.locations),
|
|
130
|
+
props.orgId,
|
|
131
|
+
props.namespace,
|
|
132
|
+
);
|
|
133
|
+
return { policyResourceLocations };
|
|
134
|
+
}, "LocationRestriction");
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// OrganizationAuditConfig — the audit sink
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
export interface OrganizationAuditConfigProps {
|
|
141
|
+
/** The organization audit logging is enabled on. */
|
|
142
|
+
orgId: string;
|
|
143
|
+
/** Defaults to "allServices". */
|
|
144
|
+
service?: string;
|
|
145
|
+
/** Defaults to ADMIN_READ / DATA_READ / DATA_WRITE. */
|
|
146
|
+
logTypes?: string[];
|
|
147
|
+
/** Namespace for all resources. */
|
|
148
|
+
namespace?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export type OrganizationAuditConfigResult = {
|
|
152
|
+
auditConfig: InstanceType<typeof IAMAuditConfig>;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const OrganizationAuditConfig = Composite<OrganizationAuditConfigProps, OrganizationAuditConfigResult>(
|
|
156
|
+
(props) => {
|
|
157
|
+
const auditConfig = new IAMAuditConfig({
|
|
158
|
+
metadata: {
|
|
159
|
+
name: "organization-audit",
|
|
160
|
+
...(props.namespace && { namespace: props.namespace }),
|
|
161
|
+
labels: labels("audit"),
|
|
162
|
+
},
|
|
163
|
+
resourceRef: {
|
|
164
|
+
kind: "Organization",
|
|
165
|
+
external: `organizations/${orgNumber(props.orgId)}`,
|
|
166
|
+
},
|
|
167
|
+
service: props.service ?? AUDIT_ALL_SERVICES.service,
|
|
168
|
+
auditLogConfigs: (props.logTypes ?? [...AUDIT_ALL_SERVICES.logTypes]).map((logType) => ({ logType })),
|
|
169
|
+
} as Record<string, unknown>);
|
|
170
|
+
return { auditConfig };
|
|
171
|
+
},
|
|
172
|
+
"OrganizationAuditConfig",
|
|
173
|
+
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GCP deep-observation noise rules (#1209) — for REST payloads, not
|
|
3
|
-
* Connector objects.
|
|
2
|
+
* GCP deep-observation noise rules (#1209, #1210) — for REST payloads, not
|
|
3
|
+
* Config Connector objects.
|
|
4
4
|
*
|
|
5
5
|
* ## What this replaced, and why it had to
|
|
6
6
|
*
|
|
@@ -22,8 +22,21 @@
|
|
|
22
22
|
* provider populates. It is hand-maintained, which is the honest cost: a
|
|
23
23
|
* server-set field nobody has listed reads as drift until it is. That is
|
|
24
24
|
* visible and fixable, where an over-broad rule silently hides real drift.
|
|
25
|
+
*
|
|
26
|
+
* ## The declared side is CNRM, and some of it never reaches GCP
|
|
27
|
+
*
|
|
28
|
+
* The declared tree is a Config Connector spec, and a few of its fields are
|
|
29
|
+
* instructions to the CNRM machinery rather than resource configuration —
|
|
30
|
+
* `projectRef`, `resourceID`, the `cnrm.cloud.google.com/*`
|
|
31
|
+
* annotations. No REST payload will ever echo them (the project is in the
|
|
32
|
+
* URL), so left alone each one reports `absent` drift on every clean apply.
|
|
33
|
+
* They are pruned as declared-side envelope, the mirror image of the
|
|
34
|
+
* live-side server fields.
|
|
25
35
|
*/
|
|
26
36
|
import type { DeepNode, DeepNormalizationHooks } from "@intentius/chant/deep-observation";
|
|
37
|
+
import { LABEL_OWNERSHIP_KEYS, type ChannelKeys } from "@intentius/chant/ownership";
|
|
38
|
+
import { GCP_RESOURCE_OWNERSHIP_KEYS } from "./ownership";
|
|
39
|
+
import { resolveGVK } from "./serializer";
|
|
27
40
|
|
|
28
41
|
/**
|
|
29
42
|
* Fields GCP populates on essentially anything, wherever they appear.
|
|
@@ -36,6 +49,7 @@ export const GCP_READ_ONLY_NAMES: ReadonlySet<string> = new Set([
|
|
|
36
49
|
"etag",
|
|
37
50
|
"selfLink",
|
|
38
51
|
"id",
|
|
52
|
+
"projectId",
|
|
39
53
|
"projectNumber",
|
|
40
54
|
"timeCreated",
|
|
41
55
|
"updated",
|
|
@@ -44,13 +58,25 @@ export const GCP_READ_ONLY_NAMES: ReadonlySet<string> = new Set([
|
|
|
44
58
|
"generation",
|
|
45
59
|
"metageneration",
|
|
46
60
|
"uid",
|
|
61
|
+
"uniqueId",
|
|
62
|
+
"email",
|
|
47
63
|
"state",
|
|
48
64
|
"reconciling",
|
|
49
65
|
"observedGeneration",
|
|
66
|
+
// Cloud Run's server-computed service surface (v2 GetService).
|
|
67
|
+
"uri",
|
|
68
|
+
"urls",
|
|
69
|
+
"conditions",
|
|
70
|
+
"terminalCondition",
|
|
71
|
+
"latestReadyRevision",
|
|
72
|
+
"latestCreatedRevision",
|
|
50
73
|
]);
|
|
51
74
|
|
|
52
75
|
/**
|
|
53
|
-
* Per-kind values GCP fills in when the request omits them
|
|
76
|
+
* Per-kind values GCP fills in when the request omits them, keyed by the
|
|
77
|
+
* index-erased pattern from the tree root (the declared-props shape
|
|
78
|
+
* `./deep-observe.ts` reshapes the live payload into — spec fields at the
|
|
79
|
+
* root, so `storageClass`, not `spec.storageClass`).
|
|
54
80
|
*
|
|
55
81
|
* Unlike {@link GCP_READ_ONLY_NAMES} these are only noise where **source never
|
|
56
82
|
* declared the property** — a default somebody explicitly wrote is a fact worth
|
|
@@ -62,10 +88,9 @@ export const GCP_SERVICE_DEFAULTS: Readonly<Record<string, Readonly<Record<strin
|
|
|
62
88
|
StorageBucket: {
|
|
63
89
|
storageClass: "STANDARD",
|
|
64
90
|
locationType: "multi-region",
|
|
65
|
-
|
|
66
|
-
|
|
91
|
+
publicAccessPrevention: "inherited",
|
|
92
|
+
uniformBucketLevelAccess: false,
|
|
67
93
|
"softDeletePolicy.retentionDurationSeconds": "604800",
|
|
68
|
-
kind: "storage#bucket",
|
|
69
94
|
},
|
|
70
95
|
PubSubTopic: {
|
|
71
96
|
"messageStoragePolicy.enforceInTransit": false,
|
|
@@ -74,11 +99,11 @@ export const GCP_SERVICE_DEFAULTS: Readonly<Record<string, Readonly<Record<strin
|
|
|
74
99
|
ackDeadlineSeconds: 10,
|
|
75
100
|
"expirationPolicy.ttl": "2678400s",
|
|
76
101
|
enableMessageOrdering: false,
|
|
102
|
+
enableExactlyOnceDelivery: false,
|
|
103
|
+
retainAckedMessages: false,
|
|
104
|
+
detached: false,
|
|
77
105
|
"retryPolicy.minimumBackoff": "10s",
|
|
78
106
|
},
|
|
79
|
-
SecretManagerSecret: {
|
|
80
|
-
etag: undefined, // covered by READ_ONLY; listed for readers looking here first
|
|
81
|
-
},
|
|
82
107
|
RunService: {
|
|
83
108
|
ingress: "INGRESS_TRAFFIC_ALL",
|
|
84
109
|
launchStage: "GA",
|
|
@@ -88,12 +113,28 @@ export const GCP_SERVICE_DEFAULTS: Readonly<Record<string, Readonly<Record<strin
|
|
|
88
113
|
},
|
|
89
114
|
};
|
|
90
115
|
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
/**
|
|
117
|
+
* chant's own ownership marker, in both vocabularies it is written in: the
|
|
118
|
+
* serializer stamps k8s-style label keys on the CNRM object
|
|
119
|
+
* (`app.kubernetes.io/managed-by`), while `gcpApply` stamps the GCP-legal
|
|
120
|
+
* equivalents on the live resource (`managed-by` — GCP label keys allow no `/`
|
|
121
|
+
* or `.`). See `./ownership.ts` for why the two surfaces differ. A declared
|
|
122
|
+
* tree carries the first family, a live payload can carry either (the applier
|
|
123
|
+
* forwards the manifest's labels where a kind accepts labels at all), and
|
|
124
|
+
* neither is drift: it is chant showing its own signature to itself — the same
|
|
125
|
+
* correction #1301 made for AWS.
|
|
126
|
+
*/
|
|
127
|
+
const CHANT_OWNERSHIP_LABEL_PATTERNS: ReadonlySet<string> = new Set(
|
|
128
|
+
[LABEL_OWNERSHIP_KEYS, GCP_RESOURCE_OWNERSHIP_KEYS].flatMap((keys: ChannelKeys) => [
|
|
129
|
+
`metadata.labels.${keys.managedBy}`,
|
|
130
|
+
`metadata.labels.${keys.stack}`,
|
|
131
|
+
`metadata.labels.${keys.env}`,
|
|
132
|
+
]),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
/** Declared-side CNRM machinery a REST payload never echoes (see module doc). */
|
|
136
|
+
const CNRM_ENVELOPE_PATTERNS: ReadonlySet<string> = new Set(["projectRef", "resourceID"]);
|
|
137
|
+
const CNRM_ANNOTATION_PREFIX = "metadata.annotations.cnrm.cloud.google.com/";
|
|
97
138
|
|
|
98
139
|
/** Last dotted segment of a path pattern — `spec.foo.etag` -> `etag`. */
|
|
99
140
|
function lastSegment(pattern: string): string {
|
|
@@ -101,6 +142,13 @@ function lastSegment(pattern: string): string {
|
|
|
101
142
|
return i === -1 ? pattern : pattern.slice(i + 1);
|
|
102
143
|
}
|
|
103
144
|
|
|
145
|
+
/** A `{}` with nothing pruned out of it, as GCP returns for an unset message. */
|
|
146
|
+
function isEmptyObject(value: unknown): boolean {
|
|
147
|
+
return (
|
|
148
|
+
typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
104
152
|
/**
|
|
105
153
|
* GCP's static deep-observation noise rules, applied by core to the declared
|
|
106
154
|
* tree and the live tree alike so the two are compared in the same shape.
|
|
@@ -110,22 +158,40 @@ export const gcpDeepNormalizationHooks: DeepNormalizationHooks = {
|
|
|
110
158
|
// Server-assigned, wherever it appears, on either side.
|
|
111
159
|
if (GCP_READ_ONLY_NAMES.has(lastSegment(node.pattern))) return true;
|
|
112
160
|
|
|
113
|
-
// chant's own ownership marker is not drift
|
|
114
|
-
// reporting them back is chant showing its own signature to itself — the
|
|
115
|
-
// same correction #1301 made for AWS after the emulator began applying tags.
|
|
161
|
+
// chant's own ownership marker is not drift (see the set's doc).
|
|
116
162
|
if (CHANT_OWNERSHIP_LABEL_PATTERNS.has(node.pattern)) return true;
|
|
117
163
|
|
|
118
|
-
//
|
|
119
|
-
//
|
|
164
|
+
// CNRM machinery on the declared side: instructions to the controller, not
|
|
165
|
+
// resource state, and no REST payload ever carries them.
|
|
166
|
+
if (CNRM_ENVELOPE_PATTERNS.has(node.pattern)) return true;
|
|
167
|
+
if (node.pattern.startsWith(CNRM_ANNOTATION_PREFIX)) return true;
|
|
168
|
+
|
|
169
|
+
// Everything below subtracts provider-populated values from the live tree,
|
|
170
|
+
// and only where source never declared the property. `counterpart` is a
|
|
171
|
+
// tri-state; only `absent` licenses it.
|
|
120
172
|
if (node.side !== "live" || node.counterpart !== "absent") return false;
|
|
173
|
+
|
|
174
|
+
// An undeclared `null` or `{}` is GCP's spelling of "unset" — a field the
|
|
175
|
+
// API includes in the payload without there being any configuration in it
|
|
176
|
+
// (a service account's `description: null`, Cloud Run's
|
|
177
|
+
// `binaryAuthorization: {}`).
|
|
178
|
+
if (node.value === null || isEmptyObject(node.value)) return true;
|
|
179
|
+
|
|
121
180
|
const kindDefaults = GCP_SERVICE_DEFAULTS[shortKind(node.entityType)];
|
|
122
181
|
if (!kindDefaults) return false;
|
|
123
182
|
return Object.prototype.hasOwnProperty.call(kindDefaults, node.pattern) && kindDefaults[node.pattern] === node.value;
|
|
124
183
|
},
|
|
125
184
|
};
|
|
126
185
|
|
|
127
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* `GCP::Pubsub::Topic` -> `PubSubTopic`, the key `GCP_SERVICE_DEFAULTS` uses —
|
|
188
|
+
* the CNRM kind, resolved through the serializer's GVK map because the naive
|
|
189
|
+
* `${service}${shortKind}` concatenation miscases 4 of the 6 appliable kinds
|
|
190
|
+
* (`PubsubTopic`, `IamServiceAccount`, …) and silently matches no defaults.
|
|
191
|
+
*/
|
|
128
192
|
function shortKind(entityType: string): string {
|
|
193
|
+
const resolved = resolveGVK(entityType);
|
|
194
|
+
if (resolved) return resolved.kind;
|
|
129
195
|
const parts = entityType.split("::");
|
|
130
196
|
return parts.length === 3 ? `${parts[1]}${parts[2]}` : entityType;
|
|
131
197
|
}
|
package/src/deep-observe.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { gcpPlugin } from "./plugin";
|
|
3
|
-
import { observeResourcesDeepGcp } from "./deep-observe";
|
|
3
|
+
import { observeResourcesDeepGcp, toCnrmTree } from "./deep-observe";
|
|
4
4
|
import { gcpDeepNormalizationHooks, GCP_READ_ONLY_NAMES, GCP_SERVICE_DEFAULTS } from "./deep-observe-hooks";
|
|
5
5
|
import type { DeepNode } from "@intentius/chant/deep-observation";
|
|
6
6
|
|
|
@@ -8,7 +8,8 @@ import type { DeepNode } from "@intentius/chant/deep-observation";
|
|
|
8
8
|
* The deep reader is on GCP REST now, not kubectl (#1209), so these stub
|
|
9
9
|
* `fetch`. What they mostly assert is the consequence of that: a REST payload
|
|
10
10
|
* carries no field ownership, so the noise rules are a static table rather than
|
|
11
|
-
* a per-resource managed-fields prune
|
|
11
|
+
* a per-resource managed-fields prune — plus the #1210 half of the port, the
|
|
12
|
+
* per-kind mapping of each REST shape onto the declared CNRM vocabulary.
|
|
12
13
|
*/
|
|
13
14
|
const fetchMock = vi.fn();
|
|
14
15
|
|
|
@@ -23,7 +24,7 @@ function entities(records: Array<[string, string, Record<string, unknown>]>) {
|
|
|
23
24
|
return new Map(records.map(([n, t, p]) => [n, { entityType: t, props: p }]));
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
async function observe(e: ReturnType<typeof entities>, opts: { owned?: boolean } = {}) {
|
|
27
|
+
async function observe(e: ReturnType<typeof entities>, opts: { owned?: boolean; buildOutput?: string } = {}) {
|
|
27
28
|
return observeResourcesDeepGcp({ environment: "local", entityNames: [...e.keys()], entities: e, ...opts });
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -68,6 +69,109 @@ describe("gcpDeepNormalizationHooks — the static rules (#1209)", () => {
|
|
|
68
69
|
// nothing to layer now, which is why the static table has to be enough.
|
|
69
70
|
expect(Object.keys(gcpDeepNormalizationHooks)).toEqual(["prune"]);
|
|
70
71
|
});
|
|
72
|
+
|
|
73
|
+
test("defaults are keyed by the CNRM kind, not a naive recasing of the entity type", () => {
|
|
74
|
+
// `GCP::Pubsub::Subscription` names the kind `PubSubSubscription`; a naive
|
|
75
|
+
// `${service}${shortKind}` gives `PubsubSubscription` and matches nothing.
|
|
76
|
+
const defaulted = node({
|
|
77
|
+
entityType: "GCP::Pubsub::Subscription",
|
|
78
|
+
pattern: "ackDeadlineSeconds",
|
|
79
|
+
value: 10,
|
|
80
|
+
side: "live",
|
|
81
|
+
counterpart: "absent",
|
|
82
|
+
});
|
|
83
|
+
expect(gcpDeepNormalizationHooks.prune!(defaulted)).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("both ownership vocabularies are pruned — the serializer's k8s labels and the applier's GCP ones", () => {
|
|
87
|
+
for (const pattern of [
|
|
88
|
+
"metadata.labels.managed-by",
|
|
89
|
+
"metadata.labels.chant-stack",
|
|
90
|
+
"metadata.labels.chant-env",
|
|
91
|
+
"metadata.labels.app.kubernetes.io/managed-by",
|
|
92
|
+
"metadata.labels.chant.intentius.io/stack",
|
|
93
|
+
"metadata.labels.chant.intentius.io/env",
|
|
94
|
+
]) {
|
|
95
|
+
expect(gcpDeepNormalizationHooks.prune!(node({ pattern, side: "live" }))).toBe(true);
|
|
96
|
+
expect(gcpDeepNormalizationHooks.prune!(node({ pattern, side: "declared" }))).toBe(true);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("declared-side CNRM machinery never reaches GCP and is not drift", () => {
|
|
101
|
+
for (const pattern of ["projectRef", "resourceID", "metadata.annotations.cnrm.cloud.google.com/project-id"]) {
|
|
102
|
+
expect(gcpDeepNormalizationHooks.prune!(node({ pattern, side: "declared" }))).toBe(true);
|
|
103
|
+
}
|
|
104
|
+
// A user's own annotation is still a fact worth diffing.
|
|
105
|
+
expect(gcpDeepNormalizationHooks.prune!(node({ pattern: "metadata.annotations.team", side: "declared" }))).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("an undeclared null or `{}` is GCP's spelling of unset, not drift", () => {
|
|
109
|
+
const nullValue = node({ pattern: "description", value: null, side: "live", counterpart: "absent" });
|
|
110
|
+
expect(gcpDeepNormalizationHooks.prune!(nullValue)).toBe(true);
|
|
111
|
+
const husk = node({ pattern: "binaryAuthorization", value: {}, side: "live", counterpart: "absent" });
|
|
112
|
+
expect(gcpDeepNormalizationHooks.prune!(husk)).toBe(true);
|
|
113
|
+
// Declared, or carrying members, it is a value like any other.
|
|
114
|
+
expect(gcpDeepNormalizationHooks.prune!(node({ pattern: "description", value: null, side: "live", counterpart: "present" }))).toBe(false);
|
|
115
|
+
expect(gcpDeepNormalizationHooks.prune!(node({ pattern: "binaryAuthorization", value: { policy: "p" }, side: "live", counterpart: "absent" }))).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("toCnrmTree — each REST shape in the declared vocabulary (#1210)", () => {
|
|
120
|
+
const ctx = { project: "my-project" };
|
|
121
|
+
|
|
122
|
+
test("a full resource path shortens to the declared name", () => {
|
|
123
|
+
const tree = toCnrmTree("PubSubTopic", { name: "projects/my-project/topics/t", messageRetentionDuration: "86400s" }, ctx);
|
|
124
|
+
expect(tree).toEqual({ metadata: { name: "t" }, messageRetentionDuration: "86400s" });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("bucket: GCS's iamConfiguration and lifecycle map onto the CNRM field names", () => {
|
|
128
|
+
const tree = toCnrmTree(
|
|
129
|
+
"StorageBucket",
|
|
130
|
+
{
|
|
131
|
+
name: "b",
|
|
132
|
+
iamConfiguration: { uniformBucketLevelAccess: { enabled: true }, publicAccessPrevention: "enforced" },
|
|
133
|
+
lifecycle: { rule: [{ action: { type: "Delete" }, condition: { age: 30 } }] },
|
|
134
|
+
},
|
|
135
|
+
ctx,
|
|
136
|
+
) as Record<string, unknown>;
|
|
137
|
+
expect(tree.uniformBucketLevelAccess).toBe(true);
|
|
138
|
+
expect(tree.publicAccessPrevention).toBe("enforced");
|
|
139
|
+
expect(tree.lifecycleRule).toEqual([{ action: { type: "Delete" }, condition: { age: 30 } }]);
|
|
140
|
+
expect(tree.iamConfiguration).toBeUndefined();
|
|
141
|
+
expect(tree.lifecycle).toBeUndefined();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("subscription: a same-project topic path becomes topicRef.name, a foreign one topicRef.external", () => {
|
|
145
|
+
const same = toCnrmTree("PubSubSubscription", { name: "projects/my-project/subscriptions/s", topic: "projects/my-project/topics/t" }, ctx) as Record<string, unknown>;
|
|
146
|
+
expect(same.topicRef).toEqual({ name: "t" });
|
|
147
|
+
expect(same.topic).toBeUndefined();
|
|
148
|
+
const foreign = toCnrmTree("PubSubSubscription", { name: "projects/my-project/subscriptions/s", topic: "projects/other/topics/t" }, ctx) as Record<string, unknown>;
|
|
149
|
+
expect(foreign.topicRef).toEqual({ external: "projects/other/topics/t" });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("secret: automatic replication maps onto CNRM's boolean spelling", () => {
|
|
153
|
+
const tree = toCnrmTree("SecretManagerSecret", { name: "projects/my-project/secrets/s", replication: { automatic: {} } }, ctx) as Record<string, unknown>;
|
|
154
|
+
expect(tree.replication).toEqual({ automatic: true });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("service account: the email identity maps back to the declared account id", () => {
|
|
158
|
+
const tree = toCnrmTree(
|
|
159
|
+
"IAMServiceAccount",
|
|
160
|
+
{ name: "projects/my-project/serviceAccounts/sa-1@my-project.iam.gserviceaccount.com", displayName: "SA" },
|
|
161
|
+
ctx,
|
|
162
|
+
);
|
|
163
|
+
expect((tree.metadata as Record<string, unknown>).name).toBe("sa-1");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("run service: location comes from the resource name the payload carries nowhere else", () => {
|
|
167
|
+
const tree = toCnrmTree(
|
|
168
|
+
"RunService",
|
|
169
|
+
{ name: "projects/my-project/locations/us-central1/services/svc", template: { containers: [{ image: "i" }] } },
|
|
170
|
+
ctx,
|
|
171
|
+
) as Record<string, unknown>;
|
|
172
|
+
expect(tree.location).toBe("us-central1");
|
|
173
|
+
expect((tree.metadata as Record<string, unknown>).name).toBe("svc");
|
|
174
|
+
});
|
|
71
175
|
});
|
|
72
176
|
|
|
73
177
|
describe("observeResourcesDeepGcp — over REST (#1209)", () => {
|
|
@@ -90,19 +194,20 @@ describe("observeResourcesDeepGcp — over REST (#1209)", () => {
|
|
|
90
194
|
const out = await observe(entities([["b", "GCP::Storage::Bucket", { metadata: { name: "my-bucket" } }]]));
|
|
91
195
|
|
|
92
196
|
expect(fetchMock.mock.calls[0][0]).toBe("http://localhost:4588/storage/v1/b/my-bucket");
|
|
93
|
-
const props = out.resources.b.properties as Record<string,
|
|
197
|
+
const props = out.resources.b.properties as Record<string, unknown>;
|
|
94
198
|
|
|
95
|
-
// Reshaped into the
|
|
96
|
-
//
|
|
97
|
-
// `
|
|
98
|
-
|
|
99
|
-
expect(props.
|
|
100
|
-
expect(props.
|
|
199
|
+
// Reshaped into the declared-props shape — identity under `metadata`, spec
|
|
200
|
+
// fields at the root, exactly as `new StorageBucket({ … })` authors them.
|
|
201
|
+
// Without this every field drifts twice, once as `declared -> <absent>`
|
|
202
|
+
// and once as `<undeclared> -> live`.
|
|
203
|
+
expect(props.storageClass).toBe("NEARLINE");
|
|
204
|
+
expect(props.location).toBe("US");
|
|
205
|
+
expect((props.metadata as Record<string, unknown>).name).toBe("my-bucket");
|
|
101
206
|
|
|
102
207
|
// Server-assigned, pruned wherever they landed.
|
|
103
|
-
expect(props.
|
|
104
|
-
expect(props.
|
|
105
|
-
expect(props.
|
|
208
|
+
expect(props.etag).toBeUndefined();
|
|
209
|
+
expect(props.selfLink).toBeUndefined();
|
|
210
|
+
expect(props.id).toBeUndefined();
|
|
106
211
|
});
|
|
107
212
|
|
|
108
213
|
test("chant's own ownership labels are not drift", async () => {
|
|
@@ -165,4 +270,44 @@ describe("observeResourcesDeepGcp — over REST (#1209)", () => {
|
|
|
165
270
|
);
|
|
166
271
|
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
167
272
|
});
|
|
273
|
+
|
|
274
|
+
test("kinds resolve through the serializer's GVK map, not naive recasing", async () => {
|
|
275
|
+
// `GCP::Pubsub::Topic` derives `PubsubTopic` naively — no such mapper, so
|
|
276
|
+
// 4 of the 6 appliable kinds silently reported unsupported-kind until the
|
|
277
|
+
// readers resolved kinds the way the serializer does.
|
|
278
|
+
fetchMock.mockResolvedValue(reply(200, { name: "projects/my-project/topics/t" }));
|
|
279
|
+
const out = await observe(entities([["t", "GCP::Pubsub::Topic", { metadata: { name: "t" } }]]));
|
|
280
|
+
expect(out.unobserved?.t).toBeUndefined();
|
|
281
|
+
expect(out.resources.t).toBeDefined();
|
|
282
|
+
expect(fetchMock.mock.calls[0][0]).toBe("http://localhost:4588/v1/projects/my-project/topics/t");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("the project falls back to the built manifest's merged annotation", async () => {
|
|
286
|
+
// A project-wide `defaultAnnotations` is a separate declarable the
|
|
287
|
+
// serializer merges at synthesis — the entity's own props never carry it,
|
|
288
|
+
// and discovery is what the observe paths receive.
|
|
289
|
+
delete process.env.GOOGLE_CLOUD_PROJECT;
|
|
290
|
+
fetchMock.mockResolvedValue(reply(200, { name: "b" }));
|
|
291
|
+
const buildOutput = [
|
|
292
|
+
"apiVersion: storage.cnrm.cloud.google.com/v1beta1",
|
|
293
|
+
"kind: StorageBucket",
|
|
294
|
+
"metadata:",
|
|
295
|
+
" name: b",
|
|
296
|
+
" annotations:",
|
|
297
|
+
" cnrm.cloud.google.com/project-id: manifest-project",
|
|
298
|
+
"spec:",
|
|
299
|
+
" location: US",
|
|
300
|
+
].join("\n");
|
|
301
|
+
const out = await observe(entities([["b", "GCP::Storage::Bucket", { metadata: { name: "b" } }]]), { buildOutput });
|
|
302
|
+
expect(out.unobserved?.b).toBeUndefined();
|
|
303
|
+
expect(out.resources.b).toBeDefined();
|
|
304
|
+
expect(fetchMock.mock.calls[0][0]).toBe("http://localhost:4588/storage/v1/b/b");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("no project from any source is still a no-binding hole, never a guess", async () => {
|
|
308
|
+
delete process.env.GOOGLE_CLOUD_PROJECT;
|
|
309
|
+
const out = await observe(entities([["t", "GCP::Pubsub::Topic", { metadata: { name: "t" } }]]));
|
|
310
|
+
expect(out.unobserved?.t?.reason).toBe("no-binding");
|
|
311
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
312
|
+
});
|
|
168
313
|
});
|