Skip to content

Commit 95bf1e3

Browse files
authored
infra: Improve local development flow and avoid false positive infra diffs (#267)
1 parent 01a6ce5 commit 95bf1e3

File tree

4 files changed

+53
-56
lines changed

4 files changed

+53
-56
lines changed

deploy/Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@ build: ## Build the Pulumi Go program
1212
# Local stack commands
1313
local-login: ## Login to local Pulumi backend
1414
pulumi login --local
15+
PULUMI_CONFIG_PASSPHRASE="" pulumi stack select local --create
1516

1617
local-preview: build local-login ## Preview local infrastructure changes
1718
PULUMI_CONFIG_PASSPHRASE="" pulumi preview --stack local
1819

1920
local-up: build local-login ## Deploy local infrastructure
2021
PULUMI_CONFIG_PASSPHRASE="" pulumi up --yes --stack local
2122

23+
local-destroy: local-login ## Destroy local infrastructure
24+
pulumi stack rm local --force --yes --preserve-config
25+
echo "Make sure to also delete your k8s cluster, e.g. minikube delete"
26+
2227
# Staging stack commands
2328
staging-login: ## Login to staging Pulumi backend
2429
pulumi login gs://mcp-registry-staging-pulumi-state

deploy/README.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,22 @@ Pre-requisites:
1010
- [Pulumi CLI installed](https://www.pulumi.com/docs/iac/download-install/)
1111
- Access to a Kubernetes cluster via kubeconfig. You can run a cluster locally with [minikube](https://minikube.sigs.k8s.io/docs/start/).
1212

13-
1. Set Pulumi's backend to local: `pulumi login --local`
14-
2. Init the local stack: `pulumi stack init local` (fine to leave `password` blank)
15-
3. Set your config:
16-
```bash
17-
# General environment
18-
pulumi config set mcp-registry:environment local
19-
20-
# To use your local kubeconfig (default)
21-
pulumi config set mcp-registry:provider local
22-
23-
# GitHub OAuth
24-
pulumi config set mcp-registry:githubClientId <your-github-client-id>
25-
pulumi config set --secret mcp-registry:githubClientSecret <your-github-client-secret>
26-
```
27-
4. Deploy: `make local-up`
28-
5. Access the repository via the ingress load balancer. You can find its external IP with `kubectl get svc ingress-nginx-controller -n ingress-nginx` (with minikube, if it's 'pending' you might need `minikube tunnel`). Then run `curl -H "Host: local.registry.modelcontextprotocol.io" -k https://<EXTERNAL-IP>/v0/ping` to check that the service is up.
13+
1. Ensure your kubeconfig is configured at the cluster you want to use. For minikube, run `minikube start && minikube tunnel`.
14+
2. Run `make local-up` to deploy the stack. Run this again if the first attempt fails.
15+
3. Access the repository via the ingress load balancer. You can find its external IP with `kubectl get svc ingress-nginx-controller -n ingress-nginx`. Then run `curl -H "Host: local.registry.modelcontextprotocol.io" -k https://<EXTERNAL-IP>/v0/ping` to check that the service is up.
16+
17+
#### To change config
18+
19+
The stack is configured out of the box for local development. But if you want to make changes, run commands like:
20+
21+
```bash
22+
PULUMI_CONFIG_PASSPHRASE="" pulumi config set mcp-registry:environment local
23+
PULUMI_CONFIG_PASSPHRASE="" pulumi config set mcp-registry:githubClientSecret --secret <some-secret-value>
24+
```
25+
26+
#### To delete the stack
27+
28+
`make local-destroy` and deleting the cluster (with minikube: `minikube delete`) will reset you back to a clean state.
2929

3030
### Production Deployment (GCP)
3131

deploy/pkg/k8s/cert_manager.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
// SetupCertManager sets up cert-manager for TLS certificates
1515
func SetupCertManager(ctx *pulumi.Context, cluster *providers.ProviderInfo) error {
1616
// Create namespace for cert-manager
17-
_, err := v1.NewNamespace(ctx, "cert-manager", &v1.NamespaceArgs{
17+
certManagerNamespace, err := v1.NewNamespace(ctx, "cert-manager", &v1.NamespaceArgs{
1818
Metadata: &metav1.ObjectMetaArgs{
1919
Name: pulumi.String("cert-manager"),
2020
},
@@ -24,13 +24,13 @@ func SetupCertManager(ctx *pulumi.Context, cluster *providers.ProviderInfo) erro
2424
}
2525

2626
// Install cert-manager for TLS certificates
27-
_, err = helm.NewChart(ctx, "cert-manager", helm.ChartArgs{
27+
certManager, err := helm.NewChart(ctx, "cert-manager", helm.ChartArgs{
2828
Chart: pulumi.String("cert-manager"),
2929
Version: pulumi.String("v1.18.2"),
3030
FetchArgs: helm.FetchArgs{
3131
Repo: pulumi.String("https://charts.jetstack.io"),
3232
},
33-
Namespace: pulumi.String("cert-manager"),
33+
Namespace: certManagerNamespace.Metadata.Name().Elem(),
3434
Values: pulumi.Map{
3535
"installCRDs": pulumi.Bool(true),
3636
"ingressShim": pulumi.Map{
@@ -69,7 +69,7 @@ func SetupCertManager(ctx *pulumi.Context, cluster *providers.ProviderInfo) erro
6969
},
7070
},
7171
},
72-
}, pulumi.Provider(cluster.Provider))
72+
}, pulumi.Provider(cluster.Provider), pulumi.DependsOn([]pulumi.Resource{certManager}))
7373
if err != nil {
7474
return err
7575
}

deploy/pkg/k8s/ingress.go

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package k8s
22

33
import (
4+
"strings"
5+
46
v1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
57
"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/helm/v3"
68
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
@@ -19,7 +21,7 @@ func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo
1921
}
2022

2123
// Create namespace for ingress-nginx
22-
_, err := v1.NewNamespace(ctx, "ingress-nginx", &v1.NamespaceArgs{
24+
ingressNginxNamespace, err := v1.NewNamespace(ctx, "ingress-nginx", &v1.NamespaceArgs{
2325
Metadata: &metav1.ObjectMetaArgs{
2426
Name: pulumi.String("ingress-nginx"),
2527
},
@@ -29,22 +31,17 @@ func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo
2931
}
3032

3133
// Install NGINX Ingress Controller
32-
ingressType := "LoadBalancer"
33-
if provider == "local" {
34-
ingressType = "NodePort"
35-
}
36-
37-
nginxIngress, err := helm.NewChart(ctx, "ingress-nginx", helm.ChartArgs{
34+
ingressNginx, err := helm.NewChart(ctx, "ingress-nginx", helm.ChartArgs{
3835
Chart: pulumi.String("ingress-nginx"),
3936
Version: pulumi.String("4.13.0"),
4037
FetchArgs: helm.FetchArgs{
4138
Repo: pulumi.String("https://kubernetes.github.io/ingress-nginx"),
4239
},
43-
Namespace: pulumi.String("ingress-nginx"),
40+
Namespace: ingressNginxNamespace.Metadata.Name().Elem(),
4441
Values: pulumi.Map{
4542
"controller": pulumi.Map{
4643
"service": pulumi.Map{
47-
"type": pulumi.String(ingressType),
44+
"type": pulumi.String("LoadBalancer"),
4845
"annotations": pulumi.Map{
4946
// Add Azure Load Balancer health probe annotation as otherwise it defaults to / which fails
5047
"service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path": pulumi.String("/healthz"),
@@ -66,35 +63,30 @@ func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo
6663
return err
6764
}
6865

69-
// Use the helm chart to get service information after deployment
70-
ingressIps := nginxIngress.Resources.ApplyT(func(resources interface{}) interface{} {
71-
if ctx.DryRun() {
72-
return []string{} // Return empty array on error during preview
73-
}
74-
75-
// Look up the service after the chart is ready
76-
svc, err := v1.GetService(
77-
ctx,
78-
"ingress-nginx-controller-lookup",
79-
pulumi.ID("ingress-nginx/ingress-nginx-controller"),
80-
&v1.ServiceState{},
81-
pulumi.Provider(cluster.Provider),
82-
pulumi.DependsOn([]pulumi.Resource{nginxIngress}),
83-
)
84-
if err != nil {
85-
return []string{} // Return empty array on error during preview
86-
}
87-
88-
// Return the LoadBalancer ingress IPs
89-
return svc.Status.LoadBalancer().Ingress().ApplyT(func(ingresses []v1.LoadBalancerIngress) []string {
90-
var ips []string
91-
for _, ingress := range ingresses {
92-
if ip := ingress.Ip; ip != nil && *ip != "" {
93-
ips = append(ips, *ip)
66+
// Extract ingress IPs from the Helm chart's controller service
67+
ingressIps := ingressNginx.Resources.ApplyT(func(resources interface{}) interface{} {
68+
// Look for the ingress-nginx-controller service
69+
resourceMap := resources.(map[string]pulumi.Resource)
70+
for resourceName, resource := range resourceMap {
71+
if strings.Contains(resourceName, "ingress-nginx-controller") &&
72+
!strings.Contains(resourceName, "admission") &&
73+
strings.Contains(resourceName, "Service") {
74+
if svc, ok := resource.(*v1.Service); ok {
75+
// Return the LoadBalancer ingress IPs
76+
return svc.Status.LoadBalancer().Ingress().ApplyT(func(ingresses []v1.LoadBalancerIngress) []string {
77+
var ips []string
78+
for _, ingress := range ingresses {
79+
if ip := ingress.Ip; ip != nil && *ip != "" {
80+
ips = append(ips, *ip)
81+
}
82+
}
83+
return ips
84+
})
9485
}
9586
}
96-
return ips
97-
})
87+
}
88+
// Return empty array if no matching service found
89+
return []string{}
9890
})
9991
ctx.Export("ingressIps", ingressIps)
10092

0 commit comments

Comments
 (0)