Skip to content

Commit 93de69a

Browse files
author
Mathias
committed
Initial commit
0 parents  commit 93de69a

12 files changed

Lines changed: 626 additions & 0 deletions

File tree

.dockerignore

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
node_modules/
2+
npm-debug.log
3+
coverage/
4+
.git/
5+
.gitignore
6+
.env
7+
.env.local
8+
.vscode/
9+
.idea/
10+
*.md
11+
tests/
12+
.eslintrc.json
13+
.prettierrc
14+
jest.config.js
15+
Dockerfile
16+
.dockerignore
17+
k8s/
18+
.github/

.github/workflows/ci-cd.yml

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
name: CI/CD Pipeline
2+
3+
on:
4+
push:
5+
branches: [ main, develop ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
env:
10+
DOCKER_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/nodejs-k8s-app
11+
NODE_VERSION: '20'
12+
13+
jobs:
14+
lint:
15+
name: Lint & Format Check
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Setup Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: ${{ env.NODE_VERSION }}
25+
cache: 'npm'
26+
27+
- name: Install dependencies
28+
run: npm ci
29+
30+
- name: Run ESLint
31+
run: npm run lint
32+
33+
- name: Check formatting
34+
run: npx prettier --check "src/**/*.js" "specs/**/*.js"
35+
36+
test:
37+
name: Tests unitaires
38+
runs-on: ubuntu-latest
39+
needs: lint
40+
41+
strategy:
42+
matrix:
43+
node-version: [20, 22]
44+
45+
steps:
46+
- uses: actions/checkout@v4
47+
48+
- name: Setup Node.js ${{ matrix.node-version }}
49+
uses: actions/setup-node@v4
50+
with:
51+
node-version: ${{ matrix.node-version }}
52+
cache: 'npm'
53+
54+
- name: Install dependencies
55+
run: npm ci
56+
57+
- name: Run tests
58+
run: npm test -- --coverage
59+
60+
- name: Upload coverage to Codecov
61+
if: matrix.node-version == 20
62+
uses: codecov/codecov-action@v3
63+
with:
64+
file: ./coverage/coverage-final.json
65+
flags: unittests
66+
fail_ci_if_error: false
67+
68+
build-and-push:
69+
name: Build et Push Docker Image
70+
needs: test
71+
runs-on: ubuntu-latest
72+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
73+
74+
outputs:
75+
image-digest: ${{ steps.build.outputs.digest }}
76+
77+
steps:
78+
- uses: actions/checkout@v4
79+
80+
- name: Set up QEMU
81+
uses: docker/setup-qemu-action@v3
82+
83+
- name: Set up Docker Buildx
84+
uses: docker/setup-buildx-action@v3
85+
86+
- name: Login to DockerHub
87+
uses: docker/login-action@v3
88+
with:
89+
username: ${{ secrets.DOCKERHUB_USERNAME }}
90+
password: ${{ secrets.DOCKERHUB_TOKEN }}
91+
92+
- name: Extract metadata
93+
id: meta
94+
uses: docker/metadata-action@v5
95+
with:
96+
images: ${{ env.DOCKER_IMAGE }}
97+
tags: |
98+
type=ref,event=branch
99+
type=sha,prefix={{branch}}-
100+
type=semver,pattern={{version}}
101+
type=raw,value=latest,enable={{is_default_branch}}
102+
103+
- name: Build and push
104+
id: build
105+
uses: docker/build-push-action@v5
106+
with:
107+
context: .
108+
platforms: linux/amd64,linux/arm64
109+
push: true
110+
tags: ${{ steps.meta.outputs.tags }}
111+
labels: ${{ steps.meta.outputs.labels }}
112+
cache-from: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache
113+
cache-to: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache,mode=max
114+
115+
- name: Image digest
116+
run: echo ${{ steps.build.outputs.digest }}
117+
118+
security-scan:
119+
name: Security Scan
120+
needs: build-and-push
121+
runs-on: ubuntu-latest
122+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
123+
124+
steps:
125+
- name: Run Trivy vulnerability scanner
126+
uses: aquasecurity/trivy-action@master
127+
with:
128+
image-ref: ${{ env.DOCKER_IMAGE }}:latest
129+
format: 'sarif'
130+
output: 'trivy-results.sarif'
131+
severity: 'CRITICAL,HIGH'
132+
133+
- name: Upload Trivy results to GitHub Security
134+
uses: github/codeql-action/upload-sarif@v2
135+
if: always()
136+
with:
137+
sarif_file: 'trivy-results.sarif'
138+
139+
- name: Run npm audit
140+
uses: actions/checkout@v4
141+
142+
- name: Setup Node.js
143+
uses: actions/setup-node@v4
144+
with:
145+
node-version: ${{ env.NODE_VERSION }}
146+
147+
- name: NPM Audit
148+
run: |
149+
npm audit --audit-level=high
150+
continue-on-error: true
151+
152+
notify:
153+
name: Notification
154+
needs: [build-and-push, security-scan]
155+
runs-on: ubuntu-latest
156+
if: always()
157+
158+
steps:
159+
- name: Success notification
160+
if: needs.build-and-push.result == 'success'
161+
run: |
162+
echo "✅ Build and deployment successful!"
163+
echo "Image: ${{ env.DOCKER_IMAGE }}:latest"
164+
165+
- name: Failure notification
166+
if: needs.build-and-push.result == 'failure'
167+
run: |
168+
echo "❌ Build or deployment failed!"
169+
exit 1

.gitignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Dependencies
2+
node_modules/
3+
package-lock.json
4+
5+
# Testing
6+
coverage/
7+
.nyc_output/
8+
9+
# Production
10+
build/
11+
dist/
12+
13+
# Misc
14+
.DS_Store
15+
.env
16+
.env.local
17+
.env.development.local
18+
.env.test.local
19+
.env.production.local
20+
21+
# Logs
22+
logs/
23+
*.log
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# IDE
29+
.vscode/
30+
.idea/
31+
*.swp
32+
*.swo
33+
*~
34+
35+
# OS
36+
Thumbs.db

.prettierrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"semi": true,
3+
"trailingComma": "none",
4+
"singleQuote": true,
5+
"printWidth": 80,
6+
"tabWidth": 2
7+
}

Dockerfile

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Stage 1: Builder
2+
FROM node:20-alpine AS builder
3+
4+
WORKDIR /app
5+
6+
# Copier les fichiers de dépendances
7+
COPY package*.json ./
8+
9+
# Installer les dépendances (seulement production)
10+
RUN npm ci --only=production
11+
12+
# Stage 2: Production
13+
FROM node:20-alpine
14+
15+
# Créer un utilisateur non-root
16+
RUN addgroup -g 1001 -S nodejs && \
17+
adduser -S nodejs -u 1001
18+
19+
WORKDIR /app
20+
21+
# Copier les dépendances depuis le builder
22+
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
23+
24+
# Copier le code source
25+
COPY --chown=nodejs:nodejs src ./src
26+
COPY --chown=nodejs:nodejs package*.json ./
27+
28+
# Exposer le port
29+
EXPOSE 3000
30+
31+
# Utiliser l'utilisateur non-root
32+
USER nodejs
33+
34+
# Health check
35+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
36+
CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
37+
38+
# Variables d'environnement
39+
ENV NODE_ENV=production \
40+
PORT=3000
41+
42+
# Commande de démarrage
43+
CMD ["node", "src/server.js"]

docker-compose.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
version: '3.8'
2+
3+
services:
4+
app:
5+
build: .
6+
ports:
7+
- "3000:3000"
8+
environment:
9+
- NODE_ENV=production
10+
- PORT=3000
11+
healthcheck:
12+
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
13+
interval: 30s
14+
timeout: 3s
15+
retries: 3
16+
start_period: 5s
17+
restart: unless-stopped

eslint.config.mjs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { defineConfig } from "eslint/config";
2+
import globals from "globals";
3+
import path from "node:path";
4+
import { fileURLToPath } from "node:url";
5+
import js from "@eslint/js";
6+
import { FlatCompat } from "@eslint/eslintrc";
7+
8+
const __filename = fileURLToPath(import.meta.url);
9+
const __dirname = path.dirname(__filename);
10+
const compat = new FlatCompat({
11+
baseDirectory: __dirname,
12+
recommendedConfig: js.configs.recommended,
13+
allConfig: js.configs.all
14+
});
15+
16+
export default defineConfig([{
17+
extends: compat.extends("eslint:recommended"),
18+
19+
languageOptions: {
20+
globals: {
21+
...globals.node,
22+
...globals.jest,
23+
},
24+
25+
ecmaVersion: 12,
26+
sourceType: "commonjs",
27+
},
28+
29+
rules: {
30+
indent: ["error", 2],
31+
"linebreak-style": ["error", "unix"],
32+
quotes: ["error", "single"],
33+
semi: ["error", "always"],
34+
"no-console": "off",
35+
},
36+
}]);

jest.config.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
coverageDirectory: 'coverage',
4+
collectCoverageFrom: [
5+
'src/**/*.js',
6+
'!src/server.js'
7+
],
8+
coverageThreshold: {
9+
global: {
10+
branches: 80,
11+
functions: 80,
12+
lines: 80,
13+
statements: 80
14+
}
15+
},
16+
testMatch: [
17+
'**/specs/**/*.spec.js'
18+
],
19+
verbose: true
20+
};

package.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "nodejs-k8s-app",
3+
"version": "1.0.0",
4+
"description": "Application Node.js déployée sur Kubernetes",
5+
"main": "src/server.js",
6+
"scripts": {
7+
"start": "node src/server.js",
8+
"dev": "nodemon src/server.js",
9+
"test": "jest --coverage --verbose",
10+
"test:watch": "jest --watch",
11+
"lint": "eslint src/ specs/",
12+
"lint:fix": "eslint src/ specs/ --fix",
13+
"format": "prettier --write \"src/**/*.js\" \"specs/**/*.js\""
14+
},
15+
"keywords": ["nodejs", "express", "kubernetes", "docker"],
16+
"author": "Votre Nom",
17+
"license": "MIT",
18+
"engines": {
19+
"node": ">=22.0.0",
20+
"npm": ">=9.0.0"
21+
},
22+
"dependencies": {
23+
"cors": "^2.8.5",
24+
"express": "^5.1.0",
25+
"helmet": "^8.1.0",
26+
"morgan": "^1.10.1"
27+
},
28+
"devDependencies": {
29+
"eslint": "^9.39.1",
30+
"jest": "^30.2.0",
31+
"nodemon": "^3.1.10",
32+
"prettier": "^3.6.2",
33+
"supertest": "^7.1.4"
34+
}
35+
}

0 commit comments

Comments
 (0)