Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import org.jboss.logging.Logger;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
* Registry of enabled AWS services based on configuration.
Expand Down Expand Up @@ -80,6 +82,41 @@ public List<String> getEnabledServices() {
return enabled;
}

/**
* Returns all known services with their status: "running" if enabled, "available" if not.
*/
public Map<String, String> getServices() {
Map<String, String> services = new LinkedHashMap<>();
services.put("ssm", status(config.services().ssm().enabled()));
services.put("sqs", status(config.services().sqs().enabled()));
services.put("s3", status(config.services().s3().enabled()));
services.put("dynamodb", status(config.services().dynamodb().enabled()));
services.put("sns", status(config.services().sns().enabled()));
services.put("lambda", status(config.services().lambda().enabled()));
services.put("apigateway", status(config.services().apigateway().enabled()));
services.put("iam", status(config.services().iam().enabled()));
services.put("elasticache", status(config.services().elasticache().enabled()));
services.put("rds", status(config.services().rds().enabled()));
services.put("events", status(config.services().eventbridge().enabled()));
services.put("logs", status(config.services().cloudwatchlogs().enabled()));
services.put("monitoring", status(config.services().cloudwatchmetrics().enabled()));
services.put("secretsmanager", status(config.services().secretsmanager().enabled()));
services.put("apigatewayv2", status(config.services().apigatewayv2().enabled()));
services.put("kinesis", status(config.services().kinesis().enabled()));
services.put("kms", status(config.services().kms().enabled()));
services.put("cognito-idp", status(config.services().cognito().enabled()));
services.put("states", status(config.services().stepfunctions().enabled()));
services.put("cloudformation", status(config.services().cloudformation().enabled()));
services.put("acm", status(config.services().acm().enabled()));
services.put("email", status(config.services().ses().enabled()));
services.put("es", status(config.services().opensearch().enabled()));
return services;
}

private static String status(boolean enabled) {
return enabled ? "running" : "available";
}

public void logEnabledServices() {
LOG.infov("Enabled services: {0}", getEnabledServices());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.github.hectorvent.floci.lifecycle;

import io.github.hectorvent.floci.core.common.ServiceRegistry;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* Internal health endpoint at /_floci/health.
* Returns the Floci version and the status of each enabled service.
* Compatible with the LocalStack /_localstack/health pattern.
*/
@Path("{path:(_floci|_localstack)/health}")
@Produces(MediaType.APPLICATION_JSON)
public class HealthController {

private final ServiceRegistry serviceRegistry;
private final String version;

@Inject
public HealthController(ServiceRegistry serviceRegistry) {
this.serviceRegistry = serviceRegistry;
this.version = resolveVersion();
}

@GET
public Response health() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("services", serviceRegistry.getServices());
result.put("edition", "floci-always-free");
result.put("version", version);

return Response.ok(result).build();
}

private static String resolveVersion() {
String env = System.getenv("FLOCI_VERSION");
if (env != null && !env.isBlank()) {
return env;
}
return "dev";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.github.hectorvent.floci.lifecycle;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.junit.jupiter.api.Assertions.assertEquals;

@QuarkusTest
class HealthControllerIntegrationTest {

private static final ObjectMapper MAPPER = new ObjectMapper();

private static final String EXPECTED_HEALTH_JSON = """
{
"services": {
"ssm": "running",
"sqs": "running",
"s3": "running",
"dynamodb": "running",
"sns": "running",
"lambda": "running",
"apigateway": "running",
"iam": "running",
"elasticache": "running",
"rds": "running",
"events": "running",
"logs": "running",
"monitoring": "running",
"secretsmanager": "running",
"apigatewayv2": "running",
"kinesis": "running",
"kms": "running",
"cognito-idp": "running",
"states": "running",
"cloudformation": "running",
"acm": "running",
"email": "running",
"es": "running"
},
"edition": "floci-always-free",
"version": "dev"
}
""";

@Test
void healthEndpoint_returnsExpectedJson() throws Exception {
String body = given()
.when()
.get("/_floci/health")
.then()
.statusCode(200)
.contentType("application/json")
.extract().body().asString();

assertEquals(MAPPER.readTree(EXPECTED_HEALTH_JSON), MAPPER.readTree(body));
}

@Test
void healthEndpoint_localstackCompatPath() throws Exception {
String body = given()
.when()
.get("/_localstack/health")
.then()
.statusCode(200)
.contentType("application/json")
.extract().body().asString();

assertEquals(MAPPER.readTree(EXPECTED_HEALTH_JSON), MAPPER.readTree(body));
}
}