@oneuptime/common 14.0.3 → 14.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (663) hide show
  1. package/Models/DatabaseModels/Alert.ts +55 -0
  2. package/Models/DatabaseModels/DatabaseServer.ts +1650 -0
  3. package/Models/DatabaseModels/DatabaseServerEndpoint.ts +540 -0
  4. package/Models/DatabaseModels/DatabaseServerFeed.ts +625 -0
  5. package/Models/DatabaseModels/DatabaseServerLabelRule.ts +514 -0
  6. package/Models/DatabaseModels/DatabaseServerOwnerRule.ts +596 -0
  7. package/Models/DatabaseModels/DatabaseServerOwnerTeam.ts +491 -0
  8. package/Models/DatabaseModels/DatabaseServerOwnerUser.ts +490 -0
  9. package/Models/DatabaseModels/Incident.ts +55 -0
  10. package/Models/DatabaseModels/Index.ts +16 -0
  11. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +26 -0
  12. package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
  13. package/Models/DatabaseModels/TelemetryException.ts +2 -0
  14. package/Models/DatabaseModels/UserProjectSsoConsent.ts +146 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.ts +673 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.ts +51 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  18. package/Server/Middleware/MultipartFormData.ts +1 -1
  19. package/Server/Middleware/TelemetryIngest.ts +15 -8
  20. package/Server/Services/AlertReminderRuleService.ts +100 -24
  21. package/Server/Services/DatabaseServerEndpointService.ts +895 -0
  22. package/Server/Services/DatabaseServerFeedService.ts +128 -0
  23. package/Server/Services/DatabaseServerLabelRuleEngineService.ts +340 -0
  24. package/Server/Services/DatabaseServerLabelRuleService.ts +14 -0
  25. package/Server/Services/DatabaseServerOwnerRuleEngineService.ts +425 -0
  26. package/Server/Services/DatabaseServerOwnerRuleService.ts +14 -0
  27. package/Server/Services/DatabaseServerOwnerTeamService.ts +180 -0
  28. package/Server/Services/DatabaseServerOwnerUserService.ts +173 -0
  29. package/Server/Services/DatabaseServerService.ts +4546 -0
  30. package/Server/Services/DatabaseService.ts +14 -8
  31. package/Server/Services/DockerResourceService.ts +43 -2
  32. package/Server/Services/IncidentReminderRuleService.ts +103 -23
  33. package/Server/Services/Index.ts +16 -0
  34. package/Server/Services/InventoryItemService.ts +113 -5
  35. package/Server/Services/IoTDeviceService.ts +145 -37
  36. package/Server/Services/MetricService.ts +1 -1
  37. package/Server/Services/OpenTelemetryIngestService.ts +19 -0
  38. package/Server/Services/PodmanResourceService.ts +43 -2
  39. package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +107 -23
  40. package/Server/Services/ServiceService.ts +1 -1
  41. package/Server/Services/TelemetryUsageBillingService.ts +29 -2
  42. package/Server/Services/UserProjectSsoConsentService.ts +74 -0
  43. package/Server/Services/UserService.ts +14 -1
  44. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +18 -0
  45. package/Server/Types/Database/QueryHelper.ts +84 -2
  46. package/Server/Utils/AI/Toolbox/MetricTools.ts +13 -2
  47. package/Server/Utils/AI/Toolbox/ResourceTools.ts +81 -2
  48. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +1 -1
  49. package/Server/Utils/Database/AffectedResourceRelations.ts +6 -0
  50. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +33 -0
  51. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +192 -9
  52. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  53. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +2264 -755
  54. package/Server/Utils/Monitor/MonitorIncident.ts +28 -1
  55. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +268 -6
  56. package/Server/Utils/Monitor/MonitorResource.ts +47 -11
  57. package/Server/Utils/Monitor/MonitorResourceContext.ts +1 -0
  58. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +239 -21
  59. package/Server/Utils/Monitor/PerSeriesResolutionRootCause.ts +43 -0
  60. package/Server/Utils/Monitor/SeriesResourceLabels.ts +291 -0
  61. package/Server/Utils/Monitor/SeriesResourceLinker.ts +111 -12
  62. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
  63. package/Server/Utils/StartServer.ts +26 -6
  64. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
  65. package/Server/Utils/Telemetry/DatabaseEndpointDiscovery.ts +688 -0
  66. package/Server/Utils/Telemetry/EntityRegistry.ts +5 -1
  67. package/Server/Utils/Telemetry/LogExceptionExtractor.ts +18 -1
  68. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +175 -12
  69. package/Server/Utils/Telemetry/ResourceFacetResolver.ts +12 -0
  70. package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +606 -36
  71. package/Server/Utils/Telemetry/Telemetry.ts +16 -0
  72. package/Server/Utils/Telemetry/TelemetryEntity.ts +22 -22
  73. package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +16 -10
  74. package/Tests/App/Accounts/SignedInRedirect.test.tsx +241 -0
  75. package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +221 -4
  76. package/Tests/App/Dashboard/AIInsightsResourcePageContext.test.ts +42 -0
  77. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +100 -0
  78. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +86 -0
  79. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +2 -0
  80. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +19 -0
  81. package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +14 -0
  82. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +26 -1
  83. package/Tests/App/Dashboard/DashboardAppSsoError.test.tsx +501 -0
  84. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +64 -1
  85. package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +178 -18
  86. package/Tests/App/Dashboard/DashboardMasterPageSsoRedirect.test.tsx +444 -0
  87. package/Tests/App/Dashboard/DashboardNavBarForeignHiddenRule.test.tsx +573 -0
  88. package/Tests/App/Dashboard/DatabaseChartAxes.test.tsx +431 -0
  89. package/Tests/App/Dashboard/DatabaseContainerDetailBadge.test.tsx +249 -0
  90. package/Tests/App/Dashboard/DatabaseDashboardE2EFixes.test.ts +681 -0
  91. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +1801 -0
  92. package/Tests/App/Dashboard/DatabaseFleetSummaryRefresh.test.tsx +280 -0
  93. package/Tests/App/Dashboard/DatabaseFormHelpers.test.tsx +321 -0
  94. package/Tests/App/Dashboard/DatabaseMetricMonitorLink.test.tsx +795 -0
  95. package/Tests/App/Dashboard/DatabaseOverviewHeader.test.tsx +580 -0
  96. package/Tests/App/Dashboard/DatabaseRecommendationsEngineMetrics.test.ts +270 -0
  97. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +1486 -0
  98. package/Tests/App/Dashboard/DatabaseServerEngineMetricsFilter.test.ts +223 -0
  99. package/Tests/App/Dashboard/DatabaseServerPresentation.test.ts +435 -0
  100. package/Tests/App/Dashboard/DatabaseServerSummary.test.ts +186 -0
  101. package/Tests/App/Dashboard/DatabaseServerTelemetryQueries.test.ts +1553 -0
  102. package/Tests/App/Dashboard/DatabaseTableTruncation.test.tsx +326 -0
  103. package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +512 -0
  104. package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +774 -0
  105. package/Tests/App/Dashboard/DatabaseViewLayout.test.tsx +233 -0
  106. package/Tests/App/Dashboard/DatabaseWorkloadBadge.test.tsx +972 -0
  107. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +5 -0
  108. package/Tests/App/Dashboard/DiscoveryReviewUnnamedHostHint.test.tsx +1061 -0
  109. package/Tests/App/Dashboard/EventOverlayScope.test.ts +147 -0
  110. package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +1 -0
  111. package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +1 -0
  112. package/Tests/App/Dashboard/MetricsViewerDrillDown.test.tsx +310 -0
  113. package/Tests/App/Dashboard/MetricsViewerEntityScopedRows.test.tsx +495 -0
  114. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +132 -1
  115. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +5 -0
  116. package/Tests/App/Dashboard/NetworkTopologyExplorerSearch.test.tsx +1267 -0
  117. package/Tests/App/Dashboard/ProductOrientation.test.tsx +4 -3
  118. package/Tests/App/Dashboard/RecommendationsLoadedContext.test.tsx +349 -0
  119. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +62 -0
  120. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
  121. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +14 -0
  122. package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +4 -1
  123. package/Tests/App/Dashboard/RumOverviewPage.test.tsx +127 -0
  124. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +2 -0
  125. package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +1085 -0
  126. package/Tests/App/Dashboard/SiteSearchBox.test.tsx +2110 -0
  127. package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +168 -8
  128. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +10 -0
  129. package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +2 -1
  130. package/Tests/App/StatusPage/StatusPageHeaderForeignHiddenRule.test.tsx +284 -0
  131. package/Tests/ClassStrings.ts +178 -0
  132. package/Tests/Fixtures/MobilePasskeyUser.json +1 -2
  133. package/Tests/ForeignHiddenRuleGuard.ts +2152 -0
  134. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +2250 -0
  135. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +24 -5
  136. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  137. package/Tests/RenderedMarkup.ts +518 -0
  138. package/Tests/ResponsiveVisibility.test.ts +490 -4
  139. package/Tests/ResponsiveVisibility.ts +505 -52
  140. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  141. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +541 -0
  142. package/Tests/Server/Infrastructure/Postgres/AddUserProjectSsoConsentMigration.test.ts +234 -0
  143. package/Tests/Server/Services/ContainerSnapshotUpsertPostgres.test.ts +319 -0
  144. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +13 -0
  145. package/Tests/Server/Services/DatabaseServerEndpointService.test.ts +2033 -0
  146. package/Tests/Server/Services/DatabaseServerFeedAndOwnerServices.test.ts +529 -0
  147. package/Tests/Server/Services/DatabaseServerLifecycle.test.ts +3302 -0
  148. package/Tests/Server/Services/DatabaseServerRegistries.test.ts +603 -0
  149. package/Tests/Server/Services/DatabaseServerRuleEngines.test.ts +754 -0
  150. package/Tests/Server/Services/DatabaseServerService.test.ts +3558 -0
  151. package/Tests/Server/Services/DatabaseServerSqlPostgres.test.ts +2045 -0
  152. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedPostgres.test.ts +362 -0
  153. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedReply.test.ts +210 -0
  154. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +18 -0
  155. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +2 -0
  156. package/Tests/Server/Services/IncomingCallPolicyEscalationRuleTarget.test.ts +225 -0
  157. package/Tests/Server/Services/InventoryItemDisplayName.test.ts +280 -2
  158. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +8 -2
  159. package/Tests/Server/Services/IoTDeviceLatestMetricsPostgres.test.ts +689 -0
  160. package/Tests/Server/Services/IoTDeviceService.test.ts +379 -0
  161. package/Tests/Server/Services/LatestMetricsValuesShape.test.ts +360 -0
  162. package/Tests/Server/Services/LlmModelPriceService.test.ts +402 -0
  163. package/Tests/Server/Services/MetricServiceAggregate.test.ts +12 -0
  164. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
  165. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +61 -9
  166. package/Tests/Server/Services/ReminderRuleLookupPostgres.test.ts +2250 -0
  167. package/Tests/Server/Services/ReminderRuleRefreshOpenSubjectsPaging.test.ts +653 -0
  168. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +68 -20
  169. package/Tests/Server/Services/SavedViewDefaults.test.ts +325 -0
  170. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
  171. package/Tests/Server/Services/StatusPageCertificateService.test.ts +169 -0
  172. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +191 -0
  173. package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +9 -0
  174. package/Tests/Server/Services/UserProjectSsoConsentPostgres.test.ts +153 -0
  175. package/Tests/Server/Services/UserProjectSsoConsentService.test.ts +192 -0
  176. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +71 -0
  177. package/Tests/Server/Services/UserSignupIdTakeoverPostgres.test.ts +238 -0
  178. package/Tests/Server/Services/WorkspaceNotificationSummaryHelpers.test.ts +592 -0
  179. package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +3 -0
  180. package/Tests/Server/Types/Database/EventOverlayScopePostgres.test.ts +1 -0
  181. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +11 -0
  182. package/Tests/Server/Types/Database/Permissions/InfrastructureOwnershipScope.test.ts +8 -0
  183. package/Tests/Server/Types/Database/QueryHelperBooleanForCriteriaBackedRule.test.ts +1094 -0
  184. package/Tests/Server/Types/Database/QueryHelperSearchAllWords.test.ts +651 -0
  185. package/Tests/Server/Utils/AI/AlertWriteTools.test.ts +256 -0
  186. package/Tests/Server/Utils/AI/IncidentWriteTools.test.ts +482 -0
  187. package/Tests/Server/Utils/AI/ResourceTools.test.ts +134 -0
  188. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +54 -0
  189. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +41 -0
  190. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +279 -9
  191. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaDisplayContext.test.ts +933 -0
  192. package/Tests/Server/Utils/Monitor/DatabaseMaintenanceSuppression.test.ts +582 -0
  193. package/Tests/Server/Utils/Monitor/DatabaseServerMonitorLinking.test.ts +753 -0
  194. package/Tests/Server/Utils/Monitor/MetricDecimalThreshold.test.ts +417 -0
  195. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +1 -0
  196. package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +1 -0
  197. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +119 -0
  198. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +60 -25
  199. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +1815 -0
  200. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesAffectedResources.test.ts +1447 -0
  201. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesRatioEmail.test.ts +352 -0
  202. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformMetricUnits.test.ts +717 -0
  203. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformRawScan.test.ts +2125 -0
  204. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformSeriesAffectedResources.test.ts +1288 -0
  205. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
  206. package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +1 -0
  207. package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +1 -0
  208. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +88 -0
  209. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +205 -0
  210. package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +62 -0
  211. package/Tests/Server/Utils/Monitor/MonitorResourceDatabaseMaintenance.test.ts +803 -0
  212. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +133 -3
  213. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
  214. package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +472 -1
  215. package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
  216. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +236 -2
  217. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +70 -0
  218. package/Tests/Server/Utils/StartServerProtobufContentType.test.ts +54 -0
  219. package/Tests/Server/Utils/Telemetry/DatabaseEndpointDiscovery.test.ts +1895 -0
  220. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +7 -0
  221. package/Tests/Server/Utils/Telemetry/LogExceptionExtractorSeverityGate.test.ts +90 -0
  222. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +570 -0
  223. package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +97 -2
  224. package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +1337 -0
  225. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +295 -0
  226. package/Tests/Types/AI/AIResourceContext.test.ts +30 -0
  227. package/Tests/Types/BaseDatabase/AggregationType.test.ts +5 -1
  228. package/Tests/Types/Dashboard/KubernetesDashboardTemplate.test.ts +184 -0
  229. package/Tests/Types/Dashboard/KubernetesDashboardTemplates.test.ts +107 -0
  230. package/Tests/Types/DatabaseServer/DatabaseContainerClassifier.test.ts +3644 -0
  231. package/Tests/Types/DatabaseServer/DatabaseContainerCommand.test.ts +340 -0
  232. package/Tests/Types/DatabaseServer/DatabaseContainerCommandCorpus.ts +826 -0
  233. package/Tests/Types/DatabaseServer/DatabaseContainerCommandPostgres.test.ts +150 -0
  234. package/Tests/Types/DatabaseServer/DatabaseEndpoint.test.ts +2657 -0
  235. package/Tests/Types/DatabaseServer/DatabaseServerDiscoverySource.test.ts +64 -0
  236. package/Tests/Types/DatabaseServer/DatabaseServerEntityKeys.test.ts +952 -0
  237. package/Tests/Types/DatabaseServer/DatabaseServerMetricCatalog.test.ts +1161 -0
  238. package/Tests/Types/DatabaseServer/DatabaseSystem.test.ts +1514 -0
  239. package/Tests/Types/DatabaseServer/DatabaseTelemetryResolver.test.ts +1558 -0
  240. package/Tests/Types/Monitor/CephAlertTemplates.test.ts +255 -6
  241. package/Tests/Types/Monitor/DatabaseAlertTemplates.test.ts +2254 -0
  242. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +292 -0
  243. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +272 -0
  244. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +253 -9
  245. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +13 -5
  246. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +4 -2
  247. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +111 -2
  248. package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +61 -0
  249. package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +114 -0
  250. package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +202 -0
  251. package/Tests/Types/NetworkDevice/DiscoveredHostNamingStatus.test.ts +401 -0
  252. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +4 -1
  253. package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +24 -2
  254. package/Tests/UI/Components/Card.test.tsx +93 -2
  255. package/Tests/UI/Components/FeedOptions.test.ts +6 -0
  256. package/Tests/UI/Components/FeedOptionsButton.test.tsx +8 -1
  257. package/Tests/UI/Components/ForeignHiddenRuleResilience.test.tsx +1147 -0
  258. package/Tests/UI/Components/HeaderRightRail.test.tsx +41 -1
  259. package/Tests/UI/Components/LogsEntityNames.test.ts +16 -1
  260. package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +7 -3
  261. package/Tests/UI/Components/ResourceFacetConfigs.test.ts +2 -1
  262. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  263. package/Tests/UI/Components/SrOnlyForeignRule.test.tsx +994 -0
  264. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +34 -1
  265. package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +4 -1
  266. package/Tests/UI/ForeignHiddenRuleGuard.test.ts +1951 -0
  267. package/Tests/UI/Rum/ChunkLoader.test.ts +76 -0
  268. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +19 -1
  269. package/Tests/UI/Rum/ReplayRailDetail.test.tsx +53 -0
  270. package/Tests/UI/Rum/ReplaySignals.test.ts +54 -0
  271. package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +19 -1
  272. package/Tests/UI/TailwindPlayCdnForeignHiddenRule.test.ts +1005 -0
  273. package/Tests/UI/ThemeGroupHoverReveal.test.ts +847 -0
  274. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +70 -0
  275. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +21 -0
  276. package/Tests/UI/Utils/ModelAPISsoRedirect.test.ts +127 -0
  277. package/Tests/UI/Utils/Navigation.test.ts +102 -0
  278. package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +23 -0
  279. package/Tests/Utils/Dashboard/KubernetesClusterCpuTileRepair.test.ts +370 -0
  280. package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
  281. package/Tests/Utils/MetricUnitUtil.test.ts +227 -2
  282. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +288 -2
  283. package/Tests/Utils/Monitor/PlatformResourceIdentityAndUnits.test.ts +1559 -0
  284. package/Tests/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.test.ts +2546 -0
  285. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +315 -0
  286. package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +336 -1
  287. package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +244 -0
  288. package/Tests/Utils/Rules/MonitorTypeRuleCriteria.test.ts +181 -0
  289. package/Tests/Utils/ValueFormatter.test.ts +50 -0
  290. package/Tests/tsconfig.json +16 -0
  291. package/Types/AI/AIResourceContext.ts +7 -0
  292. package/Types/BaseDatabase/AggregationType.ts +7 -0
  293. package/Types/Dashboard/DashboardTemplates.ts +22 -8
  294. package/Types/DatabaseServer/DatabaseConnectionSpan.ts +77 -0
  295. package/Types/DatabaseServer/DatabaseContainerClassifier.ts +2068 -0
  296. package/Types/DatabaseServer/DatabaseContainerCommand.ts +801 -0
  297. package/Types/DatabaseServer/DatabaseEndpoint.ts +1943 -0
  298. package/Types/DatabaseServer/DatabaseServerDiscoverySource.ts +68 -0
  299. package/Types/DatabaseServer/DatabaseServerMetricCatalog.ts +966 -0
  300. package/Types/DatabaseServer/DatabaseSystem.ts +2124 -0
  301. package/Types/DatabaseServer/DatabaseTelemetryResolver.ts +555 -0
  302. package/Types/Email/EmailTemplateType.ts +1 -0
  303. package/Types/Monitor/CephAlertTemplates.ts +18 -0
  304. package/Types/Monitor/DatabaseAlertTemplates.ts +2020 -0
  305. package/Types/Monitor/KubernetesAlertTemplates.ts +64 -24
  306. package/Types/Monitor/KubernetesMetricCatalog.ts +118 -19
  307. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +14 -0
  308. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +93 -10
  309. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +109 -8
  310. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +36 -7
  311. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +43 -2
  312. package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +41 -6
  313. package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +26 -0
  314. package/Types/NetworkDevice/DiscoveredHostNamingStatus.ts +147 -0
  315. package/Types/Permission.ts +267 -0
  316. package/Types/Probe/ProbeApiIngestResponse.ts +8 -0
  317. package/Types/Rules/RuleCriteriaFieldRegistry.ts +10 -0
  318. package/Types/Rules/RuleRun.ts +12 -0
  319. package/Types/Rum/SessionReplayCustomEvents.ts +29 -3
  320. package/Types/Rum/WebVitals.ts +13 -0
  321. package/Types/Telemetry/EntityType.ts +23 -0
  322. package/Types/Telemetry/ResourceFacetCatalog.ts +14 -0
  323. package/Types/Telemetry/ServiceType.ts +8 -0
  324. package/UI/Components/Banner/Banner.tsx +1 -1
  325. package/UI/Components/Card/Card.tsx +1 -1
  326. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +2 -2
  327. package/UI/Components/Charts/Line/LineChart.tsx +7 -0
  328. package/UI/Components/CommandPalette/CommandPalette.tsx +1 -1
  329. package/UI/Components/Date/StartAndEndDate.tsx +5 -5
  330. package/UI/Components/Diagram/HorizontalStepChain.tsx +1 -1
  331. package/UI/Components/Feed/FeedOptionsButton.tsx +1 -1
  332. package/UI/Components/Forms/BasicForm.tsx +1 -1
  333. package/UI/Components/Header/Header.tsx +1 -1
  334. package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -1
  335. package/UI/Components/HeaderAlert/HeaderAlert.tsx +1 -1
  336. package/UI/Components/Input/Input.tsx +11 -0
  337. package/UI/Components/LogsViewer/components/FacetValueRow.tsx +1 -1
  338. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +1 -1
  339. package/UI/Components/Modal/ModalFooter.tsx +1 -1
  340. package/UI/Components/ModelDetail/CardModelDetail.tsx +2 -2
  341. package/UI/Components/ModelTable/BaseModelTable.tsx +4 -4
  342. package/UI/Components/Navbar/NavBar.tsx +9 -2
  343. package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -2
  344. package/UI/Components/Page/Page.tsx +1 -1
  345. package/UI/Components/Pagination/Pagination.tsx +3 -3
  346. package/UI/Components/ProgressButtons/ProgressButtonItem.tsx +1 -1
  347. package/UI/Components/SideMenu/SideMenu.tsx +4 -1
  348. package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +4 -2
  349. package/UI/Components/StatusPage/ResourceList.tsx +6 -6
  350. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +1 -1
  351. package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +1 -1
  352. package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +1 -1
  353. package/UI/Components/Workflow/ComponentsModal.tsx +2 -2
  354. package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
  355. package/UI/Styles/Theme.css +8 -1
  356. package/UI/Utils/Navigation.ts +89 -42
  357. package/UI/Utils/Telemetry/TelemetryEntityNames.ts +7 -0
  358. package/UI/Utils/TelemetryService.ts +4 -3
  359. package/Utils/Dashboard/KubernetesClusterCpuTileRepair.ts +147 -0
  360. package/Utils/LabelRuleImportExport.ts +2 -0
  361. package/Utils/MetricUnitUtil.ts +103 -11
  362. package/Utils/Monitor/MetricValueFormatter.ts +66 -13
  363. package/Utils/Monitor/PlatformMetricUnitUtil.ts +215 -0
  364. package/Utils/Monitor/PlatformResourceIdentity.ts +514 -0
  365. package/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.ts +425 -0
  366. package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +32 -0
  367. package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +73 -0
  368. package/Utils/Telemetry/DatabaseServerEntityKeys.ts +275 -0
  369. package/Utils/Telemetry/EntityKey.ts +176 -0
  370. package/Utils/ValueFormatter.ts +16 -0
  371. package/build/dist/Models/DatabaseModels/Alert.js +54 -0
  372. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  373. package/build/dist/Models/DatabaseModels/DatabaseServer.js +1683 -0
  374. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -0
  375. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js +559 -0
  376. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js.map +1 -0
  377. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js +648 -0
  378. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js.map +1 -0
  379. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js +522 -0
  380. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js.map +1 -0
  381. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js +603 -0
  382. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js.map +1 -0
  383. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js +506 -0
  384. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js.map +1 -0
  385. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js +505 -0
  386. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js.map +1 -0
  387. package/build/dist/Models/DatabaseModels/Incident.js +54 -0
  388. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  389. package/build/dist/Models/DatabaseModels/Index.js +16 -0
  390. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  391. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  392. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
  393. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  394. package/build/dist/Models/DatabaseModels/TelemetryException.js +2 -0
  395. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  396. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js +162 -0
  397. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js.map +1 -0
  398. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js +240 -0
  399. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js.map +1 -0
  400. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js +26 -0
  401. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js.map +1 -0
  402. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  403. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  404. package/build/dist/Server/Middleware/MultipartFormData.js +1 -1
  405. package/build/dist/Server/Middleware/TelemetryIngest.js +15 -8
  406. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  407. package/build/dist/Server/Services/AlertReminderRuleService.js +72 -23
  408. package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
  409. package/build/dist/Server/Services/DatabaseServerEndpointService.js +694 -0
  410. package/build/dist/Server/Services/DatabaseServerEndpointService.js.map +1 -0
  411. package/build/dist/Server/Services/DatabaseServerFeedService.js +110 -0
  412. package/build/dist/Server/Services/DatabaseServerFeedService.js.map +1 -0
  413. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js +272 -0
  414. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js.map +1 -0
  415. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js +13 -0
  416. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js.map +1 -0
  417. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js +340 -0
  418. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js.map +1 -0
  419. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js +13 -0
  420. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js.map +1 -0
  421. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js +160 -0
  422. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js.map +1 -0
  423. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js +153 -0
  424. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js.map +1 -0
  425. package/build/dist/Server/Services/DatabaseServerService.js +3272 -0
  426. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -0
  427. package/build/dist/Server/Services/DatabaseService.js +8 -4
  428. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  429. package/build/dist/Server/Services/DockerResourceService.js +30 -3
  430. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  431. package/build/dist/Server/Services/IncidentReminderRuleService.js +73 -23
  432. package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
  433. package/build/dist/Server/Services/Index.js +16 -0
  434. package/build/dist/Server/Services/Index.js.map +1 -1
  435. package/build/dist/Server/Services/InventoryItemService.js +88 -5
  436. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  437. package/build/dist/Server/Services/IoTDeviceService.js +111 -24
  438. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  439. package/build/dist/Server/Services/MetricService.js +1 -1
  440. package/build/dist/Server/Services/OpenTelemetryIngestService.js +24 -9
  441. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  442. package/build/dist/Server/Services/PodmanResourceService.js +30 -3
  443. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  444. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +75 -23
  445. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
  446. package/build/dist/Server/Services/ServiceService.js +1 -1
  447. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  448. package/build/dist/Server/Services/TelemetryUsageBillingService.js +23 -2
  449. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  450. package/build/dist/Server/Services/UserProjectSsoConsentService.js +79 -0
  451. package/build/dist/Server/Services/UserProjectSsoConsentService.js.map +1 -0
  452. package/build/dist/Server/Services/UserService.js +13 -1
  453. package/build/dist/Server/Services/UserService.js.map +1 -1
  454. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +18 -0
  455. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  456. package/build/dist/Server/Types/Database/QueryHelper.js +73 -2
  457. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  458. package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +13 -2
  459. package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -1
  460. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +58 -1
  461. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
  462. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +1 -1
  463. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +6 -0
  464. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -1
  465. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +31 -0
  466. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  467. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +135 -14
  468. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  469. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -6
  470. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  471. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1489 -524
  472. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  473. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
  474. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  475. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +196 -6
  476. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  477. package/build/dist/Server/Utils/Monitor/MonitorResource.js +36 -9
  478. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  479. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +1 -0
  480. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
  481. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +175 -17
  482. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
  483. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js +29 -0
  484. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js.map +1 -0
  485. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +211 -0
  486. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  487. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +82 -10
  488. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
  489. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
  490. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  491. package/build/dist/Server/Utils/StartServer.js +20 -4
  492. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  493. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
  494. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
  495. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js +442 -0
  496. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js.map +1 -0
  497. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +5 -1
  498. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  499. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +15 -1
  500. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
  501. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +121 -5
  502. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  503. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +12 -0
  504. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
  505. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +438 -36
  506. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -1
  507. package/build/dist/Server/Utils/Telemetry/Telemetry.js +12 -0
  508. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  509. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +17 -18
  510. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  511. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +16 -10
  512. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -1
  513. package/build/dist/Types/AI/AIResourceContext.js +7 -0
  514. package/build/dist/Types/AI/AIResourceContext.js.map +1 -1
  515. package/build/dist/Types/BaseDatabase/AggregationType.js +7 -0
  516. package/build/dist/Types/BaseDatabase/AggregationType.js.map +1 -1
  517. package/build/dist/Types/Dashboard/DashboardTemplates.js +22 -8
  518. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  519. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js +71 -0
  520. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js.map +1 -0
  521. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js +1462 -0
  522. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js.map +1 -0
  523. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js +691 -0
  524. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js.map +1 -0
  525. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js +1407 -0
  526. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js.map +1 -0
  527. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js +60 -0
  528. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js.map +1 -0
  529. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js +804 -0
  530. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js.map +1 -0
  531. package/build/dist/Types/DatabaseServer/DatabaseSystem.js +1860 -0
  532. package/build/dist/Types/DatabaseServer/DatabaseSystem.js.map +1 -0
  533. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js +406 -0
  534. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js.map +1 -0
  535. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  536. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  537. package/build/dist/Types/Monitor/CephAlertTemplates.js +18 -0
  538. package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
  539. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js +1518 -0
  540. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js.map +1 -0
  541. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +57 -24
  542. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  543. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +104 -18
  544. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
  545. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +80 -3
  546. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  547. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +10 -3
  548. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  549. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +29 -0
  550. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  551. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +21 -6
  552. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -1
  553. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +25 -0
  554. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
  555. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js +136 -0
  556. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js.map +1 -0
  557. package/build/dist/Types/Permission.js +234 -0
  558. package/build/dist/Types/Permission.js.map +1 -1
  559. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +10 -0
  560. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  561. package/build/dist/Types/Rules/RuleRun.js +4 -0
  562. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  563. package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -1
  564. package/build/dist/Types/Rum/WebVitals.js +12 -0
  565. package/build/dist/Types/Rum/WebVitals.js.map +1 -1
  566. package/build/dist/Types/Telemetry/EntityType.js +23 -0
  567. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  568. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +14 -0
  569. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -1
  570. package/build/dist/Types/Telemetry/ServiceType.js +8 -0
  571. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  572. package/build/dist/UI/Components/Banner/Banner.js +1 -1
  573. package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
  574. package/build/dist/UI/Components/Card/Card.js +1 -1
  575. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  576. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
  577. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  578. package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
  579. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  580. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +1 -1
  581. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
  582. package/build/dist/UI/Components/Date/StartAndEndDate.js +5 -5
  583. package/build/dist/UI/Components/Date/StartAndEndDate.js.map +1 -1
  584. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js +1 -1
  585. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js.map +1 -1
  586. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +1 -1
  587. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -1
  588. package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
  589. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  590. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
  591. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  592. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js +1 -1
  593. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js.map +1 -1
  594. package/build/dist/UI/Components/Input/Input.js +1 -1
  595. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  596. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +1 -1
  597. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
  598. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
  599. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  600. package/build/dist/UI/Components/Modal/ModalFooter.js +1 -1
  601. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  602. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
  603. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  604. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +4 -4
  605. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  606. package/build/dist/UI/Components/Navbar/NavBar.js +9 -2
  607. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  608. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
  609. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  610. package/build/dist/UI/Components/Page/Page.js +1 -1
  611. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  612. package/build/dist/UI/Components/Pagination/Pagination.js +3 -3
  613. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  614. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js +1 -1
  615. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js.map +1 -1
  616. package/build/dist/UI/Components/SideMenu/SideMenu.js +4 -1
  617. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  618. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +3 -1
  619. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -1
  620. package/build/dist/UI/Components/StatusPage/ResourceList.js +6 -6
  621. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  622. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
  623. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  624. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +1 -1
  625. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
  626. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +1 -1
  627. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -1
  628. package/build/dist/UI/Components/Workflow/ComponentsModal.js +2 -2
  629. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  630. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
  631. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
  632. package/build/dist/UI/Utils/Navigation.js +66 -33
  633. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  634. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +7 -0
  635. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -1
  636. package/build/dist/UI/Utils/TelemetryService.js +3 -2
  637. package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
  638. package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js +110 -0
  639. package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js.map +1 -0
  640. package/build/dist/Utils/LabelRuleImportExport.js +2 -0
  641. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  642. package/build/dist/Utils/MetricUnitUtil.js +77 -13
  643. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  644. package/build/dist/Utils/Monitor/MetricValueFormatter.js +58 -13
  645. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -1
  646. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js +161 -0
  647. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js.map +1 -0
  648. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js +323 -0
  649. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js.map +1 -0
  650. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js +270 -0
  651. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js.map +1 -0
  652. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +23 -0
  653. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
  654. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +61 -0
  655. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -1
  656. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js +178 -0
  657. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js.map +1 -0
  658. package/build/dist/Utils/Telemetry/EntityKey.js +133 -0
  659. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  660. package/build/dist/Utils/ValueFormatter.js +15 -0
  661. package/build/dist/Utils/ValueFormatter.js.map +1 -1
  662. package/package.json +1 -1
  663. package/tsconfig.tests.json +7 -5
@@ -0,0 +1,2068 @@
1
+ import {
2
+ DATABASE_SYSTEMS,
3
+ DatabaseSystemDescriptor,
4
+ getDatabaseSystemDescriptor,
5
+ getDatabaseSystemFamily,
6
+ getDefaultDatabasePort,
7
+ getMoreSpecificDatabaseSystem,
8
+ isSameDatabaseFamily,
9
+ } from "./DatabaseSystem";
10
+ import { isNonServerCommand } from "./DatabaseContainerCommand";
11
+
12
+ /*
13
+ * "Is this pod / container a database, and which one?" — the pure classifier
14
+ * behind Kubernetes, Docker and Podman database auto-detection.
15
+ *
16
+ * Evidence, strongest first:
17
+ * 1. operator / Helm chart LABELS on a Kubernetes pod (CloudNativePG,
18
+ * Zalando, Crunchy, Percona, the Oracle MySQL operator, ECK, Altinity,
19
+ * cass-operator, the Vitess operator, Bitnami-style
20
+ * `app.kubernetes.io/name`) — they also name the logical cluster, its
21
+ * role and its Services;
22
+ * 2. the container IMAGE, matched on the normalized repository or its
23
+ * basename EXACTLY (never a substring: `acme/redis-cache-warmer` is not
24
+ * Redis). Normalization drops the registry, `library/`, the tag and the
25
+ * digest, folds republishing organisations into the original
26
+ * (`bitnamilegacy/`, `bitnamisecure/` → `bitnami/`) and reads Red Hat
27
+ * Software Collections names (`rhel9/postgresql-16` → `postgresql`, 16).
28
+ *
29
+ * What it refuses, on purpose: exporters, operators, admin UIs, poolers and
30
+ * proxies, backup tools (they run DB images or talk to one but are not one);
31
+ * batch pods (Job / CronJob owners — `pg_dump` runs the postgres image);
32
+ * finished pods; client, debug and Sentinel runs of a database image
33
+ * (`psql`, `redis-cli`, `sleep infinity`, an interactive shell,
34
+ * `redis-sentinel` — only on positive evidence, see
35
+ * DatabaseContainerCommand); an owner-less pod
36
+ * that declares no port (`kubectl run --rm -it psql --image=postgres`);
37
+ * Docker containers started by Testcontainers, `docker compose run` or the
38
+ * kubelet; and an application pod that merely has a database SIDECAR
39
+ * (pod-level telemetry would show the whole app on the database's page).
40
+ *
41
+ * It NEVER reads environment variables: pod specs in the inventory keep
42
+ * direct env values verbatim, passwords included. Only container name,
43
+ * image, declared ports and what a container's command line runs (its
44
+ * program, a shell's flags and the known command names of its script)
45
+ * are read.
46
+ */
47
+
48
+ export type ImageClassification =
49
+ | { kind: "database"; system: string }
50
+ | { kind: "excluded"; reason: string }
51
+ | { kind: "infrastructure-sidecar" }
52
+ | { kind: "unknown" };
53
+
54
+ export interface KubernetesContainerLike {
55
+ name?: string | undefined;
56
+ image?: string | undefined;
57
+ ports?: Array<{ containerPort?: number | undefined }> | undefined;
58
+ /*
59
+ * The container's command and args as Kubernetes has them (the program
60
+ * started is `command` followed by `args`), or the reduction
61
+ * CANDIDATE_PODS_SQL projects as `command` — see DatabaseContainerCommand.
62
+ */
63
+ command?: Array<string | null> | undefined;
64
+ args?: Array<string | null> | undefined;
65
+ }
66
+
67
+ export interface KubernetesPodLike {
68
+ namespaceKey: string;
69
+ name: string;
70
+ phase?: string | null | undefined;
71
+ labels?: Record<string, unknown> | null | undefined;
72
+ ownerReferences?:
73
+ | {
74
+ items?:
75
+ | Array<{ kind?: string | undefined; name?: string | undefined }>
76
+ | undefined;
77
+ }
78
+ | null
79
+ | undefined;
80
+ spec?:
81
+ | {
82
+ containers?: Array<KubernetesContainerLike> | undefined;
83
+ }
84
+ | null
85
+ | undefined;
86
+ }
87
+
88
+ export interface KubernetesDatabaseCandidate {
89
+ system: string;
90
+ namespace: string;
91
+ workloadKind: string;
92
+ workloadName: string;
93
+ /*
94
+ * The workload that owns the pod (a Deployment after its ReplicaSet's
95
+ * pod-template-hash is stripped), whatever identity the database got —
96
+ * a chart-labelled Deployment is a "Cluster" database whose Deployment
97
+ * still scopes telemetry. Null for an owner-less pod.
98
+ */
99
+ ownerKind: string | null;
100
+ ownerName: string | null;
101
+ podName: string;
102
+ containerName: string;
103
+ image: string | null;
104
+ version: string | null;
105
+ ports: Array<number>;
106
+ operator: string | null;
107
+ role: "primary" | "replica" | null;
108
+ serviceNames: Array<string>;
109
+ headlessServiceName: string | null;
110
+ evidence: Array<string>;
111
+ }
112
+
113
+ export interface KubernetesDatabaseGroup {
114
+ system: string;
115
+ namespace: string;
116
+ workloadKind: string;
117
+ workloadName: string;
118
+ podNames: Array<string>;
119
+ image: string | null;
120
+ version: string | null;
121
+ ports: Array<number>;
122
+ operator: string | null;
123
+ serviceNames: Array<string>;
124
+ headlessServiceName: string | null;
125
+ /*
126
+ * Each member pod with the headless Service it resolves under, for
127
+ * `buildKubernetesDatabaseAliases({ podServiceNames })`. Members of one
128
+ * operator cluster can sit behind different StatefulSets.
129
+ */
130
+ podServiceNames: Array<{ podName: string; serviceName: string }>;
131
+ // The workloads owning the member pods (kind + name), deduped and sorted.
132
+ ownerWorkloads: Array<{ kind: string; name: string }>;
133
+ }
134
+
135
+ /*
136
+ * A connection-pooler pod of an operator cluster: not a member, but the
137
+ * Service it backs is one more name clients reach the cluster by.
138
+ */
139
+ export interface KubernetesPoolerService {
140
+ system: string;
141
+ namespace: string;
142
+ clusterName: string;
143
+ serviceName: string;
144
+ }
145
+
146
+ export interface ContainerLike {
147
+ name: string;
148
+ imageName?: string | null | undefined;
149
+ containerId?: string | null | undefined;
150
+ labels?: Record<string, unknown> | null | undefined;
151
+ }
152
+
153
+ /*
154
+ * What a Docker / Podman database's workload is: the Swarm service or the
155
+ * Compose service its replicas were grouped by, else a container of its own.
156
+ * Stored as the database's workloadKind and read back as written ("detected
157
+ * from Compose service shop-db", "Swarm service/mystack_db").
158
+ */
159
+ export enum ContainerWorkloadKind {
160
+ Container = "Container",
161
+ ComposeService = "Compose service",
162
+ SwarmService = "Swarm service",
163
+ }
164
+
165
+ export interface ContainerDatabaseClassification {
166
+ system: string;
167
+ workloadKind: ContainerWorkloadKind;
168
+ workloadName: string;
169
+ containerName: string;
170
+ version: string | null;
171
+ }
172
+
173
+ // ---- images --------------------------------------------------------------
174
+
175
+ interface ExcludedImageGroup {
176
+ reason: string;
177
+ repositories: ReadonlyArray<string>;
178
+ }
179
+
180
+ /*
181
+ * Same matching rule as DatabaseSystemDescriptor.imageRepositories: an entry
182
+ * without "/" is an exact basename, an entry with one is an exact path (or
183
+ * a path suffix at a "/" boundary, for registry mirrors).
184
+ */
185
+ const EXCLUDED_IMAGE_GROUPS: ReadonlyArray<ExcludedImageGroup> = [
186
+ {
187
+ reason: "exporter",
188
+ repositories: [
189
+ "postgres-exporter",
190
+ "postgres_exporter",
191
+ "redis_exporter",
192
+ "mysqld-exporter",
193
+ "mongodb_exporter",
194
+ "elasticsearch-exporter",
195
+ "memcached-exporter",
196
+ ],
197
+ },
198
+ {
199
+ reason: "operator",
200
+ repositories: [
201
+ "cloudnative-pg/cloudnative-pg",
202
+ "zalando/postgres-operator",
203
+ "acid/postgres-operator",
204
+ "crunchydata/postgres-operator",
205
+ "mongodb/mongodb-kubernetes",
206
+ "eck/eck-operator",
207
+ ],
208
+ },
209
+ {
210
+ reason: "admin-ui",
211
+ repositories: [
212
+ "dpage/pgadmin4",
213
+ "pgadmin4",
214
+ "phpmyadmin",
215
+ "adminer",
216
+ "mongo-express",
217
+ "redisinsight",
218
+ "redis-commander",
219
+ "kibana",
220
+ "opensearch-dashboards",
221
+ "cerebro",
222
+ ],
223
+ },
224
+ {
225
+ reason: "pooler",
226
+ repositories: [
227
+ "pgbouncer",
228
+ "crunchydata/crunchy-pgbouncer",
229
+ "pgpool",
230
+ "pgpool2",
231
+ "pgcat",
232
+ "odyssey",
233
+ "supavisor",
234
+ "proxysql",
235
+ "proxysql2",
236
+ "haproxy",
237
+ "mysql-router",
238
+ "twemproxy",
239
+ "gce-proxy",
240
+ "cloud-sql-proxy",
241
+ ],
242
+ },
243
+ {
244
+ reason: "backup",
245
+ repositories: [
246
+ "pgbackrest",
247
+ "crunchydata/crunchy-pgbackrest",
248
+ "barman",
249
+ "wal-g",
250
+ "kubectl",
251
+ "percona-xtrabackup",
252
+ "percona-backup-mongodb",
253
+ "mydumper",
254
+ "postgres-backup-local",
255
+ "postgres-backup-s3",
256
+ "mysql-backup",
257
+ "db-backup",
258
+ ],
259
+ },
260
+ {
261
+ // Part of a database deployment, but not the database process itself.
262
+ reason: "companion",
263
+ repositories: [
264
+ "redis-sentinel",
265
+ "mongodb-agent",
266
+ "mongodb-agent-ubi",
267
+ "k8ssandra/system-logger",
268
+ ],
269
+ },
270
+ ];
271
+
272
+ // Basename suffixes that are never a database, whatever comes before them.
273
+ const EXCLUDED_BASENAME_SUFFIXES: ReadonlyArray<{
274
+ reason: string;
275
+ suffix: string;
276
+ }> = [
277
+ { reason: "exporter", suffix: "-exporter" },
278
+ { reason: "exporter", suffix: "_exporter" },
279
+ { reason: "operator", suffix: "-operator" },
280
+ ];
281
+
282
+ // Mesh proxies, log shippers, VPN / tunnel and monitoring agents that sit next to anything.
283
+ const INFRASTRUCTURE_SIDECAR_REPOSITORIES: ReadonlyArray<string> = [
284
+ "proxyv2",
285
+ "linkerd/proxy",
286
+ "linkerd2-proxy",
287
+ "envoy",
288
+ "envoyproxy/envoy",
289
+ "aws-appmesh-envoy",
290
+ "hashicorp/vault",
291
+ "hashicorp/consul-dataplane",
292
+ "fluent-bit",
293
+ "fluentd",
294
+ "filebeat",
295
+ "promtail",
296
+ "opentelemetry-collector",
297
+ "opentelemetry-collector-contrib",
298
+ "opentelemetry-collector-k8s",
299
+ "splunk-otel-collector",
300
+ "busybox",
301
+ "cadvisor",
302
+ "datadog/agent",
303
+ "datadoghq/agent",
304
+ "grafana/agent",
305
+ "grafana/alloy",
306
+ "timberio/vector",
307
+ "vectordotdev/vector",
308
+ "jaeger-agent",
309
+ "pmm-client",
310
+ "aws-xray-daemon",
311
+ "daprd",
312
+ "kuma-dp",
313
+ "tailscale/tailscale",
314
+ "cloudflare/cloudflared",
315
+ "bitnami/os-shell",
316
+ "pause",
317
+ ];
318
+
319
+ /*
320
+ * Anchored basename patterns for images whose name carries a version, so no
321
+ * finite exact list covers them (Zalando Spilo: `spilo-15`, `spilo-16`).
322
+ * Still whole-basename matches, never substrings.
323
+ */
324
+ const DATABASE_BASENAME_PATTERNS: ReadonlyArray<{
325
+ system: string;
326
+ pattern: RegExp;
327
+ }> = [{ system: "postgresql", pattern: /^spilo(?:-\d+)?$/ }];
328
+
329
+ const IMAGE_REPOSITORY_REGEX: RegExp = /^[a-z0-9._/-]+$/;
330
+
331
+ // A bare image id (untagged docker_stats image) names no repository.
332
+ const BARE_IMAGE_ID_REGEX: RegExp = /^(?:sha256:)?[0-9a-f]{12,64}$/;
333
+
334
+ /*
335
+ * Organisations that republish another organisation's images unchanged:
336
+ * Bitnami's catalogue moved to `bitnamilegacy/` (frozen tags) and
337
+ * `bitnamisecure/` (hardened builds), under every registry that mirrors it
338
+ * (`docker.io/`, `public.ecr.aws/`, a Harbor proxy …).
339
+ */
340
+ const IMAGE_ORGANIZATION_ALIASES: ReadonlyMap<string, string> = new Map<
341
+ string,
342
+ string
343
+ >([
344
+ ["bitnamilegacy", "bitnami"],
345
+ ["bitnamisecure", "bitnami"],
346
+ ]);
347
+
348
+ /*
349
+ * Red Hat Software Collections put the engine AND its version in the image
350
+ * name and the image build in the tag: `registry.redhat.io/rhel9/postgresql-16:1-54`,
351
+ * `quay.io/sclorg/mysql-80-c9s`, `centos/postgresql-96-centos7`. Recognised
352
+ * only under these namespaces, so `bitnami/postgresql-repmgr` or an
353
+ * application called `api-2` keeps its name.
354
+ */
355
+ const SOFTWARE_COLLECTION_NAMESPACE_REGEX: RegExp =
356
+ /^(?:rhel\d*|rhscl|sclorg|centos\d*|fedora)$/;
357
+ const SOFTWARE_COLLECTION_PLATFORM_REGEX: RegExp =
358
+ /^(?:rhel\d+|el\d+|centos\d+|c\d+s|ubi\d+|fedora)$/;
359
+ const SOFTWARE_COLLECTION_VERSION_REGEX: RegExp = /^\d{1,4}$/;
360
+ const SOFTWARE_COLLECTION_ENGINE_REGEX: RegExp = /^[a-z][a-z0-9]*$/;
361
+
362
+ // Red Hat UBI rebuilds carry a `-ubi` / `-ubi8` basename suffix.
363
+ const UBI_BASENAME_SUFFIX_REGEX: RegExp = /-ubi\d*$/;
364
+
365
+ /*
366
+ * Version sources in an image reference: a Spilo image name carries the
367
+ * PostgreSQL major, a Percona tag carries "ppg<version>", any other tag
368
+ * starts with it.
369
+ */
370
+ const SPILO_IMAGE_REGEX: RegExp = /^spilo-(\d+)$/;
371
+ const PERCONA_POSTGRESQL_TAG_REGEX: RegExp = /(?:^|-)ppg(\d+(?:\.\d+)?)(?:-|$)/;
372
+ const IMAGE_TAG_VERSION_REGEX: RegExp = /^v?(\d+(?:\.\d+){0,2})/;
373
+
374
+ interface SplitImageReference {
375
+ repository: string;
376
+ tag: string | null;
377
+ // Engine version carried by a Software Collections image NAME.
378
+ nameVersion: string | null;
379
+ }
380
+
381
+ /*
382
+ * `rhel9/postgresql-16` → { engine "postgresql", digits "16" }; null for any
383
+ * basename that is not `<engine>-<1-4 digits>[-<platform>]`.
384
+ */
385
+ function parseSoftwareCollectionBasename(
386
+ basename: string,
387
+ ): { engine: string; digits: string } | null {
388
+ const parts: Array<string> = basename.split("-");
389
+ if (
390
+ parts.length > 2 &&
391
+ SOFTWARE_COLLECTION_PLATFORM_REGEX.test(parts[parts.length - 1]!)
392
+ ) {
393
+ parts.pop();
394
+ }
395
+ if (parts.length !== 2) {
396
+ return null;
397
+ }
398
+ const engine: string = parts[0]!;
399
+ const digits: string = parts[1]!;
400
+ if (
401
+ !SOFTWARE_COLLECTION_ENGINE_REGEX.test(engine) ||
402
+ !SOFTWARE_COLLECTION_VERSION_REGEX.test(digits)
403
+ ) {
404
+ return null;
405
+ }
406
+ return { engine, digits };
407
+ }
408
+
409
+ /*
410
+ * Software Collections spell versions without the dot: `postgresql-96` is
411
+ * 9.6 but `postgresql-16` is 16, `mysql-80` is 8.0, `mariadb-105` 10.5 and
412
+ * `mariadb-1011` 10.11.
413
+ */
414
+ function formatSoftwareCollectionVersion(
415
+ engine: string,
416
+ digits: string,
417
+ ): string {
418
+ if (engine === "postgresql") {
419
+ return digits.length === 2 && digits.startsWith("9")
420
+ ? `9.${digits.substring(1)}`
421
+ : digits;
422
+ }
423
+ if (digits.length === 2) {
424
+ return `${digits.substring(0, 1)}.${digits.substring(1)}`;
425
+ }
426
+ if (digits.length === 3) {
427
+ return `${digits.substring(0, 2)}.${digits.substring(2)}`;
428
+ }
429
+ if (digits.length === 4) {
430
+ return `${digits.substring(0, 2)}.${digits.substring(2)}`;
431
+ }
432
+ return digits;
433
+ }
434
+
435
+ function splitImageReference(image: unknown): SplitImageReference | null {
436
+ if (typeof image !== "string") {
437
+ return null;
438
+ }
439
+
440
+ let value: string = image.trim().toLowerCase();
441
+ if (!value || BARE_IMAGE_ID_REGEX.test(value)) {
442
+ return null;
443
+ }
444
+
445
+ const digestIndex: number = value.indexOf("@");
446
+ if (digestIndex >= 0) {
447
+ value = value.substring(0, digestIndex);
448
+ }
449
+
450
+ let tag: string | null = null;
451
+ const tagIndex: number = value.lastIndexOf(":");
452
+ if (tagIndex > value.lastIndexOf("/")) {
453
+ tag = value.substring(tagIndex + 1) || null;
454
+ value = value.substring(0, tagIndex);
455
+ }
456
+
457
+ const segments: Array<string> = value
458
+ .split("/")
459
+ .filter((segment: string): boolean => {
460
+ return segment.length > 0;
461
+ });
462
+
463
+ // Registry host: the first segment when it has a "." or ":" or is localhost.
464
+ if (
465
+ segments.length > 1 &&
466
+ (segments[0]!.includes(".") ||
467
+ segments[0]!.includes(":") ||
468
+ segments[0] === "localhost")
469
+ ) {
470
+ segments.shift();
471
+ }
472
+ if (segments.length > 1 && segments[0] === "library") {
473
+ segments.shift();
474
+ }
475
+
476
+ // Republishing organisations → the original (never the basename itself).
477
+ for (let index: number = 0; index < segments.length - 1; index++) {
478
+ const alias: string | undefined = IMAGE_ORGANIZATION_ALIASES.get(
479
+ segments[index]!,
480
+ );
481
+ if (alias) {
482
+ segments[index] = alias;
483
+ }
484
+ }
485
+
486
+ let nameVersion: string | null = null;
487
+ const lastIndex: number = segments.length - 1;
488
+
489
+ if (lastIndex >= 0) {
490
+ let basename: string = segments[lastIndex]!;
491
+
492
+ const withoutUbi: string = basename.replace(UBI_BASENAME_SUFFIX_REGEX, "");
493
+ if (withoutUbi && withoutUbi !== basename) {
494
+ basename = withoutUbi;
495
+ }
496
+
497
+ if (
498
+ lastIndex >= 1 &&
499
+ SOFTWARE_COLLECTION_NAMESPACE_REGEX.test(segments[lastIndex - 1]!)
500
+ ) {
501
+ const collection: { engine: string; digits: string } | null =
502
+ parseSoftwareCollectionBasename(basename);
503
+ if (collection) {
504
+ basename = collection.engine;
505
+ nameVersion = formatSoftwareCollectionVersion(
506
+ collection.engine,
507
+ collection.digits,
508
+ );
509
+ }
510
+ }
511
+
512
+ segments[lastIndex] = basename;
513
+ }
514
+
515
+ const repository: string = segments.join("/");
516
+ if (!repository || !IMAGE_REPOSITORY_REGEX.test(repository)) {
517
+ return null;
518
+ }
519
+
520
+ return { repository, tag, nameVersion };
521
+ }
522
+
523
+ function basenameOf(repository: string): string {
524
+ return repository.substring(repository.lastIndexOf("/") + 1);
525
+ }
526
+
527
+ function repositoryMatches(repository: string, entry: string): boolean {
528
+ if (entry.includes("/")) {
529
+ return repository === entry || repository.endsWith(`/${entry}`);
530
+ }
531
+ return basenameOf(repository) === entry;
532
+ }
533
+
534
+ function matchesAny(
535
+ repository: string,
536
+ entries: ReadonlyArray<string>,
537
+ ): boolean {
538
+ for (const entry of entries) {
539
+ if (repositoryMatches(repository, entry)) {
540
+ return true;
541
+ }
542
+ }
543
+ return false;
544
+ }
545
+
546
+ /**
547
+ * The image's repository path without registry host, `library/`, tag or
548
+ * digest, lowercased: `docker.io/library/postgres:16` → `postgres`,
549
+ * `ghcr.io/cloudnative-pg/postgresql:16.2@sha256:…` →
550
+ * `cloudnative-pg/postgresql`. Republished images read as the original
551
+ * (`bitnamilegacy/redis-cluster` → `bitnami/redis-cluster`), a Red Hat
552
+ * Software Collections name loses its version and platform
553
+ * (`registry.redhat.io/rhel9/postgresql-16` → `rhel9/postgresql`) and a UBI
554
+ * rebuild its `-ubi` suffix. Null for an empty value or a bare image id.
555
+ */
556
+ export function normalizeImageRepository(image: unknown): string | null {
557
+ const split: SplitImageReference | null = splitImageReference(image);
558
+ return split ? split.repository : null;
559
+ }
560
+
561
+ /**
562
+ * Best-effort engine version from the image: the leading
563
+ * `major[.minor[.patch]]` of the tag (`16.2-alpine` → `16.2`,
564
+ * `16.2.0-debian-12-r5` → `16.2.0`), the Postgres major of a Spilo image
565
+ * (`spilo-16` → `16`) or of a Percona `…-ppg16-postgres` tag, and the
566
+ * version in a Software Collections name (`rhel9/mysql-80` → `8.0`; its tag
567
+ * is the image build, never read). Null for `latest`, a digest-only
568
+ * reference or a tag that does not start with a number.
569
+ */
570
+ export function parseImageVersion(image: unknown): string | null {
571
+ const split: SplitImageReference | null = splitImageReference(image);
572
+ if (!split) {
573
+ return null;
574
+ }
575
+
576
+ if (split.nameVersion) {
577
+ return split.nameVersion;
578
+ }
579
+
580
+ const spilo: RegExpExecArray | null = SPILO_IMAGE_REGEX.exec(
581
+ basenameOf(split.repository),
582
+ );
583
+ if (spilo) {
584
+ return spilo[1]!;
585
+ }
586
+
587
+ if (!split.tag) {
588
+ return null;
589
+ }
590
+
591
+ const percona: RegExpExecArray | null = PERCONA_POSTGRESQL_TAG_REGEX.exec(
592
+ split.tag,
593
+ );
594
+ if (percona) {
595
+ return percona[1]!;
596
+ }
597
+
598
+ const version: RegExpExecArray | null = IMAGE_TAG_VERSION_REGEX.exec(
599
+ split.tag,
600
+ );
601
+ return version ? version[1]! : null;
602
+ }
603
+
604
+ /**
605
+ * What a container image is: a known database engine, something that must
606
+ * not count as one (with the reason), an infrastructure sidecar, or unknown.
607
+ */
608
+ export function classifyImage(image: unknown): ImageClassification {
609
+ const repository: string | null = normalizeImageRepository(image);
610
+ if (!repository) {
611
+ return { kind: "unknown" };
612
+ }
613
+
614
+ for (const group of EXCLUDED_IMAGE_GROUPS) {
615
+ if (matchesAny(repository, group.repositories)) {
616
+ return { kind: "excluded", reason: group.reason };
617
+ }
618
+ }
619
+
620
+ const basename: string = basenameOf(repository);
621
+ for (const excluded of EXCLUDED_BASENAME_SUFFIXES) {
622
+ if (basename.endsWith(excluded.suffix)) {
623
+ return { kind: "excluded", reason: excluded.reason };
624
+ }
625
+ }
626
+
627
+ if (matchesAny(repository, INFRASTRUCTURE_SIDECAR_REPOSITORIES)) {
628
+ return { kind: "infrastructure-sidecar" };
629
+ }
630
+
631
+ for (const descriptor of DATABASE_SYSTEMS) {
632
+ if (matchesAny(repository, descriptor.imageRepositories)) {
633
+ return { kind: "database", system: descriptor.system };
634
+ }
635
+ }
636
+
637
+ for (const entry of DATABASE_BASENAME_PATTERNS) {
638
+ if (entry.pattern.test(basename)) {
639
+ return { kind: "database", system: entry.system };
640
+ }
641
+ }
642
+
643
+ return { kind: "unknown" };
644
+ }
645
+
646
+ // ---- Kubernetes pods -----------------------------------------------------
647
+
648
+ interface ContainerView {
649
+ name: string;
650
+ image: string | null;
651
+ ports: Array<number>;
652
+ nonServer: boolean;
653
+ classification: ImageClassification;
654
+ }
655
+
656
+ interface OperatorMatch {
657
+ operator: string | null;
658
+ system: string;
659
+ // Null → name the workload after the pod's owner instead.
660
+ clusterName: string | null;
661
+ role: "primary" | "replica" | null;
662
+ serviceNames: Array<string>;
663
+ evidence: string;
664
+ }
665
+
666
+ type OperatorRuleResult = OperatorMatch | "skip" | null;
667
+
668
+ type OperatorRule = (labels: Record<string, unknown>) => OperatorRuleResult;
669
+
670
+ const SKIPPED_POD_PHASES: ReadonlySet<string> = new Set<string>([
671
+ "succeeded",
672
+ "failed",
673
+ ]);
674
+
675
+ const BATCH_OWNER_KINDS: ReadonlySet<string> = new Set<string>([
676
+ "job",
677
+ "cronjob",
678
+ ]);
679
+
680
+ // Owner kinds whose name conventionally is also a Service's name.
681
+ const SERVICE_NAMED_OWNER_KINDS: ReadonlySet<string> = new Set<string>([
682
+ "StatefulSet",
683
+ "Deployment",
684
+ "DaemonSet",
685
+ ]);
686
+
687
+ /*
688
+ * `app.kubernetes.io/component` values of pods that carry a database's
689
+ * cluster/chart labels but are not a database member: poolers, proxies,
690
+ * routers, exporters, orchestrators, backup and monitoring pods.
691
+ */
692
+ const NON_MEMBER_COMPONENTS: ReadonlySet<string> = new Set<string>([
693
+ "pgpool",
694
+ "pgbouncer",
695
+ "proxysql",
696
+ "haproxy",
697
+ "mongos",
698
+ "metrics",
699
+ "exporter",
700
+ "router",
701
+ "mysqlrouter",
702
+ "orc",
703
+ "orchestrator",
704
+ "backup",
705
+ "pmm",
706
+ ]);
707
+
708
+ /*
709
+ * Exclusion reasons that make a pod a non-member on their own. "operator"
710
+ * and "companion" are deliberately absent: Percona's PostgreSQL operator
711
+ * ships its postgres operand in the operator's image repository, and a
712
+ * Sentinel / agent container never runs alone in a member pod anyway.
713
+ */
714
+ const NON_MEMBER_IMAGE_REASONS: ReadonlySet<string> = new Set<string>([
715
+ "pooler",
716
+ "exporter",
717
+ "admin-ui",
718
+ "backup",
719
+ ]);
720
+
721
+ /*
722
+ * False for a container whose image is never the database process itself:
723
+ * a mesh proxy / log shipper, or an image excluded as a pooler, exporter,
724
+ * admin UI, backup tool or companion (Sentinel, agent). An "operator" image
725
+ * still could be — Percona ships its postgres operand in one.
726
+ */
727
+ function couldBeDatabaseProcess(container: ContainerView): boolean {
728
+ const classification: ImageClassification = container.classification;
729
+ if (classification.kind === "infrastructure-sidecar") {
730
+ return false;
731
+ }
732
+ return !(
733
+ classification.kind === "excluded" &&
734
+ (NON_MEMBER_IMAGE_REASONS.has(classification.reason) ||
735
+ classification.reason === "companion")
736
+ );
737
+ }
738
+
739
+ const PRIMARY_ROLE_VALUES: ReadonlySet<string> = new Set<string>([
740
+ "primary",
741
+ "master",
742
+ ]);
743
+
744
+ const REPLICA_ROLE_VALUES: ReadonlySet<string> = new Set<string>([
745
+ "replica",
746
+ "replicas",
747
+ "read",
748
+ "secondary",
749
+ "slave",
750
+ "standby",
751
+ ]);
752
+
753
+ function labelValue(
754
+ labels: Record<string, unknown>,
755
+ key: string,
756
+ ): string | null {
757
+ if (!Object.prototype.hasOwnProperty.call(labels, key)) {
758
+ return null;
759
+ }
760
+ const value: unknown = labels[key];
761
+ if (typeof value === "string") {
762
+ const trimmed: string = value.trim();
763
+ return trimmed.length > 0 ? trimmed : null;
764
+ }
765
+ if (typeof value === "number" || typeof value === "boolean") {
766
+ return String(value);
767
+ }
768
+ return null;
769
+ }
770
+
771
+ function hasLabel(labels: Record<string, unknown>, key: string): boolean {
772
+ return Object.prototype.hasOwnProperty.call(labels, key);
773
+ }
774
+
775
+ function roleFrom(value: string | null): "primary" | "replica" | null {
776
+ if (!value) {
777
+ return null;
778
+ }
779
+ const normalized: string = value.trim().toLowerCase();
780
+ if (PRIMARY_ROLE_VALUES.has(normalized)) {
781
+ return "primary";
782
+ }
783
+ if (REPLICA_ROLE_VALUES.has(normalized)) {
784
+ return "replica";
785
+ }
786
+ return null;
787
+ }
788
+
789
+ const CHART_SYSTEM_BY_NAME: ReadonlyMap<string, string> = ((): Map<
790
+ string,
791
+ string
792
+ > => {
793
+ const map: Map<string, string> = new Map<string, string>();
794
+ for (const descriptor of DATABASE_SYSTEMS) {
795
+ for (const chartName of descriptor.kubernetesChartNames) {
796
+ // Labels are compared lowercased.
797
+ map.set(chartName.toLowerCase(), descriptor.system);
798
+ }
799
+ }
800
+ return map;
801
+ })();
802
+
803
+ const PERCONA_SYSTEM_BY_NAME: ReadonlyMap<string, string> = new Map<
804
+ string,
805
+ string
806
+ >([
807
+ ["percona-xtradb-cluster", "mysql"],
808
+ ["percona-server", "mysql"],
809
+ ["percona-server-mongodb", "mongodb"],
810
+ ]);
811
+
812
+ /**
813
+ * Label keys whose presence makes an operator rule look at a pod. With
814
+ * DATABASE_WORKLOAD_NAME_LABEL_VALUES this is the whole trigger surface of
815
+ * the label rules, so a store can pre-select the pods worth classifying
816
+ * (`labels ?| keys OR lower(labels->>'app.kubernetes.io/name') = ANY(values)`)
817
+ * — see hasDatabaseWorkloadLabels, which is the same test in TypeScript.
818
+ */
819
+ export const DATABASE_OPERATOR_LABEL_KEYS: ReadonlyArray<string> = [
820
+ "cnpg.io/cluster",
821
+ "cluster-name",
822
+ "postgres-operator.crunchydata.com/cluster",
823
+ "mysql.oracle.com/cluster",
824
+ "elasticsearch.k8s.elastic.co/cluster-name",
825
+ "clickhouse.altinity.com/chi",
826
+ "cassandra.datastax.com/cluster",
827
+ "planetscale.com/cluster",
828
+ ];
829
+
830
+ // Lowercase `app.kubernetes.io/name` values a chart / Percona rule matches.
831
+ export const DATABASE_WORKLOAD_NAME_LABEL_VALUES: ReadonlyArray<string> =
832
+ Array.from(
833
+ new Set<string>([
834
+ ...Array.from(CHART_SYSTEM_BY_NAME.keys()),
835
+ ...Array.from(PERCONA_SYSTEM_BY_NAME.keys()),
836
+ ]),
837
+ ).sort();
838
+
839
+ /**
840
+ * True when a pod's labels could make an operator / chart rule match — the
841
+ * TypeScript twin of the store-side pre-filter over
842
+ * DATABASE_OPERATOR_LABEL_KEYS and DATABASE_WORKLOAD_NAME_LABEL_VALUES.
843
+ */
844
+ export function hasDatabaseWorkloadLabels(labels: unknown): boolean {
845
+ if (!labels || typeof labels !== "object" || Array.isArray(labels)) {
846
+ return false;
847
+ }
848
+ const record: Record<string, unknown> = labels as Record<string, unknown>;
849
+ for (const key of DATABASE_OPERATOR_LABEL_KEYS) {
850
+ if (hasLabel(record, key)) {
851
+ return true;
852
+ }
853
+ }
854
+ const name: string | null = labelValue(record, "app.kubernetes.io/name");
855
+ return (
856
+ name !== null &&
857
+ DATABASE_WORKLOAD_NAME_LABEL_VALUES.includes(name.toLowerCase())
858
+ );
859
+ }
860
+
861
+ /*
862
+ * Operator / chart label rules, most specific first. Each returns a match,
863
+ * "skip" (the pod belongs to a database cluster but is not a member — a
864
+ * pooler, a backup repo host), or null (not this operator). Label keys and
865
+ * Service naming follow each operator's documented conventions, including
866
+ * the proxy / pooler Services clients usually connect through.
867
+ */
868
+ const OPERATOR_RULES: ReadonlyArray<OperatorRule> = [
869
+ // CloudNativePG.
870
+ (labels: Record<string, unknown>): OperatorRuleResult => {
871
+ const cluster: string | null = labelValue(labels, "cnpg.io/cluster");
872
+ if (!cluster) {
873
+ return null;
874
+ }
875
+ const podRole: string | null = labelValue(labels, "cnpg.io/podRole");
876
+ if (
877
+ (podRole && podRole.toLowerCase() !== "instance") ||
878
+ labelValue(labels, "cnpg.io/poolerName")
879
+ ) {
880
+ return "skip";
881
+ }
882
+ return {
883
+ operator: "cloudnative-pg",
884
+ system: "postgresql",
885
+ clusterName: cluster,
886
+ role: roleFrom(
887
+ labelValue(labels, "cnpg.io/instanceRole") ||
888
+ labelValue(labels, "role"),
889
+ ),
890
+ serviceNames: [`${cluster}-rw`, `${cluster}-ro`, `${cluster}-r`],
891
+ evidence: `label:cnpg.io/cluster=${cluster}`,
892
+ };
893
+ },
894
+
895
+ // Zalando postgres-operator (Spilo).
896
+ (labels: Record<string, unknown>): OperatorRuleResult => {
897
+ const application: string | null = labelValue(labels, "application");
898
+ const cluster: string | null = labelValue(labels, "cluster-name");
899
+ if (!application || application.toLowerCase() !== "spilo" || !cluster) {
900
+ return null;
901
+ }
902
+ return {
903
+ operator: "zalando",
904
+ system: "postgresql",
905
+ clusterName: cluster,
906
+ role: roleFrom(labelValue(labels, "spilo-role")),
907
+ serviceNames: [
908
+ cluster,
909
+ `${cluster}-repl`,
910
+ `${cluster}-pooler`,
911
+ `${cluster}-pooler-repl`,
912
+ ],
913
+ evidence: `label:cluster-name=${cluster}`,
914
+ };
915
+ },
916
+
917
+ // Crunchy Data PGO v5 (also Percona's PostgreSQL operator v2, a fork).
918
+ (labels: Record<string, unknown>): OperatorRuleResult => {
919
+ const cluster: string | null = labelValue(
920
+ labels,
921
+ "postgres-operator.crunchydata.com/cluster",
922
+ );
923
+ if (!cluster) {
924
+ return null;
925
+ }
926
+ const role: string | null = labelValue(
927
+ labels,
928
+ "postgres-operator.crunchydata.com/role",
929
+ );
930
+ if (
931
+ (role && ["pgbouncer", "pgadmin"].includes(role.toLowerCase())) ||
932
+ hasLabel(
933
+ labels,
934
+ "postgres-operator.crunchydata.com/pgbackrest-dedicated",
935
+ ) ||
936
+ hasLabel(labels, "postgres-operator.crunchydata.com/pgadmin")
937
+ ) {
938
+ return "skip";
939
+ }
940
+ return {
941
+ operator: "crunchy",
942
+ system: "postgresql",
943
+ clusterName: cluster,
944
+ role: roleFrom(role),
945
+ serviceNames: [
946
+ `${cluster}-primary`,
947
+ `${cluster}-replicas`,
948
+ `${cluster}-ha`,
949
+ `${cluster}-pgbouncer`,
950
+ ],
951
+ evidence: `label:postgres-operator.crunchydata.com/cluster=${cluster}`,
952
+ };
953
+ },
954
+
955
+ // Percona XtraDB Cluster / Server for MySQL / Server for MongoDB operators.
956
+ (labels: Record<string, unknown>): OperatorRuleResult => {
957
+ const managedBy: string | null = labelValue(
958
+ labels,
959
+ "app.kubernetes.io/managed-by",
960
+ );
961
+ const name: string | null = labelValue(labels, "app.kubernetes.io/name");
962
+ const instance: string | null = labelValue(
963
+ labels,
964
+ "app.kubernetes.io/instance",
965
+ );
966
+ if (
967
+ !managedBy ||
968
+ !managedBy.toLowerCase().startsWith("percona-") ||
969
+ !name ||
970
+ !instance
971
+ ) {
972
+ return null;
973
+ }
974
+ const system: string | undefined = PERCONA_SYSTEM_BY_NAME.get(
975
+ name.toLowerCase(),
976
+ );
977
+ if (!system) {
978
+ return null;
979
+ }
980
+ const serviceNames: Array<string> = [];
981
+ const normalizedName: string = name.toLowerCase();
982
+ if (normalizedName === "percona-xtradb-cluster") {
983
+ serviceNames.push(
984
+ `${instance}-pxc`,
985
+ `${instance}-haproxy`,
986
+ `${instance}-haproxy-replicas`,
987
+ `${instance}-proxysql`,
988
+ );
989
+ } else if (normalizedName === "percona-server") {
990
+ serviceNames.push(
991
+ `${instance}-mysql`,
992
+ `${instance}-mysql-primary`,
993
+ `${instance}-haproxy`,
994
+ `${instance}-router`,
995
+ );
996
+ } else {
997
+ const replicaSet: string | null = labelValue(
998
+ labels,
999
+ "app.kubernetes.io/replset",
1000
+ );
1001
+ if (replicaSet) {
1002
+ serviceNames.push(`${instance}-${replicaSet}`);
1003
+ }
1004
+ serviceNames.push(`${instance}-mongos`);
1005
+ }
1006
+ return {
1007
+ operator: "percona",
1008
+ system,
1009
+ clusterName: instance,
1010
+ role: null,
1011
+ serviceNames,
1012
+ evidence: `label:app.kubernetes.io/name=${name}`,
1013
+ };
1014
+ },
1015
+
1016
+ // Oracle MySQL Operator (InnoDB Cluster).
1017
+ (labels: Record<string, unknown>): OperatorRuleResult => {
1018
+ const cluster: string | null = labelValue(
1019
+ labels,
1020
+ "mysql.oracle.com/cluster",
1021
+ );
1022
+ if (!cluster) {
1023
+ return null;
1024
+ }
1025
+ const component: string | null = labelValue(labels, "component");
1026
+ if (component && NON_MEMBER_COMPONENTS.has(component.toLowerCase())) {
1027
+ return "skip";
1028
+ }
1029
+ const clusterRole: string | null = labelValue(
1030
+ labels,
1031
+ "mysql.oracle.com/cluster-role",
1032
+ );
1033
+ return {
1034
+ operator: "mysql-operator",
1035
+ system: "mysql",
1036
+ clusterName: cluster,
1037
+ role: roleFrom(clusterRole),
1038
+ serviceNames: [`${cluster}-instances`],
1039
+ evidence: `label:mysql.oracle.com/cluster=${cluster}`,
1040
+ };
1041
+ },
1042
+
1043
+ // Elastic Cloud on Kubernetes.
1044
+ (labels: Record<string, unknown>): OperatorRuleResult => {
1045
+ const type: string | null = labelValue(
1046
+ labels,
1047
+ "common.k8s.elastic.co/type",
1048
+ );
1049
+ const cluster: string | null = labelValue(
1050
+ labels,
1051
+ "elasticsearch.k8s.elastic.co/cluster-name",
1052
+ );
1053
+ if (!type || type.toLowerCase() !== "elasticsearch" || !cluster) {
1054
+ return null;
1055
+ }
1056
+ return {
1057
+ operator: "eck",
1058
+ system: "elasticsearch",
1059
+ clusterName: cluster,
1060
+ role: null,
1061
+ serviceNames: [`${cluster}-es-http`],
1062
+ evidence: `label:elasticsearch.k8s.elastic.co/cluster-name=${cluster}`,
1063
+ };
1064
+ },
1065
+
1066
+ // Altinity ClickHouse operator.
1067
+ (labels: Record<string, unknown>): OperatorRuleResult => {
1068
+ const installation: string | null = labelValue(
1069
+ labels,
1070
+ "clickhouse.altinity.com/chi",
1071
+ );
1072
+ if (!installation) {
1073
+ return null;
1074
+ }
1075
+ return {
1076
+ operator: "altinity",
1077
+ system: "clickhouse",
1078
+ clusterName: installation,
1079
+ role: null,
1080
+ serviceNames: [`clickhouse-${installation}`],
1081
+ evidence: `label:clickhouse.altinity.com/chi=${installation}`,
1082
+ };
1083
+ },
1084
+
1085
+ // K8ssandra cass-operator.
1086
+ (labels: Record<string, unknown>): OperatorRuleResult => {
1087
+ const cluster: string | null = labelValue(
1088
+ labels,
1089
+ "cassandra.datastax.com/cluster",
1090
+ );
1091
+ if (!cluster) {
1092
+ return null;
1093
+ }
1094
+ const datacenter: string | null = labelValue(
1095
+ labels,
1096
+ "cassandra.datastax.com/datacenter",
1097
+ );
1098
+ return {
1099
+ operator: "cass-operator",
1100
+ system: "cassandra",
1101
+ clusterName: cluster,
1102
+ role: null,
1103
+ serviceNames: datacenter
1104
+ ? [
1105
+ `${cluster}-${datacenter}-service`,
1106
+ `${cluster}-${datacenter}-all-pods-service`,
1107
+ ]
1108
+ : [],
1109
+ evidence: `label:cassandra.datastax.com/cluster=${cluster}`,
1110
+ };
1111
+ },
1112
+
1113
+ /*
1114
+ * The Vitess operator (PlanetScale). It runs every component on the same
1115
+ * `vitess/lite` image, so only `planetscale.com/component` tells them
1116
+ * apart. The database clients connect to is vtgate, speaking MySQL; the
1117
+ * vttablets (each with its own mysqld), vtctld, vtorc, vtbackup, vtadmin,
1118
+ * the etcd lockserver and the backup-storage subcontroller are parts of
1119
+ * the VitessCluster, not members. vtgate Services carry a hash suffix
1120
+ * (`<cluster>-vtgate-<hash>`), so none are guessed: the owner
1121
+ * Deployment's name is added, as for any Deployment.
1122
+ */
1123
+ (labels: Record<string, unknown>): OperatorRuleResult => {
1124
+ const cluster: string | null = labelValue(
1125
+ labels,
1126
+ "planetscale.com/cluster",
1127
+ );
1128
+ if (!cluster) {
1129
+ return null;
1130
+ }
1131
+ const component: string | null = labelValue(
1132
+ labels,
1133
+ "planetscale.com/component",
1134
+ );
1135
+ if (!component || component.toLowerCase() !== "vtgate") {
1136
+ return "skip";
1137
+ }
1138
+ return {
1139
+ operator: "vitess-operator",
1140
+ system: "vitess",
1141
+ clusterName: cluster,
1142
+ role: null,
1143
+ serviceNames: [],
1144
+ evidence: `label:planetscale.com/cluster=${cluster}`,
1145
+ };
1146
+ },
1147
+
1148
+ // Bitnami and most other Helm charts (`app.kubernetes.io/name`).
1149
+ (labels: Record<string, unknown>): OperatorRuleResult => {
1150
+ const chartName: string | null = labelValue(
1151
+ labels,
1152
+ "app.kubernetes.io/name",
1153
+ );
1154
+ if (!chartName) {
1155
+ return null;
1156
+ }
1157
+ const chart: string = chartName.toLowerCase();
1158
+ const system: string | undefined = CHART_SYSTEM_BY_NAME.get(chart);
1159
+ if (!system) {
1160
+ return null;
1161
+ }
1162
+
1163
+ const instance: string | null = labelValue(
1164
+ labels,
1165
+ "app.kubernetes.io/instance",
1166
+ );
1167
+ const component: string | null = labelValue(
1168
+ labels,
1169
+ "app.kubernetes.io/component",
1170
+ );
1171
+
1172
+ /*
1173
+ * Helm's conventional fullname: the release name when it already
1174
+ * contains the chart name, else `${release}-${chart}`. A
1175
+ * `fullnameOverride` breaks the guess; the owner StatefulSet's name and
1176
+ * its headless Service (added for every pod) still name the real ones.
1177
+ */
1178
+ const fullName: string | null = instance
1179
+ ? instance.toLowerCase().includes(chart)
1180
+ ? instance
1181
+ : `${instance}-${chart}`
1182
+ : null;
1183
+
1184
+ const serviceNames: Array<string> = [];
1185
+ if (fullName) {
1186
+ serviceNames.push(`${fullName}-headless`, `${fullName}-hl`);
1187
+ if (component) {
1188
+ serviceNames.push(`${fullName}-${component.toLowerCase()}`);
1189
+ if (component.toLowerCase() === "replica") {
1190
+ serviceNames.push(`${fullName}-replicas`);
1191
+ }
1192
+ }
1193
+ }
1194
+
1195
+ const managedBy: string | null = labelValue(
1196
+ labels,
1197
+ "app.kubernetes.io/managed-by",
1198
+ );
1199
+
1200
+ return {
1201
+ operator: managedBy && managedBy.toLowerCase() === "helm" ? "helm" : null,
1202
+ system,
1203
+ clusterName: fullName,
1204
+ role: roleFrom(component),
1205
+ serviceNames,
1206
+ evidence: `label:app.kubernetes.io/name=${chartName}`,
1207
+ };
1208
+ },
1209
+ ];
1210
+
1211
+ function readPorts(declaredPorts: unknown): Array<number> {
1212
+ const ports: Array<number> = [];
1213
+ if (!Array.isArray(declaredPorts)) {
1214
+ return ports;
1215
+ }
1216
+ for (const declared of declaredPorts) {
1217
+ const port: unknown =
1218
+ declared && typeof declared === "object"
1219
+ ? (declared as { containerPort?: unknown }).containerPort
1220
+ : undefined;
1221
+ if (
1222
+ typeof port === "number" &&
1223
+ Number.isInteger(port) &&
1224
+ port >= 1 &&
1225
+ port <= 65535 &&
1226
+ !ports.includes(port)
1227
+ ) {
1228
+ ports.push(port);
1229
+ }
1230
+ }
1231
+ return ports;
1232
+ }
1233
+
1234
+ /*
1235
+ * Reads ONLY name, image, declared ports and what command/args run —
1236
+ * never `env` (see the file header), never by spreading the container
1237
+ * object.
1238
+ */
1239
+ function readContainers(pod: KubernetesPodLike): Array<ContainerView> {
1240
+ const containers: unknown = pod.spec?.containers;
1241
+ if (!Array.isArray(containers)) {
1242
+ return [];
1243
+ }
1244
+
1245
+ const views: Array<ContainerView> = [];
1246
+ for (const container of containers) {
1247
+ if (!container || typeof container !== "object") {
1248
+ continue;
1249
+ }
1250
+ const name: unknown = (container as { name?: unknown }).name;
1251
+ const image: unknown = (container as { image?: unknown }).image;
1252
+
1253
+ const imageText: string | null =
1254
+ typeof image === "string" && image.trim() ? image.trim() : null;
1255
+
1256
+ views.push({
1257
+ name: typeof name === "string" ? name.trim() : "",
1258
+ image: imageText,
1259
+ ports: readPorts((container as { ports?: unknown }).ports),
1260
+ nonServer: isNonServerCommand({
1261
+ command: (container as { command?: Array<string | null> }).command,
1262
+ args: (container as { args?: Array<string | null> }).args,
1263
+ }),
1264
+ classification: classifyImage(imageText),
1265
+ });
1266
+ }
1267
+ return views;
1268
+ }
1269
+
1270
+ function readOwners(
1271
+ pod: KubernetesPodLike,
1272
+ ): Array<{ kind: string; name: string }> {
1273
+ const items: unknown = pod.ownerReferences?.items;
1274
+ if (!Array.isArray(items)) {
1275
+ return [];
1276
+ }
1277
+ const owners: Array<{ kind: string; name: string }> = [];
1278
+ for (const item of items) {
1279
+ const kind: unknown = item?.kind;
1280
+ const name: unknown = item?.name;
1281
+ if (
1282
+ typeof kind === "string" &&
1283
+ kind.trim() &&
1284
+ typeof name === "string" &&
1285
+ name.trim()
1286
+ ) {
1287
+ owners.push({ kind: kind.trim(), name: name.trim() });
1288
+ }
1289
+ }
1290
+ return owners;
1291
+ }
1292
+
1293
+ function resolveOwnerWorkload(
1294
+ podName: string,
1295
+ owner: { kind: string; name: string } | null,
1296
+ labels: Record<string, unknown>,
1297
+ ): { workloadKind: string; workloadName: string } {
1298
+ if (!owner) {
1299
+ return { workloadKind: "Pod", workloadName: podName };
1300
+ }
1301
+
1302
+ const kind: string = owner.kind.toLowerCase();
1303
+
1304
+ if (kind === "statefulset") {
1305
+ return { workloadKind: "StatefulSet", workloadName: owner.name };
1306
+ }
1307
+ if (kind === "daemonset") {
1308
+ return { workloadKind: "DaemonSet", workloadName: owner.name };
1309
+ }
1310
+ if (kind === "replicaset") {
1311
+ /*
1312
+ * A Deployment's ReplicaSet is `${deployment}-${pod-template-hash}`, and
1313
+ * the pod carries that hash as a label — so stripping it is exact, with
1314
+ * no guessing at what a hash looks like.
1315
+ */
1316
+ const hash: string | null = labelValue(labels, "pod-template-hash");
1317
+ const suffix: string = hash ? `-${hash}` : "";
1318
+ if (
1319
+ hash &&
1320
+ owner.name.endsWith(suffix) &&
1321
+ owner.name.length > suffix.length
1322
+ ) {
1323
+ return {
1324
+ workloadKind: "Deployment",
1325
+ workloadName: owner.name.substring(
1326
+ 0,
1327
+ owner.name.length - suffix.length,
1328
+ ),
1329
+ };
1330
+ }
1331
+ return { workloadKind: "ReplicaSet", workloadName: owner.name };
1332
+ }
1333
+
1334
+ return { workloadKind: owner.kind, workloadName: owner.name };
1335
+ }
1336
+
1337
+ function uniqueNonEmpty(values: Array<string>): Array<string> {
1338
+ const result: Array<string> = [];
1339
+ for (const value of values) {
1340
+ const trimmed: string = typeof value === "string" ? value.trim() : "";
1341
+ if (trimmed && !result.includes(trimmed)) {
1342
+ result.push(trimmed);
1343
+ }
1344
+ }
1345
+ return result;
1346
+ }
1347
+
1348
+ /*
1349
+ * A database container with one unrecognised neighbour is still a database
1350
+ * pod when the evidence says so on its own: a StatefulSet (the shape
1351
+ * databases are deployed in, not apps with a cache sidecar) whose database
1352
+ * container declares its engine's default port. A log shipper or backup
1353
+ * agent nobody has catalogued yet must not hide a Postgres StatefulSet.
1354
+ */
1355
+ function toleratesUnknownNeighbour(data: {
1356
+ ownerKind: string | null;
1357
+ databaseContainer: ContainerView;
1358
+ system: string;
1359
+ unknownNeighbours: number;
1360
+ }): boolean {
1361
+ if (data.unknownNeighbours !== 1 || data.ownerKind !== "StatefulSet") {
1362
+ return false;
1363
+ }
1364
+ const defaultPort: number | null = getDefaultDatabasePort(data.system);
1365
+ return (
1366
+ defaultPort !== null && data.databaseContainer.ports.includes(defaultPort)
1367
+ );
1368
+ }
1369
+
1370
+ /**
1371
+ * The database a Kubernetes pod runs, or null. Operator / chart labels are
1372
+ * consulted first (they name the cluster: workloadKind "Cluster"); otherwise
1373
+ * the first container whose image is a database, provided no other
1374
+ * container is an unrecognised application (a DB sidecar in an app pod is
1375
+ * not a database — one unknown neighbour is tolerated in a StatefulSet whose
1376
+ * database container declares the engine's default port). Batch pods,
1377
+ * finished pods, client / debug runs, owner-less pods declaring no port and
1378
+ * non-member pods of a database cluster (poolers, routers, exporters,
1379
+ * backup repos) are null.
1380
+ */
1381
+ export function classifyKubernetesPod(
1382
+ pod: KubernetesPodLike,
1383
+ extra?: {
1384
+ // StatefulSet name → its spec.serviceName (the headless Service).
1385
+ statefulSetServiceNames?: Record<string, string> | null | undefined;
1386
+ },
1387
+ ): KubernetesDatabaseCandidate | null {
1388
+ if (!pod || typeof pod !== "object") {
1389
+ return null;
1390
+ }
1391
+
1392
+ const podName: string = typeof pod.name === "string" ? pod.name.trim() : "";
1393
+ const namespace: string =
1394
+ typeof pod.namespaceKey === "string" ? pod.namespaceKey.trim() : "";
1395
+ if (!podName) {
1396
+ return null;
1397
+ }
1398
+
1399
+ if (
1400
+ typeof pod.phase === "string" &&
1401
+ SKIPPED_POD_PHASES.has(pod.phase.trim().toLowerCase())
1402
+ ) {
1403
+ return null;
1404
+ }
1405
+
1406
+ const owners: Array<{ kind: string; name: string }> = readOwners(pod);
1407
+ for (const owner of owners) {
1408
+ if (BATCH_OWNER_KINDS.has(owner.kind.toLowerCase())) {
1409
+ return null;
1410
+ }
1411
+ }
1412
+ const owner: { kind: string; name: string } | null = owners[0] || null;
1413
+
1414
+ const labels: Record<string, unknown> =
1415
+ pod.labels && typeof pod.labels === "object" ? pod.labels : {};
1416
+
1417
+ const containers: Array<ContainerView> = readContainers(pod);
1418
+
1419
+ const statefulSetServiceNames: Record<string, string> =
1420
+ extra?.statefulSetServiceNames &&
1421
+ typeof extra.statefulSetServiceNames === "object"
1422
+ ? extra.statefulSetServiceNames
1423
+ : {};
1424
+ const headlessServiceName: string | null =
1425
+ owner &&
1426
+ owner.kind.toLowerCase() === "statefulset" &&
1427
+ Object.prototype.hasOwnProperty.call(statefulSetServiceNames, owner.name) &&
1428
+ typeof statefulSetServiceNames[owner.name] === "string" &&
1429
+ statefulSetServiceNames[owner.name]!.trim()
1430
+ ? statefulSetServiceNames[owner.name]!.trim()
1431
+ : null;
1432
+
1433
+ const ownerWorkload: { workloadKind: string; workloadName: string } =
1434
+ resolveOwnerWorkload(podName, owner, labels);
1435
+ const ownerKind: string | null = owner ? ownerWorkload.workloadKind : null;
1436
+ const ownerName: string | null = owner ? ownerWorkload.workloadName : null;
1437
+
1438
+ // 1. Operator / chart labels.
1439
+ for (const rule of OPERATOR_RULES) {
1440
+ const match: OperatorRuleResult = rule(labels);
1441
+ if (match === null) {
1442
+ continue;
1443
+ }
1444
+ if (match === "skip") {
1445
+ return null;
1446
+ }
1447
+
1448
+ const component: string | null = labelValue(
1449
+ labels,
1450
+ "app.kubernetes.io/component",
1451
+ );
1452
+ if (component && NON_MEMBER_COMPONENTS.has(component.toLowerCase())) {
1453
+ return null;
1454
+ }
1455
+
1456
+ /*
1457
+ * A pod that only runs poolers / exporters / admin UIs / backup tools is
1458
+ * not a member even when it carries the cluster's labels (a router or
1459
+ * pooler Deployment the operator labels as part of the cluster).
1460
+ */
1461
+ const onlyNonMemberContainers: boolean =
1462
+ containers.length > 0 &&
1463
+ containers.every((container: ContainerView): boolean => {
1464
+ return (
1465
+ container.classification.kind === "excluded" &&
1466
+ NON_MEMBER_IMAGE_REASONS.has(container.classification.reason)
1467
+ );
1468
+ });
1469
+ if (onlyNonMemberContainers) {
1470
+ return null;
1471
+ }
1472
+
1473
+ /*
1474
+ * A chart's test hook or a debug pod is labelled like the cluster but
1475
+ * only runs a client, and a member whose database container is held
1476
+ * asleep (diagnostic mode) may still run its exporter, pooler or mesh
1477
+ * proxy: with no running container left that could be the server, it
1478
+ * is not a member — a sidecar's image, version and ports never stand in
1479
+ * for the database's.
1480
+ */
1481
+ const serverContainers: Array<ContainerView> = containers.filter(
1482
+ (container: ContainerView): boolean => {
1483
+ return !container.nonServer && couldBeDatabaseProcess(container);
1484
+ },
1485
+ );
1486
+ if (containers.length > 0 && serverContainers.length === 0) {
1487
+ return null;
1488
+ }
1489
+
1490
+ const databaseContainer: ContainerView | null =
1491
+ serverContainers.find((container: ContainerView): boolean => {
1492
+ return (
1493
+ container.classification.kind === "database" &&
1494
+ container.classification.system === match.system
1495
+ );
1496
+ }) ||
1497
+ serverContainers.find((container: ContainerView): boolean => {
1498
+ return container.classification.kind === "database";
1499
+ }) ||
1500
+ serverContainers.find((container: ContainerView): boolean => {
1501
+ return container.classification.kind === "unknown";
1502
+ }) ||
1503
+ serverContainers[0] ||
1504
+ null;
1505
+
1506
+ const workloadKind: string = match.clusterName
1507
+ ? "Cluster"
1508
+ : ownerWorkload.workloadKind;
1509
+ const workloadName: string =
1510
+ match.clusterName || ownerWorkload.workloadName;
1511
+
1512
+ const evidence: Array<string> = [match.evidence];
1513
+ if (databaseContainer?.image) {
1514
+ evidence.push(`image:${databaseContainer.image}`);
1515
+ }
1516
+
1517
+ return {
1518
+ system: match.system,
1519
+ namespace,
1520
+ workloadKind,
1521
+ workloadName,
1522
+ ownerKind,
1523
+ ownerName,
1524
+ podName,
1525
+ containerName: databaseContainer?.name || "",
1526
+ image: databaseContainer?.image || null,
1527
+ version: parseImageVersion(databaseContainer?.image),
1528
+ ports: databaseContainer ? [...databaseContainer.ports] : [],
1529
+ operator: match.operator,
1530
+ role: match.role,
1531
+ serviceNames: uniqueNonEmpty([
1532
+ workloadName,
1533
+ ...match.serviceNames,
1534
+ ownerKind && SERVICE_NAMED_OWNER_KINDS.has(ownerKind) && ownerName
1535
+ ? ownerName
1536
+ : "",
1537
+ headlessServiceName || "",
1538
+ ]),
1539
+ headlessServiceName,
1540
+ evidence,
1541
+ };
1542
+ }
1543
+
1544
+ // 2. Container images: the first database container that runs a server.
1545
+ const databaseContainer: ContainerView | undefined = containers.find(
1546
+ (container: ContainerView): boolean => {
1547
+ return (
1548
+ container.classification.kind === "database" && !container.nonServer
1549
+ );
1550
+ },
1551
+ );
1552
+ if (
1553
+ !databaseContainer ||
1554
+ databaseContainer.classification.kind !== "database"
1555
+ ) {
1556
+ return null;
1557
+ }
1558
+ const system: string = databaseContainer.classification.system;
1559
+
1560
+ const unknownNeighbours: number = containers.filter(
1561
+ (container: ContainerView): boolean => {
1562
+ return (
1563
+ container !== databaseContainer &&
1564
+ container.classification.kind === "unknown"
1565
+ );
1566
+ },
1567
+ ).length;
1568
+ if (
1569
+ unknownNeighbours > 0 &&
1570
+ !toleratesUnknownNeighbour({
1571
+ ownerKind,
1572
+ databaseContainer,
1573
+ system,
1574
+ unknownNeighbours,
1575
+ })
1576
+ ) {
1577
+ return null;
1578
+ }
1579
+
1580
+ /*
1581
+ * An owner-less pod is what `kubectl run` makes — usually a one-off client
1582
+ * session in a database image. A server run that way declares its port.
1583
+ */
1584
+ if (!owner && databaseContainer.ports.length === 0) {
1585
+ return null;
1586
+ }
1587
+
1588
+ return {
1589
+ system,
1590
+ namespace,
1591
+ workloadKind: ownerWorkload.workloadKind,
1592
+ workloadName: ownerWorkload.workloadName,
1593
+ ownerKind,
1594
+ ownerName,
1595
+ podName,
1596
+ containerName: databaseContainer.name,
1597
+ image: databaseContainer.image,
1598
+ version: parseImageVersion(databaseContainer.image),
1599
+ ports: [...databaseContainer.ports],
1600
+ operator: null,
1601
+ role: null,
1602
+ serviceNames: uniqueNonEmpty([
1603
+ ownerWorkload.workloadName,
1604
+ headlessServiceName || "",
1605
+ ]),
1606
+ headlessServiceName,
1607
+ evidence: [`image:${databaseContainer.image}`],
1608
+ };
1609
+ }
1610
+
1611
+ /**
1612
+ * The Service a CloudNativePG pooler pod backs (the Pooler's name), for
1613
+ * attachPoolerServices; null for any other pod. Pooler pods are never
1614
+ * members (classifyKubernetesPod is null for them), but applications
1615
+ * connect through their Service.
1616
+ */
1617
+ export function classifyKubernetesPoolerPod(
1618
+ pod: KubernetesPodLike,
1619
+ ): KubernetesPoolerService | null {
1620
+ if (!pod || typeof pod !== "object") {
1621
+ return null;
1622
+ }
1623
+ if (
1624
+ typeof pod.phase === "string" &&
1625
+ SKIPPED_POD_PHASES.has(pod.phase.trim().toLowerCase())
1626
+ ) {
1627
+ return null;
1628
+ }
1629
+ const labels: Record<string, unknown> =
1630
+ pod.labels && typeof pod.labels === "object" ? pod.labels : {};
1631
+ const cluster: string | null = labelValue(labels, "cnpg.io/cluster");
1632
+ const pooler: string | null = labelValue(labels, "cnpg.io/poolerName");
1633
+ if (!cluster || !pooler) {
1634
+ return null;
1635
+ }
1636
+ return {
1637
+ system: "postgresql",
1638
+ namespace:
1639
+ typeof pod.namespaceKey === "string" ? pod.namespaceKey.trim() : "",
1640
+ clusterName: cluster,
1641
+ serviceName: pooler,
1642
+ };
1643
+ }
1644
+
1645
+ function compareStrings(a: string, b: string): number {
1646
+ if (a < b) {
1647
+ return -1;
1648
+ }
1649
+ return a > b ? 1 : 0;
1650
+ }
1651
+
1652
+ /**
1653
+ * The engine of a database whose members report several engines of one
1654
+ * family: the most specific of them (getMoreSpecificDatabaseSystem). A
1655
+ * StatefulSet mid-rollout from a redis image to a valkey image is ONE
1656
+ * Valkey database, as its family-keyed identifier
1657
+ * (buildWorkloadDatabaseServerIdentifier) already says. The engines are
1658
+ * folded most-reported first, then by name, so member order never changes
1659
+ * the answer; of two forks that do not refine each other (valkey and keydb)
1660
+ * the more reported one stays, the first by name on a tie. Null when no
1661
+ * engine is given.
1662
+ */
1663
+ export function pickMostSpecificDatabaseSystem(
1664
+ systems: Array<string>,
1665
+ ): string | null {
1666
+ const counts: Map<string, number> = new Map<string, number>();
1667
+
1668
+ for (const system of Array.isArray(systems) ? systems : []) {
1669
+ if (typeof system === "string" && system) {
1670
+ counts.set(system, (counts.get(system) || 0) + 1);
1671
+ }
1672
+ }
1673
+
1674
+ const ordered: Array<string> = Array.from(counts.keys()).sort(
1675
+ (a: string, b: string): number => {
1676
+ return (
1677
+ (counts.get(b) || 0) - (counts.get(a) || 0) || compareStrings(a, b)
1678
+ );
1679
+ },
1680
+ );
1681
+
1682
+ let result: string | null = null;
1683
+
1684
+ for (const system of ordered) {
1685
+ result = getMoreSpecificDatabaseSystem(result, system);
1686
+ }
1687
+
1688
+ return result;
1689
+ }
1690
+
1691
+ /**
1692
+ * Fold per-pod candidates into one entry per database workload
1693
+ * (engine FAMILY + namespace + workloadKind + workloadName - the parts of
1694
+ * its family-keyed identifier). The group's engine is the most specific its
1695
+ * members report (pickMostSpecificDatabaseSystem). Member pods, ports,
1696
+ * Service names and owner workloads are unioned; image and version come
1697
+ * from the primary member running that engine when one is known, else from
1698
+ * the first such pod by name. Deterministic: groups and pod names are
1699
+ * sorted.
1700
+ */
1701
+ export function groupKubernetesDatabaseCandidates(
1702
+ candidates: Array<KubernetesDatabaseCandidate>,
1703
+ ): Array<KubernetesDatabaseGroup> {
1704
+ if (!Array.isArray(candidates)) {
1705
+ return [];
1706
+ }
1707
+
1708
+ const byKey: Map<string, Array<KubernetesDatabaseCandidate>> = new Map<
1709
+ string,
1710
+ Array<KubernetesDatabaseCandidate>
1711
+ >();
1712
+
1713
+ for (const candidate of candidates) {
1714
+ if (!candidate || !candidate.system || !candidate.workloadName) {
1715
+ continue;
1716
+ }
1717
+ const key: string = [
1718
+ getDatabaseSystemFamily(candidate.system) || candidate.system,
1719
+ candidate.namespace,
1720
+ candidate.workloadKind,
1721
+ candidate.workloadName,
1722
+ ].join("\u0000");
1723
+ const members: Array<KubernetesDatabaseCandidate> = byKey.get(key) || [];
1724
+ members.push(candidate);
1725
+ byKey.set(key, members);
1726
+ }
1727
+
1728
+ const groups: Array<KubernetesDatabaseGroup> = [];
1729
+
1730
+ for (const members of byKey.values()) {
1731
+ const sorted: Array<KubernetesDatabaseCandidate> = [...members].sort(
1732
+ (a: KubernetesDatabaseCandidate, b: KubernetesDatabaseCandidate) => {
1733
+ return compareStrings(a.podName, b.podName);
1734
+ },
1735
+ );
1736
+ const first: KubernetesDatabaseCandidate = sorted[0]!;
1737
+ const system: string =
1738
+ pickMostSpecificDatabaseSystem(
1739
+ sorted.map((member: KubernetesDatabaseCandidate): string => {
1740
+ return member.system;
1741
+ }),
1742
+ ) || first.system;
1743
+
1744
+ // Image and version describe the engine the group is shown as.
1745
+ const runningSystem: Array<KubernetesDatabaseCandidate> = sorted.filter(
1746
+ (member: KubernetesDatabaseCandidate): boolean => {
1747
+ return member.system === system;
1748
+ },
1749
+ );
1750
+ const pool: Array<KubernetesDatabaseCandidate> =
1751
+ runningSystem.length > 0 ? runningSystem : sorted;
1752
+ const representative: KubernetesDatabaseCandidate =
1753
+ pool.find((member: KubernetesDatabaseCandidate): boolean => {
1754
+ return member.role === "primary";
1755
+ }) || pool[0]!;
1756
+
1757
+ const podNames: Array<string> = uniqueNonEmpty(
1758
+ sorted.map((member: KubernetesDatabaseCandidate): string => {
1759
+ return member.podName;
1760
+ }),
1761
+ );
1762
+
1763
+ const ports: Array<number> = [];
1764
+ for (const member of sorted) {
1765
+ for (const port of member.ports || []) {
1766
+ if (!ports.includes(port)) {
1767
+ ports.push(port);
1768
+ }
1769
+ }
1770
+ }
1771
+ ports.sort((a: number, b: number): number => {
1772
+ return a - b;
1773
+ });
1774
+
1775
+ const serviceNames: Array<string> = uniqueNonEmpty(
1776
+ sorted.flatMap((member: KubernetesDatabaseCandidate): Array<string> => {
1777
+ return member.serviceNames || [];
1778
+ }),
1779
+ );
1780
+
1781
+ const podServiceNames: Array<{ podName: string; serviceName: string }> = [];
1782
+ for (const member of sorted) {
1783
+ if (
1784
+ member.headlessServiceName &&
1785
+ !podServiceNames.some(
1786
+ (entry: { podName: string; serviceName: string }): boolean => {
1787
+ return entry.podName === member.podName;
1788
+ },
1789
+ )
1790
+ ) {
1791
+ podServiceNames.push({
1792
+ podName: member.podName,
1793
+ serviceName: member.headlessServiceName,
1794
+ });
1795
+ }
1796
+ }
1797
+
1798
+ const ownerWorkloads: Array<{ kind: string; name: string }> = [];
1799
+ for (const member of sorted) {
1800
+ if (
1801
+ member.ownerKind &&
1802
+ member.ownerName &&
1803
+ !ownerWorkloads.some(
1804
+ (entry: { kind: string; name: string }): boolean => {
1805
+ return (
1806
+ entry.kind === member.ownerKind && entry.name === member.ownerName
1807
+ );
1808
+ },
1809
+ )
1810
+ ) {
1811
+ ownerWorkloads.push({ kind: member.ownerKind, name: member.ownerName });
1812
+ }
1813
+ }
1814
+ ownerWorkloads.sort(
1815
+ (
1816
+ a: { kind: string; name: string },
1817
+ b: { kind: string; name: string },
1818
+ ): number => {
1819
+ return compareStrings(a.kind, b.kind) || compareStrings(a.name, b.name);
1820
+ },
1821
+ );
1822
+
1823
+ groups.push({
1824
+ system,
1825
+ namespace: first.namespace,
1826
+ workloadKind: first.workloadKind,
1827
+ workloadName: first.workloadName,
1828
+ podNames,
1829
+ image: representative.image,
1830
+ version: representative.version,
1831
+ ports,
1832
+ operator:
1833
+ sorted.find((member: KubernetesDatabaseCandidate): boolean => {
1834
+ return Boolean(member.operator);
1835
+ })?.operator || null,
1836
+ serviceNames,
1837
+ headlessServiceName:
1838
+ sorted.find((member: KubernetesDatabaseCandidate): boolean => {
1839
+ return Boolean(member.headlessServiceName);
1840
+ })?.headlessServiceName || null,
1841
+ podServiceNames,
1842
+ ownerWorkloads,
1843
+ });
1844
+ }
1845
+
1846
+ return groups.sort(
1847
+ (a: KubernetesDatabaseGroup, b: KubernetesDatabaseGroup): number => {
1848
+ return (
1849
+ compareStrings(a.namespace, b.namespace) ||
1850
+ compareStrings(a.workloadKind, b.workloadKind) ||
1851
+ compareStrings(a.workloadName, b.workloadName) ||
1852
+ compareStrings(a.system, b.system)
1853
+ );
1854
+ },
1855
+ );
1856
+ }
1857
+
1858
+ /**
1859
+ * Add each pooler's Service to the operator cluster it pools (same engine
1860
+ * family, namespace and cluster name). Returns new group objects; a pooler
1861
+ * whose cluster has no group this run is dropped.
1862
+ */
1863
+ export function attachPoolerServices(
1864
+ groups: Array<KubernetesDatabaseGroup>,
1865
+ poolers: Array<KubernetesPoolerService>,
1866
+ ): Array<KubernetesDatabaseGroup> {
1867
+ if (!Array.isArray(groups)) {
1868
+ return [];
1869
+ }
1870
+ const list: Array<KubernetesPoolerService> = Array.isArray(poolers)
1871
+ ? poolers
1872
+ : [];
1873
+
1874
+ return groups.map(
1875
+ (group: KubernetesDatabaseGroup): KubernetesDatabaseGroup => {
1876
+ if (group.workloadKind !== "Cluster") {
1877
+ return group;
1878
+ }
1879
+ const extra: Array<string> = list
1880
+ .filter((pooler: KubernetesPoolerService): boolean => {
1881
+ return (
1882
+ Boolean(pooler) &&
1883
+ isSameDatabaseFamily(pooler.system, group.system) &&
1884
+ pooler.namespace === group.namespace &&
1885
+ pooler.clusterName === group.workloadName
1886
+ );
1887
+ })
1888
+ .map((pooler: KubernetesPoolerService): string => {
1889
+ return pooler.serviceName;
1890
+ })
1891
+ .sort(compareStrings);
1892
+ if (extra.length === 0) {
1893
+ return group;
1894
+ }
1895
+ return {
1896
+ ...group,
1897
+ serviceNames: uniqueNonEmpty([...group.serviceNames, ...extra]),
1898
+ };
1899
+ },
1900
+ );
1901
+ }
1902
+
1903
+ // ---- Docker / Podman containers -------------------------------------------
1904
+
1905
+ /*
1906
+ * Every container label classifyContainer reads: the Swarm / Compose
1907
+ * service that groups replicas, and the marks of Testcontainers runs,
1908
+ * `docker compose run` one-offs and kubelet-managed containers. The Docker
1909
+ * and Podman agents copy exactly these onto their docker_stats metrics as
1910
+ * resource attributes (`container_labels_to_metric_labels`, same names),
1911
+ * and the metrics ingest stores them as the container row's labels — the
1912
+ * one inventory path every agent version runs.
1913
+ */
1914
+ export const CONTAINER_CLASSIFIER_LABEL_KEYS: ReadonlyArray<string> = [
1915
+ "com.docker.compose.oneoff",
1916
+ "com.docker.compose.project",
1917
+ "com.docker.compose.service",
1918
+ "com.docker.swarm.service.name",
1919
+ "io.kubernetes.pod.name",
1920
+ "io.podman.compose.project",
1921
+ "io.podman.compose.service",
1922
+ "org.testcontainers",
1923
+ "org.testcontainers.sessionId",
1924
+ ];
1925
+
1926
+ // A Swarm task id: 25 lowercase alphanumerics (`stack_db.1.<task id>`).
1927
+ const SWARM_TASK_ID_REGEX: RegExp = /^[a-z0-9]{25}$/;
1928
+ const SWARM_SLOT_REGEX: RegExp = /^(?:\d+|[a-z0-9]{25})$/;
1929
+
1930
+ /*
1931
+ * Containers that are never a long-lived database server of their own:
1932
+ * Testcontainers runs (CI), `docker compose run` one-offs, and containers
1933
+ * the kubelet manages on a Docker node (the Kubernetes path discovers those
1934
+ * as pods).
1935
+ */
1936
+ function isEphemeralOrManagedContainer(
1937
+ labels: Record<string, unknown>,
1938
+ ): boolean {
1939
+ for (const key of Object.keys(labels)) {
1940
+ if (key === "org.testcontainers" || key.startsWith("org.testcontainers.")) {
1941
+ return true;
1942
+ }
1943
+ }
1944
+ const oneOff: string | null = labelValue(labels, "com.docker.compose.oneoff");
1945
+ if (oneOff && oneOff.toLowerCase() === "true") {
1946
+ return true;
1947
+ }
1948
+ return (
1949
+ hasLabel(labels, "io.kubernetes.pod.name") ||
1950
+ hasLabel(labels, "io.kubernetes.container.name")
1951
+ );
1952
+ }
1953
+
1954
+ /*
1955
+ * The Swarm service a task container belongs to, from its name alone
1956
+ * (`stack_db.1.<25-char task id>`, `stack_db.<node id>.<task id>`) — for
1957
+ * rows whose labels have not been inventoried.
1958
+ */
1959
+ function swarmServiceFromTaskName(containerName: string): string | null {
1960
+ const parts: Array<string> = containerName.split(".");
1961
+ if (parts.length < 3) {
1962
+ return null;
1963
+ }
1964
+ const taskId: string = parts[parts.length - 1]!;
1965
+ const slot: string = parts[parts.length - 2]!;
1966
+ if (!SWARM_TASK_ID_REGEX.test(taskId) || !SWARM_SLOT_REGEX.test(slot)) {
1967
+ return null;
1968
+ }
1969
+ const service: string = parts.slice(0, -2).join(".");
1970
+ return service || null;
1971
+ }
1972
+
1973
+ /*
1974
+ * What groups a container's replicas: its Swarm service, its Compose
1975
+ * project + service (Docker Compose and podman-compose both stamp
1976
+ * `com.docker.compose.*`; podman-compose also `io.podman.compose.*`), else
1977
+ * the container name exactly as it is — never a guessed-away suffix, since
1978
+ * `redis-6379` and `redis-6380` or `pg-14` and `pg-16` are different
1979
+ * servers. The kind says which of the three it is.
1980
+ */
1981
+ function containerWorkload(
1982
+ containerName: string,
1983
+ labels: Record<string, unknown>,
1984
+ ): { workloadKind: ContainerWorkloadKind; workloadName: string } {
1985
+ const swarmService: string | null =
1986
+ labelValue(labels, "com.docker.swarm.service.name") ||
1987
+ swarmServiceFromTaskName(containerName);
1988
+ if (swarmService) {
1989
+ return {
1990
+ workloadKind: ContainerWorkloadKind.SwarmService,
1991
+ workloadName: swarmService,
1992
+ };
1993
+ }
1994
+
1995
+ const project: string | null =
1996
+ labelValue(labels, "com.docker.compose.project") ||
1997
+ labelValue(labels, "io.podman.compose.project");
1998
+ const service: string | null =
1999
+ labelValue(labels, "com.docker.compose.service") ||
2000
+ labelValue(labels, "io.podman.compose.service");
2001
+ if (project && service) {
2002
+ return {
2003
+ workloadKind: ContainerWorkloadKind.ComposeService,
2004
+ workloadName: `${project}-${service}`,
2005
+ };
2006
+ }
2007
+
2008
+ return {
2009
+ workloadKind: ContainerWorkloadKind.Container,
2010
+ workloadName: containerName,
2011
+ };
2012
+ }
2013
+
2014
+ /**
2015
+ * The database a Docker / Podman container runs, or null. The workload is
2016
+ * the Swarm service or Compose service the container belongs to (so its
2017
+ * replicas group together), else the container's own name, and its kind
2018
+ * says which. Testcontainers runs, `docker compose run` one-offs and
2019
+ * kubelet-managed containers are null.
2020
+ */
2021
+ export function classifyContainer(
2022
+ container: ContainerLike,
2023
+ ): ContainerDatabaseClassification | null {
2024
+ if (!container || typeof container.name !== "string") {
2025
+ return null;
2026
+ }
2027
+
2028
+ // Docker reports names as "/name" (and occasionally "/a,/b").
2029
+ const containerName: string = (container.name.split(",")[0] || "")
2030
+ .trim()
2031
+ .replace(/^\/+/, "");
2032
+ if (!containerName) {
2033
+ return null;
2034
+ }
2035
+
2036
+ const labels: Record<string, unknown> =
2037
+ container.labels &&
2038
+ typeof container.labels === "object" &&
2039
+ !Array.isArray(container.labels)
2040
+ ? container.labels
2041
+ : {};
2042
+
2043
+ if (isEphemeralOrManagedContainer(labels)) {
2044
+ return null;
2045
+ }
2046
+
2047
+ const classification: ImageClassification = classifyImage(
2048
+ container.imageName,
2049
+ );
2050
+ if (classification.kind !== "database") {
2051
+ return null;
2052
+ }
2053
+
2054
+ const descriptor: DatabaseSystemDescriptor | null =
2055
+ getDatabaseSystemDescriptor(classification.system);
2056
+ const workload: {
2057
+ workloadKind: ContainerWorkloadKind;
2058
+ workloadName: string;
2059
+ } = containerWorkload(containerName, labels);
2060
+
2061
+ return {
2062
+ system: descriptor ? descriptor.system : classification.system,
2063
+ workloadKind: workload.workloadKind,
2064
+ workloadName: workload.workloadName,
2065
+ containerName,
2066
+ version: parseImageVersion(container.imageName),
2067
+ };
2068
+ }