Add dry-run provisioning command generation

This commit is contained in:
litoral05
2026-05-05 12:29:33 +01:00
parent c09aff2fcb
commit 6f70cbfe67
4 changed files with 42 additions and 9 deletions
@@ -2,6 +2,7 @@ package com.litoralregas.openvpn.openvpn;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public interface IpAllocationRepository extends JpaRepository<IpAllocation, UUID> { public interface IpAllocationRepository extends JpaRepository<IpAllocation, UUID> {
@@ -11,4 +12,5 @@ public interface IpAllocationRepository extends JpaRepository<IpAllocation, UUID
boolean existsByLanSubnet(String lanSubnet); boolean existsByLanSubnet(String lanSubnet);
boolean existsByVpnIp(String vpnIp); boolean existsByVpnIp(String vpnIp);
Optional<IpAllocation> findByRouterId(UUID routerId);
} }
@@ -113,4 +113,9 @@ public class IpAllocationService {
public void delete(UUID id) { public void delete(UUID id) {
repository.deleteById(id); repository.deleteById(id);
} }
public IpAllocation findByRouterId(UUID routerId) {
return repository.findByRouterId(routerId)
.orElseThrow(() -> new IllegalArgumentException("No IP allocation found for router: " + routerId));
}
} }
@@ -64,4 +64,22 @@ public class OpenVpnService {
return null; return null;
} }
public String buildProvisionCommand(String clientName, String lanSubnet, String vpnIp) {
validateShellSafe(clientName);
validateShellSafe(lanSubnet);
validateShellSafe(vpnIp);
return properties.getToolsPath()
+ "/provision-client.sh "
+ clientName + " "
+ lanSubnet + " "
+ vpnIp;
}
private void validateShellSafe(String value) {
if (value == null || !value.matches("^[a-zA-Z0-9._/-]+$")) {
throw new IllegalArgumentException("Unsafe command value: " + value);
}
}
} }
@@ -3,6 +3,8 @@ package com.litoralregas.openvpn.router;
import com.litoralregas.openvpn.deployment.DeploymentAction; import com.litoralregas.openvpn.deployment.DeploymentAction;
import com.litoralregas.openvpn.deployment.DeploymentResponse; import com.litoralregas.openvpn.deployment.DeploymentResponse;
import com.litoralregas.openvpn.deployment.DeploymentService; import com.litoralregas.openvpn.deployment.DeploymentService;
import com.litoralregas.openvpn.openvpn.IpAllocationService;
import com.litoralregas.openvpn.openvpn.OpenVpnService;
import com.litoralregas.openvpn.ssh.SshService; import com.litoralregas.openvpn.ssh.SshService;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -17,11 +19,15 @@ public class RouterController {
private final RouterService service; private final RouterService service;
private final DeploymentService deploymentService; private final DeploymentService deploymentService;
private final SshService sshService; private final SshService sshService;
private final IpAllocationService ipAllocationService;
private final OpenVpnService openVpnService;
public RouterController(RouterService service, DeploymentService deploymentService, SshService sshService) { public RouterController(RouterService service, DeploymentService deploymentService, SshService sshService, IpAllocationService ipAllocationService, OpenVpnService openVpnService) {
this.service = service; this.service = service;
this.deploymentService = deploymentService; this.deploymentService = deploymentService;
this.sshService = sshService; this.sshService = sshService;
this.ipAllocationService = ipAllocationService;
this.openVpnService = openVpnService;
} }
@GetMapping @GetMapping
@@ -61,20 +67,22 @@ public class RouterController {
try { try {
service.forceStatus(id, RouterStatus.PROVISIONING); service.forceStatus(id, RouterStatus.PROVISIONING);
var result = sshService.executeOnConfiguredVps( var allocation = ipAllocationService.findByRouterId(id);
"echo 'Provisioning router: " + router.getName() + "' && whoami && hostname"
);
if (result.exitCode() != 0) { String command = openVpnService.buildProvisionCommand(
throw new IllegalStateException(result.stderr()); allocation.getClientName(),
} allocation.getLanSubnet(),
allocation.getVpnIp()
);
var finishedDeployment = deploymentService.finishSuccess( var finishedDeployment = deploymentService.finishSuccess(
deployment, deployment,
result.stdout() "DRY RUN ONLY. Would execute: " + command
); );
service.forceStatus(id, RouterStatus.REMOVING); // Keep this as PROVISIONING? No — dry run succeeded but real provision did not happen.
// So we should NOT mark as PROVISIONED yet.
service.forceStatus(id, RouterStatus.PENDING);
return DeploymentResponse.from(finishedDeployment); return DeploymentResponse.from(finishedDeployment);
} catch (Exception exception) { } catch (Exception exception) {