Skip to content

Commit 8d6e387

Browse files
committed
feat: consolidate k8s service fetching
1 parent 2e63167 commit 8d6e387

3 files changed

Lines changed: 42 additions & 42 deletions

File tree

src/k8s-operations.test.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { CoreV1Api, V1Service } from '@kubernetes/client-node'
2-
import { getCloudttyActiveTime, getLogTime, groupK8sServices, toK8sService } from './k8s-operations'
2+
import { getCloudttyActiveTime, getLogTime, mergeCanaryServices, toK8sService } from './k8s-operations'
33

44
// Mock the KubeConfig
55
jest.mock('@kubernetes/client-node', () => {
@@ -30,9 +30,9 @@ describe('toK8sService', () => {
3030
expect(result).toEqual({ name: 'my-svc', ports: [8080], managedByKnative: false })
3131
})
3232

33-
test('uses app.kubernetes.io/name label as canonical name', () => {
34-
const svc = makeService({ metadata: { name: 'my-svc-v1', labels: { 'app.kubernetes.io/name': 'my-svc' } } })
35-
expect(toK8sService(svc)?.name).toBe('my-svc')
33+
test('returns the raw service name', () => {
34+
const svc = makeService({ metadata: { name: 'my-svc-v1', labels: {} } })
35+
expect(toK8sService(svc)?.name).toBe('my-svc-v1')
3636
})
3737

3838
test('filters out knative private services', () => {
@@ -60,40 +60,45 @@ describe('toK8sService', () => {
6060
})
6161
})
6262

63-
describe('groupK8sServices', () => {
64-
test('returns services unchanged when no duplicates', () => {
63+
describe('mergeCanaryServices', () => {
64+
test('returns services unchanged when no canary variants present', () => {
6565
const services = [
6666
{ name: 'svc-a', ports: [80], managedByKnative: false },
6767
{ name: 'svc-b', ports: [8080], managedByKnative: false },
6868
]
69-
expect(groupK8sServices(services)).toEqual(services)
69+
expect(mergeCanaryServices(services)).toEqual(services)
7070
})
7171

72-
test('merges canary variants with the same canonical name', () => {
72+
test('groups -v1 and -v2 variants into a single entry with the base name', () => {
7373
const services = [
74-
{ name: 'my-svc', ports: [80], managedByKnative: false },
75-
{ name: 'my-svc', ports: [80], managedByKnative: false },
74+
{ name: 'my-svc-v1', ports: [80], managedByKnative: false },
75+
{ name: 'my-svc-v2', ports: [80], managedByKnative: false },
7676
]
77-
expect(groupK8sServices(services)).toEqual([{ name: 'my-svc', ports: [80], managedByKnative: false }])
77+
expect(mergeCanaryServices(services)).toEqual([{ name: 'my-svc', ports: [80], managedByKnative: false }])
7878
})
7979

80-
test('deduplicates ports across merged services', () => {
80+
test('does not strip suffix when only one variant exists', () => {
81+
const services = [{ name: 'my-svc-v1', ports: [80], managedByKnative: false }]
82+
expect(mergeCanaryServices(services)).toEqual([{ name: 'my-svc-v1', ports: [80], managedByKnative: false }])
83+
})
84+
85+
test('deduplicates ports when merging canary variants', () => {
8186
const services = [
82-
{ name: 'my-svc', ports: [80, 443], managedByKnative: false },
83-
{ name: 'my-svc', ports: [443, 8080], managedByKnative: false },
87+
{ name: 'my-svc-v1', ports: [80, 443], managedByKnative: false },
88+
{ name: 'my-svc-v2', ports: [443, 8080], managedByKnative: false },
8489
]
85-
const result = groupK8sServices(services)
90+
const result = mergeCanaryServices(services)
8691
expect(result).toHaveLength(1)
8792
expect(result[0].ports).toEqual(expect.arrayContaining([80, 443, 8080]))
8893
expect(result[0].ports).toHaveLength(3)
8994
})
9095

91-
test('propagates managedByKnative if any service in the group has it set', () => {
96+
test('propagates managedByKnative if any variant in the group has it set', () => {
9297
const services = [
93-
{ name: 'my-svc', ports: [80], managedByKnative: false },
94-
{ name: 'my-svc', ports: [80], managedByKnative: true },
98+
{ name: 'my-svc-v1', ports: [80], managedByKnative: false },
99+
{ name: 'my-svc-v2', ports: [80], managedByKnative: true },
95100
]
96-
expect(groupK8sServices(services)[0].managedByKnative).toBe(true)
101+
expect(mergeCanaryServices(services)[0].managedByKnative).toBe(true)
97102
})
98103
})
99104

src/k8s-operations.ts

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,6 @@ export async function getTeamSecretsFromK8s(namespace: string) {
424424
export function toK8sService(item: V1Service): K8sService | null {
425425
const knativeServiceTypeLabel = 'networking.internal.knative.dev/serviceType'
426426
const knativeServiceLabel = 'serving.knative.dev/service'
427-
const appNameLabel = 'app.kubernetes.io/name'
428427

429428
const labels = item.metadata?.labels ?? {}
430429

@@ -441,28 +440,24 @@ export function toK8sService(item: V1Service): K8sService | null {
441440
managedByKnative = true
442441
}
443442

444-
// Group canary services (e.g. foo-v1 and foo-v2) by their common app.kubernetes.io/name label
445-
const canonicalName = labels[appNameLabel] ?? name
446443
const ports = item.spec?.ports?.map((p) => p.port) ?? []
447444

448-
return { name: canonicalName, ports, managedByKnative }
445+
return { name, ports, managedByKnative }
449446
}
450447

451-
export function groupK8sServices(services: K8sService[]): K8sService[] {
452-
const grouped = new Map<string, K8sService>()
453-
454-
for (const svc of services) {
455-
const existing = grouped.get(svc.name)
456-
if (existing) {
457-
grouped.set(svc.name, {
458-
...existing,
459-
ports: Array.from(new Set([...(existing.ports ?? []), ...(svc.ports ?? [])])),
460-
managedByKnative: existing.managedByKnative || svc.managedByKnative,
461-
})
462-
} else {
463-
grouped.set(svc.name, svc)
464-
}
465-
}
466-
467-
return Array.from(grouped.values())
448+
// Canary deployments produce two services: <name>-v1 and <name>-v2.
449+
// This function consolidates them into a single entry with the base name.
450+
// It works in two steps:
451+
// 1. Filter: drop -v2 when a matching -v1 exists (keeping only one representative)
452+
// 2. Map: rename the remaining -v1 to the base name when a matching -v2 exists
453+
// Services without a matching counterpart are left unchanged.
454+
export function mergeCanaryServices(services: K8sService[]): K8sService[] {
455+
const nameSet = new Set(services.map((s) => s.name))
456+
457+
return services
458+
.filter((svc) => !svc.name.endsWith('-v2') || !nameSet.has(svc.name.replace(/-v2$/, '-v1')))
459+
.map((svc) => {
460+
const baseName = svc.name.replace(/-v1$/, '')
461+
return nameSet.has(`${baseName}-v2`) ? { ...svc, name: baseName } : svc
462+
})
468463
}

src/otomi-stack.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ import {
121121
getKubernetesVersion,
122122
getSecretValues,
123123
getTeamSecretsFromK8s,
124-
groupK8sServices,
124+
mergeCanaryServices,
125125
toK8sService,
126126
watchPodUntilRunning,
127127
} from './k8s-operations'
@@ -2196,7 +2196,7 @@ export default class OtomiStack {
21962196
const { items } = await this.getApiClient().listNamespacedService({ namespace: `team-${teamId}` })
21972197
const mapped = items.flatMap((item) => toK8sService(item) ?? [])
21982198

2199-
return groupK8sServices(mapped)
2199+
return mergeCanaryServices(mapped)
22002200
}
22012201

22022202
async getKubecfg(teamId: string): Promise<KubeConfig> {

0 commit comments

Comments
 (0)