Skip to content
Closed
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ Le serveur Node.js expose le port de débogage `9229` lorsqu'il est lancé en mo
3. Cliquez sur "Open dedicated DevTools for Node" ou attendez que votre target apparaisse sous "Remote Target".
4. Vous pouvez maintenant mettre des points d'arrêt, inspecter les variables, etc.

La documentation NestJS propose un outil de développement intégré : [NestJS DevTools](https://docs.nestjs.com/devtools/overview) — supervision, profiling et débogage des applications NestJS.

### Construction des images

Ce dépôt utilise des fichiers docker-compose pour construire les images docker:
Expand Down
19 changes: 19 additions & 0 deletions apps/nginx-strangler/conf.d/routing.conf
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ server {
}

# ── Routes migrées vers NestJS ────────────────────────────────────────────────
location /api/v1/projects-bulk {
proxy_pass http://server-nestjs;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# Préfixe projets : tout sauf /environments et /logs (restés en legacy)
location ^~ /api/v1/projects {
proxy_pass http://server-nestjs;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /api/v1/service-chains {
proxy_pass http://server-nestjs;
proxy_http_version 1.1;
Expand Down
14 changes: 14 additions & 0 deletions apps/server-nestjs/.env-example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
DEV_SETUP="true"
# Mode d'exécution Node.js (development | production | test)
NODE_ENV=development
# Activation des plugins externes (true | false). Actif par défaut en développement.
USE_PLUGINS=true
# HOME=/home/node
# Secret utilisé pour signer les cookies de session (min. 32 caractères)
SESSION_SECRET=a-very-strong-secret-with-more-than-32-char
Expand Down Expand Up @@ -41,7 +43,19 @@ CONTACT_EMAIL=cloudpinative-relations@interieur.gouv.fr
# --- Configuration OpenCDS ---
# URL de l'API OpenCDS (laisser vide pour désactiver)
OPENCDS_URL=
# URL interne de l'API OpenCDS
OPENCDS_INTERNAL_URL=
# Token d'authentification pour l'API OpenCDS
OPENCDS_API_TOKEN=token
# Vérification du certificat TLS de l'API OpenCDS (true | false)
OPENCDS_API_TLS_REJECT_UNAUTHORIZED=true

# --- Activation des plugins externes ---
# Mettez "true" et renseignez les variables ci-dessus pour activer chaque plugin.
USE_GITLAB=false
USE_NEXUS=false
USE_SONARQUBE=false
USE_REGISTRY=false
USE_HARBOR=false
USE_SERVICE_CHAIN=false
USE_ARGOCD=false
15 changes: 15 additions & 0 deletions apps/server-nestjs/.env.docker-example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ DOCKER=true
DEV_SETUP="true"
# Mode d'exécution Node.js (development | production | test)
NODE_ENV=development
# Activation des plugins externes (true | false).
USE_PLUGINS=fasle
# Secret utilisé pour signer les cookies de session (min. 32 caractères)
SESSION_SECRET=a-very-strong-secret-with-more-than-32-char

Expand Down Expand Up @@ -43,7 +45,20 @@ CONTACT_EMAIL=cloudpinative-relations@interieur.gouv.fr
# URL de l'API OpenCDS (laisser vide pour désactiver)
# Par défaut configuré sur le conteneur Mockoon (à changer sur envs non-PAX)
OPENCDS_URL=
# URL interne de l'API OpenCDS
OPENCDS_INTERNAL_URL=
# Token d'authentification pour l'API OpenCDS
OPENCDS_API_TOKEN=token
# Vérification du certificat TLS de l'API OpenCDS (true | false)
OPENCDS_API_TLS_REJECT_UNAUTHORIZED=false

# --- Activation des plugins externes ---
# Mettez "true" et renseignez les variables ci-dessus pour activer chaque plugin.
USE_GITLAB=false
USE_VAULT=false
USE_NEXUS=false
USE_SONARQUBE=false
USE_REGISTRY=false
USE_HARBOR=false
USE_SERVICE_CHAIN=false
USE_ARGOCD=false
15 changes: 15 additions & 0 deletions apps/server-nestjs/.env.integ-example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
DEV_SETUP="false"
# Active le mode intégration (charge ce fichier en surcharge)
INTEGRATION=true
# Activation des plugins externes (true | false). Désactivé en mode intégration.
USE_PLUGINS=false

# --- Keycloak ---
# Protocole (généralement https en intégration)
Expand Down Expand Up @@ -111,5 +113,18 @@ DSO_OBSERVABILITY_CHART_VERSION=dso-observability-0.1.7

# Configuration OpenCDS
OPENCDS_URL=
OPENCDS_INTERNAL_URL=
OPENCDS_API_TOKEN=token
OPENCDS_API_TLS_REJECT_UNAUTHORIZED=false

# --- Activation des plugins externes ---
# Désactivés par défaut en intégration : renseignez les variables et passez à "true" pour activer.
USE_KEYCLOAK=true
USE_GITLAB=true
USE_VAULT=true
USE_NEXUS=true
USE_SONARQUBE=true
USE_REGISTRY=true
USE_HARBOR=true
USE_SERVICE_CHAIN=true
USE_ARGOCD=true
54 changes: 54 additions & 0 deletions apps/server-nestjs/src/config/argocd.config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { argocdConfigFactory } from './argocd.config'
import { resetEnvs } from './config.spec.utils'

describe('argocdConfig', () => {
beforeEach(() => { resetEnvs(['ARGO_NAMESPACE', 'ARGOCD_URL', 'ARGOCD_INTERNAL_URL', 'DSO_ENV_CHART_VERSION', 'DSO_NS_CHART_VERSION', 'VAULT__DEPLOY_VAULT_CONNECTION_IN_NS']) })
afterEach(() => { vi.unstubAllEnvs() })

it('parses a full config and composes internalOrPublicUrl/probeUrl', () => {
vi.stubEnv('ARGOCD_URL', 'https://argocd.example.com')
vi.stubEnv('ARGOCD_INTERNAL_URL', 'https://argocd.internal:8080')
vi.stubEnv('ARGOCD_EXTRA_REPOSITORIES', 'repo1')
vi.stubEnv('VAULT__DEPLOY_VAULT_CONNECTION_IN_NS', 'true')
vi.stubEnv('PROJECTS_ROOT_DIR', 'forge-test/projects')
expect(argocdConfigFactory()).toMatchObject({
namespace: 'argocd',
url: 'https://argocd.example.com',
internalUrl: 'https://argocd.internal:8080',
extraRepositories: ['repo1'],
dsoEnvChartVersion: 'dso-env-1.6.0',
dsoNsChartVersion: 'dso-ns-1.1.5',
vaultDeployVaultConnectionInNs: true,
internalOrPublicUrl: 'https://argocd.internal:8080',
probeUrl: 'https://argocd.example.com/api/version',
})
})

it('falls back to public url when internal url is absent', () => {
vi.stubEnv('ARGOCD_URL', 'https://argocd.example.com')
vi.stubEnv('ARGOCD_EXTRA_REPOSITORIES', 'repo1')
vi.stubEnv('VAULT__DEPLOY_VAULT_CONNECTION_IN_NS', 'true')
vi.stubEnv('PROJECTS_ROOT_DIR', 'forge-test/projects')
const cfg = argocdConfigFactory()
expect(cfg.internalUrl).toBeUndefined()
expect(cfg.internalOrPublicUrl).toBe('https://argocd.example.com')
})

it('applies flag defaults', () => {
vi.stubEnv('ARGOCD_URL', 'https://argocd.example.com')
vi.stubEnv('ARGOCD_EXTRA_REPOSITORIES', 'repo1')
vi.stubEnv('PROJECTS_ROOT_DIR', 'forge-test/projects')
expect(argocdConfigFactory().vaultDeployVaultConnectionInNs).toBe(false)
})

it('defaults extraRepositories to an empty array', () => {
vi.stubEnv('ARGOCD_URL', 'https://argocd.example.com')
vi.stubEnv('PROJECTS_ROOT_DIR', 'forge-test/projects')
expect(argocdConfigFactory().extraRepositories).toEqual([])
})

it('parses with empty vars (ArgoCD disabled, probeUrl undefined)', () => {
expect(argocdConfigFactory().probeUrl).toBeUndefined()
})
})
27 changes: 27 additions & 0 deletions apps/server-nestjs/src/config/argocd.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { registerAs } from '@nestjs/config'
import z from 'zod'
import { csv, optionalUrl, truthySchema } from './config.utils'

const argocdFeatureSchema = z.object({
ARGO_NAMESPACE: z.string().default('argocd'),
ARGOCD_URL: optionalUrl,
ARGOCD_INTERNAL_URL: optionalUrl,
ARGOCD_EXTRA_REPOSITORIES: csv(z.string()),
DSO_ENV_CHART_VERSION: z.string().default('dso-env-1.6.0'),
DSO_NS_CHART_VERSION: z.string().default('dso-ns-1.1.5'),
VAULT__DEPLOY_VAULT_CONNECTION_IN_NS: truthySchema.default('false').transform(v => v === 'true' || v === '1'),
}).transform(raw => ({
namespace: raw.ARGO_NAMESPACE,
url: raw.ARGOCD_URL,
internalUrl: raw.ARGOCD_INTERNAL_URL,
extraRepositories: raw.ARGOCD_EXTRA_REPOSITORIES,
dsoEnvChartVersion: raw.DSO_ENV_CHART_VERSION,
dsoNsChartVersion: raw.DSO_NS_CHART_VERSION,
vaultDeployVaultConnectionInNs: raw.VAULT__DEPLOY_VAULT_CONNECTION_IN_NS,
internalOrPublicUrl: raw.ARGOCD_INTERNAL_URL ?? raw.ARGOCD_URL,
probeUrl: raw.ARGOCD_URL ? new URL('/api/version', raw.ARGOCD_URL).toString() : undefined,
}))

export type ArgocdConfig = z.infer<typeof argocdFeatureSchema>

export const argocdConfigFactory = registerAs('argocd', () => argocdFeatureSchema.parse(process.env))
67 changes: 67 additions & 0 deletions apps/server-nestjs/src/config/base.config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { baseConfigFactory } from './base.config'
import { resetEnvs } from './config.spec.utils'

describe('baseConfig', () => {
beforeEach(() => { resetEnvs(['NODE_ENV', 'CI', 'SERVER_HOST', 'SERVER_PORT', 'APP_VERSION', 'DB_URL', 'PROJECTS_ROOT_DIR']) })
afterEach(() => { vi.unstubAllEnvs() })

it('parses a full config and derives env flags', () => {
vi.stubEnv('NODE_ENV', 'development')
vi.stubEnv('CI', 'true')
vi.stubEnv('SERVER_HOST', 'host')
vi.stubEnv('SERVER_PORT', '4000')
vi.stubEnv('APP_VERSION', '1.2.3')
vi.stubEnv('PROJECTS_ROOT_DIR', 'forge-test/projects')
vi.stubEnv('DB_URL', 'postgres://db')
expect(baseConfigFactory()).toEqual({
isTest: false,
isDev: true,
isCI: true,
isProd: false,
serverHost: 'host',
serverPort: 4000,
appVersion: 'dev',
dbUrl: 'postgres://db',
projectsRootDir: 'forge-test/projects',
})
})

it('uses APP_VERSION in production, "dev" otherwise', () => {
vi.stubEnv('NODE_ENV', 'production')
vi.stubEnv('CI', 'true')
vi.stubEnv('SERVER_HOST', 'host')
vi.stubEnv('SERVER_PORT', '4000')
vi.stubEnv('APP_VERSION', '1.2.3')
vi.stubEnv('PROJECTS_ROOT_DIR', 'forge-test/projects')
vi.stubEnv('DB_URL', 'postgres://db')
expect(baseConfigFactory().appVersion).toBe('1.2.3')
vi.stubEnv('NODE_ENV', 'test')
vi.stubEnv('CI', 'true')
vi.stubEnv('SERVER_HOST', 'host')
vi.stubEnv('SERVER_PORT', '4000')
vi.stubEnv('APP_VERSION', '1.2.3')
vi.stubEnv('PROJECTS_ROOT_DIR', 'forge-test/projects')
vi.stubEnv('DB_URL', 'postgres://db')
expect(baseConfigFactory().appVersion).toBe('dev')
})

it('applies defaults when optional vars are absent', () => {
vi.stubEnv('PROJECTS_ROOT_DIR', '/r')
vi.stubEnv('NODE_ENV', 'production')
expect(baseConfigFactory()).toMatchObject({
isDev: false,
isProd: true,
isCI: false,
serverHost: 'localhost',
serverPort: 0,
appVersion: 'unknown',
dbUrl: undefined,
})
})

it('parses with an empty PROJECTS_ROOT_DIR (disabled integrations)', () => {
vi.stubEnv('NODE_ENV', 'production')
expect(baseConfigFactory()).toMatchObject({ projectsRootDir: undefined, isProd: true })
})
})
30 changes: 30 additions & 0 deletions apps/server-nestjs/src/config/base.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { registerAs } from '@nestjs/config'
import z from 'zod'
import { flag, truthySchema } from './config.utils'

const baseFeatureSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).optional(),
CI: flag(truthySchema.default('false')),
SERVER_HOST: z.string().default('localhost'),
SERVER_PORT: z.string().transform(Number).default('0'),
APP_VERSION: z.string().optional().default('unknown'),
DB_URL: z.string().url().optional(),
PROJECTS_ROOT_DIR: z.string().optional(),
}).transform((raw) => {
const nodeEnv = raw.NODE_ENV ?? 'production'
return {
isTest: raw.NODE_ENV === 'test',
isDev: raw.NODE_ENV === 'development',
isCI: raw.CI,
isProd: nodeEnv === 'production',
serverHost: raw.SERVER_HOST,
serverPort: raw.SERVER_PORT,
appVersion: raw.NODE_ENV === 'production' ? raw.APP_VERSION : 'dev',
dbUrl: raw.DB_URL,
projectsRootDir: raw.PROJECTS_ROOT_DIR,
}
})

export type BaseConfig = z.infer<typeof baseFeatureSchema>

export const baseConfigFactory = registerAs('base', () => baseFeatureSchema.parse(process.env))
8 changes: 8 additions & 0 deletions apps/server-nestjs/src/config/config.spec.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { vi } from 'vitest'

// Stub every env key a config reads as undefined (removed) so the test
// runner's real environment cannot leak into a config parse. Tests then
// stub only the keys they exercise. Pair with vi.unstubAllEnvs() in afterEach.
export function resetEnvs(keys: readonly string[]): void {
for (const key of keys) vi.stubEnv(key, undefined)
}
38 changes: 38 additions & 0 deletions apps/server-nestjs/src/config/config.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, expect, it } from 'vitest'
import z from 'zod'
import { csv, flag, truthySchema } from './config.utils'

describe('config.utils', () => {
describe('flag', () => {
it('coerces true/1 (case-insensitive) to true', () => {
const schema = flag(truthySchema.default('false'))
expect(schema.parse('true')).toBe(true)
expect(schema.parse('1')).toBe(true)
expect(schema.parse('TRUE')).toBe(true)
})

it('coerces false/0 to false', () => {
const schema = flag(truthySchema.default('true'))
expect(schema.parse('false')).toBe(false)
expect(schema.parse('0')).toBe(false)
})

it('falls back to the schema default when missing', () => {
expect(flag(truthySchema.default('false')).parse(undefined)).toBe(false)
expect(flag(truthySchema.default('true')).parse(undefined)).toBe(true)
})
})

describe('csv', () => {
const schema = csv(z.string())

it('splits, trims and drops empty parts', () => {
expect(schema.parse('a, b ,,c')).toEqual(['a', 'b', 'c'])
})

it('maps missing/empty to an empty array', () => {
expect(schema.parse(undefined)).toEqual([])
expect(schema.parse('')).toEqual([])
})
})
})
Loading
Loading