Skip to content

Commit b6c66b1

Browse files
dennisvankekemElderMattj-zimnowodaferruhcihan
authored
feat: static team settings page (#2024)
Co-authored-by: ElderMatt <[email protected]> Co-authored-by: Jehoszafat Zimnowoda <[email protected]> Co-authored-by: Ferruh Cihan <[email protected]>
1 parent b731a85 commit b6c66b1

File tree

11 files changed

+306
-149
lines changed

11 files changed

+306
-149
lines changed

src/cmd/migrate.test.ts

+101-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import { globSync } from 'glob'
12
import { applyChanges, Changes, filterChanges, getBuildName, policiesMigration } from 'src/cmd/migrate'
23
import stubs from 'src/test-stubs'
3-
import { globSync } from 'glob'
4-
import { getFileMap } from '../common/repo'
54
import { env } from '../common/envalid'
5+
import { getFileMap } from '../common/repo'
66

77
jest.mock('uuid', () => ({
88
v4: jest.fn(() => 'my-fixed-uuid'),
@@ -571,6 +571,105 @@ describe('Build image name migration', () => {
571571
}, 20000)
572572
})
573573

574+
describe('teamSettingsMigration', () => {
575+
// Create a mock values object representing teams with settings that need migration.
576+
const getTeamSettingsMockValues = (): any => ({
577+
versions: { specVersion: 1 },
578+
teamConfig: {
579+
team1: {
580+
settings: {
581+
alerts: {
582+
583+
opsgenie: 'ops_value',
584+
teams: 'keep this alert',
585+
},
586+
selfService: {
587+
service: ['ingress'],
588+
access: ['downloadKubeConfig', 'shell'],
589+
policies: ['edit policies'],
590+
apps: ['argocd', 'gitea'],
591+
},
592+
},
593+
},
594+
team2: {
595+
settings: {
596+
alerts: {
597+
teams: 'team2 alert',
598+
},
599+
selfService: {
600+
service: [],
601+
access: [],
602+
policies: [],
603+
apps: ['argocd'],
604+
},
605+
},
606+
},
607+
},
608+
})
609+
610+
// Expected values after migration:
611+
// - The alerts block should have the 'email' and 'opsgenie' keys removed.
612+
// - The selfService arrays ('service', 'access', 'policies', 'apps') are replaced with a new
613+
// teamMembers object with the correct boolean values.
614+
const getTeamSettingsExpectedValues = (): any => ({
615+
versions: { specVersion: 2 },
616+
teamConfig: {
617+
team1: {
618+
settings: {
619+
alerts: {
620+
teams: 'keep this alert',
621+
},
622+
selfService: {
623+
teamMembers: {
624+
createServices: true, // 'ingress' was present in service.
625+
editSecurityPolicies: true, // 'edit policies' was present in policies.
626+
useCloudShell: true, // 'shell' was present in access.
627+
downloadKubeconfig: true, // 'downloadKubeConfig' was present in access.
628+
downloadDockerLogin: false, // 'downloadDockerConfig' was not provided.
629+
},
630+
},
631+
},
632+
},
633+
team2: {
634+
settings: {
635+
alerts: {
636+
teams: 'team2 alert',
637+
},
638+
selfService: {
639+
teamMembers: {
640+
createServices: false,
641+
editSecurityPolicies: false,
642+
useCloudShell: false,
643+
downloadKubeconfig: false,
644+
downloadDockerLogin: false,
645+
},
646+
},
647+
},
648+
},
649+
},
650+
})
651+
652+
// Set up the values and changes flag to trigger the teamSettingsMigration.
653+
const teamSettingValues: any = getTeamSettingsMockValues()
654+
const valuesChanges: any = {
655+
version: 2,
656+
teamSettingsMigration: true,
657+
}
658+
const deps: any = {
659+
cd: jest.fn(),
660+
rename: jest.fn(),
661+
hfValues: jest.fn().mockReturnValue(teamSettingValues),
662+
terminal,
663+
writeValues: jest.fn(),
664+
}
665+
666+
it('should migrate team settings correctly', async () => {
667+
await applyChanges([valuesChanges], false, deps)
668+
const expectedValues = getTeamSettingsExpectedValues()
669+
expect(deps.writeValues).toBeCalledWith(expectedValues, true)
670+
}, 20000)
671+
})
672+
574673
jest.mock('glob')
575674
describe('Policies migration', () => {
576675
const mockFilePaths = ['/path/to/env/teams/admin/policies.yaml', '/path/to/env/teams/alpha/policies.yaml']

src/cmd/migrate.ts

+63
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ interface Change {
5151
[mutation: string]: string
5252
}>
5353
networkPoliciesMigration?: boolean
54+
teamSettingsMigration?: boolean
5455
teamResourceQuotaMigration?: boolean
5556
buildImageNameMigration?: boolean
5657
policiesMigration?: boolean
@@ -303,6 +304,67 @@ const networkPoliciesMigration = async (values: Record<string, any>): Promise<vo
303304
)
304305
}
305306

307+
const teamSettingsMigration = (values: Record<string, any>): void => {
308+
const teams: Array<string> = Object.keys(values?.teamConfig as Record<string, any>)
309+
310+
teams.map((teamName) => {
311+
// Get the alerts block for the team and remove email and opsgenie
312+
const alerts = get(values, `teamConfig.${teamName}.settings.alerts`)
313+
if (alerts?.email) unset(alerts, 'email')
314+
if (alerts?.opsgenie) unset(alerts, 'opsgenie')
315+
// Get the selfService block for the team
316+
const selfService = get(values, `teamConfig.${teamName}.settings.selfService`)
317+
if (!selfService) return
318+
319+
// Initialize the new teamMembers structure with default boolean values
320+
const teamMembers = {
321+
createServices: false,
322+
editSecurityPolicies: false,
323+
useCloudShell: false,
324+
downloadKubeconfig: false,
325+
downloadDockerLogin: false,
326+
}
327+
328+
// Map selfService.service.ingress -> teamMembers.createServices
329+
const servicePermissions = get(selfService, 'service', [])
330+
if (Array.isArray(servicePermissions) && servicePermissions.includes('ingress')) {
331+
teamMembers.createServices = true
332+
}
333+
334+
// Map selfService.access keys to corresponding teamMembers fields
335+
// - downloadKubeConfig -> downloadKubeconfig
336+
// - downloadDockerConfig -> downloadDockerLogin
337+
// - shell -> useCloudShell
338+
const accessPermissions = get(selfService, 'access', [])
339+
if (Array.isArray(accessPermissions)) {
340+
if (accessPermissions.includes('downloadKubeConfig')) {
341+
teamMembers.downloadKubeconfig = true
342+
}
343+
if (accessPermissions.includes('downloadDockerConfig')) {
344+
teamMembers.downloadDockerLogin = true
345+
}
346+
if (accessPermissions.includes('shell')) {
347+
teamMembers.useCloudShell = true
348+
}
349+
}
350+
351+
// Map selfService.policies.edit_policies -> teamMembers.editSecurityPolicies.
352+
// Note: In the source schema, the string "edit policies" is used.
353+
const policies = get(selfService, 'policies', [])
354+
if (Array.isArray(policies) && policies.includes('edit policies')) {
355+
teamMembers.editSecurityPolicies = true
356+
}
357+
358+
// Set the new teamMembers object on selfService
359+
set(selfService, 'teamMembers', teamMembers)
360+
361+
unset(selfService, 'service')
362+
unset(selfService, 'access')
363+
unset(selfService, 'policies')
364+
unset(selfService, 'apps')
365+
})
366+
}
367+
306368
export const getBuildName = (name: string, tag: string): string => {
307369
return `${name}-${tag}`
308370
.toLowerCase()
@@ -437,6 +499,7 @@ export const applyChanges = async (
437499
}
438500

439501
if (c.networkPoliciesMigration) await networkPoliciesMigration(values)
502+
if (c.teamSettingsMigration) teamSettingsMigration(values)
440503
if (c.teamResourceQuotaMigration) teamResourceQuotaMigration(values)
441504
if (c.buildImageNameMigration) await buildImageNameMigration(values)
442505
if (c.policiesMigration) await policiesMigration()

src/common/values.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { pathExists } from 'fs-extra'
22
import { mkdir, unlink, writeFile } from 'fs/promises'
3-
import { cloneDeep, get, isEmpty, isEqual, merge, omit, pick, set } from 'lodash'
3+
import { cloneDeep, get, isEmpty, isEqual, merge, mergeWith, omit, pick, set } from 'lodash'
44
import path from 'path'
55
import { supportedK8sVersions } from 'src/supportedK8sVersions.json'
66
import { stringify } from 'yaml'
@@ -121,7 +121,9 @@ export const writeValuesToFile = async (
121121
const values = cloneDeep(inValues)
122122
const originalValues = (await loadYaml(targetPath + suffix, { noError: true })) ?? {}
123123
d.debug('originalValues: ', JSON.stringify(originalValues, null, 2))
124-
const mergeResult = merge(cloneDeep(originalValues), values)
124+
const mergeResult = mergeWith(cloneDeep(originalValues), values, (prev, next) => {
125+
return next
126+
})
125127
const cleanedValues = removeBlankAttributes(values)
126128
const cleanedMergeResult = removeBlankAttributes(mergeResult)
127129
if (((overwrite && isEmpty(cleanedValues)) || (!overwrite && isEmpty(cleanedMergeResult))) && isSecretsFile) {

tests/fixtures/env/settings/alerts.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ metadata:
33
name: alerts
44
labels: {}
55
spec:
6-
email: {}
7-
msteams: {}
86
receivers:
7+
- slack
98
- msteams
9+
msteams: {}
1010
slack: {}
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
kind: AplAlertSet
22
spec:
3-
email:
4-
5-
nonCritical: [email protected]
3+
slack:
4+
url: https://hooks.slack.com/services/id
65
msteams:
76
highPrio: https://xxxxxxx.com
87
lowPrio: https://xxxxxxxx.com
9-
slack:
10-
url: https://hooks.slack.com/services/id
118
name: alerts
129
metadata:
1310
name: alerts

tests/fixtures/env/teams/admin/settings.yaml

+6-8
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@ spec:
88
alertmanager: true
99
grafana: true
1010
selfService:
11-
access:
12-
- shell
13-
- downloadCertificateAuthority
14-
policies:
15-
- edit policies
16-
apps: []
17-
service:
18-
- ingress
11+
teamMembers:
12+
createServices: false
13+
editSecurityPolicies: true
14+
useCloudShell: true
15+
downloadKubeconfig: false
16+
downloadDockerLogin: false
1917
alerts:
2018
groupInterval: 5m
2119
receivers:

tests/fixtures/env/teams/demo/secrets.settings.yaml

-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ kind: AplTeamSettingSet
22
spec:
33
password: somesecretvalue
44
alerts:
5-
email:
6-
7-
nonCritical: [email protected]
85
slack:
96
url: https://slack.con
107
name: demo

tests/fixtures/env/teams/demo/settings.yaml

+6-14
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ metadata:
55
apl.io/teamId: demo
66
spec:
77
alerts:
8-
email:
9-
10-
nonCritical: [email protected]
118
receivers:
129
- slack
1310
repeatInterval: 3h
@@ -30,14 +27,9 @@ spec:
3027
- name: services.loadbalancers
3128
value: '0'
3229
selfService:
33-
access:
34-
- shell
35-
- downloadCertificateAuthority
36-
apps: []
37-
policies:
38-
- edit policies
39-
service:
40-
- ingress
41-
team:
42-
- alerts
43-
password: somesecretvalue
30+
teamMembers:
31+
createServices: true
32+
editSecurityPolicies: true
33+
useCloudShell: true
34+
downloadKubeconfig: false
35+
downloadDockerLogin: false

tests/fixtures/env/teams/dev/settings.yaml

+6-8
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@ spec:
1111
egressPublic: false
1212
ingressPrivate: true
1313
selfService:
14-
access:
15-
- shell
16-
- downloadCertificateAuthority
17-
policies:
18-
- edit policies
19-
apps: []
20-
service:
21-
- ingress
14+
teamMembers:
15+
createServices: false
16+
editSecurityPolicies: true
17+
useCloudShell: true
18+
downloadKubeconfig: false
19+
downloadDockerLogin: false
2220
password: IkdUsKPcGAdanjas
2321
alerts:
2422
groupInterval: 5m

values-changes.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,8 @@ changes:
343343
- 'databases.keycloak.imported'
344344
- 'databases.gitea.imported'
345345
- 'databases.gitea.useOtomiDB'
346+
- version: 34
347+
teamSettingsMigration: true
346348
- version: 35
347349
teamResourceQuotaMigration: true
348350
- version: 36

0 commit comments

Comments
 (0)