diff --git a/README.md b/README.md index 04995dc..26f7770 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ -# Gestión de secretos en distintos entornos (Java + Docker + CI/CD) +# Configuración de entornos (Java + Docker + CI/CD) -Este documento resume cómo manejar de manera segura credenciales y secretos en distintos entornos: desarrollo local, Eclipse, Docker, docker-compose y CI/CD (GitHub Actions). +Este documento resume cómo gestionar la configuración y los secretos de una aplicación en distintos entornos: desarrollo local, Eclipse, Docker, docker-compose y CI/CD (GitHub Actions). --- ## 1. Desarrollo local (Java) -Leer secretos usando `System.getenv()`: +Leer la configuración usando `System.getenv()`: ```java +String host = System.getenv("DB_HOST"); +String port = System.getenv("DB_PORT"); +String name = System.getenv("DB_NAME"); String user = System.getenv("DB_USER"); String password = System.getenv("DB_PASSWORD"); @@ -21,12 +24,18 @@ if (user == null || password == null) { **Windows (PowerShell):** ```powershell +$env:DB_HOST="localhost" +$env:DB_PORT="3306" +$env:DB_NAME="mapa_mundi" $env:DB_USER="mi_usuario" $env:DB_PASSWORD="mi_password" ``` **Linux / Mac / WSL:** ```bash +export DB_HOST=localhost +export DB_PORT=3306 +export DB_NAME=mapa_mundi export DB_USER=mi_usuario export DB_PASSWORD=mi_password ``` @@ -39,26 +48,34 @@ export DB_PASSWORD=mi_password 2. Selecciona tu aplicación → pestaña **Environment** 3. Añade las variables: ``` + Name: DB_HOST Value: localhost + Name: DB_PORT Value: 3306 + Name: DB_NAME Value: mapa_mundi Name: DB_USER Value: mi_usuario Name: DB_PASSWORD Value: mi_password ``` 4. Apply → Run -> Esto hace que `System.getenv("DB_USER")` funcione al ejecutar la app desde Eclipse. +> Esto hace que `System.getenv("DB_HOST")` funcione al ejecutar la app desde Eclipse. --- ## 2. Dockerfile -Nunca incluir secretos directamente. Solo declarar variables vacías como documentación: +Nunca incluir secretos directamente. Declarar todas las variables vacías como documentación: ```dockerfile -# Documentación de variables esperadas en runtime +# Configuración (no sensible) +ENV DB_HOST="" +ENV DB_PORT="" +ENV DB_NAME="" + +# Secretos ENV DB_USER="" ENV DB_PASSWORD="" ``` -> La aplicación sigue leyendo los valores con `System.getenv()` en tiempo de ejecución. Los secretos reales se pasan al contenedor. +> Los valores reales se pasan al contenedor en tiempo de ejecución. La aplicación los lee con `System.getenv()`. --- @@ -66,13 +83,25 @@ ENV DB_PASSWORD="" **Opción A — Variables en línea:** ```bash -docker run -e DB_USER=mi_usuario -e DB_PASSWORD=mi_password -p 8080:8080 mi-imagen +docker run \ + -e DB_HOST=localhost \ + -e DB_PORT=3306 \ + -e DB_NAME=mapa_mundi \ + -e DB_USER=mi_usuario \ + -e DB_PASSWORD=mi_password \ + -p 8080:8080 mi-imagen ``` **Opción B — Archivo `.env` (recomendado):** Archivo `.env`: ``` +# Configuración (no sensible) +DB_HOST=mysql.vdlp +DB_PORT=3306 +DB_NAME=mapa_mundi + +# Secretos DB_USER=mi_usuario DB_PASSWORD=mi_password ``` @@ -89,6 +118,12 @@ docker run --env-file .env -p 8080:8080 mi-imagen Archivo `.env`: ``` +# Configuración (no sensible) +DB_HOST=mysql.vdlp +DB_PORT=3306 +DB_NAME=mapa_mundi + +# Secretos DB_USER=mi_usuario DB_PASSWORD=mi_password DB_ROOT_PASSWORD=root @@ -132,7 +167,10 @@ volumes: ## 5. CI/CD — GitHub Actions -Definir los secretos en el repositorio: **Settings → Secrets and variables → Actions**. +Definir en el repositorio (**Settings → Secrets and variables → Actions**): + +- **Variables** (no sensibles): `DB_HOST`, `DB_PORT`, `DB_NAME` → en la pestaña *Variables*. +- **Secretos**: `DB_USER`, `DB_PASSWORD` → en la pestaña *Secrets*. ```yaml name: Build & Deploy @@ -151,9 +189,12 @@ jobs: - name: Build Docker image run: docker build -t mi-app . - - name: Run container with secrets + - name: Run container run: | docker run \ + -e DB_HOST=${{ vars.DB_HOST }} \ + -e DB_PORT=${{ vars.DB_PORT }} \ + -e DB_NAME=${{ vars.DB_NAME }} \ -e DB_USER=${{ secrets.DB_USER }} \ -e DB_PASSWORD=${{ secrets.DB_PASSWORD }} \ mi-app @@ -168,12 +209,26 @@ jobs: - No subir `.env` al repositorio. - No hardcodear credenciales en `config.properties` ni en el código. - No incluir secretos en la imagen Docker. -- Documentar las variables necesarias sin poner valores reales (README, Dockerfile vacío). +- Documentar las variables necesarias sin valores reales (README, Dockerfile vacío). +- Separar visualmente configuración y secretos en el `.env`. - Para producción avanzada: considerar **Secret Manager** (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault). --- -## 7. Tabla comparativa por entorno +## 7. Variables: configuración vs secretos + +| Variable | Ejemplo | ¿Es secreto? | +|---|---|---| +| `DB_HOST` | `mysql.vdlp` | No | +| `DB_PORT` | `3306` | No | +| `DB_NAME` | `mapa_mundi` | No | +| `DB_USER` | `mi_usuario` | Sí | +| `DB_PASSWORD` | `mi_password` | Sí | +| `DB_ROOT_PASSWORD` | `root` | Sí | + +--- + +## 8. Tabla comparativa por entorno | Entorno | Mecanismo | Dónde se definen | ¿Se sube al repo? | |---|---|---|---| @@ -182,8 +237,4 @@ jobs: | Dockerfile | `ENV` (vacío) | El propio Dockerfile | Sí (sin valores) | | Docker runtime | `-e` / `--env-file` | Línea de comandos / `.env` | No | | Docker Compose | `env_file` | `.env` | No | -| GitHub Actions | `secrets.*` | Settings del repositorio | No | - - ---- -Fuentes: [ChatGPT](https://chat.openai.com) + [Claude](https://claude.ai) \ No newline at end of file +| GitHub Actions | `vars.*` / `secrets.*` | Settings del repositorio | No | \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3dc0b54..8e9a855 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,12 @@ spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-jdbc + + org.springframework.boot diff --git a/src/main/java/org/lapaloma/mapamundi/controller/ContinenteController.java b/src/main/java/org/lapaloma/mapamundi/controller/ContinenteController.java index d602e82..f66bfdd 100644 --- a/src/main/java/org/lapaloma/mapamundi/controller/ContinenteController.java +++ b/src/main/java/org/lapaloma/mapamundi/controller/ContinenteController.java @@ -20,8 +20,13 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api/continentes") public class ContinenteController { - private final ContinenteService continenteService = new ContinenteService(); + private final ContinenteService continenteService; + // Spring inyecta automáticamente el service con su DAO + public ContinenteController(ContinenteService continenteService) { + this.continenteService = continenteService; + } + // GET /api/continentes - listar todos los continentes @GetMapping public List getAll() { diff --git a/src/main/java/org/lapaloma/mapamundi/dao/impl/ContinenteDaoJDBC.java b/src/main/java/org/lapaloma/mapamundi/dao/impl/ContinenteDaoJDBC.java index 3e6114b..04f6a4a 100644 --- a/src/main/java/org/lapaloma/mapamundi/dao/impl/ContinenteDaoJDBC.java +++ b/src/main/java/org/lapaloma/mapamundi/dao/impl/ContinenteDaoJDBC.java @@ -7,172 +7,181 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import javax.sql.DataSource; + import org.lapaloma.mapamundi.dao.IContinenteDAO; -import org.lapaloma.mapamundi.gestores.GestorConexionJDBC; import org.lapaloma.mapamundi.vo.Continente; +import org.springframework.stereotype.Repository; +@Repository public class ContinenteDaoJDBC implements IContinenteDAO { + private final DataSource dataSource; - @Override - public Continente obtenerContinentePorClave(String codigo) { - Continente continente = null; + // Spring inyecta el DataSource configurado automáticamente + public ContinenteDaoJDBC(DataSource dataSource) { + this.dataSource = dataSource; + } - String sentenciaSQL = """ - SELECT * FROM T_CONTINENTE - WHERE codigo=? - """; + @Override + public Continente obtenerContinentePorClave(String codigo) { + Continente continente = null; - try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); - PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { + String sentenciaSQL = """ + SELECT * FROM T_CONTINENTE + WHERE codigo=? + """; - sentenciaJDBCPreparada.setString(1, codigo); - System.out.println(sentenciaJDBCPreparada); + try (Connection conexion = dataSource.getConnection(); + PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { - ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery(); + sentenciaJDBCPreparada.setString(1, codigo); + System.out.println(sentenciaJDBCPreparada); - if (resultadoSentencia.next()) { - continente = getLineaFromResultSet(resultadoSentencia); - } + ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery(); - } catch (Exception e) { - e.printStackTrace(); - } + if (resultadoSentencia.next()) { + continente = getLineaFromResultSet(resultadoSentencia); + } - return continente; - } + } catch (Exception e) { + e.printStackTrace(); + } - @Override - public void actualizarContinente(Continente continente) { + return continente; + } - String sentenciaSQL = """ - UPDATE T_CONTINENTE - SET nombre_continente=? - WHERE codigo=? - """; + @Override + public void actualizarContinente(Continente continente) { - try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); - PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { + String sentenciaSQL = """ + UPDATE T_CONTINENTE + SET nombre_continente=? + WHERE codigo=? + """; - sentenciaJDBCPreparada.setString(1, continente.getNombre()); - sentenciaJDBCPreparada.setString(2, continente.getCodigo()); + try (Connection conexion = dataSource.getConnection(); + PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { - System.out.println(sentenciaJDBCPreparada); + sentenciaJDBCPreparada.setString(1, continente.getNombre()); + sentenciaJDBCPreparada.setString(2, continente.getCodigo()); - sentenciaJDBCPreparada.executeUpdate(); + System.out.println(sentenciaJDBCPreparada); - } catch (Exception e) { - e.printStackTrace(); - } + sentenciaJDBCPreparada.executeUpdate(); - } + } catch (Exception e) { + e.printStackTrace(); + } - @Override - public void crearContinente(Continente continente) { + } - String sentenciaSQL = """ - INSERT INTO T_CONTINENTE (codigo, nombre_continente) - VALUES (?, ?) - """; + @Override + public void crearContinente(Continente continente) { - try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); - PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { + String sentenciaSQL = """ + INSERT INTO T_CONTINENTE (codigo, nombre_continente) + VALUES (?, ?) + """; - sentenciaJDBCPreparada.setString(1, continente.getCodigo()); - sentenciaJDBCPreparada.setString(2, continente.getNombre()); + try (Connection conexion = dataSource.getConnection(); + PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { - System.out.println(sentenciaJDBCPreparada); + sentenciaJDBCPreparada.setString(1, continente.getCodigo()); + sentenciaJDBCPreparada.setString(2, continente.getNombre()); - sentenciaJDBCPreparada.executeUpdate(); + System.out.println(sentenciaJDBCPreparada); - } catch (Exception e) { - e.printStackTrace(); - } - } + sentenciaJDBCPreparada.executeUpdate(); - @Override - public void borrarContinente(Continente continente) { + } catch (Exception e) { + e.printStackTrace(); + } + } - String sentenciaSQL = """ - DELETE FROM T_CONTINENTE - WHERE codigo=? - """; + @Override + public void borrarContinente(Continente continente) { - try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); - PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { + String sentenciaSQL = """ + DELETE FROM T_CONTINENTE + WHERE codigo=? + """; - sentenciaJDBCPreparada.setString(1, continente.getCodigo()); + try (Connection conexion = dataSource.getConnection(); + PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { - sentenciaJDBCPreparada.executeUpdate(); + sentenciaJDBCPreparada.setString(1, continente.getCodigo()); - } catch (Exception e) { - e.printStackTrace(); - } - } + sentenciaJDBCPreparada.executeUpdate(); - @Override - public List obtenerListaContinentes() { + } catch (Exception e) { + e.printStackTrace(); + } + } - List lista = new ArrayList<>(); + @Override + public List obtenerListaContinentes() { - String sentenciaSQL = """ - SELECT * FROM T_CONTINENTE - """; + List lista = new ArrayList<>(); - try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); - PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { + String sentenciaSQL = """ + SELECT * FROM T_CONTINENTE + """; - System.out.println(sentenciaJDBCPreparada); + try (Connection conexion = dataSource.getConnection(); + PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { - ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery(); + System.out.println(sentenciaJDBCPreparada); - while (resultadoSentencia.next()) { - lista.add(getLineaFromResultSet(resultadoSentencia)); - } + ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery(); - } catch (Exception e) { - e.printStackTrace(); - } + while (resultadoSentencia.next()) { + lista.add(getLineaFromResultSet(resultadoSentencia)); + } - return lista; - } + } catch (Exception e) { + e.printStackTrace(); + } - @Override - public List obtenerContinentePorNombre(String nombre) { + return lista; + } - List lista = new ArrayList<>(); + @Override + public List obtenerContinentePorNombre(String nombre) { - String sentenciaSQL = """ - SELECT * FROM T_CONTINENTE - WHERE nombre_continente LIKE ? - """; + List lista = new ArrayList<>(); - try (Connection conexion = GestorConexionJDBC.getConexionSGDB(); - PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { + String sentenciaSQL = """ + SELECT * FROM T_CONTINENTE + WHERE nombre_continente LIKE ? + """; - sentenciaJDBCPreparada.setString(1, "%" + nombre + "%"); + try (Connection conexion = dataSource.getConnection(); + PreparedStatement sentenciaJDBCPreparada = conexion.prepareStatement(sentenciaSQL);) { - System.out.println(sentenciaJDBCPreparada); + sentenciaJDBCPreparada.setString(1, "%" + nombre + "%"); - ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery(); + System.out.println(sentenciaJDBCPreparada); - while (resultadoSentencia.next()) { - lista.add(getLineaFromResultSet(resultadoSentencia)); - } + ResultSet resultadoSentencia = sentenciaJDBCPreparada.executeQuery(); - } catch (Exception e) { - e.printStackTrace(); - } + while (resultadoSentencia.next()) { + lista.add(getLineaFromResultSet(resultadoSentencia)); + } - return lista; - } + } catch (Exception e) { + e.printStackTrace(); + } - private Continente getLineaFromResultSet(ResultSet resultadoSentencia) throws SQLException { + return lista; + } - Continente continente = new Continente(); + private Continente getLineaFromResultSet(ResultSet resultadoSentencia) throws SQLException { - continente.setCodigo(resultadoSentencia.getString("codigo")); - continente.setNombre(resultadoSentencia.getString("nombre_continente")); + Continente continente = new Continente(); - return continente; - } + continente.setCodigo(resultadoSentencia.getString("codigo")); + continente.setNombre(resultadoSentencia.getString("nombre_continente")); + + return continente; + } } \ No newline at end of file diff --git a/src/main/java/org/lapaloma/mapamundi/gestores/GestorConexionJDBC.java b/src/main/java/org/lapaloma/mapamundi/gestores/GestorConexionJDBC.java deleted file mode 100644 index c1b6020..0000000 --- a/src/main/java/org/lapaloma/mapamundi/gestores/GestorConexionJDBC.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.lapaloma.mapamundi.gestores; - -import java.sql.Connection; -import java.sql.DriverManager; - -public class GestorConexionJDBC { - - // Evita que pueda construirse un objeto de la clase. - private GestorConexionJDBC() { - } - - public static Connection getConexionSGDB() throws Exception { - Connection conexionSGDB = null; - - // Datos URL - String urlBBDD = GestorFicheroConfiguracion.obtenerValor("jdbc.url"); - - String usuario = System.getenv("DB_USER"); - String contrasenya = System.getenv("DB_PASSWORD"); - - String claseDriver = GestorFicheroConfiguracion.obtenerValor("jdbc.driver"); - Class.forName(claseDriver); - - conexionSGDB = DriverManager.getConnection(urlBBDD, usuario, contrasenya); - - return conexionSGDB; - } - -} diff --git a/src/main/java/org/lapaloma/mapamundi/service/ContinenteService.java b/src/main/java/org/lapaloma/mapamundi/service/ContinenteService.java index 7305839..50e5aa1 100644 --- a/src/main/java/org/lapaloma/mapamundi/service/ContinenteService.java +++ b/src/main/java/org/lapaloma/mapamundi/service/ContinenteService.java @@ -6,14 +6,21 @@ package org.lapaloma.mapamundi.service; import java.util.List; import org.lapaloma.mapamundi.dao.IContinenteDAO; -import org.lapaloma.mapamundi.dao.impl.ContinenteDaoJDBC; import org.lapaloma.mapamundi.excepcion.ContinenteNoEncontradoException; import org.lapaloma.mapamundi.vo.Continente; +import org.springframework.stereotype.Service; +@Service public class ContinenteService { - IContinenteDAO continenteDAO = new ContinenteDaoJDBC(); + private final IContinenteDAO continenteDAO; + // Spring inyecta el DAO automáticamente + public ContinenteService(IContinenteDAO continenteDAO) { + this.continenteDAO = continenteDAO; + } + + public Continente obtenerContinentePorClave(String codigo) { @@ -23,9 +30,6 @@ public class ContinenteService { Continente continente = continenteDAO.obtenerContinentePorClave(codigo); - // Esta línea simula un error de negocio, ignorando lo que devuelve el DAO - continente = null; - if (continente == null) { throw new ContinenteNoEncontradoException( @@ -40,6 +44,9 @@ public class ContinenteService { List lista = continenteDAO.obtenerListaContinentes(); + // Esto provoca error + //lista = null; + if (lista == null || lista.isEmpty()) { throw new RuntimeException("No hay continentes disponibles"); } @@ -55,6 +62,9 @@ public class ContinenteService { List lista = continenteDAO.obtenerContinentePorNombre(nombre); + // Al comentar este código desaparece el error + lista =null; + if (lista == null || lista.isEmpty()) { throw new ContinenteNoEncontradoException( "No existen continentes con nombre: " + nombre diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..2f3577f --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,8 @@ +spring.datasource.url=${DB_URL} +spring.datasource.username=${DB_USER} +spring.datasource.password=${DB_PASSWORD} +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +# Opcional: pool +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=2 \ No newline at end of file diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties deleted file mode 100644 index d41c651..0000000 --- a/src/main/resources/config.properties +++ /dev/null @@ -1,4 +0,0 @@ -# Parámetros de conexión a la base de datos MapaMundi en SGDB MySQL -jdbc.driver = com.mysql.cj.jdbc.Driver -jdbc.url = jdbc:mysql://172.16.0.181:3306/Mapa_Mundi - diff --git a/src/test/java/org/lapaloma/mapamundi/service/ContinenteServiceTest.java b/src/test/java/org/lapaloma/mapamundi/service/ContinenteServiceTest.java index b6d9ea5..e9514f2 100644 --- a/src/test/java/org/lapaloma/mapamundi/service/ContinenteServiceTest.java +++ b/src/test/java/org/lapaloma/mapamundi/service/ContinenteServiceTest.java @@ -21,8 +21,7 @@ class ContinenteServiceTest { @BeforeEach void setUp() { fakeDAO = new FakeContinenteDAO(); - continenteService = new ContinenteService(); - continenteService.continenteDAO = fakeDAO; + continenteService = new ContinenteService(fakeDAO); } // =========================