diff --git a/api/apiv1/design/database.go b/api/apiv1/design/database.go index 31ab60c7..d377e584 100644 --- a/api/apiv1/design/database.go +++ b/api/apiv1/design/database.go @@ -129,6 +129,27 @@ var DatabaseUserSpec = g.Type("DatabaseUserSpec", func() { g.Required("username") }) +var DatabaseConnection = g.Type("DatabaseConnection", func() { + g.Description("Controls how the service connects to the database. " + + "When omitted, all nodes are included with the local node first " + + "and target_session_attrs is derived from the service config.") + g.Attribute("target_nodes", g.ArrayOf(g.String), func() { + g.Description("Optional ordered list of database node names. When set, " + + "the service's database connection includes only the listed nodes " + + "in the specified order.") + g.Example([]string{"n1", "n2"}) + g.Meta("struct:tag:json", "target_nodes,omitempty") + }) + g.Attribute("target_session_attrs", g.String, func() { + g.Description("Optional libpq target_session_attrs value. When set, " + + "overrides the default derived from the service config. " + + "Valid values: primary, prefer-standby, standby, read-write, any.") + g.Enum("primary", "prefer-standby", "standby", "read-write", "any") + g.Example("primary") + g.Meta("struct:tag:json", "target_session_attrs,omitempty") + }) +}) + var ServiceSpec = g.Type("ServiceSpec", func() { g.Attribute("service_id", Identifier, func() { g.Description("The unique identifier for this service.") @@ -197,6 +218,10 @@ var ServiceSpec = g.Type("ServiceSpec", func() { g.Description("Orchestrator-specific options for this service.") g.Meta("struct:tag:json", "orchestrator_opts,omitempty") }) + g.Attribute("database_connection", DatabaseConnection, func() { + g.Description("Optional database connection routing configuration.") + g.Meta("struct:tag:json", "database_connection,omitempty") + }) g.Required("service_id", "service_type", "version", "host_ids", "config") }) diff --git a/api/apiv1/gen/control_plane/service.go b/api/apiv1/gen/control_plane/service.go index bae6cc01..b77e0e81 100644 --- a/api/apiv1/gen/control_plane/service.go +++ b/api/apiv1/gen/control_plane/service.go @@ -320,6 +320,19 @@ type Database struct { Spec *DatabaseSpec `json:"spec,omitempty"` } +// Controls how the service connects to the database. When omitted, all nodes +// are included with the local node first and target_session_attrs is derived +// from the service config. +type DatabaseConnection struct { + // Optional ordered list of database node names. When set, the service's + // database connection includes only the listed nodes in the specified order. + TargetNodes []string `json:"target_nodes,omitempty"` + // Optional libpq target_session_attrs value. When set, overrides the default + // derived from the service config. Valid values: primary, prefer-standby, + // standby, read-write, any. + TargetSessionAttrs *string `json:"target_session_attrs,omitempty"` +} + type DatabaseNodeSpec struct { // The name of the database node. Name string `json:"name"` @@ -978,6 +991,8 @@ type ServiceSpec struct { Memory *string `json:"memory,omitempty"` // Orchestrator-specific options for this service. OrchestratorOpts *OrchestratorOpts `json:"orchestrator_opts,omitempty"` + // Optional database connection routing configuration. + DatabaseConnection *DatabaseConnection `json:"database_connection,omitempty"` } // StartInstancePayload is the payload type of the control-plane service diff --git a/api/apiv1/gen/http/control_plane/client/encode_decode.go b/api/apiv1/gen/http/control_plane/client/encode_decode.go index 0d3e61ca..48cd9765 100644 --- a/api/apiv1/gen/http/control_plane/client/encode_decode.go +++ b/api/apiv1/gen/http/control_plane/client/encode_decode.go @@ -4731,6 +4731,29 @@ func marshalControlplaneServiceSpecToServiceSpecRequestBody(v *controlplane.Serv if v.OrchestratorOpts != nil { res.OrchestratorOpts = marshalControlplaneOrchestratorOptsToOrchestratorOptsRequestBody(v.OrchestratorOpts) } + if v.DatabaseConnection != nil { + res.DatabaseConnection = marshalControlplaneDatabaseConnectionToDatabaseConnectionRequestBody(v.DatabaseConnection) + } + + return res +} + +// marshalControlplaneDatabaseConnectionToDatabaseConnectionRequestBody builds +// a value of type *DatabaseConnectionRequestBody from a value of type +// *controlplane.DatabaseConnection. +func marshalControlplaneDatabaseConnectionToDatabaseConnectionRequestBody(v *controlplane.DatabaseConnection) *DatabaseConnectionRequestBody { + if v == nil { + return nil + } + res := &DatabaseConnectionRequestBody{ + TargetSessionAttrs: v.TargetSessionAttrs, + } + if v.TargetNodes != nil { + res.TargetNodes = make([]string, len(v.TargetNodes)) + for i, val := range v.TargetNodes { + res.TargetNodes[i] = val + } + } return res } @@ -5152,6 +5175,29 @@ func marshalServiceSpecRequestBodyToControlplaneServiceSpec(v *ServiceSpecReques if v.OrchestratorOpts != nil { res.OrchestratorOpts = marshalOrchestratorOptsRequestBodyToControlplaneOrchestratorOpts(v.OrchestratorOpts) } + if v.DatabaseConnection != nil { + res.DatabaseConnection = marshalDatabaseConnectionRequestBodyToControlplaneDatabaseConnection(v.DatabaseConnection) + } + + return res +} + +// marshalDatabaseConnectionRequestBodyToControlplaneDatabaseConnection builds +// a value of type *controlplane.DatabaseConnection from a value of type +// *DatabaseConnectionRequestBody. +func marshalDatabaseConnectionRequestBodyToControlplaneDatabaseConnection(v *DatabaseConnectionRequestBody) *controlplane.DatabaseConnection { + if v == nil { + return nil + } + res := &controlplane.DatabaseConnection{ + TargetSessionAttrs: v.TargetSessionAttrs, + } + if v.TargetNodes != nil { + res.TargetNodes = make([]string, len(v.TargetNodes)) + for i, val := range v.TargetNodes { + res.TargetNodes[i] = val + } + } return res } @@ -5687,6 +5733,29 @@ func unmarshalServiceSpecResponseBodyToControlplaneServiceSpec(v *ServiceSpecRes if v.OrchestratorOpts != nil { res.OrchestratorOpts = unmarshalOrchestratorOptsResponseBodyToControlplaneOrchestratorOpts(v.OrchestratorOpts) } + if v.DatabaseConnection != nil { + res.DatabaseConnection = unmarshalDatabaseConnectionResponseBodyToControlplaneDatabaseConnection(v.DatabaseConnection) + } + + return res +} + +// unmarshalDatabaseConnectionResponseBodyToControlplaneDatabaseConnection +// builds a value of type *controlplane.DatabaseConnection from a value of type +// *DatabaseConnectionResponseBody. +func unmarshalDatabaseConnectionResponseBodyToControlplaneDatabaseConnection(v *DatabaseConnectionResponseBody) *controlplane.DatabaseConnection { + if v == nil { + return nil + } + res := &controlplane.DatabaseConnection{ + TargetSessionAttrs: v.TargetSessionAttrs, + } + if v.TargetNodes != nil { + res.TargetNodes = make([]string, len(v.TargetNodes)) + for i, val := range v.TargetNodes { + res.TargetNodes[i] = val + } + } return res } @@ -6110,6 +6179,29 @@ func marshalControlplaneServiceSpecToServiceSpecRequestBodyRequestBody(v *contro if v.OrchestratorOpts != nil { res.OrchestratorOpts = marshalControlplaneOrchestratorOptsToOrchestratorOptsRequestBodyRequestBody(v.OrchestratorOpts) } + if v.DatabaseConnection != nil { + res.DatabaseConnection = marshalControlplaneDatabaseConnectionToDatabaseConnectionRequestBodyRequestBody(v.DatabaseConnection) + } + + return res +} + +// marshalControlplaneDatabaseConnectionToDatabaseConnectionRequestBodyRequestBody +// builds a value of type *DatabaseConnectionRequestBodyRequestBody from a +// value of type *controlplane.DatabaseConnection. +func marshalControlplaneDatabaseConnectionToDatabaseConnectionRequestBodyRequestBody(v *controlplane.DatabaseConnection) *DatabaseConnectionRequestBodyRequestBody { + if v == nil { + return nil + } + res := &DatabaseConnectionRequestBodyRequestBody{ + TargetSessionAttrs: v.TargetSessionAttrs, + } + if v.TargetNodes != nil { + res.TargetNodes = make([]string, len(v.TargetNodes)) + for i, val := range v.TargetNodes { + res.TargetNodes[i] = val + } + } return res } @@ -6533,6 +6625,29 @@ func marshalServiceSpecRequestBodyRequestBodyToControlplaneServiceSpec(v *Servic if v.OrchestratorOpts != nil { res.OrchestratorOpts = marshalOrchestratorOptsRequestBodyRequestBodyToControlplaneOrchestratorOpts(v.OrchestratorOpts) } + if v.DatabaseConnection != nil { + res.DatabaseConnection = marshalDatabaseConnectionRequestBodyRequestBodyToControlplaneDatabaseConnection(v.DatabaseConnection) + } + + return res +} + +// marshalDatabaseConnectionRequestBodyRequestBodyToControlplaneDatabaseConnection +// builds a value of type *controlplane.DatabaseConnection from a value of type +// *DatabaseConnectionRequestBodyRequestBody. +func marshalDatabaseConnectionRequestBodyRequestBodyToControlplaneDatabaseConnection(v *DatabaseConnectionRequestBodyRequestBody) *controlplane.DatabaseConnection { + if v == nil { + return nil + } + res := &controlplane.DatabaseConnection{ + TargetSessionAttrs: v.TargetSessionAttrs, + } + if v.TargetNodes != nil { + res.TargetNodes = make([]string, len(v.TargetNodes)) + for i, val := range v.TargetNodes { + res.TargetNodes[i] = val + } + } return res } diff --git a/api/apiv1/gen/http/control_plane/client/types.go b/api/apiv1/gen/http/control_plane/client/types.go index df58b9b5..7844df1a 100644 --- a/api/apiv1/gen/http/control_plane/client/types.go +++ b/api/apiv1/gen/http/control_plane/client/types.go @@ -2104,6 +2104,19 @@ type ServiceSpecRequestBody struct { Memory *string `json:"memory,omitempty"` // Orchestrator-specific options for this service. OrchestratorOpts *OrchestratorOptsRequestBody `json:"orchestrator_opts,omitempty"` + // Optional database connection routing configuration. + DatabaseConnection *DatabaseConnectionRequestBody `json:"database_connection,omitempty"` +} + +// DatabaseConnectionRequestBody is used to define fields on request body types. +type DatabaseConnectionRequestBody struct { + // Optional ordered list of database node names. When set, the service's + // database connection includes only the listed nodes in the specified order. + TargetNodes []string `json:"target_nodes,omitempty"` + // Optional libpq target_session_attrs value. When set, overrides the default + // derived from the service config. Valid values: primary, prefer-standby, + // standby, read-write, any. + TargetSessionAttrs *string `json:"target_session_attrs,omitempty"` } // DatabaseResponseBody is used to define fields on response body types. @@ -2492,6 +2505,20 @@ type ServiceSpecResponseBody struct { Memory *string `json:"memory,omitempty"` // Orchestrator-specific options for this service. OrchestratorOpts *OrchestratorOptsResponseBody `json:"orchestrator_opts,omitempty"` + // Optional database connection routing configuration. + DatabaseConnection *DatabaseConnectionResponseBody `json:"database_connection,omitempty"` +} + +// DatabaseConnectionResponseBody is used to define fields on response body +// types. +type DatabaseConnectionResponseBody struct { + // Optional ordered list of database node names. When set, the service's + // database connection includes only the listed nodes in the specified order. + TargetNodes []string `json:"target_nodes,omitempty"` + // Optional libpq target_session_attrs value. When set, overrides the default + // derived from the service config. Valid values: primary, prefer-standby, + // standby, read-write, any. + TargetSessionAttrs *string `json:"target_session_attrs,omitempty"` } // DatabaseSpecRequestBodyRequestBody is used to define fields on request body @@ -2807,6 +2834,20 @@ type ServiceSpecRequestBodyRequestBody struct { Memory *string `json:"memory,omitempty"` // Orchestrator-specific options for this service. OrchestratorOpts *OrchestratorOptsRequestBodyRequestBody `json:"orchestrator_opts,omitempty"` + // Optional database connection routing configuration. + DatabaseConnection *DatabaseConnectionRequestBodyRequestBody `json:"database_connection,omitempty"` +} + +// DatabaseConnectionRequestBodyRequestBody is used to define fields on request +// body types. +type DatabaseConnectionRequestBodyRequestBody struct { + // Optional ordered list of database node names. When set, the service's + // database connection includes only the listed nodes in the specified order. + TargetNodes []string `json:"target_nodes,omitempty"` + // Optional libpq target_session_attrs value. When set, overrides the default + // derived from the service config. Valid values: primary, prefer-standby, + // standby, read-write, any. + TargetSessionAttrs *string `json:"target_session_attrs,omitempty"` } // TaskLogEntryResponseBody is used to define fields on response body types. @@ -6250,6 +6291,22 @@ func ValidateServiceSpecRequestBody(body *ServiceSpecRequestBody) (err error) { err = goa.MergeErrors(err, err2) } } + if body.DatabaseConnection != nil { + if err2 := ValidateDatabaseConnectionRequestBody(body.DatabaseConnection); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// ValidateDatabaseConnectionRequestBody runs the validations defined on +// DatabaseConnectionRequestBody +func ValidateDatabaseConnectionRequestBody(body *DatabaseConnectionRequestBody) (err error) { + if body.TargetSessionAttrs != nil { + if !(*body.TargetSessionAttrs == "primary" || *body.TargetSessionAttrs == "prefer-standby" || *body.TargetSessionAttrs == "standby" || *body.TargetSessionAttrs == "read-write" || *body.TargetSessionAttrs == "any") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.target_session_attrs", *body.TargetSessionAttrs, []any{"primary", "prefer-standby", "standby", "read-write", "any"})) + } + } return } @@ -6360,6 +6417,12 @@ func ValidateServiceSpecResponseBody(body *ServiceSpecResponseBody) (err error) return } +// ValidateDatabaseConnectionResponseBody runs a no-op validation on +// DatabaseConnectionResponseBody +func ValidateDatabaseConnectionResponseBody(body *DatabaseConnectionResponseBody) (err error) { + return +} + // ValidateDatabaseSpecRequestBodyRequestBody runs the validations defined on // DatabaseSpecRequestBodyRequestBody func ValidateDatabaseSpecRequestBodyRequestBody(body *DatabaseSpecRequestBodyRequestBody) (err error) { @@ -6992,6 +7055,22 @@ func ValidateServiceSpecRequestBodyRequestBody(body *ServiceSpecRequestBodyReque err = goa.MergeErrors(err, err2) } } + if body.DatabaseConnection != nil { + if err2 := ValidateDatabaseConnectionRequestBodyRequestBody(body.DatabaseConnection); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// ValidateDatabaseConnectionRequestBodyRequestBody runs the validations +// defined on DatabaseConnectionRequestBodyRequestBody +func ValidateDatabaseConnectionRequestBodyRequestBody(body *DatabaseConnectionRequestBodyRequestBody) (err error) { + if body.TargetSessionAttrs != nil { + if !(*body.TargetSessionAttrs == "primary" || *body.TargetSessionAttrs == "prefer-standby" || *body.TargetSessionAttrs == "standby" || *body.TargetSessionAttrs == "read-write" || *body.TargetSessionAttrs == "any") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.target_session_attrs", *body.TargetSessionAttrs, []any{"primary", "prefer-standby", "standby", "read-write", "any"})) + } + } return } diff --git a/api/apiv1/gen/http/control_plane/server/encode_decode.go b/api/apiv1/gen/http/control_plane/server/encode_decode.go index 9ceb6b4b..bdcb2b76 100644 --- a/api/apiv1/gen/http/control_plane/server/encode_decode.go +++ b/api/apiv1/gen/http/control_plane/server/encode_decode.go @@ -4092,6 +4092,29 @@ func unmarshalServiceSpecRequestBodyToControlplaneServiceSpec(v *ServiceSpecRequ if v.OrchestratorOpts != nil { res.OrchestratorOpts = unmarshalOrchestratorOptsRequestBodyToControlplaneOrchestratorOpts(v.OrchestratorOpts) } + if v.DatabaseConnection != nil { + res.DatabaseConnection = unmarshalDatabaseConnectionRequestBodyToControlplaneDatabaseConnection(v.DatabaseConnection) + } + + return res +} + +// unmarshalDatabaseConnectionRequestBodyToControlplaneDatabaseConnection +// builds a value of type *controlplane.DatabaseConnection from a value of type +// *DatabaseConnectionRequestBody. +func unmarshalDatabaseConnectionRequestBodyToControlplaneDatabaseConnection(v *DatabaseConnectionRequestBody) *controlplane.DatabaseConnection { + if v == nil { + return nil + } + res := &controlplane.DatabaseConnection{ + TargetSessionAttrs: v.TargetSessionAttrs, + } + if v.TargetNodes != nil { + res.TargetNodes = make([]string, len(v.TargetNodes)) + for i, val := range v.TargetNodes { + res.TargetNodes[i] = val + } + } return res } @@ -4647,6 +4670,29 @@ func marshalControlplaneServiceSpecToServiceSpecResponseBody(v *controlplane.Ser if v.OrchestratorOpts != nil { res.OrchestratorOpts = marshalControlplaneOrchestratorOptsToOrchestratorOptsResponseBody(v.OrchestratorOpts) } + if v.DatabaseConnection != nil { + res.DatabaseConnection = marshalControlplaneDatabaseConnectionToDatabaseConnectionResponseBody(v.DatabaseConnection) + } + + return res +} + +// marshalControlplaneDatabaseConnectionToDatabaseConnectionResponseBody builds +// a value of type *DatabaseConnectionResponseBody from a value of type +// *controlplane.DatabaseConnection. +func marshalControlplaneDatabaseConnectionToDatabaseConnectionResponseBody(v *controlplane.DatabaseConnection) *DatabaseConnectionResponseBody { + if v == nil { + return nil + } + res := &DatabaseConnectionResponseBody{ + TargetSessionAttrs: v.TargetSessionAttrs, + } + if v.TargetNodes != nil { + res.TargetNodes = make([]string, len(v.TargetNodes)) + for i, val := range v.TargetNodes { + res.TargetNodes[i] = val + } + } return res } @@ -5050,6 +5096,29 @@ func unmarshalServiceSpecRequestBodyRequestBodyToControlplaneServiceSpec(v *Serv if v.OrchestratorOpts != nil { res.OrchestratorOpts = unmarshalOrchestratorOptsRequestBodyRequestBodyToControlplaneOrchestratorOpts(v.OrchestratorOpts) } + if v.DatabaseConnection != nil { + res.DatabaseConnection = unmarshalDatabaseConnectionRequestBodyRequestBodyToControlplaneDatabaseConnection(v.DatabaseConnection) + } + + return res +} + +// unmarshalDatabaseConnectionRequestBodyRequestBodyToControlplaneDatabaseConnection +// builds a value of type *controlplane.DatabaseConnection from a value of type +// *DatabaseConnectionRequestBodyRequestBody. +func unmarshalDatabaseConnectionRequestBodyRequestBodyToControlplaneDatabaseConnection(v *DatabaseConnectionRequestBodyRequestBody) *controlplane.DatabaseConnection { + if v == nil { + return nil + } + res := &controlplane.DatabaseConnection{ + TargetSessionAttrs: v.TargetSessionAttrs, + } + if v.TargetNodes != nil { + res.TargetNodes = make([]string, len(v.TargetNodes)) + for i, val := range v.TargetNodes { + res.TargetNodes[i] = val + } + } return res } diff --git a/api/apiv1/gen/http/control_plane/server/types.go b/api/apiv1/gen/http/control_plane/server/types.go index d90d2281..d3ed2b79 100644 --- a/api/apiv1/gen/http/control_plane/server/types.go +++ b/api/apiv1/gen/http/control_plane/server/types.go @@ -2188,6 +2188,20 @@ type ServiceSpecResponseBody struct { Memory *string `json:"memory,omitempty"` // Orchestrator-specific options for this service. OrchestratorOpts *OrchestratorOptsResponseBody `json:"orchestrator_opts,omitempty"` + // Optional database connection routing configuration. + DatabaseConnection *DatabaseConnectionResponseBody `json:"database_connection,omitempty"` +} + +// DatabaseConnectionResponseBody is used to define fields on response body +// types. +type DatabaseConnectionResponseBody struct { + // Optional ordered list of database node names. When set, the service's + // database connection includes only the listed nodes in the specified order. + TargetNodes []string `json:"target_nodes,omitempty"` + // Optional libpq target_session_attrs value. When set, overrides the default + // derived from the service config. Valid values: primary, prefer-standby, + // standby, read-write, any. + TargetSessionAttrs *string `json:"target_session_attrs,omitempty"` } // TaskLogEntryResponseBody is used to define fields on response body types. @@ -2502,6 +2516,19 @@ type ServiceSpecRequestBody struct { Memory *string `json:"memory,omitempty"` // Orchestrator-specific options for this service. OrchestratorOpts *OrchestratorOptsRequestBody `json:"orchestrator_opts,omitempty"` + // Optional database connection routing configuration. + DatabaseConnection *DatabaseConnectionRequestBody `json:"database_connection,omitempty"` +} + +// DatabaseConnectionRequestBody is used to define fields on request body types. +type DatabaseConnectionRequestBody struct { + // Optional ordered list of database node names. When set, the service's + // database connection includes only the listed nodes in the specified order. + TargetNodes []string `json:"target_nodes,omitempty"` + // Optional libpq target_session_attrs value. When set, overrides the default + // derived from the service config. Valid values: primary, prefer-standby, + // standby, read-write, any. + TargetSessionAttrs *string `json:"target_session_attrs,omitempty"` } // DatabaseSpecRequestBodyRequestBody is used to define fields on request body @@ -2817,6 +2844,20 @@ type ServiceSpecRequestBodyRequestBody struct { Memory *string `json:"memory,omitempty"` // Orchestrator-specific options for this service. OrchestratorOpts *OrchestratorOptsRequestBodyRequestBody `json:"orchestrator_opts,omitempty"` + // Optional database connection routing configuration. + DatabaseConnection *DatabaseConnectionRequestBodyRequestBody `json:"database_connection,omitempty"` +} + +// DatabaseConnectionRequestBodyRequestBody is used to define fields on request +// body types. +type DatabaseConnectionRequestBodyRequestBody struct { + // Optional ordered list of database node names. When set, the service's + // database connection includes only the listed nodes in the specified order. + TargetNodes []string `json:"target_nodes,omitempty"` + // Optional libpq target_session_attrs value. When set, overrides the default + // derived from the service config. Valid values: primary, prefer-standby, + // standby, read-write, any. + TargetSessionAttrs *string `json:"target_session_attrs,omitempty"` } // NewInitClusterResponseBody builds the HTTP response body from the result of @@ -5761,6 +5802,22 @@ func ValidateServiceSpecRequestBody(body *ServiceSpecRequestBody) (err error) { err = goa.MergeErrors(err, err2) } } + if body.DatabaseConnection != nil { + if err2 := ValidateDatabaseConnectionRequestBody(body.DatabaseConnection); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// ValidateDatabaseConnectionRequestBody runs the validations defined on +// DatabaseConnectionRequestBody +func ValidateDatabaseConnectionRequestBody(body *DatabaseConnectionRequestBody) (err error) { + if body.TargetSessionAttrs != nil { + if !(*body.TargetSessionAttrs == "primary" || *body.TargetSessionAttrs == "prefer-standby" || *body.TargetSessionAttrs == "standby" || *body.TargetSessionAttrs == "read-write" || *body.TargetSessionAttrs == "any") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.target_session_attrs", *body.TargetSessionAttrs, []any{"primary", "prefer-standby", "standby", "read-write", "any"})) + } + } return } @@ -6487,5 +6544,21 @@ func ValidateServiceSpecRequestBodyRequestBody(body *ServiceSpecRequestBodyReque err = goa.MergeErrors(err, err2) } } + if body.DatabaseConnection != nil { + if err2 := ValidateDatabaseConnectionRequestBodyRequestBody(body.DatabaseConnection); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// ValidateDatabaseConnectionRequestBodyRequestBody runs the validations +// defined on DatabaseConnectionRequestBodyRequestBody +func ValidateDatabaseConnectionRequestBodyRequestBody(body *DatabaseConnectionRequestBodyRequestBody) (err error) { + if body.TargetSessionAttrs != nil { + if !(*body.TargetSessionAttrs == "primary" || *body.TargetSessionAttrs == "prefer-standby" || *body.TargetSessionAttrs == "standby" || *body.TargetSessionAttrs == "read-write" || *body.TargetSessionAttrs == "any") { + err = goa.MergeErrors(err, goa.InvalidEnumValueError("body.target_session_attrs", *body.TargetSessionAttrs, []any{"primary", "prefer-standby", "standby", "read-write", "any"})) + } + } return } diff --git a/api/apiv1/gen/http/openapi.json b/api/apiv1/gen/http/openapi.json index b78c936e..fe3cc886 100644 --- a/api/apiv1/gen/http/openapi.json +++ b/api/apiv1/gen/http/openapi.json @@ -2626,7 +2626,7 @@ }, "additionalProperties": { "type": "string", - "example": "Dolorum fugit." + "example": "Ratione asperiores iusto nostrum commodi quidem occaecati." } }, "backup_options": { @@ -2637,7 +2637,7 @@ }, "additionalProperties": { "type": "string", - "example": "Aut ratione asperiores." + "example": "Nobis explicabo eum dolores quae." } }, "type": { @@ -3835,6 +3835,44 @@ "state" ] }, + "DatabaseConnection": { + "title": "DatabaseConnection", + "type": "object", + "properties": { + "target_nodes": { + "type": "array", + "items": { + "type": "string", + "example": "Voluptatem mollitia est." + }, + "description": "Optional ordered list of database node names. When set, the service's database connection includes only the listed nodes in the specified order.", + "example": [ + "n1", + "n2" + ] + }, + "target_session_attrs": { + "type": "string", + "description": "Optional libpq target_session_attrs value. When set, overrides the default derived from the service config. Valid values: primary, prefer-standby, standby, read-write, any.", + "example": "primary", + "enum": [ + "primary", + "prefer-standby", + "standby", + "read-write", + "any" + ] + } + }, + "description": "Controls how the service connects to the database. When omitted, all nodes are included with the local node first and target_session_attrs is derived from the service config.", + "example": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + } + }, "DatabaseNodeSpec": { "title": "DatabaseNodeSpec", "type": "object", @@ -4631,77 +4669,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" }, - "cpus": "500m", "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -4773,77 +4747,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" }, - "cpus": "500m", "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -5342,6 +5252,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -5413,6 +5330,91 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + }, + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -6002,7 +6004,7 @@ "type": "boolean", "description": "If true, skip the health validations that prevent running failover on a healthy cluster.", "default": false, - "example": false + "example": true } }, "example": { @@ -6662,6 +6664,16 @@ "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", "type": "create" }, + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + }, { "completed_at": "2025-06-18T16:52:35Z", "created_at": "2025-06-18T16:52:05Z", @@ -7312,16 +7324,6 @@ "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", "type": "create" }, - { - "completed_at": "2025-06-18T16:52:35Z", - "created_at": "2025-06-18T16:52:05Z", - "database_id": "storefront", - "entity_id": "storefront", - "scope": "database", - "status": "completed", - "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", - "type": "create" - }, { "completed_at": "2025-06-18T16:52:35Z", "created_at": "2025-06-18T16:52:05Z", @@ -7715,6 +7717,16 @@ "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", "type": "create" }, + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + }, { "completed_at": "2025-06-18T16:52:35Z", "created_at": "2025-06-18T16:52:05Z", @@ -8100,7 +8112,7 @@ "type": "array", "items": { "type": "string", - "example": "Eum dolores quae minima." + "example": "Reprehenderit totam ea velit consequatur sequi error." }, "description": "The nodes to restore. Defaults to all nodes if empty or unspecified.", "example": [ @@ -8741,6 +8753,9 @@ "example": "500m", "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" }, + "database_connection": { + "$ref": "#/definitions/DatabaseConnection" + }, "host_ids": { "type": "array", "items": { @@ -8804,6 +8819,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696" ], @@ -9210,6 +9232,14 @@ "message": "task started", "timestamp": "2025-05-29T15:43:13Z" }, + { + "fields": { + "option.enabled": true, + "status": "creating" + }, + "message": "task started", + "timestamp": "2025-05-29T15:43:13Z" + }, { "fields": { "option.enabled": true, diff --git a/api/apiv1/gen/http/openapi.yaml b/api/apiv1/gen/http/openapi.yaml index 86097d0e..b9173c76 100644 --- a/api/apiv1/gen/http/openapi.yaml +++ b/api/apiv1/gen/http/openapi.yaml @@ -1843,7 +1843,7 @@ definitions: key: value additionalProperties: type: string - example: Dolorum fugit. + example: Ratione asperiores iusto nostrum commodi quidem occaecati. backup_options: type: object description: Options for the backup. @@ -1851,7 +1851,7 @@ definitions: archive-check: "n" additionalProperties: type: string - example: Aut ratione asperiores. + example: Nobis explicabo eum dolores quae. type: type: string description: The type of backup. @@ -2712,6 +2712,35 @@ definitions: - created_at - updated_at - state + DatabaseConnection: + title: DatabaseConnection + type: object + properties: + target_nodes: + type: array + items: + type: string + example: Voluptatem mollitia est. + description: Optional ordered list of database node names. When set, the service's database connection includes only the listed nodes in the specified order. + example: + - n1 + - n2 + target_session_attrs: + type: string + description: 'Optional libpq target_session_attrs value. When set, overrides the default derived from the service config. Valid values: primary, prefer-standby, standby, read-write, any.' + example: primary + enum: + - primary + - prefer-standby + - standby + - read-write + - any + description: Controls how the service connects to the database. When omitted, all nodes are included with the local node first and target_session_attrs is derived from the service config. + example: + target_nodes: + - n1 + - n2 + target_session_attrs: primary DatabaseNodeSpec: title: DatabaseNodeSpec type: object @@ -3312,6 +3341,11 @@ definitions: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -3356,94 +3390,11 @@ definitions: llm_provider: openai openai_api_key: sk-... cpus: 500m - host_ids: - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - memory: 512M - orchestrator_opts: - swarm: - extra_labels: - traefik.enable: "true" - traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) - extra_networks: - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - extra_volumes: - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - port: 0 - service_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - service_type: mcp - version: latest - - config: - llm_model: gpt-4 - llm_provider: openai - openai_api_key: sk-... - cpus: 500m - host_ids: - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - memory: 512M - orchestrator_opts: - swarm: - extra_labels: - traefik.enable: "true" - traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) - extra_networks: - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - extra_volumes: - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - port: 0 - service_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - service_type: mcp - version: latest - - config: - llm_model: gpt-4 - llm_provider: openai - openai_api_key: sk-... - cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -3802,6 +3753,11 @@ definitions: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -3846,6 +3802,60 @@ definitions: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 512M + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + port: 0 + service_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + service_type: mcp + version: latest + - config: + llm_model: gpt-4 + llm_provider: openai + openai_api_key: sk-... + cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -4273,7 +4283,7 @@ definitions: type: boolean description: If true, skip the health validations that prevent running failover on a healthy cluster. default: false - example: false + example: true example: candidate_instance_id: 68f50878-44d2-4524-a823-e31bd478706d-n1-689qacsi skip_validation: true @@ -4768,6 +4778,14 @@ definitions: status: completed task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create example: tasks: - completed_at: "2025-06-18T17:54:36Z" @@ -5229,14 +5247,6 @@ definitions: status: completed task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 type: create - - completed_at: "2025-06-18T16:52:35Z" - created_at: "2025-06-18T16:52:05Z" - database_id: storefront - entity_id: storefront - scope: database - status: completed - task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 - type: create example: tasks: - completed_at: "2025-06-18T17:54:36Z" @@ -5504,6 +5514,14 @@ definitions: status: completed task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create example: tasks: - completed_at: "2025-06-18T17:54:36Z" @@ -5784,7 +5802,7 @@ definitions: type: array items: type: string - example: Eum dolores quae minima. + example: Reprehenderit totam ea velit consequatur sequi error. description: The nodes to restore. Defaults to all nodes if empty or unspecified. example: - n1 @@ -6264,6 +6282,8 @@ definitions: 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. example: 500m pattern: ^[0-9]+(\.[0-9]{1,3}|m)?$ + database_connection: + $ref: '#/definitions/DatabaseConnection' host_ids: type: array items: @@ -6315,6 +6335,11 @@ definitions: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 512M @@ -6605,6 +6630,11 @@ definitions: status: creating message: task started timestamp: "2025-05-29T15:43:13Z" + - fields: + option.enabled: true + status: creating + message: task started + timestamp: "2025-05-29T15:43:13Z" last_entry_id: type: string description: The ID of the last entry in the task log. diff --git a/api/apiv1/gen/http/openapi3.json b/api/apiv1/gen/http/openapi3.json index fb7e5f50..76416d74 100644 --- a/api/apiv1/gen/http/openapi3.json +++ b/api/apiv1/gen/http/openapi3.json @@ -1746,20 +1746,17 @@ "type": "array", "items": { "type": "string", - "example": "Eius temporibus incidunt porro." + "example": "Cupiditate molestiae." }, "description": "Host IDs to treat as removed during this update. Events targeting these hosts will be skipped.", "example": [ - "Non ut.", - "Eos ut voluptates quo.", - "Laborum tenetur.", - "Et quas." + "Accusamus quia dolores.", + "Laboriosam placeat distinctio atque dolor autem." ] }, "example": [ - "Asperiores quis in sunt dignissimos.", - "Cupiditate molestiae.", - "Et accusamus quia dolores recusandae." + "Excepturi sapiente neque doloribus consequatur voluptatibus.", + "Et nisi nihil corporis." ] }, { @@ -5571,6 +5568,29 @@ "s3_region": "us-east-1", "type": "s3" }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "f6b84a99-5e91-4203-be1e-131fe82e5984", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, { "azure_account": "pgedge-backups", "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", @@ -5748,7 +5768,7 @@ }, "additionalProperties": { "type": "string", - "example": "Neque doloribus ipsa et itaque aut reprehenderit." + "example": "Quibusdam veniam." } }, "backup_options": { @@ -5759,7 +5779,7 @@ }, "additionalProperties": { "type": "string", - "example": "Ea velit consequatur sequi error et quibusdam." + "example": "Non expedita sequi eligendi delectus sit quas." } }, "type": { @@ -5831,7 +5851,7 @@ }, "additionalProperties": { "type": "string", - "example": "Sed quis architecto magnam temporibus ipsa et." + "example": "Placeat a." } }, "gcs_bucket": { @@ -6421,7 +6441,7 @@ "type": "array", "items": { "type": "string", - "example": "s2a", + "example": "76l", "minLength": 3, "maxLength": 128 }, @@ -6473,7 +6493,7 @@ "type": "array", "items": { "type": "string", - "example": "Expedita sequi." + "example": "Quasi ipsum officiis et est et." }, "description": "Existing server to join", "example": [ @@ -6511,7 +6531,7 @@ } }, "example": { - "state": "available" + "state": "error" }, "required": [ "state" @@ -6849,52 +6869,7 @@ ], "port": 5432 }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", + "created_at": "1971-07-18T16:52:12Z", "error": "failed to get patroni status: connection refused", "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", @@ -6902,7 +6877,7 @@ "postgres": { "patroni_paused": false, "patroni_state": "unknown", - "pending_restart": false, + "pending_restart": true, "role": "primary", "version": "18.1" }, @@ -6927,9 +6902,9 @@ ], "version": "4.10.0" }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" }, { "connection_info": { @@ -6939,7 +6914,7 @@ ], "port": 5432 }, - "created_at": "1991-01-10T16:19:34Z", + "created_at": "1971-07-18T16:52:12Z", "error": "failed to get patroni status: connection refused", "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", @@ -6947,7 +6922,7 @@ "postgres": { "patroni_paused": false, "patroni_state": "unknown", - "pending_restart": false, + "pending_restart": true, "role": "primary", "version": "18.1" }, @@ -6972,9 +6947,9 @@ ], "version": "4.10.0" }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" } ] }, @@ -7007,16 +6982,6 @@ "image_version": "1.0.0", "last_health_at": "2025-01-28T10:00:00Z", "ports": [ - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, { "container_port": 8080, "host_port": 8080, @@ -7054,16 +7019,6 @@ "image_version": "1.0.0", "last_health_at": "2025-01-28T10:00:00Z", "ports": [ - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, { "container_port": 8080, "host_port": 8080, @@ -7101,16 +7056,6 @@ "image_version": "1.0.0", "last_health_at": "2025-01-28T10:00:00Z", "ports": [ - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, { "container_port": 8080, "host_port": 8080, @@ -7148,16 +7093,6 @@ "image_version": "1.0.0", "last_health_at": "2025-01-28T10:00:00Z", "ports": [ - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, { "container_port": 8080, "host_port": 8080, @@ -7181,7 +7116,7 @@ "state": { "type": "string", "description": "Current state of the database.", - "example": "modifying", + "example": "deleting", "enum": [ "creating", "modifying", @@ -7438,51 +7373,6 @@ "status_updated_at": "1993-04-25T23:06:28Z", "updated_at": "1981-08-22T23:20:01Z" }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "2011-03-15T17:48:48Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": true, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "available", - "status_updated_at": "1993-04-25T23:06:28Z", - "updated_at": "1981-08-22T23:20:01Z" - }, { "connection_info": { "addresses": [ @@ -7631,53 +7521,6 @@ }, "updated_at": "2025-01-28T10:05:00Z" }, - { - "created_at": "2025-01-28T10:00:00Z", - "database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "error": "failed to start container: image not found", - "host_id": "host-1", - "service_id": "mcp-server", - "service_instance_id": "mcp-server-host-1", - "state": "running", - "status": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "container_id": "a1b2c3d4e5f6", - "health_check": { - "checked_at": "2025-01-28T10:00:00Z", - "message": "Connection refused", - "status": "healthy" - }, - "image_version": "1.0.0", - "last_health_at": "2025-01-28T10:00:00Z", - "ports": [ - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - } - ], - "service_ready": true - }, - "updated_at": "2025-01-28T10:05:00Z" - }, { "created_at": "2025-01-28T10:00:00Z", "database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", @@ -7733,7 +7576,7 @@ "state": { "type": "string", "description": "Current state of the database.", - "example": "unknown", + "example": "restoring", "enum": [ "creating", "modifying", @@ -8285,7 +8128,7 @@ "state": { "type": "string", "description": "Current state of the database.", - "example": "unknown", + "example": "creating", "enum": [ "creating", "modifying", @@ -8587,51 +8430,6 @@ "status_updated_at": "1993-04-25T23:06:28Z", "updated_at": "1981-08-22T23:20:01Z" }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "2011-03-15T17:48:48Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": true, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "available", - "status_updated_at": "1993-04-25T23:06:28Z", - "updated_at": "1981-08-22T23:20:01Z" - }, { "connection_info": { "addresses": [ @@ -8733,53 +8531,6 @@ }, "updated_at": "2025-01-28T10:05:00Z" }, - { - "created_at": "2025-01-28T10:00:00Z", - "database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "error": "failed to start container: image not found", - "host_id": "host-1", - "service_id": "mcp-server", - "service_instance_id": "mcp-server-host-1", - "state": "running", - "status": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "container_id": "a1b2c3d4e5f6", - "health_check": { - "checked_at": "2025-01-28T10:00:00Z", - "message": "Connection refused", - "status": "healthy" - }, - "image_version": "1.0.0", - "last_health_at": "2025-01-28T10:00:00Z", - "ports": [ - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - } - ], - "service_ready": true - }, - "updated_at": "2025-01-28T10:05:00Z" - }, { "created_at": "2025-01-28T10:00:00Z", "database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", @@ -8835,7 +8586,7 @@ "state": { "type": "string", "description": "Current state of the database.", - "example": "restoring", + "example": "creating", "enum": [ "creating", "modifying", @@ -9136,63 +8887,106 @@ "state": "available", "status_updated_at": "1993-04-25T23:06:28Z", "updated_at": "1981-08-22T23:20:01Z" - } - ] - }, - "service_instances": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceInstance" - }, - "description": "Service instances running alongside this database.", - "example": [ + }, { - "created_at": "2025-01-28T10:00:00Z", - "database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "error": "failed to start container: image not found", - "host_id": "host-1", - "service_id": "mcp-server", - "service_instance_id": "mcp-server-host-1", - "state": "running", - "status": { + "connection_info": { "addresses": [ "10.24.34.2", "i-0123456789abcdef.ec2.internal" ], - "container_id": "a1b2c3d4e5f6", - "health_check": { - "checked_at": "2025-01-28T10:00:00Z", - "message": "Connection refused", - "status": "healthy" - }, - "image_version": "1.0.0", - "last_health_at": "2025-01-28T10:00:00Z", - "ports": [ + "port": 5432 + }, + "created_at": "2011-03-15T17:48:48Z", + "error": "failed to get patroni status: connection refused", + "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", + "node_name": "n1", + "postgres": { + "patroni_paused": false, + "patroni_state": "unknown", + "pending_restart": true, + "role": "primary", + "version": "18.1" + }, + "spock": { + "read_only": "off", + "subscriptions": [ { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" }, { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" }, { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + } + ], + "version": "4.10.0" + }, + "state": "available", + "status_updated_at": "1993-04-25T23:06:28Z", + "updated_at": "1981-08-22T23:20:01Z" + }, + { + "connection_info": { + "addresses": [ + "10.24.34.2", + "i-0123456789abcdef.ec2.internal" + ], + "port": 5432 + }, + "created_at": "2011-03-15T17:48:48Z", + "error": "failed to get patroni status: connection refused", + "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", + "node_name": "n1", + "postgres": { + "patroni_paused": false, + "patroni_state": "unknown", + "pending_restart": true, + "role": "primary", + "version": "18.1" + }, + "spock": { + "read_only": "off", + "subscriptions": [ + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" }, { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" } ], - "service_ready": true + "version": "4.10.0" }, - "updated_at": "2025-01-28T10:05:00Z" - }, + "state": "available", + "status_updated_at": "1993-04-25T23:06:28Z", + "updated_at": "1981-08-22T23:20:01Z" + } + ] + }, + "service_instances": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ServiceInstance" + }, + "description": "Service instances running alongside this database.", + "example": [ { "created_at": "2025-01-28T10:00:00Z", "database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", @@ -9342,7 +9136,7 @@ "state": { "type": "string", "description": "Current state of the database.", - "example": "failed", + "example": "backing_up", "enum": [ "creating", "modifying", @@ -9531,6 +9325,43 @@ "state" ] }, + "DatabaseConnection": { + "type": "object", + "properties": { + "target_nodes": { + "type": "array", + "items": { + "type": "string", + "example": "Ab sed exercitationem rerum animi et itaque." + }, + "description": "Optional ordered list of database node names. When set, the service's database connection includes only the listed nodes in the specified order.", + "example": [ + "n1", + "n2" + ] + }, + "target_session_attrs": { + "type": "string", + "description": "Optional libpq target_session_attrs value. When set, overrides the default derived from the service config. Valid values: primary, prefer-standby, standby, read-write, any.", + "example": "primary", + "enum": [ + "primary", + "prefer-standby", + "standby", + "read-write", + "any" + ] + } + }, + "description": "Controls how the service connects to the database. When omitted, all nodes are included with the local node first and target_session_attrs is derived from the service config.", + "example": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + } + }, "DatabaseNodeSpec": { "type": "object", "properties": { @@ -9554,8 +9385,6 @@ }, "description": "The IDs of the hosts that should run this node. When multiple hosts are specified, one host will chosen as a primary, and the others will be read replicas.", "example": [ - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "de3b1388-1f0c-42f1-a86c-59ab72f255ec" ], "minItems": 1 @@ -9661,7 +9490,6 @@ }, "cpus": "500m", "host_ids": [ - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "de3b1388-1f0c-42f1-a86c-59ab72f255ec" ], @@ -9939,7 +9767,6 @@ }, "cpus": "500m", "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "500M", @@ -10063,6 +9890,8 @@ }, "description": "The IDs of the hosts that should run this node. When multiple hosts are specified, one host will chosen as a primary, and the others will be read replicas.", "example": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "minItems": 1 @@ -10214,7 +10043,6 @@ }, "cpus": "500m", "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], @@ -10616,8 +10444,6 @@ }, "description": "The IDs of the hosts that should run this node. When multiple hosts are specified, one host will chosen as a primary, and the others will be read replicas.", "example": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "minItems": 1 @@ -10769,6 +10595,7 @@ }, "cpus": "500m", "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], @@ -10893,7 +10720,6 @@ }, "description": "The IDs of the hosts that should run this node. When multiple hosts are specified, one host will chosen as a primary, and the others will be read replicas.", "example": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], @@ -11046,6 +10872,8 @@ }, "cpus": "500m", "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "500M", @@ -11552,8 +11380,6 @@ }, "cpus": "500m", "host_ids": [ - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "de3b1388-1f0c-42f1-a86c-59ab72f255ec" ], "memory": "500M", @@ -11649,68 +11475,58 @@ "source_node_name": "n1" }, "source_node": "n1" - } - ], - "minItems": 1, - "maxItems": 9 - }, - "orchestrator_opts": { - "$ref": "#/components/schemas/OrchestratorOpts" - }, - "patroni_port": { - "type": "integer", - "description": "The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.", - "example": 8888, - "format": "int64", - "minimum": 0, - "maximum": 65535 - }, - "port": { - "type": "integer", - "description": "The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature.", - "example": 5432, - "format": "int64", - "minimum": 0, - "maximum": 65535 - }, - "postgres_version": { - "type": "string", - "description": "The Postgres version in 'major.minor' format.", - "example": "17.6", - "pattern": "^\\d{2}\\.\\d{1,2}$" - }, - "postgresql_conf": { - "type": "object", - "description": "Additional postgresql.conf settings. Will be merged with the settings provided by control-plane.", - "example": { - "max_connections": 1000 - }, - "maxLength": 64, - "additionalProperties": true - }, - "restore_config": { - "$ref": "#/components/schemas/RestoreConfigSpec" - }, - "services": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceSpec" - }, - "description": "Service instances to run alongside the database (e.g., MCP servers).", - "example": [ + }, { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "f6b84a99-5e91-4203-be1e-131fe82e5984", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] + }, + "cpus": "500m", + "host_ids": [ "de3b1388-1f0c-42f1-a86c-59ab72f255ec" ], - "memory": "512M", + "memory": "500M", + "name": "n1", "orchestrator_opts": { "swarm": { "extra_labels": { @@ -11765,24 +11581,95 @@ ] } }, - "port": 0, - "service_id": "analytics-service", - "service_type": "mcp", - "version": "latest" + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "f6b84a99-5e91-4203-be1e-131fe82e5984", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "02f1a7db-fca8-4521-b57a-2a375c1ced51", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" }, { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "f6b84a99-5e91-4203-be1e-131fe82e5984", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] }, "cpus": "500m", "host_ids": [ - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "de3b1388-1f0c-42f1-a86c-59ab72f255ec" ], - "memory": "512M", + "memory": "500M", + "name": "n1", "orchestrator_opts": { "swarm": { "extra_labels": { @@ -11837,11 +11724,92 @@ ] } }, - "port": 0, - "service_id": "analytics-service", - "service_type": "mcp", - "version": "latest" - }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "f6b84a99-5e91-4203-be1e-131fe82e5984", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "02f1a7db-fca8-4521-b57a-2a375c1ced51", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + } + ], + "minItems": 1, + "maxItems": 9 + }, + "orchestrator_opts": { + "$ref": "#/components/schemas/OrchestratorOpts" + }, + "patroni_port": { + "type": "integer", + "description": "The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.", + "example": 8888, + "format": "int64", + "minimum": 0, + "maximum": 65535 + }, + "port": { + "type": "integer", + "description": "The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature.", + "example": 5432, + "format": "int64", + "minimum": 0, + "maximum": 65535 + }, + "postgres_version": { + "type": "string", + "description": "The Postgres version in 'major.minor' format.", + "example": "17.6", + "pattern": "^\\d{2}\\.\\d{1,2}$" + }, + "postgresql_conf": { + "type": "object", + "description": "Additional postgresql.conf settings. Will be merged with the settings provided by control-plane.", + "example": { + "max_connections": 1000 + }, + "maxLength": 64, + "additionalProperties": true + }, + "restore_config": { + "$ref": "#/components/schemas/RestoreConfigSpec" + }, + "services": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ServiceSpec" + }, + "description": "Service instances to run alongside the database (e.g., MCP servers).", + "example": [ { "config": { "llm_model": "gpt-4", @@ -11849,6 +11817,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "de3b1388-1f0c-42f1-a86c-59ab72f255ec", @@ -11913,27 +11888,106 @@ "service_id": "analytics-service", "service_type": "mcp", "version": "latest" - } - ] - }, - "spock_version": { - "type": "string", - "description": "The major version of the Spock extension.", - "example": "5", - "pattern": "^\\d{1}$" - } - }, - "example": { - "backup_config": { - "repositories": [ + }, { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "de3b1388-1f0c-42f1-a86c-59ab72f255ec" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "analytics-service", + "service_type": "mcp", + "version": "latest" + } + ] + }, + "spock_version": { + "type": "string", + "description": "The major version of the Spock extension.", + "example": "5", + "pattern": "^\\d{1}$" + } + }, + "example": { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", "storage-upload-chunk-size": "5MiB" }, "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", @@ -12060,8 +12114,6 @@ }, "cpus": "500m", "host_ids": [ - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "de3b1388-1f0c-42f1-a86c-59ab72f255ec" ], "memory": "500M", @@ -12205,8 +12257,149 @@ }, "cpus": "500m", "host_ids": [ - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "de3b1388-1f0c-42f1-a86c-59ab72f255ec" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "f6b84a99-5e91-4203-be1e-131fe82e5984", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "02f1a7db-fca8-4521-b57a-2a375c1ced51", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + }, + { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "f6b84a99-5e91-4203-be1e-131fe82e5984", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] + }, + "cpus": "500m", + "host_ids": [ "de3b1388-1f0c-42f1-a86c-59ab72f255ec" ], "memory": "500M", @@ -12402,6 +12595,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "de3b1388-1f0c-42f1-a86c-59ab72f255ec", @@ -12474,6 +12674,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "de3b1388-1f0c-42f1-a86c-59ab72f255ec", @@ -12538,18 +12745,176 @@ "service_id": "analytics-service", "service_type": "mcp", "version": "latest" - } - ], - "spock_version": "5" - }, - "required": [ - "database_name", - "nodes" - ] - }, - "DatabaseSpec2": { - "type": "object", - "properties": { + }, + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "de3b1388-1f0c-42f1-a86c-59ab72f255ec" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "analytics-service", + "service_type": "mcp", + "version": "latest" + }, + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "de3b1388-1f0c-42f1-a86c-59ab72f255ec" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "analytics-service", + "service_type": "mcp", + "version": "latest" + } + ], + "spock_version": "5" + }, + "required": [ + "database_name", + "nodes" + ] + }, + "DatabaseSpec2": { + "type": "object", + "properties": { "backup_config": { "$ref": "#/components/schemas/BackupConfigSpec" }, @@ -12818,138 +13183,106 @@ "source_node_name": "n1" }, "source_node": "n1" - } - ], - "minItems": 1, - "maxItems": 9 - }, - "orchestrator_opts": { - "$ref": "#/components/schemas/OrchestratorOpts" - }, - "patroni_port": { - "type": "integer", - "description": "The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.", - "example": 8888, - "format": "int64", - "minimum": 0, - "maximum": 65535 - }, - "port": { - "type": "integer", - "description": "The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature.", - "example": 5432, - "format": "int64", - "minimum": 0, - "maximum": 65535 - }, - "postgres_version": { - "type": "string", - "description": "The Postgres version in 'major.minor' format.", - "example": "17.6", - "pattern": "^\\d{2}\\.\\d{1,2}$" - }, - "postgresql_conf": { - "type": "object", - "description": "Additional postgresql.conf settings. Will be merged with the settings provided by control-plane.", - "example": { - "max_connections": 1000 - }, - "maxLength": 64, - "additionalProperties": true - }, - "restore_config": { - "$ref": "#/components/schemas/RestoreConfigSpec" - }, - "services": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceSpec2" - }, - "description": "Service instances to run alongside the database (e.g., MCP servers).", - "example": [ + }, { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] }, "cpus": "500m", "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], - "memory": "512M", + "memory": "500M", + "name": "n1", "orchestrator_opts": { "swarm": { "extra_labels": { @@ -13004,23 +13337,143 @@ ] } }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" }, { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] }, "cpus": "500m", "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], - "memory": "512M", + "memory": "500M", + "name": "n1", "orchestrator_opts": { "swarm": { "extra_labels": { @@ -13075,1174 +13528,681 @@ ] } }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - } - ] - }, - "spock_version": { - "type": "string", - "description": "The major version of the Spock extension.", - "example": "5", - "pattern": "^\\d{1}$" - } - }, - "example": { - "backup_config": { - "repositories": [ - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - } - ], - "schedules": [ - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - } - ] - }, - "cpus": "500m", - "database_name": "northwind", - "database_users": [ - { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": true, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" - }, - { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": true, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" - }, - { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": true, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" - } - ], - "memory": "500M", - "nodes": [ - { - "backup_config": { - "repositories": [ - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { + "restore_config": { + "repository": { "azure_account": "pgedge-backups", "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "azure_endpoint": "blob.core.usgovcloudapi.net", "azure_key": "YXpLZXk=", "base_path": "/backups", "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" }, "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "gcs_endpoint": "localhost", "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "s3_endpoint": "s3.us-east-1.amazonaws.com", "s3_key": "AKIAIOSFODNN7EXAMPLE", "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "s3_region": "us-east-1", "type": "s3" - } - ], - "schedules": [ - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - } - ] - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "500M", - "name": "n1", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "patroni_port": 8888, - "port": 5432, - "postgres_version": "17.6", - "postgresql_conf": { - "max_connections": 1000 - }, - "restore_config": { - "repository": { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - "restore_options": { - "set": "20250505-153628F", - "target": "123456", - "type": "xid" + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" }, - "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "source_database_name": "northwind", - "source_node_name": "n1" - }, - "source_node": "n1" - } - ], + "source_node": "n1" + } + ], + "minItems": 1, + "maxItems": 9 + }, "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } + "$ref": "#/components/schemas/OrchestratorOpts" + }, + "patroni_port": { + "type": "integer", + "description": "The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.", + "example": 8888, + "format": "int64", + "minimum": 0, + "maximum": 65535 + }, + "port": { + "type": "integer", + "description": "The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature.", + "example": 5432, + "format": "int64", + "minimum": 0, + "maximum": 65535 + }, + "postgres_version": { + "type": "string", + "description": "The Postgres version in 'major.minor' format.", + "example": "17.6", + "pattern": "^\\d{2}\\.\\d{1,2}$" }, - "patroni_port": 8888, - "port": 5432, - "postgres_version": "17.6", "postgresql_conf": { - "max_connections": 1000 + "type": "object", + "description": "Additional postgresql.conf settings. Will be merged with the settings provided by control-plane.", + "example": { + "max_connections": 1000 + }, + "maxLength": 64, + "additionalProperties": true }, "restore_config": { - "repository": { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - "restore_options": { - "set": "20250505-153628F", - "target": "123456", - "type": "xid" - }, - "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "source_database_name": "northwind", - "source_node_name": "n1" + "$ref": "#/components/schemas/RestoreConfigSpec" }, - "services": [ - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" + "services": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ServiceSpec2" + }, + "description": "Service instances to run alongside the database (e.g., MCP servers).", + "example": [ + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" }, - "id": "traefik-public" - } + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + }, + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" }, - "id": "traefik-public" + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + }, + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - } - ], - "spock_version": "5" - }, - "required": [ - "database_name", - "nodes" - ] - }, - "DatabaseSpec3": { - "type": "object", - "properties": { - "backup_config": { - "$ref": "#/components/schemas/BackupConfigSpec" - }, - "cpus": { - "type": "string", - "description": "The number of CPUs to allocate for the database and to use for tuning Postgres. Defaults to the number of available CPUs on the host. Can include an SI suffix, e.g. '500m' for 500 millicpus. Whether this limit is enforced depends on the orchestrator.", - "example": "500m", - "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + } + ] }, - "database_name": { + "spock_version": { "type": "string", - "description": "The name of the Postgres database.", - "example": "northwind", - "minLength": 1, - "maxLength": 31 - }, - "database_users": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DatabaseUserSpec" - }, - "description": "The users to create for this database.", - "example": [ + "description": "The major version of the Spock extension.", + "example": "5", + "pattern": "^\\d{1}$" + } + }, + "example": { + "backup_config": { + "repositories": [ { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": false, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" }, { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": false, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" - }, + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": false, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" } ], - "maxItems": 16 - }, - "memory": { - "type": "string", - "description": "The amount of memory in SI or IEC notation to allocate for the database and to use for tuning Postgres. Defaults to the total available memory on the host. Whether this limit is enforced depends on the orchestrator.", - "example": "500M", - "maxLength": 16 + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] }, - "nodes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DatabaseNodeSpec3" + "cpus": "500m", + "database_name": "northwind", + "database_users": [ + { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": true, + "password": "secret", + "roles": [ + "pgedge_superuser" + ], + "username": "admin" }, - "description": "The Spock nodes for this database.", - "example": [ - { - "backup_config": { - "repositories": [ - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - } - ], - "schedules": [ - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" + { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": true, + "password": "secret", + "roles": [ + "pgedge_superuser" + ], + "username": "admin" + }, + { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": true, + "password": "secret", + "roles": [ + "pgedge_superuser" + ], + "username": "admin" + } + ], + "memory": "500M", + "nodes": [ + { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - } - ] - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "500M", - "name": "n1", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "patroni_port": 8888, - "port": 5432, - "postgres_version": "17.6", - "postgresql_conf": { - "max_connections": 1000 - }, - "restore_config": { - "repository": { + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { "azure_account": "pgedge-backups", "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "azure_endpoint": "blob.core.usgovcloudapi.net", "azure_key": "YXpLZXk=", "base_path": "/backups", "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "gcs_endpoint": "localhost", "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "s3_endpoint": "s3.us-east-1.amazonaws.com", "s3_key": "AKIAIOSFODNN7EXAMPLE", "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "s3_region": "us-east-1", "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" }, - "restore_options": { - "set": "20250505-153628F", - "target": "123456", - "type": "xid" + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" }, - "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "source_database_name": "northwind", - "source_node_name": "n1" - }, - "source_node": "n1" + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] }, - { - "backup_config": { - "repositories": [ + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" + "id": "traefik-public" }, { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" + "id": "traefik-public" }, { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" + "id": "traefik-public" } ], - "schedules": [ + "extra_volumes": [ { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" }, { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" }, { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" } ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "500M", - "name": "n1", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "patroni_port": 8888, - "port": 5432, - "postgres_version": "17.6", - "postgresql_conf": { - "max_connections": 1000 + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" }, - "restore_config": { - "repository": { + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + }, + { + "backup_config": { + "repositories": [ + { "azure_account": "pgedge-backups", "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "azure_endpoint": "blob.core.usgovcloudapi.net", "azure_key": "YXpLZXk=", "base_path": "/backups", "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "gcs_endpoint": "localhost", "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "s3_endpoint": "s3.us-east-1.amazonaws.com", "s3_key": "AKIAIOSFODNN7EXAMPLE", @@ -14250,189 +14210,190 @@ "s3_region": "us-east-1", "type": "s3" }, - "restore_options": { - "set": "20250505-153628F", - "target": "123456", - "type": "xid" + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" }, - "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "source_database_name": "northwind", - "source_node_name": "n1" - }, - "source_node": "n1" + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] }, - { - "backup_config": { - "repositories": [ + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" + "id": "traefik-public" }, { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" + "id": "traefik-public" }, { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" + "id": "traefik-public" } ], - "schedules": [ + "extra_volumes": [ { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" }, { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" }, { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" } ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "500M", - "name": "n1", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "patroni_port": 8888, - "port": 5432, - "postgres_version": "17.6", - "postgresql_conf": { - "max_connections": 1000 + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" }, - "restore_config": { - "repository": { + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + }, + { + "backup_config": { + "repositories": [ + { "azure_account": "pgedge-backups", "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "azure_endpoint": "blob.core.usgovcloudapi.net", "azure_key": "YXpLZXk=", "base_path": "/backups", "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "gcs_endpoint": "localhost", "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", "s3_endpoint": "s3.us-east-1.amazonaws.com", "s3_key": "AKIAIOSFODNN7EXAMPLE", @@ -14440,592 +14401,282 @@ "s3_region": "us-east-1", "type": "s3" }, - "restore_options": { - "set": "20250505-153628F", - "target": "123456", - "type": "xid" + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" }, - "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "source_database_name": "northwind", - "source_node_name": "n1" - }, - "source_node": "n1" - } - ], - "minItems": 1, - "maxItems": 9 - }, - "orchestrator_opts": { - "$ref": "#/components/schemas/OrchestratorOpts" - }, - "patroni_port": { - "type": "integer", - "description": "The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.", - "example": 8888, - "format": "int64", - "minimum": 0, - "maximum": 65535 - }, - "port": { - "type": "integer", - "description": "The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature.", - "example": 5432, - "format": "int64", - "minimum": 0, - "maximum": 65535 - }, - "postgres_version": { - "type": "string", - "description": "The Postgres version in 'major.minor' format.", - "example": "17.6", - "pattern": "^\\d{2}\\.\\d{1,2}$" - }, - "postgresql_conf": { - "type": "object", - "description": "Additional postgresql.conf settings. Will be merged with the settings provided by control-plane.", - "example": { - "max_connections": 1000 - }, - "maxLength": 64, - "additionalProperties": true - }, - "restore_config": { - "$ref": "#/components/schemas/RestoreConfigSpec" - }, - "services": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceSpec3" - }, - "description": "Service instances to run alongside the database (e.g., MCP servers).", - "example": [ - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" + ] }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - } - ] - }, - "spock_version": { - "type": "string", - "description": "The major version of the Spock extension.", - "example": "5", - "pattern": "^\\d{1}$" - } - }, - "example": { - "backup_config": { - "repositories": [ - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - } - ], - "schedules": [ - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - } - ] - }, - "cpus": "500m", - "database_name": "northwind", - "database_users": [ - { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": false, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" - }, - { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": false, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" - }, - { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": false, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" + "source_node": "n1" } ], - "memory": "500M", - "nodes": [ - { - "backup_config": { - "repositories": [ - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - } - ], - "schedules": [ - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - } - ] + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "services": [ + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], - "memory": "500M", - "name": "n1", + "memory": "512M", "orchestrator_opts": { "swarm": { "extra_labels": { @@ -15080,147 +14731,35 @@ ] } }, - "patroni_port": 8888, - "port": 5432, - "postgres_version": "17.6", - "postgresql_conf": { - "max_connections": 1000 - }, - "restore_config": { - "repository": { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - "restore_options": { - "set": "20250505-153628F", - "target": "123456", - "type": "xid" - }, - "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "source_database_name": "northwind", - "source_node_name": "n1" - }, - "source_node": "n1" + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" }, { - "backup_config": { - "repositories": [ - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - } - ], - "schedules": [ - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - } - ] - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "500M", - "name": "n1", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" }, "extra_networks": [ { @@ -15270,142 +14809,30 @@ ] } }, - "patroni_port": 8888, - "port": 5432, - "postgres_version": "17.6", - "postgresql_conf": { - "max_connections": 1000 - }, - "restore_config": { - "repository": { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - "restore_options": { - "set": "20250505-153628F", - "target": "123456", - "type": "xid" - }, - "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "source_database_name": "northwind", - "source_node_name": "n1" - }, - "source_node": "n1" + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" }, { - "backup_config": { - "repositories": [ - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - } - ], - "schedules": [ - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - } - ] + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], - "memory": "500M", - "name": "n1", + "memory": "512M", "orchestrator_opts": { "swarm": { "extra_labels": { @@ -15460,153 +14887,35 @@ ] } }, - "patroni_port": 8888, - "port": 5432, - "postgres_version": "17.6", - "postgresql_conf": { - "max_connections": 1000 + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + }, + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." }, - "restore_config": { - "repository": { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - "restore_options": { - "set": "20250505-153628F", - "target": "123456", - "type": "xid" - }, - "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "source_database_name": "northwind", - "source_node_name": "n1" - }, - "source_node": "n1" - } - ], - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "patroni_port": 8888, - "port": 5432, - "postgres_version": "17.6", - "postgresql_conf": { - "max_connections": 1000 - }, - "restore_config": { - "repository": { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - "restore_options": { - "set": "20250505-153628F", - "target": "123456", - "type": "xid" - }, - "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "source_database_name": "northwind", - "source_node_name": "n1" - }, - "services": [ - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" }, "extra_networks": [ { @@ -15660,335 +14969,312 @@ "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", "service_type": "mcp", "version": "latest" + } + ], + "spock_version": "5" + }, + "required": [ + "database_name", + "nodes" + ] + }, + "DatabaseSpec3": { + "type": "object", + "properties": { + "backup_config": { + "$ref": "#/components/schemas/BackupConfigSpec" + }, + "cpus": { + "type": "string", + "description": "The number of CPUs to allocate for the database and to use for tuning Postgres. Defaults to the number of available CPUs on the host. Can include an SI suffix, e.g. '500m' for 500 millicpus. Whether this limit is enforced depends on the orchestrator.", + "example": "500m", + "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" + }, + "database_name": { + "type": "string", + "description": "The name of the Postgres database.", + "example": "northwind", + "minLength": 1, + "maxLength": 31 + }, + "database_users": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DatabaseUserSpec" }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." + "description": "The users to create for this database.", + "example": [ + { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": false, + "password": "secret", + "roles": [ + "pgedge_superuser" + ], + "username": "admin" }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ + { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": false, + "password": "secret", + "roles": [ + "pgedge_superuser" + ], + "username": "admin" + }, + { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": false, + "password": "secret", + "roles": [ + "pgedge_superuser" + ], + "username": "admin" + } + ], + "maxItems": 16 + }, + "memory": { + "type": "string", + "description": "The amount of memory in SI or IEC notation to allocate for the database and to use for tuning Postgres. Defaults to the total available memory on the host. Whether this limit is enforced depends on the orchestrator.", + "example": "500M", + "maxLength": 16 + }, + "nodes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DatabaseNodeSpec3" + }, + "description": "The Spock nodes for this database.", + "example": [ + { + "backup_config": { + "repositories": [ { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, - "id": "traefik-public" + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" }, { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, - "id": "traefik-public" + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" } ], - "extra_volumes": [ + "schedules": [ { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" }, { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" }, { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" } ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" + }, + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" }, - "id": "traefik-public" + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + }, + { + "backup_config": { + "repositories": [ { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - } - ], - "spock_version": "5" - }, - "required": [ - "database_name", - "nodes" - ] - }, - "DatabaseSpec4": { - "type": "object", - "properties": { - "backup_config": { - "$ref": "#/components/schemas/BackupConfigSpec" - }, - "cpus": { - "type": "string", - "description": "The number of CPUs to allocate for the database and to use for tuning Postgres. Defaults to the number of available CPUs on the host. Can include an SI suffix, e.g. '500m' for 500 millicpus. Whether this limit is enforced depends on the orchestrator.", - "example": "500m", - "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" - }, - "database_name": { - "type": "string", - "description": "The name of the Postgres database.", - "example": "northwind", - "minLength": 1, - "maxLength": 31 - }, - "database_users": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DatabaseUserSpec" - }, - "description": "The users to create for this database.", - "example": [ - { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": false, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" - }, - { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": false, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" - }, - { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": false, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" - } - ], - "maxItems": 16 - }, - "memory": { - "type": "string", - "description": "The amount of memory in SI or IEC notation to allocate for the database and to use for tuning Postgres. Defaults to the total available memory on the host. Whether this limit is enforced depends on the orchestrator.", - "example": "500M", - "maxLength": 16 - }, - "nodes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DatabaseNodeSpec4" - }, - "description": "The Spock nodes for this database.", - "example": [ - { - "backup_config": { - "repositories": [ - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, { "azure_account": "pgedge-backups", "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", @@ -16152,67 +15438,105 @@ "source_node_name": "n1" }, "source_node": "n1" - } - ], - "minItems": 1, - "maxItems": 9 - }, - "orchestrator_opts": { - "$ref": "#/components/schemas/OrchestratorOpts" - }, - "patroni_port": { - "type": "integer", - "description": "The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.", - "example": 8888, - "format": "int64", - "minimum": 0, - "maximum": 65535 - }, - "port": { - "type": "integer", - "description": "The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature.", - "example": 5432, - "format": "int64", - "minimum": 0, - "maximum": 65535 - }, - "postgres_version": { - "type": "string", - "description": "The Postgres version in 'major.minor' format.", - "example": "17.6", - "pattern": "^\\d{2}\\.\\d{1,2}$" - }, - "postgresql_conf": { - "type": "object", - "description": "Additional postgresql.conf settings. Will be merged with the settings provided by control-plane.", - "example": { - "max_connections": 1000 - }, - "maxLength": 64, - "additionalProperties": true - }, - "restore_config": { - "$ref": "#/components/schemas/RestoreConfigSpec" - }, - "services": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceSpec4" - }, - "description": "Service instances to run alongside the database (e.g., MCP servers).", - "example": [ + }, { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] }, "cpus": "500m", "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], - "memory": "512M", + "memory": "500M", + "name": "n1", "orchestrator_opts": { "swarm": { "extra_labels": { @@ -16267,11 +15591,92 @@ ] } }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + } + ], + "minItems": 1, + "maxItems": 9 + }, + "orchestrator_opts": { + "$ref": "#/components/schemas/OrchestratorOpts" + }, + "patroni_port": { + "type": "integer", + "description": "The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.", + "example": 8888, + "format": "int64", + "minimum": 0, + "maximum": 65535 + }, + "port": { + "type": "integer", + "description": "The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature.", + "example": 5432, + "format": "int64", + "minimum": 0, + "maximum": 65535 + }, + "postgres_version": { + "type": "string", + "description": "The Postgres version in 'major.minor' format.", + "example": "17.6", + "pattern": "^\\d{2}\\.\\d{1,2}$" + }, + "postgresql_conf": { + "type": "object", + "description": "Additional postgresql.conf settings. Will be merged with the settings provided by control-plane.", + "example": { + "max_connections": 1000 + }, + "maxLength": 64, + "additionalProperties": true + }, + "restore_config": { + "$ref": "#/components/schemas/RestoreConfigSpec" + }, + "services": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ServiceSpec3" + }, + "description": "Service instances to run alongside the database (e.g., MCP servers).", + "example": [ { "config": { "llm_model": "gpt-4", @@ -16279,6 +15684,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -16350,6 +15762,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -16421,6 +15840,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -17009,44 +16435,234 @@ "source_node_name": "n1" }, "source_node": "n1" - } - ], - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + }, + { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" }, - "id": "traefik-public" + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] + }, + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + } + ], + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" } ], "extra_volumes": [ @@ -17109,6 +16725,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -17180,6 +16803,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -17251,6 +16881,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -17314,50 +16951,128 @@ "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", "service_type": "mcp", "version": "latest" - } - ], - "spock_version": "5" - }, - "required": [ - "database_name", - "nodes" - ] - }, - "DatabaseSpec5": { - "type": "object", - "properties": { - "backup_config": { - "$ref": "#/components/schemas/BackupConfigSpec" - }, - "cpus": { - "type": "string", - "description": "The number of CPUs to allocate for the database and to use for tuning Postgres. Defaults to the number of available CPUs on the host. Can include an SI suffix, e.g. '500m' for 500 millicpus. Whether this limit is enforced depends on the orchestrator.", - "example": "500m", - "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" - }, - "database_name": { - "type": "string", - "description": "The name of the Postgres database.", - "example": "northwind", - "minLength": 1, - "maxLength": 31 - }, - "database_users": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DatabaseUserSpec" }, - "description": "The users to create for this database.", - "example": [ - { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" ], - "db_owner": false, - "password": "secret", - "roles": [ + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + } + ], + "spock_version": "5" + }, + "required": [ + "database_name", + "nodes" + ] + }, + "DatabaseSpec4": { + "type": "object", + "properties": { + "backup_config": { + "$ref": "#/components/schemas/BackupConfigSpec" + }, + "cpus": { + "type": "string", + "description": "The number of CPUs to allocate for the database and to use for tuning Postgres. Defaults to the number of available CPUs on the host. Can include an SI suffix, e.g. '500m' for 500 millicpus. Whether this limit is enforced depends on the orchestrator.", + "example": "500m", + "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" + }, + "database_name": { + "type": "string", + "description": "The name of the Postgres database.", + "example": "northwind", + "minLength": 1, + "maxLength": 31 + }, + "database_users": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DatabaseUserSpec" + }, + "description": "The users to create for this database.", + "example": [ + { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": false, + "password": "secret", + "roles": [ "pgedge_superuser" ], "username": "admin" @@ -17400,7 +17115,7 @@ "nodes": { "type": "array", "items": { - "$ref": "#/components/schemas/DatabaseNodeSpec5" + "$ref": "#/components/schemas/DatabaseNodeSpec4" }, "description": "The Spock nodes for this database.", "example": [ @@ -17593,67 +17308,106 @@ "source_node_name": "n1" }, "source_node": "n1" - } - ], - "minItems": 1, - "maxItems": 9 - }, - "orchestrator_opts": { - "$ref": "#/components/schemas/OrchestratorOpts" - }, - "patroni_port": { - "type": "integer", - "description": "The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.", - "example": 8888, - "format": "int64", - "minimum": 0, - "maximum": 65535 - }, - "port": { - "type": "integer", - "description": "The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature.", - "example": 5432, - "format": "int64", - "minimum": 0, - "maximum": 65535 - }, - "postgres_version": { - "type": "string", - "description": "The Postgres version in 'major.minor' format.", - "example": "17.6", - "pattern": "^\\d{2}\\.\\d{1,2}$" - }, - "postgresql_conf": { - "type": "object", - "description": "Additional postgresql.conf settings. Will be merged with the settings provided by control-plane.", - "example": { - "max_connections": 1000 - }, - "maxLength": 64, - "additionalProperties": true - }, - "restore_config": { - "$ref": "#/components/schemas/RestoreConfigSpec" - }, - "services": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceSpec5" - }, - "description": "Service instances to run alongside the database (e.g., MCP servers).", - "example": [ + }, { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] }, "cpus": "500m", "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], - "memory": "512M", - "orchestrator_opts": { + "memory": "500M", + "name": "n1", + "orchestrator_opts": { "swarm": { "extra_labels": { "traefik.enable": "true", @@ -17707,22 +17461,142 @@ ] } }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" }, { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] }, "cpus": "500m", "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], - "memory": "512M", + "memory": "500M", + "name": "n1", "orchestrator_opts": { "swarm": { "extra_labels": { @@ -17777,11 +17651,92 @@ ] } }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + } + ], + "minItems": 1, + "maxItems": 9 + }, + "orchestrator_opts": { + "$ref": "#/components/schemas/OrchestratorOpts" + }, + "patroni_port": { + "type": "integer", + "description": "The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.", + "example": 8888, + "format": "int64", + "minimum": 0, + "maximum": 65535 + }, + "port": { + "type": "integer", + "description": "The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature.", + "example": 5432, + "format": "int64", + "minimum": 0, + "maximum": 65535 + }, + "postgres_version": { + "type": "string", + "description": "The Postgres version in 'major.minor' format.", + "example": "17.6", + "pattern": "^\\d{2}\\.\\d{1,2}$" + }, + "postgresql_conf": { + "type": "object", + "description": "Additional postgresql.conf settings. Will be merged with the settings provided by control-plane.", + "example": { + "max_connections": 1000 + }, + "maxLength": 64, + "additionalProperties": true + }, + "restore_config": { + "$ref": "#/components/schemas/RestoreConfigSpec" + }, + "services": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ServiceSpec4" + }, + "description": "Service instances to run alongside the database (e.g., MCP servers).", + "example": [ { "config": { "llm_model": "gpt-4", @@ -17789,7 +17744,15 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -17859,7 +17822,15 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -17921,24 +17892,180 @@ "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", "service_type": "mcp", "version": "latest" - } - ] - }, - "spock_version": { - "type": "string", - "description": "The major version of the Spock extension.", - "example": "5", - "pattern": "^\\d{1}$" - } - }, - "example": { - "backup_config": { - "repositories": [ + }, { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + }, + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + } + ] + }, + "spock_version": { + "type": "string", + "description": "The major version of the Spock extension.", + "example": "5", + "pattern": "^\\d{1}$" + } + }, + "example": { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", "base_path": "/backups", "custom_options": { "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", @@ -18736,7 +18863,93 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + }, + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -18806,7 +19019,15 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -18876,7 +19097,15 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -18947,7 +19176,7 @@ "nodes" ] }, - "DatabaseSpec6": { + "DatabaseSpec5": { "type": "object", "properties": { "backup_config": { @@ -19024,7 +19253,7 @@ "nodes": { "type": "array", "items": { - "$ref": "#/components/schemas/DatabaseNodeSpec6" + "$ref": "#/components/schemas/DatabaseNodeSpec5" }, "description": "The Spock nodes for this database.", "example": [ @@ -19452,7 +19681,7 @@ "services": { "type": "array", "items": { - "$ref": "#/components/schemas/ServiceSpec6" + "$ref": "#/components/schemas/ServiceSpec5" }, "description": "Service instances to run alongside the database (e.g., MCP servers).", "example": [ @@ -19463,8 +19692,14 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -19534,150 +19769,14 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "512M", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "port": 0, - "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "service_type": "mcp", - "version": "latest" - }, - { - "config": { - "llm_model": "gpt-4", - "llm_provider": "openai", - "openai_api_key": "sk-..." - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -20074,46 +20173,236 @@ "source_node_name": "n1" }, "source_node": "n1" - } - ], - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + }, + { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" }, - "id": "traefik-public" - } - ], + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] + }, + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + } + ], + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], "extra_volumes": [ { "destination_path": "/backups/container", @@ -20174,8 +20463,14 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -20245,8 +20540,14 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -20316,8 +20617,14 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -20387,8 +20694,14 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -20459,7 +20772,7 @@ "nodes" ] }, - "DatabaseSpec7": { + "DatabaseSpec6": { "type": "object", "properties": { "backup_config": { @@ -20536,7 +20849,7 @@ "nodes": { "type": "array", "items": { - "$ref": "#/components/schemas/DatabaseNodeSpec7" + "$ref": "#/components/schemas/DatabaseNodeSpec6" }, "description": "The Spock nodes for this database.", "example": [ @@ -21154,7 +21467,7 @@ "services": { "type": "array", "items": { - "$ref": "#/components/schemas/ServiceSpec7" + "$ref": "#/components/schemas/ServiceSpec6" }, "description": "Service instances to run alongside the database (e.g., MCP servers).", "example": [ @@ -21165,6 +21478,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -21236,6 +21556,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -21299,88 +21626,244 @@ "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", "service_type": "mcp", "version": "latest" - } - ] - }, - "spock_version": { - "type": "string", - "description": "The major version of the Spock extension.", - "example": "5", - "pattern": "^\\d{1}$" - } - }, - "example": { - "backup_config": { - "repositories": [ - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" }, { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - } + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + }, + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + } + ] + }, + "spock_version": { + "type": "string", + "description": "The major version of the Spock extension.", + "example": "5", + "pattern": "^\\d{1}$" + } + }, + "example": { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } ], "schedules": [ { @@ -21824,196 +22307,6 @@ "source_node_name": "n1" }, "source_node": "n1" - }, - { - "backup_config": { - "repositories": [ - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", - "storage-upload-chunk-size": "5MiB" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "retention_full": 2, - "retention_full_type": "count", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - } - ], - "schedules": [ - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - }, - { - "cron_expression": "0 6 * * ?", - "id": "daily-full-backup", - "type": "full" - } - ] - }, - "cpus": "500m", - "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", - "76f9b8c0-4958-11f0-a489-3bb29577c696" - ], - "memory": "500M", - "name": "n1", - "orchestrator_opts": { - "swarm": { - "extra_labels": { - "traefik.enable": "true", - "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" - }, - "extra_networks": [ - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - } - ], - "extra_volumes": [ - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - } - ] - } - }, - "patroni_port": 8888, - "port": 5432, - "postgres_version": "17.6", - "postgresql_conf": { - "max_connections": 1000 - }, - "restore_config": { - "repository": { - "azure_account": "pgedge-backups", - "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "azure_endpoint": "blob.core.usgovcloudapi.net", - "azure_key": "YXpLZXk=", - "base_path": "/backups", - "custom_options": { - "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" - }, - "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "gcs_endpoint": "localhost", - "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", - "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", - "s3_endpoint": "s3.us-east-1.amazonaws.com", - "s3_key": "AKIAIOSFODNN7EXAMPLE", - "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "s3_region": "us-east-1", - "type": "s3" - }, - "restore_options": { - "set": "20250505-153628F", - "target": "123456", - "type": "xid" - }, - "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "source_database_name": "northwind", - "source_node_name": "n1" - }, - "source_node": "n1" } ], "orchestrator_opts": { @@ -22114,6 +22407,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -22185,6 +22485,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -22256,6 +22563,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -22328,629 +22642,2455 @@ "nodes" ] }, - "DatabaseSummary": { + "DatabaseSpec7": { "type": "object", "properties": { - "created_at": { + "backup_config": { + "$ref": "#/components/schemas/BackupConfigSpec" + }, + "cpus": { "type": "string", - "description": "The time that the database was created.", - "example": "2025-01-01T01:30:00Z", - "format": "date-time" + "description": "The number of CPUs to allocate for the database and to use for tuning Postgres. Defaults to the number of available CPUs on the host. Can include an SI suffix, e.g. '500m' for 500 millicpus. Whether this limit is enforced depends on the orchestrator.", + "example": "500m", + "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" }, - "id": { + "database_name": { "type": "string", - "description": "A user-specified identifier. Must be 1-63 characters, contain only lower-cased letters and hyphens, start and end with a letter or number, and not contain consecutive hyphens.", - "example": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "description": "The name of the Postgres database.", + "example": "northwind", "minLength": 1, - "maxLength": 63 + "maxLength": 31 }, - "instances": { + "database_users": { "type": "array", "items": { - "$ref": "#/components/schemas/Instance" + "$ref": "#/components/schemas/DatabaseUserSpec" }, - "description": "All of the instances in the database.", + "description": "The users to create for this database.", "example": [ { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": false, + "password": "secret", + "roles": [ + "pgedge_superuser" + ], + "username": "admin" }, { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - } - ] - }, - "state": { - "type": "string", - "description": "Current state of the database.", - "example": "backing_up", - "enum": [ - "creating", - "modifying", - "available", - "deleting", - "degraded", - "failed", - "backing_up", - "restoring", - "unknown" - ] - }, - "tenant_id": { - "type": "string", - "description": "A user-specified identifier. Must be 1-63 characters, contain only lower-cased letters and hyphens, start and end with a letter or number, and not contain consecutive hyphens.", - "example": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "minLength": 1, - "maxLength": 63 - }, - "updated_at": { - "type": "string", - "description": "The time that the database was last updated.", - "example": "2025-01-01T02:30:00Z", - "format": "date-time" - } - }, - "example": { - "created_at": "2025-01-01T01:30:00Z", - "id": "02f1a7db-fca8-4521-b57a-2a375c1ced51", - "instances": [ - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } + "db_owner": false, + "password": "secret", + "roles": [ + "pgedge_superuser" ], - "version": "4.10.0" + "username": "admin" }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" + { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } + "db_owner": false, + "password": "secret", + "roles": [ + "pgedge_superuser" ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - } - ], - "state": "creating", - "tenant_id": "8210ec10-2dca-406c-ac4a-0661d2189954", - "updated_at": "2025-01-01T02:30:00Z" - }, - "required": [ - "id", - "created_at", - "updated_at", - "state" - ] - }, - "DatabaseUserSpec": { - "type": "object", - "properties": { - "attributes": { - "type": "array", - "items": { - "type": "string", - "example": "Eos natus." - }, - "description": "The attributes to assign to this database user.", - "example": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" + "username": "admin" + } ], "maxItems": 16 }, - "db_owner": { - "type": "boolean", - "description": "If true, this user will be granted database ownership.", - "example": true - }, - "password": { - "type": "string", - "description": "The password for this database user. This field will be excluded from the response of all endpoints. It can also be omitted from update requests to keep the current value.", - "example": "secret", - "minLength": 1 - }, - "roles": { - "type": "array", - "items": { - "type": "string", - "example": "Excepturi consectetur accusantium ut." - }, - "description": "The roles to assign to this database user.", - "example": [ - "pgedge_superuser" - ], - "maxItems": 16 - }, - "username": { - "type": "string", - "description": "The username for this database user.", - "example": "admin", - "minLength": 1 - } - }, - "example": { - "attributes": [ - "LOGIN", - "CREATEDB", - "CREATEROLE" - ], - "db_owner": true, - "password": "secret", - "roles": [ - "pgedge_superuser" - ], - "username": "admin" - }, - "required": [ - "username" - ] - }, - "DeleteDatabaseResponse": { - "type": "object", - "properties": { - "task": { - "$ref": "#/components/schemas/Task" - } - }, - "example": { - "task": { - "created_at": "2025-06-18T16:48:59Z", - "database_id": "storefront", - "status": "pending", - "task_id": "019783f1-9f17-77e7-9a08-fa6ab39e3b29", - "type": "delete" - } - }, - "required": [ - "task" - ] - }, - "EtcdClusterMember": { - "type": "object", - "properties": { - "client_urls": { - "type": "array", - "items": { - "type": "string", - "example": "Voluptatem beatae quasi ipsum officiis et." - }, - "description": "The Etcd client endpoint for this cluster member.", - "example": [ - "http://192.168.1.1:2379" - ] - }, - "name": { + "memory": { "type": "string", - "description": "The name of the Etcd cluster member.", - "example": "host-1" + "description": "The amount of memory in SI or IEC notation to allocate for the database and to use for tuning Postgres. Defaults to the total available memory on the host. Whether this limit is enforced depends on the orchestrator.", + "example": "500M", + "maxLength": 16 }, - "peer_urls": { - "type": "array", - "items": { - "type": "string", - "example": "Delectus sit quas." - }, - "description": "The Etcd peer endpoint for this cluster member.", - "example": [ - "http://192.168.1.1:2380" - ] - } - }, - "example": { - "client_urls": [ - "http://192.168.1.1:2379" - ], - "name": "host-1", - "peer_urls": [ - "http://192.168.1.1:2380" - ] - }, - "required": [ - "name", - "peer_urls", - "client_urls" - ] - }, - "ExtraNetworkSpec": { - "type": "object", - "properties": { - "aliases": { + "nodes": { "type": "array", "items": { - "type": "string", - "example": "Placeat placeat a enim." + "$ref": "#/components/schemas/DatabaseNodeSpec7" }, - "description": "Optional network-scoped aliases for the container.", + "description": "The Spock nodes for this database.", "example": [ - "pg-db", - "db-alias" - ], - "maxItems": 8 - }, - "driver_opts": { - "type": "object", - "description": "Optional driver options for the network connection.", - "example": { - "com.docker.network.endpoint.expose": "true" - }, - "additionalProperties": { - "type": "string", - "example": "Voluptates maxime hic aut omnis magnam." - } - }, - "id": { - "type": "string", - "description": "The name or ID of the network to connect to.", - "example": "traefik-public" - } - }, - "description": "Describes an additional Docker network to attach the container to.", - "example": { - "aliases": [ - "pg-db", - "db-alias" - ], - "driver_opts": { - "com.docker.network.endpoint.expose": "true" - }, - "id": "traefik-public" - }, - "required": [ - "id" - ] - }, - "ExtraVolumesSpec": { - "type": "object", - "properties": { - "destination_path": { - "type": "string", - "description": "The path inside the container where the volume will be mounted.", - "example": "/backups/container", - "maxLength": 256 - }, - "host_path": { - "type": "string", - "description": "The host path for the volume.", - "example": "/Users/user/backups/host", - "maxLength": 256 - } - }, - "description": "Extra volumes to mount from the host to the database container.", - "example": { - "destination_path": "/backups/container", - "host_path": "/Users/user/backups/host" - }, - "required": [ - "host_path", - "destination_path" - ] - }, - "FailoverDatabaseNodeRequest": { - "type": "object", - "properties": { - "candidate_instance_id": { - "type": "string", - "description": "Optional instance_id of the replica to promote. If omitted, a candidate will be selected.", - "example": "68f50878-44d2-4524-a823-e31bd478706d-n1-689qacsi" - }, - "database_id": { - "type": "string", - "description": "A user-specified identifier. Must be 1-63 characters, contain only lower-cased letters and hyphens, start and end with a letter or number, and not contain consecutive hyphens.", - "example": "76f9b8c0-4958-11f0-a489-3bb29577c696", - "minLength": 1, - "maxLength": 63 - }, - "node_name": { - "type": "string", - "description": "Name of the node to initiate the failover from.", - "example": "n1", - "pattern": "n[0-9]+" - }, - "skip_validation": { - "type": "boolean", - "description": "If true, skip the health validations that prevent running failover on a healthy cluster.", - "default": false, - "example": false - } - }, - "example": { - "candidate_instance_id": "68f50878-44d2-4524-a823-e31bd478706d-n1-689qacsi", - "database_id": "my-app", - "node_name": "n1", - "skip_validation": true - }, - "required": [ - "database_id", - "node_name" - ] - }, - "FailoverDatabaseNodeRequest2": { - "type": "object", - "properties": { - "candidate_instance_id": { - "type": "string", - "description": "Optional instance_id of the replica to promote. If omitted, a candidate will be selected.", - "example": "68f50878-44d2-4524-a823-e31bd478706d-n1-689qacsi" - }, - "skip_validation": { - "type": "boolean", - "description": "If true, skip the health validations that prevent running failover on a healthy cluster.", - "default": false, - "example": false - } - }, - "example": { - "candidate_instance_id": "68f50878-44d2-4524-a823-e31bd478706d-n1-689qacsi", - "skip_validation": true - } - }, - "FailoverDatabaseNodeResponse": { - "type": "object", - "properties": { - "task": { - "$ref": "#/components/schemas/Task" - } - }, - "example": { - "task": { - "created_at": "2025-06-18T17:54:28Z", - "database_id": "storefront", - "status": "pending", - "task_id": "0197842d-9082-7496-b787-77bd2e11809f", - "type": "failover" - } - }, - "required": [ - "task" - ] + { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] + }, + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + }, + { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] + }, + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + }, + { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] + }, + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + } + ], + "minItems": 1, + "maxItems": 9 + }, + "orchestrator_opts": { + "$ref": "#/components/schemas/OrchestratorOpts" + }, + "patroni_port": { + "type": "integer", + "description": "The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.", + "example": 8888, + "format": "int64", + "minimum": 0, + "maximum": 65535 + }, + "port": { + "type": "integer", + "description": "The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature.", + "example": 5432, + "format": "int64", + "minimum": 0, + "maximum": 65535 + }, + "postgres_version": { + "type": "string", + "description": "The Postgres version in 'major.minor' format.", + "example": "17.6", + "pattern": "^\\d{2}\\.\\d{1,2}$" + }, + "postgresql_conf": { + "type": "object", + "description": "Additional postgresql.conf settings. Will be merged with the settings provided by control-plane.", + "example": { + "max_connections": 1000 + }, + "maxLength": 64, + "additionalProperties": true + }, + "restore_config": { + "$ref": "#/components/schemas/RestoreConfigSpec" + }, + "services": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ServiceSpec7" + }, + "description": "Service instances to run alongside the database (e.g., MCP servers).", + "example": [ + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + }, + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + } + ] + }, + "spock_version": { + "type": "string", + "description": "The major version of the Spock extension.", + "example": "5", + "pattern": "^\\d{1}$" + } + }, + "example": { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] + }, + "cpus": "500m", + "database_name": "northwind", + "database_users": [ + { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": false, + "password": "secret", + "roles": [ + "pgedge_superuser" + ], + "username": "admin" + }, + { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": false, + "password": "secret", + "roles": [ + "pgedge_superuser" + ], + "username": "admin" + }, + { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": false, + "password": "secret", + "roles": [ + "pgedge_superuser" + ], + "username": "admin" + } + ], + "memory": "500M", + "nodes": [ + { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] + }, + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + }, + { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] + }, + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + }, + { + "backup_config": { + "repositories": [ + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab", + "storage-upload-chunk-size": "5MiB" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "retention_full": 2, + "retention_full_type": "count", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + } + ], + "schedules": [ + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + }, + { + "cron_expression": "0 6 * * ?", + "id": "daily-full-backup", + "type": "full" + } + ] + }, + "cpus": "500m", + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "500M", + "name": "n1", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "source_node": "n1" + } + ], + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "patroni_port": 8888, + "port": 5432, + "postgres_version": "17.6", + "postgresql_conf": { + "max_connections": 1000 + }, + "restore_config": { + "repository": { + "azure_account": "pgedge-backups", + "azure_container": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "azure_endpoint": "blob.core.usgovcloudapi.net", + "azure_key": "YXpLZXk=", + "base_path": "/backups", + "custom_options": { + "s3-kms-key-id": "1234abcd-12ab-34cd-56ef-1234567890ab" + }, + "gcs_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "gcs_endpoint": "localhost", + "gcs_key": "ZXhhbXBsZSBnY3Mga2V5Cg==", + "id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "s3_bucket": "pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1", + "s3_endpoint": "s3.us-east-1.amazonaws.com", + "s3_key": "AKIAIOSFODNN7EXAMPLE", + "s3_key_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "s3_region": "us-east-1", + "type": "s3" + }, + "restore_options": { + "set": "20250505-153628F", + "target": "123456", + "type": "xid" + }, + "source_database_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "source_database_name": "northwind", + "source_node_name": "n1" + }, + "services": [ + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + }, + { + "config": { + "llm_model": "gpt-4", + "llm_provider": "openai", + "openai_api_key": "sk-..." + }, + "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, + "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696" + ], + "memory": "512M", + "orchestrator_opts": { + "swarm": { + "extra_labels": { + "traefik.enable": "true", + "traefik.tcp.routers.mydb.rule": "HostSNI(`mydb.example.com`)" + }, + "extra_networks": [ + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + } + ], + "extra_volumes": [ + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + } + ] + } + }, + "port": 0, + "service_id": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "service_type": "mcp", + "version": "latest" + } + ], + "spock_version": "5" + }, + "required": [ + "database_name", + "nodes" + ] + }, + "DatabaseSummary": { + "type": "object", + "properties": { + "created_at": { + "type": "string", + "description": "The time that the database was created.", + "example": "2025-01-01T01:30:00Z", + "format": "date-time" + }, + "id": { + "type": "string", + "description": "A user-specified identifier. Must be 1-63 characters, contain only lower-cased letters and hyphens, start and end with a letter or number, and not contain consecutive hyphens.", + "example": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "minLength": 1, + "maxLength": 63 + }, + "instances": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Instance" + }, + "description": "All of the instances in the database.", + "example": [ + { + "connection_info": { + "addresses": [ + "10.24.34.2", + "i-0123456789abcdef.ec2.internal" + ], + "port": 5432 + }, + "created_at": "1971-07-18T16:52:12Z", + "error": "failed to get patroni status: connection refused", + "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", + "node_name": "n1", + "postgres": { + "patroni_paused": false, + "patroni_state": "unknown", + "pending_restart": true, + "role": "primary", + "version": "18.1" + }, + "spock": { + "read_only": "off", + "subscriptions": [ + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + } + ], + "version": "4.10.0" + }, + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" + }, + { + "connection_info": { + "addresses": [ + "10.24.34.2", + "i-0123456789abcdef.ec2.internal" + ], + "port": 5432 + }, + "created_at": "1971-07-18T16:52:12Z", + "error": "failed to get patroni status: connection refused", + "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", + "node_name": "n1", + "postgres": { + "patroni_paused": false, + "patroni_state": "unknown", + "pending_restart": true, + "role": "primary", + "version": "18.1" + }, + "spock": { + "read_only": "off", + "subscriptions": [ + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + } + ], + "version": "4.10.0" + }, + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" + }, + { + "connection_info": { + "addresses": [ + "10.24.34.2", + "i-0123456789abcdef.ec2.internal" + ], + "port": 5432 + }, + "created_at": "1971-07-18T16:52:12Z", + "error": "failed to get patroni status: connection refused", + "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", + "node_name": "n1", + "postgres": { + "patroni_paused": false, + "patroni_state": "unknown", + "pending_restart": true, + "role": "primary", + "version": "18.1" + }, + "spock": { + "read_only": "off", + "subscriptions": [ + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + } + ], + "version": "4.10.0" + }, + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" + }, + { + "connection_info": { + "addresses": [ + "10.24.34.2", + "i-0123456789abcdef.ec2.internal" + ], + "port": 5432 + }, + "created_at": "1971-07-18T16:52:12Z", + "error": "failed to get patroni status: connection refused", + "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", + "node_name": "n1", + "postgres": { + "patroni_paused": false, + "patroni_state": "unknown", + "pending_restart": true, + "role": "primary", + "version": "18.1" + }, + "spock": { + "read_only": "off", + "subscriptions": [ + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + } + ], + "version": "4.10.0" + }, + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" + } + ] + }, + "state": { + "type": "string", + "description": "Current state of the database.", + "example": "failed", + "enum": [ + "creating", + "modifying", + "available", + "deleting", + "degraded", + "failed", + "backing_up", + "restoring", + "unknown" + ] + }, + "tenant_id": { + "type": "string", + "description": "A user-specified identifier. Must be 1-63 characters, contain only lower-cased letters and hyphens, start and end with a letter or number, and not contain consecutive hyphens.", + "example": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "minLength": 1, + "maxLength": 63 + }, + "updated_at": { + "type": "string", + "description": "The time that the database was last updated.", + "example": "2025-01-01T02:30:00Z", + "format": "date-time" + } + }, + "example": { + "created_at": "2025-01-01T01:30:00Z", + "id": "02f1a7db-fca8-4521-b57a-2a375c1ced51", + "instances": [ + { + "connection_info": { + "addresses": [ + "10.24.34.2", + "i-0123456789abcdef.ec2.internal" + ], + "port": 5432 + }, + "created_at": "1971-07-18T16:52:12Z", + "error": "failed to get patroni status: connection refused", + "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", + "node_name": "n1", + "postgres": { + "patroni_paused": false, + "patroni_state": "unknown", + "pending_restart": true, + "role": "primary", + "version": "18.1" + }, + "spock": { + "read_only": "off", + "subscriptions": [ + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + } + ], + "version": "4.10.0" + }, + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" + }, + { + "connection_info": { + "addresses": [ + "10.24.34.2", + "i-0123456789abcdef.ec2.internal" + ], + "port": 5432 + }, + "created_at": "1971-07-18T16:52:12Z", + "error": "failed to get patroni status: connection refused", + "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", + "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", + "node_name": "n1", + "postgres": { + "patroni_paused": false, + "patroni_state": "unknown", + "pending_restart": true, + "role": "primary", + "version": "18.1" + }, + "spock": { + "read_only": "off", + "subscriptions": [ + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + } + ], + "version": "4.10.0" + }, + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" + } + ], + "state": "degraded", + "tenant_id": "8210ec10-2dca-406c-ac4a-0661d2189954", + "updated_at": "2025-01-01T02:30:00Z" + }, + "required": [ + "id", + "created_at", + "updated_at", + "state" + ] + }, + "DatabaseUserSpec": { + "type": "object", + "properties": { + "attributes": { + "type": "array", + "items": { + "type": "string", + "example": "Et nam quo qui qui natus." + }, + "description": "The attributes to assign to this database user.", + "example": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "maxItems": 16 + }, + "db_owner": { + "type": "boolean", + "description": "If true, this user will be granted database ownership.", + "example": false + }, + "password": { + "type": "string", + "description": "The password for this database user. This field will be excluded from the response of all endpoints. It can also be omitted from update requests to keep the current value.", + "example": "secret", + "minLength": 1 + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "example": "Veniam exercitationem placeat temporibus aut." + }, + "description": "The roles to assign to this database user.", + "example": [ + "pgedge_superuser" + ], + "maxItems": 16 + }, + "username": { + "type": "string", + "description": "The username for this database user.", + "example": "admin", + "minLength": 1 + } + }, + "example": { + "attributes": [ + "LOGIN", + "CREATEDB", + "CREATEROLE" + ], + "db_owner": false, + "password": "secret", + "roles": [ + "pgedge_superuser" + ], + "username": "admin" + }, + "required": [ + "username" + ] + }, + "DeleteDatabaseResponse": { + "type": "object", + "properties": { + "task": { + "$ref": "#/components/schemas/Task" + } + }, + "example": { + "task": { + "created_at": "2025-06-18T16:48:59Z", + "database_id": "storefront", + "status": "pending", + "task_id": "019783f1-9f17-77e7-9a08-fa6ab39e3b29", + "type": "delete" + } + }, + "required": [ + "task" + ] + }, + "EtcdClusterMember": { + "type": "object", + "properties": { + "client_urls": { + "type": "array", + "items": { + "type": "string", + "example": "Accusantium ut aperiam qui sed quis architecto." + }, + "description": "The Etcd client endpoint for this cluster member.", + "example": [ + "http://192.168.1.1:2379" + ] + }, + "name": { + "type": "string", + "description": "The name of the Etcd cluster member.", + "example": "host-1" + }, + "peer_urls": { + "type": "array", + "items": { + "type": "string", + "example": "Labore explicabo culpa eos natus aperiam excepturi." + }, + "description": "The Etcd peer endpoint for this cluster member.", + "example": [ + "http://192.168.1.1:2380" + ] + } + }, + "example": { + "client_urls": [ + "http://192.168.1.1:2379" + ], + "name": "host-1", + "peer_urls": [ + "http://192.168.1.1:2380" + ] + }, + "required": [ + "name", + "peer_urls", + "client_urls" + ] + }, + "ExtraNetworkSpec": { + "type": "object", + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string", + "example": "Illo dolore." + }, + "description": "Optional network-scoped aliases for the container.", + "example": [ + "pg-db", + "db-alias" + ], + "maxItems": 8 + }, + "driver_opts": { + "type": "object", + "description": "Optional driver options for the network connection.", + "example": { + "com.docker.network.endpoint.expose": "true" + }, + "additionalProperties": { + "type": "string", + "example": "Accusamus alias ipsa qui." + } + }, + "id": { + "type": "string", + "description": "The name or ID of the network to connect to.", + "example": "traefik-public" + } + }, + "description": "Describes an additional Docker network to attach the container to.", + "example": { + "aliases": [ + "pg-db", + "db-alias" + ], + "driver_opts": { + "com.docker.network.endpoint.expose": "true" + }, + "id": "traefik-public" + }, + "required": [ + "id" + ] + }, + "ExtraVolumesSpec": { + "type": "object", + "properties": { + "destination_path": { + "type": "string", + "description": "The path inside the container where the volume will be mounted.", + "example": "/backups/container", + "maxLength": 256 + }, + "host_path": { + "type": "string", + "description": "The host path for the volume.", + "example": "/Users/user/backups/host", + "maxLength": 256 + } + }, + "description": "Extra volumes to mount from the host to the database container.", + "example": { + "destination_path": "/backups/container", + "host_path": "/Users/user/backups/host" + }, + "required": [ + "host_path", + "destination_path" + ] + }, + "FailoverDatabaseNodeRequest": { + "type": "object", + "properties": { + "candidate_instance_id": { + "type": "string", + "description": "Optional instance_id of the replica to promote. If omitted, a candidate will be selected.", + "example": "68f50878-44d2-4524-a823-e31bd478706d-n1-689qacsi" + }, + "database_id": { + "type": "string", + "description": "A user-specified identifier. Must be 1-63 characters, contain only lower-cased letters and hyphens, start and end with a letter or number, and not contain consecutive hyphens.", + "example": "76f9b8c0-4958-11f0-a489-3bb29577c696", + "minLength": 1, + "maxLength": 63 + }, + "node_name": { + "type": "string", + "description": "Name of the node to initiate the failover from.", + "example": "n1", + "pattern": "n[0-9]+" + }, + "skip_validation": { + "type": "boolean", + "description": "If true, skip the health validations that prevent running failover on a healthy cluster.", + "default": false, + "example": true + } + }, + "example": { + "candidate_instance_id": "68f50878-44d2-4524-a823-e31bd478706d-n1-689qacsi", + "database_id": "my-app", + "node_name": "n1", + "skip_validation": true + }, + "required": [ + "database_id", + "node_name" + ] + }, + "FailoverDatabaseNodeRequest2": { + "type": "object", + "properties": { + "candidate_instance_id": { + "type": "string", + "description": "Optional instance_id of the replica to promote. If omitted, a candidate will be selected.", + "example": "68f50878-44d2-4524-a823-e31bd478706d-n1-689qacsi" + }, + "skip_validation": { + "type": "boolean", + "description": "If true, skip the health validations that prevent running failover on a healthy cluster.", + "default": false, + "example": false + } + }, + "example": { + "candidate_instance_id": "68f50878-44d2-4524-a823-e31bd478706d-n1-689qacsi", + "skip_validation": false + } + }, + "FailoverDatabaseNodeResponse": { + "type": "object", + "properties": { + "task": { + "$ref": "#/components/schemas/Task" + } + }, + "example": { + "task": { + "created_at": "2025-06-18T17:54:28Z", + "database_id": "storefront", + "status": "pending", + "task_id": "0197842d-9082-7496-b787-77bd2e11809f", + "type": "failover" + } + }, + "required": [ + "task" + ] }, "HealthCheckResult": { "type": "object", @@ -22995,7 +25135,7 @@ "type": "array", "items": { "type": "string", - "example": "Similique sed neque eos rerum quia." + "example": "Quas nostrum eos reprehenderit harum sapiente qui." }, "description": "The addresses that this host advertises to client applications.", "example": [ @@ -23050,7 +25190,7 @@ "type": "array", "items": { "type": "string", - "example": "At esse ut possimus error eligendi." + "example": "Quo recusandae quibusdam fuga molestiae." }, "description": "The addresses that this host advertises to other hosts.", "example": [ @@ -23072,10 +25212,6 @@ "postgres_version": "17.6", "spock_version": "5" }, - { - "postgres_version": "17.6", - "spock_version": "5" - }, { "postgres_version": "17.6", "spock_version": "5" @@ -23132,14 +25268,6 @@ "updated_at": "2021-07-01T12:34:56Z" }, "supported_pgedge_versions": [ - { - "postgres_version": "17.6", - "spock_version": "5" - }, - { - "postgres_version": "17.6", - "spock_version": "5" - }, { "postgres_version": "17.6", "spock_version": "5" @@ -23196,7 +25324,7 @@ "type": "object", "description": "The status of each component of the host.", "example": { - "Ullam minus dolore eos et.": { + "Possimus error eligendi recusandae.": { "details": { "alarms": [ "3: NOSPACE" @@ -23229,7 +25357,16 @@ }, "example": { "components": { - "Animi dolor quam aliquid cupiditate.": { + "Atque facilis non modi explicabo illum.": { + "details": { + "alarms": [ + "3: NOSPACE" + ] + }, + "error": "failed to connect to etcd", + "healthy": false + }, + "Neque eos rerum quia.": { "details": { "alarms": [ "3: NOSPACE" @@ -23238,7 +25375,7 @@ "error": "failed to connect to etcd", "healthy": false }, - "Eos aut dignissimos cum voluptate modi consequatur.": { + "Qui et eius reiciendis accusamus.": { "details": { "alarms": [ "3: NOSPACE" @@ -23282,7 +25419,7 @@ "created_at": { "type": "string", "description": "The time that the instance was created.", - "example": "2004-09-06T11:51:20Z", + "example": "1977-01-28T10:07:37Z", "format": "date-time" }, "error": { @@ -23313,7 +25450,7 @@ }, "state": { "type": "string", - "example": "stopped", + "example": "available", "enum": [ "creating", "modifying", @@ -23329,13 +25466,13 @@ "status_updated_at": { "type": "string", "description": "The time that the instance status information was last updated.", - "example": "2001-10-21T23:10:32Z", + "example": "1999-11-24T20:30:24Z", "format": "date-time" }, "updated_at": { "type": "string", "description": "The time that the instance was last modified.", - "example": "2000-06-12T17:41:55Z", + "example": "2012-07-13T20:43:05Z", "format": "date-time" } }, @@ -23348,7 +25485,7 @@ ], "port": 5432 }, - "created_at": "1980-04-21T14:10:30Z", + "created_at": "2003-06-28T19:25:47Z", "error": "failed to get patroni status: connection refused", "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", @@ -23356,7 +25493,7 @@ "postgres": { "patroni_paused": false, "patroni_state": "unknown", - "pending_restart": false, + "pending_restart": true, "role": "primary", "version": "18.1" }, @@ -23381,9 +25518,9 @@ ], "version": "4.10.0" }, - "state": "failed", - "status_updated_at": "1999-10-09T09:59:39Z", - "updated_at": "2006-10-19T03:24:20Z" + "state": "modifying", + "status_updated_at": "1992-10-18T20:01:09Z", + "updated_at": "1982-02-16T16:16:42Z" }, "required": [ "id", @@ -23401,7 +25538,7 @@ "type": "array", "items": { "type": "string", - "example": "Explicabo illum alias qui et eius." + "example": "Eum ducimus." }, "description": "The addresses of the host that's running this instance.", "example": [ @@ -23454,9 +25591,9 @@ }, "description": "Postgres status information for a pgEdge instance.", "example": { - "patroni_paused": true, + "patroni_paused": false, "patroni_state": "unknown", - "pending_restart": false, + "pending_restart": true, "role": "primary", "version": "18.1" } @@ -23485,545 +25622,187 @@ "name": "sub_n1n2", "provider_node": "n2", "status": "down" - } - ] - }, - "version": { - "type": "string", - "description": "The version of Spock for this instance.", - "example": "4.10.0" - } - }, - "description": "Spock status information for a pgEdge instance.", - "example": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - } - }, - "InstanceSubscription": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the subscription.", - "example": "sub_n1n2" - }, - "provider_node": { - "type": "string", - "description": "The Spock node name of the provider for this subscription.", - "example": "n2", - "pattern": "n[0-9]+" - }, - "status": { - "type": "string", - "description": "The current status of the subscription.", - "example": "down" - } - }, - "description": "Status information for a Spock subscription.", - "example": { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - "required": [ - "provider_node", - "name", - "status" - ] - }, - "ListDatabaseTasksResponse": { - "type": "object", - "properties": { - "tasks": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Task" - }, - "description": "The tasks for the given database.", - "example": [ - { - "completed_at": "2025-06-18T16:52:35Z", - "created_at": "2025-06-18T16:52:05Z", - "database_id": "storefront", - "entity_id": "storefront", - "scope": "database", - "status": "completed", - "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", - "type": "create" }, { - "completed_at": "2025-06-18T16:52:35Z", - "created_at": "2025-06-18T16:52:05Z", - "database_id": "storefront", - "entity_id": "storefront", - "scope": "database", - "status": "completed", - "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", - "type": "create" + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" }, { - "completed_at": "2025-06-18T16:52:35Z", - "created_at": "2025-06-18T16:52:05Z", - "database_id": "storefront", - "entity_id": "storefront", - "scope": "database", - "status": "completed", - "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", - "type": "create" + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" } ] + }, + "version": { + "type": "string", + "description": "The version of Spock for this instance.", + "example": "4.10.0" } }, + "description": "Spock status information for a pgEdge instance.", "example": { - "tasks": [ + "read_only": "off", + "subscriptions": [ { - "completed_at": "2025-06-18T17:54:36Z", - "created_at": "2025-06-18T17:54:28Z", - "database_id": "storefront", - "entity_id": "storefront", - "instance_id": "storefront-n1-689qacsi", - "scope": "database", - "status": "completed", - "task_id": "0197842d-9082-7496-b787-77bd2e11809f", - "type": "node_backup" + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" }, { - "completed_at": "2025-06-18T17:54:04Z", - "created_at": "2025-06-18T17:53:17Z", - "database_id": "storefront", - "entity_id": "storefront", - "scope": "database", - "status": "completed", - "task_id": "0197842c-7c4f-7a8c-829e-7405c2a41c8c", - "type": "update" + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" }, { - "completed_at": "2025-06-18T17:23:28Z", - "created_at": "2025-06-18T17:23:14Z", - "database_id": "storefront", - "entity_id": "storefront", - "scope": "database", - "status": "completed", - "task_id": "01978410-fb5d-7cd2-bbd2-66c0bf929dc0", - "type": "update" + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" }, { - "completed_at": "2025-06-18T16:52:35Z", - "created_at": "2025-06-18T16:52:05Z", - "database_id": "storefront", - "entity_id": "storefront", - "scope": "database", - "status": "completed", - "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", - "type": "create" + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" } - ] - }, - "required": [ - "tasks" - ] + ], + "version": "4.10.0" + } }, - "ListDatabasesResponse": { + "InstanceSubscription": { "type": "object", "properties": { - "databases": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DatabaseSummary" - }, - "description": "The databases managed by this cluster.", - "example": [ - { - "created_at": "2025-01-01T01:30:00Z", - "id": "02f1a7db-fca8-4521-b57a-2a375c1ced51", - "instances": [ - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - } - ], - "state": "modifying", - "tenant_id": "8210ec10-2dca-406c-ac4a-0661d2189954", - "updated_at": "2025-01-01T02:30:00Z" + "name": { + "type": "string", + "description": "The name of the subscription.", + "example": "sub_n1n2" + }, + "provider_node": { + "type": "string", + "description": "The Spock node name of the provider for this subscription.", + "example": "n2", + "pattern": "n[0-9]+" + }, + "status": { + "type": "string", + "description": "The current status of the subscription.", + "example": "down" + } + }, + "description": "Status information for a Spock subscription.", + "example": { + "name": "sub_n1n2", + "provider_node": "n2", + "status": "down" + }, + "required": [ + "provider_node", + "name", + "status" + ] + }, + "ListDatabaseTasksResponse": { + "type": "object", + "properties": { + "tasks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Task" + }, + "description": "The tasks for the given database.", + "example": [ + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" }, { - "created_at": "2025-01-01T01:30:00Z", - "id": "02f1a7db-fca8-4521-b57a-2a375c1ced51", - "instances": [ - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - } - ], - "state": "modifying", - "tenant_id": "8210ec10-2dca-406c-ac4a-0661d2189954", - "updated_at": "2025-01-01T02:30:00Z" + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" }, + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + } + ] + } + }, + "example": { + "tasks": [ + { + "completed_at": "2025-06-18T17:54:36Z", + "created_at": "2025-06-18T17:54:28Z", + "database_id": "storefront", + "entity_id": "storefront", + "instance_id": "storefront-n1-689qacsi", + "scope": "database", + "status": "completed", + "task_id": "0197842d-9082-7496-b787-77bd2e11809f", + "type": "node_backup" + }, + { + "completed_at": "2025-06-18T17:54:04Z", + "created_at": "2025-06-18T17:53:17Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "0197842c-7c4f-7a8c-829e-7405c2a41c8c", + "type": "update" + }, + { + "completed_at": "2025-06-18T17:23:28Z", + "created_at": "2025-06-18T17:23:14Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "01978410-fb5d-7cd2-bbd2-66c0bf929dc0", + "type": "update" + }, + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + } + ] + }, + "required": [ + "tasks" + ] + }, + "ListDatabasesResponse": { + "type": "object", + "properties": { + "databases": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DatabaseSummary" + }, + "description": "The databases managed by this cluster.", + "example": [ { "created_at": "2025-01-01T01:30:00Z", "id": "02f1a7db-fca8-4521-b57a-2a375c1ced51", @@ -24036,97 +25815,7 @@ ], "port": 5432 }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", - "error": "failed to get patroni status: connection refused", - "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", - "node_name": "n1", - "postgres": { - "patroni_paused": false, - "patroni_state": "unknown", - "pending_restart": false, - "role": "primary", - "version": "18.1" - }, - "spock": { - "read_only": "off", - "subscriptions": [ - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - }, - { - "name": "sub_n1n2", - "provider_node": "n2", - "status": "down" - } - ], - "version": "4.10.0" - }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, - { - "connection_info": { - "addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "port": 5432 - }, - "created_at": "1991-01-10T16:19:34Z", + "created_at": "1971-07-18T16:52:12Z", "error": "failed to get patroni status: connection refused", "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", @@ -24134,7 +25823,7 @@ "postgres": { "patroni_paused": false, "patroni_state": "unknown", - "pending_restart": false, + "pending_restart": true, "role": "primary", "version": "18.1" }, @@ -24159,9 +25848,9 @@ ], "version": "4.10.0" }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" }, { "connection_info": { @@ -24171,7 +25860,7 @@ ], "port": 5432 }, - "created_at": "1991-01-10T16:19:34Z", + "created_at": "1971-07-18T16:52:12Z", "error": "failed to get patroni status: connection refused", "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", @@ -24179,7 +25868,7 @@ "postgres": { "patroni_paused": false, "patroni_state": "unknown", - "pending_restart": false, + "pending_restart": true, "role": "primary", "version": "18.1" }, @@ -24204,12 +25893,12 @@ ], "version": "4.10.0" }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" } ], - "state": "modifying", + "state": "failed", "tenant_id": "8210ec10-2dca-406c-ac4a-0661d2189954", "updated_at": "2025-01-01T02:30:00Z" }, @@ -24225,7 +25914,7 @@ ], "port": 5432 }, - "created_at": "1991-01-10T16:19:34Z", + "created_at": "1971-07-18T16:52:12Z", "error": "failed to get patroni status: connection refused", "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", @@ -24233,7 +25922,7 @@ "postgres": { "patroni_paused": false, "patroni_state": "unknown", - "pending_restart": false, + "pending_restart": true, "role": "primary", "version": "18.1" }, @@ -24258,9 +25947,9 @@ ], "version": "4.10.0" }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" }, { "connection_info": { @@ -24270,7 +25959,7 @@ ], "port": 5432 }, - "created_at": "1991-01-10T16:19:34Z", + "created_at": "1971-07-18T16:52:12Z", "error": "failed to get patroni status: connection refused", "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", @@ -24278,7 +25967,7 @@ "postgres": { "patroni_paused": false, "patroni_state": "unknown", - "pending_restart": false, + "pending_restart": true, "role": "primary", "version": "18.1" }, @@ -24303,10 +25992,19 @@ ], "version": "4.10.0" }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" - }, + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" + } + ], + "state": "failed", + "tenant_id": "8210ec10-2dca-406c-ac4a-0661d2189954", + "updated_at": "2025-01-01T02:30:00Z" + }, + { + "created_at": "2025-01-01T01:30:00Z", + "id": "02f1a7db-fca8-4521-b57a-2a375c1ced51", + "instances": [ { "connection_info": { "addresses": [ @@ -24315,7 +26013,7 @@ ], "port": 5432 }, - "created_at": "1991-01-10T16:19:34Z", + "created_at": "1971-07-18T16:52:12Z", "error": "failed to get patroni status: connection refused", "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", @@ -24323,7 +26021,7 @@ "postgres": { "patroni_paused": false, "patroni_state": "unknown", - "pending_restart": false, + "pending_restart": true, "role": "primary", "version": "18.1" }, @@ -24348,9 +26046,9 @@ ], "version": "4.10.0" }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" }, { "connection_info": { @@ -24360,7 +26058,7 @@ ], "port": 5432 }, - "created_at": "1991-01-10T16:19:34Z", + "created_at": "1971-07-18T16:52:12Z", "error": "failed to get patroni status: connection refused", "host_id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "id": "a67cbb36-c3c3-49c9-8aac-f4a0438a883d", @@ -24368,7 +26066,7 @@ "postgres": { "patroni_paused": false, "patroni_state": "unknown", - "pending_restart": false, + "pending_restart": true, "role": "primary", "version": "18.1" }, @@ -24393,12 +26091,12 @@ ], "version": "4.10.0" }, - "state": "modifying", - "status_updated_at": "2006-12-23T09:53:03Z", - "updated_at": "1994-06-04T08:19:03Z" + "state": "deleting", + "status_updated_at": "1999-09-23T18:53:48Z", + "updated_at": "2013-04-06T16:38:09Z" } ], - "state": "modifying", + "state": "failed", "tenant_id": "8210ec10-2dca-406c-ac4a-0661d2189954", "updated_at": "2025-01-01T02:30:00Z" } @@ -24661,6 +26359,26 @@ }, "description": "The tasks for the given host.", "example": [ + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + }, + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + }, { "completed_at": "2025-06-18T16:52:35Z", "created_at": "2025-06-18T16:52:05Z", @@ -24708,136 +26426,10 @@ "hosts": { "type": "array", "items": { - "$ref": "#/components/schemas/Host" - }, - "description": "List of hosts in the cluster", - "example": [ - { - "client_addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "cohort": { - "control_available": true, - "member_id": "lah4bsznw6kc0hp7biylmmmll", - "type": "swarm" - }, - "cpus": 4, - "data_dir": "/data", - "default_pgedge_version": { - "postgres_version": "17.6", - "spock_version": "5" - }, - "etcd_mode": "server", - "id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "memory": "16GiB", - "orchestrator": "swarm", - "peer_addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "status": { - "components": { - "Enim et voluptatum ex ea dolore.": { - "details": { - "alarms": [ - "3: NOSPACE" - ] - }, - "error": "failed to connect to etcd", - "healthy": false - }, - "Tenetur nostrum repellendus sint qui.": { - "details": { - "alarms": [ - "3: NOSPACE" - ] - }, - "error": "failed to connect to etcd", - "healthy": false - } - }, - "state": "available", - "updated_at": "2021-07-01T12:34:56Z" - }, - "supported_pgedge_versions": [ - { - "postgres_version": "17.6", - "spock_version": "5" - }, - { - "postgres_version": "17.6", - "spock_version": "5" - }, - { - "postgres_version": "17.6", - "spock_version": "5" - } - ] - }, - { - "client_addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "cohort": { - "control_available": true, - "member_id": "lah4bsznw6kc0hp7biylmmmll", - "type": "swarm" - }, - "cpus": 4, - "data_dir": "/data", - "default_pgedge_version": { - "postgres_version": "17.6", - "spock_version": "5" - }, - "etcd_mode": "server", - "id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "memory": "16GiB", - "orchestrator": "swarm", - "peer_addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "status": { - "components": { - "Enim et voluptatum ex ea dolore.": { - "details": { - "alarms": [ - "3: NOSPACE" - ] - }, - "error": "failed to connect to etcd", - "healthy": false - }, - "Tenetur nostrum repellendus sint qui.": { - "details": { - "alarms": [ - "3: NOSPACE" - ] - }, - "error": "failed to connect to etcd", - "healthy": false - } - }, - "state": "available", - "updated_at": "2021-07-01T12:34:56Z" - }, - "supported_pgedge_versions": [ - { - "postgres_version": "17.6", - "spock_version": "5" - }, - { - "postgres_version": "17.6", - "spock_version": "5" - }, - { - "postgres_version": "17.6", - "spock_version": "5" - } - ] - }, + "$ref": "#/components/schemas/Host" + }, + "description": "List of hosts in the cluster", + "example": [ { "client_addresses": [ "10.24.34.2", @@ -25033,132 +26625,6 @@ } ] }, - { - "client_addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "cohort": { - "control_available": true, - "member_id": "lah4bsznw6kc0hp7biylmmmll", - "type": "swarm" - }, - "cpus": 4, - "data_dir": "/data", - "default_pgedge_version": { - "postgres_version": "17.6", - "spock_version": "5" - }, - "etcd_mode": "server", - "id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "memory": "16GiB", - "orchestrator": "swarm", - "peer_addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "status": { - "components": { - "Enim et voluptatum ex ea dolore.": { - "details": { - "alarms": [ - "3: NOSPACE" - ] - }, - "error": "failed to connect to etcd", - "healthy": false - }, - "Tenetur nostrum repellendus sint qui.": { - "details": { - "alarms": [ - "3: NOSPACE" - ] - }, - "error": "failed to connect to etcd", - "healthy": false - } - }, - "state": "available", - "updated_at": "2021-07-01T12:34:56Z" - }, - "supported_pgedge_versions": [ - { - "postgres_version": "17.6", - "spock_version": "5" - }, - { - "postgres_version": "17.6", - "spock_version": "5" - }, - { - "postgres_version": "17.6", - "spock_version": "5" - } - ] - }, - { - "client_addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "cohort": { - "control_available": true, - "member_id": "lah4bsznw6kc0hp7biylmmmll", - "type": "swarm" - }, - "cpus": 4, - "data_dir": "/data", - "default_pgedge_version": { - "postgres_version": "17.6", - "spock_version": "5" - }, - "etcd_mode": "server", - "id": "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "memory": "16GiB", - "orchestrator": "swarm", - "peer_addresses": [ - "10.24.34.2", - "i-0123456789abcdef.ec2.internal" - ], - "status": { - "components": { - "Enim et voluptatum ex ea dolore.": { - "details": { - "alarms": [ - "3: NOSPACE" - ] - }, - "error": "failed to connect to etcd", - "healthy": false - }, - "Tenetur nostrum repellendus sint qui.": { - "details": { - "alarms": [ - "3: NOSPACE" - ] - }, - "error": "failed to connect to etcd", - "healthy": false - } - }, - "state": "available", - "updated_at": "2021-07-01T12:34:56Z" - }, - "supported_pgedge_versions": [ - { - "postgres_version": "17.6", - "spock_version": "5" - }, - { - "postgres_version": "17.6", - "spock_version": "5" - }, - { - "postgres_version": "17.6", - "spock_version": "5" - } - ] - }, { "client_addresses": [ "10.24.34.2", @@ -25248,6 +26714,16 @@ "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", "type": "create" }, + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + }, { "completed_at": "2025-06-18T16:52:35Z", "created_at": "2025-06-18T16:52:05Z", @@ -25424,6 +26900,26 @@ }, "description": "The tasks that will update databases affected by the host removal.", "example": [ + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + }, + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + }, { "completed_at": "2025-06-18T16:52:35Z", "created_at": "2025-06-18T16:52:05Z", @@ -25459,6 +26955,26 @@ "type": "create" }, "update_database_tasks": [ + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + }, + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + }, { "completed_at": "2025-06-18T16:52:35Z", "created_at": "2025-06-18T16:52:05Z", @@ -25527,7 +27043,7 @@ "maxLength": 32, "additionalProperties": { "type": "string", - "example": "Temporibus aut." + "example": "Dolor autem eum." } }, "source_database_id": { @@ -25598,7 +27114,7 @@ "type": "array", "items": { "type": "string", - "example": "Sed exercitationem rerum animi et." + "example": "At praesentium ut dolorem sapiente." }, "description": "The nodes to restore. Defaults to all nodes if empty or unspecified.", "example": [ @@ -25658,6 +27174,16 @@ "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", "type": "create" }, + { + "completed_at": "2025-06-18T16:52:35Z", + "created_at": "2025-06-18T16:52:05Z", + "database_id": "storefront", + "entity_id": "storefront", + "scope": "database", + "status": "completed", + "task_id": "019783f4-75f4-71e7-85a3-c9b96b345d77", + "type": "create" + }, { "completed_at": "2025-06-18T16:52:35Z", "created_at": "2025-06-18T16:52:05Z", @@ -26165,7 +27691,7 @@ }, "additionalProperties": { "type": "string", - "example": "Qui qui natus qui veniam exercitationem." + "example": "Aut omnis magnam aspernatur occaecati itaque dolores." } }, "gcs_bucket": { @@ -26345,16 +27871,6 @@ "image_version": "1.0.0", "last_health_at": "2025-01-28T10:00:00Z", "ports": [ - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, - { - "container_port": 8080, - "host_port": 8080, - "name": "web-client" - }, { "container_port": 8080, "host_port": 8080, @@ -26387,7 +27903,7 @@ "type": "array", "items": { "type": "string", - "example": "Perferendis vitae quas nam voluptatum." + "example": "Necessitatibus labore ipsa id modi et et." }, "description": "The addresses of the host that's running this service instance.", "example": [ @@ -26421,6 +27937,16 @@ }, "description": "Port mappings for this service instance.", "example": [ + { + "container_port": 8080, + "host_port": 8080, + "name": "web-client" + }, + { + "container_port": 8080, + "host_port": 8080, + "name": "web-client" + }, { "container_port": 8080, "host_port": 8080, @@ -26464,6 +27990,11 @@ "host_port": 8080, "name": "web-client" }, + { + "container_port": 8080, + "host_port": 8080, + "name": "web-client" + }, { "container_port": 8080, "host_port": 8080, @@ -26492,6 +28023,9 @@ "example": "500m", "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" }, + "database_connection": { + "$ref": "#/components/schemas/DatabaseConnection" + }, "host_ids": { "type": "array", "items": { @@ -26503,7 +28037,6 @@ }, "description": "The IDs of the hosts that should run this service. One service instance will be created per host.", "example": [ - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "de3b1388-1f0c-42f1-a86c-59ab72f255ec" ], "minItems": 1 @@ -26555,9 +28088,14 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", - "de3b1388-1f0c-42f1-a86c-59ab72f255ec", "de3b1388-1f0c-42f1-a86c-59ab72f255ec" ], "memory": "512M", @@ -26647,6 +28185,9 @@ "example": "500m", "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" }, + "database_connection": { + "$ref": "#/components/schemas/DatabaseConnection" + }, "host_ids": { "type": "array", "items": { @@ -26657,7 +28198,6 @@ }, "description": "The IDs of the hosts that should run this service. One service instance will be created per host.", "example": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], @@ -26710,7 +28250,16 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -26800,6 +28349,9 @@ "example": "500m", "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" }, + "database_connection": { + "$ref": "#/components/schemas/DatabaseConnection" + }, "host_ids": { "type": "array", "items": { @@ -26810,6 +28362,8 @@ }, "description": "The IDs of the hosts that should run this service. One service instance will be created per host.", "example": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "minItems": 1 @@ -26861,7 +28415,15 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -26951,6 +28513,9 @@ "example": "500m", "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" }, + "database_connection": { + "$ref": "#/components/schemas/DatabaseConnection" + }, "host_ids": { "type": "array", "items": { @@ -27013,7 +28578,15 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], @@ -27104,6 +28677,9 @@ "example": "500m", "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" }, + "database_connection": { + "$ref": "#/components/schemas/DatabaseConnection" + }, "host_ids": { "type": "array", "items": { @@ -27167,6 +28743,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" @@ -27258,6 +28841,9 @@ "example": "500m", "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" }, + "database_connection": { + "$ref": "#/components/schemas/DatabaseConnection" + }, "host_ids": { "type": "array", "items": { @@ -27268,6 +28854,7 @@ }, "description": "The IDs of the hosts that should run this service. One service instance will be created per host.", "example": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], @@ -27320,6 +28907,13 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696", @@ -27412,6 +29006,9 @@ "example": "500m", "pattern": "^[0-9]+(\\.[0-9]{1,3}|m)?$" }, + "database_connection": { + "$ref": "#/components/schemas/DatabaseConnection" + }, "host_ids": { "type": "array", "items": { @@ -27422,6 +29019,7 @@ }, "description": "The IDs of the hosts that should run this service. One service instance will be created per host.", "example": [ + "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "minItems": 1 @@ -27473,8 +29071,14 @@ "openai_api_key": "sk-..." }, "cpus": "500m", + "database_connection": { + "target_nodes": [ + "n1", + "n2" + ], + "target_session_attrs": "primary" + }, "host_ids": [ - "76f9b8c0-4958-11f0-a489-3bb29577c696", "76f9b8c0-4958-11f0-a489-3bb29577c696" ], "memory": "512M", @@ -27605,7 +29209,7 @@ }, "additionalProperties": { "type": "string", - "example": "Dolores voluptatum dolor autem eum et et." + "example": "Aut doloribus rem culpa ullam minus dolore." } }, "extra_networks": { @@ -27945,6 +29549,14 @@ "message": "task started", "timestamp": "2025-05-29T15:43:13Z" }, + { + "fields": { + "option.enabled": true, + "status": "creating" + }, + "message": "task started", + "timestamp": "2025-05-29T15:43:13Z" + }, { "fields": { "option.enabled": true, diff --git a/api/apiv1/gen/http/openapi3.yaml b/api/apiv1/gen/http/openapi3.yaml index ad8ab7a9..e8158e54 100644 --- a/api/apiv1/gen/http/openapi3.yaml +++ b/api/apiv1/gen/http/openapi3.yaml @@ -1139,17 +1139,14 @@ paths: type: array items: type: string - example: Eius temporibus incidunt porro. + example: Cupiditate molestiae. description: Host IDs to treat as removed during this update. Events targeting these hosts will be skipped. example: - - Non ut. - - Eos ut voluptates quo. - - Laborum tenetur. - - Et quas. + - Accusamus quia dolores. + - Laboriosam placeat distinctio atque dolor autem. example: - - Asperiores quis in sunt dignissimos. - - Cupiditate molestiae. - - Et accusamus quia dolores recusandae. + - Excepturi sapiente neque doloribus consequatur voluptatibus. + - Et nisi nihil corporis. - name: database_id in: path description: ID of the database to update. @@ -3758,6 +3755,26 @@ components: s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY s3_region: us-east-1 type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: f6b84a99-5e91-4203-be1e-131fe82e5984 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 minItems: 1 schedules: type: array @@ -3873,7 +3890,7 @@ components: key: value additionalProperties: type: string - example: Neque doloribus ipsa et itaque aut reprehenderit. + example: Quibusdam veniam. backup_options: type: object description: Options for the backup. @@ -3881,7 +3898,7 @@ components: archive-check: "n" additionalProperties: type: string - example: Ea velit consequatur sequi error et quibusdam. + example: Non expedita sequi eligendi delectus sit quas. type: type: string description: The type of backup. @@ -3937,7 +3954,7 @@ components: storage-upload-chunk-size: 5MiB additionalProperties: type: string - example: Sed quis architecto magnam temporibus ipsa et. + example: Placeat a. gcs_bucket: type: string description: The GCS bucket name for this repository. Only applies when type = 'gcs'. @@ -4376,7 +4393,7 @@ components: type: array items: type: string - example: s2a + example: 76l minLength: 3 maxLength: 128 description: The peer addresses of the host that's joining the cluster. @@ -4417,7 +4434,7 @@ components: type: array items: type: string - example: Expedita sequi. + example: Quasi ipsum officiis et est et. description: Existing server to join example: - http://192.168.1.1:3000 @@ -4443,7 +4460,7 @@ components: - available - error example: - state: available + state: error required: - state ComponentStatus: @@ -4669,39 +4686,7 @@ components: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -4709,7 +4694,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -4725,15 +4710,15 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" - connection_info: addresses: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -4741,7 +4726,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -4757,9 +4742,9 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" service_instances: type: array items: @@ -4791,12 +4776,6 @@ components: - container_port: 8080 host_port: 8080 name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client service_ready: true updated_at: "2025-01-28T10:05:00Z" - created_at: "2025-01-28T10:00:00Z" @@ -4824,12 +4803,6 @@ components: - container_port: 8080 host_port: 8080 name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client service_ready: true updated_at: "2025-01-28T10:05:00Z" - created_at: "2025-01-28T10:00:00Z" @@ -4857,12 +4830,6 @@ components: - container_port: 8080 host_port: 8080 name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client service_ready: true updated_at: "2025-01-28T10:05:00Z" - created_at: "2025-01-28T10:00:00Z" @@ -4890,12 +4857,6 @@ components: - container_port: 8080 host_port: 8080 name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client service_ready: true updated_at: "2025-01-28T10:05:00Z" spec: @@ -4903,7 +4864,7 @@ components: state: type: string description: Current state of the database. - example: modifying + example: deleting enum: - creating - modifying @@ -5119,38 +5080,6 @@ components: state: available status_updated_at: "1993-04-25T23:06:28Z" updated_at: "1981-08-22T23:20:01Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "2011-03-15T17:48:48Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: true - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: available - status_updated_at: "1993-04-25T23:06:28Z" - updated_at: "1981-08-22T23:20:01Z" service_instances: type: array items: @@ -5256,45 +5185,12 @@ components: name: web-client service_ready: true updated_at: "2025-01-28T10:05:00Z" - - created_at: "2025-01-28T10:00:00Z" - database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - error: 'failed to start container: image not found' - host_id: host-1 - service_id: mcp-server - service_instance_id: mcp-server-host-1 - state: running - status: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - container_id: a1b2c3d4e5f6 - health_check: - checked_at: "2025-01-28T10:00:00Z" - message: Connection refused - status: healthy - image_version: 1.0.0 - last_health_at: "2025-01-28T10:00:00Z" - ports: - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - service_ready: true - updated_at: "2025-01-28T10:05:00Z" spec: $ref: '#/components/schemas/DatabaseSpec3' state: type: string description: Current state of the database. - example: unknown + example: restoring enum: - creating - modifying @@ -5685,7 +5581,7 @@ components: state: type: string description: Current state of the database. - example: unknown + example: creating enum: - creating - modifying @@ -5933,38 +5829,6 @@ components: state: available status_updated_at: "1993-04-25T23:06:28Z" updated_at: "1981-08-22T23:20:01Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "2011-03-15T17:48:48Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: true - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: available - status_updated_at: "1993-04-25T23:06:28Z" - updated_at: "1981-08-22T23:20:01Z" service_instances: type: array items: @@ -6037,45 +5901,12 @@ components: name: web-client service_ready: true updated_at: "2025-01-28T10:05:00Z" - - created_at: "2025-01-28T10:00:00Z" - database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - error: 'failed to start container: image not found' - host_id: host-1 - service_id: mcp-server - service_instance_id: mcp-server-host-1 - state: running - status: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - container_id: a1b2c3d4e5f6 - health_check: - checked_at: "2025-01-28T10:00:00Z" - message: Connection refused - status: healthy - image_version: 1.0.0 - last_health_at: "2025-01-28T10:00:00Z" - ports: - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - service_ready: true - updated_at: "2025-01-28T10:05:00Z" spec: $ref: '#/components/schemas/DatabaseSpec6' state: type: string description: Current state of the database. - example: restoring + example: creating enum: - creating - modifying @@ -6291,53 +6122,84 @@ components: state: available status_updated_at: "1993-04-25T23:06:28Z" updated_at: "1981-08-22T23:20:01Z" - service_instances: - type: array - items: - $ref: '#/components/schemas/ServiceInstance' - description: Service instances running alongside this database. - example: - - created_at: "2025-01-28T10:00:00Z" - database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - error: 'failed to start container: image not found' - host_id: host-1 - service_id: mcp-server - service_instance_id: mcp-server-host-1 - state: running - status: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - container_id: a1b2c3d4e5f6 - health_check: - checked_at: "2025-01-28T10:00:00Z" - message: Connection refused - status: healthy - image_version: 1.0.0 - last_health_at: "2025-01-28T10:00:00Z" - ports: - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - service_ready: true - updated_at: "2025-01-28T10:05:00Z" - - created_at: "2025-01-28T10:00:00Z" - database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - error: 'failed to start container: image not found' - host_id: host-1 - service_id: mcp-server - service_instance_id: mcp-server-host-1 - state: running - status: + - connection_info: + addresses: + - 10.24.34.2 + - i-0123456789abcdef.ec2.internal + port: 5432 + created_at: "2011-03-15T17:48:48Z" + error: 'failed to get patroni status: connection refused' + host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec + id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d + node_name: n1 + postgres: + patroni_paused: false + patroni_state: unknown + pending_restart: true + role: primary + version: "18.1" + spock: + read_only: "off" + subscriptions: + - name: sub_n1n2 + provider_node: n2 + status: down + - name: sub_n1n2 + provider_node: n2 + status: down + - name: sub_n1n2 + provider_node: n2 + status: down + version: 4.10.0 + state: available + status_updated_at: "1993-04-25T23:06:28Z" + updated_at: "1981-08-22T23:20:01Z" + - connection_info: + addresses: + - 10.24.34.2 + - i-0123456789abcdef.ec2.internal + port: 5432 + created_at: "2011-03-15T17:48:48Z" + error: 'failed to get patroni status: connection refused' + host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec + id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d + node_name: n1 + postgres: + patroni_paused: false + patroni_state: unknown + pending_restart: true + role: primary + version: "18.1" + spock: + read_only: "off" + subscriptions: + - name: sub_n1n2 + provider_node: n2 + status: down + - name: sub_n1n2 + provider_node: n2 + status: down + - name: sub_n1n2 + provider_node: n2 + status: down + version: 4.10.0 + state: available + status_updated_at: "1993-04-25T23:06:28Z" + updated_at: "1981-08-22T23:20:01Z" + service_instances: + type: array + items: + $ref: '#/components/schemas/ServiceInstance' + description: Service instances running alongside this database. + example: + - created_at: "2025-01-28T10:00:00Z" + database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + error: 'failed to start container: image not found' + host_id: host-1 + service_id: mcp-server + service_instance_id: mcp-server-host-1 + state: running + status: addresses: - 10.24.34.2 - i-0123456789abcdef.ec2.internal @@ -6434,7 +6296,7 @@ components: state: type: string description: Current state of the database. - example: failed + example: backing_up enum: - creating - modifying @@ -6566,6 +6428,34 @@ components: - created_at - updated_at - state + DatabaseConnection: + type: object + properties: + target_nodes: + type: array + items: + type: string + example: Ab sed exercitationem rerum animi et itaque. + description: Optional ordered list of database node names. When set, the service's database connection includes only the listed nodes in the specified order. + example: + - n1 + - n2 + target_session_attrs: + type: string + description: 'Optional libpq target_session_attrs value. When set, overrides the default derived from the service config. Valid values: primary, prefer-standby, standby, read-write, any.' + example: primary + enum: + - primary + - prefer-standby + - standby + - read-write + - any + description: Controls how the service connects to the database. When omitted, all nodes are included with the local node first and target_session_attrs is derived from the service config. + example: + target_nodes: + - n1 + - n2 + target_session_attrs: primary DatabaseNodeSpec: type: object properties: @@ -6587,8 +6477,6 @@ components: description: The IDs of the hosts that should run this node. When multiple hosts are specified, one host will chosen as a primary, and the others will be read replicas. example: - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec minItems: 1 memory: type: string @@ -6670,7 +6558,6 @@ components: host_ids: - de3b1388-1f0c-42f1-a86c-59ab72f255ec - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec memory: 500M name: n1 orchestrator_opts: @@ -6881,7 +6768,6 @@ components: cpus: 500m host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 500M name: n1 orchestrator_opts: @@ -6970,6 +6856,8 @@ components: description: The IDs of the hosts that should run this node. When multiple hosts are specified, one host will chosen as a primary, and the others will be read replicas. example: - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 minItems: 1 memory: type: string @@ -7091,7 +6979,6 @@ components: host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 500M name: n1 orchestrator_opts: @@ -7391,8 +7278,6 @@ components: description: The IDs of the hosts that should run this node. When multiple hosts are specified, one host will chosen as a primary, and the others will be read replicas. example: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 minItems: 1 memory: type: string @@ -7514,6 +7399,7 @@ components: host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 500M name: n1 orchestrator_opts: @@ -7603,7 +7489,6 @@ components: example: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 minItems: 1 memory: type: string @@ -7724,6 +7609,8 @@ components: cpus: 500m host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 500M name: n1 orchestrator_opts: @@ -8099,8 +7986,6 @@ components: cpus: 500m host_ids: - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec memory: 500M name: n1 orchestrator_opts: @@ -8166,99 +8051,43 @@ components: source_database_name: northwind source_node_name: n1 source_node: n1 - minItems: 1 - maxItems: 9 - orchestrator_opts: - $ref: '#/components/schemas/OrchestratorOpts' - patroni_port: - type: integer - description: 'The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.' - example: 8888 - format: int64 - minimum: 0 - maximum: 65535 - port: - type: integer - description: The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature. - example: 5432 - format: int64 - minimum: 0 - maximum: 65535 - postgres_version: - type: string - description: The Postgres version in 'major.minor' format. - example: "17.6" - pattern: ^\d{2}\.\d{1,2}$ - postgresql_conf: - type: object - description: Additional postgresql.conf settings. Will be merged with the settings provided by control-plane. - example: - max_connections: 1000 - maxLength: 64 - additionalProperties: true - restore_config: - $ref: '#/components/schemas/RestoreConfigSpec' - services: - type: array - items: - $ref: '#/components/schemas/ServiceSpec' - description: Service instances to run alongside the database (e.g., MCP servers). - example: - - config: - llm_model: gpt-4 - llm_provider: openai - openai_api_key: sk-... - cpus: 500m - host_ids: - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - memory: 512M - orchestrator_opts: - swarm: - extra_labels: - traefik.enable: "true" - traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) - extra_networks: - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - extra_volumes: - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - port: 0 - service_id: analytics-service - service_type: mcp - version: latest - - config: - llm_model: gpt-4 - llm_provider: openai - openai_api_key: sk-... + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: f6b84a99-5e91-4203-be1e-131fe82e5984 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full cpus: 500m host_ids: - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - memory: 512M + memory: 500M + name: n1 orchestrator_opts: swarm: extra_labels: @@ -8290,30 +8119,251 @@ components: host_path: /Users/user/backups/host - destination_path: /backups/container host_path: /Users/user/backups/host - port: 0 - service_id: analytics-service - service_type: mcp - version: latest - - config: - llm_model: gpt-4 - llm_provider: openai - openai_api_key: sk-... - cpus: 500m - host_ids: - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - memory: 512M - orchestrator_opts: - swarm: - extra_labels: - traefik.enable: "true" - traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) - extra_networks: - - aliases: - - pg-db - - db-alias - driver_opts: + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: f6b84a99-5e91-4203-be1e-131fe82e5984 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 02f1a7db-fca8-4521-b57a-2a375c1ced51 + source_database_name: northwind + source_node_name: n1 + source_node: n1 + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: f6b84a99-5e91-4203-be1e-131fe82e5984 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + cpus: 500m + host_ids: + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + memory: 500M + name: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: f6b84a99-5e91-4203-be1e-131fe82e5984 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 02f1a7db-fca8-4521-b57a-2a375c1ced51 + source_database_name: northwind + source_node_name: n1 + source_node: n1 + minItems: 1 + maxItems: 9 + orchestrator_opts: + $ref: '#/components/schemas/OrchestratorOpts' + patroni_port: + type: integer + description: 'The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.' + example: 8888 + format: int64 + minimum: 0 + maximum: 65535 + port: + type: integer + description: The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature. + example: 5432 + format: int64 + minimum: 0 + maximum: 65535 + postgres_version: + type: string + description: The Postgres version in 'major.minor' format. + example: "17.6" + pattern: ^\d{2}\.\d{1,2}$ + postgresql_conf: + type: object + description: Additional postgresql.conf settings. Will be merged with the settings provided by control-plane. + example: + max_connections: 1000 + maxLength: 64 + additionalProperties: true + restore_config: + $ref: '#/components/schemas/RestoreConfigSpec' + services: + type: array + items: + $ref: '#/components/schemas/ServiceSpec' + description: Service instances to run alongside the database (e.g., MCP servers). + example: + - config: + llm_model: gpt-4 + llm_provider: openai + openai_api_key: sk-... + cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary + host_ids: + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + memory: 512M + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + port: 0 + service_id: analytics-service + service_type: mcp + version: latest + - config: + llm_model: gpt-4 + llm_provider: openai + openai_api_key: sk-... + cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary + host_ids: + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + memory: 512M + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: com.docker.network.endpoint.expose: "true" id: traefik-public - aliases: @@ -8444,8 +8494,6 @@ components: cpus: 500m host_ids: - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec memory: 500M name: n1 orchestrator_opts: @@ -8546,8 +8594,6 @@ components: cpus: 500m host_ids: - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec memory: 500M name: n1 orchestrator_opts: @@ -8613,23 +8659,123 @@ components: source_database_name: northwind source_node_name: n1 source_node: n1 - orchestrator_opts: - swarm: - extra_labels: - traefik.enable: "true" - traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) - extra_networks: - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: f6b84a99-5e91-4203-be1e-131fe82e5984 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + cpus: 500m + host_ids: + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + memory: 500M + name: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: f6b84a99-5e91-4203-be1e-131fe82e5984 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 02f1a7db-fca8-4521-b57a-2a375c1ced51 + source_database_name: northwind + source_node_name: n1 + source_node: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" id: traefik-public - aliases: - pg-db @@ -8681,6 +8827,111 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary + host_ids: + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + memory: 512M + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + port: 0 + service_id: analytics-service + service_type: mcp + version: latest + - config: + llm_model: gpt-4 + llm_provider: openai + openai_api_key: sk-... + cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary + host_ids: + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + - de3b1388-1f0c-42f1-a86c-59ab72f255ec + memory: 512M + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + port: 0 + service_id: analytics-service + service_type: mcp + version: latest + - config: + llm_model: gpt-4 + llm_provider: openai + openai_api_key: sk-... + cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - de3b1388-1f0c-42f1-a86c-59ab72f255ec - de3b1388-1f0c-42f1-a86c-59ab72f255ec @@ -8726,6 +8977,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - de3b1388-1f0c-42f1-a86c-59ab72f255ec - de3b1388-1f0c-42f1-a86c-59ab72f255ec @@ -8973,6 +9229,290 @@ components: source_database_name: northwind source_node_name: n1 source_node: n1 + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + cpus: 500m + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 500M + name: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + source_database_name: northwind + source_node_name: n1 + source_node: n1 + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + cpus: 500m + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 500M + name: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + source_database_name: northwind + source_node_name: n1 + source_node: n1 minItems: 1 maxItems: 9 orchestrator_opts: @@ -9016,6 +9556,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -9060,6 +9605,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -9104,6 +9654,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -9395,6 +9950,290 @@ components: source_database_name: northwind source_node_name: n1 source_node: n1 + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + cpus: 500m + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 500M + name: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + source_database_name: northwind + source_node_name: n1 + source_node: n1 + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + cpus: 500m + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 500M + name: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + source_database_name: northwind + source_node_name: n1 + source_node: n1 orchestrator_opts: swarm: extra_labels: @@ -9463,6 +10302,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -9507,6 +10351,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -9551,6 +10400,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -9595,6 +10449,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -10165,6 +11024,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -10209,6 +11073,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -10253,50 +11122,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m - host_ids: - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - memory: 512M - orchestrator_opts: - swarm: - extra_labels: - traefik.enable: "true" - traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) - extra_networks: - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - extra_volumes: - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - port: 0 - service_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - service_type: mcp - version: latest - - config: - llm_model: gpt-4 - llm_provider: openai - openai_api_key: sk-... - cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -10937,6 +11767,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -10981,6 +11816,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -11025,6 +11865,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -11069,6 +11914,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -11314,6 +12164,288 @@ components: source_database_name: northwind source_node_name: n1 source_node: n1 + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + cpus: 500m + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 500M + name: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + source_database_name: northwind + source_node_name: n1 + source_node: n1 + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + cpus: 500m + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 500M + name: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + source_database_name: northwind + source_node_name: n1 + source_node: n1 minItems: 1 maxItems: 9 orchestrator_opts: @@ -11357,6 +12489,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -11401,6 +12538,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -11445,6 +12587,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -11489,6 +12636,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -11920,6 +13072,147 @@ components: source_database_name: northwind source_node_name: n1 source_node: n1 + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + cpus: 500m + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 500M + name: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + source_database_name: northwind + source_node_name: n1 + source_node: n1 orchestrator_opts: swarm: extra_labels: @@ -11988,6 +13281,60 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 512M + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + port: 0 + service_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + service_type: mcp + version: latest + - config: + llm_model: gpt-4 + llm_provider: openai + openai_api_key: sk-... + cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -12032,6 +13379,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -12076,6 +13428,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -12321,95 +13678,84 @@ components: source_database_name: northwind source_node_name: n1 source_node: n1 - minItems: 1 - maxItems: 9 - orchestrator_opts: - $ref: '#/components/schemas/OrchestratorOpts' - patroni_port: - type: integer - description: 'The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.' - example: 8888 - format: int64 - minimum: 0 - maximum: 65535 - port: - type: integer - description: The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature. - example: 5432 - format: int64 - minimum: 0 - maximum: 65535 - postgres_version: - type: string - description: The Postgres version in 'major.minor' format. - example: "17.6" - pattern: ^\d{2}\.\d{1,2}$ - postgresql_conf: - type: object - description: Additional postgresql.conf settings. Will be merged with the settings provided by control-plane. - example: - max_connections: 1000 - maxLength: 64 - additionalProperties: true - restore_config: - $ref: '#/components/schemas/RestoreConfigSpec' - services: - type: array - items: - $ref: '#/components/schemas/ServiceSpec5' - description: Service instances to run alongside the database (e.g., MCP servers). - example: - - config: - llm_model: gpt-4 - llm_provider: openai - openai_api_key: sk-... - cpus: 500m - host_ids: - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - memory: 512M - orchestrator_opts: - swarm: - extra_labels: - traefik.enable: "true" - traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) - extra_networks: - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - extra_volumes: - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - port: 0 - service_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - service_type: mcp - version: latest - - config: - llm_model: gpt-4 - llm_provider: openai - openai_api_key: sk-... + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full cpus: 500m host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - memory: 512M + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 500M + name: n1 orchestrator_opts: swarm: extra_labels: @@ -12441,15 +13787,86 @@ components: host_path: /Users/user/backups/host - destination_path: /backups/container host_path: /Users/user/backups/host - port: 0 - service_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - service_type: mcp - version: latest + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + source_database_name: northwind + source_node_name: n1 + source_node: n1 + minItems: 1 + maxItems: 9 + orchestrator_opts: + $ref: '#/components/schemas/OrchestratorOpts' + patroni_port: + type: integer + description: 'The port used by Patroni for this database. NOTE: This field is not currently supported for Docker Swarm.' + example: 8888 + format: int64 + minimum: 0 + maximum: 65535 + port: + type: integer + description: The port used by the Postgres database. If the port is 0, each instance will be assigned a random port. If the port is unspecified, the database will not be exposed on any port, dependent on orchestrator support for that feature. + example: 5432 + format: int64 + minimum: 0 + maximum: 65535 + postgres_version: + type: string + description: The Postgres version in 'major.minor' format. + example: "17.6" + pattern: ^\d{2}\.\d{1,2}$ + postgresql_conf: + type: object + description: Additional postgresql.conf settings. Will be merged with the settings provided by control-plane. + example: + max_connections: 1000 + maxLength: 64 + additionalProperties: true + restore_config: + $ref: '#/components/schemas/RestoreConfigSpec' + services: + type: array + items: + $ref: '#/components/schemas/ServiceSpec5' + description: Service instances to run alongside the database (e.g., MCP servers). + example: - config: llm_model: gpt-4 llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 512M @@ -12493,6 +13910,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 512M @@ -12923,147 +14345,6 @@ components: source_database_name: northwind source_node_name: n1 source_node: n1 - - backup_config: - repositories: - - azure_account: pgedge-backups - azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - azure_endpoint: blob.core.usgovcloudapi.net - azure_key: YXpLZXk= - base_path: /backups - custom_options: - s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab - storage-upload-chunk-size: 5MiB - gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - gcs_endpoint: localhost - gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== - id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - retention_full: 2 - retention_full_type: count - s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - s3_endpoint: s3.us-east-1.amazonaws.com - s3_key: AKIAIOSFODNN7EXAMPLE - s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - s3_region: us-east-1 - type: s3 - - azure_account: pgedge-backups - azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - azure_endpoint: blob.core.usgovcloudapi.net - azure_key: YXpLZXk= - base_path: /backups - custom_options: - s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab - storage-upload-chunk-size: 5MiB - gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - gcs_endpoint: localhost - gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== - id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - retention_full: 2 - retention_full_type: count - s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - s3_endpoint: s3.us-east-1.amazonaws.com - s3_key: AKIAIOSFODNN7EXAMPLE - s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - s3_region: us-east-1 - type: s3 - - azure_account: pgedge-backups - azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - azure_endpoint: blob.core.usgovcloudapi.net - azure_key: YXpLZXk= - base_path: /backups - custom_options: - s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab - storage-upload-chunk-size: 5MiB - gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - gcs_endpoint: localhost - gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== - id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - retention_full: 2 - retention_full_type: count - s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - s3_endpoint: s3.us-east-1.amazonaws.com - s3_key: AKIAIOSFODNN7EXAMPLE - s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - s3_region: us-east-1 - type: s3 - schedules: - - cron_expression: 0 6 * * ? - id: daily-full-backup - type: full - - cron_expression: 0 6 * * ? - id: daily-full-backup - type: full - - cron_expression: 0 6 * * ? - id: daily-full-backup - type: full - cpus: 500m - host_ids: - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - memory: 500M - name: n1 - orchestrator_opts: - swarm: - extra_labels: - traefik.enable: "true" - traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) - extra_networks: - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - extra_volumes: - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - patroni_port: 8888 - port: 5432 - postgres_version: "17.6" - postgresql_conf: - max_connections: 1000 - restore_config: - repository: - azure_account: pgedge-backups - azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - azure_endpoint: blob.core.usgovcloudapi.net - azure_key: YXpLZXk= - base_path: /backups - custom_options: - s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab - gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - gcs_endpoint: localhost - gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== - id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 - s3_endpoint: s3.us-east-1.amazonaws.com - s3_key: AKIAIOSFODNN7EXAMPLE - s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - s3_region: us-east-1 - type: s3 - restore_options: - set: 20250505-153628F - target: "123456" - type: xid - source_database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - source_database_name: northwind - source_node_name: n1 - source_node: n1 orchestrator_opts: swarm: extra_labels: @@ -13132,6 +14413,59 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 512M + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + port: 0 + service_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + service_type: mcp + version: latest + - config: + llm_model: gpt-4 + llm_provider: openai + openai_api_key: sk-... + cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 512M @@ -13175,6 +14509,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 512M @@ -13218,6 +14557,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 512M @@ -13603,6 +14947,147 @@ components: source_database_name: northwind source_node_name: n1 source_node: n1 + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + cpus: 500m + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 500M + name: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + source_database_name: northwind + source_node_name: n1 + source_node: n1 minItems: 1 maxItems: 9 orchestrator_opts: @@ -13646,6 +15131,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -13690,6 +15180,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -13734,6 +15229,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -13778,6 +15278,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -14068,6 +15573,147 @@ components: source_database_name: northwind source_node_name: n1 source_node: n1 + - backup_config: + repositories: + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + - azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + storage-upload-chunk-size: 5MiB + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + retention_full: 2 + retention_full_type: count + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + schedules: + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + - cron_expression: 0 6 * * ? + id: daily-full-backup + type: full + cpus: 500m + host_ids: + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + memory: 500M + name: n1 + orchestrator_opts: + swarm: + extra_labels: + traefik.enable: "true" + traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) + extra_networks: + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + - aliases: + - pg-db + - db-alias + driver_opts: + com.docker.network.endpoint.expose: "true" + id: traefik-public + extra_volumes: + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + - destination_path: /backups/container + host_path: /Users/user/backups/host + patroni_port: 8888 + port: 5432 + postgres_version: "17.6" + postgresql_conf: + max_connections: 1000 + restore_config: + repository: + azure_account: pgedge-backups + azure_container: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + azure_endpoint: blob.core.usgovcloudapi.net + azure_key: YXpLZXk= + base_path: /backups + custom_options: + s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab + gcs_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + gcs_endpoint: localhost + gcs_key: ZXhhbXBsZSBnY3Mga2V5Cg== + id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + s3_bucket: pgedge-backups-9f81786f-373b-4ff2-afee-e054a06a96f1 + s3_endpoint: s3.us-east-1.amazonaws.com + s3_key: AKIAIOSFODNN7EXAMPLE + s3_key_secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + s3_region: us-east-1 + type: s3 + restore_options: + set: 20250505-153628F + target: "123456" + type: xid + source_database_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 + source_database_name: northwind + source_node_name: n1 + source_node: n1 orchestrator_opts: swarm: extra_labels: @@ -14136,6 +15782,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -14180,6 +15831,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -14224,50 +15880,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m - host_ids: - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - memory: 512M - orchestrator_opts: - swarm: - extra_labels: - traefik.enable: "true" - traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) - extra_networks: - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - extra_volumes: - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - port: 0 - service_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - service_type: mcp - version: latest - - config: - llm_model: gpt-4 - llm_provider: openai - openai_api_key: sk-... - cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -14838,6 +16455,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -14882,6 +16504,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -15522,6 +17149,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -15566,50 +17198,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m - host_ids: - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 - memory: 512M - orchestrator_opts: - swarm: - extra_labels: - traefik.enable: "true" - traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) - extra_networks: - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - - aliases: - - pg-db - - db-alias - driver_opts: - com.docker.network.endpoint.expose: "true" - id: traefik-public - extra_volumes: - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - - destination_path: /backups/container - host_path: /Users/user/backups/host - port: 0 - service_id: 76f9b8c0-4958-11f0-a489-3bb29577c696 - service_type: mcp - version: latest - - config: - llm_model: gpt-4 - llm_provider: openai - openai_api_key: sk-... - cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -15678,7 +17271,7 @@ components: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -15686,7 +17279,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -15702,15 +17295,15 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" - connection_info: addresses: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -15718,7 +17311,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -15734,108 +17327,108 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - state: - type: string - description: Current state of the database. - example: backing_up - enum: - - creating - - modifying - - available - - deleting - - degraded - - failed - - backing_up - - restoring - - unknown - tenant_id: - type: string - description: A user-specified identifier. Must be 1-63 characters, contain only lower-cased letters and hyphens, start and end with a letter or number, and not contain consecutive hyphens. - example: 76f9b8c0-4958-11f0-a489-3bb29577c696 - minLength: 1 - maxLength: 63 - updated_at: - type: string - description: The time that the database was last updated. - example: "2025-01-01T02:30:00Z" - format: date-time - example: - created_at: "2025-01-01T01:30:00Z" - id: 02f1a7db-fca8-4521-b57a-2a375c1ced51 - instances: - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" + - connection_info: + addresses: + - 10.24.34.2 + - i-0123456789abcdef.ec2.internal + port: 5432 + created_at: "1971-07-18T16:52:12Z" + error: 'failed to get patroni status: connection refused' + host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec + id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d + node_name: n1 + postgres: + patroni_paused: false + patroni_state: unknown + pending_restart: true + role: primary + version: "18.1" + spock: + read_only: "off" + subscriptions: + - name: sub_n1n2 + provider_node: n2 + status: down + - name: sub_n1n2 + provider_node: n2 + status: down + - name: sub_n1n2 + provider_node: n2 + status: down + version: 4.10.0 + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" + - connection_info: + addresses: + - 10.24.34.2 + - i-0123456789abcdef.ec2.internal + port: 5432 + created_at: "1971-07-18T16:52:12Z" + error: 'failed to get patroni status: connection refused' + host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec + id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d + node_name: n1 + postgres: + patroni_paused: false + patroni_state: unknown + pending_restart: true + role: primary + version: "18.1" + spock: + read_only: "off" + subscriptions: + - name: sub_n1n2 + provider_node: n2 + status: down + - name: sub_n1n2 + provider_node: n2 + status: down + - name: sub_n1n2 + provider_node: n2 + status: down + version: 4.10.0 + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" + state: + type: string + description: Current state of the database. + example: failed + enum: + - creating + - modifying + - available + - deleting + - degraded + - failed + - backing_up + - restoring + - unknown + tenant_id: + type: string + description: A user-specified identifier. Must be 1-63 characters, contain only lower-cased letters and hyphens, start and end with a letter or number, and not contain consecutive hyphens. + example: 76f9b8c0-4958-11f0-a489-3bb29577c696 + minLength: 1 + maxLength: 63 + updated_at: + type: string + description: The time that the database was last updated. + example: "2025-01-01T02:30:00Z" + format: date-time + example: + created_at: "2025-01-01T01:30:00Z" + id: 02f1a7db-fca8-4521-b57a-2a375c1ced51 + instances: - connection_info: addresses: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -15843,7 +17436,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -15859,15 +17452,15 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" - connection_info: addresses: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -15875,7 +17468,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -15891,10 +17484,10 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - state: creating + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" + state: degraded tenant_id: 8210ec10-2dca-406c-ac4a-0661d2189954 updated_at: "2025-01-01T02:30:00Z" required: @@ -15909,7 +17502,7 @@ components: type: array items: type: string - example: Eos natus. + example: Et nam quo qui qui natus. description: The attributes to assign to this database user. example: - LOGIN @@ -15919,7 +17512,7 @@ components: db_owner: type: boolean description: If true, this user will be granted database ownership. - example: true + example: false password: type: string description: The password for this database user. This field will be excluded from the response of all endpoints. It can also be omitted from update requests to keep the current value. @@ -15929,7 +17522,7 @@ components: type: array items: type: string - example: Excepturi consectetur accusantium ut. + example: Veniam exercitationem placeat temporibus aut. description: The roles to assign to this database user. example: - pgedge_superuser @@ -15944,7 +17537,7 @@ components: - LOGIN - CREATEDB - CREATEROLE - db_owner: true + db_owner: false password: secret roles: - pgedge_superuser @@ -15972,7 +17565,7 @@ components: type: array items: type: string - example: Voluptatem beatae quasi ipsum officiis et. + example: Accusantium ut aperiam qui sed quis architecto. description: The Etcd client endpoint for this cluster member. example: - http://192.168.1.1:2379 @@ -15984,7 +17577,7 @@ components: type: array items: type: string - example: Delectus sit quas. + example: Labore explicabo culpa eos natus aperiam excepturi. description: The Etcd peer endpoint for this cluster member. example: - http://192.168.1.1:2380 @@ -16005,7 +17598,7 @@ components: type: array items: type: string - example: Placeat placeat a enim. + example: Illo dolore. description: Optional network-scoped aliases for the container. example: - pg-db @@ -16018,7 +17611,7 @@ components: com.docker.network.endpoint.expose: "true" additionalProperties: type: string - example: Voluptates maxime hic aut omnis magnam. + example: Accusamus alias ipsa qui. id: type: string description: The name or ID of the network to connect to. @@ -16075,7 +17668,7 @@ components: type: boolean description: If true, skip the health validations that prevent running failover on a healthy cluster. default: false - example: false + example: true example: candidate_instance_id: 68f50878-44d2-4524-a823-e31bd478706d-n1-689qacsi database_id: my-app @@ -16098,7 +17691,7 @@ components: example: false example: candidate_instance_id: 68f50878-44d2-4524-a823-e31bd478706d-n1-689qacsi - skip_validation: true + skip_validation: false FailoverDatabaseNodeResponse: type: object properties: @@ -16148,7 +17741,7 @@ components: type: array items: type: string - example: Similique sed neque eos rerum quia. + example: Quas nostrum eos reprehenderit harum sapiente qui. description: The addresses that this host advertises to client applications. example: - 10.24.34.2 @@ -16191,7 +17784,7 @@ components: type: array items: type: string - example: At esse ut possimus error eligendi. + example: Quo recusandae quibusdam fuga molestiae. description: The addresses that this host advertises to other hosts. example: - 10.24.34.2 @@ -16208,8 +17801,6 @@ components: spock_version: "5" - postgres_version: "17.6" spock_version: "5" - - postgres_version: "17.6" - spock_version: "5" example: client_addresses: - 10.24.34.2 @@ -16251,10 +17842,6 @@ components: spock_version: "5" - postgres_version: "17.6" spock_version: "5" - - postgres_version: "17.6" - spock_version: "5" - - postgres_version: "17.6" - spock_version: "5" required: - id - orchestrator @@ -16292,7 +17879,7 @@ components: type: object description: The status of each component of the host. example: - Ullam minus dolore eos et.: + Possimus error eligendi recusandae.: details: alarms: - '3: NOSPACE' @@ -16315,13 +17902,19 @@ components: format: date-time example: components: - Animi dolor quam aliquid cupiditate.: + Atque facilis non modi explicabo illum.: + details: + alarms: + - '3: NOSPACE' + error: failed to connect to etcd + healthy: false + Neque eos rerum quia.: details: alarms: - '3: NOSPACE' error: failed to connect to etcd healthy: false - Eos aut dignissimos cum voluptate modi consequatur.: + Qui et eius reiciendis accusamus.: details: alarms: - '3: NOSPACE' @@ -16353,7 +17946,7 @@ components: created_at: type: string description: The time that the instance was created. - example: "2004-09-06T11:51:20Z" + example: "1977-01-28T10:07:37Z" format: date-time error: type: string @@ -16377,7 +17970,7 @@ components: $ref: '#/components/schemas/InstanceSpockStatus' state: type: string - example: stopped + example: available enum: - creating - modifying @@ -16391,12 +17984,12 @@ components: status_updated_at: type: string description: The time that the instance status information was last updated. - example: "2001-10-21T23:10:32Z" + example: "1999-11-24T20:30:24Z" format: date-time updated_at: type: string description: The time that the instance was last modified. - example: "2000-06-12T17:41:55Z" + example: "2012-07-13T20:43:05Z" format: date-time description: An instance of pgEdge Postgres running on a host. example: @@ -16405,7 +17998,7 @@ components: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1980-04-21T14:10:30Z" + created_at: "2003-06-28T19:25:47Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -16413,7 +18006,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -16429,9 +18022,9 @@ components: provider_node: n2 status: down version: 4.10.0 - state: failed - status_updated_at: "1999-10-09T09:59:39Z" - updated_at: "2006-10-19T03:24:20Z" + state: modifying + status_updated_at: "1992-10-18T20:01:09Z" + updated_at: "1982-02-16T16:16:42Z" required: - id - host_id @@ -16446,7 +18039,7 @@ components: type: array items: type: string - example: Explicabo illum alias qui et eius. + example: Eum ducimus. description: The addresses of the host that's running this instance. example: - 10.24.34.2 @@ -16485,9 +18078,9 @@ components: example: "18.1" description: Postgres status information for a pgEdge instance. example: - patroni_paused: true + patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" InstanceSpockStatus: @@ -16509,6 +18102,12 @@ components: - name: sub_n1n2 provider_node: n2 status: down + - name: sub_n1n2 + provider_node: n2 + status: down + - name: sub_n1n2 + provider_node: n2 + status: down version: type: string description: The version of Spock for this instance. @@ -16523,6 +18122,12 @@ components: - name: sub_n1n2 provider_node: n2 status: down + - name: sub_n1n2 + provider_node: n2 + status: down + - name: sub_n1n2 + provider_node: n2 + status: down version: 4.10.0 InstanceSubscription: type: object @@ -16550,218 +18155,84 @@ components: - name - status ListDatabaseTasksResponse: - type: object - properties: - tasks: - type: array - items: - $ref: '#/components/schemas/Task' - description: The tasks for the given database. - example: - - completed_at: "2025-06-18T16:52:35Z" - created_at: "2025-06-18T16:52:05Z" - database_id: storefront - entity_id: storefront - scope: database - status: completed - task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 - type: create - - completed_at: "2025-06-18T16:52:35Z" - created_at: "2025-06-18T16:52:05Z" - database_id: storefront - entity_id: storefront - scope: database - status: completed - task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 - type: create - - completed_at: "2025-06-18T16:52:35Z" - created_at: "2025-06-18T16:52:05Z" - database_id: storefront - entity_id: storefront - scope: database - status: completed - task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 - type: create - example: - tasks: - - completed_at: "2025-06-18T17:54:36Z" - created_at: "2025-06-18T17:54:28Z" - database_id: storefront - entity_id: storefront - instance_id: storefront-n1-689qacsi - scope: database - status: completed - task_id: 0197842d-9082-7496-b787-77bd2e11809f - type: node_backup - - completed_at: "2025-06-18T17:54:04Z" - created_at: "2025-06-18T17:53:17Z" - database_id: storefront - entity_id: storefront - scope: database - status: completed - task_id: 0197842c-7c4f-7a8c-829e-7405c2a41c8c - type: update - - completed_at: "2025-06-18T17:23:28Z" - created_at: "2025-06-18T17:23:14Z" - database_id: storefront - entity_id: storefront - scope: database - status: completed - task_id: 01978410-fb5d-7cd2-bbd2-66c0bf929dc0 - type: update - - completed_at: "2025-06-18T16:52:35Z" - created_at: "2025-06-18T16:52:05Z" - database_id: storefront - entity_id: storefront - scope: database - status: completed - task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 - type: create - required: - - tasks - ListDatabasesResponse: - type: object - properties: - databases: - type: array - items: - $ref: '#/components/schemas/DatabaseSummary' - description: The databases managed by this cluster. - example: - - created_at: "2025-01-01T01:30:00Z" - id: 02f1a7db-fca8-4521-b57a-2a375c1ced51 - instances: - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - state: modifying - tenant_id: 8210ec10-2dca-406c-ac4a-0661d2189954 - updated_at: "2025-01-01T02:30:00Z" + type: object + properties: + tasks: + type: array + items: + $ref: '#/components/schemas/Task' + description: The tasks for the given database. + example: + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create + example: + tasks: + - completed_at: "2025-06-18T17:54:36Z" + created_at: "2025-06-18T17:54:28Z" + database_id: storefront + entity_id: storefront + instance_id: storefront-n1-689qacsi + scope: database + status: completed + task_id: 0197842d-9082-7496-b787-77bd2e11809f + type: node_backup + - completed_at: "2025-06-18T17:54:04Z" + created_at: "2025-06-18T17:53:17Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 0197842c-7c4f-7a8c-829e-7405c2a41c8c + type: update + - completed_at: "2025-06-18T17:23:28Z" + created_at: "2025-06-18T17:23:14Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 01978410-fb5d-7cd2-bbd2-66c0bf929dc0 + type: update + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create + required: + - tasks + ListDatabasesResponse: + type: object + properties: + databases: + type: array + items: + $ref: '#/components/schemas/DatabaseSummary' + description: The databases managed by this cluster. + example: - created_at: "2025-01-01T01:30:00Z" id: 02f1a7db-fca8-4521-b57a-2a375c1ced51 instances: @@ -16770,71 +18241,7 @@ components: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -16842,7 +18249,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -16858,15 +18265,15 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" - connection_info: addresses: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -16874,7 +18281,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -16890,10 +18297,10 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - state: modifying + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" + state: failed tenant_id: 8210ec10-2dca-406c-ac4a-0661d2189954 updated_at: "2025-01-01T02:30:00Z" - created_at: "2025-01-01T01:30:00Z" @@ -16904,71 +18311,7 @@ components: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -16976,7 +18319,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -16992,15 +18335,15 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" - connection_info: addresses: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -17008,7 +18351,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -17024,10 +18367,10 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - state: modifying + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" + state: failed tenant_id: 8210ec10-2dca-406c-ac4a-0661d2189954 updated_at: "2025-01-01T02:30:00Z" - created_at: "2025-01-01T01:30:00Z" @@ -17038,71 +18381,7 @@ components: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" - error: 'failed to get patroni status: connection refused' - host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d - node_name: n1 - postgres: - patroni_paused: false - patroni_state: unknown - pending_restart: false - role: primary - version: "18.1" - spock: - read_only: "off" - subscriptions: - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - - name: sub_n1n2 - provider_node: n2 - status: down - version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - - connection_info: - addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -17110,7 +18389,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -17126,15 +18405,15 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" - connection_info: addresses: - 10.24.34.2 - i-0123456789abcdef.ec2.internal port: 5432 - created_at: "1991-01-10T16:19:34Z" + created_at: "1971-07-18T16:52:12Z" error: 'failed to get patroni status: connection refused' host_id: de3b1388-1f0c-42f1-a86c-59ab72f255ec id: a67cbb36-c3c3-49c9-8aac-f4a0438a883d @@ -17142,7 +18421,7 @@ components: postgres: patroni_paused: false patroni_state: unknown - pending_restart: false + pending_restart: true role: primary version: "18.1" spock: @@ -17158,10 +18437,10 @@ components: provider_node: n2 status: down version: 4.10.0 - state: modifying - status_updated_at: "2006-12-23T09:53:03Z" - updated_at: "1994-06-04T08:19:03Z" - state: modifying + state: deleting + status_updated_at: "1999-09-23T18:53:48Z" + updated_at: "2013-04-06T16:38:09Z" + state: failed tenant_id: 8210ec10-2dca-406c-ac4a-0661d2189954 updated_at: "2025-01-01T02:30:00Z" example: @@ -17359,6 +18638,22 @@ components: status: completed task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create example: tasks: - completed_at: "2025-06-18T17:54:36Z" @@ -17464,90 +18759,6 @@ components: spock_version: "5" - postgres_version: "17.6" spock_version: "5" - - client_addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - cohort: - control_available: true - member_id: lah4bsznw6kc0hp7biylmmmll - type: swarm - cpus: 4 - data_dir: /data - default_pgedge_version: - postgres_version: "17.6" - spock_version: "5" - etcd_mode: server - id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - memory: 16GiB - orchestrator: swarm - peer_addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - status: - components: - Enim et voluptatum ex ea dolore.: - details: - alarms: - - '3: NOSPACE' - error: failed to connect to etcd - healthy: false - Tenetur nostrum repellendus sint qui.: - details: - alarms: - - '3: NOSPACE' - error: failed to connect to etcd - healthy: false - state: available - updated_at: "2021-07-01T12:34:56Z" - supported_pgedge_versions: - - postgres_version: "17.6" - spock_version: "5" - - postgres_version: "17.6" - spock_version: "5" - - postgres_version: "17.6" - spock_version: "5" - - client_addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - cohort: - control_available: true - member_id: lah4bsznw6kc0hp7biylmmmll - type: swarm - cpus: 4 - data_dir: /data - default_pgedge_version: - postgres_version: "17.6" - spock_version: "5" - etcd_mode: server - id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - memory: 16GiB - orchestrator: swarm - peer_addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - status: - components: - Enim et voluptatum ex ea dolore.: - details: - alarms: - - '3: NOSPACE' - error: failed to connect to etcd - healthy: false - Tenetur nostrum repellendus sint qui.: - details: - alarms: - - '3: NOSPACE' - error: failed to connect to etcd - healthy: false - state: available - updated_at: "2021-07-01T12:34:56Z" - supported_pgedge_versions: - - postgres_version: "17.6" - spock_version: "5" - - postgres_version: "17.6" - spock_version: "5" - - postgres_version: "17.6" - spock_version: "5" description: Response containing the list of hosts example: hosts: @@ -17635,90 +18846,6 @@ components: spock_version: "5" - postgres_version: "17.6" spock_version: "5" - - client_addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - cohort: - control_available: true - member_id: lah4bsznw6kc0hp7biylmmmll - type: swarm - cpus: 4 - data_dir: /data - default_pgedge_version: - postgres_version: "17.6" - spock_version: "5" - etcd_mode: server - id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - memory: 16GiB - orchestrator: swarm - peer_addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - status: - components: - Enim et voluptatum ex ea dolore.: - details: - alarms: - - '3: NOSPACE' - error: failed to connect to etcd - healthy: false - Tenetur nostrum repellendus sint qui.: - details: - alarms: - - '3: NOSPACE' - error: failed to connect to etcd - healthy: false - state: available - updated_at: "2021-07-01T12:34:56Z" - supported_pgedge_versions: - - postgres_version: "17.6" - spock_version: "5" - - postgres_version: "17.6" - spock_version: "5" - - postgres_version: "17.6" - spock_version: "5" - - client_addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - cohort: - control_available: true - member_id: lah4bsznw6kc0hp7biylmmmll - type: swarm - cpus: 4 - data_dir: /data - default_pgedge_version: - postgres_version: "17.6" - spock_version: "5" - etcd_mode: server - id: de3b1388-1f0c-42f1-a86c-59ab72f255ec - memory: 16GiB - orchestrator: swarm - peer_addresses: - - 10.24.34.2 - - i-0123456789abcdef.ec2.internal - status: - components: - Enim et voluptatum ex ea dolore.: - details: - alarms: - - '3: NOSPACE' - error: failed to connect to etcd - healthy: false - Tenetur nostrum repellendus sint qui.: - details: - alarms: - - '3: NOSPACE' - error: failed to connect to etcd - healthy: false - state: available - updated_at: "2021-07-01T12:34:56Z" - supported_pgedge_versions: - - postgres_version: "17.6" - spock_version: "5" - - postgres_version: "17.6" - spock_version: "5" - - postgres_version: "17.6" - spock_version: "5" required: - hosts ListTasksResponse: @@ -17746,6 +18873,14 @@ components: status: completed task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create example: tasks: - completed_at: "2025-06-18T17:54:36Z" @@ -17876,6 +19011,22 @@ components: status: completed task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create example: task: completed_at: "2025-06-18T16:52:35Z" @@ -17903,6 +19054,22 @@ components: status: completed task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create required: - task - update_database_tasks @@ -17939,7 +19106,7 @@ components: maxLength: 32 additionalProperties: type: string - example: Temporibus aut. + example: Dolor autem eum. source_database_id: type: string description: A user-specified identifier. Must be 1-63 characters, contain only lower-cased letters and hyphens, start and end with a letter or number, and not contain consecutive hyphens. @@ -17997,7 +19164,7 @@ components: type: array items: type: string - example: Sed exercitationem rerum animi et. + example: At praesentium ut dolorem sapiente. description: The nodes to restore. Defaults to all nodes if empty or unspecified. example: - n1 @@ -18049,6 +19216,14 @@ components: status: completed task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 type: create + - completed_at: "2025-06-18T16:52:35Z" + created_at: "2025-06-18T16:52:05Z" + database_id: storefront + entity_id: storefront + scope: database + status: completed + task_id: 019783f4-75f4-71e7-85a3-c9b96b345d77 + type: create task: $ref: '#/components/schemas/Task' example: @@ -18402,7 +19577,7 @@ components: s3-kms-key-id: 1234abcd-12ab-34cd-56ef-1234567890ab additionalProperties: type: string - example: Qui qui natus qui veniam exercitationem. + example: Aut omnis magnam aspernatur occaecati itaque dolores. gcs_bucket: type: string description: The GCS bucket name for this repository. Only applies when type = 'gcs'. @@ -18557,12 +19732,6 @@ components: - container_port: 8080 host_port: 8080 name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client - - container_port: 8080 - host_port: 8080 - name: web-client service_ready: true updated_at: "2025-01-28T10:05:00Z" required: @@ -18580,7 +19749,7 @@ components: type: array items: type: string - example: Perferendis vitae quas nam voluptatum. + example: Necessitatibus labore ipsa id modi et et. description: The addresses of the host that's running this service instance. example: - 10.24.34.2 @@ -18612,6 +19781,12 @@ components: - container_port: 8080 host_port: 8080 name: web-client + - container_port: 8080 + host_port: 8080 + name: web-client + - container_port: 8080 + host_port: 8080 + name: web-client service_ready: type: boolean description: Whether the service is ready to accept requests. @@ -18638,6 +19813,9 @@ components: - container_port: 8080 host_port: 8080 name: web-client + - container_port: 8080 + host_port: 8080 + name: web-client service_ready: true ServiceSpec: type: object @@ -18655,6 +19833,8 @@ components: 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. example: 500m pattern: ^[0-9]+(\.[0-9]{1,3}|m)?$ + database_connection: + $ref: '#/components/schemas/DatabaseConnection' host_ids: type: array items: @@ -18666,7 +19846,6 @@ components: description: The IDs of the hosts that should run this service. One service instance will be created per host. example: - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec minItems: 1 memory: type: string @@ -18706,10 +19885,13 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec - - de3b1388-1f0c-42f1-a86c-59ab72f255ec memory: 512M orchestrator_opts: swarm: @@ -18768,6 +19950,8 @@ components: 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. example: 500m pattern: ^[0-9]+(\.[0-9]{1,3}|m)?$ + database_connection: + $ref: '#/components/schemas/DatabaseConnection' host_ids: type: array items: @@ -18779,7 +19963,6 @@ components: example: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 minItems: 1 memory: type: string @@ -18819,8 +20002,15 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 512M orchestrator_opts: swarm: @@ -18879,6 +20069,8 @@ components: 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. example: 500m pattern: ^[0-9]+(\.[0-9]{1,3}|m)?$ + database_connection: + $ref: '#/components/schemas/DatabaseConnection' host_ids: type: array items: @@ -18889,6 +20081,8 @@ components: description: The IDs of the hosts that should run this service. One service instance will be created per host. example: - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 minItems: 1 memory: type: string @@ -18928,8 +20122,14 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 512M orchestrator_opts: swarm: @@ -18988,6 +20188,8 @@ components: 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. example: 500m pattern: ^[0-9]+(\.[0-9]{1,3}|m)?$ + database_connection: + $ref: '#/components/schemas/DatabaseConnection' host_ids: type: array items: @@ -19038,9 +20240,15 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 512M orchestrator_opts: swarm: @@ -19099,6 +20307,8 @@ components: 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. example: 500m pattern: ^[0-9]+(\.[0-9]{1,3}|m)?$ + database_connection: + $ref: '#/components/schemas/DatabaseConnection' host_ids: type: array items: @@ -19150,6 +20360,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -19211,6 +20426,8 @@ components: 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. example: 500m pattern: ^[0-9]+(\.[0-9]{1,3}|m)?$ + database_connection: + $ref: '#/components/schemas/DatabaseConnection' host_ids: type: array items: @@ -19222,6 +20439,7 @@ components: example: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 minItems: 1 memory: type: string @@ -19261,6 +20479,11 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - 76f9b8c0-4958-11f0-a489-3bb29577c696 @@ -19323,6 +20546,8 @@ components: 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. example: 500m pattern: ^[0-9]+(\.[0-9]{1,3}|m)?$ + database_connection: + $ref: '#/components/schemas/DatabaseConnection' host_ids: type: array items: @@ -19333,6 +20558,7 @@ components: description: The IDs of the hosts that should run this service. One service instance will be created per host. example: - 76f9b8c0-4958-11f0-a489-3bb29577c696 + - 76f9b8c0-4958-11f0-a489-3bb29577c696 minItems: 1 memory: type: string @@ -19372,9 +20598,13 @@ components: llm_provider: openai openai_api_key: sk-... cpus: 500m + database_connection: + target_nodes: + - n1 + - n2 + target_session_attrs: primary host_ids: - 76f9b8c0-4958-11f0-a489-3bb29577c696 - - 76f9b8c0-4958-11f0-a489-3bb29577c696 memory: 512M orchestrator_opts: swarm: @@ -19464,7 +20694,7 @@ components: traefik.tcp.routers.mydb.rule: HostSNI(`mydb.example.com`) additionalProperties: type: string - example: Dolores voluptatum dolor autem eum et et. + example: Aut doloribus rem culpa ullam minus dolore. extra_networks: type: array items: @@ -19712,6 +20942,11 @@ components: status: creating message: task started timestamp: "2025-05-29T15:43:13Z" + - fields: + option.enabled: true + status: creating + message: task started + timestamp: "2025-05-29T15:43:13Z" last_entry_id: type: string description: The ID of the last entry in the task log. diff --git a/docs/development/supported-services.md b/docs/development/supported-services.md index c0733878..3bcbabe0 100644 --- a/docs/development/supported-services.md +++ b/docs/development/supported-services.md @@ -5,12 +5,8 @@ MCP is the reference implementation throughout. All touch points are summarized the [checklist](#checklist-adding-a-new-service-type) at the end. > [!CAUTION] -> This document is subject to change. The MCP service is not yet fully -> implemented, and several areas are still in active design: +> This document is subject to change. Some areas are still in active design: > -> - **Configuration delivery**: How config reaches service containers (env vars, -> config files, mounts, etc.) is still being designed and may end up being -> service-specific. > - **Workflow refactoring**: Some of the workflow code (e.g., service > provisioning) needs to be refactored based on PR feedback. This work is > being coordinated with the SystemD migration tasks. @@ -61,7 +57,8 @@ API Request (spec.services) → For each (service, host_id): → Resolve service image from registry → Validate Postgres/Spock version compatibility - → Generate resource objects (network, user role, spec, instance, monitor) + → Build multi-host connection info (BuildServiceHostList) + → Generate resource objects (network, user role, dir, config, spec, instance, monitor) → Merge service resources into EndState → Compute plan (diff current state vs. desired state) → Apply plan (execute resource Create/Update/Delete) @@ -87,8 +84,10 @@ The `ServiceSpec` type has these attributes: | `host_ids` | `[]Identifier` | Which hosts should run this service (one instance per host) | | `port` | `Int` (optional) | Host port to publish; `0` = random; omitted = not published | | `config` | `MapOf(String, Any)` | Service-specific configuration | +| `database_connection` | `DatabaseConnection` (optional) | Controls database connection topology (see [Optional: `database_connection`](#optional-database_connection)) | | `cpus` | `String` (optional) | CPU limit; accepts SI suffix `m` (e.g., `"500m"`, `"1"`) | | `memory` | `String` (optional) | Memory limit in SI or IEC notation (e.g., `"512M"`, `"1GiB"`) | +| `orchestrator_opts` | `OrchestratorOpts` (optional) | Orchestrator-specific options (e.g., Swarm extra labels) | To add a new service type, add its string value to the enum on `service_type`: @@ -235,22 +234,30 @@ constraint >=15"`. ## Resource Lifecycle -Every service instance is represented by five resources that participate in the -standard plan/apply reconciliation cycle. These resource types are generic — -adding a new service type does **not** require new resource types. +Every service instance is represented by a chain of resources that participate +in the standard plan/apply reconciliation cycle. The generic resources (Network, +ServiceUserRole, DirResource, ServiceInstanceSpec, ServiceInstance, Monitor) +are shared by all service types. Service-type-specific config resources (e.g., +MCPConfigResource) slot into the chain between DirResource and +ServiceInstanceSpec. ### Dependency chain ```text -Phase 1: Network (swarm.network) — no dependencies - ServiceUserRole (swarm.service_user_role) — no dependencies -Phase 2: ServiceInstanceSpec (swarm.service_instance_spec) — depends on Network + ServiceUserRole -Phase 3: ServiceInstance (swarm.service_instance) — depends on ServiceUserRole + ServiceInstanceSpec -Phase 4: ServiceInstanceMonitor (monitor.service_instance) — depends on ServiceInstance +Phase 1: Network (swarm.network) — no dependencies + ServiceUserRole (swarm.service_user_role) — no dependencies +Phase 2: DirResource (filesystem.dir) — no dependencies +Phase 3: MCPConfigResource (swarm.mcp_config) — depends on DirResource + ServiceUserRole +Phase 4: ServiceInstanceSpec (swarm.service_instance_spec) — depends on Network + MCPConfigResource +Phase 5: ServiceInstance (swarm.service_instance) — depends on ServiceUserRole + ServiceInstanceSpec +Phase 6: ServiceInstanceMonitor (monitor.service_instance) — depends on ServiceInstance ``` -On deletion, the order reverses: monitor first, then instance, spec, and -finally the network and user role. +On deletion, the order reverses: monitor first, then instance, spec, config, +directory, and finally the network and user role. + +A new service type replaces `MCPConfigResource` (Phase 3) with its own config +resource. The rest of the chain is unchanged. ### What each resource does @@ -262,14 +269,35 @@ naturally via identifier matching (both generate the same Runs on `ManagerExecutor`. **ServiceUserRole** (`server/internal/orchestrator/swarm/service_user_role.go`): -Manages the Postgres user lifecycle for a service instance. On `Create`, it -generates a deterministic username via `database.GenerateServiceUsername()` (format: -`svc_{serviceID}_{hostID}`), generates a random 32-byte password, and creates a -Postgres role with `pgedge_application_read_only` privileges. On `Delete`, it -drops the role. Runs on `HostExecutor(postgresHostID)` because it needs Docker -access to the Postgres container for direct database connectivity. See -`docs/development/service-credentials.md` for full details on credential -generation. +Manages the Postgres user lifecycle for a service. This resource is keyed by +`ServiceID`, so one role is shared across all instances of the same service. On +`Create`, it generates a deterministic username via +`database.GenerateServiceUsername()` (format: `svc_{serviceID}`), generates a +random 32-byte password, creates the Postgres role with `LOGIN` and grants +read-only access to the `public` schema. Credentials are persisted in the +resource state and reused on subsequent reconciliation cycles. The role is +created on the primary instance and Spock replicates it to all other nodes +automatically. On `Delete`, it drops the role. Runs on +`PrimaryExecutor(nodeName)`. See `docs/development/service-credentials.md` for +full details on credential generation. + +**DirResource** (`server/internal/filesystem/dir_resource.go`): Creates and +manages a host-side directory for the service instance's data files. The +directory is bind-mounted into the container at `/app/data`. Config resources +(like MCPConfigResource) write files into this directory before the container +starts. On `Delete`, the directory and its contents are removed. Runs on +`HostExecutor(hostID)`. + +**MCPConfigResource** (`server/internal/orchestrator/swarm/mcp_config_resource.go`): +Generates and writes MCP server config files to the data directory. Manages +three files: +- `config.yaml`: CP-owned, overwritten on every Create/Update +- `tokens.yaml`: Application-owned, written only on first Create +- `users.yaml`: Application-owned, written only on first Create + +Credentials are populated from the ServiceUserRole resource at runtime. A new +service type would create an analogous config resource for its own config format. +Runs on `HostExecutor(hostID)`. **ServiceInstanceSpec** (`server/internal/orchestrator/swarm/service_instance_spec.go`): A virtual resource that generates the Docker Swarm `ServiceSpec`. Its @@ -299,8 +327,9 @@ in etcd. Runs on `HostExecutor(hostID)`. 1. Resolves the `ServiceImage` via `GetServiceImage(serviceType, version)` 2. Validates Postgres/Spock version compatibility if constraints exist -3. Constructs the four orchestrator resources (`Network`, `ServiceUserRole`, - `ServiceInstanceSpec`, `ServiceInstance`) +3. Constructs the resource chain: `Network` → `ServiceUserRole` → + `DirResource` → config resource (e.g., `MCPConfigResource`) → + `ServiceInstanceSpec` → `ServiceInstance` 4. Serializes them to `[]*resource.ResourceData` via `resource.ToResourceData()` 5. Returns a `*database.ServiceInstanceResources` wrapper @@ -328,6 +357,12 @@ The generated spec configures: - **Networks**: attaches to both the default bridge network (for control plane access and external connectivity) and the database overlay network (for Postgres connectivity) +- **Bind mount**: the host-side data directory (managed by `DirResource`) is + mounted into the container at `/app/data`. Config files written by the + service-type-specific config resource (e.g., `MCPConfigResource`) are + available to the container at startup. +- **Entrypoint**: overrides the default container entrypoint to pass the config + file path as a CLI argument (e.g., `-config /app/data/config.yaml` for MCP) - **Port publication**: `buildServicePortConfig()` publishes port 8080 in host mode. If the `port` field in the spec is nil, no port is published. If it's 0, Docker assigns a random port. If it's a specific value, that port is used. @@ -335,18 +370,36 @@ The generated spec configures: with a 30s start period, 10s interval, 5s timeout, and 3 retries - **Resource limits**: CPU and memory limits from the spec, if provided -> [!NOTE] -> How configuration reaches the service container (environment variables, config -> files, mounts, etc.) is still evolving and will vary per service type. See -> `buildServiceEnvVars()` in `service_spec.go` for the current MCP -> implementation, but expect this area to change as new service types are added. - -For a new service type, you may need to: - -- Extend `ServiceContainerSpecOptions` with service-specific fields -- Add branches in `ServiceContainerSpec()` or its helper functions to handle - the new service type's requirements (different health check endpoint, different - target port, mount points, entrypoint, etc.) +### Configuration delivery + +Configuration reaches service containers via **bind-mounted config files**, not +environment variables. The pattern follows Patroni's config delivery: + +1. `DirResource` creates a host-side directory for the service instance +2. A service-type-specific config resource (e.g., `MCPConfigResource`) writes + config files into that directory +3. `ServiceContainerSpec()` bind-mounts the directory into the container +4. The container entrypoint reads the config file from the mount path + +This approach has several advantages over environment variables: +- Config files can be updated without recreating the container (the config + resource's `Update()` method overwrites the file, and the service can reload) +- Structured formats (YAML, JSON) are easier to validate and debug than + flattened env vars +- Sensitive values (API keys, passwords) are stored in files with restricted + permissions (`0600`) rather than being visible in `docker inspect` +- Application-owned files (like MCP's `tokens.yaml` and `users.yaml`) can be + written once and preserved across config updates + +For a new service type, you will need to: + +- Create a config resource (analogous to `MCPConfigResource`) that generates + your service's config files and writes them to the data directory +- Adjust the entrypoint/args in `ServiceContainerSpec()` if your service reads + config from a different path or uses a different CLI flag +- Extend `ServiceContainerSpecOptions` if your service needs additional + container-level settings (different health check endpoint, different target + port, additional mount points, etc.) ## Workflow Integration @@ -359,9 +412,9 @@ that computes the reconciliation plan. It: 1. Computes `NodeInstances` from the spec 2. Generates node resources (same as before services existed) -3. Determines a `postgresHostID` for `ServiceUserRole` executor routing — - `ServiceUserRole.Create()` needs local Docker access to the Postgres - container, so it picks the first available Postgres host +3. Determines a `nodeName` for `ServiceUserRole` executor routing — + `ServiceUserRole` runs on `PrimaryExecutor(nodeName)` so the role is + created on the primary instance and Spock replicates it to all nodes 4. Iterates `spec.Services` and for each `(service, hostID)` pair, calls `getServiceResources()` 5. Passes both node and service resources to `operations.UpdateDatabase()` @@ -373,13 +426,16 @@ for a single service instance: 1. Generates the `ServiceInstanceID` via `database.GenerateServiceInstanceID(databaseID, serviceID, hostID)` -2. Resolves the Postgres hostname via `findPostgresInstance()`, which prefers a - co-located instance (same host as the service) for lower latency but falls - back to any instance in the database -3. Constructs a `database.ServiceInstanceSpec` with all the inputs -4. Fires the `GenerateServiceInstanceResources` activity (executes on the +2. Resolves `target_session_attrs` via `resolveTargetSessionAttrs()` (see + [Database Connection Topology](#database-connection-topology)) +3. Builds the ordered host list via `database.BuildServiceHostList()`, which + produces an ordered `[]ServiceHostEntry` array with co-located and local-node + instances prioritized +4. Constructs a `database.ServiceInstanceSpec` with all the inputs (including + `DatabaseHosts` and `TargetSessionAttrs`) +5. Fires the `GenerateServiceInstanceResources` activity (executes on the manager queue) -5. Wraps the result in `operations.ServiceResources`, adding the +6. Wraps the result in `operations.ServiceResources`, adding the `ServiceInstanceMonitorResource` ### EndState @@ -404,6 +460,181 @@ Resources that exist in the current state but are absent from the end state are automatically marked `PendingDeletion` by the plan engine, which generates delete events in reverse dependency order. +## Database Connection Topology + +Services connect to Postgres via the Docker overlay network. Rather than +connecting to a single instance, each service instance receives an **ordered list +of hosts** so that libpq (or the service's connection library) can try multiple +instances in priority order and select one matching the desired role (primary vs +standby). + +The architecture separates **generic host ordering** (reusable by all service +types) from **service-specific configuration** (how each service type maps its +own semantics to connection parameters). + +### Generic layer: `BuildServiceHostList` + +`BuildServiceHostList()` in `server/internal/database/service_connection.go` is +the shared host list builder. It is **service-type agnostic** — it knows nothing +about MCP, `allow_writes`, or any service-specific config. It accepts: + +```go +type BuildServiceHostListParams struct { + ServiceHostID string // Host where the service instance runs + NodeInstances []*NodeInstances // All database instances, grouped by node + TargetNodes []string // Optional ordered node filter (from database_connection.target_nodes) + TargetSessionAttrs string // Caller-provided: "primary", "prefer-standby", etc. +} +``` + +And returns: + +```go +type ServiceConnectionInfo struct { + Hosts []ServiceHostEntry // Ordered host:port pairs + TargetSessionAttrs string // Passed through unchanged +} +``` + +**Ordering algorithm:** + +1. **Determine node list:** If `TargetNodes` is set, use only listed nodes in + the specified order. Otherwise, use all nodes with the local node (containing + the service's host) first, then remaining nodes in iteration order. +2. **Group by node:** For each node, list instances with the co-located instance + (same host as the service) first, then remaining instances. +3. **Pass through `TargetSessionAttrs`:** The builder does not interpret this + value — it comes from the caller. + +Hostname format: `postgres-{instanceID}` (matches swarm convention). Port: +always `5432` (internal container port via overlay network). + +### Service-specific layer: `resolveTargetSessionAttrs` + +Each service type maps its own config semantics to a `target_session_attrs` +value. This dispatch lives in `server/internal/workflows/plan_update.go`: + +```go +func resolveTargetSessionAttrs(serviceSpec *database.ServiceSpec) string { + // Tier 1: Explicit user setting in database_connection + if serviceSpec.DatabaseConnection != nil && serviceSpec.DatabaseConnection.TargetSessionAttrs != "" { + return serviceSpec.DatabaseConnection.TargetSessionAttrs + } + // Tier 2: Per-service-type default + switch serviceSpec.ServiceType { + case "mcp": + if allowWrites, ok := serviceSpec.Config["allow_writes"].(bool); ok && allowWrites { + return "primary" + } + return "prefer-standby" + default: + return "prefer-standby" + } +} +``` + +This is the **only service-type-specific code** in the workflow path. If the +user explicitly sets `database_connection.target_session_attrs`, that value is +used directly (already validated at the API layer). Otherwise, the per-service- +type default applies. The fallback is `"prefer-standby"` for safety — a new +service type defaults to read-only behavior. `prefer-standby` falls back to +the primary when no standbys exist, so it works in all topologies. + +### Services without multi-host support + +`BuildServiceHostList` always produces the full ordered host list regardless of +whether the service supports multi-host connections. Each service type's config +generator decides how to consume the list: + +- **Multi-host services** (e.g., MCP) use the full `hosts` array and + `target_session_attrs` for automatic failover at the connection layer. +- **Single-host services** take `hosts[0]` — the ordering algorithm ensures + this is the optimal choice (co-located instance, local node). This is + equivalent to the pre-PLAT-463 `findPostgresInstance` behavior. + +Single-host services lose automatic connection-layer failover. After a Patroni +failover within a node, the service points at the old primary (now a replica) +until the Control Plane intervenes. This is acceptable for standard multi-active +deployments (1 host per node) where every host runs a writable primary and +Swarm self-heals. For HA topologies, Phase 3 (proactive config regeneration on +failover) narrows the window by detecting role changes and regenerating the +config with the new primary as `hosts[0]`. + +No changes to the shared infrastructure are needed — this is purely a config +generator concern. When adding a new service type, document whether it supports +multi-host connections in the service type's config generator. + +### Config generation (per service type) + +Each service type has its own config generator that maps the generic +`[]ServiceHostEntry` into whatever format the service expects. For MCP, this +is `GenerateMCPConfig()` in +`server/internal/orchestrator/swarm/mcp_config.go`, which produces a YAML +config with a structured `hosts` array: + +```yaml +databases: + - name: mydb + hosts: + - host: postgres-abc123 + port: 5432 + - host: postgres-def456 + port: 5432 + target_session_attrs: prefer-standby + # ... other fields +``` + +A future service type would implement its own config generator, converting +`[]ServiceHostEntry` into the format that service expects (e.g., a +comma-separated DSN, a JSON config, environment variables, etc.). + +### What a new service type inherits automatically + +By using `BuildServiceHostList`, any new service type automatically gets: + +- **Co-location preference:** Instances on the same host as the service are + tried first for lowest latency +- **Local-node preference:** Instances on the same database node are tried + before remote nodes +- **`database_connection.target_nodes` filtering:** API users can override the default + ordering with an explicit node list in the service spec +- **Multi-host failover:** The full host list enables the service's connection + library to try alternate instances if the preferred one is unavailable + +### What a new service type must implement + +| Touch point | What to do | +|-------------|------------| +| `resolveTargetSessionAttrs` in `plan_update.go` | Add a `case` for the per-service-type default `target_session_attrs` (used when `database_connection.target_session_attrs` is not set) | +| Config generator | Create a config generator (analogous to `GenerateMCPConfig`) that maps `[]ServiceHostEntry` + `TargetSessionAttrs` into your service's config format | +| `GenerateServiceInstanceResources` in `orchestrator.go` | Wire the config generator into the resource generation pipeline (analogous to `MCPConfigResource`) | +| Validation in `validate.go` (if needed) | Cross-validate `database_connection.target_session_attrs` against service-specific config (e.g., MCP rejects `allow_writes: true` with standby-targeting attrs) | + +### Optional: `database_connection` + +The `ServiceSpec` includes an optional `database_connection` struct: + +```go +type DatabaseConnection struct { + TargetNodes []string `json:"target_nodes,omitempty"` + TargetSessionAttrs string `json:"target_session_attrs,omitempty"` +} +``` + +- **`target_nodes`**: When set, `BuildServiceHostList` uses only the listed nodes in + the specified order, ignoring co-location-based ordering. Useful when the API + user wants explicit control over which database nodes a service connects to + (e.g., pinning a read-heavy service to a specific replica node). +- **`target_session_attrs`**: When set, overrides the per-service-type default + (e.g., MCP's `allow_writes` → `primary`/`prefer-standby` mapping). Valid + values: `primary`, `prefer-standby`, `standby`, `read-write`, `any`. + +Validation: format, uniqueness, node-name existence (against the database +spec's node names), and `target_session_attrs` enum are all checked at the API +layer. MCP cross-validates `allow_writes` vs `target_session_attrs` to reject +unsafe combinations (e.g., `allow_writes: true` with +`target_session_attrs: prefer-standby`). + ## Domain Model ### ServiceSpec @@ -412,20 +643,23 @@ delete events in reverse dependency order. ```go type ServiceSpec struct { - ServiceID string `json:"service_id"` - ServiceType string `json:"service_type"` - Version string `json:"version"` - HostIDs []string `json:"host_ids"` - Config map[string]any `json:"config"` - Port *int `json:"port,omitempty"` - CPUs *float64 `json:"cpus,omitempty"` - MemoryBytes *uint64 `json:"memory,omitempty"` + ServiceID string `json:"service_id"` + ServiceType string `json:"service_type"` + Version string `json:"version"` + HostIDs []string `json:"host_ids"` + Config map[string]any `json:"config"` + DatabaseConnection *DatabaseConnection `json:"database_connection,omitempty"` + Port *int `json:"port,omitempty"` + CPUs *float64 `json:"cpus,omitempty"` + MemoryBytes *uint64 `json:"memory,omitempty"` + OrchestratorOpts *OrchestratorOpts `json:"orchestrator_opts,omitempty"` } ``` This is the spec-level declaration that lives inside `Spec.Services`. It's service-type-agnostic — no fields are MCP-specific. The `Config` map holds all -service-specific settings. +service-specific settings. The optional `DatabaseConnection` controls connection +topology (see [Optional: `database_connection`](#optional-database_connection)). ### ServiceInstance @@ -442,7 +676,7 @@ password). | Function | Format | Example | |----------|--------|---------| | `GenerateServiceInstanceID(dbID, svcID, hostID)` | `"{dbID}-{svcID}-{hostID}"` | `"mydb-mcp-host1"` | -| `GenerateServiceUsername(svcID, hostID)` | `"svc_{svcID}_{hostID}"` | `"svc_mcp_host1"` | +| `GenerateServiceUsername(svcID)` | `"svc_{svcID}"` | `"svc_mcp_server"` | | `GenerateDatabaseNetworkID(dbID)` | `"{dbID}"` | `"mydb"` | `GenerateDatabaseNetworkID` returns the **resource identifier** used to look up @@ -451,7 +685,9 @@ name is `"{databaseID}-database"` (set in the `Network.Name` field in `orchestrator.go`). Usernames longer than 63 characters are truncated with a deterministic hash -suffix. See `docs/development/service-credentials.md` for details. +suffix. Because the username is now per-service (not per-instance), all +instances of the same service share one set of credentials. See +`docs/development/service-credentials.md` for details. ### ServiceResources @@ -488,7 +724,7 @@ pattern uses per-case check functions: checks: []checkFunc{ checkLabels(expectedLabels), checkNetworks("bridge", "my-db-database"), - checkEnv("PGHOST=...", "PGPORT=5432", ...), + checkContainerSpec(image, mounts, command, args), checkPlacement("node.id==swarm-node-1"), checkHealthcheck("/health", 8080), checkPorts(8080, 5434), @@ -625,6 +861,10 @@ Use these examples to verify your integration or to hand-test with `curl`. "llm_provider": "anthropic", "llm_model": "claude-sonnet-4-5", "anthropic_api_key": "sk-ant-..." + }, + "database_connection": { + "target_nodes": ["n1"], + "target_session_attrs": "primary" } } ] @@ -843,10 +1083,12 @@ Example of a failed service instance in the API response: | 2. Regenerate | — | `make -C api generate` | | 3. Validation | `server/internal/api/apiv1/validate.go` | Add type to allowlist in `validateServiceSpec()`; add `validateMyServiceConfig()` function | | 4. Image registry | `server/internal/orchestrator/swarm/service_images.go` | Call `versions.addServiceImage()` in `NewServiceVersions()` | -| 5. Container spec | `server/internal/orchestrator/swarm/service_spec.go` | Service-specific configuration delivery, health check, mounts, entrypoint | -| 6. Unit tests | `swarm/service_spec_test.go`, `swarm/service_images_test.go` | Add cases for new type | -| 7. Golden plan tests | `operations/update_database_test.go` | Already covered generically; regenerate with `-update` if resource shape changes | -| 8. E2E tests | `e2e/service_provisioning_test.go` | Add provision, lifecycle, stability, and failure/recovery tests | +| 5. Connection topology | `server/internal/workflows/plan_update.go` | Add `case` to `resolveTargetSessionAttrs()` for your service type | +| 6. Config generator | `server/internal/orchestrator/swarm/` | Create config generator mapping `[]ServiceHostEntry` to your service's config format; document whether the service supports multi-host connections (see [Services without multi-host support](#services-without-multi-host-support)) | +| 7. Container spec | `server/internal/orchestrator/swarm/service_spec.go` | Service-specific configuration delivery, health check, mounts, entrypoint | +| 8. Unit tests | `swarm/service_spec_test.go`, `swarm/service_images_test.go` | Add cases for new type | +| 9. Golden plan tests | `operations/update_database_test.go` | Already covered generically; regenerate with `-update` if resource shape changes | +| 10. E2E tests | `e2e/service_provisioning_test.go` | Add provision, lifecycle, stability, and failure/recovery tests | ## What Doesn't Change @@ -854,11 +1096,13 @@ The following are service-type-agnostic and require no modification: - `ServiceSpec` struct — `server/internal/database/spec.go` - `ServiceInstance` domain model — `server/internal/database/service_instance.go` -- Workflow code — `server/internal/workflows/plan_update.go` -- Resource types — `server/internal/orchestrator/swarm/` (all five resource - types are generic) -- `GenerateServiceInstanceResources()` — - `server/internal/orchestrator/swarm/orchestrator.go` +- Workflow code — `server/internal/workflows/plan_update.go` (except adding a + `case` to `resolveTargetSessionAttrs` for your service type's + `target_session_attrs` mapping) +- Generic resource types — `Network`, `ServiceUserRole`, `DirResource`, + `ServiceInstanceSpec`, `ServiceInstance`, `ServiceInstanceMonitor` are + service-type-agnostic (you add a service-type-specific config resource + alongside them) - Operations layer — `server/internal/database/operations/` (`UpdateDatabase`, `EndState`) - Store/etcd layer @@ -870,19 +1114,20 @@ The following are service-type-agnostic and require no modification: write access (`INSERT`, `UPDATE`, `DELETE`, DDL). This will require a mechanism for the service spec to declare the required access level and for `ServiceUserRole` to provision the appropriate role accordingly. -- **Primary-aware database connection routing**: Services currently connect to a - Postgres instance resolved at provisioning time by `findPostgresInstance()`, - which prefers a co-located instance but does not distinguish between primary - and replica. Services that require read/write access will need their database - connection routed to the current primary, and that routing will need to - survive failovers and switchovers. -- **Persistent bind mounts**: Service containers currently have no persistent - storage (`Mounts: []mount.Mount{}`). Some services need configuration or - application state that survives container restarts (e.g., token stores, local - databases, generated config files). This will require adding bind mount - support to `ServiceContainerSpec()`, along with corresponding filesystem - directory resources to manage the host-side paths — following the same pattern - used by Patroni and pgBackRest config resources. +- **Primary-aware database connection routing** *(in progress — PLAT-463)*: + `BuildServiceHostList` and `resolveTargetSessionAttrs` provide multi-host + connection topology with `target_session_attrs` support. Services receive an + ordered host list and the connection library selects the appropriate instance + (primary or standby) at connect time. See [Database Connection + Topology](#database-connection-topology) for details. Future phases will add + proactive config regeneration on failover/switchover events. +- **Persistent bind mounts** *(implemented)*: Service containers now use a + `DirResource` to create a host-side data directory, which is bind-mounted + into the container at `/app/data`. Config files are written to this directory + by service-type-specific config resources (e.g., `MCPConfigResource`). + Application-owned files (like token and user stores) are preserved across + config updates. See [Configuration delivery](#configuration-delivery) for + details. ## Appendix: MCP Reference Implementation for AI-Assisted Development @@ -1128,29 +1373,26 @@ func serviceImageTag(cfg config.Config, imageRef string) string { **`ServiceContainerSpecOptions`** — the input struct: ```go -// lines 15–32 type ServiceContainerSpecOptions struct { - ServiceSpec *database.ServiceSpec - ServiceInstanceID string - DatabaseID string - DatabaseName string - HostID string - ServiceName string - Hostname string - CohortMemberID string - ServiceImage *ServiceImage - Credentials *database.ServiceUser - DatabaseNetworkID string - DatabaseHost string - DatabasePort int - Port *int + ServiceSpec *database.ServiceSpec + ServiceInstanceID string + DatabaseID string + DatabaseName string + HostID string + ServiceName string + Hostname string + CohortMemberID string + ServiceImage *ServiceImage + Credentials *database.ServiceUser + DatabaseNetworkID string + Port *int + DataPath string // Host-side directory for bind mount } ``` **`ServiceContainerSpec()`** — builds the Docker Swarm `ServiceSpec`: ```go -// lines 35–117 func ServiceContainerSpec(opts *ServiceContainerSpecOptions) (swarm.ServiceSpec, error) { labels := map[string]string{ "pgedge.component": "service", @@ -1160,35 +1402,37 @@ func ServiceContainerSpec(opts *ServiceContainerSpecOptions) (swarm.ServiceSpec, "pgedge.host.id": opts.HostID, } + // Merge user-provided extra labels + if opts.ServiceSpec.OrchestratorOpts != nil && opts.ServiceSpec.OrchestratorOpts.Swarm != nil { + for k, v := range opts.ServiceSpec.OrchestratorOpts.Swarm.ExtraLabels { + labels[k] = v + } + } + networks := []swarm.NetworkAttachmentConfig{ {Target: "bridge"}, {Target: opts.DatabaseNetworkID}, } - env := buildServiceEnvVars(opts) image := opts.ServiceImage.Tag ports := buildServicePortConfig(opts.Port) - var resources *swarm.ResourceRequirements - if opts.ServiceSpec.CPUs != nil || opts.ServiceSpec.MemoryBytes != nil { - resources = &swarm.ResourceRequirements{ - Limits: &swarm.Limit{}, - } - if opts.ServiceSpec.CPUs != nil { - resources.Limits.NanoCPUs = int64(*opts.ServiceSpec.CPUs * 1e9) - } - if opts.ServiceSpec.MemoryBytes != nil { - resources.Limits.MemoryBytes = int64(*opts.ServiceSpec.MemoryBytes) - } + // Bind mount for config/auth files + mounts := []mount.Mount{ + docker.BuildMount(opts.DataPath, "/app/data", false), } + // ... resource limits omitted for brevity ... + return swarm.ServiceSpec{ TaskTemplate: swarm.TaskSpec{ ContainerSpec: &swarm.ContainerSpec{ Image: image, Labels: labels, Hostname: opts.Hostname, - Env: env, + User: fmt.Sprintf("%d", mcpContainerUID), + Command: []string{"/app/pgedge-postgres-mcp"}, + Args: []string{"-config", "/app/data/config.yaml"}, Healthcheck: &container.HealthConfig{ Test: []string{"CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"}, StartPeriod: time.Second * 30, @@ -1196,7 +1440,7 @@ func ServiceContainerSpec(opts *ServiceContainerSpecOptions) (swarm.ServiceSpec, Timeout: time.Second * 5, Retries: 3, }, - Mounts: []mount.Mount{}, + Mounts: mounts, }, Networks: networks, Placement: &swarm.Placement{ @@ -1218,56 +1462,32 @@ func ServiceContainerSpec(opts *ServiceContainerSpecOptions) (swarm.ServiceSpec, } ``` -**`buildServiceEnvVars()`** — MCP-specific env var injection (this is expected to -change; see the caution at the top of this document): +**Configuration delivery** — Config is delivered via bind-mounted YAML files, +not environment variables. The container spec sets up the bind mount and +overrides the entrypoint to pass the config path: ```go -// lines 120–168 -func buildServiceEnvVars(opts *ServiceContainerSpecOptions) []string { - env := []string{ - fmt.Sprintf("PGHOST=%s", opts.DatabaseHost), - fmt.Sprintf("PGPORT=%d", opts.DatabasePort), - fmt.Sprintf("PGDATABASE=%s", opts.DatabaseName), - "PGSSLMODE=prefer", - fmt.Sprintf("PGEDGE_SERVICE_ID=%s", opts.ServiceSpec.ServiceID), - fmt.Sprintf("PGEDGE_DATABASE_ID=%s", opts.DatabaseID), - } +// Bind mount for config/auth files +mounts := []mount.Mount{ + docker.BuildMount(opts.DataPath, "/app/data", false), +} - if opts.Credentials != nil { - env = append(env, - fmt.Sprintf("PGUSER=%s", opts.Credentials.Username), - fmt.Sprintf("PGPASSWORD=%s", opts.Credentials.Password), - ) - } +// Container entrypoint passes config file path +Command: []string{"/app/pgedge-postgres-mcp"}, +Args: []string{"-config", "/app/data/config.yaml"}, +``` - // MCP-specific config → env var mapping - if provider, ok := opts.ServiceSpec.Config["llm_provider"].(string); ok { - env = append(env, fmt.Sprintf("PGEDGE_LLM_PROVIDER=%s", provider)) - } - if model, ok := opts.ServiceSpec.Config["llm_model"].(string); ok { - env = append(env, fmt.Sprintf("PGEDGE_LLM_MODEL=%s", model)) - } +The config files themselves are generated by `MCPConfigResource` (see +`server/internal/orchestrator/swarm/mcp_config_resource.go`), which writes +three files to the data directory: - if provider, ok := opts.ServiceSpec.Config["llm_provider"].(string); ok { - switch provider { - case "anthropic": - if key, ok := opts.ServiceSpec.Config["anthropic_api_key"].(string); ok { - env = append(env, fmt.Sprintf("PGEDGE_ANTHROPIC_API_KEY=%s", key)) - } - case "openai": - if key, ok := opts.ServiceSpec.Config["openai_api_key"].(string); ok { - env = append(env, fmt.Sprintf("PGEDGE_OPENAI_API_KEY=%s", key)) - } - case "ollama": - if url, ok := opts.ServiceSpec.Config["ollama_url"].(string); ok { - env = append(env, fmt.Sprintf("PGEDGE_OLLAMA_URL=%s", url)) - } - } - } +- `config.yaml` — CP-owned, regenerated on every Create/Update +- `tokens.yaml` — application-owned, written only on first Create +- `users.yaml` — application-owned, written only on first Create - return env -} -``` +For a new service type, create an analogous config resource that writes your +service's config files to the data directory. The `DirResource` and bind mount +are generic and reusable. **`buildServicePortConfig()`** — port publication: @@ -1431,23 +1651,39 @@ follow these steps in order: `versions.addServiceImage("my-service", ...)` call in `NewServiceVersions()` (see [A.3](#a3-image-registry-serverinternalorchestratorswarmservice_imagesgo)). -4. **`server/internal/orchestrator/swarm/service_spec.go`**: If your service - needs different environment variables, a different health check endpoint, - different port, or bind mounts, modify `ServiceContainerSpec()` and/or its - helpers to branch on `ServiceType` (see +4. **Config resource**: Create a config resource (analogous to + `MCPConfigResource` in `mcp_config_resource.go`) that generates your + service's config files and writes them to the data directory. The + `DirResource` creates the host-side directory; your config resource writes + files into it. + +5. **`server/internal/workflows/plan_update.go`**: Add a `case` to + `resolveTargetSessionAttrs()` mapping your service's config to the + appropriate `target_session_attrs` value (see [Database Connection + Topology](#database-connection-topology)). + +6. **`server/internal/orchestrator/swarm/orchestrator.go`**: Wire your config + resource into the resource chain in `GenerateServiceInstanceResources()`, + in the same position as `MCPConfigResource` (between `DirResource` and + `ServiceInstanceSpec`). + +7. **`server/internal/orchestrator/swarm/service_spec.go`**: If your service + needs a different health check endpoint, different port, entrypoint, or + additional mounts, modify `ServiceContainerSpec()` to branch on + `ServiceType` (see [A.4](#a4-container-spec-serverinternalorchestratorswarmservice_specgo)). -5. **`server/internal/orchestrator/swarm/service_spec_test.go`**: Add table-driven - test cases for the new service type's container spec, env vars, and port config. +8. **`server/internal/orchestrator/swarm/service_spec_test.go`**: Add + table-driven test cases for the new service type's container spec, bind + mounts, and port config. -6. **`server/internal/orchestrator/swarm/service_images_test.go`**: Add test +9. **`server/internal/orchestrator/swarm/service_images_test.go`**: Add test cases for `GetServiceImage()` with the new type and version(s). -7. **`e2e/service_provisioning_test.go`**: Add E2E tests following the patterns - in [A.6](#a6-e2e-test-pattern-e2eservice_provisioning_testgo): single-host - provision, multi-host, add/remove from existing database, stability (unrelated - update doesn't recreate), bad version failure + recovery. +10. **`e2e/service_provisioning_test.go`**: Add E2E tests following the patterns + in [A.6](#a6-e2e-test-pattern-e2eservice_provisioning_testgo): single-host + provision, multi-host, add/remove from existing database, stability + (unrelated update doesn't recreate), bad version failure + recovery. **Files that do NOT need changes**: `spec.go`, `service_instance.go`, -`plan_update.go`, `orchestrator.go`, `resources.go`, `end.go`, any store/etcd -code. +`resources.go`, `end.go`, any store/etcd code. diff --git a/server/internal/api/apiv1/convert.go b/server/internal/api/apiv1/convert.go index 6f73dc5c..f251b77d 100644 --- a/server/internal/api/apiv1/convert.go +++ b/server/internal/api/apiv1/convert.go @@ -224,15 +224,16 @@ func serviceSpecToAPI(svc *database.ServiceSpec) *api.ServiceSpec { } return &api.ServiceSpec{ - ServiceID: api.Identifier(svc.ServiceID), - ServiceType: svc.ServiceType, - Version: svc.Version, - HostIds: hostIDs, - Port: svc.Port, - Config: filteredConfig, - Cpus: utils.NillablePointerTo(humanizeCPUs(utils.FromPointer(svc.CPUs))), - Memory: utils.NillablePointerTo(humanizeBytes(utils.FromPointer(svc.MemoryBytes))), - OrchestratorOpts: orchestratorOptsToAPI(svc.OrchestratorOpts), + ServiceID: api.Identifier(svc.ServiceID), + ServiceType: svc.ServiceType, + Version: svc.Version, + HostIds: hostIDs, + Port: svc.Port, + Config: filteredConfig, + Cpus: utils.NillablePointerTo(humanizeCPUs(utils.FromPointer(svc.CPUs))), + Memory: utils.NillablePointerTo(humanizeBytes(utils.FromPointer(svc.MemoryBytes))), + OrchestratorOpts: orchestratorOptsToAPI(svc.OrchestratorOpts), + DatabaseConnection: databaseConnectionToAPI(svc.DatabaseConnection), } } @@ -650,15 +651,16 @@ func apiToServiceSpec(apiSvc *api.ServiceSpec) (*database.ServiceSpec, error) { } return &database.ServiceSpec{ - ServiceID: string(apiSvc.ServiceID), - ServiceType: apiSvc.ServiceType, - Version: apiSvc.Version, - HostIDs: hostIDs, - Port: apiSvc.Port, - Config: apiSvc.Config, - CPUs: cpus, - MemoryBytes: memory, - OrchestratorOpts: orchestratorOptsToDatabase(apiSvc.OrchestratorOpts), + ServiceID: string(apiSvc.ServiceID), + ServiceType: apiSvc.ServiceType, + Version: apiSvc.Version, + HostIDs: hostIDs, + Port: apiSvc.Port, + Config: apiSvc.Config, + CPUs: cpus, + MemoryBytes: memory, + OrchestratorOpts: orchestratorOptsToDatabase(apiSvc.OrchestratorOpts), + DatabaseConnection: apiToDatabaseConnection(apiSvc.DatabaseConnection), }, nil } @@ -968,6 +970,26 @@ func identToString(id api.Identifier, path []string) (string, error) { return out, nil } +func apiToDatabaseConnection(conn *api.DatabaseConnection) *database.DatabaseConnection { + if conn == nil { + return nil + } + return &database.DatabaseConnection{ + TargetNodes: conn.TargetNodes, + TargetSessionAttrs: utils.FromPointer(conn.TargetSessionAttrs), + } +} + +func databaseConnectionToAPI(conn *database.DatabaseConnection) *api.DatabaseConnection { + if conn == nil { + return nil + } + return &api.DatabaseConnection{ + TargetNodes: conn.TargetNodes, + TargetSessionAttrs: utils.NillablePointerTo(conn.TargetSessionAttrs), + } +} + func orchestratorOptsToDatabase(opts *api.OrchestratorOpts) *database.OrchestratorOpts { if opts == nil { return nil diff --git a/server/internal/api/apiv1/validate.go b/server/internal/api/apiv1/validate.go index 220f62ca..554ed811 100644 --- a/server/internal/api/apiv1/validate.go +++ b/server/internal/api/apiv1/validate.go @@ -140,7 +140,7 @@ func validateDatabaseSpec(spec *api.DatabaseSpec) error { } seenServiceIDs.Add(string(svc.ServiceID)) - errs = append(errs, validateServiceSpec(svc, svcPath, false)...) + errs = append(errs, validateServiceSpec(svc, svcPath, false, seenNodeNames)...) } return errors.Join(errs...) @@ -177,10 +177,16 @@ func validateDatabaseUpdate(old *database.Spec, new *api.DatabaseSpec) error { } } + // Build the full set of node names from the new spec for cross-validation. + newNodeNames := make(ds.Set[string], len(new.Nodes)) + for _, n := range new.Nodes { + newNodeNames.Add(n.Name) + } + // Validate services with isUpdate=true to reject bootstrap-only fields for i, svc := range new.Services { svcPath := []string{"services", arrayIndexPath(i)} - errs = append(errs, validateServiceSpec(svc, svcPath, true)...) + errs = append(errs, validateServiceSpec(svc, svcPath, true, newNodeNames)...) } return errors.Join(errs...) @@ -242,7 +248,7 @@ func validateNode(node *api.DatabaseNodeSpec, path []string) []error { return errs } -func validateServiceSpec(svc *api.ServiceSpec, path []string, isUpdate bool) []error { +func validateServiceSpec(svc *api.ServiceSpec, path []string, isUpdate bool, nodeNames ...ds.Set[string]) []error { var errs []error // Validate service_id @@ -287,6 +293,28 @@ func validateServiceSpec(svc *api.ServiceSpec, path []string, isUpdate bool) []e errs = append(errs, validatePostgRESTServiceConfig(svc.Config, appendPath(path, "config"))...) } + // Validate database_connection if provided + if svc.DatabaseConnection != nil { + dcPath := appendPath(path, "database_connection") + var nn ds.Set[string] + if len(nodeNames) > 0 { + nn = nodeNames[0] + } + errs = append(errs, validateDatabaseConnection(svc.DatabaseConnection, dcPath, nn)...) + } + + // MCP-specific cross-validation: allow_writes vs target_session_attrs + if svc.ServiceType == "mcp" && svc.DatabaseConnection != nil && svc.DatabaseConnection.TargetSessionAttrs != nil { + if allowWrites, ok := svc.Config["allow_writes"].(bool); ok && allowWrites { + tsa := *svc.DatabaseConnection.TargetSessionAttrs + writeSafe := map[string]bool{database.TargetSessionAttrsPrimary: true, database.TargetSessionAttrsReadWrite: true} + if tsa != "" && !writeSafe[tsa] { + err := fmt.Errorf("allow_writes requires target_session_attrs 'primary' or 'read-write', got '%s'", tsa) + errs = append(errs, newValidationError(err, appendPath(path, "database_connection", "target_session_attrs"))) + } + } + } + // Validate cpus if provided if svc.Cpus != nil { errs = append(errs, validateCPUs(svc.Cpus, appendPath(path, "cpus"))...) @@ -321,6 +349,44 @@ func validatePostgRESTServiceConfig(config map[string]any, path []string) []erro return result } +func validateDatabaseConnection(dc *api.DatabaseConnection, path []string, nodeNames ds.Set[string]) []error { + var errs []error + + // Validate target_nodes: no duplicates, no empty strings, must exist in spec + if dc.TargetNodes != nil { + seen := make(ds.Set[string], len(dc.TargetNodes)) + for i, node := range dc.TargetNodes { + nodePath := appendPath(path, "target_nodes", arrayIndexPath(i)) + if node == "" { + errs = append(errs, newValidationError(errors.New("node name must not be empty"), nodePath)) + } else if nodeNames != nil && !nodeNames.Has(node) { + errs = append(errs, newValidationError(fmt.Errorf("node %q does not exist in the database spec", node), nodePath)) + } + if seen.Has(node) { + errs = append(errs, newValidationError(fmt.Errorf("duplicate node name %q", node), nodePath)) + } + seen.Add(node) + } + } + + // Validate target_session_attrs enum (belt-and-suspenders — Goa also validates this) + if dc.TargetSessionAttrs != nil && *dc.TargetSessionAttrs != "" { + valid := map[string]bool{ + database.TargetSessionAttrsPrimary: true, + database.TargetSessionAttrsPreferStandby: true, + database.TargetSessionAttrsStandby: true, + database.TargetSessionAttrsReadWrite: true, + database.TargetSessionAttrsAny: true, + } + if !valid[*dc.TargetSessionAttrs] { + err := fmt.Errorf("invalid target_session_attrs %q (must be primary, prefer-standby, standby, read-write, or any)", *dc.TargetSessionAttrs) + errs = append(errs, newValidationError(err, appendPath(path, "target_session_attrs"))) + } + } + + return errs +} + func validateCPUs(value *string, path []string) []error { var errs []error diff --git a/server/internal/api/apiv1/validate_test.go b/server/internal/api/apiv1/validate_test.go index c3b0d5ce..6b6c9b6a 100644 --- a/server/internal/api/apiv1/validate_test.go +++ b/server/internal/api/apiv1/validate_test.go @@ -5,6 +5,7 @@ import ( "testing" api "github.com/pgEdge/control-plane/api/apiv1/gen/control_plane" + "github.com/pgEdge/control-plane/server/internal/ds" "github.com/pgEdge/control-plane/server/internal/utils" "github.com/stretchr/testify/assert" ) @@ -1195,6 +1196,150 @@ func TestValidateServiceSpec(t *testing.T) { } } +func TestValidateDatabaseConnection(t *testing.T) { + nodeNames := ds.Set[string]{"n1": true, "n2": true, "n3": true} + + for _, tc := range []struct { + name string + dc *api.DatabaseConnection + nodeNames ds.Set[string] + expected []string + }{ + { + name: "valid target_nodes subset", + dc: &api.DatabaseConnection{TargetNodes: []string{"n1", "n2"}}, + nodeNames: nodeNames, + }, + { + name: "valid target_session_attrs", + dc: &api.DatabaseConnection{TargetSessionAttrs: utils.PointerTo("primary")}, + nodeNames: nodeNames, + }, + { + name: "valid both fields", + dc: &api.DatabaseConnection{TargetNodes: []string{"n1"}, TargetSessionAttrs: utils.PointerTo("prefer-standby")}, + nodeNames: nodeNames, + }, + { + name: "empty target_nodes is valid", + dc: &api.DatabaseConnection{}, + nodeNames: nodeNames, + }, + { + name: "nil node names skips existence check", + dc: &api.DatabaseConnection{TargetNodes: []string{"unknown"}}, + nodeNames: nil, + }, + { + name: "duplicate target_nodes", + dc: &api.DatabaseConnection{TargetNodes: []string{"n1", "n2", "n1"}}, + nodeNames: nodeNames, + expected: []string{`target_nodes[2]: duplicate node name "n1"`}, + }, + { + name: "empty node name", + dc: &api.DatabaseConnection{TargetNodes: []string{"n1", ""}}, + nodeNames: nodeNames, + expected: []string{"target_nodes[1]: node name must not be empty"}, + }, + { + name: "nonexistent target_node", + dc: &api.DatabaseConnection{TargetNodes: []string{"n1", "n99"}}, + nodeNames: nodeNames, + expected: []string{`target_nodes[1]: node "n99" does not exist in the database spec`}, + }, + { + name: "multiple nonexistent target_nodes", + dc: &api.DatabaseConnection{TargetNodes: []string{"n1", "n99", "n100"}}, + nodeNames: nodeNames, + expected: []string{ + `target_nodes[1]: node "n99" does not exist in the database spec`, + `target_nodes[2]: node "n100" does not exist in the database spec`, + }, + }, + { + name: "invalid target_session_attrs", + dc: &api.DatabaseConnection{TargetSessionAttrs: utils.PointerTo("invalid")}, + nodeNames: nodeNames, + expected: []string{`target_session_attrs: invalid target_session_attrs "invalid"`}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + err := errors.Join(validateDatabaseConnection(tc.dc, nil, tc.nodeNames)...) + if len(tc.expected) < 1 { + assert.NoError(t, err) + } else { + for _, expected := range tc.expected { + assert.ErrorContains(t, err, expected) + } + } + }) + } +} + +func TestValidateServiceSpec_DatabaseConnectionCrossValidation(t *testing.T) { + nodeNames := ds.Set[string]{"n1": true, "n2": true} + + t.Run("allow_writes with unsafe target_session_attrs", func(t *testing.T) { + svc := &api.ServiceSpec{ + ServiceID: "mcp-server", + ServiceType: "mcp", + Version: "1.0.0", + HostIds: []api.Identifier{"host-1"}, + Config: map[string]any{ + "llm_provider": "anthropic", + "llm_model": "claude-sonnet-4-5", + "anthropic_api_key": "sk-ant-...", + "allow_writes": true, + }, + DatabaseConnection: &api.DatabaseConnection{ + TargetSessionAttrs: utils.PointerTo("prefer-standby"), + }, + } + err := errors.Join(validateServiceSpec(svc, nil, false, nodeNames)...) + assert.ErrorContains(t, err, "allow_writes requires target_session_attrs 'primary' or 'read-write'") + }) + + t.Run("allow_writes with safe target_session_attrs", func(t *testing.T) { + svc := &api.ServiceSpec{ + ServiceID: "mcp-server", + ServiceType: "mcp", + Version: "1.0.0", + HostIds: []api.Identifier{"host-1"}, + Config: map[string]any{ + "llm_provider": "anthropic", + "llm_model": "claude-sonnet-4-5", + "anthropic_api_key": "sk-ant-...", + "allow_writes": true, + }, + DatabaseConnection: &api.DatabaseConnection{ + TargetSessionAttrs: utils.PointerTo("primary"), + }, + } + err := errors.Join(validateServiceSpec(svc, nil, false, nodeNames)...) + assert.NoError(t, err) + }) + + t.Run("nonexistent target_node via service spec", func(t *testing.T) { + svc := &api.ServiceSpec{ + ServiceID: "mcp-server", + ServiceType: "mcp", + Version: "1.0.0", + HostIds: []api.Identifier{"host-1"}, + Config: map[string]any{ + "llm_provider": "anthropic", + "llm_model": "claude-sonnet-4-5", + "anthropic_api_key": "sk-ant-...", + }, + DatabaseConnection: &api.DatabaseConnection{ + TargetNodes: []string{"n1", "nonexistent"}, + }, + } + err := errors.Join(validateServiceSpec(svc, nil, false, nodeNames)...) + assert.ErrorContains(t, err, `node "nonexistent" does not exist in the database spec`) + }) +} + func TestValidateOrchestratorOpts(t *testing.T) { for _, tc := range []struct { name string diff --git a/server/internal/database/service_connection.go b/server/internal/database/service_connection.go new file mode 100644 index 00000000..0cfddcb2 --- /dev/null +++ b/server/internal/database/service_connection.go @@ -0,0 +1,145 @@ +package database + +import ( + "errors" + "fmt" +) + +// internalPostgresPort is the Postgres port inside the container, used for +// service-to-database connections over the overlay network. This is always +// 5432 regardless of the published host port. +const internalPostgresPort = 5432 + +// libpq target_session_attrs values (PG 14+). +// See: https://www.postgresql.org/docs/current/libpq-connect.html +const ( + TargetSessionAttrsPrimary = "primary" + TargetSessionAttrsPreferStandby = "prefer-standby" + TargetSessionAttrsStandby = "standby" + TargetSessionAttrsReadWrite = "read-write" + TargetSessionAttrsAny = "any" +) + +// ServiceHostEntry represents a single host:port pair in an ordered host list. +type ServiceHostEntry struct { + Host string + Port int +} + +// ServiceConnectionInfo holds the ordered host list and connection parameters +// for a service instance's database connection. +type ServiceConnectionInfo struct { + Hosts []ServiceHostEntry + TargetSessionAttrs string +} + +// BuildServiceHostListParams holds the inputs for BuildServiceHostList. +type BuildServiceHostListParams struct { + ServiceHostID string // Host where the service instance runs + NodeInstances []*NodeInstances // All database instances, grouped by node + TargetNodes []string // Optional ordered node filter (from database_connection.target_nodes) + TargetSessionAttrs string // Caller-provided: "primary", "prefer-standby", etc. +} + +// BuildServiceHostList produces an ordered list of database host:port entries +// for a service instance's connection string. The ordering is determined by +// co-location with the service host and optional node filtering. +// +// Algorithm: +// 1. Determine node list and ordering: +// - If TargetNodes is set: use only listed nodes in that order, ignoring co-location +// - If TargetNodes is not set: all nodes, with the local node (containing a +// co-located instance) first, then remaining nodes in iteration order +// 2. Build host list, grouped by node: +// - For each node group: co-located instance first (same host as service), then remaining +// - Hostname format: "postgres-{instanceID}" (swarm overlay convention) +// - Port: always 5432 (internal container port via overlay network) +// 3. Pass through TargetSessionAttrs unchanged. +// +// Invariant: Only database instances from NodeInstances generate entries. +// ServiceHostID affects ordering only, never membership. A service on a +// dedicated host (no database instance on that host) does not add the service +// host to the list. +func BuildServiceHostList(params *BuildServiceHostListParams) (*ServiceConnectionInfo, error) { + nodesByName := make(map[string]*NodeInstances, len(params.NodeInstances)) + for _, ni := range params.NodeInstances { + nodesByName[ni.NodeName] = ni + } + + // Determine the ordered list of nodes to include. + var orderedNodes []*NodeInstances + if len(params.TargetNodes) > 0 { + // TargetNodes mode: use only listed nodes in the caller-specified order. + orderedNodes = make([]*NodeInstances, 0, len(params.TargetNodes)) + for _, name := range params.TargetNodes { + ni, ok := nodesByName[name] + if !ok { + return nil, fmt.Errorf("target node %q does not exist in the database spec", name) + } + orderedNodes = append(orderedNodes, ni) + } + } else { + // Default mode: all nodes, with the local node first. + orderedNodes = make([]*NodeInstances, 0, len(params.NodeInstances)) + localIdx := -1 + for i, ni := range params.NodeInstances { + if localIdx == -1 && containsHost(ni, params.ServiceHostID) { + localIdx = i + } + } + if localIdx >= 0 { + orderedNodes = append(orderedNodes, params.NodeInstances[localIdx]) + } + for i, ni := range params.NodeInstances { + if i != localIdx { + orderedNodes = append(orderedNodes, ni) + } + } + } + + // Build the host list from the ordered nodes. + var hosts []ServiceHostEntry + for _, ni := range orderedNodes { + hosts = append(hosts, buildNodeHosts(ni, params.ServiceHostID)...) + } + + if len(hosts) == 0 { + return nil, errors.New("no database instances found") + } + + return &ServiceConnectionInfo{ + Hosts: hosts, + TargetSessionAttrs: params.TargetSessionAttrs, + }, nil +} + +// containsHost returns true if any instance in the node runs on the given host. +func containsHost(ni *NodeInstances, hostID string) bool { + for _, inst := range ni.Instances { + if inst.HostID == hostID { + return true + } + } + return false +} + +// buildNodeHosts returns host entries for a single node, with the co-located +// instance (matching serviceHostID) first. +func buildNodeHosts(ni *NodeInstances, serviceHostID string) []ServiceHostEntry { + var colocated []ServiceHostEntry + var rest []ServiceHostEntry + + for _, inst := range ni.Instances { + entry := ServiceHostEntry{ + Host: fmt.Sprintf("postgres-%s", inst.InstanceID), + Port: internalPostgresPort, + } + if inst.HostID == serviceHostID { + colocated = append(colocated, entry) + } else { + rest = append(rest, entry) + } + } + + return append(colocated, rest...) +} diff --git a/server/internal/database/service_connection_test.go b/server/internal/database/service_connection_test.go new file mode 100644 index 00000000..63855650 --- /dev/null +++ b/server/internal/database/service_connection_test.go @@ -0,0 +1,253 @@ +package database + +import ( + "fmt" + "testing" +) + +func TestBuildServiceHostList(t *testing.T) { + // Helper to build a minimal InstanceSpec for testing. + inst := func(instanceID, hostID string) *InstanceSpec { + return &InstanceSpec{ + InstanceID: instanceID, + HostID: hostID, + } + } + + // Helper to build a ServiceHostEntry. + he := func(instanceID string) ServiceHostEntry { + return ServiceHostEntry{ + Host: fmt.Sprintf("postgres-%s", instanceID), + Port: 5432, + } + } + + t.Run("standard multi-active no replicas", func(t *testing.T) { + // 2 nodes, 1 host each. Service on host-1 -> local node first. + params := &BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: []*NodeInstances{ + {NodeName: "n1", Instances: []*InstanceSpec{inst("db1-n1-h1", "host-1")}}, + {NodeName: "n2", Instances: []*InstanceSpec{inst("db1-n2-h2", "host-2")}}, + }, + } + + result, err := BuildServiceHostList(params) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expected := []ServiceHostEntry{ + he("db1-n1-h1"), // local node first + he("db1-n2-h2"), // remote node second + } + assertHostsEqual(t, expected, result.Hosts) + }) + + t.Run("HA within node", func(t *testing.T) { + // Node with 2 hosts. Service on host-2 -> co-located instance first + // within the local node group. + params := &BuildServiceHostListParams{ + ServiceHostID: "host-2", + NodeInstances: []*NodeInstances{ + { + NodeName: "n1", + Instances: []*InstanceSpec{ + inst("db1-n1-h1", "host-1"), + inst("db1-n1-h2", "host-2"), + }, + }, + }, + } + + result, err := BuildServiceHostList(params) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expected := []ServiceHostEntry{ + he("db1-n1-h2"), // co-located first + he("db1-n1-h1"), // other instance in same node + } + assertHostsEqual(t, expected, result.Hosts) + }) + + t.Run("dedicated service host", func(t *testing.T) { + // Service on a host with no database instances -> all instances + // included, no local-first reordering. The service host must NOT + // appear in the host list. + params := &BuildServiceHostListParams{ + ServiceHostID: "service-host", + NodeInstances: []*NodeInstances{ + {NodeName: "n1", Instances: []*InstanceSpec{inst("db1-n1-h1", "host-1")}}, + {NodeName: "n2", Instances: []*InstanceSpec{inst("db1-n2-h2", "host-2")}}, + }, + } + + result, err := BuildServiceHostList(params) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // No local-first reordering (service host has no instances), so + // iteration order is preserved. + expected := []ServiceHostEntry{ + he("db1-n1-h1"), + he("db1-n2-h2"), + } + assertHostsEqual(t, expected, result.Hosts) + + // Assert the dedicated service host does NOT appear in any entry. + for _, h := range result.Hosts { + if h.Host == "postgres-service-host" { + t.Error("service host should not appear in the host list") + } + } + }) + + t.Run("3-node multi-active plus HA", func(t *testing.T) { + // 3 nodes: n1 has 2 hosts, n2 has 1 host, n3 has 1 host. + // Service on host-1b (second host of n1). + params := &BuildServiceHostListParams{ + ServiceHostID: "host-1b", + NodeInstances: []*NodeInstances{ + { + NodeName: "n1", + Instances: []*InstanceSpec{ + inst("db1-n1-h1a", "host-1a"), + inst("db1-n1-h1b", "host-1b"), + }, + }, + {NodeName: "n2", Instances: []*InstanceSpec{inst("db1-n2-h2", "host-2")}}, + {NodeName: "n3", Instances: []*InstanceSpec{inst("db1-n3-h3", "host-3")}}, + }, + } + + result, err := BuildServiceHostList(params) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // n1 first (local), with co-located instance first within n1. + // Then n2, n3 in iteration order. + expected := []ServiceHostEntry{ + he("db1-n1-h1b"), // co-located in local node + he("db1-n1-h1a"), // other instance in local node + he("db1-n2-h2"), // remote node + he("db1-n3-h3"), // remote node + } + assertHostsEqual(t, expected, result.Hosts) + }) + + t.Run("with TargetNodes", func(t *testing.T) { + // Filter to subset of nodes; user-specified order wins over co-location + // for node ordering. Within each node, co-location still applies. + params := &BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: []*NodeInstances{ + {NodeName: "n1", Instances: []*InstanceSpec{inst("db1-n1-h1", "host-1")}}, + {NodeName: "n2", Instances: []*InstanceSpec{inst("db1-n2-h2", "host-2")}}, + {NodeName: "n3", Instances: []*InstanceSpec{inst("db1-n3-h3", "host-3")}}, + }, + TargetNodes: []string{"n3", "n1"}, // user-specified order: n3 first, n1 second + } + + result, err := BuildServiceHostList(params) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // n3 first (user-specified), then n1. n2 excluded entirely. + expected := []ServiceHostEntry{ + he("db1-n3-h3"), + he("db1-n1-h1"), + } + assertHostsEqual(t, expected, result.Hosts) + }) + + t.Run("TargetNodes with non-existent node", func(t *testing.T) { + params := &BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: []*NodeInstances{ + {NodeName: "n1", Instances: []*InstanceSpec{inst("db1-n1-h1", "host-1")}}, + }, + TargetNodes: []string{"n1", "does-not-exist"}, + } + + _, err := BuildServiceHostList(params) + if err == nil { + t.Fatal("expected error for non-existent target node, got nil") + } + }) + + t.Run("target_session_attrs passthrough", func(t *testing.T) { + params := &BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: []*NodeInstances{ + {NodeName: "n1", Instances: []*InstanceSpec{inst("db1-n1-h1", "host-1")}}, + }, + TargetSessionAttrs: TargetSessionAttrsPreferStandby, + } + + result, err := BuildServiceHostList(params) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if result.TargetSessionAttrs != TargetSessionAttrsPreferStandby { + t.Errorf("TargetSessionAttrs = %q, want %q", result.TargetSessionAttrs, TargetSessionAttrsPreferStandby) + } + }) + + t.Run("empty node instances", func(t *testing.T) { + params := &BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: []*NodeInstances{}, + } + + _, err := BuildServiceHostList(params) + if err == nil { + t.Fatal("expected error for empty node instances, got nil") + } + }) + + t.Run("nil node instances", func(t *testing.T) { + params := &BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: nil, + } + + _, err := BuildServiceHostList(params) + if err == nil { + t.Fatal("expected error for nil node instances, got nil") + } + }) + + t.Run("nodes with empty instances slice", func(t *testing.T) { + // Nodes exist but have no instances in them. + params := &BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: []*NodeInstances{ + {NodeName: "n1", Instances: []*InstanceSpec{}}, + }, + } + + _, err := BuildServiceHostList(params) + if err == nil { + t.Fatal("expected error when all nodes have empty instances, got nil") + } + }) +} + +// assertHostsEqual compares two slices of ServiceHostEntry for equality. +func assertHostsEqual(t *testing.T, expected, actual []ServiceHostEntry) { + t.Helper() + if len(expected) != len(actual) { + t.Fatalf("host list length = %d, want %d\n got: %v\n want: %v", len(actual), len(expected), actual, expected) + } + for i := range expected { + if expected[i] != actual[i] { + t.Errorf("host[%d] = %v, want %v", i, actual[i], expected[i]) + } + } +} diff --git a/server/internal/database/service_instance.go b/server/internal/database/service_instance.go index e5c1ebe9..0fffc3d0 100644 --- a/server/internal/database/service_instance.go +++ b/server/internal/database/service_instance.go @@ -161,19 +161,19 @@ func GenerateDatabaseNetworkID(databaseID string) string { // ServiceInstanceSpec contains the specification for generating service instance resources. type ServiceInstanceSpec struct { - ServiceInstanceID string - ServiceSpec *ServiceSpec - PgEdgeVersion *host.PgEdgeVersion // Database version, used for compatibility validation - DatabaseID string - DatabaseName string - HostID string - CohortMemberID string - Credentials *ServiceUser - DatabaseNetworkID string - NodeName string // Database node name (for ServiceUserRole PrimaryExecutor routing) - DatabaseHost string // Postgres instance hostname to connect to - DatabasePort int // Postgres instance port - Port *int // Service instance published port (optional, 0 = random) + ServiceInstanceID string + ServiceSpec *ServiceSpec + PgEdgeVersion *host.PgEdgeVersion // Database version, used for compatibility validation + DatabaseID string + DatabaseName string + HostID string + CohortMemberID string + Credentials *ServiceUser + DatabaseNetworkID string + NodeName string // Database node name (for ServiceUserRole PrimaryExecutor routing) + DatabaseHosts []ServiceHostEntry // Ordered list of Postgres host:port entries + TargetSessionAttrs string // libpq target_session_attrs value + Port *int // Service instance published port (optional, 0 = random) } // storedToServiceInstance converts stored service instance and status to ServiceInstance. diff --git a/server/internal/database/spec.go b/server/internal/database/spec.go index 9df1de55..85a1726b 100644 --- a/server/internal/database/spec.go +++ b/server/internal/database/spec.go @@ -115,16 +115,33 @@ func (u *User) DefaultOptionalFieldsFrom(other *User) { } } +// DatabaseConnection controls how a service connects to the database. +type DatabaseConnection struct { + TargetNodes []string `json:"target_nodes,omitempty"` + TargetSessionAttrs string `json:"target_session_attrs,omitempty"` +} + +func (r *DatabaseConnection) Clone() *DatabaseConnection { + if r == nil { + return nil + } + return &DatabaseConnection{ + TargetNodes: slices.Clone(r.TargetNodes), + TargetSessionAttrs: r.TargetSessionAttrs, + } +} + type ServiceSpec struct { - ServiceID string `json:"service_id"` - ServiceType string `json:"service_type"` - Version string `json:"version"` - HostIDs []string `json:"host_ids"` - Config map[string]any `json:"config"` - Port *int `json:"port,omitempty"` - CPUs *float64 `json:"cpus,omitempty"` - MemoryBytes *uint64 `json:"memory,omitempty"` - OrchestratorOpts *OrchestratorOpts `json:"orchestrator_opts,omitempty"` + ServiceID string `json:"service_id"` + ServiceType string `json:"service_type"` + Version string `json:"version"` + HostIDs []string `json:"host_ids"` + Config map[string]any `json:"config"` + Port *int `json:"port,omitempty"` + CPUs *float64 `json:"cpus,omitempty"` + MemoryBytes *uint64 `json:"memory,omitempty"` + OrchestratorOpts *OrchestratorOpts `json:"orchestrator_opts,omitempty"` + DatabaseConnection *DatabaseConnection `json:"database_connection,omitempty"` } func (s *ServiceSpec) Clone() *ServiceSpec { @@ -132,15 +149,16 @@ func (s *ServiceSpec) Clone() *ServiceSpec { return nil } return &ServiceSpec{ - ServiceID: s.ServiceID, - ServiceType: s.ServiceType, - Version: s.Version, - HostIDs: slices.Clone(s.HostIDs), - Config: maps.Clone(s.Config), - Port: utils.ClonePointer(s.Port), - CPUs: utils.ClonePointer(s.CPUs), - MemoryBytes: utils.ClonePointer(s.MemoryBytes), - OrchestratorOpts: s.OrchestratorOpts.Clone(), + ServiceID: s.ServiceID, + ServiceType: s.ServiceType, + Version: s.Version, + HostIDs: slices.Clone(s.HostIDs), + Config: maps.Clone(s.Config), + Port: utils.ClonePointer(s.Port), + CPUs: utils.ClonePointer(s.CPUs), + MemoryBytes: utils.ClonePointer(s.MemoryBytes), + OrchestratorOpts: s.OrchestratorOpts.Clone(), + DatabaseConnection: s.DatabaseConnection.Clone(), } } diff --git a/server/internal/database/subscription_resource.go b/server/internal/database/subscription_resource.go index 420ffc20..4e95be56 100644 --- a/server/internal/database/subscription_resource.go +++ b/server/internal/database/subscription_resource.go @@ -169,7 +169,7 @@ func (s *SubscriptionResource) providerDSN(ctx context.Context, rc *resource.Con SSLKey: providers[0].ConnectionInfo.PeerSSLKey, SSLRootCert: providers[0].ConnectionInfo.PeerSSLRootCert, Extra: map[string]string{ - "target_session_attrs": "primary", + "target_session_attrs": TargetSessionAttrsPrimary, }, }, nil } diff --git a/server/internal/orchestrator/swarm/mcp_config.go b/server/internal/orchestrator/swarm/mcp_config.go index 194a483d..a5c22c7c 100644 --- a/server/internal/orchestrator/swarm/mcp_config.go +++ b/server/internal/orchestrator/swarm/mcp_config.go @@ -29,16 +29,21 @@ type mcpAuthConfig struct { UserFile string `yaml:"user_file"` } +type mcpHostEntry struct { + Host string `yaml:"host"` + Port int `yaml:"port"` +} + type mcpDatabaseConfig struct { - Name string `yaml:"name"` - Host string `yaml:"host"` - Port int `yaml:"port"` - Database string `yaml:"database"` - User string `yaml:"user"` - Password string `yaml:"password"` - SSLMode string `yaml:"sslmode"` - AllowWrites bool `yaml:"allow_writes"` - Pool mcpPoolConfig `yaml:"pool"` + Name string `yaml:"name"` + Hosts []mcpHostEntry `yaml:"hosts"` + TargetSessionAttrs string `yaml:"target_session_attrs,omitempty"` + Database string `yaml:"database"` + User string `yaml:"user"` + Password string `yaml:"password"` + SSLMode string `yaml:"sslmode"` + AllowWrites bool `yaml:"allow_writes"` + Pool mcpPoolConfig `yaml:"pool"` } type mcpPoolConfig struct { @@ -83,12 +88,12 @@ type mcpToolsConfig struct { // MCPConfigParams holds all inputs needed to generate a config.yaml for the MCP server. type MCPConfigParams struct { - Config *database.MCPServiceConfig - DatabaseName string - DatabaseHost string - DatabasePort int - Username string - Password string + Config *database.MCPServiceConfig + DatabaseName string + DatabaseHosts []database.ServiceHostEntry + TargetSessionAttrs string + Username string + Password string } // GenerateMCPConfig generates the YAML config file content for the MCP server. @@ -187,6 +192,12 @@ func GenerateMCPConfig(params *MCPConfigParams) ([]byte, error) { tools.CountRows = boolPtr(false) } + // Map database hosts to MCP config format + hosts := make([]mcpHostEntry, len(params.DatabaseHosts)) + for i, h := range params.DatabaseHosts { + hosts[i] = mcpHostEntry{Host: h.Host, Port: h.Port} + } + yamlCfg := &mcpYAMLConfig{ HTTP: mcpHTTPConfig{ Enabled: true, @@ -199,14 +210,14 @@ func GenerateMCPConfig(params *MCPConfigParams) ([]byte, error) { }, Databases: []mcpDatabaseConfig{ { - Name: params.DatabaseName, - Host: params.DatabaseHost, - Port: params.DatabasePort, - Database: params.DatabaseName, - User: params.Username, - Password: params.Password, - SSLMode: "prefer", - AllowWrites: allowWrites, + Name: params.DatabaseName, + Hosts: hosts, + TargetSessionAttrs: params.TargetSessionAttrs, + Database: params.DatabaseName, + User: params.Username, + Password: params.Password, + SSLMode: "prefer", + AllowWrites: allowWrites, Pool: mcpPoolConfig{ MaxConns: poolMaxConns, }, diff --git a/server/internal/orchestrator/swarm/mcp_config_resource.go b/server/internal/orchestrator/swarm/mcp_config_resource.go index 94498083..138f4e85 100644 --- a/server/internal/orchestrator/swarm/mcp_config_resource.go +++ b/server/internal/orchestrator/swarm/mcp_config_resource.go @@ -33,16 +33,16 @@ func MCPConfigResourceIdentifier(serviceInstanceID string) resource.Identifier { // - tokens.yaml: Application-owned, written only on first Create if init_token is set // - users.yaml: Application-owned, written only on first Create if init_users is set type MCPConfigResource struct { - ServiceInstanceID string `json:"service_instance_id"` - ServiceID string `json:"service_id"` - HostID string `json:"host_id"` - DirResourceID string `json:"dir_resource_id"` - Config *database.MCPServiceConfig `json:"config"` - DatabaseName string `json:"database_name"` - DatabaseHost string `json:"database_host"` - DatabasePort int `json:"database_port"` - Username string `json:"username"` - Password string `json:"password"` + ServiceInstanceID string `json:"service_instance_id"` + ServiceID string `json:"service_id"` + HostID string `json:"host_id"` + DirResourceID string `json:"dir_resource_id"` + Config *database.MCPServiceConfig `json:"config"` + DatabaseName string `json:"database_name"` + DatabaseHosts []database.ServiceHostEntry `json:"database_hosts"` + TargetSessionAttrs string `json:"target_session_attrs"` + Username string `json:"username"` + Password string `json:"password"` } func (r *MCPConfigResource) ResourceVersion() string { @@ -171,12 +171,12 @@ func (r *MCPConfigResource) Delete(ctx context.Context, rc *resource.Context) er // writeConfigFile generates and writes the config.yaml file. func (r *MCPConfigResource) writeConfigFile(fs afero.Fs, dirPath string) error { content, err := GenerateMCPConfig(&MCPConfigParams{ - Config: r.Config, - DatabaseName: r.DatabaseName, - DatabaseHost: r.DatabaseHost, - DatabasePort: r.DatabasePort, - Username: r.Username, - Password: r.Password, + Config: r.Config, + DatabaseName: r.DatabaseName, + DatabaseHosts: r.DatabaseHosts, + TargetSessionAttrs: r.TargetSessionAttrs, + Username: r.Username, + Password: r.Password, }) if err != nil { return fmt.Errorf("failed to generate MCP config: %w", err) diff --git a/server/internal/orchestrator/swarm/mcp_config_test.go b/server/internal/orchestrator/swarm/mcp_config_test.go index d449bf8d..fffa44df 100644 --- a/server/internal/orchestrator/swarm/mcp_config_test.go +++ b/server/internal/orchestrator/swarm/mcp_config_test.go @@ -1,6 +1,7 @@ package swarm import ( + "fmt" "testing" "github.com/goccy/go-yaml" @@ -26,11 +27,10 @@ func TestGenerateMCPConfig_MinimalConfig(t *testing.T) { LLMModel: "claude-sonnet-4-5", AnthropicAPIKey: strPtr("sk-ant-api03-test"), }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -88,11 +88,10 @@ func TestGenerateMCPConfig_DefaultValues(t *testing.T) { AnthropicAPIKey: strPtr("sk-ant-api03-test"), // LLMTemperature, LLMMaxTokens, PoolMaxConns, AllowWrites all nil }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -135,11 +134,10 @@ func TestGenerateMCPConfig_CustomValues(t *testing.T) { PoolMaxConns: &poolMax, AllowWrites: &allowW, }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -174,11 +172,10 @@ func TestGenerateMCPConfig_ProviderKeys_Anthropic(t *testing.T) { LLMModel: "claude-sonnet-4-5", AnthropicAPIKey: &apiKey, }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -207,11 +204,10 @@ func TestGenerateMCPConfig_ProviderKeys_OpenAI(t *testing.T) { LLMModel: "gpt-4", OpenAIAPIKey: &apiKey, }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -240,11 +236,10 @@ func TestGenerateMCPConfig_ProviderKeys_Ollama(t *testing.T) { LLMModel: "llama3", OllamaURL: &ollamaURL, }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -279,11 +274,10 @@ func TestGenerateMCPConfig_EmbeddingPresent(t *testing.T) { EmbeddingModel: &embModel, EmbeddingAPIKey: &embAPIKey, }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -318,11 +312,10 @@ func TestGenerateMCPConfig_EmbeddingAbsent(t *testing.T) { LLMModel: "claude-sonnet-4-5", AnthropicAPIKey: strPtr("sk-ant-api03-test"), }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -351,11 +344,10 @@ func TestGenerateMCPConfig_EmbeddingOpenAI(t *testing.T) { EmbeddingModel: &embModel, EmbeddingAPIKey: &embAPIKey, }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -389,11 +381,10 @@ func TestGenerateMCPConfig_EmbeddingOllama(t *testing.T) { EmbeddingProvider: &embProvider, EmbeddingModel: &embModel, }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -426,11 +417,10 @@ func TestGenerateMCPConfig_ToolToggles_AllDisabled(t *testing.T) { DisableSearchKnowledgebase: &trueVal, DisableCountRows: &trueVal, }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -473,11 +463,10 @@ func TestGenerateMCPConfig_ToolToggles_NoneDisabled(t *testing.T) { LLMModel: "claude-sonnet-4-5", AnthropicAPIKey: strPtr("sk-ant-api03-test"), }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -522,11 +511,10 @@ func TestGenerateMCPConfig_ToolToggles_DisableFalseIsNoop(t *testing.T) { AnthropicAPIKey: strPtr("sk-ant-api03-test"), DisableQueryDatabase: &falseVal, }, - DatabaseName: "mydb", - DatabaseHost: "db-host", - DatabasePort: 5432, - Username: "appuser", - Password: "secret", + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", } data, err := GenerateMCPConfig(params) @@ -549,11 +537,10 @@ func TestGenerateMCPConfig_DatabaseConfig(t *testing.T) { LLMModel: "claude-sonnet-4-5", AnthropicAPIKey: strPtr("sk-ant-api03-test"), }, - DatabaseName: "myspecialdb", - DatabaseHost: "pg-primary.internal", - DatabasePort: 5433, - Username: "svc_myspecialdb", - Password: "supersecret", + DatabaseName: "myspecialdb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "pg-primary.internal", Port: 5433}}, + Username: "svc_myspecialdb", + Password: "supersecret", } data, err := GenerateMCPConfig(params) @@ -574,11 +561,14 @@ func TestGenerateMCPConfig_DatabaseConfig(t *testing.T) { if db.Database != "myspecialdb" { t.Errorf("databases[0].database = %q, want %q", db.Database, "myspecialdb") } - if db.Host != "pg-primary.internal" { - t.Errorf("databases[0].host = %q, want %q", db.Host, "pg-primary.internal") + if len(db.Hosts) != 1 { + t.Fatalf("databases[0].hosts len = %d, want 1", len(db.Hosts)) } - if db.Port != 5433 { - t.Errorf("databases[0].port = %d, want 5433", db.Port) + if db.Hosts[0].Host != "pg-primary.internal" { + t.Errorf("databases[0].hosts[0].host = %q, want %q", db.Hosts[0].Host, "pg-primary.internal") + } + if db.Hosts[0].Port != 5433 { + t.Errorf("databases[0].hosts[0].port = %d, want 5433", db.Hosts[0].Port) } if db.User != "svc_myspecialdb" { t.Errorf("databases[0].user = %q, want %q", db.User, "svc_myspecialdb") @@ -590,3 +580,331 @@ func TestGenerateMCPConfig_DatabaseConfig(t *testing.T) { t.Errorf("databases[0].sslmode = %q, want %q", db.SSLMode, "prefer") } } + +// TestGenerateMCPConfig_MultiHostTopology exercises the full path from +// service spec → BuildServiceHostList → GenerateMCPConfig → YAML output. +// It verifies that the generated YAML contains the correct ordered hosts +// array and target_session_attrs for various topologies. +func TestGenerateMCPConfig_MultiHostTopology(t *testing.T) { + // inst builds a minimal InstanceSpec for testing. + inst := func(instanceID, hostID string) *database.InstanceSpec { + return &database.InstanceSpec{ + InstanceID: instanceID, + HostID: hostID, + } + } + + // baseMCPConfig returns a minimal MCPServiceConfig to avoid nil-pointer + // issues in GenerateMCPConfig. + baseMCPConfig := func() *database.MCPServiceConfig { + return &database.MCPServiceConfig{ + LLMProvider: "anthropic", + LLMModel: "claude-sonnet-4-5", + AnthropicAPIKey: strPtr("sk-ant-api03-test"), + } + } + + // assertHostEntries verifies the YAML hosts array matches the expected + // host:port pairs in order. + assertHostEntries := func(t *testing.T, expected []database.ServiceHostEntry, actual []mcpHostEntry) { + t.Helper() + if len(actual) != len(expected) { + t.Fatalf("hosts length = %d, want %d\n got: %v\n want: %v", len(actual), len(expected), actual, expected) + } + for i := range expected { + if actual[i].Host != expected[i].Host { + t.Errorf("hosts[%d].host = %q, want %q", i, actual[i].Host, expected[i].Host) + } + if actual[i].Port != expected[i].Port { + t.Errorf("hosts[%d].port = %d, want %d", i, actual[i].Port, expected[i].Port) + } + } + } + + // he builds the expected ServiceHostEntry for a given instance ID + // (mirrors the "postgres-{instanceID}" convention at port 5432). + he := func(instanceID string) database.ServiceHostEntry { + return database.ServiceHostEntry{ + Host: fmt.Sprintf("postgres-%s", instanceID), + Port: 5432, + } + } + + t.Run("2-node multi-active service on host-1", func(t *testing.T) { + nodeInstances := []*database.NodeInstances{ + {NodeName: "n1", Instances: []*database.InstanceSpec{inst("inst1", "host-1")}}, + {NodeName: "n2", Instances: []*database.InstanceSpec{inst("inst2", "host-2")}}, + } + + targetSessionAttrs := database.TargetSessionAttrsPrimary + connInfo, err := database.BuildServiceHostList(&database.BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: nodeInstances, + TargetSessionAttrs: targetSessionAttrs, + }) + if err != nil { + t.Fatalf("BuildServiceHostList error: %v", err) + } + + data, err := GenerateMCPConfig(&MCPConfigParams{ + Config: baseMCPConfig(), + DatabaseName: "mydb", + DatabaseHosts: connInfo.Hosts, + TargetSessionAttrs: connInfo.TargetSessionAttrs, + Username: "appuser", + Password: "secret", + }) + if err != nil { + t.Fatalf("GenerateMCPConfig error: %v", err) + } + + cfg := parseYAML(t, data) + if len(cfg.Databases) != 1 { + t.Fatalf("databases len = %d, want 1", len(cfg.Databases)) + } + db := cfg.Databases[0] + + // Local node (n1 on host-1) should appear first. + assertHostEntries(t, []database.ServiceHostEntry{ + he("inst1"), + he("inst2"), + }, db.Hosts) + + // allow_writes: true → target_session_attrs: primary + if db.TargetSessionAttrs != database.TargetSessionAttrsPrimary { + t.Errorf("target_session_attrs = %q, want %q", db.TargetSessionAttrs, database.TargetSessionAttrsPrimary) + } + }) + + t.Run("HA within node service on replica host", func(t *testing.T) { + nodeInstances := []*database.NodeInstances{ + { + NodeName: "n1", + Instances: []*database.InstanceSpec{ + inst("inst1-h1", "host-1"), + inst("inst1-h2", "host-2"), + }, + }, + {NodeName: "n2", Instances: []*database.InstanceSpec{inst("inst2-h3", "host-3")}}, + } + + targetSessionAttrs := database.TargetSessionAttrsPrimary + connInfo, err := database.BuildServiceHostList(&database.BuildServiceHostListParams{ + ServiceHostID: "host-2", + NodeInstances: nodeInstances, + TargetSessionAttrs: targetSessionAttrs, + }) + if err != nil { + t.Fatalf("BuildServiceHostList error: %v", err) + } + + data, err := GenerateMCPConfig(&MCPConfigParams{ + Config: baseMCPConfig(), + DatabaseName: "mydb", + DatabaseHosts: connInfo.Hosts, + TargetSessionAttrs: connInfo.TargetSessionAttrs, + Username: "appuser", + Password: "secret", + }) + if err != nil { + t.Fatalf("GenerateMCPConfig error: %v", err) + } + + cfg := parseYAML(t, data) + db := cfg.Databases[0] + + // Co-located instance (inst1-h2 on host-2) first within n1, + // then inst1-h1 (other n1 instance), then n2. + assertHostEntries(t, []database.ServiceHostEntry{ + he("inst1-h2"), + he("inst1-h1"), + he("inst2-h3"), + }, db.Hosts) + + if db.TargetSessionAttrs != database.TargetSessionAttrsPrimary { + t.Errorf("target_session_attrs = %q, want %q", db.TargetSessionAttrs, database.TargetSessionAttrsPrimary) + } + }) + + t.Run("target_nodes filter", func(t *testing.T) { + targetNodes := []string{"n1", "n2"} + nodeInstances := []*database.NodeInstances{ + {NodeName: "n1", Instances: []*database.InstanceSpec{inst("inst1", "host-1")}}, + {NodeName: "n2", Instances: []*database.InstanceSpec{inst("inst2", "host-2")}}, + {NodeName: "n3", Instances: []*database.InstanceSpec{inst("inst3", "host-3")}}, + } + + targetSessionAttrs := database.TargetSessionAttrsPrimary + connInfo, err := database.BuildServiceHostList(&database.BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: nodeInstances, + TargetNodes: targetNodes, + TargetSessionAttrs: targetSessionAttrs, + }) + if err != nil { + t.Fatalf("BuildServiceHostList error: %v", err) + } + + data, err := GenerateMCPConfig(&MCPConfigParams{ + Config: baseMCPConfig(), + DatabaseName: "mydb", + DatabaseHosts: connInfo.Hosts, + TargetSessionAttrs: connInfo.TargetSessionAttrs, + Username: "appuser", + Password: "secret", + }) + if err != nil { + t.Fatalf("GenerateMCPConfig error: %v", err) + } + + cfg := parseYAML(t, data) + db := cfg.Databases[0] + + // Only n1 and n2 should be included; n3 excluded. + assertHostEntries(t, []database.ServiceHostEntry{ + he("inst1"), + he("inst2"), + }, db.Hosts) + }) + + t.Run("allow_writes true derives primary", func(t *testing.T) { + nodeInstances := []*database.NodeInstances{ + {NodeName: "n1", Instances: []*database.InstanceSpec{inst("inst1", "host-1")}}, + } + + targetSessionAttrs := database.TargetSessionAttrsPrimary + connInfo, err := database.BuildServiceHostList(&database.BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: nodeInstances, + TargetSessionAttrs: targetSessionAttrs, + }) + if err != nil { + t.Fatalf("BuildServiceHostList error: %v", err) + } + + data, err := GenerateMCPConfig(&MCPConfigParams{ + Config: baseMCPConfig(), + DatabaseName: "mydb", + DatabaseHosts: connInfo.Hosts, + TargetSessionAttrs: connInfo.TargetSessionAttrs, + Username: "appuser", + Password: "secret", + }) + if err != nil { + t.Fatalf("GenerateMCPConfig error: %v", err) + } + + cfg := parseYAML(t, data) + if cfg.Databases[0].TargetSessionAttrs != database.TargetSessionAttrsPrimary { + t.Errorf("target_session_attrs = %q, want %q", cfg.Databases[0].TargetSessionAttrs, database.TargetSessionAttrsPrimary) + } + }) + + t.Run("allow_writes false derives prefer-standby", func(t *testing.T) { + nodeInstances := []*database.NodeInstances{ + {NodeName: "n1", Instances: []*database.InstanceSpec{inst("inst1", "host-1")}}, + } + + targetSessionAttrs := database.TargetSessionAttrsPreferStandby + connInfo, err := database.BuildServiceHostList(&database.BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: nodeInstances, + TargetSessionAttrs: targetSessionAttrs, + }) + if err != nil { + t.Fatalf("BuildServiceHostList error: %v", err) + } + + data, err := GenerateMCPConfig(&MCPConfigParams{ + Config: baseMCPConfig(), + DatabaseName: "mydb", + DatabaseHosts: connInfo.Hosts, + TargetSessionAttrs: connInfo.TargetSessionAttrs, + Username: "appuser", + Password: "secret", + }) + if err != nil { + t.Fatalf("GenerateMCPConfig error: %v", err) + } + + cfg := parseYAML(t, data) + if cfg.Databases[0].TargetSessionAttrs != database.TargetSessionAttrsPreferStandby { + t.Errorf("target_session_attrs = %q, want %q", cfg.Databases[0].TargetSessionAttrs, database.TargetSessionAttrsPreferStandby) + } + }) + + t.Run("explicit database_connection target_session_attrs overrides derived", func(t *testing.T) { + nodeInstances := []*database.NodeInstances{ + {NodeName: "n1", Instances: []*database.InstanceSpec{inst("inst1", "host-1")}}, + } + + targetSessionAttrs := database.TargetSessionAttrsReadWrite + connInfo, err := database.BuildServiceHostList(&database.BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: nodeInstances, + TargetSessionAttrs: targetSessionAttrs, + }) + if err != nil { + t.Fatalf("BuildServiceHostList error: %v", err) + } + + data, err := GenerateMCPConfig(&MCPConfigParams{ + Config: baseMCPConfig(), + DatabaseName: "mydb", + DatabaseHosts: connInfo.Hosts, + TargetSessionAttrs: connInfo.TargetSessionAttrs, + Username: "appuser", + Password: "secret", + }) + if err != nil { + t.Fatalf("GenerateMCPConfig error: %v", err) + } + + cfg := parseYAML(t, data) + // Explicit "read-write" should override the allow_writes→"primary" default. + if cfg.Databases[0].TargetSessionAttrs != database.TargetSessionAttrsReadWrite { + t.Errorf("target_session_attrs = %q, want %q", cfg.Databases[0].TargetSessionAttrs, database.TargetSessionAttrsReadWrite) + } + }) + + t.Run("single node single host", func(t *testing.T) { + nodeInstances := []*database.NodeInstances{ + {NodeName: "n1", Instances: []*database.InstanceSpec{inst("only-inst", "host-1")}}, + } + + targetSessionAttrs := database.TargetSessionAttrsPreferStandby + connInfo, err := database.BuildServiceHostList(&database.BuildServiceHostListParams{ + ServiceHostID: "host-1", + NodeInstances: nodeInstances, + TargetSessionAttrs: targetSessionAttrs, + }) + if err != nil { + t.Fatalf("BuildServiceHostList error: %v", err) + } + + data, err := GenerateMCPConfig(&MCPConfigParams{ + Config: baseMCPConfig(), + DatabaseName: "mydb", + DatabaseHosts: connInfo.Hosts, + TargetSessionAttrs: connInfo.TargetSessionAttrs, + Username: "appuser", + Password: "secret", + }) + if err != nil { + t.Fatalf("GenerateMCPConfig error: %v", err) + } + + cfg := parseYAML(t, data) + db := cfg.Databases[0] + + // Single host in structured format (not legacy host/port). + assertHostEntries(t, []database.ServiceHostEntry{ + he("only-inst"), + }, db.Hosts) + + // No allow_writes set → prefer-standby + if db.TargetSessionAttrs != database.TargetSessionAttrsPreferStandby { + t.Errorf("target_session_attrs = %q, want %q", db.TargetSessionAttrs, database.TargetSessionAttrsPreferStandby) + } + }) +} diff --git a/server/internal/orchestrator/swarm/orchestrator.go b/server/internal/orchestrator/swarm/orchestrator.go index ed4e34a5..701f596f 100644 --- a/server/internal/orchestrator/swarm/orchestrator.go +++ b/server/internal/orchestrator/swarm/orchestrator.go @@ -467,14 +467,14 @@ func (o *Orchestrator) GenerateServiceInstanceResources(spec *database.ServiceIn // MCP config resource (generates config.yaml, tokens.yaml, users.yaml) mcpConfigResource := &MCPConfigResource{ - ServiceInstanceID: spec.ServiceInstanceID, - ServiceID: spec.ServiceSpec.ServiceID, - HostID: spec.HostID, - DirResourceID: dataDirID, - Config: mcpConfig, - DatabaseName: spec.DatabaseName, - DatabaseHost: spec.DatabaseHost, - DatabasePort: spec.DatabasePort, + ServiceInstanceID: spec.ServiceInstanceID, + ServiceID: spec.ServiceSpec.ServiceID, + HostID: spec.HostID, + DirResourceID: dataDirID, + Config: mcpConfig, + DatabaseName: spec.DatabaseName, + DatabaseHosts: spec.DatabaseHosts, + TargetSessionAttrs: spec.TargetSessionAttrs, } if spec.Credentials != nil { mcpConfigResource.Username = spec.Credentials.Username @@ -484,21 +484,21 @@ func (o *Orchestrator) GenerateServiceInstanceResources(spec *database.ServiceIn // Service instance spec resource serviceName := ServiceInstanceName(spec.ServiceSpec.ServiceType, spec.DatabaseID, spec.ServiceSpec.ServiceID, spec.HostID) serviceInstanceSpec := &ServiceInstanceSpecResource{ - ServiceInstanceID: spec.ServiceInstanceID, - ServiceSpec: spec.ServiceSpec, - DatabaseID: spec.DatabaseID, - DatabaseName: spec.DatabaseName, - HostID: spec.HostID, - ServiceName: serviceName, - Hostname: serviceName, - CohortMemberID: o.swarmNodeID, - ServiceImage: serviceImage, - Credentials: spec.Credentials, - DatabaseNetworkID: databaseNetwork.Name, - DatabaseHost: spec.DatabaseHost, - DatabasePort: spec.DatabasePort, - Port: spec.Port, - DataDirID: dataDirID, + ServiceInstanceID: spec.ServiceInstanceID, + ServiceSpec: spec.ServiceSpec, + DatabaseID: spec.DatabaseID, + DatabaseName: spec.DatabaseName, + HostID: spec.HostID, + ServiceName: serviceName, + Hostname: serviceName, + CohortMemberID: o.swarmNodeID, + ServiceImage: serviceImage, + Credentials: spec.Credentials, + DatabaseNetworkID: databaseNetwork.Name, + DatabaseHosts: spec.DatabaseHosts, + TargetSessionAttrs: spec.TargetSessionAttrs, + Port: spec.Port, + DataDirID: dataDirID, } // Service instance resource (actual Docker service) diff --git a/server/internal/orchestrator/swarm/service_instance_spec.go b/server/internal/orchestrator/swarm/service_instance_spec.go index ca103915..e8b44847 100644 --- a/server/internal/orchestrator/swarm/service_instance_spec.go +++ b/server/internal/orchestrator/swarm/service_instance_spec.go @@ -23,22 +23,22 @@ func ServiceInstanceSpecResourceIdentifier(serviceInstanceID string) resource.Id } type ServiceInstanceSpecResource struct { - ServiceInstanceID string `json:"service_instance_id"` - ServiceSpec *database.ServiceSpec `json:"service_spec"` - DatabaseID string `json:"database_id"` - DatabaseName string `json:"database_name"` - HostID string `json:"host_id"` - ServiceName string `json:"service_name"` - Hostname string `json:"hostname"` - CohortMemberID string `json:"cohort_member_id"` - ServiceImage *ServiceImage `json:"service_image"` - Credentials *database.ServiceUser `json:"credentials"` - DatabaseNetworkID string `json:"database_network_id"` - DatabaseHost string `json:"database_host"` // Postgres instance hostname - DatabasePort int `json:"database_port"` // Postgres instance port - Port *int `json:"port"` // Service published port (optional, 0 = random) - DataDirID string `json:"data_dir_id"` // DirResource ID for the service data directory - Spec swarm.ServiceSpec `json:"spec"` + ServiceInstanceID string `json:"service_instance_id"` + ServiceSpec *database.ServiceSpec `json:"service_spec"` + DatabaseID string `json:"database_id"` + DatabaseName string `json:"database_name"` + HostID string `json:"host_id"` + ServiceName string `json:"service_name"` + Hostname string `json:"hostname"` + CohortMemberID string `json:"cohort_member_id"` + ServiceImage *ServiceImage `json:"service_image"` + Credentials *database.ServiceUser `json:"credentials"` + DatabaseNetworkID string `json:"database_network_id"` + DatabaseHosts []database.ServiceHostEntry `json:"database_hosts"` // Ordered Postgres host:port entries + TargetSessionAttrs string `json:"target_session_attrs"` // libpq target_session_attrs + Port *int `json:"port"` // Service published port (optional, 0 = random) + DataDirID string `json:"data_dir_id"` // DirResource ID for the service data directory + Spec swarm.ServiceSpec `json:"spec"` } func (s *ServiceInstanceSpecResource) ResourceVersion() string { @@ -114,8 +114,6 @@ func (s *ServiceInstanceSpecResource) Refresh(ctx context.Context, rc *resource. ServiceImage: s.ServiceImage, Credentials: s.Credentials, DatabaseNetworkID: network.NetworkID, - DatabaseHost: s.DatabaseHost, - DatabasePort: s.DatabasePort, Port: s.Port, DataPath: dataPath, }) diff --git a/server/internal/orchestrator/swarm/service_spec.go b/server/internal/orchestrator/swarm/service_spec.go index 4c7034f5..f566fa0f 100644 --- a/server/internal/orchestrator/swarm/service_spec.go +++ b/server/internal/orchestrator/swarm/service_spec.go @@ -28,9 +28,6 @@ type ServiceContainerSpecOptions struct { ServiceImage *ServiceImage Credentials *database.ServiceUser DatabaseNetworkID string - // Database connection info - DatabaseHost string - DatabasePort int // Service port configuration Port *int // DataPath is the host-side directory path for the bind mount diff --git a/server/internal/orchestrator/swarm/service_spec_test.go b/server/internal/orchestrator/swarm/service_spec_test.go index f1d4869c..a9d5eac5 100644 --- a/server/internal/orchestrator/swarm/service_spec_test.go +++ b/server/internal/orchestrator/swarm/service_spec_test.go @@ -54,8 +54,6 @@ func TestServiceContainerSpec(t *testing.T) { Role: "pgedge_application_read_only", }, DatabaseNetworkID: "db1-database", - DatabaseHost: "postgres-instance-1", - DatabasePort: 5432, Port: intPtr(8080), DataPath: "/var/lib/pgedge/services/db1-mcp-server-host1", }, @@ -171,8 +169,6 @@ func TestServiceContainerSpec(t *testing.T) { Tag: "ghcr.io/pgedge/postgres-mcp:latest", }, DatabaseNetworkID: "db1-database", - DatabaseHost: "postgres-instance-1", - DatabasePort: 5432, DataPath: "/var/lib/pgedge/services/db1-mcp-server-host1", }, wantErr: false, diff --git a/server/internal/workflows/plan_update.go b/server/internal/workflows/plan_update.go index da6c702b..e1c38fce 100644 --- a/server/internal/workflows/plan_update.go +++ b/server/internal/workflows/plan_update.go @@ -101,25 +101,36 @@ func (w *Workflows) getServiceResources( return nil, fmt.Errorf("failed to parse pgedge version: %w", err) } - // Resolve Postgres connection info for the service container. - // Services connect to Postgres via the overlay network using the instance hostname. - databaseHost, databasePort, err := findPostgresInstance(nodeInstances, hostID) + // Build ordered host list for multi-host database connections. + targetSessionAttrs := resolveTargetSessionAttrs(serviceSpec) + + var targetNodes []string + if serviceSpec.DatabaseConnection != nil { + targetNodes = serviceSpec.DatabaseConnection.TargetNodes + } + + connInfo, err := database.BuildServiceHostList(&database.BuildServiceHostListParams{ + ServiceHostID: hostID, + NodeInstances: nodeInstances, + TargetNodes: targetNodes, + TargetSessionAttrs: targetSessionAttrs, + }) if err != nil { - return nil, fmt.Errorf("failed to resolve postgres instance for service: %w", err) + return nil, fmt.Errorf("failed to build service host list: %w", err) } serviceInstanceSpec := &database.ServiceInstanceSpec{ - ServiceInstanceID: serviceInstanceID, - ServiceSpec: serviceSpec, - PgEdgeVersion: pgEdgeVersion, - DatabaseID: spec.DatabaseID, - DatabaseName: spec.DatabaseName, - HostID: hostID, - NodeName: nodeName, - DatabaseNetworkID: database.GenerateDatabaseNetworkID(spec.DatabaseID), - DatabaseHost: databaseHost, - DatabasePort: databasePort, - Port: serviceSpec.Port, + ServiceInstanceID: serviceInstanceID, + ServiceSpec: serviceSpec, + PgEdgeVersion: pgEdgeVersion, + DatabaseID: spec.DatabaseID, + DatabaseName: spec.DatabaseName, + HostID: hostID, + NodeName: nodeName, + DatabaseNetworkID: database.GenerateDatabaseNetworkID(spec.DatabaseID), + DatabaseHosts: connInfo.Hosts, + TargetSessionAttrs: connInfo.TargetSessionAttrs, + Port: serviceSpec.Port, // Credentials: nil — ServiceUserRole.Create() will generate them } @@ -140,30 +151,26 @@ func (w *Workflows) getServiceResources( }, nil } -// findPostgresInstance resolves the Postgres hostname and port for a service -// container from the database spec. It prefers a co-located instance (same host -// as the service) for lower latency, falling back to any instance in the database. -// The hostname follows the swarm orchestrator convention: "postgres-{instanceID}". -// The returned port is always the internal container port (5432), not the published -// host port, because service containers connect via the overlay network. -func findPostgresInstance(nodeInstances []*database.NodeInstances, serviceHostID string) (string, int, error) { - const internalPort = 5432 - - var fallback *database.InstanceSpec - for _, node := range nodeInstances { - for _, inst := range node.Instances { - if fallback == nil { - fallback = inst - } - if inst.HostID == serviceHostID { - return fmt.Sprintf("postgres-%s", inst.InstanceID), internalPort, nil - } - } +// resolveTargetSessionAttrs determines the target_session_attrs value for a +// service. Explicit user setting wins; otherwise each service type maps its +// own config semantics to the appropriate libpq value. +func resolveTargetSessionAttrs(serviceSpec *database.ServiceSpec) string { + // Tier 1: Explicit user setting in database_connection + if serviceSpec.DatabaseConnection != nil && serviceSpec.DatabaseConnection.TargetSessionAttrs != "" { + return serviceSpec.DatabaseConnection.TargetSessionAttrs } - - if fallback != nil { - return fmt.Sprintf("postgres-%s", fallback.InstanceID), internalPort, nil + // Tier 2: Per-service-type default + switch serviceSpec.ServiceType { + case "mcp": + // MCP maps allow_writes → primary/prefer-standby + if allowWrites, ok := serviceSpec.Config["allow_writes"].(bool); ok && allowWrites { + return database.TargetSessionAttrsPrimary + } + return database.TargetSessionAttrsPreferStandby + // Future service types add cases here. + default: + // Default to "prefer-standby" for safety — read-only unless the + // service explicitly opts in to writes. + return database.TargetSessionAttrsPreferStandby } - - return "", 0, fmt.Errorf("no postgres instances found for service host %s", serviceHostID) } diff --git a/server/internal/workflows/plan_update_test.go b/server/internal/workflows/plan_update_test.go new file mode 100644 index 00000000..c8e31f7e --- /dev/null +++ b/server/internal/workflows/plan_update_test.go @@ -0,0 +1,87 @@ +package workflows + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/pgEdge/control-plane/server/internal/database" +) + +func TestResolveTargetSessionAttrs(t *testing.T) { + tests := []struct { + name string + spec *database.ServiceSpec + expected string + }{ + { + name: "explicit target_session_attrs overrides everything", + spec: &database.ServiceSpec{ + ServiceType: "mcp", + Config: map[string]any{"allow_writes": true}, + DatabaseConnection: &database.DatabaseConnection{ + TargetSessionAttrs: database.TargetSessionAttrsStandby, + }, + }, + expected: database.TargetSessionAttrsStandby, + }, + { + name: "MCP with allow_writes true returns primary", + spec: &database.ServiceSpec{ + ServiceType: "mcp", + Config: map[string]any{"allow_writes": true}, + }, + expected: database.TargetSessionAttrsPrimary, + }, + { + name: "MCP with allow_writes false returns prefer-standby", + spec: &database.ServiceSpec{ + ServiceType: "mcp", + Config: map[string]any{"allow_writes": false}, + }, + expected: database.TargetSessionAttrsPreferStandby, + }, + { + name: "MCP with no allow_writes in config returns prefer-standby", + spec: &database.ServiceSpec{ + ServiceType: "mcp", + Config: map[string]any{"other_key": "value"}, + }, + expected: database.TargetSessionAttrsPreferStandby, + }, + { + name: "MCP with nil config returns prefer-standby", + spec: &database.ServiceSpec{ + ServiceType: "mcp", + Config: nil, + }, + expected: database.TargetSessionAttrsPreferStandby, + }, + { + name: "unknown service type returns prefer-standby", + spec: &database.ServiceSpec{ + ServiceType: "unknown-type", + Config: map[string]any{"allow_writes": true}, + }, + expected: database.TargetSessionAttrsPreferStandby, + }, + { + name: "explicit override wins over MCP allow_writes=true", + spec: &database.ServiceSpec{ + ServiceType: "mcp", + Config: map[string]any{"allow_writes": true}, + DatabaseConnection: &database.DatabaseConnection{ + TargetSessionAttrs: database.TargetSessionAttrsAny, + }, + }, + expected: database.TargetSessionAttrsAny, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := resolveTargetSessionAttrs(tc.spec) + assert.Equal(t, tc.expected, got) + }) + } +}