|
| 1 | +import type { Project } from '@cpn-console/hooks' |
| 2 | +import { removeTrailingSlash, requiredEnv } from '@cpn-console/shared' |
| 3 | +import { Gitlab, type ProjectSchema } from '@gitbeaker/core' |
| 4 | +import yaml from 'js-yaml' |
| 5 | + |
| 6 | +const valuesPath = 'helm/values.yaml' |
| 7 | +const valuesBranch = 'main' |
| 8 | +const groupName = 'observability' |
| 9 | +const repoName = 'observability' |
| 10 | + |
| 11 | +export type EnvType = 'prod' | 'hprod' |
| 12 | +interface Tenant {} |
| 13 | + |
| 14 | +interface Env { |
| 15 | + groups?: string[] |
| 16 | + tenants: { |
| 17 | + [x: `${EnvType}-${string}`]: Tenant |
| 18 | + } |
| 19 | +} |
| 20 | +export interface ObservabilityProject { |
| 21 | + projectName: string // slug |
| 22 | + envs: { |
| 23 | + prod: Env |
| 24 | + hprod: Env |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +interface ObservabilityData { |
| 29 | + global: { |
| 30 | + projects: { |
| 31 | + [x: string]: ObservabilityProject |
| 32 | + } |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +const yamlInitData = ` |
| 37 | + global: |
| 38 | + tenants: [] |
| 39 | + ` |
| 40 | + |
| 41 | +export class ObservabilityRepoManager { |
| 42 | + private gitlabApi: Gitlab |
| 43 | + |
| 44 | + constructor() { |
| 45 | + const gitlabUrl = removeTrailingSlash(requiredEnv('GITLAB_URL')) |
| 46 | + const gitlabToken = requiredEnv('GITLAB_TOKEN') |
| 47 | + this.gitlabApi = new Gitlab({ token: gitlabToken, host: gitlabUrl }) |
| 48 | + } |
| 49 | + |
| 50 | + private async findOrCreateRepo(): Promise<ProjectSchema> { |
| 51 | + try { |
| 52 | + // Find or create parent Gitlab group |
| 53 | + const groups = await this.gitlabApi.Groups.search(groupName) |
| 54 | + let group = groups.find(g => g.full_path === groupName || g.name === groupName) |
| 55 | + if(!group) { |
| 56 | + group = await this.gitlabApi.Groups.create(groupName, groupName) |
| 57 | + } |
| 58 | + // Find or create parent Gitlab repository |
| 59 | + const projects: ProjectSchema[] = await this.gitlabApi.Groups.allProjects(group.id) |
| 60 | + const repo = projects.find(p => p.name === repoName) |
| 61 | + if (!repo) { |
| 62 | + return this.gitlabApi.Projects.create({ |
| 63 | + name: repoName, |
| 64 | + path: repoName, |
| 65 | + namespaceId: group.id, |
| 66 | + description: 'Repo for Observatorium values, managed by DSO console', |
| 67 | + }) |
| 68 | + } |
| 69 | + return repo |
| 70 | + } catch (error) { |
| 71 | + throw new Error(`Unexpected error: ${error}`) |
| 72 | + } |
| 73 | + } |
| 74 | + |
| 75 | + // Fonction pour récupérer le fichier values.yaml |
| 76 | + private async getValuesFile(project: ProjectSchema): Promise<ObservabilityData | null> { |
| 77 | + try { |
| 78 | + // Essayer de récupérer le fichier |
| 79 | + const file = await this.gitlabApi.RepositoryFiles.show(project.id, valuesPath, valuesBranch) |
| 80 | + return yaml.load(Buffer.from(file.content, 'base64').toString('utf-8')) as ObservabilityData |
| 81 | + } catch (error) { |
| 82 | + console.log(error) |
| 83 | + return null |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + private writeYamlFile(data: object): string { |
| 88 | + try { |
| 89 | + return yaml.dump(data, { |
| 90 | + styles: { |
| 91 | + '!!seq': 'flow', |
| 92 | + }, |
| 93 | + sortKeys: false, |
| 94 | + lineWidth: -1, // Pour éviter le retour à la ligne automatique |
| 95 | + }) |
| 96 | + } catch (e) { |
| 97 | + console.error(e) |
| 98 | + return '' |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + // Fonction pour éditer, committer et pousser un fichier YAML |
| 103 | + public async commitAndPushYamlFile(project: ProjectSchema, filePath: string, branch: string, commitMessage: string, yamlString: string): Promise<void> { |
| 104 | + const encodedContent = Buffer.from(yamlString).toString('utf-8') |
| 105 | + try { |
| 106 | + // Vérifier si le fichier existe déjà |
| 107 | + await this.gitlabApi.RepositoryFiles.show(project.id, filePath, branch) |
| 108 | + // Si le fichier existe, mise à jour |
| 109 | + await this.gitlabApi.RepositoryFiles.edit(project.id, filePath, branch, encodedContent, commitMessage) |
| 110 | + console.log(`Fichier YAML commité et poussé: ${filePath}`) |
| 111 | + } catch (error: any) { |
| 112 | + console.log('Le fichier n\'existe pas') |
| 113 | + // Si le fichier n'existe pas, création |
| 114 | + console.log(`error : ${JSON.stringify(error)}`) |
| 115 | + console.log(error) |
| 116 | + await this.gitlabApi.RepositoryFiles.create(project.id, filePath, branch, encodedContent, commitMessage) |
| 117 | + console.log(`Fichier YAML créé et poussé: ${filePath}`) |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + public async updateProjectConfig(project: Project, projectValue: ObservabilityProject): Promise<string> { |
| 122 | + // Déplacer toute la logique de création ou de récupération de groupe et de repo ici |
| 123 | + const gitlabRepo = await this.findOrCreateRepo() |
| 124 | + |
| 125 | + // Récupérer le fichier values.yaml |
| 126 | + const yamlFile = await this.getValuesFile(gitlabRepo) |
| 127 | + || yaml.load(Buffer.from(yamlInitData, 'base64').toString('utf-8')) as ObservabilityData |
| 128 | + |
| 129 | + const projects = yamlFile.global?.projects || {} |
| 130 | + |
| 131 | + if (JSON.stringify(projects[project.id]) === JSON.stringify(projectValue)) { |
| 132 | + return 'Already up-to-date' |
| 133 | + } |
| 134 | + |
| 135 | + projects[project.id] = projectValue |
| 136 | + |
| 137 | + const yamlString = this.writeYamlFile({ |
| 138 | + ...yamlFile, |
| 139 | + global: { |
| 140 | + ...yamlFile.global, |
| 141 | + projects, |
| 142 | + }, |
| 143 | + }) |
| 144 | + |
| 145 | + await this.commitAndPushYamlFile( |
| 146 | + gitlabRepo, |
| 147 | + valuesPath, |
| 148 | + valuesBranch, |
| 149 | + `Update project ${project.slug}`, |
| 150 | + yamlString, |
| 151 | + ) |
| 152 | + return `Update: ${project.slug}` |
| 153 | + } |
| 154 | + |
| 155 | + public async deleteProjectConfig(project: Project) { |
| 156 | + // Même logique de groupe et de repo que pour l'upsert |
| 157 | + const gitlabRepo = await this.findOrCreateRepo() |
| 158 | + |
| 159 | + // Récupérer le fichier values.yaml |
| 160 | + const yamlFile = await this.getValuesFile(gitlabRepo) |
| 161 | + |
| 162 | + // Rechercher le projet à supprimer |
| 163 | + if (!yamlFile || (yamlFile.global?.projects && !(project.id in yamlFile.global.projects))) { |
| 164 | + return |
| 165 | + } |
| 166 | + |
| 167 | + // Modifier le fichier YAML et commiter |
| 168 | + const yamlFileStripped = structuredClone(yamlFile) |
| 169 | + delete yamlFileStripped.global?.projects?.[project.id] |
| 170 | + |
| 171 | + const yamlString = this.writeYamlFile(yamlFileStripped) |
| 172 | + |
| 173 | + return this.commitAndPushYamlFile( |
| 174 | + gitlabRepo, |
| 175 | + valuesPath, |
| 176 | + valuesBranch, |
| 177 | + `Delete project ${project.name}`, |
| 178 | + yamlString, |
| 179 | + ) |
| 180 | + } |
| 181 | +} |
0 commit comments