fix: move DB seeding from bootstrap script to Java ApplicationRunner
All checks were successful
CI / build (push) Successful in 40s
CI / docker (push) Successful in 33s
SonarQube Analysis / sonarqube (push) Successful in 1m21s

The bootstrap script runs before cameleer-saas (Flyway), so tenant
tables don't exist yet. Moved DB seeding to BootstrapDataSeeder
ApplicationRunner which runs after Flyway migrations complete.
Reads bootstrap JSON and creates tenant/environment/license if missing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-05 02:55:43 +02:00
parent 827e388349
commit 00ee8876c1
2 changed files with 126 additions and 37 deletions

View File

@@ -0,0 +1,123 @@
package net.siegeln.cameleer.saas.config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.siegeln.cameleer.saas.tenant.TenantEntity;
import net.siegeln.cameleer.saas.tenant.TenantRepository;
import net.siegeln.cameleer.saas.tenant.TenantStatus;
import net.siegeln.cameleer.saas.tenant.Tier;
import net.siegeln.cameleer.saas.environment.EnvironmentEntity;
import net.siegeln.cameleer.saas.environment.EnvironmentRepository;
import net.siegeln.cameleer.saas.environment.EnvironmentStatus;
import net.siegeln.cameleer.saas.license.LicenseEntity;
import net.siegeln.cameleer.saas.license.LicenseRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.io.File;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
@Component
public class BootstrapDataSeeder implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(BootstrapDataSeeder.class);
private static final String BOOTSTRAP_FILE = "/data/bootstrap/logto-bootstrap.json";
private final TenantRepository tenantRepository;
private final EnvironmentRepository environmentRepository;
private final LicenseRepository licenseRepository;
private final ObjectMapper objectMapper = new ObjectMapper();
public BootstrapDataSeeder(TenantRepository tenantRepository,
EnvironmentRepository environmentRepository,
LicenseRepository licenseRepository) {
this.tenantRepository = tenantRepository;
this.environmentRepository = environmentRepository;
this.licenseRepository = licenseRepository;
}
@Override
public void run(ApplicationArguments args) {
File file = new File(BOOTSTRAP_FILE);
if (!file.exists()) {
log.info("No bootstrap file found at {} — skipping data seeding", BOOTSTRAP_FILE);
return;
}
try {
JsonNode config = objectMapper.readTree(file);
String orgId = getField(config, "organizationId");
String tenantName = getField(config, "tenantName");
String tenantSlug = getField(config, "tenantSlug");
String bootstrapToken = getField(config, "bootstrapToken");
if (orgId == null || tenantSlug == null) {
log.info("Bootstrap file missing organizationId or tenantSlug — skipping");
return;
}
// Check if tenant already exists
if (tenantRepository.existsBySlug(tenantSlug)) {
log.info("Tenant '{}' already exists — skipping bootstrap seeding", tenantSlug);
return;
}
log.info("Seeding bootstrap tenant '{}'...", tenantSlug);
// Create tenant
TenantEntity tenant = new TenantEntity();
tenant.setName(tenantName != null ? tenantName : "Example Tenant");
tenant.setSlug(tenantSlug);
tenant.setTier(Tier.LOW);
tenant.setStatus(TenantStatus.ACTIVE);
tenant.setLogtoOrgId(orgId);
tenant = tenantRepository.save(tenant);
log.info("Created tenant: {} ({})", tenant.getSlug(), tenant.getId());
// Create default environment
EnvironmentEntity env = new EnvironmentEntity();
env.setTenantId(tenant.getId());
env.setSlug("default");
env.setDisplayName("Default");
env.setBootstrapToken(bootstrapToken != null ? bootstrapToken : "default-bootstrap-token");
env.setStatus(EnvironmentStatus.ACTIVE);
environmentRepository.save(env);
log.info("Created default environment for tenant '{}'", tenantSlug);
// Create license
LicenseEntity license = new LicenseEntity();
license.setTenantId(tenant.getId());
license.setTier("LOW");
license.setFeatures(Map.of(
"topology", true,
"lineage", false,
"correlation", false,
"debugger", false,
"replay", false
));
license.setLimits(Map.of(
"max_agents", 3,
"retention_days", 7,
"max_environments", 1
));
license.setIssuedAt(Instant.now());
license.setExpiresAt(Instant.now().plus(365, ChronoUnit.DAYS));
license.setToken("bootstrap-license");
licenseRepository.save(license);
log.info("Created license for tenant '{}'", tenantSlug);
log.info("Bootstrap data seeding complete.");
} catch (Exception e) {
log.error("Failed to seed bootstrap data: {}", e.getMessage(), e);
}
}
private String getField(JsonNode node, String field) {
return node.has(field) ? node.get(field).asText() : null;
}
}