feat: commit inicial da aplicacao com pipeline
This commit is contained in:
@@ -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
@@ -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
@@ -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.'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user