@oneuptime/common 14.0.3 → 14.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/Alert.ts +55 -0
- package/Models/DatabaseModels/DatabaseServer.ts +1650 -0
- package/Models/DatabaseModels/DatabaseServerEndpoint.ts +540 -0
- package/Models/DatabaseModels/DatabaseServerFeed.ts +625 -0
- package/Models/DatabaseModels/DatabaseServerLabelRule.ts +514 -0
- package/Models/DatabaseModels/DatabaseServerOwnerRule.ts +596 -0
- package/Models/DatabaseModels/DatabaseServerOwnerTeam.ts +491 -0
- package/Models/DatabaseModels/DatabaseServerOwnerUser.ts +490 -0
- package/Models/DatabaseModels/Incident.ts +55 -0
- package/Models/DatabaseModels/Index.ts +16 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +26 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
- package/Models/DatabaseModels/TelemetryException.ts +2 -0
- package/Models/DatabaseModels/UserProjectSsoConsent.ts +146 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.ts +673 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/MultipartFormData.ts +1 -1
- package/Server/Middleware/TelemetryIngest.ts +15 -8
- package/Server/Services/AlertReminderRuleService.ts +100 -24
- package/Server/Services/DatabaseServerEndpointService.ts +895 -0
- package/Server/Services/DatabaseServerFeedService.ts +128 -0
- package/Server/Services/DatabaseServerLabelRuleEngineService.ts +340 -0
- package/Server/Services/DatabaseServerLabelRuleService.ts +14 -0
- package/Server/Services/DatabaseServerOwnerRuleEngineService.ts +425 -0
- package/Server/Services/DatabaseServerOwnerRuleService.ts +14 -0
- package/Server/Services/DatabaseServerOwnerTeamService.ts +180 -0
- package/Server/Services/DatabaseServerOwnerUserService.ts +173 -0
- package/Server/Services/DatabaseServerService.ts +4546 -0
- package/Server/Services/DatabaseService.ts +14 -8
- package/Server/Services/DockerResourceService.ts +43 -2
- package/Server/Services/IncidentReminderRuleService.ts +103 -23
- package/Server/Services/Index.ts +16 -0
- package/Server/Services/InventoryItemService.ts +113 -5
- package/Server/Services/IoTDeviceService.ts +145 -37
- package/Server/Services/OpenTelemetryIngestService.ts +19 -0
- package/Server/Services/PodmanResourceService.ts +43 -2
- package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +107 -23
- package/Server/Services/ServiceService.ts +1 -1
- package/Server/Services/TelemetryUsageBillingService.ts +29 -2
- package/Server/Services/UserProjectSsoConsentService.ts +74 -0
- package/Server/Services/UserService.ts +14 -1
- package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +18 -0
- package/Server/Types/Database/QueryHelper.ts +30 -1
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +69 -1
- package/Server/Utils/Database/AffectedResourceRelations.ts +6 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +188 -6
- package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +2262 -755
- package/Server/Utils/Monitor/MonitorIncident.ts +28 -1
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +268 -6
- package/Server/Utils/Monitor/MonitorResource.ts +47 -11
- package/Server/Utils/Monitor/MonitorResourceContext.ts +1 -0
- package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +239 -21
- package/Server/Utils/Monitor/PerSeriesResolutionRootCause.ts +43 -0
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +291 -0
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +111 -12
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
- package/Server/Utils/StartServer.ts +26 -6
- package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
- package/Server/Utils/Telemetry/DatabaseEndpointDiscovery.ts +688 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +5 -1
- package/Server/Utils/Telemetry/LogExceptionExtractor.ts +18 -1
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +175 -12
- package/Server/Utils/Telemetry/ResourceFacetResolver.ts +12 -0
- package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +606 -36
- package/Server/Utils/Telemetry/Telemetry.ts +16 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +22 -22
- package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +16 -10
- package/Tests/App/Accounts/SignedInRedirect.test.tsx +241 -0
- package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +221 -4
- package/Tests/App/Dashboard/AIInsightsResourcePageContext.test.ts +42 -0
- package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +100 -0
- package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +86 -0
- package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +2 -0
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +19 -0
- package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +14 -0
- package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +26 -1
- package/Tests/App/Dashboard/DashboardAppSsoError.test.tsx +501 -0
- package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +64 -1
- package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +178 -18
- package/Tests/App/Dashboard/DashboardMasterPageSsoRedirect.test.tsx +444 -0
- package/Tests/App/Dashboard/DashboardNavBarForeignHiddenRule.test.tsx +573 -0
- package/Tests/App/Dashboard/DatabaseChartAxes.test.tsx +431 -0
- package/Tests/App/Dashboard/DatabaseContainerDetailBadge.test.tsx +249 -0
- package/Tests/App/Dashboard/DatabaseDashboardE2EFixes.test.ts +681 -0
- package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +1801 -0
- package/Tests/App/Dashboard/DatabaseFleetSummaryRefresh.test.tsx +280 -0
- package/Tests/App/Dashboard/DatabaseFormHelpers.test.tsx +321 -0
- package/Tests/App/Dashboard/DatabaseMetricMonitorLink.test.tsx +795 -0
- package/Tests/App/Dashboard/DatabaseOverviewHeader.test.tsx +580 -0
- package/Tests/App/Dashboard/DatabaseRecommendationsEngineMetrics.test.ts +270 -0
- package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +1486 -0
- package/Tests/App/Dashboard/DatabaseServerEngineMetricsFilter.test.ts +223 -0
- package/Tests/App/Dashboard/DatabaseServerPresentation.test.ts +435 -0
- package/Tests/App/Dashboard/DatabaseServerSummary.test.ts +186 -0
- package/Tests/App/Dashboard/DatabaseServerTelemetryQueries.test.ts +1553 -0
- package/Tests/App/Dashboard/DatabaseTableTruncation.test.tsx +326 -0
- package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +512 -0
- package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +774 -0
- package/Tests/App/Dashboard/DatabaseViewLayout.test.tsx +233 -0
- package/Tests/App/Dashboard/DatabaseWorkloadBadge.test.tsx +972 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +5 -0
- package/Tests/App/Dashboard/DiscoveryReviewUnnamedHostHint.test.tsx +1061 -0
- package/Tests/App/Dashboard/EventOverlayScope.test.ts +147 -0
- package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +1 -0
- package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +1 -0
- package/Tests/App/Dashboard/MetricsViewerDrillDown.test.tsx +310 -0
- package/Tests/App/Dashboard/MetricsViewerEntityScopedRows.test.tsx +495 -0
- package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +132 -1
- package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +5 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +4 -3
- package/Tests/App/Dashboard/RecommendationsLoadedContext.test.tsx +349 -0
- package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +62 -0
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +14 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +2 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +1085 -0
- package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +10 -0
- package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +2 -1
- package/Tests/App/StatusPage/StatusPageHeaderForeignHiddenRule.test.tsx +284 -0
- package/Tests/ClassStrings.ts +178 -0
- package/Tests/Fixtures/MobilePasskeyUser.json +1 -2
- package/Tests/ForeignHiddenRuleGuard.ts +2152 -0
- package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +2250 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +24 -5
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/RenderedMarkup.ts +518 -0
- package/Tests/ResponsiveVisibility.test.ts +490 -4
- package/Tests/ResponsiveVisibility.ts +505 -52
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +541 -0
- package/Tests/Server/Infrastructure/Postgres/AddUserProjectSsoConsentMigration.test.ts +234 -0
- package/Tests/Server/Services/ContainerSnapshotUpsertPostgres.test.ts +319 -0
- package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +13 -0
- package/Tests/Server/Services/DatabaseServerEndpointService.test.ts +2033 -0
- package/Tests/Server/Services/DatabaseServerFeedAndOwnerServices.test.ts +529 -0
- package/Tests/Server/Services/DatabaseServerLifecycle.test.ts +3302 -0
- package/Tests/Server/Services/DatabaseServerRegistries.test.ts +603 -0
- package/Tests/Server/Services/DatabaseServerRuleEngines.test.ts +754 -0
- package/Tests/Server/Services/DatabaseServerService.test.ts +3558 -0
- package/Tests/Server/Services/DatabaseServerSqlPostgres.test.ts +2045 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedPostgres.test.ts +362 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedReply.test.ts +210 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +18 -0
- package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +2 -0
- package/Tests/Server/Services/IncomingCallPolicyEscalationRuleTarget.test.ts +225 -0
- package/Tests/Server/Services/InventoryItemDisplayName.test.ts +280 -2
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +8 -2
- package/Tests/Server/Services/IoTDeviceLatestMetricsPostgres.test.ts +689 -0
- package/Tests/Server/Services/IoTDeviceService.test.ts +379 -0
- package/Tests/Server/Services/LatestMetricsValuesShape.test.ts +360 -0
- package/Tests/Server/Services/LlmModelPriceService.test.ts +402 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
- package/Tests/Server/Services/OwnerRowUniqueness.test.ts +61 -9
- package/Tests/Server/Services/ReminderRuleLookupPostgres.test.ts +2250 -0
- package/Tests/Server/Services/ReminderRuleRefreshOpenSubjectsPaging.test.ts +653 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +68 -20
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
- package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +9 -0
- package/Tests/Server/Services/UserProjectSsoConsentPostgres.test.ts +153 -0
- package/Tests/Server/Services/UserProjectSsoConsentService.test.ts +192 -0
- package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +71 -0
- package/Tests/Server/Services/UserSignupIdTakeoverPostgres.test.ts +238 -0
- package/Tests/Server/Services/WorkspaceNotificationSummaryHelpers.test.ts +592 -0
- package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +3 -0
- package/Tests/Server/Types/Database/EventOverlayScopePostgres.test.ts +1 -0
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +11 -0
- package/Tests/Server/Types/Database/Permissions/InfrastructureOwnershipScope.test.ts +8 -0
- package/Tests/Server/Types/Database/QueryHelperBooleanForCriteriaBackedRule.test.ts +1094 -0
- package/Tests/Server/Utils/AI/ResourceTools.test.ts +134 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +41 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +279 -9
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaDisplayContext.test.ts +933 -0
- package/Tests/Server/Utils/Monitor/DatabaseMaintenanceSuppression.test.ts +582 -0
- package/Tests/Server/Utils/Monitor/DatabaseServerMonitorLinking.test.ts +753 -0
- package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +119 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +60 -25
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +1719 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesAffectedResources.test.ts +1447 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesRatioEmail.test.ts +352 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformMetricUnits.test.ts +717 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformRawScan.test.ts +2125 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformSeriesAffectedResources.test.ts +1288 -0
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +88 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +205 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +62 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceDatabaseMaintenance.test.ts +803 -0
- package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +133 -3
- package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +472 -1
- package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +236 -2
- package/Tests/Server/Utils/SeriesResourceLabels.test.ts +70 -0
- package/Tests/Server/Utils/StartServerProtobufContentType.test.ts +54 -0
- package/Tests/Server/Utils/Telemetry/DatabaseEndpointDiscovery.test.ts +1895 -0
- package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +7 -0
- package/Tests/Server/Utils/Telemetry/LogExceptionExtractorSeverityGate.test.ts +90 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +570 -0
- package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +97 -2
- package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +1337 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +295 -0
- package/Tests/Types/AI/AIResourceContext.test.ts +30 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerClassifier.test.ts +3644 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommand.test.ts +340 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommandCorpus.ts +826 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommandPostgres.test.ts +150 -0
- package/Tests/Types/DatabaseServer/DatabaseEndpoint.test.ts +2657 -0
- package/Tests/Types/DatabaseServer/DatabaseServerDiscoverySource.test.ts +64 -0
- package/Tests/Types/DatabaseServer/DatabaseServerEntityKeys.test.ts +952 -0
- package/Tests/Types/DatabaseServer/DatabaseServerMetricCatalog.test.ts +1161 -0
- package/Tests/Types/DatabaseServer/DatabaseSystem.test.ts +1514 -0
- package/Tests/Types/DatabaseServer/DatabaseTelemetryResolver.test.ts +1558 -0
- package/Tests/Types/Monitor/CephAlertTemplates.test.ts +255 -6
- package/Tests/Types/Monitor/DatabaseAlertTemplates.test.ts +2254 -0
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +298 -0
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +271 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +253 -9
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +13 -5
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +4 -2
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +111 -2
- package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +61 -0
- package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +114 -0
- package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +181 -0
- package/Tests/Types/NetworkDevice/DiscoveredHostNamingStatus.test.ts +401 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +4 -1
- package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +24 -2
- package/Tests/UI/Components/Card.test.tsx +93 -2
- package/Tests/UI/Components/FeedOptions.test.ts +6 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +8 -1
- package/Tests/UI/Components/ForeignHiddenRuleResilience.test.tsx +1147 -0
- package/Tests/UI/Components/HeaderRightRail.test.tsx +41 -1
- package/Tests/UI/Components/LogsEntityNames.test.ts +16 -1
- package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +7 -3
- package/Tests/UI/Components/ResourceFacetConfigs.test.ts +2 -1
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/SrOnlyForeignRule.test.tsx +994 -0
- package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +34 -1
- package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +4 -1
- package/Tests/UI/ForeignHiddenRuleGuard.test.ts +1951 -0
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +19 -1
- package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +19 -1
- package/Tests/UI/TailwindPlayCdnForeignHiddenRule.test.ts +1005 -0
- package/Tests/UI/ThemeGroupHoverReveal.test.ts +847 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +70 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +21 -0
- package/Tests/UI/Utils/ModelAPISsoRedirect.test.ts +127 -0
- package/Tests/UI/Utils/Navigation.test.ts +102 -0
- package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +23 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
- package/Tests/Utils/MetricUnitUtil.test.ts +227 -2
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +288 -2
- package/Tests/Utils/Monitor/PlatformResourceIdentityAndUnits.test.ts +1559 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.test.ts +2546 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +315 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +336 -1
- package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +244 -0
- package/Tests/Utils/ValueFormatter.test.ts +50 -0
- package/Tests/tsconfig.json +16 -0
- package/Types/AI/AIResourceContext.ts +7 -0
- package/Types/Dashboard/DashboardTemplates.ts +2 -1
- package/Types/DatabaseServer/DatabaseConnectionSpan.ts +77 -0
- package/Types/DatabaseServer/DatabaseContainerClassifier.ts +2068 -0
- package/Types/DatabaseServer/DatabaseContainerCommand.ts +801 -0
- package/Types/DatabaseServer/DatabaseEndpoint.ts +1943 -0
- package/Types/DatabaseServer/DatabaseServerDiscoverySource.ts +68 -0
- package/Types/DatabaseServer/DatabaseServerMetricCatalog.ts +966 -0
- package/Types/DatabaseServer/DatabaseSystem.ts +2124 -0
- package/Types/DatabaseServer/DatabaseTelemetryResolver.ts +555 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Monitor/CephAlertTemplates.ts +18 -0
- package/Types/Monitor/DatabaseAlertTemplates.ts +2020 -0
- package/Types/Monitor/KubernetesAlertTemplates.ts +64 -24
- package/Types/Monitor/KubernetesMetricCatalog.ts +118 -19
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +14 -0
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +93 -10
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +109 -8
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +36 -7
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +43 -2
- package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +41 -6
- package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +26 -0
- package/Types/NetworkDevice/DiscoveredHostNamingStatus.ts +147 -0
- package/Types/Permission.ts +267 -0
- package/Types/Probe/ProbeApiIngestResponse.ts +8 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +10 -0
- package/Types/Rules/RuleRun.ts +12 -0
- package/Types/Telemetry/EntityType.ts +23 -0
- package/Types/Telemetry/ResourceFacetCatalog.ts +14 -0
- package/Types/Telemetry/ServiceType.ts +8 -0
- package/UI/Components/Banner/Banner.tsx +1 -1
- package/UI/Components/Card/Card.tsx +1 -1
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +2 -2
- package/UI/Components/Charts/Line/LineChart.tsx +7 -0
- package/UI/Components/CommandPalette/CommandPalette.tsx +1 -1
- package/UI/Components/Date/StartAndEndDate.tsx +5 -5
- package/UI/Components/Diagram/HorizontalStepChain.tsx +1 -1
- package/UI/Components/Feed/FeedOptionsButton.tsx +1 -1
- package/UI/Components/Forms/BasicForm.tsx +1 -1
- package/UI/Components/Header/Header.tsx +1 -1
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -1
- package/UI/Components/HeaderAlert/HeaderAlert.tsx +1 -1
- package/UI/Components/LogsViewer/components/FacetValueRow.tsx +1 -1
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +1 -1
- package/UI/Components/Modal/ModalFooter.tsx +1 -1
- package/UI/Components/ModelDetail/CardModelDetail.tsx +2 -2
- package/UI/Components/ModelTable/BaseModelTable.tsx +4 -4
- package/UI/Components/Navbar/NavBar.tsx +9 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -2
- package/UI/Components/Page/Page.tsx +1 -1
- package/UI/Components/Pagination/Pagination.tsx +3 -3
- package/UI/Components/ProgressButtons/ProgressButtonItem.tsx +1 -1
- package/UI/Components/SideMenu/SideMenu.tsx +4 -1
- package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +4 -2
- package/UI/Components/StatusPage/ResourceList.tsx +6 -6
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +1 -1
- package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +1 -1
- package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +1 -1
- package/UI/Components/Workflow/ComponentsModal.tsx +2 -2
- package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
- package/UI/Styles/Theme.css +8 -1
- package/UI/Utils/Navigation.ts +89 -42
- package/UI/Utils/Telemetry/TelemetryEntityNames.ts +7 -0
- package/UI/Utils/TelemetryService.ts +4 -3
- package/Utils/LabelRuleImportExport.ts +2 -0
- package/Utils/MetricUnitUtil.ts +103 -11
- package/Utils/Monitor/MetricValueFormatter.ts +66 -13
- package/Utils/Monitor/PlatformMetricUnitUtil.ts +215 -0
- package/Utils/Monitor/PlatformResourceIdentity.ts +514 -0
- package/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.ts +425 -0
- package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +32 -0
- package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +73 -0
- package/Utils/Telemetry/DatabaseServerEntityKeys.ts +275 -0
- package/Utils/Telemetry/EntityKey.ts +176 -0
- package/Utils/ValueFormatter.ts +16 -0
- package/build/dist/Models/DatabaseModels/Alert.js +54 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseServer.js +1683 -0
- package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js +559 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js +522 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js +603 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js +506 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js +505 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Incident.js +54 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +16 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +2 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js +162 -0
- package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js +240 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MultipartFormData.js +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +15 -8
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AlertReminderRuleService.js +72 -23
- package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseServerEndpointService.js +694 -0
- package/build/dist/Server/Services/DatabaseServerEndpointService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerFeedService.js +110 -0
- package/build/dist/Server/Services/DatabaseServerFeedService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js +272 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleService.js +13 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js +340 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js +160 -0
- package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerUserService.js +153 -0
- package/build/dist/Server/Services/DatabaseServerOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerService.js +3272 -0
- package/build/dist/Server/Services/DatabaseServerService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseService.js +8 -4
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +30 -3
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/IncidentReminderRuleService.js +73 -23
- package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +16 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +88 -5
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +111 -24
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +24 -9
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +30 -3
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +75 -23
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +1 -1
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +23 -2
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/UserProjectSsoConsentService.js +79 -0
- package/build/dist/Server/Services/UserProjectSsoConsentService.js.map +1 -0
- package/build/dist/Server/Services/UserService.js +13 -1
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +18 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +26 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +46 -0
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +6 -0
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +134 -13
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -6
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1489 -524
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +196 -6
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +36 -9
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +175 -17
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js +29 -0
- package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +211 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +82 -10
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +20 -4
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js +442 -0
- package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +5 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +15 -1
- package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +121 -5
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +12 -0
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +438 -36
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +12 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +17 -18
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +16 -10
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -1
- package/build/dist/Types/AI/AIResourceContext.js +7 -0
- package/build/dist/Types/AI/AIResourceContext.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +2 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js +71 -0
- package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js +1462 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js +691 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js +1407 -0
- package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js +60 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js +804 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseSystem.js +1860 -0
- package/build/dist/Types/DatabaseServer/DatabaseSystem.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js +406 -0
- package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Monitor/CephAlertTemplates.js +18 -0
- package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/DatabaseAlertTemplates.js +1518 -0
- package/build/dist/Types/Monitor/DatabaseAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +57 -24
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +104 -18
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +80 -3
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +10 -3
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +29 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +21 -6
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -1
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +25 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
- package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js +136 -0
- package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js.map +1 -0
- package/build/dist/Types/Permission.js +234 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +10 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityType.js +23 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +14 -0
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -1
- package/build/dist/Types/Telemetry/ServiceType.js +8 -0
- package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
- package/build/dist/UI/Components/Banner/Banner.js +1 -1
- package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
- package/build/dist/UI/Components/Card/Card.js +1 -1
- package/build/dist/UI/Components/Card/Card.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js +1 -1
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
- package/build/dist/UI/Components/Date/StartAndEndDate.js +5 -5
- package/build/dist/UI/Components/Date/StartAndEndDate.js.map +1 -1
- package/build/dist/UI/Components/Diagram/HorizontalStepChain.js +1 -1
- package/build/dist/UI/Components/Diagram/HorizontalStepChain.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +1 -1
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js +1 -1
- package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +4 -4
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +9 -2
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/Page/Page.js +1 -1
- package/build/dist/UI/Components/Page/Page.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +3 -3
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js +1 -1
- package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js +4 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +3 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceList.js +6 -6
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +2 -2
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +66 -33
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +7 -0
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -1
- package/build/dist/UI/Utils/TelemetryService.js +3 -2
- package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
- package/build/dist/Utils/LabelRuleImportExport.js +2 -0
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +77 -13
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +58 -13
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -1
- package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js +161 -0
- package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/PlatformResourceIdentity.js +323 -0
- package/build/dist/Utils/Monitor/PlatformResourceIdentity.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js +270 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +23 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +61 -0
- package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js +178 -0
- package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +133 -0
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/build/dist/Utils/ValueFormatter.js +15 -0
- package/build/dist/Utils/ValueFormatter.js.map +1 -1
- package/package.json +1 -1
- package/tsconfig.tests.json +7 -5
|
@@ -0,0 +1,1462 @@
|
|
|
1
|
+
import { DATABASE_SYSTEMS, getDatabaseSystemDescriptor, getDatabaseSystemFamily, getDefaultDatabasePort, getMoreSpecificDatabaseSystem, isSameDatabaseFamily, } from "./DatabaseSystem";
|
|
2
|
+
import { isNonServerCommand } from "./DatabaseContainerCommand";
|
|
3
|
+
/*
|
|
4
|
+
* What a Docker / Podman database's workload is: the Swarm service or the
|
|
5
|
+
* Compose service its replicas were grouped by, else a container of its own.
|
|
6
|
+
* Stored as the database's workloadKind and read back as written ("detected
|
|
7
|
+
* from Compose service shop-db", "Swarm service/mystack_db").
|
|
8
|
+
*/
|
|
9
|
+
export var ContainerWorkloadKind;
|
|
10
|
+
(function (ContainerWorkloadKind) {
|
|
11
|
+
ContainerWorkloadKind["Container"] = "Container";
|
|
12
|
+
ContainerWorkloadKind["ComposeService"] = "Compose service";
|
|
13
|
+
ContainerWorkloadKind["SwarmService"] = "Swarm service";
|
|
14
|
+
})(ContainerWorkloadKind || (ContainerWorkloadKind = {}));
|
|
15
|
+
/*
|
|
16
|
+
* Same matching rule as DatabaseSystemDescriptor.imageRepositories: an entry
|
|
17
|
+
* without "/" is an exact basename, an entry with one is an exact path (or
|
|
18
|
+
* a path suffix at a "/" boundary, for registry mirrors).
|
|
19
|
+
*/
|
|
20
|
+
const EXCLUDED_IMAGE_GROUPS = [
|
|
21
|
+
{
|
|
22
|
+
reason: "exporter",
|
|
23
|
+
repositories: [
|
|
24
|
+
"postgres-exporter",
|
|
25
|
+
"postgres_exporter",
|
|
26
|
+
"redis_exporter",
|
|
27
|
+
"mysqld-exporter",
|
|
28
|
+
"mongodb_exporter",
|
|
29
|
+
"elasticsearch-exporter",
|
|
30
|
+
"memcached-exporter",
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
reason: "operator",
|
|
35
|
+
repositories: [
|
|
36
|
+
"cloudnative-pg/cloudnative-pg",
|
|
37
|
+
"zalando/postgres-operator",
|
|
38
|
+
"acid/postgres-operator",
|
|
39
|
+
"crunchydata/postgres-operator",
|
|
40
|
+
"mongodb/mongodb-kubernetes",
|
|
41
|
+
"eck/eck-operator",
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
reason: "admin-ui",
|
|
46
|
+
repositories: [
|
|
47
|
+
"dpage/pgadmin4",
|
|
48
|
+
"pgadmin4",
|
|
49
|
+
"phpmyadmin",
|
|
50
|
+
"adminer",
|
|
51
|
+
"mongo-express",
|
|
52
|
+
"redisinsight",
|
|
53
|
+
"redis-commander",
|
|
54
|
+
"kibana",
|
|
55
|
+
"opensearch-dashboards",
|
|
56
|
+
"cerebro",
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
reason: "pooler",
|
|
61
|
+
repositories: [
|
|
62
|
+
"pgbouncer",
|
|
63
|
+
"crunchydata/crunchy-pgbouncer",
|
|
64
|
+
"pgpool",
|
|
65
|
+
"pgpool2",
|
|
66
|
+
"pgcat",
|
|
67
|
+
"odyssey",
|
|
68
|
+
"supavisor",
|
|
69
|
+
"proxysql",
|
|
70
|
+
"proxysql2",
|
|
71
|
+
"haproxy",
|
|
72
|
+
"mysql-router",
|
|
73
|
+
"twemproxy",
|
|
74
|
+
"gce-proxy",
|
|
75
|
+
"cloud-sql-proxy",
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
reason: "backup",
|
|
80
|
+
repositories: [
|
|
81
|
+
"pgbackrest",
|
|
82
|
+
"crunchydata/crunchy-pgbackrest",
|
|
83
|
+
"barman",
|
|
84
|
+
"wal-g",
|
|
85
|
+
"kubectl",
|
|
86
|
+
"percona-xtrabackup",
|
|
87
|
+
"percona-backup-mongodb",
|
|
88
|
+
"mydumper",
|
|
89
|
+
"postgres-backup-local",
|
|
90
|
+
"postgres-backup-s3",
|
|
91
|
+
"mysql-backup",
|
|
92
|
+
"db-backup",
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
// Part of a database deployment, but not the database process itself.
|
|
97
|
+
reason: "companion",
|
|
98
|
+
repositories: [
|
|
99
|
+
"redis-sentinel",
|
|
100
|
+
"mongodb-agent",
|
|
101
|
+
"mongodb-agent-ubi",
|
|
102
|
+
"k8ssandra/system-logger",
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
// Basename suffixes that are never a database, whatever comes before them.
|
|
107
|
+
const EXCLUDED_BASENAME_SUFFIXES = [
|
|
108
|
+
{ reason: "exporter", suffix: "-exporter" },
|
|
109
|
+
{ reason: "exporter", suffix: "_exporter" },
|
|
110
|
+
{ reason: "operator", suffix: "-operator" },
|
|
111
|
+
];
|
|
112
|
+
// Mesh proxies, log shippers, VPN / tunnel and monitoring agents that sit next to anything.
|
|
113
|
+
const INFRASTRUCTURE_SIDECAR_REPOSITORIES = [
|
|
114
|
+
"proxyv2",
|
|
115
|
+
"linkerd/proxy",
|
|
116
|
+
"linkerd2-proxy",
|
|
117
|
+
"envoy",
|
|
118
|
+
"envoyproxy/envoy",
|
|
119
|
+
"aws-appmesh-envoy",
|
|
120
|
+
"hashicorp/vault",
|
|
121
|
+
"hashicorp/consul-dataplane",
|
|
122
|
+
"fluent-bit",
|
|
123
|
+
"fluentd",
|
|
124
|
+
"filebeat",
|
|
125
|
+
"promtail",
|
|
126
|
+
"opentelemetry-collector",
|
|
127
|
+
"opentelemetry-collector-contrib",
|
|
128
|
+
"opentelemetry-collector-k8s",
|
|
129
|
+
"splunk-otel-collector",
|
|
130
|
+
"busybox",
|
|
131
|
+
"cadvisor",
|
|
132
|
+
"datadog/agent",
|
|
133
|
+
"datadoghq/agent",
|
|
134
|
+
"grafana/agent",
|
|
135
|
+
"grafana/alloy",
|
|
136
|
+
"timberio/vector",
|
|
137
|
+
"vectordotdev/vector",
|
|
138
|
+
"jaeger-agent",
|
|
139
|
+
"pmm-client",
|
|
140
|
+
"aws-xray-daemon",
|
|
141
|
+
"daprd",
|
|
142
|
+
"kuma-dp",
|
|
143
|
+
"tailscale/tailscale",
|
|
144
|
+
"cloudflare/cloudflared",
|
|
145
|
+
"bitnami/os-shell",
|
|
146
|
+
"pause",
|
|
147
|
+
];
|
|
148
|
+
/*
|
|
149
|
+
* Anchored basename patterns for images whose name carries a version, so no
|
|
150
|
+
* finite exact list covers them (Zalando Spilo: `spilo-15`, `spilo-16`).
|
|
151
|
+
* Still whole-basename matches, never substrings.
|
|
152
|
+
*/
|
|
153
|
+
const DATABASE_BASENAME_PATTERNS = [{ system: "postgresql", pattern: /^spilo(?:-\d+)?$/ }];
|
|
154
|
+
const IMAGE_REPOSITORY_REGEX = /^[a-z0-9._/-]+$/;
|
|
155
|
+
// A bare image id (untagged docker_stats image) names no repository.
|
|
156
|
+
const BARE_IMAGE_ID_REGEX = /^(?:sha256:)?[0-9a-f]{12,64}$/;
|
|
157
|
+
/*
|
|
158
|
+
* Organisations that republish another organisation's images unchanged:
|
|
159
|
+
* Bitnami's catalogue moved to `bitnamilegacy/` (frozen tags) and
|
|
160
|
+
* `bitnamisecure/` (hardened builds), under every registry that mirrors it
|
|
161
|
+
* (`docker.io/`, `public.ecr.aws/`, a Harbor proxy …).
|
|
162
|
+
*/
|
|
163
|
+
const IMAGE_ORGANIZATION_ALIASES = new Map([
|
|
164
|
+
["bitnamilegacy", "bitnami"],
|
|
165
|
+
["bitnamisecure", "bitnami"],
|
|
166
|
+
]);
|
|
167
|
+
/*
|
|
168
|
+
* Red Hat Software Collections put the engine AND its version in the image
|
|
169
|
+
* name and the image build in the tag: `registry.redhat.io/rhel9/postgresql-16:1-54`,
|
|
170
|
+
* `quay.io/sclorg/mysql-80-c9s`, `centos/postgresql-96-centos7`. Recognised
|
|
171
|
+
* only under these namespaces, so `bitnami/postgresql-repmgr` or an
|
|
172
|
+
* application called `api-2` keeps its name.
|
|
173
|
+
*/
|
|
174
|
+
const SOFTWARE_COLLECTION_NAMESPACE_REGEX = /^(?:rhel\d*|rhscl|sclorg|centos\d*|fedora)$/;
|
|
175
|
+
const SOFTWARE_COLLECTION_PLATFORM_REGEX = /^(?:rhel\d+|el\d+|centos\d+|c\d+s|ubi\d+|fedora)$/;
|
|
176
|
+
const SOFTWARE_COLLECTION_VERSION_REGEX = /^\d{1,4}$/;
|
|
177
|
+
const SOFTWARE_COLLECTION_ENGINE_REGEX = /^[a-z][a-z0-9]*$/;
|
|
178
|
+
// Red Hat UBI rebuilds carry a `-ubi` / `-ubi8` basename suffix.
|
|
179
|
+
const UBI_BASENAME_SUFFIX_REGEX = /-ubi\d*$/;
|
|
180
|
+
/*
|
|
181
|
+
* Version sources in an image reference: a Spilo image name carries the
|
|
182
|
+
* PostgreSQL major, a Percona tag carries "ppg<version>", any other tag
|
|
183
|
+
* starts with it.
|
|
184
|
+
*/
|
|
185
|
+
const SPILO_IMAGE_REGEX = /^spilo-(\d+)$/;
|
|
186
|
+
const PERCONA_POSTGRESQL_TAG_REGEX = /(?:^|-)ppg(\d+(?:\.\d+)?)(?:-|$)/;
|
|
187
|
+
const IMAGE_TAG_VERSION_REGEX = /^v?(\d+(?:\.\d+){0,2})/;
|
|
188
|
+
/*
|
|
189
|
+
* `rhel9/postgresql-16` → { engine "postgresql", digits "16" }; null for any
|
|
190
|
+
* basename that is not `<engine>-<1-4 digits>[-<platform>]`.
|
|
191
|
+
*/
|
|
192
|
+
function parseSoftwareCollectionBasename(basename) {
|
|
193
|
+
const parts = basename.split("-");
|
|
194
|
+
if (parts.length > 2 &&
|
|
195
|
+
SOFTWARE_COLLECTION_PLATFORM_REGEX.test(parts[parts.length - 1])) {
|
|
196
|
+
parts.pop();
|
|
197
|
+
}
|
|
198
|
+
if (parts.length !== 2) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
const engine = parts[0];
|
|
202
|
+
const digits = parts[1];
|
|
203
|
+
if (!SOFTWARE_COLLECTION_ENGINE_REGEX.test(engine) ||
|
|
204
|
+
!SOFTWARE_COLLECTION_VERSION_REGEX.test(digits)) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return { engine, digits };
|
|
208
|
+
}
|
|
209
|
+
/*
|
|
210
|
+
* Software Collections spell versions without the dot: `postgresql-96` is
|
|
211
|
+
* 9.6 but `postgresql-16` is 16, `mysql-80` is 8.0, `mariadb-105` 10.5 and
|
|
212
|
+
* `mariadb-1011` 10.11.
|
|
213
|
+
*/
|
|
214
|
+
function formatSoftwareCollectionVersion(engine, digits) {
|
|
215
|
+
if (engine === "postgresql") {
|
|
216
|
+
return digits.length === 2 && digits.startsWith("9")
|
|
217
|
+
? `9.${digits.substring(1)}`
|
|
218
|
+
: digits;
|
|
219
|
+
}
|
|
220
|
+
if (digits.length === 2) {
|
|
221
|
+
return `${digits.substring(0, 1)}.${digits.substring(1)}`;
|
|
222
|
+
}
|
|
223
|
+
if (digits.length === 3) {
|
|
224
|
+
return `${digits.substring(0, 2)}.${digits.substring(2)}`;
|
|
225
|
+
}
|
|
226
|
+
if (digits.length === 4) {
|
|
227
|
+
return `${digits.substring(0, 2)}.${digits.substring(2)}`;
|
|
228
|
+
}
|
|
229
|
+
return digits;
|
|
230
|
+
}
|
|
231
|
+
function splitImageReference(image) {
|
|
232
|
+
if (typeof image !== "string") {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
let value = image.trim().toLowerCase();
|
|
236
|
+
if (!value || BARE_IMAGE_ID_REGEX.test(value)) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const digestIndex = value.indexOf("@");
|
|
240
|
+
if (digestIndex >= 0) {
|
|
241
|
+
value = value.substring(0, digestIndex);
|
|
242
|
+
}
|
|
243
|
+
let tag = null;
|
|
244
|
+
const tagIndex = value.lastIndexOf(":");
|
|
245
|
+
if (tagIndex > value.lastIndexOf("/")) {
|
|
246
|
+
tag = value.substring(tagIndex + 1) || null;
|
|
247
|
+
value = value.substring(0, tagIndex);
|
|
248
|
+
}
|
|
249
|
+
const segments = value
|
|
250
|
+
.split("/")
|
|
251
|
+
.filter((segment) => {
|
|
252
|
+
return segment.length > 0;
|
|
253
|
+
});
|
|
254
|
+
// Registry host: the first segment when it has a "." or ":" or is localhost.
|
|
255
|
+
if (segments.length > 1 &&
|
|
256
|
+
(segments[0].includes(".") ||
|
|
257
|
+
segments[0].includes(":") ||
|
|
258
|
+
segments[0] === "localhost")) {
|
|
259
|
+
segments.shift();
|
|
260
|
+
}
|
|
261
|
+
if (segments.length > 1 && segments[0] === "library") {
|
|
262
|
+
segments.shift();
|
|
263
|
+
}
|
|
264
|
+
// Republishing organisations → the original (never the basename itself).
|
|
265
|
+
for (let index = 0; index < segments.length - 1; index++) {
|
|
266
|
+
const alias = IMAGE_ORGANIZATION_ALIASES.get(segments[index]);
|
|
267
|
+
if (alias) {
|
|
268
|
+
segments[index] = alias;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
let nameVersion = null;
|
|
272
|
+
const lastIndex = segments.length - 1;
|
|
273
|
+
if (lastIndex >= 0) {
|
|
274
|
+
let basename = segments[lastIndex];
|
|
275
|
+
const withoutUbi = basename.replace(UBI_BASENAME_SUFFIX_REGEX, "");
|
|
276
|
+
if (withoutUbi && withoutUbi !== basename) {
|
|
277
|
+
basename = withoutUbi;
|
|
278
|
+
}
|
|
279
|
+
if (lastIndex >= 1 &&
|
|
280
|
+
SOFTWARE_COLLECTION_NAMESPACE_REGEX.test(segments[lastIndex - 1])) {
|
|
281
|
+
const collection = parseSoftwareCollectionBasename(basename);
|
|
282
|
+
if (collection) {
|
|
283
|
+
basename = collection.engine;
|
|
284
|
+
nameVersion = formatSoftwareCollectionVersion(collection.engine, collection.digits);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
segments[lastIndex] = basename;
|
|
288
|
+
}
|
|
289
|
+
const repository = segments.join("/");
|
|
290
|
+
if (!repository || !IMAGE_REPOSITORY_REGEX.test(repository)) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
return { repository, tag, nameVersion };
|
|
294
|
+
}
|
|
295
|
+
function basenameOf(repository) {
|
|
296
|
+
return repository.substring(repository.lastIndexOf("/") + 1);
|
|
297
|
+
}
|
|
298
|
+
function repositoryMatches(repository, entry) {
|
|
299
|
+
if (entry.includes("/")) {
|
|
300
|
+
return repository === entry || repository.endsWith(`/${entry}`);
|
|
301
|
+
}
|
|
302
|
+
return basenameOf(repository) === entry;
|
|
303
|
+
}
|
|
304
|
+
function matchesAny(repository, entries) {
|
|
305
|
+
for (const entry of entries) {
|
|
306
|
+
if (repositoryMatches(repository, entry)) {
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* The image's repository path without registry host, `library/`, tag or
|
|
314
|
+
* digest, lowercased: `docker.io/library/postgres:16` → `postgres`,
|
|
315
|
+
* `ghcr.io/cloudnative-pg/postgresql:16.2@sha256:…` →
|
|
316
|
+
* `cloudnative-pg/postgresql`. Republished images read as the original
|
|
317
|
+
* (`bitnamilegacy/redis-cluster` → `bitnami/redis-cluster`), a Red Hat
|
|
318
|
+
* Software Collections name loses its version and platform
|
|
319
|
+
* (`registry.redhat.io/rhel9/postgresql-16` → `rhel9/postgresql`) and a UBI
|
|
320
|
+
* rebuild its `-ubi` suffix. Null for an empty value or a bare image id.
|
|
321
|
+
*/
|
|
322
|
+
export function normalizeImageRepository(image) {
|
|
323
|
+
const split = splitImageReference(image);
|
|
324
|
+
return split ? split.repository : null;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Best-effort engine version from the image: the leading
|
|
328
|
+
* `major[.minor[.patch]]` of the tag (`16.2-alpine` → `16.2`,
|
|
329
|
+
* `16.2.0-debian-12-r5` → `16.2.0`), the Postgres major of a Spilo image
|
|
330
|
+
* (`spilo-16` → `16`) or of a Percona `…-ppg16-postgres` tag, and the
|
|
331
|
+
* version in a Software Collections name (`rhel9/mysql-80` → `8.0`; its tag
|
|
332
|
+
* is the image build, never read). Null for `latest`, a digest-only
|
|
333
|
+
* reference or a tag that does not start with a number.
|
|
334
|
+
*/
|
|
335
|
+
export function parseImageVersion(image) {
|
|
336
|
+
const split = splitImageReference(image);
|
|
337
|
+
if (!split) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
if (split.nameVersion) {
|
|
341
|
+
return split.nameVersion;
|
|
342
|
+
}
|
|
343
|
+
const spilo = SPILO_IMAGE_REGEX.exec(basenameOf(split.repository));
|
|
344
|
+
if (spilo) {
|
|
345
|
+
return spilo[1];
|
|
346
|
+
}
|
|
347
|
+
if (!split.tag) {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
const percona = PERCONA_POSTGRESQL_TAG_REGEX.exec(split.tag);
|
|
351
|
+
if (percona) {
|
|
352
|
+
return percona[1];
|
|
353
|
+
}
|
|
354
|
+
const version = IMAGE_TAG_VERSION_REGEX.exec(split.tag);
|
|
355
|
+
return version ? version[1] : null;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* What a container image is: a known database engine, something that must
|
|
359
|
+
* not count as one (with the reason), an infrastructure sidecar, or unknown.
|
|
360
|
+
*/
|
|
361
|
+
export function classifyImage(image) {
|
|
362
|
+
const repository = normalizeImageRepository(image);
|
|
363
|
+
if (!repository) {
|
|
364
|
+
return { kind: "unknown" };
|
|
365
|
+
}
|
|
366
|
+
for (const group of EXCLUDED_IMAGE_GROUPS) {
|
|
367
|
+
if (matchesAny(repository, group.repositories)) {
|
|
368
|
+
return { kind: "excluded", reason: group.reason };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const basename = basenameOf(repository);
|
|
372
|
+
for (const excluded of EXCLUDED_BASENAME_SUFFIXES) {
|
|
373
|
+
if (basename.endsWith(excluded.suffix)) {
|
|
374
|
+
return { kind: "excluded", reason: excluded.reason };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (matchesAny(repository, INFRASTRUCTURE_SIDECAR_REPOSITORIES)) {
|
|
378
|
+
return { kind: "infrastructure-sidecar" };
|
|
379
|
+
}
|
|
380
|
+
for (const descriptor of DATABASE_SYSTEMS) {
|
|
381
|
+
if (matchesAny(repository, descriptor.imageRepositories)) {
|
|
382
|
+
return { kind: "database", system: descriptor.system };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
for (const entry of DATABASE_BASENAME_PATTERNS) {
|
|
386
|
+
if (entry.pattern.test(basename)) {
|
|
387
|
+
return { kind: "database", system: entry.system };
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return { kind: "unknown" };
|
|
391
|
+
}
|
|
392
|
+
const SKIPPED_POD_PHASES = new Set([
|
|
393
|
+
"succeeded",
|
|
394
|
+
"failed",
|
|
395
|
+
]);
|
|
396
|
+
const BATCH_OWNER_KINDS = new Set([
|
|
397
|
+
"job",
|
|
398
|
+
"cronjob",
|
|
399
|
+
]);
|
|
400
|
+
// Owner kinds whose name conventionally is also a Service's name.
|
|
401
|
+
const SERVICE_NAMED_OWNER_KINDS = new Set([
|
|
402
|
+
"StatefulSet",
|
|
403
|
+
"Deployment",
|
|
404
|
+
"DaemonSet",
|
|
405
|
+
]);
|
|
406
|
+
/*
|
|
407
|
+
* `app.kubernetes.io/component` values of pods that carry a database's
|
|
408
|
+
* cluster/chart labels but are not a database member: poolers, proxies,
|
|
409
|
+
* routers, exporters, orchestrators, backup and monitoring pods.
|
|
410
|
+
*/
|
|
411
|
+
const NON_MEMBER_COMPONENTS = new Set([
|
|
412
|
+
"pgpool",
|
|
413
|
+
"pgbouncer",
|
|
414
|
+
"proxysql",
|
|
415
|
+
"haproxy",
|
|
416
|
+
"mongos",
|
|
417
|
+
"metrics",
|
|
418
|
+
"exporter",
|
|
419
|
+
"router",
|
|
420
|
+
"mysqlrouter",
|
|
421
|
+
"orc",
|
|
422
|
+
"orchestrator",
|
|
423
|
+
"backup",
|
|
424
|
+
"pmm",
|
|
425
|
+
]);
|
|
426
|
+
/*
|
|
427
|
+
* Exclusion reasons that make a pod a non-member on their own. "operator"
|
|
428
|
+
* and "companion" are deliberately absent: Percona's PostgreSQL operator
|
|
429
|
+
* ships its postgres operand in the operator's image repository, and a
|
|
430
|
+
* Sentinel / agent container never runs alone in a member pod anyway.
|
|
431
|
+
*/
|
|
432
|
+
const NON_MEMBER_IMAGE_REASONS = new Set([
|
|
433
|
+
"pooler",
|
|
434
|
+
"exporter",
|
|
435
|
+
"admin-ui",
|
|
436
|
+
"backup",
|
|
437
|
+
]);
|
|
438
|
+
/*
|
|
439
|
+
* False for a container whose image is never the database process itself:
|
|
440
|
+
* a mesh proxy / log shipper, or an image excluded as a pooler, exporter,
|
|
441
|
+
* admin UI, backup tool or companion (Sentinel, agent). An "operator" image
|
|
442
|
+
* still could be — Percona ships its postgres operand in one.
|
|
443
|
+
*/
|
|
444
|
+
function couldBeDatabaseProcess(container) {
|
|
445
|
+
const classification = container.classification;
|
|
446
|
+
if (classification.kind === "infrastructure-sidecar") {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
return !(classification.kind === "excluded" &&
|
|
450
|
+
(NON_MEMBER_IMAGE_REASONS.has(classification.reason) ||
|
|
451
|
+
classification.reason === "companion"));
|
|
452
|
+
}
|
|
453
|
+
const PRIMARY_ROLE_VALUES = new Set([
|
|
454
|
+
"primary",
|
|
455
|
+
"master",
|
|
456
|
+
]);
|
|
457
|
+
const REPLICA_ROLE_VALUES = new Set([
|
|
458
|
+
"replica",
|
|
459
|
+
"replicas",
|
|
460
|
+
"read",
|
|
461
|
+
"secondary",
|
|
462
|
+
"slave",
|
|
463
|
+
"standby",
|
|
464
|
+
]);
|
|
465
|
+
function labelValue(labels, key) {
|
|
466
|
+
if (!Object.prototype.hasOwnProperty.call(labels, key)) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
const value = labels[key];
|
|
470
|
+
if (typeof value === "string") {
|
|
471
|
+
const trimmed = value.trim();
|
|
472
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
473
|
+
}
|
|
474
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
475
|
+
return String(value);
|
|
476
|
+
}
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
function hasLabel(labels, key) {
|
|
480
|
+
return Object.prototype.hasOwnProperty.call(labels, key);
|
|
481
|
+
}
|
|
482
|
+
function roleFrom(value) {
|
|
483
|
+
if (!value) {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
const normalized = value.trim().toLowerCase();
|
|
487
|
+
if (PRIMARY_ROLE_VALUES.has(normalized)) {
|
|
488
|
+
return "primary";
|
|
489
|
+
}
|
|
490
|
+
if (REPLICA_ROLE_VALUES.has(normalized)) {
|
|
491
|
+
return "replica";
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
const CHART_SYSTEM_BY_NAME = (() => {
|
|
496
|
+
const map = new Map();
|
|
497
|
+
for (const descriptor of DATABASE_SYSTEMS) {
|
|
498
|
+
for (const chartName of descriptor.kubernetesChartNames) {
|
|
499
|
+
// Labels are compared lowercased.
|
|
500
|
+
map.set(chartName.toLowerCase(), descriptor.system);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return map;
|
|
504
|
+
})();
|
|
505
|
+
const PERCONA_SYSTEM_BY_NAME = new Map([
|
|
506
|
+
["percona-xtradb-cluster", "mysql"],
|
|
507
|
+
["percona-server", "mysql"],
|
|
508
|
+
["percona-server-mongodb", "mongodb"],
|
|
509
|
+
]);
|
|
510
|
+
/**
|
|
511
|
+
* Label keys whose presence makes an operator rule look at a pod. With
|
|
512
|
+
* DATABASE_WORKLOAD_NAME_LABEL_VALUES this is the whole trigger surface of
|
|
513
|
+
* the label rules, so a store can pre-select the pods worth classifying
|
|
514
|
+
* (`labels ?| keys OR lower(labels->>'app.kubernetes.io/name') = ANY(values)`)
|
|
515
|
+
* — see hasDatabaseWorkloadLabels, which is the same test in TypeScript.
|
|
516
|
+
*/
|
|
517
|
+
export const DATABASE_OPERATOR_LABEL_KEYS = [
|
|
518
|
+
"cnpg.io/cluster",
|
|
519
|
+
"cluster-name",
|
|
520
|
+
"postgres-operator.crunchydata.com/cluster",
|
|
521
|
+
"mysql.oracle.com/cluster",
|
|
522
|
+
"elasticsearch.k8s.elastic.co/cluster-name",
|
|
523
|
+
"clickhouse.altinity.com/chi",
|
|
524
|
+
"cassandra.datastax.com/cluster",
|
|
525
|
+
"planetscale.com/cluster",
|
|
526
|
+
];
|
|
527
|
+
// Lowercase `app.kubernetes.io/name` values a chart / Percona rule matches.
|
|
528
|
+
export const DATABASE_WORKLOAD_NAME_LABEL_VALUES = Array.from(new Set([
|
|
529
|
+
...Array.from(CHART_SYSTEM_BY_NAME.keys()),
|
|
530
|
+
...Array.from(PERCONA_SYSTEM_BY_NAME.keys()),
|
|
531
|
+
])).sort();
|
|
532
|
+
/**
|
|
533
|
+
* True when a pod's labels could make an operator / chart rule match — the
|
|
534
|
+
* TypeScript twin of the store-side pre-filter over
|
|
535
|
+
* DATABASE_OPERATOR_LABEL_KEYS and DATABASE_WORKLOAD_NAME_LABEL_VALUES.
|
|
536
|
+
*/
|
|
537
|
+
export function hasDatabaseWorkloadLabels(labels) {
|
|
538
|
+
if (!labels || typeof labels !== "object" || Array.isArray(labels)) {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
const record = labels;
|
|
542
|
+
for (const key of DATABASE_OPERATOR_LABEL_KEYS) {
|
|
543
|
+
if (hasLabel(record, key)) {
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const name = labelValue(record, "app.kubernetes.io/name");
|
|
548
|
+
return (name !== null &&
|
|
549
|
+
DATABASE_WORKLOAD_NAME_LABEL_VALUES.includes(name.toLowerCase()));
|
|
550
|
+
}
|
|
551
|
+
/*
|
|
552
|
+
* Operator / chart label rules, most specific first. Each returns a match,
|
|
553
|
+
* "skip" (the pod belongs to a database cluster but is not a member — a
|
|
554
|
+
* pooler, a backup repo host), or null (not this operator). Label keys and
|
|
555
|
+
* Service naming follow each operator's documented conventions, including
|
|
556
|
+
* the proxy / pooler Services clients usually connect through.
|
|
557
|
+
*/
|
|
558
|
+
const OPERATOR_RULES = [
|
|
559
|
+
// CloudNativePG.
|
|
560
|
+
(labels) => {
|
|
561
|
+
const cluster = labelValue(labels, "cnpg.io/cluster");
|
|
562
|
+
if (!cluster) {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
const podRole = labelValue(labels, "cnpg.io/podRole");
|
|
566
|
+
if ((podRole && podRole.toLowerCase() !== "instance") ||
|
|
567
|
+
labelValue(labels, "cnpg.io/poolerName")) {
|
|
568
|
+
return "skip";
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
operator: "cloudnative-pg",
|
|
572
|
+
system: "postgresql",
|
|
573
|
+
clusterName: cluster,
|
|
574
|
+
role: roleFrom(labelValue(labels, "cnpg.io/instanceRole") ||
|
|
575
|
+
labelValue(labels, "role")),
|
|
576
|
+
serviceNames: [`${cluster}-rw`, `${cluster}-ro`, `${cluster}-r`],
|
|
577
|
+
evidence: `label:cnpg.io/cluster=${cluster}`,
|
|
578
|
+
};
|
|
579
|
+
},
|
|
580
|
+
// Zalando postgres-operator (Spilo).
|
|
581
|
+
(labels) => {
|
|
582
|
+
const application = labelValue(labels, "application");
|
|
583
|
+
const cluster = labelValue(labels, "cluster-name");
|
|
584
|
+
if (!application || application.toLowerCase() !== "spilo" || !cluster) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
operator: "zalando",
|
|
589
|
+
system: "postgresql",
|
|
590
|
+
clusterName: cluster,
|
|
591
|
+
role: roleFrom(labelValue(labels, "spilo-role")),
|
|
592
|
+
serviceNames: [
|
|
593
|
+
cluster,
|
|
594
|
+
`${cluster}-repl`,
|
|
595
|
+
`${cluster}-pooler`,
|
|
596
|
+
`${cluster}-pooler-repl`,
|
|
597
|
+
],
|
|
598
|
+
evidence: `label:cluster-name=${cluster}`,
|
|
599
|
+
};
|
|
600
|
+
},
|
|
601
|
+
// Crunchy Data PGO v5 (also Percona's PostgreSQL operator v2, a fork).
|
|
602
|
+
(labels) => {
|
|
603
|
+
const cluster = labelValue(labels, "postgres-operator.crunchydata.com/cluster");
|
|
604
|
+
if (!cluster) {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
const role = labelValue(labels, "postgres-operator.crunchydata.com/role");
|
|
608
|
+
if ((role && ["pgbouncer", "pgadmin"].includes(role.toLowerCase())) ||
|
|
609
|
+
hasLabel(labels, "postgres-operator.crunchydata.com/pgbackrest-dedicated") ||
|
|
610
|
+
hasLabel(labels, "postgres-operator.crunchydata.com/pgadmin")) {
|
|
611
|
+
return "skip";
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
operator: "crunchy",
|
|
615
|
+
system: "postgresql",
|
|
616
|
+
clusterName: cluster,
|
|
617
|
+
role: roleFrom(role),
|
|
618
|
+
serviceNames: [
|
|
619
|
+
`${cluster}-primary`,
|
|
620
|
+
`${cluster}-replicas`,
|
|
621
|
+
`${cluster}-ha`,
|
|
622
|
+
`${cluster}-pgbouncer`,
|
|
623
|
+
],
|
|
624
|
+
evidence: `label:postgres-operator.crunchydata.com/cluster=${cluster}`,
|
|
625
|
+
};
|
|
626
|
+
},
|
|
627
|
+
// Percona XtraDB Cluster / Server for MySQL / Server for MongoDB operators.
|
|
628
|
+
(labels) => {
|
|
629
|
+
const managedBy = labelValue(labels, "app.kubernetes.io/managed-by");
|
|
630
|
+
const name = labelValue(labels, "app.kubernetes.io/name");
|
|
631
|
+
const instance = labelValue(labels, "app.kubernetes.io/instance");
|
|
632
|
+
if (!managedBy ||
|
|
633
|
+
!managedBy.toLowerCase().startsWith("percona-") ||
|
|
634
|
+
!name ||
|
|
635
|
+
!instance) {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
const system = PERCONA_SYSTEM_BY_NAME.get(name.toLowerCase());
|
|
639
|
+
if (!system) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
const serviceNames = [];
|
|
643
|
+
const normalizedName = name.toLowerCase();
|
|
644
|
+
if (normalizedName === "percona-xtradb-cluster") {
|
|
645
|
+
serviceNames.push(`${instance}-pxc`, `${instance}-haproxy`, `${instance}-haproxy-replicas`, `${instance}-proxysql`);
|
|
646
|
+
}
|
|
647
|
+
else if (normalizedName === "percona-server") {
|
|
648
|
+
serviceNames.push(`${instance}-mysql`, `${instance}-mysql-primary`, `${instance}-haproxy`, `${instance}-router`);
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
const replicaSet = labelValue(labels, "app.kubernetes.io/replset");
|
|
652
|
+
if (replicaSet) {
|
|
653
|
+
serviceNames.push(`${instance}-${replicaSet}`);
|
|
654
|
+
}
|
|
655
|
+
serviceNames.push(`${instance}-mongos`);
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
operator: "percona",
|
|
659
|
+
system,
|
|
660
|
+
clusterName: instance,
|
|
661
|
+
role: null,
|
|
662
|
+
serviceNames,
|
|
663
|
+
evidence: `label:app.kubernetes.io/name=${name}`,
|
|
664
|
+
};
|
|
665
|
+
},
|
|
666
|
+
// Oracle MySQL Operator (InnoDB Cluster).
|
|
667
|
+
(labels) => {
|
|
668
|
+
const cluster = labelValue(labels, "mysql.oracle.com/cluster");
|
|
669
|
+
if (!cluster) {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
const component = labelValue(labels, "component");
|
|
673
|
+
if (component && NON_MEMBER_COMPONENTS.has(component.toLowerCase())) {
|
|
674
|
+
return "skip";
|
|
675
|
+
}
|
|
676
|
+
const clusterRole = labelValue(labels, "mysql.oracle.com/cluster-role");
|
|
677
|
+
return {
|
|
678
|
+
operator: "mysql-operator",
|
|
679
|
+
system: "mysql",
|
|
680
|
+
clusterName: cluster,
|
|
681
|
+
role: roleFrom(clusterRole),
|
|
682
|
+
serviceNames: [`${cluster}-instances`],
|
|
683
|
+
evidence: `label:mysql.oracle.com/cluster=${cluster}`,
|
|
684
|
+
};
|
|
685
|
+
},
|
|
686
|
+
// Elastic Cloud on Kubernetes.
|
|
687
|
+
(labels) => {
|
|
688
|
+
const type = labelValue(labels, "common.k8s.elastic.co/type");
|
|
689
|
+
const cluster = labelValue(labels, "elasticsearch.k8s.elastic.co/cluster-name");
|
|
690
|
+
if (!type || type.toLowerCase() !== "elasticsearch" || !cluster) {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
operator: "eck",
|
|
695
|
+
system: "elasticsearch",
|
|
696
|
+
clusterName: cluster,
|
|
697
|
+
role: null,
|
|
698
|
+
serviceNames: [`${cluster}-es-http`],
|
|
699
|
+
evidence: `label:elasticsearch.k8s.elastic.co/cluster-name=${cluster}`,
|
|
700
|
+
};
|
|
701
|
+
},
|
|
702
|
+
// Altinity ClickHouse operator.
|
|
703
|
+
(labels) => {
|
|
704
|
+
const installation = labelValue(labels, "clickhouse.altinity.com/chi");
|
|
705
|
+
if (!installation) {
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
return {
|
|
709
|
+
operator: "altinity",
|
|
710
|
+
system: "clickhouse",
|
|
711
|
+
clusterName: installation,
|
|
712
|
+
role: null,
|
|
713
|
+
serviceNames: [`clickhouse-${installation}`],
|
|
714
|
+
evidence: `label:clickhouse.altinity.com/chi=${installation}`,
|
|
715
|
+
};
|
|
716
|
+
},
|
|
717
|
+
// K8ssandra cass-operator.
|
|
718
|
+
(labels) => {
|
|
719
|
+
const cluster = labelValue(labels, "cassandra.datastax.com/cluster");
|
|
720
|
+
if (!cluster) {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
const datacenter = labelValue(labels, "cassandra.datastax.com/datacenter");
|
|
724
|
+
return {
|
|
725
|
+
operator: "cass-operator",
|
|
726
|
+
system: "cassandra",
|
|
727
|
+
clusterName: cluster,
|
|
728
|
+
role: null,
|
|
729
|
+
serviceNames: datacenter
|
|
730
|
+
? [
|
|
731
|
+
`${cluster}-${datacenter}-service`,
|
|
732
|
+
`${cluster}-${datacenter}-all-pods-service`,
|
|
733
|
+
]
|
|
734
|
+
: [],
|
|
735
|
+
evidence: `label:cassandra.datastax.com/cluster=${cluster}`,
|
|
736
|
+
};
|
|
737
|
+
},
|
|
738
|
+
/*
|
|
739
|
+
* The Vitess operator (PlanetScale). It runs every component on the same
|
|
740
|
+
* `vitess/lite` image, so only `planetscale.com/component` tells them
|
|
741
|
+
* apart. The database clients connect to is vtgate, speaking MySQL; the
|
|
742
|
+
* vttablets (each with its own mysqld), vtctld, vtorc, vtbackup, vtadmin,
|
|
743
|
+
* the etcd lockserver and the backup-storage subcontroller are parts of
|
|
744
|
+
* the VitessCluster, not members. vtgate Services carry a hash suffix
|
|
745
|
+
* (`<cluster>-vtgate-<hash>`), so none are guessed: the owner
|
|
746
|
+
* Deployment's name is added, as for any Deployment.
|
|
747
|
+
*/
|
|
748
|
+
(labels) => {
|
|
749
|
+
const cluster = labelValue(labels, "planetscale.com/cluster");
|
|
750
|
+
if (!cluster) {
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
const component = labelValue(labels, "planetscale.com/component");
|
|
754
|
+
if (!component || component.toLowerCase() !== "vtgate") {
|
|
755
|
+
return "skip";
|
|
756
|
+
}
|
|
757
|
+
return {
|
|
758
|
+
operator: "vitess-operator",
|
|
759
|
+
system: "vitess",
|
|
760
|
+
clusterName: cluster,
|
|
761
|
+
role: null,
|
|
762
|
+
serviceNames: [],
|
|
763
|
+
evidence: `label:planetscale.com/cluster=${cluster}`,
|
|
764
|
+
};
|
|
765
|
+
},
|
|
766
|
+
// Bitnami and most other Helm charts (`app.kubernetes.io/name`).
|
|
767
|
+
(labels) => {
|
|
768
|
+
const chartName = labelValue(labels, "app.kubernetes.io/name");
|
|
769
|
+
if (!chartName) {
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
772
|
+
const chart = chartName.toLowerCase();
|
|
773
|
+
const system = CHART_SYSTEM_BY_NAME.get(chart);
|
|
774
|
+
if (!system) {
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
777
|
+
const instance = labelValue(labels, "app.kubernetes.io/instance");
|
|
778
|
+
const component = labelValue(labels, "app.kubernetes.io/component");
|
|
779
|
+
/*
|
|
780
|
+
* Helm's conventional fullname: the release name when it already
|
|
781
|
+
* contains the chart name, else `${release}-${chart}`. A
|
|
782
|
+
* `fullnameOverride` breaks the guess; the owner StatefulSet's name and
|
|
783
|
+
* its headless Service (added for every pod) still name the real ones.
|
|
784
|
+
*/
|
|
785
|
+
const fullName = instance
|
|
786
|
+
? instance.toLowerCase().includes(chart)
|
|
787
|
+
? instance
|
|
788
|
+
: `${instance}-${chart}`
|
|
789
|
+
: null;
|
|
790
|
+
const serviceNames = [];
|
|
791
|
+
if (fullName) {
|
|
792
|
+
serviceNames.push(`${fullName}-headless`, `${fullName}-hl`);
|
|
793
|
+
if (component) {
|
|
794
|
+
serviceNames.push(`${fullName}-${component.toLowerCase()}`);
|
|
795
|
+
if (component.toLowerCase() === "replica") {
|
|
796
|
+
serviceNames.push(`${fullName}-replicas`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
const managedBy = labelValue(labels, "app.kubernetes.io/managed-by");
|
|
801
|
+
return {
|
|
802
|
+
operator: managedBy && managedBy.toLowerCase() === "helm" ? "helm" : null,
|
|
803
|
+
system,
|
|
804
|
+
clusterName: fullName,
|
|
805
|
+
role: roleFrom(component),
|
|
806
|
+
serviceNames,
|
|
807
|
+
evidence: `label:app.kubernetes.io/name=${chartName}`,
|
|
808
|
+
};
|
|
809
|
+
},
|
|
810
|
+
];
|
|
811
|
+
function readPorts(declaredPorts) {
|
|
812
|
+
const ports = [];
|
|
813
|
+
if (!Array.isArray(declaredPorts)) {
|
|
814
|
+
return ports;
|
|
815
|
+
}
|
|
816
|
+
for (const declared of declaredPorts) {
|
|
817
|
+
const port = declared && typeof declared === "object"
|
|
818
|
+
? declared.containerPort
|
|
819
|
+
: undefined;
|
|
820
|
+
if (typeof port === "number" &&
|
|
821
|
+
Number.isInteger(port) &&
|
|
822
|
+
port >= 1 &&
|
|
823
|
+
port <= 65535 &&
|
|
824
|
+
!ports.includes(port)) {
|
|
825
|
+
ports.push(port);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return ports;
|
|
829
|
+
}
|
|
830
|
+
/*
|
|
831
|
+
* Reads ONLY name, image, declared ports and what command/args run —
|
|
832
|
+
* never `env` (see the file header), never by spreading the container
|
|
833
|
+
* object.
|
|
834
|
+
*/
|
|
835
|
+
function readContainers(pod) {
|
|
836
|
+
var _a;
|
|
837
|
+
const containers = (_a = pod.spec) === null || _a === void 0 ? void 0 : _a.containers;
|
|
838
|
+
if (!Array.isArray(containers)) {
|
|
839
|
+
return [];
|
|
840
|
+
}
|
|
841
|
+
const views = [];
|
|
842
|
+
for (const container of containers) {
|
|
843
|
+
if (!container || typeof container !== "object") {
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
const name = container.name;
|
|
847
|
+
const image = container.image;
|
|
848
|
+
const imageText = typeof image === "string" && image.trim() ? image.trim() : null;
|
|
849
|
+
views.push({
|
|
850
|
+
name: typeof name === "string" ? name.trim() : "",
|
|
851
|
+
image: imageText,
|
|
852
|
+
ports: readPorts(container.ports),
|
|
853
|
+
nonServer: isNonServerCommand({
|
|
854
|
+
command: container.command,
|
|
855
|
+
args: container.args,
|
|
856
|
+
}),
|
|
857
|
+
classification: classifyImage(imageText),
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
return views;
|
|
861
|
+
}
|
|
862
|
+
function readOwners(pod) {
|
|
863
|
+
var _a;
|
|
864
|
+
const items = (_a = pod.ownerReferences) === null || _a === void 0 ? void 0 : _a.items;
|
|
865
|
+
if (!Array.isArray(items)) {
|
|
866
|
+
return [];
|
|
867
|
+
}
|
|
868
|
+
const owners = [];
|
|
869
|
+
for (const item of items) {
|
|
870
|
+
const kind = item === null || item === void 0 ? void 0 : item.kind;
|
|
871
|
+
const name = item === null || item === void 0 ? void 0 : item.name;
|
|
872
|
+
if (typeof kind === "string" &&
|
|
873
|
+
kind.trim() &&
|
|
874
|
+
typeof name === "string" &&
|
|
875
|
+
name.trim()) {
|
|
876
|
+
owners.push({ kind: kind.trim(), name: name.trim() });
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return owners;
|
|
880
|
+
}
|
|
881
|
+
function resolveOwnerWorkload(podName, owner, labels) {
|
|
882
|
+
if (!owner) {
|
|
883
|
+
return { workloadKind: "Pod", workloadName: podName };
|
|
884
|
+
}
|
|
885
|
+
const kind = owner.kind.toLowerCase();
|
|
886
|
+
if (kind === "statefulset") {
|
|
887
|
+
return { workloadKind: "StatefulSet", workloadName: owner.name };
|
|
888
|
+
}
|
|
889
|
+
if (kind === "daemonset") {
|
|
890
|
+
return { workloadKind: "DaemonSet", workloadName: owner.name };
|
|
891
|
+
}
|
|
892
|
+
if (kind === "replicaset") {
|
|
893
|
+
/*
|
|
894
|
+
* A Deployment's ReplicaSet is `${deployment}-${pod-template-hash}`, and
|
|
895
|
+
* the pod carries that hash as a label — so stripping it is exact, with
|
|
896
|
+
* no guessing at what a hash looks like.
|
|
897
|
+
*/
|
|
898
|
+
const hash = labelValue(labels, "pod-template-hash");
|
|
899
|
+
const suffix = hash ? `-${hash}` : "";
|
|
900
|
+
if (hash &&
|
|
901
|
+
owner.name.endsWith(suffix) &&
|
|
902
|
+
owner.name.length > suffix.length) {
|
|
903
|
+
return {
|
|
904
|
+
workloadKind: "Deployment",
|
|
905
|
+
workloadName: owner.name.substring(0, owner.name.length - suffix.length),
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
return { workloadKind: "ReplicaSet", workloadName: owner.name };
|
|
909
|
+
}
|
|
910
|
+
return { workloadKind: owner.kind, workloadName: owner.name };
|
|
911
|
+
}
|
|
912
|
+
function uniqueNonEmpty(values) {
|
|
913
|
+
const result = [];
|
|
914
|
+
for (const value of values) {
|
|
915
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
916
|
+
if (trimmed && !result.includes(trimmed)) {
|
|
917
|
+
result.push(trimmed);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return result;
|
|
921
|
+
}
|
|
922
|
+
/*
|
|
923
|
+
* A database container with one unrecognised neighbour is still a database
|
|
924
|
+
* pod when the evidence says so on its own: a StatefulSet (the shape
|
|
925
|
+
* databases are deployed in, not apps with a cache sidecar) whose database
|
|
926
|
+
* container declares its engine's default port. A log shipper or backup
|
|
927
|
+
* agent nobody has catalogued yet must not hide a Postgres StatefulSet.
|
|
928
|
+
*/
|
|
929
|
+
function toleratesUnknownNeighbour(data) {
|
|
930
|
+
if (data.unknownNeighbours !== 1 || data.ownerKind !== "StatefulSet") {
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
const defaultPort = getDefaultDatabasePort(data.system);
|
|
934
|
+
return (defaultPort !== null && data.databaseContainer.ports.includes(defaultPort));
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* The database a Kubernetes pod runs, or null. Operator / chart labels are
|
|
938
|
+
* consulted first (they name the cluster: workloadKind "Cluster"); otherwise
|
|
939
|
+
* the first container whose image is a database, provided no other
|
|
940
|
+
* container is an unrecognised application (a DB sidecar in an app pod is
|
|
941
|
+
* not a database — one unknown neighbour is tolerated in a StatefulSet whose
|
|
942
|
+
* database container declares the engine's default port). Batch pods,
|
|
943
|
+
* finished pods, client / debug runs, owner-less pods declaring no port and
|
|
944
|
+
* non-member pods of a database cluster (poolers, routers, exporters,
|
|
945
|
+
* backup repos) are null.
|
|
946
|
+
*/
|
|
947
|
+
export function classifyKubernetesPod(pod, extra) {
|
|
948
|
+
if (!pod || typeof pod !== "object") {
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
const podName = typeof pod.name === "string" ? pod.name.trim() : "";
|
|
952
|
+
const namespace = typeof pod.namespaceKey === "string" ? pod.namespaceKey.trim() : "";
|
|
953
|
+
if (!podName) {
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
if (typeof pod.phase === "string" &&
|
|
957
|
+
SKIPPED_POD_PHASES.has(pod.phase.trim().toLowerCase())) {
|
|
958
|
+
return null;
|
|
959
|
+
}
|
|
960
|
+
const owners = readOwners(pod);
|
|
961
|
+
for (const owner of owners) {
|
|
962
|
+
if (BATCH_OWNER_KINDS.has(owner.kind.toLowerCase())) {
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
const owner = owners[0] || null;
|
|
967
|
+
const labels = pod.labels && typeof pod.labels === "object" ? pod.labels : {};
|
|
968
|
+
const containers = readContainers(pod);
|
|
969
|
+
const statefulSetServiceNames = (extra === null || extra === void 0 ? void 0 : extra.statefulSetServiceNames) &&
|
|
970
|
+
typeof extra.statefulSetServiceNames === "object"
|
|
971
|
+
? extra.statefulSetServiceNames
|
|
972
|
+
: {};
|
|
973
|
+
const headlessServiceName = owner &&
|
|
974
|
+
owner.kind.toLowerCase() === "statefulset" &&
|
|
975
|
+
Object.prototype.hasOwnProperty.call(statefulSetServiceNames, owner.name) &&
|
|
976
|
+
typeof statefulSetServiceNames[owner.name] === "string" &&
|
|
977
|
+
statefulSetServiceNames[owner.name].trim()
|
|
978
|
+
? statefulSetServiceNames[owner.name].trim()
|
|
979
|
+
: null;
|
|
980
|
+
const ownerWorkload = resolveOwnerWorkload(podName, owner, labels);
|
|
981
|
+
const ownerKind = owner ? ownerWorkload.workloadKind : null;
|
|
982
|
+
const ownerName = owner ? ownerWorkload.workloadName : null;
|
|
983
|
+
// 1. Operator / chart labels.
|
|
984
|
+
for (const rule of OPERATOR_RULES) {
|
|
985
|
+
const match = rule(labels);
|
|
986
|
+
if (match === null) {
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
if (match === "skip") {
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
const component = labelValue(labels, "app.kubernetes.io/component");
|
|
993
|
+
if (component && NON_MEMBER_COMPONENTS.has(component.toLowerCase())) {
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
/*
|
|
997
|
+
* A pod that only runs poolers / exporters / admin UIs / backup tools is
|
|
998
|
+
* not a member even when it carries the cluster's labels (a router or
|
|
999
|
+
* pooler Deployment the operator labels as part of the cluster).
|
|
1000
|
+
*/
|
|
1001
|
+
const onlyNonMemberContainers = containers.length > 0 &&
|
|
1002
|
+
containers.every((container) => {
|
|
1003
|
+
return (container.classification.kind === "excluded" &&
|
|
1004
|
+
NON_MEMBER_IMAGE_REASONS.has(container.classification.reason));
|
|
1005
|
+
});
|
|
1006
|
+
if (onlyNonMemberContainers) {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
/*
|
|
1010
|
+
* A chart's test hook or a debug pod is labelled like the cluster but
|
|
1011
|
+
* only runs a client, and a member whose database container is held
|
|
1012
|
+
* asleep (diagnostic mode) may still run its exporter, pooler or mesh
|
|
1013
|
+
* proxy: with no running container left that could be the server, it
|
|
1014
|
+
* is not a member — a sidecar's image, version and ports never stand in
|
|
1015
|
+
* for the database's.
|
|
1016
|
+
*/
|
|
1017
|
+
const serverContainers = containers.filter((container) => {
|
|
1018
|
+
return !container.nonServer && couldBeDatabaseProcess(container);
|
|
1019
|
+
});
|
|
1020
|
+
if (containers.length > 0 && serverContainers.length === 0) {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
const databaseContainer = serverContainers.find((container) => {
|
|
1024
|
+
return (container.classification.kind === "database" &&
|
|
1025
|
+
container.classification.system === match.system);
|
|
1026
|
+
}) ||
|
|
1027
|
+
serverContainers.find((container) => {
|
|
1028
|
+
return container.classification.kind === "database";
|
|
1029
|
+
}) ||
|
|
1030
|
+
serverContainers.find((container) => {
|
|
1031
|
+
return container.classification.kind === "unknown";
|
|
1032
|
+
}) ||
|
|
1033
|
+
serverContainers[0] ||
|
|
1034
|
+
null;
|
|
1035
|
+
const workloadKind = match.clusterName
|
|
1036
|
+
? "Cluster"
|
|
1037
|
+
: ownerWorkload.workloadKind;
|
|
1038
|
+
const workloadName = match.clusterName || ownerWorkload.workloadName;
|
|
1039
|
+
const evidence = [match.evidence];
|
|
1040
|
+
if (databaseContainer === null || databaseContainer === void 0 ? void 0 : databaseContainer.image) {
|
|
1041
|
+
evidence.push(`image:${databaseContainer.image}`);
|
|
1042
|
+
}
|
|
1043
|
+
return {
|
|
1044
|
+
system: match.system,
|
|
1045
|
+
namespace,
|
|
1046
|
+
workloadKind,
|
|
1047
|
+
workloadName,
|
|
1048
|
+
ownerKind,
|
|
1049
|
+
ownerName,
|
|
1050
|
+
podName,
|
|
1051
|
+
containerName: (databaseContainer === null || databaseContainer === void 0 ? void 0 : databaseContainer.name) || "",
|
|
1052
|
+
image: (databaseContainer === null || databaseContainer === void 0 ? void 0 : databaseContainer.image) || null,
|
|
1053
|
+
version: parseImageVersion(databaseContainer === null || databaseContainer === void 0 ? void 0 : databaseContainer.image),
|
|
1054
|
+
ports: databaseContainer ? [...databaseContainer.ports] : [],
|
|
1055
|
+
operator: match.operator,
|
|
1056
|
+
role: match.role,
|
|
1057
|
+
serviceNames: uniqueNonEmpty([
|
|
1058
|
+
workloadName,
|
|
1059
|
+
...match.serviceNames,
|
|
1060
|
+
ownerKind && SERVICE_NAMED_OWNER_KINDS.has(ownerKind) && ownerName
|
|
1061
|
+
? ownerName
|
|
1062
|
+
: "",
|
|
1063
|
+
headlessServiceName || "",
|
|
1064
|
+
]),
|
|
1065
|
+
headlessServiceName,
|
|
1066
|
+
evidence,
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
// 2. Container images: the first database container that runs a server.
|
|
1070
|
+
const databaseContainer = containers.find((container) => {
|
|
1071
|
+
return (container.classification.kind === "database" && !container.nonServer);
|
|
1072
|
+
});
|
|
1073
|
+
if (!databaseContainer ||
|
|
1074
|
+
databaseContainer.classification.kind !== "database") {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
const system = databaseContainer.classification.system;
|
|
1078
|
+
const unknownNeighbours = containers.filter((container) => {
|
|
1079
|
+
return (container !== databaseContainer &&
|
|
1080
|
+
container.classification.kind === "unknown");
|
|
1081
|
+
}).length;
|
|
1082
|
+
if (unknownNeighbours > 0 &&
|
|
1083
|
+
!toleratesUnknownNeighbour({
|
|
1084
|
+
ownerKind,
|
|
1085
|
+
databaseContainer,
|
|
1086
|
+
system,
|
|
1087
|
+
unknownNeighbours,
|
|
1088
|
+
})) {
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
/*
|
|
1092
|
+
* An owner-less pod is what `kubectl run` makes — usually a one-off client
|
|
1093
|
+
* session in a database image. A server run that way declares its port.
|
|
1094
|
+
*/
|
|
1095
|
+
if (!owner && databaseContainer.ports.length === 0) {
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
return {
|
|
1099
|
+
system,
|
|
1100
|
+
namespace,
|
|
1101
|
+
workloadKind: ownerWorkload.workloadKind,
|
|
1102
|
+
workloadName: ownerWorkload.workloadName,
|
|
1103
|
+
ownerKind,
|
|
1104
|
+
ownerName,
|
|
1105
|
+
podName,
|
|
1106
|
+
containerName: databaseContainer.name,
|
|
1107
|
+
image: databaseContainer.image,
|
|
1108
|
+
version: parseImageVersion(databaseContainer.image),
|
|
1109
|
+
ports: [...databaseContainer.ports],
|
|
1110
|
+
operator: null,
|
|
1111
|
+
role: null,
|
|
1112
|
+
serviceNames: uniqueNonEmpty([
|
|
1113
|
+
ownerWorkload.workloadName,
|
|
1114
|
+
headlessServiceName || "",
|
|
1115
|
+
]),
|
|
1116
|
+
headlessServiceName,
|
|
1117
|
+
evidence: [`image:${databaseContainer.image}`],
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* The Service a CloudNativePG pooler pod backs (the Pooler's name), for
|
|
1122
|
+
* attachPoolerServices; null for any other pod. Pooler pods are never
|
|
1123
|
+
* members (classifyKubernetesPod is null for them), but applications
|
|
1124
|
+
* connect through their Service.
|
|
1125
|
+
*/
|
|
1126
|
+
export function classifyKubernetesPoolerPod(pod) {
|
|
1127
|
+
if (!pod || typeof pod !== "object") {
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
if (typeof pod.phase === "string" &&
|
|
1131
|
+
SKIPPED_POD_PHASES.has(pod.phase.trim().toLowerCase())) {
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
const labels = pod.labels && typeof pod.labels === "object" ? pod.labels : {};
|
|
1135
|
+
const cluster = labelValue(labels, "cnpg.io/cluster");
|
|
1136
|
+
const pooler = labelValue(labels, "cnpg.io/poolerName");
|
|
1137
|
+
if (!cluster || !pooler) {
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
return {
|
|
1141
|
+
system: "postgresql",
|
|
1142
|
+
namespace: typeof pod.namespaceKey === "string" ? pod.namespaceKey.trim() : "",
|
|
1143
|
+
clusterName: cluster,
|
|
1144
|
+
serviceName: pooler,
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
function compareStrings(a, b) {
|
|
1148
|
+
if (a < b) {
|
|
1149
|
+
return -1;
|
|
1150
|
+
}
|
|
1151
|
+
return a > b ? 1 : 0;
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* The engine of a database whose members report several engines of one
|
|
1155
|
+
* family: the most specific of them (getMoreSpecificDatabaseSystem). A
|
|
1156
|
+
* StatefulSet mid-rollout from a redis image to a valkey image is ONE
|
|
1157
|
+
* Valkey database, as its family-keyed identifier
|
|
1158
|
+
* (buildWorkloadDatabaseServerIdentifier) already says. The engines are
|
|
1159
|
+
* folded most-reported first, then by name, so member order never changes
|
|
1160
|
+
* the answer; of two forks that do not refine each other (valkey and keydb)
|
|
1161
|
+
* the more reported one stays, the first by name on a tie. Null when no
|
|
1162
|
+
* engine is given.
|
|
1163
|
+
*/
|
|
1164
|
+
export function pickMostSpecificDatabaseSystem(systems) {
|
|
1165
|
+
const counts = new Map();
|
|
1166
|
+
for (const system of Array.isArray(systems) ? systems : []) {
|
|
1167
|
+
if (typeof system === "string" && system) {
|
|
1168
|
+
counts.set(system, (counts.get(system) || 0) + 1);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
const ordered = Array.from(counts.keys()).sort((a, b) => {
|
|
1172
|
+
return ((counts.get(b) || 0) - (counts.get(a) || 0) || compareStrings(a, b));
|
|
1173
|
+
});
|
|
1174
|
+
let result = null;
|
|
1175
|
+
for (const system of ordered) {
|
|
1176
|
+
result = getMoreSpecificDatabaseSystem(result, system);
|
|
1177
|
+
}
|
|
1178
|
+
return result;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Fold per-pod candidates into one entry per database workload
|
|
1182
|
+
* (engine FAMILY + namespace + workloadKind + workloadName - the parts of
|
|
1183
|
+
* its family-keyed identifier). The group's engine is the most specific its
|
|
1184
|
+
* members report (pickMostSpecificDatabaseSystem). Member pods, ports,
|
|
1185
|
+
* Service names and owner workloads are unioned; image and version come
|
|
1186
|
+
* from the primary member running that engine when one is known, else from
|
|
1187
|
+
* the first such pod by name. Deterministic: groups and pod names are
|
|
1188
|
+
* sorted.
|
|
1189
|
+
*/
|
|
1190
|
+
export function groupKubernetesDatabaseCandidates(candidates) {
|
|
1191
|
+
var _a, _b;
|
|
1192
|
+
if (!Array.isArray(candidates)) {
|
|
1193
|
+
return [];
|
|
1194
|
+
}
|
|
1195
|
+
const byKey = new Map();
|
|
1196
|
+
for (const candidate of candidates) {
|
|
1197
|
+
if (!candidate || !candidate.system || !candidate.workloadName) {
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
const key = [
|
|
1201
|
+
getDatabaseSystemFamily(candidate.system) || candidate.system,
|
|
1202
|
+
candidate.namespace,
|
|
1203
|
+
candidate.workloadKind,
|
|
1204
|
+
candidate.workloadName,
|
|
1205
|
+
].join("\u0000");
|
|
1206
|
+
const members = byKey.get(key) || [];
|
|
1207
|
+
members.push(candidate);
|
|
1208
|
+
byKey.set(key, members);
|
|
1209
|
+
}
|
|
1210
|
+
const groups = [];
|
|
1211
|
+
for (const members of byKey.values()) {
|
|
1212
|
+
const sorted = [...members].sort((a, b) => {
|
|
1213
|
+
return compareStrings(a.podName, b.podName);
|
|
1214
|
+
});
|
|
1215
|
+
const first = sorted[0];
|
|
1216
|
+
const system = pickMostSpecificDatabaseSystem(sorted.map((member) => {
|
|
1217
|
+
return member.system;
|
|
1218
|
+
})) || first.system;
|
|
1219
|
+
// Image and version describe the engine the group is shown as.
|
|
1220
|
+
const runningSystem = sorted.filter((member) => {
|
|
1221
|
+
return member.system === system;
|
|
1222
|
+
});
|
|
1223
|
+
const pool = runningSystem.length > 0 ? runningSystem : sorted;
|
|
1224
|
+
const representative = pool.find((member) => {
|
|
1225
|
+
return member.role === "primary";
|
|
1226
|
+
}) || pool[0];
|
|
1227
|
+
const podNames = uniqueNonEmpty(sorted.map((member) => {
|
|
1228
|
+
return member.podName;
|
|
1229
|
+
}));
|
|
1230
|
+
const ports = [];
|
|
1231
|
+
for (const member of sorted) {
|
|
1232
|
+
for (const port of member.ports || []) {
|
|
1233
|
+
if (!ports.includes(port)) {
|
|
1234
|
+
ports.push(port);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
ports.sort((a, b) => {
|
|
1239
|
+
return a - b;
|
|
1240
|
+
});
|
|
1241
|
+
const serviceNames = uniqueNonEmpty(sorted.flatMap((member) => {
|
|
1242
|
+
return member.serviceNames || [];
|
|
1243
|
+
}));
|
|
1244
|
+
const podServiceNames = [];
|
|
1245
|
+
for (const member of sorted) {
|
|
1246
|
+
if (member.headlessServiceName &&
|
|
1247
|
+
!podServiceNames.some((entry) => {
|
|
1248
|
+
return entry.podName === member.podName;
|
|
1249
|
+
})) {
|
|
1250
|
+
podServiceNames.push({
|
|
1251
|
+
podName: member.podName,
|
|
1252
|
+
serviceName: member.headlessServiceName,
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
const ownerWorkloads = [];
|
|
1257
|
+
for (const member of sorted) {
|
|
1258
|
+
if (member.ownerKind &&
|
|
1259
|
+
member.ownerName &&
|
|
1260
|
+
!ownerWorkloads.some((entry) => {
|
|
1261
|
+
return (entry.kind === member.ownerKind && entry.name === member.ownerName);
|
|
1262
|
+
})) {
|
|
1263
|
+
ownerWorkloads.push({ kind: member.ownerKind, name: member.ownerName });
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
ownerWorkloads.sort((a, b) => {
|
|
1267
|
+
return compareStrings(a.kind, b.kind) || compareStrings(a.name, b.name);
|
|
1268
|
+
});
|
|
1269
|
+
groups.push({
|
|
1270
|
+
system,
|
|
1271
|
+
namespace: first.namespace,
|
|
1272
|
+
workloadKind: first.workloadKind,
|
|
1273
|
+
workloadName: first.workloadName,
|
|
1274
|
+
podNames,
|
|
1275
|
+
image: representative.image,
|
|
1276
|
+
version: representative.version,
|
|
1277
|
+
ports,
|
|
1278
|
+
operator: ((_a = sorted.find((member) => {
|
|
1279
|
+
return Boolean(member.operator);
|
|
1280
|
+
})) === null || _a === void 0 ? void 0 : _a.operator) || null,
|
|
1281
|
+
serviceNames,
|
|
1282
|
+
headlessServiceName: ((_b = sorted.find((member) => {
|
|
1283
|
+
return Boolean(member.headlessServiceName);
|
|
1284
|
+
})) === null || _b === void 0 ? void 0 : _b.headlessServiceName) || null,
|
|
1285
|
+
podServiceNames,
|
|
1286
|
+
ownerWorkloads,
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
return groups.sort((a, b) => {
|
|
1290
|
+
return (compareStrings(a.namespace, b.namespace) ||
|
|
1291
|
+
compareStrings(a.workloadKind, b.workloadKind) ||
|
|
1292
|
+
compareStrings(a.workloadName, b.workloadName) ||
|
|
1293
|
+
compareStrings(a.system, b.system));
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Add each pooler's Service to the operator cluster it pools (same engine
|
|
1298
|
+
* family, namespace and cluster name). Returns new group objects; a pooler
|
|
1299
|
+
* whose cluster has no group this run is dropped.
|
|
1300
|
+
*/
|
|
1301
|
+
export function attachPoolerServices(groups, poolers) {
|
|
1302
|
+
if (!Array.isArray(groups)) {
|
|
1303
|
+
return [];
|
|
1304
|
+
}
|
|
1305
|
+
const list = Array.isArray(poolers)
|
|
1306
|
+
? poolers
|
|
1307
|
+
: [];
|
|
1308
|
+
return groups.map((group) => {
|
|
1309
|
+
if (group.workloadKind !== "Cluster") {
|
|
1310
|
+
return group;
|
|
1311
|
+
}
|
|
1312
|
+
const extra = list
|
|
1313
|
+
.filter((pooler) => {
|
|
1314
|
+
return (Boolean(pooler) &&
|
|
1315
|
+
isSameDatabaseFamily(pooler.system, group.system) &&
|
|
1316
|
+
pooler.namespace === group.namespace &&
|
|
1317
|
+
pooler.clusterName === group.workloadName);
|
|
1318
|
+
})
|
|
1319
|
+
.map((pooler) => {
|
|
1320
|
+
return pooler.serviceName;
|
|
1321
|
+
})
|
|
1322
|
+
.sort(compareStrings);
|
|
1323
|
+
if (extra.length === 0) {
|
|
1324
|
+
return group;
|
|
1325
|
+
}
|
|
1326
|
+
return Object.assign(Object.assign({}, group), { serviceNames: uniqueNonEmpty([...group.serviceNames, ...extra]) });
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
// ---- Docker / Podman containers -------------------------------------------
|
|
1330
|
+
/*
|
|
1331
|
+
* Every container label classifyContainer reads: the Swarm / Compose
|
|
1332
|
+
* service that groups replicas, and the marks of Testcontainers runs,
|
|
1333
|
+
* `docker compose run` one-offs and kubelet-managed containers. The Docker
|
|
1334
|
+
* and Podman agents copy exactly these onto their docker_stats metrics as
|
|
1335
|
+
* resource attributes (`container_labels_to_metric_labels`, same names),
|
|
1336
|
+
* and the metrics ingest stores them as the container row's labels — the
|
|
1337
|
+
* one inventory path every agent version runs.
|
|
1338
|
+
*/
|
|
1339
|
+
export const CONTAINER_CLASSIFIER_LABEL_KEYS = [
|
|
1340
|
+
"com.docker.compose.oneoff",
|
|
1341
|
+
"com.docker.compose.project",
|
|
1342
|
+
"com.docker.compose.service",
|
|
1343
|
+
"com.docker.swarm.service.name",
|
|
1344
|
+
"io.kubernetes.pod.name",
|
|
1345
|
+
"io.podman.compose.project",
|
|
1346
|
+
"io.podman.compose.service",
|
|
1347
|
+
"org.testcontainers",
|
|
1348
|
+
"org.testcontainers.sessionId",
|
|
1349
|
+
];
|
|
1350
|
+
// A Swarm task id: 25 lowercase alphanumerics (`stack_db.1.<task id>`).
|
|
1351
|
+
const SWARM_TASK_ID_REGEX = /^[a-z0-9]{25}$/;
|
|
1352
|
+
const SWARM_SLOT_REGEX = /^(?:\d+|[a-z0-9]{25})$/;
|
|
1353
|
+
/*
|
|
1354
|
+
* Containers that are never a long-lived database server of their own:
|
|
1355
|
+
* Testcontainers runs (CI), `docker compose run` one-offs, and containers
|
|
1356
|
+
* the kubelet manages on a Docker node (the Kubernetes path discovers those
|
|
1357
|
+
* as pods).
|
|
1358
|
+
*/
|
|
1359
|
+
function isEphemeralOrManagedContainer(labels) {
|
|
1360
|
+
for (const key of Object.keys(labels)) {
|
|
1361
|
+
if (key === "org.testcontainers" || key.startsWith("org.testcontainers.")) {
|
|
1362
|
+
return true;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
const oneOff = labelValue(labels, "com.docker.compose.oneoff");
|
|
1366
|
+
if (oneOff && oneOff.toLowerCase() === "true") {
|
|
1367
|
+
return true;
|
|
1368
|
+
}
|
|
1369
|
+
return (hasLabel(labels, "io.kubernetes.pod.name") ||
|
|
1370
|
+
hasLabel(labels, "io.kubernetes.container.name"));
|
|
1371
|
+
}
|
|
1372
|
+
/*
|
|
1373
|
+
* The Swarm service a task container belongs to, from its name alone
|
|
1374
|
+
* (`stack_db.1.<25-char task id>`, `stack_db.<node id>.<task id>`) — for
|
|
1375
|
+
* rows whose labels have not been inventoried.
|
|
1376
|
+
*/
|
|
1377
|
+
function swarmServiceFromTaskName(containerName) {
|
|
1378
|
+
const parts = containerName.split(".");
|
|
1379
|
+
if (parts.length < 3) {
|
|
1380
|
+
return null;
|
|
1381
|
+
}
|
|
1382
|
+
const taskId = parts[parts.length - 1];
|
|
1383
|
+
const slot = parts[parts.length - 2];
|
|
1384
|
+
if (!SWARM_TASK_ID_REGEX.test(taskId) || !SWARM_SLOT_REGEX.test(slot)) {
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
const service = parts.slice(0, -2).join(".");
|
|
1388
|
+
return service || null;
|
|
1389
|
+
}
|
|
1390
|
+
/*
|
|
1391
|
+
* What groups a container's replicas: its Swarm service, its Compose
|
|
1392
|
+
* project + service (Docker Compose and podman-compose both stamp
|
|
1393
|
+
* `com.docker.compose.*`; podman-compose also `io.podman.compose.*`), else
|
|
1394
|
+
* the container name exactly as it is — never a guessed-away suffix, since
|
|
1395
|
+
* `redis-6379` and `redis-6380` or `pg-14` and `pg-16` are different
|
|
1396
|
+
* servers. The kind says which of the three it is.
|
|
1397
|
+
*/
|
|
1398
|
+
function containerWorkload(containerName, labels) {
|
|
1399
|
+
const swarmService = labelValue(labels, "com.docker.swarm.service.name") ||
|
|
1400
|
+
swarmServiceFromTaskName(containerName);
|
|
1401
|
+
if (swarmService) {
|
|
1402
|
+
return {
|
|
1403
|
+
workloadKind: ContainerWorkloadKind.SwarmService,
|
|
1404
|
+
workloadName: swarmService,
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
const project = labelValue(labels, "com.docker.compose.project") ||
|
|
1408
|
+
labelValue(labels, "io.podman.compose.project");
|
|
1409
|
+
const service = labelValue(labels, "com.docker.compose.service") ||
|
|
1410
|
+
labelValue(labels, "io.podman.compose.service");
|
|
1411
|
+
if (project && service) {
|
|
1412
|
+
return {
|
|
1413
|
+
workloadKind: ContainerWorkloadKind.ComposeService,
|
|
1414
|
+
workloadName: `${project}-${service}`,
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
return {
|
|
1418
|
+
workloadKind: ContainerWorkloadKind.Container,
|
|
1419
|
+
workloadName: containerName,
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* The database a Docker / Podman container runs, or null. The workload is
|
|
1424
|
+
* the Swarm service or Compose service the container belongs to (so its
|
|
1425
|
+
* replicas group together), else the container's own name, and its kind
|
|
1426
|
+
* says which. Testcontainers runs, `docker compose run` one-offs and
|
|
1427
|
+
* kubelet-managed containers are null.
|
|
1428
|
+
*/
|
|
1429
|
+
export function classifyContainer(container) {
|
|
1430
|
+
if (!container || typeof container.name !== "string") {
|
|
1431
|
+
return null;
|
|
1432
|
+
}
|
|
1433
|
+
// Docker reports names as "/name" (and occasionally "/a,/b").
|
|
1434
|
+
const containerName = (container.name.split(",")[0] || "")
|
|
1435
|
+
.trim()
|
|
1436
|
+
.replace(/^\/+/, "");
|
|
1437
|
+
if (!containerName) {
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1440
|
+
const labels = container.labels &&
|
|
1441
|
+
typeof container.labels === "object" &&
|
|
1442
|
+
!Array.isArray(container.labels)
|
|
1443
|
+
? container.labels
|
|
1444
|
+
: {};
|
|
1445
|
+
if (isEphemeralOrManagedContainer(labels)) {
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
const classification = classifyImage(container.imageName);
|
|
1449
|
+
if (classification.kind !== "database") {
|
|
1450
|
+
return null;
|
|
1451
|
+
}
|
|
1452
|
+
const descriptor = getDatabaseSystemDescriptor(classification.system);
|
|
1453
|
+
const workload = containerWorkload(containerName, labels);
|
|
1454
|
+
return {
|
|
1455
|
+
system: descriptor ? descriptor.system : classification.system,
|
|
1456
|
+
workloadKind: workload.workloadKind,
|
|
1457
|
+
workloadName: workload.workloadName,
|
|
1458
|
+
containerName,
|
|
1459
|
+
version: parseImageVersion(container.imageName),
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
//# sourceMappingURL=DatabaseContainerClassifier.js.map
|