Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ bruno/test-scenarios/**/repos
bruno/test-scenarios/environments
!bruno/test-scenarios/environments/compose.bru
**/.claude/settings.local.json
.idea

# build artifacts
*-results.xml
Expand Down
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,15 @@ dev-down:

.PHONY: dev-teardown
dev-teardown: dev-down
docker service ls \
--filter=label=pgedge.component=postgres \
--format '{{ .ID }}' \
| xargs docker service rm
# remove postgres and supported services
ids=$$(docker service ls -q); \
if [ -n "$$ids" ]; then \
echo "$$ids" \
| xargs docker service inspect \
--format '{{.ID}} {{index .Spec.Labels "pgedge.component"}}' \
| awk '$$2=="postgres" || $$2=="service" {print $$1}' \
| xargs docker service rm; \
fi
docker network ls \
--filter=scope=swarm \
--format '{{ .Name }}' \
Expand Down
72 changes: 71 additions & 1 deletion api/apiv1/design/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
cpuPattern = `^[0-9]+(\.[0-9]{1,3}|m)?$`
postgresVersionPattern = `^\d{2}\.\d{1,2}$`
spockVersionPattern = `^\d{1}$`
serviceVersionPattern = `^(\d+\.\d+\.\d+|latest)$`
)

var HostIDs = g.ArrayOf(Identifier, func() {
Expand Down Expand Up @@ -105,6 +106,66 @@ var DatabaseUserSpec = g.Type("DatabaseUserSpec", func() {
g.Required("username")
})

var ServiceSpec = g.Type("ServiceSpec", func() {
g.Attribute("service_id", Identifier, func() {
g.Description("The unique identifier for this service.")
g.Example("mcp-server")
g.Example("analytics-service")
})
g.Attribute("service_type", g.String, func() {
g.Description("The type of service to run.")
g.Enum("mcp")
g.Example("mcp")
})
g.Attribute("version", g.String, func() {
g.Description("The version of the service in semver format (e.g., '1.0.0') or the literal 'latest'.")
g.Pattern(serviceVersionPattern)
g.Example("1.0.0")
g.Example("1.2.3")
g.Example("latest")
})
g.Attribute("host_ids", HostIDs, func() {
g.Description("The IDs of the hosts that should run this service. One service instance will be created per host.")
g.MinLength(1)
})
g.Attribute("port", g.Int, func() {
g.Description("The port to publish the service on the host. If 0, Docker assigns a random port. If unspecified, no port is published and the service is not accessible from outside the Docker network.")
g.Minimum(0)
g.Maximum(65535)
g.Example(8080)
g.Example(9000)
g.Example(0)
})
g.Attribute("config", g.MapOf(g.String, g.Any), func() {
g.Description("Service-specific configuration. For MCP services, this includes llm_provider, llm_model, and provider-specific API keys.")
g.Example(map[string]any{
"llm_provider": "anthropic",
"llm_model": "claude-sonnet-4-5",
"anthropic_api_key": "sk-ant-...",
})
g.Example(map[string]any{
"llm_provider": "openai",
"llm_model": "gpt-4",
"openai_api_key": "sk-...",
})
})
g.Attribute("cpus", g.String, func() {
g.Description("The number of CPUs to allocate for this service. It can include the SI suffix 'm', e.g. '500m' for 500 millicpus. Defaults to container defaults if unspecified.")
g.Pattern(cpuPattern)
g.Example("1")
g.Example("0.5")
g.Example("500m")
})
g.Attribute("memory", g.String, func() {
g.Description("The amount of memory in SI or IEC notation to allocate for this service. Defaults to container defaults if unspecified.")
g.MaxLength(16)
g.Example("1GiB")
g.Example("512M")
})

g.Required("service_id", "service_type", "version", "host_ids", "config")
})

var BackupRepositorySpec = g.Type("BackupRepositorySpec", func() {
g.Attribute("id", Identifier, func() {
g.Description("The unique identifier of this repository.")
Expand Down Expand Up @@ -425,6 +486,9 @@ var DatabaseSpec = g.Type("DatabaseSpec", func() {
g.Description("The users to create for this database.")
g.MaxLength(16)
})
g.Attribute("services", g.ArrayOf(ServiceSpec), func() {
g.Description("Service instances to run alongside the database (e.g., MCP servers).")
})
g.Attribute("backup_config", BackupConfigSpec, func() {
g.Description("The backup configuration for this database.")
})
Expand Down Expand Up @@ -485,6 +549,9 @@ var Database = g.ResultType("Database", func() {
g.Attribute("instances", g.CollectionOf(Instance), func() {
g.Description("All of the instances in the database.")
})
g.Attribute("service_instances", g.CollectionOf(ServiceInstance), func() {
g.Description("Service instances running alongside this database.")
})
g.Attribute("spec", DatabaseSpec, func() {
g.Description("The user-provided specification for the database.")
})
Expand All @@ -499,6 +566,9 @@ var Database = g.ResultType("Database", func() {
g.Attribute("instances", func() {
g.View("default")
})
g.Attribute("service_instances", func() {
g.View("default")
})
g.Attribute("spec")

g.Example(exampleDatabase)
Expand All @@ -515,7 +585,7 @@ var Database = g.ResultType("Database", func() {
})
})

g.Required("id", "created_at", "updated_at", "state")
g.Required("id", "created_at", "updated_at", "state", "instances", "service_instances")
})

var CreateDatabaseRequest = g.Type("CreateDatabaseRequest", func() {
Expand Down
143 changes: 143 additions & 0 deletions api/apiv1/design/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,146 @@ var Instance = g.ResultType("Instance", func() {

g.Required("id", "host_id", "node_name", "created_at", "updated_at", "state")
})

var PortMapping = g.Type("PortMapping", func() {
g.Description("Port mapping information for a service instance.")
g.Attribute("name", g.String, func() {
g.Description("The name of the port (e.g., 'http', 'web-client').")
g.Example("http")
g.Example("web-client")
})
g.Attribute("container_port", g.Int, func() {
g.Description("The port number inside the container.")
g.Minimum(1)
g.Maximum(65535)
g.Example(8080)
})
g.Attribute("host_port", g.Int, func() {
g.Description("The port number on the host (if port-forwarded).")
g.Minimum(1)
g.Maximum(65535)
g.Example(8080)
})

g.Required("name")
})

var HealthCheckResult = g.Type("HealthCheckResult", func() {
g.Description("Health check result for a service instance.")
g.Attribute("status", g.String, func() {
g.Description("The health status.")
g.Enum("healthy", "unhealthy", "unknown")
g.Example("healthy")
})
g.Attribute("message", g.String, func() {
g.Description("Optional message about the health status.")
g.Example("Service responding normally")
g.Example("Connection refused")
})
g.Attribute("checked_at", g.String, func() {
g.Format(g.FormatDateTime)
g.Description("The time this health check was performed.")
g.Example("2025-01-28T10:00:00Z")
})

g.Required("status", "checked_at")
})

var ServiceInstanceStatus = g.Type("ServiceInstanceStatus", func() {
g.Description("Runtime status information for a service instance.")
g.Attribute("container_id", g.String, func() {
g.Description("The Docker container ID.")
g.Example("a1b2c3d4e5f6")
})
g.Attribute("image_version", g.String, func() {
g.Description("The container image version currently running.")
g.Example("1.0.0")
})
g.Attribute("hostname", g.String, func() {
g.Description("The hostname of the service instance.")
g.Example("mcp-server-host-1.internal")
})
g.Attribute("ipv4_address", g.String, func() {
g.Description("The IPv4 address of the service instance.")
g.Format(g.FormatIPv4)
g.Example("10.0.1.5")
})
g.Attribute("ports", g.ArrayOf(PortMapping), func() {
g.Description("Port mappings for this service instance.")
})
g.Attribute("health_check", HealthCheckResult, func() {
g.Description("Most recent health check result.")
})
g.Attribute("last_health_at", g.String, func() {
g.Format(g.FormatDateTime)
g.Description("The time of the last health check attempt.")
g.Example("2025-01-28T10:00:00Z")
})
g.Attribute("service_ready", g.Boolean, func() {
g.Description("Whether the service is ready to accept requests.")
g.Example(true)
})
})

var ServiceInstance = g.ResultType("ServiceInstance", func() {
g.Description("A service instance running on a host alongside the database.")
g.Attributes(func() {
g.Attribute("service_instance_id", g.String, func() {
g.Description("Unique identifier for the service instance.")
g.Example("mcp-server-host-1")
})
g.Attribute("service_id", g.String, func() {
g.Description("The service ID from the DatabaseSpec.")
g.Example("mcp-server")
})
g.Attribute("database_id", Identifier, func() {
g.Description("The ID of the database this service belongs to.")
g.Example("production")
})
g.Attribute("host_id", g.String, func() {
g.Description("The ID of the host this service instance is running on.")
g.Example("host-1")
})
g.Attribute("state", g.String, func() {
g.Description("Current state of the service instance.")
g.Enum(
"creating",
"running",
"failed",
"deleting",
)
g.Example("running")
})
g.Attribute("status", ServiceInstanceStatus, func() {
g.Description("Runtime status information for the service instance.")
})
g.Attribute("created_at", g.String, func() {
g.Format(g.FormatDateTime)
g.Description("The time that the service instance was created.")
g.Example("2025-01-28T10:00:00Z")
})
g.Attribute("updated_at", g.String, func() {
g.Format(g.FormatDateTime)
g.Description("The time that the service instance was last updated.")
g.Example("2025-01-28T10:05:00Z")
})
g.Attribute("error", g.String, func() {
g.Description("An error message if the service instance is in an error state.")
g.Example("failed to start container: image not found")
})
})

g.View("default", func() {
g.Attribute("service_instance_id")
g.Attribute("service_id")
g.Attribute("database_id")
g.Attribute("host_id")
g.Attribute("state")
g.Attribute("status")
g.Attribute("created_at")
g.Attribute("updated_at")
g.Attribute("error")
})

g.Required("service_instance_id", "service_id", "database_id", "host_id", "state", "created_at", "updated_at")
})
Loading