Compare commits

..

8 Commits

Author SHA1 Message Date
Juan Manuel Bardallo 623b115fe5 Merge pull request 'docs(changelog): add 0.7.4 release notes for DHCP leases API' (#13) from devel into main
ogdhcp/pipeline/head This commit looks good Details
ogdhcp/pipeline/tag This commit looks good Details
ogaquarium/pipeline/head Build started... Details
Reviewed-on: #13
2026-07-31 10:17:57 +02:00
Juan Manuel Bardallo a8ed1b833a docs(changelog): add 0.7.4 release notes for DHCP leases API
ogdhcp/pipeline/head This commit looks good Details
ogaquarium/pipeline/head Build started... Details
2026-07-31 10:12:18 +02:00
Nicolas Arenas 01a834776f Merge branch 'devel'
ogdhcp/pipeline/head This commit looks good Details
2026-05-14 18:17:57 +02:00
Nicolas Arenas 67952b6f8b Update jenkinsfile
ogdhcp/pipeline/head This commit looks good Details
2026-05-14 18:05:20 +02:00
Nicolas Arenas 603d6e00e2 Merge pull request 'Add endpoints to remove leases from subnet and host' (#12) from add_clean_leases into main
ogdhcp/pipeline/head This commit looks good Details
Reviewed-on: #12
2026-04-20 13:47:46 +02:00
Nicolas Arenas ba461ead15 Adds leases hoook to kea config 2026-04-17 20:58:32 +02:00
Nicolas Arenas 54eab1342b Add endpoints to remove leases from subnet and host 2026-04-17 19:53:08 +02:00
Nicolas Arenas 5db1a54779 Add .venv and .vscode to .gitignore
ogdhcp/pipeline/head This commit looks good Details
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 14:46:27 +01:00
6 changed files with 372 additions and 1 deletions

1
.gitignore vendored
View File

@ -47,3 +47,4 @@ debian/files
debian/*.debhelper debian/*.debhelper
debian/*.debhelper.log debian/*.debhelper.log
debian/debhelper-build-stamp debian/debhelper-build-stamp
.vscode/

View File

@ -1,5 +1,10 @@
# Changelog # Changelog
## [0.7.4] - 2026-07-31
### Added
- Añadidos endpoints a la API para eliminar concesiones (leases) a nivel de host y de subred.
- Añadido el hook de leases a la configuración de Kea.
## [0.7.3] - 2025-10-20 ## [0.7.3] - 2025-10-20
### Fixed ### Fixed
- Arregla el código de DNS en el kea cuando se actualiza la información de una subred existente. - Arregla el código de DNS en el kea cuando se actualiza la información de una subred existente.

View File

@ -61,6 +61,19 @@ pipeline {
} }
} }
stage('Generate Changelog (Devel)') {
when {
branch 'devel'
}
steps {
script {
def devName = params.DEV_NAME ? params.DEV_NAME : env.DEFAULT_DEV_NAME
def devEmail = params.DEV_EMAIL ? params.DEV_EMAIL : env.DEFAULT_DEV_EMAIL
generateDebianChangelog(env.BUILD_DIR, devName, devEmail, "devel")
}
}
}
stage('Build') { stage('Build') {
steps { steps {
script { script {
@ -96,6 +109,19 @@ pipeline {
} }
} }
} }
stage ('Publish to Debian Repository (Devel)') {
when {
branch 'devel'
}
agent { label 'debian-repo' }
steps {
script {
def versionPattern = "-${env.BUILD_NUMBER}~devel"
publicarEnAptly('/var/tmp/opengnsys/debian-repo/ogdhcp', 'devel', versionPattern)
}
}
}
} }
post { post {
success { success {
@ -108,6 +134,13 @@ pipeline {
string(name: 'TRIGGERED_BY', value: "${env.JOB_NAME}-${env.BUILD_NUMBER}") string(name: 'TRIGGERED_BY', value: "${env.JOB_NAME}-${env.BUILD_NUMBER}")
] ]
} }
if (env.BRANCH_NAME == 'devel') {
build job: 'Aptly publish develop repository',
wait: false,
parameters: [
string(name: 'TRIGGERED_BY', value: "${env.JOB_NAME}-${env.BUILD_NUMBER}")
]
}
} }
} }
always { always {

View File

@ -2209,6 +2209,261 @@ public function restoreDhcpConfiguration(): JsonResponse
} }
/**
* @OA\Delete(
* path="/ogdhcp/v1/subnets/{subnetId}/leases",
* summary="Elimina todos los leases de una subred",
* @OA\Parameter(
* name="subnetId",
* in="path",
* description="ID de la subred cuyos leases se van a eliminar",
* required=true,
* @OA\Schema(type="integer")
* ),
* @OA\Response(
* response=200,
* description="Leases eliminados correctamente",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="string", example="Leases de la subred eliminados correctamente"),
* @OA\Property(property="message", type="string", example="Leases deleted.")
* )
* ),
* @OA\Response(
* response=404,
* description="No se encontraron leases en la subred",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string", example="No se encontraron leases en la subred con id '44'")
* )
* ),
* @OA\Response(
* response=500,
* description="Error al comunicarse con el agente de control de Kea",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string", example="Error al ejecutar lease4-wipe")
* )
* )
* )
* @Route("/ogdhcp/v1/subnets/{subnetId}/leases", methods={"DELETE"})
*/
public function wipeSubnetLeases(Request $request): JsonResponse
{
$operation = 'wipeSubnetLeases';
$component = 'ogdhcp';
$subnetId = (int) $request->get('subnetId');
try {
$this->logger->info(json_encode([
'severity' => 'INFO',
'operation' => $operation,
'component' => $component,
'params' => ['subnet_id' => $subnetId],
'desc' => 'Iniciando eliminación de leases de la subred.'
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
$response = $this->curlKeaService->executeLeaseCommand('lease4-wipe', ['subnet-id' => $subnetId]);
if (!$response) {
$errorText = "No se obtuvo respuesta del agente de control de Kea al ejecutar lease4-wipe.";
$this->logger->error(json_encode([
'severity' => 'ERROR',
'operation' => $operation,
'component' => $component,
'http_code' => Response::HTTP_INTERNAL_SERVER_ERROR,
'desc' => $errorText,
'params' => ['subnet_id' => $subnetId]
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
return new JsonResponse(['error' => $errorText], Response::HTTP_INTERNAL_SERVER_ERROR);
}
$result = $response[0]['result'];
$text = $response[0]['text'] ?? '';
// result 3 = empty: el comando se ejecutó pero no había leases
if ($result === 3) {
$errorText = "No se encontraron leases en la subred con id '$subnetId'";
$this->logger->info(json_encode([
'severity' => 'INFO',
'operation' => $operation,
'component' => $component,
'http_code' => Response::HTTP_NOT_FOUND,
'desc' => $errorText,
'params' => ['subnet_id' => $subnetId]
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
return new JsonResponse(['error' => $errorText], Response::HTTP_NOT_FOUND);
}
if ($result !== 0) {
$errorText = "Error al eliminar los leases de la subred '$subnetId': $text";
$this->logger->error(json_encode([
'severity' => 'ERROR',
'operation' => $operation,
'component' => $component,
'http_code' => Response::HTTP_INTERNAL_SERVER_ERROR,
'desc' => $errorText,
'params' => ['subnet_id' => $subnetId, 'kea_result' => $result]
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
return new JsonResponse(['error' => $errorText], Response::HTTP_INTERNAL_SERVER_ERROR);
}
$this->logger->info(json_encode([
'severity' => 'INFO',
'operation' => $operation,
'component' => $component,
'http_code' => Response::HTTP_OK,
'desc' => 'Leases de la subred eliminados correctamente.',
'params' => ['subnet_id' => $subnetId]
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
return new JsonResponse([
'success' => "Leases de la subred eliminados correctamente",
'message' => $text
], Response::HTTP_OK);
} catch (Exception $e) {
$errorText = "Error al ejecutar lease4-wipe: " . $e->getMessage();
$this->logger->error(json_encode([
'severity' => 'ERROR',
'operation' => $operation,
'component' => $component,
'http_code' => Response::HTTP_INTERNAL_SERVER_ERROR,
'desc' => $errorText,
'params' => ['subnet_id' => $subnetId]
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
return new JsonResponse(['error' => $errorText], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @OA\Delete(
* path="/ogdhcp/v1/leases/{ipAddress}",
* summary="Elimina el lease de una máquina por su dirección IP",
* @OA\Parameter(
* name="ipAddress",
* in="path",
* description="Dirección IP del lease a eliminar (ej: 192.168.1.11)",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(
* response=200,
* description="Lease eliminado correctamente",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="string", example="Lease eliminado correctamente"),
* @OA\Property(property="message", type="string", example="IPv4 lease deleted.")
* )
* ),
* @OA\Response(
* response=404,
* description="No se encontró un lease para la IP indicada",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string", example="No se encontró un lease para la IP '192.168.1.11'")
* )
* ),
* @OA\Response(
* response=500,
* description="Error al comunicarse con el agente de control de Kea",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string", example="Error al ejecutar lease4-del")
* )
* )
* )
* @Route("/ogdhcp/v1/leases/{ipAddress}", methods={"DELETE"}, requirements={"ipAddress"=".+"})
*/
public function deleteHostLease(Request $request): JsonResponse
{
$operation = 'deleteHostLease';
$component = 'ogdhcp';
$ipAddress = $request->get('ipAddress');
try {
$this->logger->info(json_encode([
'severity' => 'INFO',
'operation' => $operation,
'component' => $component,
'params' => ['ip_address' => $ipAddress],
'desc' => 'Iniciando eliminación del lease del host.'
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
$response = $this->curlKeaService->executeLeaseCommand('lease4-del', ['ip-address' => $ipAddress]);
if (!$response) {
$errorText = "No se obtuvo respuesta del agente de control de Kea al ejecutar lease4-del.";
$this->logger->error(json_encode([
'severity' => 'ERROR',
'operation' => $operation,
'component' => $component,
'http_code' => Response::HTTP_INTERNAL_SERVER_ERROR,
'desc' => $errorText,
'params' => ['ip_address' => $ipAddress]
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
return new JsonResponse(['error' => $errorText], Response::HTTP_INTERNAL_SERVER_ERROR);
}
$result = $response[0]['result'];
$text = $response[0]['text'] ?? '';
// result 3 = empty: no se encontró un lease para esa IP
if ($result === 3) {
$errorText = "No se encontró un lease para la IP '$ipAddress'";
$this->logger->info(json_encode([
'severity' => 'INFO',
'operation' => $operation,
'component' => $component,
'http_code' => Response::HTTP_NOT_FOUND,
'desc' => $errorText,
'params' => ['ip_address' => $ipAddress]
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
return new JsonResponse(['error' => $errorText], Response::HTTP_NOT_FOUND);
}
if ($result !== 0) {
$errorText = "Error al eliminar el lease de la IP '$ipAddress': $text";
$this->logger->error(json_encode([
'severity' => 'ERROR',
'operation' => $operation,
'component' => $component,
'http_code' => Response::HTTP_INTERNAL_SERVER_ERROR,
'desc' => $errorText,
'params' => ['ip_address' => $ipAddress, 'kea_result' => $result]
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
return new JsonResponse(['error' => $errorText], Response::HTTP_INTERNAL_SERVER_ERROR);
}
$this->logger->info(json_encode([
'severity' => 'INFO',
'operation' => $operation,
'component' => $component,
'http_code' => Response::HTTP_OK,
'desc' => 'Lease eliminado correctamente.',
'params' => ['ip_address' => $ipAddress]
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
return new JsonResponse([
'success' => "Lease eliminado correctamente",
'message' => $text
], Response::HTTP_OK);
} catch (Exception $e) {
$errorText = "Error al ejecutar lease4-del: " . $e->getMessage();
$this->logger->error(json_encode([
'severity' => 'ERROR',
'operation' => $operation,
'component' => $component,
'http_code' => Response::HTTP_INTERNAL_SERVER_ERROR,
'desc' => $errorText,
'params' => ['ip_address' => $ipAddress]
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
return new JsonResponse(['error' => $errorText], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
} }

View File

@ -61,6 +61,45 @@ class CurlKeaService
} }
} }
public function executeLeaseCommand($command, $arguments)
{
$apiUrl = getenv('DHCP_IP');
if (!$apiUrl) {
$apiUrl = 'http://localhost:8000/';
}
$requestData = [
'command' => $command,
'service' => ['dhcp4'],
'arguments' => $arguments,
];
$jsonData = json_encode($requestData);
try {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Expect:']);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$output = json_decode($response, true);
if ($httpCode >= 200 && $httpCode < 300) {
return $output;
} else {
$error = curl_error($ch);
throw new Exception($error);
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
public function convertMaskToCIDR($mask) public function convertMaskToCIDR($mask)
{ {
$bits = 0; $bits = 0;

View File

@ -24,6 +24,28 @@ restore_config_if_modified() {
} }
# Añade el hook lease-cmds a kea-dhcp4.conf solo si no está ya presente
ensure_lease_cmds_hook() {
local config="$1"
local lib="$2"
[ -f "$config" ] || return 0
[ -n "$lib" ] || return 0
if grep -q "libdhcp_lease_cmds" "$config"; then
echo "El hook lease-cmds ya está presente en $config, no se añade de nuevo."
return 0
fi
echo "Añadiendo hook lease-cmds a $config..."
local tmp
tmp=$(mktemp)
jq --arg lib "$lib" \
'.Dhcp4["hooks-libraries"] = (.Dhcp4["hooks-libraries"] // []) + [{"library": $lib}]' \
"$config" > "$tmp" && mv "$tmp" "$config"
chown _kea:_kea "$config"
}
KEA_CTRL_AGENT_CONF="/etc/kea/kea-ctrl-agent.conf" KEA_CTRL_AGENT_CONF="/etc/kea/kea-ctrl-agent.conf"
PUBLIC_DIR=/opt/opengnsys/ogdhcp/api/public PUBLIC_DIR=/opt/opengnsys/ogdhcp/api/public
db_get opengnsys/ogdhcp_interfaces db_get opengnsys/ogdhcp_interfaces
@ -95,8 +117,18 @@ EOF
echo "Configurando kea-dhcp4..." echo "Configurando kea-dhcp4..."
IFS=',' read -r -a INTERFACES <<< "$OGDHCP_INTERFACES" IFS=',' read -r -a INTERFACES <<< "$OGDHCP_INTERFACES"
KEA_CONFIG="/etc/kea/kea-dhcp4.conf" KEA_CONFIG="/etc/kea/kea-dhcp4.conf"
# Detectar la ruta de libdhcp_lease_cmds.so (varía según arquitectura)
LEASE_CMDS_LIB=$(find /usr/lib -name "libdhcp_lease_cmds.so" 2>/dev/null | head -1)
if [ -z "$LEASE_CMDS_LIB" ]; then
echo "Advertencia: no se encontró libdhcp_lease_cmds.so. El hook lease-cmds no estará activo."
else
echo "Librería lease-cmds encontrada en: $LEASE_CMDS_LIB"
fi
if [ -e "$KEA_CONFIG" ]; then if [ -e "$KEA_CONFIG" ]; then
dpkg-divert --package ogdhcp --divert "$KEA_CONFIG.dpkg-dist" --rename "$KEA_CONFIG" dpkg-divert --package ogdhcp --divert "$KEA_CONFIG.dpkg-dist" --rename "$KEA_CONFIG"
# El fichero se genera desde cero, no hay riesgo de duplicación
cat > "$KEA_CONFIG" <<EOF cat > "$KEA_CONFIG" <<EOF
{ {
"Dhcp4": { "Dhcp4": {
@ -128,6 +160,8 @@ EOF
} }
} }
EOF EOF
# Añadir el hook tras generar el fichero (la función verifica que no esté ya)
ensure_lease_cmds_hook "$KEA_CONFIG" "$LEASE_CMDS_LIB"
fi fi
else else
@ -137,6 +171,10 @@ EOF
restore_config_if_modified "/opt/opengnsys/ogdhcp/api/.env" restore_config_if_modified "/opt/opengnsys/ogdhcp/api/.env"
restore_config_if_modified "/etc/nginx/sites-available/ogdhcp.conf" restore_config_if_modified "/etc/nginx/sites-available/ogdhcp.conf"
restore_config_if_modified "/etc/php/$PHP_VERSION/fpm/pool.d/ogdhcp.conf" restore_config_if_modified "/etc/php/$PHP_VERSION/fpm/pool.d/ogdhcp.conf"
# En upgrade: añadir el hook si se instaló kea-hook-lease-cmds y no estaba configurado
LEASE_CMDS_LIB=$(find /usr/lib -name "libdhcp_lease_cmds.so" 2>/dev/null | head -1)
ensure_lease_cmds_hook "/etc/kea/kea-dhcp4.conf" "$LEASE_CMDS_LIB"
fi fi
echo ">>> Configuración de ogdhcp completada." echo ">>> Configuración de ogdhcp completada."
;; ;;