feat: commit inicial da aplicacao com pipeline

This commit is contained in:
2026-06-29 23:04:57 -03:00
parent 87fd60e82f
commit e9bb8d83b0
7 changed files with 310 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
# Arquivos que NÃO devem ser copiados para dentro da imagem Docker.
# Funciona como um .gitignore, mas para o "docker build".
node_modules
npm-debug.log
# Arquivos de teste (não precisamos em produção)
test/
# Arquivos de configuração do pipeline
Jenkinsfile
docker-compose*.yml
# Git
.git
.gitignore
# Docs
README.md
*.md
+39
View File
@@ -0,0 +1,39 @@
# ==========================================================
# Dockerfile — "Receita" para criar a imagem Docker da app
# ==========================================================
# Analogia: assim como você cria uma VM a partir de um template,
# este arquivo define o template (imagem) do container.
#
# O Jenkins executa "docker build" usando este arquivo para
# criar a imagem que será implantada no staging.
# ==========================================================
# 1. Imagem base: Node.js 20 Alpine (versão enxuta do Linux)
# Alpine é uma distro minimalista (~5 MB), ideal para containers
FROM node:20-alpine
# 2. Diretório de trabalho dentro do container
WORKDIR /app
# 3. Copia APENAS os arquivos de dependências primeiro
# Isso aproveita o cache do Docker: se package.json não mudar,
# o "npm ci" não roda de novo (build mais rápido)
COPY package*.json ./
# 4. Instala dependências de produção
# "npm ci" é mais rápido e determinístico que "npm install"
# "--omit=dev" ignora dependências de teste (jest, supertest)
RUN npm ci --omit=dev
# 5. Copia o código-fonte da aplicação
COPY src/ ./src/
# 6. Porta que a aplicação escuta
EXPOSE 3000
# 7. Roda como usuário não-root (segurança)
# A imagem node:alpine já inclui o usuário "node"
USER node
# 8. Comando que inicia a aplicação quando o container sobe
CMD ["node", "src/index.js"]
Vendored
+127
View File
@@ -0,0 +1,127 @@
// ============================================================
// JENKINSFILE — Definição do Pipeline CI/CD
// ============================================================
// Este arquivo é o coração do CI/CD. Ele descreve, em código,
// TODAS as etapas que o Jenkins deve executar quando você faz
// um "git push".
//
// Conceito: "Pipeline as Code" — o pipeline é versionado junto
// com o código da aplicação. Se alguém perguntar "como o deploy
// funciona?", a resposta está aqui.
//
// SINTAXE: Groovy (linguagem do Jenkins). Não precisa saber
// Groovy — a estrutura é declarativa e autoexplicativa.
// ============================================================
pipeline {
// "agent any" = executa em qualquer worker disponível.
// No nosso lab, só temos um (o próprio SRVJENKINS).
agent any
// Variáveis de ambiente disponíveis em todos os stages
environment {
REGISTRY = '10.15.1.211:5000' // Docker Registry (roda no próprio SRVJENKINS)
APP_NAME = 'cicd-demo-app' // Nome da imagem Docker
STAGING_IP = '10.15.1.212' // IP do SRVSTAGING
}
stages {
// ==================================================
// STAGE 1: Instalar Dependências
// ==================================================
// O Jenkins já clonou o repositório automaticamente
// (configurado no Job). Agora instalamos as dependências
// Node.js listadas no package.json.
stage('Install') {
steps {
echo '📦 Instalando dependências (npm install)...'
sh 'npm install'
}
}
// ==================================================
// STAGE 2: Executar Testes
// ==================================================
// Roda os testes automatizados (Jest).
// Se QUALQUER teste falhar, o pipeline PARA AQUI.
// Código quebrado NUNCA chega ao servidor de staging.
stage('Test') {
steps {
echo '🧪 Executando testes automatizados...'
sh 'npm test'
}
}
// ==================================================
// STAGE 3: Construir Imagem Docker
// ==================================================
// Cria a imagem Docker da aplicação usando o Dockerfile.
// Gera duas tags:
// - BUILD_NUMBER: versão específica (ex: :14)
// - latest: sempre aponta para a versão mais recente
stage('Build Image') {
steps {
echo '🐳 Construindo imagem Docker...'
sh "docker build -t ${REGISTRY}/${APP_NAME}:${BUILD_NUMBER} ."
sh "docker tag ${REGISTRY}/${APP_NAME}:${BUILD_NUMBER} ${REGISTRY}/${APP_NAME}:latest"
}
}
// ==================================================
// STAGE 4: Enviar Imagem para o Registry
// ==================================================
// "Push" da imagem para o Docker Registry local.
// O Registry é o "repositório de templates" — o staging
// vai buscar a imagem de lá.
stage('Push to Registry') {
steps {
echo '📤 Enviando imagem para o Registry local...'
sh "docker push ${REGISTRY}/${APP_NAME}:${BUILD_NUMBER}"
sh "docker push ${REGISTRY}/${APP_NAME}:latest"
}
}
// ==================================================
// STAGE 5: Deploy no Staging
// ==================================================
// Conecta via SSH no SRVSTAGING e:
// 1. Puxa a imagem mais recente do Registry
// 2. Para e remove o container antigo (se existir)
// 3. Sobe um novo container com a versão nova
//
// PREREQUISITO: A credencial 'staging-ssh-key' precisa
// estar cadastrada no Jenkins (Manage Jenkins → Credentials)
stage('Deploy to Staging') {
steps {
echo '🚀 Fazendo deploy no SRVSTAGING...'
sshagent(credentials: ['staging-ssh-key']) {
sh """
ssh -o StrictHostKeyChecking=no root@${STAGING_IP} '
docker pull ${REGISTRY}/${APP_NAME}:latest &&
(docker stop ${APP_NAME} || true) &&
(docker rm ${APP_NAME} || true) &&
docker run -d \
--name ${APP_NAME} \
--restart unless-stopped \
-p 3001:3000 \
${REGISTRY}/${APP_NAME}:latest
'
"""
}
}
}
}
// Ações executadas APÓS o pipeline terminar
post {
success {
echo '✅ Pipeline executado com sucesso!'
echo '🌐 App disponível em: http://10.15.1.212:3001'
}
failure {
echo '❌ Pipeline falhou. Verifique os logs acima.'
}
}
}
BIN
View File
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
{
"name": "cicd-demo-app",
"version": "1.0.0",
"description": "Aplicação de demonstração para pipeline CI/CD",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"test": "jest --verbose --forceExit"
},
"dependencies": {
"express": "^4.21.0"
},
"devDependencies": {
"jest": "^29.7.0",
"supertest": "^7.0.0"
}
}
+47
View File
@@ -0,0 +1,47 @@
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// ============================================================
// Aplicação de demonstração CI/CD
// Cada vez que você alterar este arquivo e fizer "git push",
// o Jenkins vai automaticamente testar, construir e implantar.
// ============================================================
// Rota principal — retorna informações da aplicação
app.get('/', (req, res) => {
res.json({
app: 'CI/CD Demo',
versao: '1.0.0',
status: 'rodando',
ambiente: process.env.NODE_ENV || 'development',
timestamp: new Date().toISOString(),
mensagem: 'Pipeline CI/CD funcionando! 🚀'
});
});
// Health check — usado para verificar se a aplicação está saudável
// Em produção, load balancers e orquestradores usam este endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'healthy' });
});
// Rota de informações do sistema
app.get('/info', (req, res) => {
res.json({
node_version: process.version,
platform: process.platform,
uptime_seconds: Math.floor(process.uptime()),
memory_mb: Math.round(process.memoryUsage().rss / 1024 / 1024)
});
});
// Inicia o servidor apenas se NÃO estiver em modo de teste.
// Nos testes, o supertest cria seu próprio servidor temporário.
if (process.env.NODE_ENV !== 'test') {
app.listen(PORT, '0.0.0.0', () => {
console.log(`✅ Servidor rodando em http://0.0.0.0:${PORT}`);
});
}
module.exports = app;
+60
View File
@@ -0,0 +1,60 @@
// ============================================================
// Testes automatizados da aplicação
// O Jenkins executa estes testes a cada push.
// Se QUALQUER teste falhar, o pipeline para e NÃO faz deploy.
// Isso é o "firewall de qualidade" do CI/CD.
// ============================================================
const request = require('supertest');
const app = require('../src/index');
describe('Rota Principal GET /', () => {
test('deve retornar status 200', async () => {
const res = await request(app).get('/');
expect(res.statusCode).toBe(200);
});
test('deve retornar o nome da aplicação', async () => {
const res = await request(app).get('/');
expect(res.body).toHaveProperty('app', 'CI/CD Demo');
});
test('deve retornar status "rodando"', async () => {
const res = await request(app).get('/');
expect(res.body).toHaveProperty('status', 'rodando');
});
test('deve conter um timestamp', async () => {
const res = await request(app).get('/');
expect(res.body).toHaveProperty('timestamp');
});
});
describe('Health Check GET /health', () => {
test('deve retornar status 200', async () => {
const res = await request(app).get('/health');
expect(res.statusCode).toBe(200);
});
test('deve retornar status "healthy"', async () => {
const res = await request(app).get('/health');
expect(res.body).toHaveProperty('status', 'healthy');
});
});
describe('Informações do Sistema GET /info', () => {
test('deve retornar versão do Node.js', async () => {
const res = await request(app).get('/info');
expect(res.statusCode).toBe(200);
expect(res.body).toHaveProperty('node_version');
});
test('deve retornar uptime em segundos', async () => {
const res = await request(app).get('/info');
expect(res.body).toHaveProperty('uptime_seconds');
expect(typeof res.body.uptime_seconds).toBe('number');
});
});