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
3 changes: 3 additions & 0 deletions apps/server-nestjs/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ FROM dev AS build
RUN pnpm --filter @cpn-console/logger run build
RUN pnpm --filter @cpn-console/shared run build

# Build hooks (génère dist/ et types/ nécessaires aux imports)
RUN pnpm --filter @cpn-console/hooks run build

# Réinjecter shared buildé dans node_modules (injectWorkspacePackages copie au moment de l'install)
RUN pnpm --filter @cpn-console/server-nestjs install --frozen-lockfile

Expand Down
2 changes: 2 additions & 0 deletions apps/server-nestjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@fastify/swagger": "^8.15.0",
"@fastify/swagger-ui": "^4.2.0",
"@gitbeaker/core": "^40.6.0",
"@gitbeaker/requester-utils": "^40.6.0",
"@gitbeaker/rest": "^40.6.0",
"@keycloak/keycloak-admin-client": "^24.0.0",
"@kubernetes-models/argo-cd": "^2.7.2",
Expand Down Expand Up @@ -80,6 +81,7 @@
"rxjs": "^7.8.2",
"undici": "^7.24.0",
"vitest-mock-extended": "^2.0.2",
"yaml": "^2.7.1",
"zod": "^3.25.76"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,27 @@
}

async injectDataInDatabase(path: string) {
this.logger.log('Starting init DB...')
this.logger.log(`Starting database initialization using data from ${path}`)
const { data } = await import(path)
await this.databaseInitializationService.initDb(data)
this.logger.log('initDb invoked successfully')
this.logger.log('Database initialization completed successfully')
}

async initApp() {

Check failure on line 40 in apps/server-nestjs/src/cpin-module/application-initialization/application-initialization-service/application-initialization.service.ts

View check run for this annotation

cloud-pi-native-sonarqube / SonarQube Code Analysis

apps/server-nestjs/src/cpin-module/application-initialization/application-initialization-service/application-initialization.service.ts#L40

Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.
try {
await this.databaseService.getConnection()
} catch (error) {
this.logger.error(error.message)
if (error instanceof Error) {
this.logger.error(`Database connection failed: ${error.message}`, error.stack)
} else {
this.logger.error(`Database connection failed: ${String(error)}`)
}
throw error
}

this.pluginManagementService.initPm()

this.logger.log('Reading init database file')
this.logger.log('Loading database initialization data')

try {
const dataPath
Expand All @@ -60,54 +64,59 @@
this.configurationService.isProd
&& !this.configurationService.isDevSetup
) {
this.logger.log('Cleaning up imported data file...')
this.logger.log(`Cleaning up imported data module at ${dataPath}`)
await rm(resolve(__dirname, dataPath))
this.logger.log(`Successfully deleted '${dataPath}'`)
this.logger.log(`Deleted imported data module at ${dataPath}`)
}
} catch (error) {
if (
error.code === 'ERR_MODULE_NOT_FOUND'
|| error.message.includes('Failed to load')
|| error.message.includes('Cannot find module')
) {
this.logger.log('No initDb file, skipping')
if (error instanceof Error) {
const errno = error as NodeJS.ErrnoException
const errorCode = typeof errno.code === 'string' ? errno.code : undefined
if (
errorCode === 'ERR_MODULE_NOT_FOUND'
|| error.message.includes('Failed to load')
|| error.message.includes('Cannot find module')
) {
this.logger.log('No database initialization data module was found, so initialization was skipped')
} else {
this.logger.warn(`Database initialization failed: ${error.message}`)
throw error
}
} else {
this.logger.warn(error.message)
this.logger.warn(`Database initialization failed: ${String(error)}`)
throw error
}
}

this.logger.debug({
isDev: this.configurationService.isDev,
isTest: this.configurationService.isTest,
isCI: this.configurationService.isCI,
isDevSetup: this.configurationService.isDevSetup,
isProd: this.configurationService.isProd,
})
this.logger.debug(`Runtime environment flags: isDev=${this.configurationService.isDev} isTest=${this.configurationService.isTest} isCI=${this.configurationService.isCI} isDevSetup=${this.configurationService.isDevSetup} isProd=${this.configurationService.isProd}`)
Comment thread
shikanime marked this conversation as resolved.
}

async exitGracefully(error?: Error) {
if (error instanceof Error) {
this.logger.fatal(error)
this.logger.fatal(`Exiting due to an unhandled error: ${error.message}`)
}
// @TODO Determine if it is necessary, or if we would rather plug ourselves
// onto NestJS lifecycle, or even if all this is actually necessary
// at all anymore
//
// await app.close();

this.logger.log('Closing connections...')
this.logger.log('Closing connections')
await this.databaseService.closeConnections()
this.logger.log('Exiting...')
this.logger.log('Exiting')
process.exit(error instanceof Error ? 1 : 0)
}

logExitCode(code: number) {
this.logger.warn(`received signal: ${code}`)
this.logger.warn(`Process is exiting with code ${code}`)
}

logUnhandledRejection(reason: unknown, promise: Promise<unknown>) {
this.logger.error({ message: 'Unhandled Rejection', promise, reason })
logUnhandledRejection(reason: unknown, _promise: Promise<unknown>) {
if (reason instanceof Error) {
this.logger.error(`Unhandled Promise rejection: ${reason.message}`, reason.stack)
return
}
this.logger.error(`Unhandled Promise rejection: ${String(reason)}`)
}

handleExit() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,70 @@ export class ConfigurationService {
= process.env.CONTACT_EMAIL
?? 'cloudpinative-relations@interieur.gouv.fr'

// argocd
argoNamespace = process.env.ARGO_NAMESPACE ?? 'argocd'
argocdUrl = process.env.ARGOCD_URL
argocdExtraRepositories = process.env.ARGOCD_EXTRA_REPOSITORIES

// dso
dsoEnvChartVersion = process.env.DSO_ENV_CHART_VERSION ?? 'dso-env-1.6.0'
dsoNsChartVersion = process.env.DSO_NS_CHART_VERSION ?? 'dso-ns-1.1.5'
Comment thread
shikanime marked this conversation as resolved.

// plugins
mockPlugins = process.env.MOCK_PLUGINS === 'true'
projectRootDir = process.env.PROJECTS_ROOT_DIR
pluginsDir = process.env.PLUGINS_DIR ?? '/plugins'

// gitlab
gitlabToken = process.env.GITLAB_TOKEN
gitlabUrl = process.env.GITLAB_URL
gitlabInternalUrl = process.env.GITLAB_INTERNAL_URL

gitlabMirrorTokenExpirationDays = Number(process.env.GITLAB_MIRROR_TOKEN_EXPIRATION_DAYS ?? 180)
gitlabMirrorTokenRotationThresholdDays = Number(process.env.GITLAB_MIRROR_TOKEN_ROTATION_THRESHOLD_DAYS ?? 90)

// vault
vaultToken = process.env.VAULT_TOKEN
vaultUrl = process.env.VAULT_URL
vaultInternalUrl = process.env.VAULT_INTERNAL_URL

vaultKvName = process.env.VAULT_KV_NAME ?? 'forge-dso'

// registry (harbor)
harborUrl = process.env.HARBOR_URL
harborInternalUrl = process.env.HARBOR_INTERNAL_URL
harborAdmin = process.env.HARBOR_ADMIN
harborAdminPassword = process.env.HARBOR_ADMIN_PASSWORD
harborRuleTemplate = process.env.HARBOR_RULE_TEMPLATE
harborRuleCount = process.env.HARBOR_RULE_COUNT
harborRetentionCron = process.env.HARBOR_RETENTION_CRON

// nexus
nexusUrl = process.env.NEXUS_URL
nexusInternalUrl = process.env.NEXUS_INTERNAL_URL
nexusAdmin = process.env.NEXUS_ADMIN
nexusAdminPassword = process.env.NEXUS_ADMIN_PASSWORD
nexusSecretExposedUrl
= process.env.NEXUS__SECRET_EXPOSE_INTERNAL_URL === 'true'
Comment thread
shikanime marked this conversation as resolved.
? process.env.NEXUS_INTERNAL_URL
: process.env.NEXUS_URL

getInternalOrPublicGitlabUrl() {
return this.gitlabInternalUrl ?? this.gitlabUrl
}

getInternalOrPublicVaultUrl() {
return this.vaultInternalUrl ?? this.vaultUrl
}

getInternalOrPublicHarborUrl() {
return this.harborInternalUrl ?? this.harborUrl
}

getInternalOrPublicNexusUrl() {
return this.nexusInternalUrl ?? this.nexusUrl
}

NODE_ENV
= process.env.NODE_ENV === 'test'
? 'test'
Expand Down
63 changes: 63 additions & 0 deletions apps/server-nestjs/src/modules/argocd/argocd-datastore.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { Prisma } from '@prisma/client'
import { Inject, Injectable } from '@nestjs/common'
import { PrismaService } from '../../cpin-module/infrastructure/database/prisma.service'

export const projectSelect = {
id: true,
name: true,
slug: true,
plugins: {
select: {
pluginName: true,
key: true,
value: true,
},
},
repositories: {
select: {
id: true,
internalRepoName: true,
isInfra: true,
helmValuesFiles: true,
deployRevision: true,
deployPath: true,
},
},
environments: {
select: {
id: true,
name: true,
clusterId: true,
cpu: true,
gpu: true,
memory: true,
autosync: true,
},
},
clusters: {
select: {
id: true,
label: true,
zone: {
select: {
slug: true,
},
},
},
},
} satisfies Prisma.ProjectSelect

export type ProjectWithDetails = Prisma.ProjectGetPayload<{
select: typeof projectSelect
}>

@Injectable()
export class ArgoCDDatastoreService {
constructor(@Inject(PrismaService) private readonly prisma: PrismaService) {}

async getAllProjects(): Promise<ProjectWithDetails[]> {
return this.prisma.project.findMany({
select: projectSelect,
})
}
}
25 changes: 25 additions & 0 deletions apps/server-nestjs/src/modules/argocd/argocd-health.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Inject, Injectable } from '@nestjs/common'
import { HealthIndicatorService } from '@nestjs/terminus'
import { ConfigurationService } from '../../cpin-module/infrastructure/configuration/configuration.service'

@Injectable()
export class ArgoCDHealthService {
constructor(
@Inject(ConfigurationService) private readonly config: ConfigurationService,
@Inject(HealthIndicatorService) private readonly healthIndicator: HealthIndicatorService,
) {}

async check(key: string) {
const indicator = this.healthIndicator.check(key)
if (!this.config.argocdUrl) return indicator.down('Not configured')

const url = new URL('/api/version', this.config.argocdUrl).toString()
try {
const response = await fetch(url)
if (response.status < 500) return indicator.up({ httpStatus: response.status })
return indicator.down({ httpStatus: response.status })
} catch (error) {
return indicator.down(error instanceof Error ? error.message : String(error))
}
}
}
16 changes: 16 additions & 0 deletions apps/server-nestjs/src/modules/argocd/argocd.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common'
import { HealthIndicatorService } from '@nestjs/terminus'
import { ConfigurationModule } from '../../cpin-module/infrastructure/configuration/configuration.module'
import { InfrastructureModule } from '../../cpin-module/infrastructure/infrastructure.module'
import { GitlabModule } from '../gitlab/gitlab.module'
import { VaultModule } from '../vault/vault.module'
import { ArgoCDDatastoreService } from './argocd-datastore.service'
import { ArgoCDHealthService } from './argocd-health.service'
import { ArgoCDService } from './argocd.service'

@Module({
imports: [ConfigurationModule, InfrastructureModule, GitlabModule, VaultModule],
providers: [HealthIndicatorService, ArgoCDHealthService, ArgoCDService, ArgoCDDatastoreService],
exports: [ArgoCDHealthService],
})
export class ArgoCDModule {}
Loading
Loading