fix: re-provision containers when restart finds them missing
All checks were successful
CI / build (push) Successful in 1m22s
CI / docker (push) Successful in 39s

When Docker containers have been removed (e.g. manual cleanup or image
update), restart now falls back to full re-provisioning instead of
failing with 404. Applies to both vendor and tenant portal restart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-11 08:37:04 +02:00
parent d2caa737b9
commit 6f8b84fb1a
4 changed files with 43 additions and 6 deletions

View File

@@ -9,6 +9,10 @@ import net.siegeln.cameleer.saas.provisioning.ProvisioningProperties;
import net.siegeln.cameleer.saas.provisioning.TenantProvisioner;
import net.siegeln.cameleer.saas.tenant.TenantEntity;
import net.siegeln.cameleer.saas.tenant.TenantService;
import net.siegeln.cameleer.saas.vendor.VendorTenantService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.time.Instant;
@@ -20,25 +24,30 @@ import java.util.UUID;
@Service
public class TenantPortalService {
private static final Logger log = LoggerFactory.getLogger(TenantPortalService.class);
private final TenantService tenantService;
private final LicenseService licenseService;
private final ServerApiClient serverApiClient;
private final LogtoManagementClient logtoClient;
private final TenantProvisioner tenantProvisioner;
private final ProvisioningProperties provisioningProps;
private final VendorTenantService vendorTenantService;
public TenantPortalService(TenantService tenantService,
LicenseService licenseService,
ServerApiClient serverApiClient,
LogtoManagementClient logtoClient,
TenantProvisioner tenantProvisioner,
ProvisioningProperties provisioningProps) {
ProvisioningProperties provisioningProps,
@Lazy VendorTenantService vendorTenantService) {
this.tenantService = tenantService;
this.licenseService = licenseService;
this.serverApiClient = serverApiClient;
this.logtoClient = logtoClient;
this.tenantProvisioner = tenantProvisioner;
this.provisioningProps = provisioningProps;
this.vendorTenantService = vendorTenantService;
}
// --- Inner records ---
@@ -180,9 +189,22 @@ public class TenantPortalService {
public void restartServer() {
TenantEntity tenant = resolveTenant();
if (tenantProvisioner.isAvailable()) {
tenantProvisioner.stop(tenant.getSlug());
if (!tenantProvisioner.isAvailable()) return;
tenantProvisioner.stop(tenant.getSlug());
try {
tenantProvisioner.start(tenant.getSlug());
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("re-provision required")) {
log.info("Containers missing for '{}' — re-provisioning", tenant.getSlug());
tenantProvisioner.remove(tenant.getSlug());
var license = licenseService.getActiveLicense(tenant.getId()).orElse(null);
String token = license != null ? license.getToken() : "";
vendorTenantService.provisionAsync(
tenant.getId(), tenant.getSlug(), tenant.getTier().name(), token, null);
return;
}
throw e;
}
}
}

View File

@@ -73,6 +73,9 @@ public class DockerTenantProvisioner implements TenantProvisioner {
try {
docker.startContainerCmd(serverContainerName(slug)).exec();
docker.startContainerCmd(uiContainerName(slug)).exec();
} catch (NotFoundException e) {
log.warn("Containers for '{}' not found — cannot start (re-provision needed)", slug);
throw new RuntimeException("Containers not found for '" + slug + "' — re-provision required", e);
} catch (Exception e) {
log.error("Failed to start containers for '{}'", slug, e);
throw new RuntimeException("Start failed: " + e.getMessage(), e);

View File

@@ -220,9 +220,21 @@ public class VendorTenantService {
public void restartServer(UUID tenantId) {
TenantEntity tenant = tenantService.getById(tenantId)
.orElseThrow(() -> new IllegalArgumentException("Tenant not found"));
if (tenantProvisioner.isAvailable()) {
tenantProvisioner.stop(tenant.getSlug());
if (!tenantProvisioner.isAvailable()) return;
tenantProvisioner.stop(tenant.getSlug());
try {
tenantProvisioner.start(tenant.getSlug());
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("re-provision required")) {
log.info("Containers missing for '{}' — re-provisioning", tenant.getSlug());
tenantProvisioner.remove(tenant.getSlug());
var license = licenseService.getActiveLicense(tenantId).orElse(null);
String token = license != null ? license.getToken() : "";
provisionAsync(tenantId, tenant.getSlug(), tenant.getTier().name(), token, null);
return;
}
throw e;
}
}

View File

@@ -56,7 +56,7 @@ class TenantPortalServiceTest {
@BeforeEach
void setUp() {
TenantContext.setTenantId(tenantId);
tenantPortalService = new TenantPortalService(tenantService, licenseService, serverApiClient, logtoClient, tenantProvisioner, provisioningProps);
tenantPortalService = new TenantPortalService(tenantService, licenseService, serverApiClient, logtoClient, tenantProvisioner, provisioningProps, null);
}
@AfterEach