Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,12 @@ Migrations: standard Prisma Migrate. Major version data migrations in `migration

- Template env files use `-example` suffix (not `.example`)
- `ci/scripts/init-env.sh` copies `*-example` to active equivalents (non-destructive)

## graphify

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Hein ? Ça sort d'où ça ? 😅


This project has a graphify knowledge graph at graphify-out/.

Rules:
- Before answering architecture or codebase questions, read graphify-out/GRAPH_REPORT.md for god nodes and community structure
- If graphify-out/wiki/index.md exists, navigate it instead of reading raw files
- After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost)
2 changes: 2 additions & 0 deletions apps/server-nestjs/src/main.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'
import { ScheduleModule } from '@nestjs/schedule'
import { DeploymentModule } from './modules/deployment/deployment.module'
import { HealthzModule } from './modules/healthz/healthz.module'
import { InfrastructureModule } from './modules/infrastructure/infrastructure.module'
import { KeycloakModule } from './modules/keycloak/keycloak.module'
import { LogModule } from './modules/log/log.module'
import { ProjectBulkModule } from './modules/project-bulk/project-bulk.module'
Expand All @@ -17,6 +18,7 @@ import { VersionModule } from './modules/version/version.module'

@Module({
imports: [
InfrastructureModule,
HealthzModule,
KeycloakModule,
ScheduleModule.forRoot(),
Expand Down
44 changes: 44 additions & 0 deletions apps/server-nestjs/src/modules/argocd/argocd-datastore.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Prisma } from '@prisma/client'
import { ENABLED } from '@cpn-console/shared'
import { Inject, Injectable } from '@nestjs/common'
import { PrismaService } from '../infrastructure/database/prisma.service'
import { AUTO_SYNC_PLUGIN_KEY, PLUGIN_NAME, SUSPENDED_PLUGIN_KEY } from './argocd.constant'

export const projectSelect = {
id: true,
Expand Down Expand Up @@ -101,4 +103,46 @@ export class ArgoCDDatastoreService {
select: projectSelect,
})
}

async getAutoSyncProjects(): Promise<ProjectWithDetails[]> {
return this.prisma.project.findMany({
select: projectSelect,
where: {
AND: [
{
plugins: {
some: {
pluginName: PLUGIN_NAME,
key: AUTO_SYNC_PLUGIN_KEY,
value: ENABLED,
},
},
},
{
NOT: {
plugins: {
some: {
pluginName: PLUGIN_NAME,
key: SUSPENDED_PLUGIN_KEY,
value: ENABLED,
},
},
},
},
],
},
})
}

async getProject(id: string): Promise<ProjectWithDetails | null> {
return this.prisma.project.findUnique({ where: { id }, select: projectSelect })
}

async getAdminPluginConfig(pluginName: string, key: string): Promise<string | null> {
const result = await this.prisma.adminPlugin.findUnique({
where: { pluginName_key: { pluginName, key } },
select: { value: true },
})
return result?.value ?? null
}
}
78 changes: 58 additions & 20 deletions apps/server-nestjs/src/modules/argocd/argocd-plugin.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import type { ServiceInfos } from '@cpn-console/hooks'
import { DISABLED, ENABLED } from '@cpn-console/shared'
import { Injectable } from '@nestjs/common'
import {
AUTO_SYNC_PLUGIN_KEY,
DEFAULT_DSO_ENV_CHART_VERSION,
DEFAULT_DSO_NS_CHART_VERSION,
DSO_ENV_CHART_VERSION_PLUGIN_KEY,
DSO_NS_CHART_VERSION_PLUGIN_KEY,
EXTRA_REPOSITORIES_PLUGIN_KEY,
PLATFORM_ADMIN_GROUP_PATH,
PLATFORM_ADMIN_GROUP_PATH_PLUGIN_KEY,
PLATFORM_READONLY_GROUP_PATH,
PLATFORM_READONLY_GROUP_PATH_PLUGIN_KEY,
PLATFORM_SECURITY_GROUP_PATH,
PROJECT_ADMIN_GROUP_PATH_SUFFIX,
PROJECT_ADMIN_GROUP_PATH_SUFFIX_PLUGIN_KEY,
PROJECT_DEVELOPER_GROUP_PATH_SUFFIX,
PROJECT_DEVELOPER_GROUP_PATH_SUFFIX_PLUGIN_KEY,
PROJECT_DEVOPS_GROUP_PATH_SUFFIX,
PROJECT_DEVOPS_GROUP_PATH_SUFFIX_PLUGIN_KEY,
PROJECT_READONLY_GROUP_PATH_SUFFIX,
PROJECT_READONLY_GROUP_PATH_SUFFIX_PLUGIN_KEY,
PROJECT_SECURITY_GROUP_PATH_SUFFIX,
SUSPENDED_PLUGIN_KEY,
} from './argocd.constant'

@Injectable()
Expand All @@ -29,7 +41,7 @@ export class ArgoCDPluginService {
description: 'ArgoCD est un outil déclaratif de livraison continue GitOps pour Kubernetes',
config: {
global: [{
key: 'extraRepositories',
key: EXTRA_REPOSITORIES_PLUGIN_KEY,
kind: 'text',
permissions: {
admin: { read: true, write: true },
Expand All @@ -40,7 +52,7 @@ export class ArgoCDPluginService {
description: extraRepositoriesDesc,
placeholder: 'https://github.com/',
}, {
key: 'platformAdminGroupPath',
key: PLATFORM_ADMIN_GROUP_PATH_PLUGIN_KEY,
kind: 'text',
permissions: {
admin: { read: true, write: true },
Expand All @@ -50,7 +62,7 @@ export class ArgoCDPluginService {
value: PLATFORM_ADMIN_GROUP_PATH,
description: 'Chemin du groupe administrateur de plateforme',
}, {
key: 'platformReadonlyGroupPath',
key: PLATFORM_READONLY_GROUP_PATH_PLUGIN_KEY,
kind: 'text',
permissions: {
admin: { read: true, write: true },
Expand All @@ -70,7 +82,7 @@ export class ArgoCDPluginService {
value: PLATFORM_SECURITY_GROUP_PATH,
description: 'Chemin du groupe sécurité de plateforme',
}, {
key: 'projectAdminGroupPathSuffix',
key: PROJECT_ADMIN_GROUP_PATH_SUFFIX_PLUGIN_KEY,
kind: 'text',
permissions: {
admin: { read: true, write: true },
Expand All @@ -80,7 +92,7 @@ export class ArgoCDPluginService {
value: PROJECT_ADMIN_GROUP_PATH_SUFFIX,
description: 'Suffixe du chemin du groupe administrateur de projet',
}, {
key: 'projectDevopsGroupPathSuffix',
key: PROJECT_DEVOPS_GROUP_PATH_SUFFIX_PLUGIN_KEY,
kind: 'text',
permissions: {
admin: { read: true, write: true },
Expand All @@ -90,7 +102,7 @@ export class ArgoCDPluginService {
value: PROJECT_DEVOPS_GROUP_PATH_SUFFIX,
description: 'Suffixe du chemin du groupe devops de projet',
}, {
key: 'projectDevelopperGroupPathSuffix',
key: PROJECT_DEVELOPER_GROUP_PATH_SUFFIX_PLUGIN_KEY,
kind: 'text',
permissions: {
admin: { read: true, write: true },
Expand All @@ -100,7 +112,7 @@ export class ArgoCDPluginService {
value: PROJECT_DEVELOPER_GROUP_PATH_SUFFIX,
description: 'Suffixe du chemin du groupe développeur de projet',
}, {
key: 'projectReadonlyGroupPathSuffix',
key: PROJECT_READONLY_GROUP_PATH_SUFFIX_PLUGIN_KEY,
kind: 'text',
permissions: {
admin: { read: true, write: true },
Expand All @@ -120,7 +132,7 @@ export class ArgoCDPluginService {
value: PROJECT_SECURITY_GROUP_PATH_SUFFIX,
description: 'Suffixe du chemin du groupe sécurité de projet',
}, {
key: 'dsoEnvChartVersion',
key: DSO_ENV_CHART_VERSION_PLUGIN_KEY,
kind: 'text',
permissions: {
admin: { read: true, write: true },
Expand All @@ -131,7 +143,7 @@ export class ArgoCDPluginService {
description: 'Version du chart Helm dso-env',
placeholder: DEFAULT_DSO_ENV_CHART_VERSION,
}, {
key: 'dsoNsChartVersion',
key: DSO_NS_CHART_VERSION_PLUGIN_KEY,
kind: 'text',
permissions: {
admin: { read: true, write: true },
Expand All @@ -142,18 +154,44 @@ export class ArgoCDPluginService {
description: 'Version du chart Helm dso-ns',
placeholder: DEFAULT_DSO_NS_CHART_VERSION,
}],
project: [{
key: 'extraRepositories',
kind: 'text',
permissions: {
admin: { read: true, write: true },
user: { read: true, write: false },
project: [
{
kind: 'switch',
key: SUSPENDED_PLUGIN_KEY,
initialValue: ENABLED,
permissions: {
admin: { read: true, write: true },
user: { read: true, write: true },
},
title: 'Suspendre le projet',
value: ENABLED,
description: 'Suspendre la synchronisation ArgoCD pour ce projet',
},
title: 'Source repositories',
value: '',
description: extraRepositoriesDesc,
placeholder: 'https://github.com/',
}],
{
kind: 'switch',
key: AUTO_SYNC_PLUGIN_KEY,
initialValue: DISABLED,
permissions: {
admin: { read: true, write: true },
user: { read: true, write: true },
},
title: 'Synchronisation automatique ArgoCD',
value: DISABLED,
description: 'Synchroniser automatiquement le projet ArgoCD',
},
{
key: EXTRA_REPOSITORIES_PLUGIN_KEY,
kind: 'text',
permissions: {
admin: { read: true, write: true },
user: { read: true, write: false },
},
title: 'Source repositories',
value: '',
description: extraRepositoriesDesc,
placeholder: 'https://github.com/',
},
],
},
} as const satisfies ServiceInfos
}
Expand Down
18 changes: 18 additions & 0 deletions apps/server-nestjs/src/modules/argocd/argocd.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,21 @@ export const PROJECT_SECURITY_GROUP_PATH_SUFFIX = '/console/security'
// Default chart versions
export const DEFAULT_DSO_ENV_CHART_VERSION = 'dso-env-1.6.0'
export const DEFAULT_DSO_NS_CHART_VERSION = 'dso-ns-1.1.5'

// Plugin configuration keys
export const EXTRA_REPOSITORIES_PLUGIN_KEY = 'extraRepositories'
export const PLATFORM_ADMIN_GROUP_PATH_PLUGIN_KEY = 'platformAdminGroupPath'
export const PLATFORM_READONLY_GROUP_PATH_PLUGIN_KEY = 'platformReadonlyGroupPath'
export const PROJECT_ADMIN_GROUP_PATH_SUFFIX_PLUGIN_KEY = 'projectAdminGroupPathSuffix'
export const PROJECT_DEVOPS_GROUP_PATH_SUFFIX_PLUGIN_KEY = 'projectDevopsGroupPathSuffix'
export const PROJECT_DEVELOPER_GROUP_PATH_SUFFIX_PLUGIN_KEY = 'projectDevelopperGroupPathSuffix'
export const PROJECT_READONLY_GROUP_PATH_SUFFIX_PLUGIN_KEY = 'projectReadonlyGroupPathSuffix'
export const DSO_ENV_CHART_VERSION_PLUGIN_KEY = 'dsoEnvChartVersion'
export const DSO_NS_CHART_VERSION_PLUGIN_KEY = 'dsoNsChartVersion'

// Plugin name
export const PLUGIN_NAME = 'argocd'

// Plugin synchronization flags (shared plugin config keys)
export const AUTO_SYNC_PLUGIN_KEY = 'autoSync'
export const SUSPENDED_PLUGIN_KEY = 'suspended'
8 changes: 4 additions & 4 deletions apps/server-nestjs/src/modules/argocd/argocd.module.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Module } from '@nestjs/common'
import { HealthIndicatorService } from '@nestjs/terminus'
import { TerminusModule } from '@nestjs/terminus'
import { GitlabModule } from '../gitlab/gitlab.module'
import { ConfigurationModule } from '../infrastructure/configuration/configuration.module'
import { InfrastructureModule } from '../infrastructure/infrastructure.module'
import { DatabaseModule } from '../infrastructure/database/database.module'
import { VaultModule } from '../vault/vault.module'
import { ArgoCDDatastoreService } from './argocd-datastore.service'
import { ArgoCDHealthService } from './argocd-health.service'
import { ArgoCDPluginService } from './argocd-plugin.service'
import { ArgoCDService } from './argocd.service'

@Module({
imports: [ConfigurationModule, InfrastructureModule, GitlabModule, VaultModule],
providers: [HealthIndicatorService, ArgoCDHealthService, ArgoCDPluginService, ArgoCDService, ArgoCDDatastoreService],
imports: [ConfigurationModule, DatabaseModule, GitlabModule, TerminusModule, VaultModule],
providers: [ArgoCDHealthService, ArgoCDPluginService, ArgoCDService, ArgoCDDatastoreService],
exports: [ArgoCDHealthService, ArgoCDPluginService, ArgoCDService],
})
export class ArgoCDModule {}
8 changes: 4 additions & 4 deletions apps/server-nestjs/src/modules/argocd/argocd.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ describe('argoCDService', () => {
})

const infraProject = makeProjectSchema({ id: 100, http_url_to_repo: 'https://gitlab.internal/infra' })
datastore.getAllProjects.mockResolvedValue([mockProject])
datastore.getAutoSyncProjects.mockResolvedValue([mockProject])
gitlab.getOrCreateInfraGroupRepo.mockResolvedValue(infraProject)
gitlab.getOrCreateProjectGroupPublicUrl.mockResolvedValue('https://gitlab.internal/group')
gitlab.getOrCreateInfraGroupRepoPublicUrl.mockResolvedValue('https://gitlab.internal/infra-repo')
Expand Down Expand Up @@ -313,7 +313,7 @@ describe('argoCDService', () => {
})

const infraProject = makeProjectSchema({ id: 100, http_url_to_repo: 'https://gitlab.internal/infra' })
datastore.getAllProjects.mockResolvedValue([mockProject])
datastore.getAutoSyncProjects.mockResolvedValue([mockProject])
gitlab.getOrCreateInfraGroupRepo.mockResolvedValue(infraProject)
gitlab.getOrCreateProjectGroupPublicUrl.mockResolvedValue('https://gitlab.internal/group')
gitlab.getOrCreateInfraGroupRepoPublicUrl.mockResolvedValue('https://gitlab.internal/infra-repo')
Expand Down Expand Up @@ -364,7 +364,7 @@ describe('argoCDService', () => {
})

const infraProject = makeProjectSchema({ id: 100, http_url_to_repo: 'https://gitlab.internal/infra' })
datastore.getAllProjects.mockResolvedValue([mockProject])
datastore.getAutoSyncProjects.mockResolvedValue([mockProject])
gitlab.getOrCreateInfraGroupRepo.mockResolvedValue(infraProject)
gitlab.getOrCreateProjectGroupPublicUrl.mockResolvedValue('https://gitlab.internal/group')
gitlab.getOrCreateInfraGroupRepoPublicUrl.mockResolvedValue('https://gitlab.internal/infra-repo')
Expand Down Expand Up @@ -409,7 +409,7 @@ describe('argoCDService', () => {
})

const infraProject = makeProjectSchema({ id: 100, http_url_to_repo: 'https://gitlab.internal/infra' })
datastore.getAllProjects.mockResolvedValue([mockProject])
datastore.getAutoSyncProjects.mockResolvedValue([mockProject])
gitlab.getOrCreateInfraGroupRepo.mockResolvedValue(infraProject)
gitlab.getOrCreateProjectGroupPublicUrl.mockResolvedValue('https://gitlab.internal/group')
gitlab.getOrCreateInfraGroupRepoPublicUrl.mockResolvedValue('https://gitlab.internal/infra-repo')
Expand Down
9 changes: 7 additions & 2 deletions apps/server-nestjs/src/modules/argocd/argocd.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
PROJECT_READONLY_GROUP_PATH_SUFFIX,
PROJECT_SECURITY_GROUP_PATH_SUFFIX,
} from './argocd.constant'
import { isSuspended } from './argocd.utils'

@Injectable()
export class ArgoCDService {
Expand All @@ -45,6 +46,10 @@ export class ArgoCDService {

@StartActiveSpan()
private async syncProject(project: ProjectWithDetails) {
if (isSuspended(project)) {
this.logger.log(`Skipping ArgoCD sync for suspended project ${project.slug}`)
return
}
const span = trace.getActiveSpan()
span?.setAttribute('project.slug', project.slug)
this.logger.log(`Handling a project upsert event for ${project.slug}`)
Expand All @@ -66,11 +71,11 @@ export class ArgoCDService {
this.logger.log(`ArgoCD sync completed for project ${project.slug}`)
}

// @Cron(CronExpression.EVERY_HOUR)
@Cron(CronExpression.EVERY_HOUR)
@StartActiveSpan()
async handleCron() {
this.logger.log('Starting ArgoCD reconciliation')
const projects = await this.argoCDDatastore.getAllProjects()
const projects = await this.argoCDDatastore.getAutoSyncProjects()
const span = trace.getActiveSpan()
span?.setAttribute('argocd.projects.count', projects.length)
this.logger.log(`Loaded ${projects.length} projects for ArgoCD reconciliation`)
Expand Down
11 changes: 11 additions & 0 deletions apps/server-nestjs/src/modules/argocd/argocd.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ProjectWithDetails } from './argocd-datastore.service'
import { ENABLED } from '@cpn-console/shared'
import { AUTO_SYNC_PLUGIN_KEY, PLUGIN_NAME, SUSPENDED_PLUGIN_KEY } from './argocd.constant'

export function isSuspended(project: ProjectWithDetails): boolean {
return project.plugins?.some(p => p.pluginName === PLUGIN_NAME && p.key === SUSPENDED_PLUGIN_KEY && p.value === ENABLED) ?? false
}

export function isAutoSync(project: ProjectWithDetails): boolean {
return project.plugins?.some(p => p.pluginName === PLUGIN_NAME && p.key === AUTO_SYNC_PLUGIN_KEY && p.value === ENABLED) ?? false
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Module } from '@nestjs/common'
import { AppEventsModule } from '../events/app-events.module'
import { InfrastructureModule } from '../infrastructure/infrastructure.module'
import { DatabaseModule } from '../infrastructure/database/database.module'
import { EventsModule } from '../infrastructure/events/events.module'
import { ProjectPermissionModule } from '../infrastructure/permission/project/project.module'
import { ProjectModule } from '../project/project.module'
import { DeploymentDatastoreService } from './deployment-datastore.service'
import { DeploymentController } from './deployment.controller'
import { DeploymentService } from './deployment.service'

@Module({
imports: [AppEventsModule, InfrastructureModule],
imports: [AppEventsModule, DatabaseModule, EventsModule, ProjectModule, ProjectPermissionModule],
controllers: [DeploymentController],
providers: [
DeploymentDatastoreService,
Expand Down
Loading