@skyhook-io/k8s-ui 1.16.2 → 1.17.0
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/package.json +3 -3
- package/src/components/applications/ApplicationDetail.test.tsx +15 -0
- package/src/components/applications/ApplicationDetail.tsx +17 -15
- package/src/components/checks/types.ts +2 -13
- package/src/components/cnpg/CNPGBackupSummary.tsx +223 -0
- package/src/components/cnpg/CNPGClusterSummary.tsx +222 -0
- package/src/components/cnpg/CNPGDeclarativeSummary.tsx +264 -0
- package/src/components/cnpg/CNPGImageCatalogSummary.tsx +80 -0
- package/src/components/cnpg/CNPGObjectStoreSummary.tsx +176 -0
- package/src/components/cnpg/CNPGObjectSummary.test.tsx +236 -0
- package/src/components/cnpg/CNPGPoolerSummary.tsx +70 -0
- package/src/components/cnpg/CNPGSharedSummary.tsx +88 -0
- package/src/components/cnpg/index.ts +14 -0
- package/src/components/cnpg/primitives.tsx +134 -0
- package/src/components/cnpg/relations.test.ts +304 -0
- package/src/components/cnpg/relations.ts +417 -0
- package/src/components/cnpg/workspace.test.ts +285 -0
- package/src/components/cnpg/workspace.ts +700 -0
- package/src/components/gitops/GitOpsTableView.tsx +9 -24
- package/src/components/gitops/detail-helpers.ts +1 -1
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +5 -12
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +2 -5
- package/src/components/issues/IssuesView.tsx +10 -1
- package/src/components/issues/issue-timing.ts +2 -2
- package/src/components/issues/issues.test.ts +1 -1
- package/src/components/issues/types.ts +24 -0
- package/src/components/logs/WorkloadLogsViewer.notice.test.tsx +59 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +17 -3
- package/src/components/logs/useLogBuffer.test.ts +17 -0
- package/src/components/logs/useLogStream.ts +11 -0
- package/src/components/resources/ResourcesSidebar.test.tsx +76 -0
- package/src/components/resources/ResourcesSidebar.tsx +183 -11
- package/src/components/resources/ResourcesView.tsx +12 -9
- package/src/components/resources/index.ts +1 -1
- package/src/components/resources/renderers/AnalysisRunRenderer.tsx +3 -3
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +6 -5
- package/src/components/resources/renderers/CAPIClusterRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +2 -2
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +2 -1
- package/src/components/resources/renderers/CNPGObjectStoreRenderer.test.tsx +25 -0
- package/src/components/resources/renderers/CNPGObjectStoreRenderer.tsx +16 -7
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +2 -2
- package/src/components/resources/renderers/CNPGShared.tsx +2 -2
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/CronJobRenderer.tsx +6 -4
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +3 -2
- package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +24 -0
- package/src/components/resources/renderers/EndpointSliceRenderer.tsx +1 -1
- package/src/components/resources/renderers/EventRenderer.tsx +3 -3
- package/src/components/resources/renderers/ExperimentRenderer.tsx +2 -2
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +2 -2
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +4 -4
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +11 -24
- package/src/components/resources/renderers/GatewayRouteReferences.test.tsx +63 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/HPARenderer.tsx +2 -2
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +12 -25
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/IngressClassReferences.test.tsx +52 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +10 -3
- package/src/components/resources/renderers/IngressRenderer.tsx +6 -2
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +2 -14
- package/src/components/resources/renderers/KedaCredentials.test.tsx +85 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +2 -2
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +6 -3
- package/src/components/resources/renderers/KustomizationRenderer.tsx +2 -2
- package/src/components/resources/renderers/KyvernoGlobalContextRenderer.tsx +3 -2
- package/src/components/resources/renderers/KyvernoQueueRenderers.tsx +4 -3
- package/src/components/resources/renderers/LeaseRenderer.tsx +3 -15
- package/src/components/resources/renderers/NamespaceRenderer.test.ts +69 -2
- package/src/components/resources/renderers/NamespaceRenderer.tsx +6 -1
- package/src/components/resources/renderers/NodeRenderer.test.tsx +81 -1
- package/src/components/resources/renderers/NodeRenderer.tsx +56 -15
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/PodClassReferences.test.tsx +42 -0
- package/src/components/resources/renderers/PodNomination.test.tsx +57 -0
- package/src/components/resources/renderers/PodRenderer.tsx +21 -7
- package/src/components/resources/renderers/RolloutRenderer.test.tsx +1 -1
- package/src/components/resources/renderers/RolloutRenderer.tsx +3 -2
- package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/SecretRenderer.test.tsx +88 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +34 -5
- package/src/components/resources/renderers/ServiceRenderer.test.tsx +43 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +11 -3
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +8 -21
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +5 -7
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +3 -3
- package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.tsx +2 -2
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +3 -3
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +2 -2
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/WorkflowRenderer.tsx +4 -3
- package/src/components/resources/renderers/kyverno-modern-cells.tsx +1 -1
- package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +2 -2
- package/src/components/resources/resource-utils-cnpg-objectstore.test.ts +1 -1
- package/src/components/resources/resource-utils-cnpg.ts +3 -7
- package/src/components/resources/resource-utils-keda.ts +1 -7
- package/src/components/resources/resource-utils-kyverno.ts +1 -7
- package/src/components/resources/resource-utils-velero.ts +0 -12
- package/src/components/resources/resource-utils.ts +17 -33
- package/src/components/resources/status-overclaim.test.ts +1 -1
- package/src/components/shared/DrainPlanDialog.tsx +1 -0
- package/src/components/shared/GitOpsWriteWarning.test.tsx +86 -0
- package/src/components/shared/GitOpsWriteWarning.tsx +135 -0
- package/src/components/shared/ResourceActionsBar.tsx +9 -20
- package/src/components/shared/ResourceRendererDispatch.tsx +1 -1
- package/src/components/shared/SetImageDialog.test.ts +75 -6
- package/src/components/shared/SetImageDialog.tsx +92 -84
- package/src/components/shared/index.ts +2 -1
- package/src/components/timeline/TimelineList.tsx +187 -38
- package/src/components/timeline/TimelineListFold.test.tsx +56 -0
- package/src/components/timeline/TimelineSwimlanes.tsx +2 -10
- package/src/components/timeline/TimelineToolbarState.test.tsx +98 -0
- package/src/components/timeline/index.ts +1 -0
- package/src/components/timeline/timeline-filters.ts +7 -0
- package/src/components/topology/TopologyGraph.tsx +5 -3
- package/src/components/ui/ScheduleValue.test.tsx +5 -0
- package/src/components/ui/ScheduleValue.tsx +3 -2
- package/src/components/ui/drawer-components.test.tsx +66 -1
- package/src/components/ui/drawer-components.tsx +43 -29
- package/src/components/workload/WorkloadView.deleted.test.tsx +58 -0
- package/src/components/workload/WorkloadView.deletedDialog.test.tsx +45 -0
- package/src/components/workload/WorkloadView.secretSave.test.tsx +62 -0
- package/src/components/workload/WorkloadView.tsx +216 -48
- package/src/components/workload/index.ts +1 -0
- package/src/components/workload/workload-logs-availability.test.ts +6 -0
- package/src/index.ts +3 -0
- package/src/types/capacity.ts +10 -0
- package/src/types/core.ts +32 -1
- package/src/utils/format.test.ts +40 -2
- package/src/utils/format.ts +5 -1
- package/src/utils/gateway-references.test.ts +17 -0
- package/src/utils/gateway-references.ts +29 -0
- package/src/utils/gitops-write-guard.test.ts +401 -0
- package/src/utils/gitops-write-guard.ts +509 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/ingress-class-references.ts +22 -0
- package/src/utils/log-level.ts +14 -0
- package/src/utils/navigation.test.ts +13 -0
- package/src/utils/navigation.ts +5 -0
- package/src/utils/node-lifecycle.test.ts +36 -0
- package/src/utils/node-lifecycle.ts +77 -0
- package/src/utils/resource-hierarchy.test.ts +32 -0
- package/src/utils/resource-hierarchy.ts +28 -2
- package/src/utils/resource-refs.test.ts +23 -0
- package/src/utils/resource-refs.ts +30 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
appliedFact,
|
|
4
|
+
backupDestination,
|
|
5
|
+
backupsForScheduledBackup,
|
|
6
|
+
clustersUsingCatalog,
|
|
7
|
+
databaseForDeclaration,
|
|
8
|
+
gitopsSourceOf,
|
|
9
|
+
inferredObjectStoreHealth,
|
|
10
|
+
isBackupFromSchedule,
|
|
11
|
+
issuesForObject,
|
|
12
|
+
missingManagedRole,
|
|
13
|
+
objectStoreForBackup,
|
|
14
|
+
relationUnavailable,
|
|
15
|
+
replicationForDatabase,
|
|
16
|
+
scheduledBackupOf,
|
|
17
|
+
usersOfObjectStore,
|
|
18
|
+
} from './relations'
|
|
19
|
+
import { CNPG_WORKSPACE_KEYS, type CNPGWorkspaceIssue, type CNPGWorkspaceResponse } from './workspace'
|
|
20
|
+
|
|
21
|
+
const PG = 'postgresql.cnpg.io/v1'
|
|
22
|
+
const BARMAN = 'barmancloud.cnpg.io/v1'
|
|
23
|
+
const VELERO = 'velero.io/v1'
|
|
24
|
+
const PLUGIN = 'barman-cloud.cloudnative-pg.io'
|
|
25
|
+
|
|
26
|
+
function cluster(name: string, ns = 'pg', spec: any = {}, status: any = {}): any {
|
|
27
|
+
return { apiVersion: PG, kind: 'Cluster', metadata: { name, namespace: ns }, spec, status }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function pluginCluster(name: string, store: string, serverName?: string, conditions?: any[]): any {
|
|
31
|
+
return cluster(
|
|
32
|
+
name,
|
|
33
|
+
'pg',
|
|
34
|
+
{ plugins: [{ name: PLUGIN, isWALArchiver: true, parameters: { barmanObjectName: store, ...(serverName ? { serverName } : {}) } }] },
|
|
35
|
+
conditions ? { conditions } : {},
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function backup(name: string, extra: any = {}): any {
|
|
40
|
+
return {
|
|
41
|
+
apiVersion: PG,
|
|
42
|
+
kind: 'Backup',
|
|
43
|
+
metadata: { name, namespace: 'pg', ...(extra.metadata ?? {}) },
|
|
44
|
+
spec: { cluster: { name: 'main' }, ...(extra.spec ?? {}) },
|
|
45
|
+
status: extra.status ?? {},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('scheduledBackupOf / backupsForScheduledBackup', () => {
|
|
50
|
+
const sched = { apiVersion: PG, kind: 'ScheduledBackup', metadata: { name: 'nightly', namespace: 'pg', uid: 'uid-new' } }
|
|
51
|
+
const ownedBy = (name: string, uid: string, extra: any = {}) =>
|
|
52
|
+
backup(name, {
|
|
53
|
+
...extra,
|
|
54
|
+
metadata: { ...(extra.metadata ?? {}), ownerReferences: [{ apiVersion: PG, kind: 'ScheduledBackup', name: 'nightly', uid }] },
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('reads the owner reference name', () => {
|
|
58
|
+
expect(scheduledBackupOf(ownedBy('b1', 'uid-new'))).toBe('nightly')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('falls back to the operator label when the owner is the Cluster or unset', () => {
|
|
62
|
+
const b = backup('b1', {
|
|
63
|
+
metadata: {
|
|
64
|
+
labels: { 'cnpg.io/scheduled-backup': 'nightly' },
|
|
65
|
+
ownerReferences: [{ apiVersion: PG, kind: 'Cluster', name: 'main' }],
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
expect(scheduledBackupOf(b)).toBe('nightly')
|
|
69
|
+
expect(isBackupFromSchedule(b, sched)).toBe(true)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('returns null for an on-demand backup', () => {
|
|
73
|
+
expect(scheduledBackupOf(backup('b1'))).toBeNull()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('ignores an owner reference from another API group', () => {
|
|
77
|
+
const b = backup('b1', { metadata: { ownerReferences: [{ apiVersion: 'example.com/v1', kind: 'ScheduledBackup', name: 'nightly' }] } })
|
|
78
|
+
expect(scheduledBackupOf(b)).toBeNull()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('does not attribute a recreated schedule the old schedule\'s Backups', () => {
|
|
82
|
+
const old = ownedBy('old', 'uid-old', { metadata: { labels: { 'cnpg.io/scheduled-backup': 'nightly' } } })
|
|
83
|
+
expect(isBackupFromSchedule(old, sched)).toBe(false)
|
|
84
|
+
expect(isBackupFromSchedule(ownedBy('cur', 'uid-new'), sched)).toBe(true)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('lists owned backups newest first and never a Velero Backup', () => {
|
|
88
|
+
const owned = (name: string, startedAt: string, apiVersion = PG) => ({
|
|
89
|
+
...backup(name, { metadata: { labels: { 'cnpg.io/scheduled-backup': 'nightly' } }, status: { startedAt } }),
|
|
90
|
+
apiVersion,
|
|
91
|
+
})
|
|
92
|
+
const list = [
|
|
93
|
+
owned('old', '2026-09-01T00:00:00Z'),
|
|
94
|
+
owned('new', '2026-09-02T00:00:00Z'),
|
|
95
|
+
owned('velero', '2026-09-03T00:00:00Z', VELERO),
|
|
96
|
+
ownedBy('stale', 'uid-old', { status: { startedAt: '2026-09-04T00:00:00Z' } }),
|
|
97
|
+
backup('other'),
|
|
98
|
+
{ ...owned('elsewhere', '2026-09-03T00:00:00Z'), metadata: { name: 'elsewhere', namespace: 'x', labels: { 'cnpg.io/scheduled-backup': 'nightly' } } },
|
|
99
|
+
]
|
|
100
|
+
expect(backupsForScheduledBackup(sched, list).map((b) => b.metadata.name)).toEqual(['new', 'old'])
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('objectStoreForBackup / backupDestination', () => {
|
|
105
|
+
const clusters = [pluginCluster('main', 'store-a')]
|
|
106
|
+
|
|
107
|
+
it('prefers the store the backup recorded', () => {
|
|
108
|
+
const b = backup('b', { spec: { method: 'plugin', pluginConfiguration: { name: PLUGIN, parameters: { barmanObjectName: 'store-b' } } } })
|
|
109
|
+
expect(objectStoreForBackup(b, clusters)).toEqual({ name: 'store-b', inferred: false })
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it("marks a store taken from the Cluster's current plugin as inferred", () => {
|
|
113
|
+
const b = backup('b', { spec: { method: 'plugin', pluginConfiguration: { name: PLUGIN } } })
|
|
114
|
+
expect(objectStoreForBackup(b, clusters)).toEqual({ name: 'store-a', inferred: true })
|
|
115
|
+
expect(backupDestination(b, clusters)).toEqual({ type: 'objectStore', name: 'store-a', inferred: true })
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('checks plugin identity before reading barmanObjectName', () => {
|
|
119
|
+
const other = backup('b', { spec: { method: 'plugin', pluginConfiguration: { name: 'other.example.com', parameters: { barmanObjectName: 'store-b' } } } })
|
|
120
|
+
expect(objectStoreForBackup(other, clusters)).toBeNull()
|
|
121
|
+
const unnamed = backup('b', { spec: { method: 'plugin', pluginConfiguration: { parameters: { barmanObjectName: 'store-b' } } } })
|
|
122
|
+
expect(objectStoreForBackup(unnamed, clusters)).toBeNull()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('reports in-tree paths and volume snapshots', () => {
|
|
126
|
+
expect(backupDestination(backup('b', { status: { method: 'barmanObjectStore', destinationPath: 's3://x' } }), clusters)).toEqual({ type: 'path', path: 's3://x' })
|
|
127
|
+
expect(backupDestination(backup('b', { spec: { method: 'volumeSnapshot' } }), clusters)).toEqual({ type: 'volumeSnapshot' })
|
|
128
|
+
expect(backupDestination(backup('b'), clusters)).toEqual({ type: 'unknown' })
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe('ObjectStore users and inferred health', () => {
|
|
133
|
+
const store = {
|
|
134
|
+
apiVersion: BARMAN,
|
|
135
|
+
kind: 'ObjectStore',
|
|
136
|
+
metadata: { name: 'store', namespace: 'pg' },
|
|
137
|
+
status: {
|
|
138
|
+
serverRecoveryWindow: {
|
|
139
|
+
'srv-a': { firstRecoverabilityPoint: '2026-09-01T00:00:00Z', lastSuccessfulBackupTime: '2026-09-02T00:00:00Z' },
|
|
140
|
+
b: { lastSuccessfulBackupTime: '2026-09-02T00:00:00Z', lastFailedBackupTime: '2026-09-03T00:00:00Z' },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
const ok = [{ type: 'ContinuousArchiving', status: 'True' }]
|
|
145
|
+
|
|
146
|
+
it('finds users in the same namespace, keyed by serverName', () => {
|
|
147
|
+
const clusters = [
|
|
148
|
+
pluginCluster('a', 'store', 'srv-a', ok),
|
|
149
|
+
pluginCluster('b', 'store'),
|
|
150
|
+
pluginCluster('c', 'other'),
|
|
151
|
+
{ ...pluginCluster('d', 'store'), metadata: { name: 'd', namespace: 'elsewhere' } },
|
|
152
|
+
{ ...pluginCluster('e', 'store'), apiVersion: 'cluster.x-k8s.io/v1beta1' },
|
|
153
|
+
]
|
|
154
|
+
const users = usersOfObjectStore(store, clusters)
|
|
155
|
+
expect(users.map((u) => [u.cluster.metadata.name, u.serverName])).toEqual([
|
|
156
|
+
['a', 'srv-a'],
|
|
157
|
+
['b', 'b'],
|
|
158
|
+
])
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('reports failing when any user has a failure newer than its last success', () => {
|
|
162
|
+
const users = usersOfObjectStore(store, [pluginCluster('a', 'store', 'srv-a', ok), pluginCluster('b', 'store', undefined, ok)])
|
|
163
|
+
const h = inferredObjectStoreHealth(store, users)
|
|
164
|
+
expect(h.summary.tone).toBe('unhealthy')
|
|
165
|
+
expect(h.summary.text).toBe('Uploads failing for 1 of 2 clusters')
|
|
166
|
+
expect(h.summary.at).toBe('2026-09-03T00:00:00Z')
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('reports failing from a False ContinuousArchiving condition', () => {
|
|
170
|
+
const users = usersOfObjectStore(store, [pluginCluster('a', 'store', 'srv-a', [{ type: 'ContinuousArchiving', status: 'False', message: 'boom' }])])
|
|
171
|
+
expect(inferredObjectStoreHealth(store, users).summary).toMatchObject({ text: 'Uploads failing', tone: 'unhealthy' })
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('is healthy only when every user reports archiving', () => {
|
|
175
|
+
const users = usersOfObjectStore(store, [pluginCluster('a', 'store', 'srv-a', ok)])
|
|
176
|
+
expect(inferredObjectStoreHealth(store, users).summary.tone).toBe('healthy')
|
|
177
|
+
const unreported = usersOfObjectStore(store, [pluginCluster('a', 'store', 'srv-a')])
|
|
178
|
+
expect(inferredObjectStoreHealth(store, unreported).summary).toMatchObject({ text: 'No failures reported', tone: 'unknown' })
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('says so when nothing uses the store', () => {
|
|
182
|
+
expect(inferredObjectStoreHealth(store, []).summary).toMatchObject({ text: 'No visible cluster uses this store', tone: 'unknown' })
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe('clustersUsingCatalog', () => {
|
|
187
|
+
const catalog = { apiVersion: PG, kind: 'ImageCatalog', metadata: { name: 'pg', namespace: 'pg' } }
|
|
188
|
+
const clusterCatalog = { apiVersion: PG, kind: 'ClusterImageCatalog', metadata: { name: 'pg' } }
|
|
189
|
+
|
|
190
|
+
it('treats an omitted ref kind as ImageCatalog and stays in the namespace', () => {
|
|
191
|
+
const clusters = [
|
|
192
|
+
cluster('a', 'pg', { imageCatalogRef: { name: 'pg', major: 16 } }),
|
|
193
|
+
cluster('b', 'pg', { imageCatalogRef: { kind: 'ImageCatalog', name: 'pg', major: 17 } }),
|
|
194
|
+
cluster('c', 'other', { imageCatalogRef: { name: 'pg', major: 16 } }),
|
|
195
|
+
cluster('d', 'pg', { imageCatalogRef: { kind: 'ClusterImageCatalog', name: 'pg', major: 16 } }),
|
|
196
|
+
]
|
|
197
|
+
expect(clustersUsingCatalog(catalog, clusters).map((u) => [u.cluster.metadata.name, u.major])).toEqual([
|
|
198
|
+
['a', 16],
|
|
199
|
+
['b', 17],
|
|
200
|
+
])
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('matches ClusterImageCatalog refs from any namespace, never an omitted kind', () => {
|
|
204
|
+
const clusters = [
|
|
205
|
+
cluster('a', 'x', { imageCatalogRef: { kind: 'ClusterImageCatalog', name: 'pg', major: 16 } }),
|
|
206
|
+
cluster('b', 'y', { imageCatalogRef: { kind: 'ClusterImageCatalog', name: 'pg' } }),
|
|
207
|
+
cluster('c', 'pg', { imageCatalogRef: { name: 'pg', major: 16 } }),
|
|
208
|
+
]
|
|
209
|
+
expect(clustersUsingCatalog(clusterCatalog, clusters).map((u) => [u.cluster.metadata.name, u.major])).toEqual([
|
|
210
|
+
['a', 16],
|
|
211
|
+
['b', null],
|
|
212
|
+
])
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
describe('issuesForObject', () => {
|
|
217
|
+
const issue = (over: Partial<CNPGWorkspaceIssue>): CNPGWorkspaceIssue => ({
|
|
218
|
+
id: Math.random().toString(),
|
|
219
|
+
severity: 'warning',
|
|
220
|
+
kind: 'Backup',
|
|
221
|
+
group: 'postgresql.cnpg.io',
|
|
222
|
+
namespace: 'pg',
|
|
223
|
+
name: 'b1',
|
|
224
|
+
reason: 'CNPGBackupFailed',
|
|
225
|
+
...over,
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('matches kind, group, namespace and name, critical first', () => {
|
|
229
|
+
const issues = [
|
|
230
|
+
issue({ id: 'w' }),
|
|
231
|
+
issue({ id: 'c', severity: 'critical' }),
|
|
232
|
+
issue({ id: 'velero', group: 'velero.io' }),
|
|
233
|
+
issue({ id: 'ns', namespace: 'other' }),
|
|
234
|
+
issue({ id: 'name', name: 'b2' }),
|
|
235
|
+
issue({ id: 'kind', kind: 'ScheduledBackup' }),
|
|
236
|
+
]
|
|
237
|
+
const got = issuesForObject(issues, { kind: 'Backup', group: 'postgresql.cnpg.io', namespace: 'pg', name: 'b1' })
|
|
238
|
+
expect(got.map((i) => i.id)).toEqual(['c', 'w'])
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
describe('declarations', () => {
|
|
243
|
+
it('distinguishes pending from failed', () => {
|
|
244
|
+
expect(appliedFact({ status: {} }).tone).toBe('unknown')
|
|
245
|
+
expect(appliedFact({ status: { applied: false } }).text).toBe('Not applied')
|
|
246
|
+
expect(appliedFact({ status: { applied: true } }).text).toBe('Applied')
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('names a missing role only when it is absent from managed roles', () => {
|
|
250
|
+
const db = { status: { applied: false, message: 'role "app" does not exist' } }
|
|
251
|
+
expect(missingManagedRole(db, cluster('main', 'pg', { managed: { roles: [{ name: 'other' }] } }))).toBe('app')
|
|
252
|
+
expect(missingManagedRole(db, cluster('main', 'pg', { managed: { roles: [{ name: 'app' }] } }))).toBeNull()
|
|
253
|
+
expect(missingManagedRole(db, null)).toBeNull()
|
|
254
|
+
expect(missingManagedRole({ status: { message: 'connection refused' } }, cluster('main'))).toBeNull()
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('reads the GitOps owner labels', () => {
|
|
258
|
+
expect(gitopsSourceOf({ metadata: { labels: { 'argocd.argoproj.io/instance': 'app' } } })).toEqual({ tool: 'argocd', name: 'app' })
|
|
259
|
+
expect(gitopsSourceOf({ metadata: { labels: { 'kustomize.toolkit.fluxcd.io/name': 'k', 'kustomize.toolkit.fluxcd.io/namespace': 'flux' } } })).toEqual({
|
|
260
|
+
tool: 'flux',
|
|
261
|
+
name: 'k',
|
|
262
|
+
namespace: 'flux',
|
|
263
|
+
})
|
|
264
|
+
expect(gitopsSourceOf({ metadata: {} })).toBeNull()
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
const decl = (kind: string, name: string, clusterName: string, dbname: string, ns = 'pg') => ({
|
|
268
|
+
apiVersion: PG,
|
|
269
|
+
kind,
|
|
270
|
+
metadata: { name, namespace: ns },
|
|
271
|
+
spec: { cluster: { name: clusterName }, dbname },
|
|
272
|
+
})
|
|
273
|
+
const database = { apiVersion: PG, kind: 'Database', metadata: { name: 'app-db', namespace: 'pg' }, spec: { cluster: { name: 'main' }, name: 'app' } }
|
|
274
|
+
|
|
275
|
+
it('finds publications and subscriptions on the same cluster and database', () => {
|
|
276
|
+
const pubs = [decl('Publication', 'p1', 'main', 'app'), decl('Publication', 'p2', 'main', 'other'), decl('Publication', 'p3', 'other', 'app')]
|
|
277
|
+
const subs = [decl('Subscription', 's1', 'main', 'app'), decl('Subscription', 's2', 'main', 'app', 'x')]
|
|
278
|
+
const r = replicationForDatabase(database, pubs, subs)
|
|
279
|
+
expect(r.publications.map((p) => p.metadata.name)).toEqual(['p1'])
|
|
280
|
+
expect(r.subscriptions.map((s) => s.metadata.name)).toEqual(['s1'])
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('finds the Database declaring a publication database', () => {
|
|
284
|
+
expect(databaseForDeclaration(decl('Publication', 'p1', 'main', 'app'), [database])?.metadata.name).toBe('app-db')
|
|
285
|
+
expect(databaseForDeclaration(decl('Publication', 'p1', 'other', 'app'), [database])).toBeNull()
|
|
286
|
+
})
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
describe('relationUnavailable', () => {
|
|
290
|
+
const ws = (state: any, deniedNamespaces?: string[]): CNPGWorkspaceResponse => {
|
|
291
|
+
const coverage: CNPGWorkspaceResponse['coverage'] = {}
|
|
292
|
+
for (const k of CNPG_WORKSPACE_KEYS) coverage[k] = { state: 'full' }
|
|
293
|
+
coverage.backups = { state, deniedNamespaces }
|
|
294
|
+
return { installed: true, context: 'c', namespaces: null, coverage, objects: {}, issues: [], audit: [], backupsOmitted: 0 }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
it('answers from coverage', () => {
|
|
298
|
+
expect(relationUnavailable(ws('full'), 'backups', 'pg', 'Backups')).toBeNull()
|
|
299
|
+
expect(relationUnavailable(ws('partial', ['pg']), 'backups', 'pg', 'Backups')).toBe('No access to Backups')
|
|
300
|
+
expect(relationUnavailable(ws('partial', ['other']), 'backups', 'pg', 'Backups')).toBeNull()
|
|
301
|
+
expect(relationUnavailable(ws('denied'), 'backups', 'pg', 'Backups')).toBe('No access to Backups')
|
|
302
|
+
expect(relationUnavailable(null, 'backups', 'pg', 'Backups')).toBe('Backups could not be read')
|
|
303
|
+
})
|
|
304
|
+
})
|