Refactor database implementation to use PostgreSQL instead of MongoDB (#289)
Fixes #228 ## Motivation and Context See #19 ## How Has This Been Tested? - Run locally - Run in docker compose - Run infra in minikube - Unit and integration tests all passing :) ## Breaking Changes Yes: users will not be able to access any data they used to have in custom mongodb. We are accepting this breaking change, given the registry is in experimental development status. ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context I have not implemented connection pooling support for simplicity, and because cloudnative-pg already provides built-in connection pooling with pgBouncer so if we want it later we can switch to this endpoint easily.
This commit is contained in:
parent
ee910ca286
commit
ebaf4c9c99
6 changed files with 846 additions and 564 deletions
|
|
@ -69,7 +69,7 @@ func SetupCertManager(ctx *pulumi.Context, cluster *providers.ProviderInfo) erro
|
|||
},
|
||||
},
|
||||
},
|
||||
}, pulumi.Provider(cluster.Provider), pulumi.DependsOn([]pulumi.Resource{certManager}))
|
||||
}, pulumi.Provider(cluster.Provider), pulumi.DependsOnInputs(certManager.Ready))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,19 @@ func DeployAll(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment
|
|||
}
|
||||
|
||||
// Setup ingress controller
|
||||
err = SetupIngressController(ctx, cluster, environment)
|
||||
ingressNginx, err := SetupIngressController(ctx, cluster, environment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Deploy MongoDB
|
||||
err = DeployMongoDB(ctx, cluster, environment)
|
||||
// Deploy PostgreSQL databases
|
||||
pgCluster, err := DeployPostgresDatabases(ctx, cluster, environment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Deploy MCP Registry
|
||||
service, err = DeployMCPRegistry(ctx, cluster, environment)
|
||||
service, err = DeployMCPRegistry(ctx, cluster, environment, ingressNginx, pgCluster)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
// SetupIngressController sets up the NGINX Ingress Controller
|
||||
func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string) error {
|
||||
func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string) (*helm.Chart, error) {
|
||||
conf := config.New(ctx, "mcp-registry")
|
||||
provider := conf.Get("provider")
|
||||
if provider == "" {
|
||||
|
|
@ -27,7 +27,7 @@ func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo
|
|||
},
|
||||
}, pulumi.Provider(cluster.Provider))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Install NGINX Ingress Controller
|
||||
|
|
@ -60,7 +60,7 @@ func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo
|
|||
},
|
||||
}, pulumi.Provider(cluster.Provider))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract ingress IPs from the Helm chart's controller service
|
||||
|
|
@ -90,5 +90,5 @@ func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo
|
|||
})
|
||||
ctx.Export("ingressIps", ingressIps)
|
||||
|
||||
return nil
|
||||
return ingressNginx, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
|
||||
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
|
||||
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
|
||||
"github.com/modelcontextprotocol/registry/deploy/infra/pkg/providers"
|
||||
)
|
||||
|
||||
// DeployMongoDB deploys MongoDB to the Kubernetes cluster
|
||||
func DeployMongoDB(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string) error {
|
||||
// Create PersistentVolumeClaim for MongoDB data
|
||||
_, err := corev1.NewPersistentVolumeClaim(ctx, "mongodb-pvc", &corev1.PersistentVolumeClaimArgs{
|
||||
Metadata: &metav1.ObjectMetaArgs{
|
||||
Name: pulumi.String("mongodb-pvc"),
|
||||
Namespace: pulumi.String("default"),
|
||||
Labels: pulumi.StringMap{
|
||||
"app": pulumi.String("mongodb"),
|
||||
"environment": pulumi.String(environment),
|
||||
},
|
||||
},
|
||||
Spec: &corev1.PersistentVolumeClaimSpecArgs{
|
||||
AccessModes: pulumi.StringArray{
|
||||
pulumi.String("ReadWriteOnce"),
|
||||
},
|
||||
Resources: &corev1.VolumeResourceRequirementsArgs{
|
||||
Requests: pulumi.StringMap{
|
||||
"storage": pulumi.String("10Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, pulumi.Provider(cluster.Provider))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create MongoDB Deployment
|
||||
_, err = v1.NewDeployment(ctx, "mongodb", &v1.DeploymentArgs{
|
||||
Metadata: &metav1.ObjectMetaArgs{
|
||||
Name: pulumi.String("mongodb"),
|
||||
Namespace: pulumi.String("default"),
|
||||
Labels: pulumi.StringMap{
|
||||
"app": pulumi.String("mongodb"),
|
||||
"environment": pulumi.String(environment),
|
||||
},
|
||||
},
|
||||
Spec: &v1.DeploymentSpecArgs{
|
||||
Selector: &metav1.LabelSelectorArgs{
|
||||
MatchLabels: pulumi.StringMap{
|
||||
"app": pulumi.String("mongodb"),
|
||||
},
|
||||
},
|
||||
Template: &corev1.PodTemplateSpecArgs{
|
||||
Metadata: &metav1.ObjectMetaArgs{
|
||||
Labels: pulumi.StringMap{
|
||||
"app": pulumi.String("mongodb"),
|
||||
},
|
||||
},
|
||||
Spec: &corev1.PodSpecArgs{
|
||||
Containers: corev1.ContainerArray{
|
||||
&corev1.ContainerArgs{
|
||||
Name: pulumi.String("mongodb"),
|
||||
Image: pulumi.String("mongo:7.0"),
|
||||
Ports: corev1.ContainerPortArray{
|
||||
&corev1.ContainerPortArgs{
|
||||
ContainerPort: pulumi.Int(27017),
|
||||
Name: pulumi.String("mongodb"),
|
||||
},
|
||||
},
|
||||
VolumeMounts: corev1.VolumeMountArray{
|
||||
&corev1.VolumeMountArgs{
|
||||
Name: pulumi.String("mongodb-data"),
|
||||
MountPath: pulumi.String("/data/db"),
|
||||
},
|
||||
},
|
||||
LivenessProbe: &corev1.ProbeArgs{
|
||||
TcpSocket: &corev1.TCPSocketActionArgs{
|
||||
Port: pulumi.Int(27017),
|
||||
},
|
||||
InitialDelaySeconds: pulumi.Int(30),
|
||||
TimeoutSeconds: pulumi.Int(5),
|
||||
},
|
||||
ReadinessProbe: &corev1.ProbeArgs{
|
||||
TcpSocket: &corev1.TCPSocketActionArgs{
|
||||
Port: pulumi.Int(27017),
|
||||
},
|
||||
InitialDelaySeconds: pulumi.Int(5),
|
||||
TimeoutSeconds: pulumi.Int(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: corev1.VolumeArray{
|
||||
&corev1.VolumeArgs{
|
||||
Name: pulumi.String("mongodb-data"),
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSourceArgs{
|
||||
ClaimName: pulumi.String("mongodb-pvc"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, pulumi.Provider(cluster.Provider))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create MongoDB Service
|
||||
_, err = corev1.NewService(ctx, "mongodb", &corev1.ServiceArgs{
|
||||
Metadata: &metav1.ObjectMetaArgs{
|
||||
Name: pulumi.String("mongodb"),
|
||||
Namespace: pulumi.String("default"),
|
||||
Labels: pulumi.StringMap{
|
||||
"app": pulumi.String("mongodb"),
|
||||
"environment": pulumi.String(environment),
|
||||
},
|
||||
},
|
||||
Spec: &corev1.ServiceSpecArgs{
|
||||
Selector: pulumi.StringMap{
|
||||
"app": pulumi.String("mongodb"),
|
||||
},
|
||||
Ports: corev1.ServicePortArray{
|
||||
&corev1.ServicePortArgs{
|
||||
Port: pulumi.Int(27017),
|
||||
TargetPort: pulumi.Int(27017),
|
||||
Name: pulumi.String("mongodb"),
|
||||
},
|
||||
},
|
||||
Type: pulumi.String("ClusterIP"),
|
||||
},
|
||||
}, pulumi.Provider(cluster.Provider))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create MongoDB service: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
70
deploy/pkg/k8s/postgres.go
Normal file
70
deploy/pkg/k8s/postgres.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apiextensions"
|
||||
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
|
||||
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/helm/v3"
|
||||
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
|
||||
"github.com/modelcontextprotocol/registry/deploy/infra/pkg/providers"
|
||||
)
|
||||
|
||||
// DeployPostgresDatabases deploys the CloudNative PostgreSQL operator and PostgreSQL cluster
|
||||
func DeployPostgresDatabases(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string) (*apiextensions.CustomResource, error) {
|
||||
// Create cnpg-system namespace
|
||||
cnpgNamespace, err := corev1.NewNamespace(ctx, "cnpg-system", &corev1.NamespaceArgs{
|
||||
Metadata: &metav1.ObjectMetaArgs{
|
||||
Name: pulumi.String("cnpg-system"),
|
||||
},
|
||||
}, pulumi.Provider(cluster.Provider))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Install cloudnative-pg Helm chart
|
||||
cloudNativePG, err := helm.NewChart(ctx, "cloudnative-pg", helm.ChartArgs{
|
||||
Chart: pulumi.String("cloudnative-pg"),
|
||||
Version: pulumi.String("v0.26.0"),
|
||||
FetchArgs: helm.FetchArgs{
|
||||
Repo: pulumi.String("https://cloudnative-pg.github.io/charts"),
|
||||
},
|
||||
Namespace: cnpgNamespace.Metadata.Name().Elem(),
|
||||
Values: pulumi.Map{
|
||||
"webhooks": pulumi.Map{
|
||||
"replicaCount": pulumi.Int(1),
|
||||
},
|
||||
},
|
||||
}, pulumi.Provider(cluster.Provider))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create PostgreSQL cluster with proper timeout handling
|
||||
// Note: This may fail on first run until CloudNativePG operator is fully ready
|
||||
pgCluster, err := apiextensions.NewCustomResource(ctx, "registry-pg", &apiextensions.CustomResourceArgs{
|
||||
ApiVersion: pulumi.String("postgresql.cnpg.io/v1"),
|
||||
Kind: pulumi.String("Cluster"),
|
||||
Metadata: &metav1.ObjectMetaArgs{
|
||||
Name: pulumi.String("registry-pg"),
|
||||
Namespace: pulumi.String("default"),
|
||||
Labels: pulumi.StringMap{
|
||||
"app": pulumi.String("registry-pg"),
|
||||
"environment": pulumi.String(environment),
|
||||
},
|
||||
},
|
||||
OtherFields: map[string]any{
|
||||
"spec": map[string]any{
|
||||
"instances": 1,
|
||||
"storage": map[string]any{
|
||||
"size": "50Gi",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, pulumi.Provider(cluster.Provider), pulumi.DependsOnInputs(cloudNativePG.Ready))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pgCluster, nil
|
||||
}
|
||||
|
|
@ -4,8 +4,10 @@ import (
|
|||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apiextensions"
|
||||
v1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
|
||||
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
|
||||
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/helm/v3"
|
||||
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
|
||||
networkingv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/networking/v1"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
|
|
@ -26,7 +28,7 @@ func getGitCommitHash() string {
|
|||
}
|
||||
|
||||
// DeployMCPRegistry deploys the MCP Registry to the Kubernetes cluster
|
||||
func DeployMCPRegistry(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string) (*corev1.Service, error) {
|
||||
func DeployMCPRegistry(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string, ingressNginx *helm.Chart, pgCluster *apiextensions.CustomResource) (*corev1.Service, error) {
|
||||
conf := config.New(ctx, "mcp-registry")
|
||||
githubClientId := conf.Require("githubClientId")
|
||||
|
||||
|
|
@ -91,8 +93,18 @@ func DeployMCPRegistry(ctx *pulumi.Context, cluster *providers.ProviderInfo, env
|
|||
},
|
||||
Env: corev1.EnvVarArray{
|
||||
&corev1.EnvVarArgs{
|
||||
Name: pulumi.String("MCP_REGISTRY_DATABASE_URL"),
|
||||
Value: pulumi.String("mongodb://mongodb.default.svc.cluster.local:27017"),
|
||||
Name: pulumi.String("MCP_REGISTRY_DATABASE_URL"),
|
||||
ValueFrom: &corev1.EnvVarSourceArgs{
|
||||
SecretKeyRef: &corev1.SecretKeySelectorArgs{
|
||||
Name: pgCluster.Metadata.Name().ApplyT(func(name *string) string {
|
||||
if name == nil {
|
||||
return "registry-pg-app"
|
||||
}
|
||||
return *name + "-app"
|
||||
}).(pulumi.StringOutput),
|
||||
Key: pulumi.String("uri"),
|
||||
},
|
||||
},
|
||||
},
|
||||
&corev1.EnvVarArgs{
|
||||
Name: pulumi.String("MCP_REGISTRY_GITHUB_CLIENT_ID"),
|
||||
|
|
@ -227,7 +239,7 @@ func DeployMCPRegistry(ctx *pulumi.Context, cluster *providers.ProviderInfo, env
|
|||
return rules
|
||||
}).(networkingv1.IngressRuleArrayOutput),
|
||||
},
|
||||
}, pulumi.Provider(cluster.Provider))
|
||||
}, pulumi.Provider(cluster.Provider), pulumi.DependsOnInputs(ingressNginx.Ready))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue