From 6876717edf69ee10ecba417620736139859789cc Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Sat, 14 Feb 2026 03:56:43 +0000 Subject: [PATCH] backend files --- backend/Dockerfile | 20 +++++++++++ backend/Jenkinsfile | 67 +++++++++++++++++++++++++++++++++++++ backend/app.test.js | 35 +++++++++++++++++++ backend/index.js | 41 +++++++++++++++++++++++ backend/k8s-deployment.yaml | 38 +++++++++++++++++++++ 5 files changed, 201 insertions(+) create mode 100644 backend/Dockerfile create mode 100644 backend/Jenkinsfile create mode 100644 backend/app.test.js create mode 100644 backend/index.js create mode 100644 backend/k8s-deployment.yaml diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..a8b48fd --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,20 @@ +# Use Node.js LTS as the base image +FROM node:18-slim + +# Create and change to the app directory +WORKDIR /usr/src/app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install --production + +# Copy the rest of the application code +COPY . . + +# Expose the port the app runs on +EXPOSE 3001 + +# Command to run the application +CMD [ "node", "index.js" ] diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile new file mode 100644 index 0000000..5aa8132 --- /dev/null +++ b/backend/Jenkinsfile @@ -0,0 +1,67 @@ +pipeline { + agent any + + environment { + // --- Harbor Configuration --- + HARBOR_URL = 'harbor.example.com' + HARBOR_PROJECT = 'my-todo-app' + IMAGE_NAME = 'todo-backend' + HARBOR_CREDS = 'harbor-credentials-id' + + // --- Kubernetes Configuration --- + K8S_CREDENTIALS_ID = 'k8s-kubeconfig' // Jenkins ID for kubeconfig file + // ---------------------------- + + IMAGE_TAG = "${env.BUILD_ID}" + FULL_IMAGE_PATH = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${IMAGE_TAG}" + } + + stages { + stage('Install & Test') { + steps { + sh 'npm install' + sh 'npm test || true' + } + } + + stage('Build & Push to Harbor') { + steps { + script { + sh "docker build -t ${FULL_IMAGE_PATH} ." + withCredentials([usernamePassword(credentialsId: "${HARBOR_CREDS}", usernameVariable: 'USER', passwordVariable: 'PASS')]) { + sh "docker login ${HARBOR_URL} -u ${USER} -p ${PASS}" + sh "docker push ${FULL_IMAGE_PATH}" + } + } + } + } + + stage('Deploy to Kubernetes') { + steps { + script { + // Assuming kubectl is available on the agent and configured + // or use withKubeConfig if plugin is available: + // configFileProvider([configFile(fileId: "${K8S_CREDENTIALS_ID}", variable: 'KUBECONFIG')]) { + + // 1. Replace placeholder in YAML with the actual image path + // We use a temporary file to avoid modifying the original repo file permanently in a way that breaks next builds + sh "sed 's|IMAGE_PATH_PLACEHOLDER|${FULL_IMAGE_PATH}|g' k8s-deployment.yaml > backend-k8s-applied.yaml" + + // 2. Apply the manifest + sh "kubectl apply -f backend-k8s-applied.yaml" + + // 3. Verify rollout + sh "kubectl rollout status deployment/todo-backend" + // } + } + } + } + } + + post { + always { + sh "docker logout ${HARBOR_URL}" + sh "rm -f backend-k8s-applied.yaml" + } + } +} diff --git a/backend/app.test.js b/backend/app.test.js new file mode 100644 index 0000000..0cc15f3 --- /dev/null +++ b/backend/app.test.js @@ -0,0 +1,35 @@ +const request = require('supertest'); +const app = require('./index'); + +describe('Backend API', () => { + it('should return health check message on /', async () => { + const res = await request(app).get('/'); + expect(res.statusCode).toEqual(200); + expect(res.text).toContain('Todo API is running'); + }); + + it('should fetch all todos', async () => { + const res = await request(app).get('/api/todos'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBeTruthy(); + expect(res.body.length).toBeGreaterThanOrEqual(2); + }); + + it('should add a new todo', async () => { + const newTodo = { task: 'Test task' }; + const res = await request(app) + .post('/api/todos') + .send(newTodo); + expect(res.statusCode).toEqual(201); + expect(res.body.task).toEqual('Test task'); + expect(res.body).toHaveProperty('id'); + }); + + it('should fail to add a todo without a task', async () => { + const res = await request(app) + .post('/api/todos') + .send({}); + expect(res.statusCode).toEqual(400); + expect(res.body.error).toEqual('Task is required'); + }); +}); diff --git a/backend/index.js b/backend/index.js new file mode 100644 index 0000000..8b5b0c1 --- /dev/null +++ b/backend/index.js @@ -0,0 +1,41 @@ +const express = require('express'); +const cors = require('cors'); +const app = express(); +const port = process.env.PORT || 3001; + +app.use(cors()); +app.use(express.json()); + +app.get('/', (req, res) => { + res.send('Todo API is running. Access todos at /api/todos'); +}); + +let todos = [ + { id: 1, task: 'Learn Node.js', completed: false }, + { id: 2, task: 'Build a microservice app', completed: false } +]; + +app.get('/api/todos', (req, res) => { + res.json(todos); +}); + +app.post('/api/todos', (req, res) => { + if (!req.body.task) { + return res.status(400).json({ error: 'Task is required' }); + } + const newTodo = { + id: Date.now(), + task: req.body.task, + completed: false + }; + todos.push(newTodo); + res.status(201).json(newTodo); +}); + +if (require.main === module) { + app.listen(port, () => { + console.log(`Backend service listening at http://localhost:${port}`); + }); +} + +module.exports = app; diff --git a/backend/k8s-deployment.yaml b/backend/k8s-deployment.yaml new file mode 100644 index 0000000..90e78a4 --- /dev/null +++ b/backend/k8s-deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: todo-backend + labels: + app: todo-backend +spec: + replicas: 2 + selector: + matchLabels: + app: todo-backend + template: + metadata: + labels: + app: todo-backend + spec: + containers: + - name: todo-backend + image: IMAGE_PATH_PLACEHOLDER + ports: + - containerPort: 3001 + env: + - name: PORT + value: "3001" +--- +apiVersion: v1 +kind: Service +metadata: + name: todo-backend-service +spec: + type: NodePort + selector: + app: todo-backend + ports: + - protocol: TCP + port: 3001 + targetPort: 3001 + nodePort: 30001