diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d91af28..e56f7bc 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -134,9 +134,289 @@ jobs: ./kill-minikube.sh --help || exit 1 ./setup-minikube.sh --help || exit 1 ./setup-env.sh --help || exit 1 + ./test-router.sh --help || exit 1 echo "✅ All script help options working" + - name: Test new test utilities + run: | + # Test that test utilities can be sourced without errors + echo "Testing test utilities..." + + # Source test utilities and test basic functions + source scripts/test-utils.sh + + # Test that functions are available + if ! type test_router_health > /dev/null 2>&1; then + echo "❌ test_router_health function not found" + exit 1 + fi + + if ! type test_search_products > /dev/null 2>&1; then + echo "❌ test_search_products function not found" + exit 1 + fi + + if ! type test_router_comprehensive > /dev/null 2>&1; then + echo "❌ test_router_comprehensive function not found" + exit 1 + fi + + echo "✅ Test utilities functions available" + + # Test that test utilities can be run directly + echo "Testing test utilities execution..." + + # Test that the script can be run with --help (should show usage) + if ! bash scripts/test-utils.sh --help 2>&1 | \ + grep -q "Available tests"; then + echo "❌ test-utils.sh --help not working correctly" + exit 1 + fi + + echo "✅ Test utilities execution working" + + - name: Test script organization + run: | + echo "🔍 Dynamically discovering scripts..." + + # Discover user-facing scripts in root directory + echo "📁 Checking user-facing scripts in root directory..." + user_scripts=($(find . -maxdepth 1 -name "*.sh" -type f \ + -exec basename {} \; | grep -v "^\.$")) + + if [ ${#user_scripts[@]} -eq 0 ]; then + echo "❌ No user-facing scripts found in root directory" + exit 1 + fi + + echo "✅ Found ${#user_scripts[@]} user-facing scripts:" + for script in "${user_scripts[@]}"; do + echo " - $script" + done + + # Discover internal scripts in scripts directory + echo "📁 Checking internal scripts in scripts directory..." + if [ ! -d "scripts" ]; then + echo "❌ scripts directory not found" + exit 1 + fi + + internal_scripts=($(find scripts -name "*.sh" -type f \ + -exec basename {} \;)) + + if [ ${#internal_scripts[@]} -eq 0 ]; then + echo "❌ No internal scripts found in scripts directory" + exit 1 + fi + + echo "✅ Found ${#internal_scripts[@]} internal scripts:" + for script in "${internal_scripts[@]}"; do + echo " - scripts/$script" + done + + echo "✅ Script organization is correct" + + # Test that scripts are using test utilities (no duplication) + echo "Testing for script duplication..." + + # Check all scripts for hardcoded curl commands that should use test + # utilities + echo "🔍 Checking for hardcoded curl commands in scripts..." + + # Check user-facing scripts + for script in "${user_scripts[@]}"; do + if grep -q "curl.*localhost:4000" "$script"; then + echo "❌ $script contains hardcoded curl commands for router \ + testing" + echo " Should use test utilities instead" + exit 1 + fi + done + + # Check internal scripts (but allow some curl usage in test-utils.sh + # itself) + for script in "${internal_scripts[@]}"; do + if [ "$script" != "test-utils.sh" ] && \ + grep -q "curl.*localhost:4000.*health" "scripts/$script"; then + echo "❌ scripts/$script contains hardcoded curl commands for \ + health checks" + echo " Should use test utilities instead" + exit 1 + fi + done + + echo "✅ No duplication found - scripts using test utilities \ + correctly" + + # Test that test utilities contain expected functions + echo "Testing test utilities content..." + + # Check that test-utils.sh exists + if [ ! -f "scripts/test-utils.sh" ]; then + echo "❌ test-utils.sh not found in scripts directory" + exit 1 + fi + + # Discover test functions dynamically + echo "🔍 Discovering test functions in test-utils.sh..." + test_functions=($(grep -E "^test_[a-zA-Z_]+\(\)" \ + scripts/test-utils.sh | sed 's/() {.*//' | sort)) + + if [ ${#test_functions[@]} -eq 0 ]; then + echo "❌ No test functions found in test-utils.sh" + exit 1 + fi + + echo "✅ Found ${#test_functions[@]} test functions:" + for func in "${test_functions[@]}"; do + echo " - $func" + done + + # Check for essential test functions + essential_functions=("test_router_health" "test_search_products") + for func in "${essential_functions[@]}"; do + if [[ ! " ${test_functions[@]} " =~ " ${func} " ]]; then + echo "❌ Essential test function not found: $func" + exit 1 + fi + done + + echo "✅ Test utilities contain essential functions" + + # Test that test-router.sh works correctly + echo "Testing test-router.sh functionality..." + + # Check that test-router.sh exists + if [ ! -f "test-router.sh" ]; then + echo "❌ test-router.sh not found in root directory" + exit 1 + fi + + # Test that it can show help + if ! ./test-router.sh --help 2>&1 | grep -q "Test Names:"; then + echo "❌ test-router.sh --help not working correctly" + exit 1 + fi + + # Discover available tests dynamically + echo "🔍 Discovering available tests in test-router.sh..." + available_tests=($(./test-router.sh --help 2>&1 | \ + grep -A 20 "Test Names:" | grep -E "^ [a-zA-Z-]+" | \ + sed 's/^ //' | sed 's/ .*//' | tr '\n' ' ')) + + if [ ${#available_tests[@]} -eq 0 ]; then + echo "❌ No tests found in test-router.sh help output" + exit 1 + fi + + echo "✅ Found ${#available_tests[@]} available tests:" + for test in "${available_tests[@]}"; do + echo " - $test" + done + + # Check for essential tests + essential_tests=("health" "products" "status" "all") + for test in "${essential_tests[@]}"; do + if [[ ! " ${available_tests[@]} " =~ " ${test} " ]]; then + echo "❌ Essential test not found: $test" + exit 1 + fi + done + + echo "✅ test-router.sh functionality working correctly" + + # Test that build validation script exists and has help + echo "Testing build validation script..." + + # Check if build-validate.sh exists + if [ ! -f "scripts/build-validate.sh" ]; then + echo "❌ build-validate.sh not found in scripts directory" + exit 1 + fi + + # Test that it has help functionality + if ! ./scripts/build-validate.sh --help 2>&1 | grep -q "Usage:"; then + echo "❌ build-validate.sh --help not working correctly" + exit 1 + fi + + echo "✅ build-validate.sh exists and has help functionality" + + # Test all scripts that have --help functionality + echo "Testing script help functionality..." + echo "🔍 Checking which scripts support --help..." + + help_scripts=() + for script in "${user_scripts[@]}"; do + if ./"$script" --help 2>&1 | grep -q "Usage:\|Options:"; then + help_scripts+=("$script") + fi + done + + for script in "${internal_scripts[@]}"; do + if ./"scripts/$script" --help 2>&1 | \ + grep -q "Usage:\|Options:"; then + help_scripts+=("scripts/$script") + fi + done + + if [ ${#help_scripts[@]} -gt 0 ]; then + echo "✅ Found ${#help_scripts[@]} scripts with help \ + functionality:" + for script in "${help_scripts[@]}"; do + echo " - $script" + done + else + echo "⚠️ No scripts found with help functionality" + fi + + # Test that documentation structure is correct + echo "Testing documentation structure..." + + # Check that README.md exists and points to SETUP.md + if ! grep -q "SETUP.md" README.md; then + echo "❌ README.md missing reference to SETUP.md" + exit 1 + fi + + # Check that SETUP.md exists and contains commands + if [ ! -f "SETUP.md" ]; then + echo "❌ SETUP.md not found" + exit 1 + fi + + # Check that ARCHITECTURE.md exists + if [ ! -f "ARCHITECTURE.md" ]; then + echo "❌ ARCHITECTURE.md not found" + exit 1 + fi + + # Check that README-K8S.md is deleted + if [ -f "README-K8S.md" ]; then + echo "❌ README-K8S.md still exists (should be deleted)" + exit 1 + fi + + echo "✅ Documentation structure is correct" + + # Test that cleanup-k8s.sh provides helpful output + echo "Testing cleanup-k8s.sh output..." + + # Test that it mentions minikube is still running + if ! ./cleanup-k8s.sh 2>&1 | grep -q "Minikube is still running"; then + echo "❌ cleanup-k8s.sh missing minikube warning" + exit 1 + fi + + # Test that it mentions kill-minikube.sh + if ! ./cleanup-k8s.sh 2>&1 | grep -q "kill-minikube.sh"; then + echo "❌ cleanup-k8s.sh missing kill-minikube.sh reference" + exit 1 + fi + + echo "✅ cleanup-k8s.sh provides helpful output" + test-k8s-yaml: name: Test Kubernetes YAML Format runs-on: ubuntu-latest diff --git a/AI_INSTRUCTIONS.md b/AI_INSTRUCTIONS.md index cc77830..acab2b0 100644 --- a/AI_INSTRUCTIONS.md +++ b/AI_INSTRUCTIONS.md @@ -103,7 +103,7 @@ fi 3. **Transform URLs during deployment** (sed replacement to Kubernetes service URLs) 4. **Never commit Kubernetes URLs** to the supergraph.graphql file -### Example of Correct URL Transformation in deploy.sh +### Example of Correct URL Transformation in run-k8s.sh ```bash # Generate supergraph with localhost URLs first @@ -138,7 +138,7 @@ sed 's|http://localhost:4001|http://subgraphs-service.apollo-supergraph.svc.clus - Faster startup, easier debugging - No container overhead -2. **Kubernetes Deployment** (`deploy.sh`): +2. **Kubernetes Deployment** (`run-k8s.sh`): - Runs WITH minikube Kubernetes - Everything containerized - Production-like environment @@ -153,7 +153,7 @@ sed 's|http://localhost:4001|http://subgraphs-service.apollo-supergraph.svc.clus **Each script has a specific purpose - don't confuse them:** - `run-local.sh` - Local development WITHOUT Kubernetes -- `deploy.sh` - Kubernetes deployment WITH minikube +- `run-k8s.sh` - Kubernetes deployment WITH minikube - `setup-minikube.sh` - Setup minikube cluster - `kill-minikube.sh` - Stop and delete minikube cluster - `cleanup-k8s.sh` - Clean up Kubernetes resources @@ -178,7 +178,7 @@ sed 's|http://localhost:4001|http://subgraphs-service.apollo-supergraph.svc.clus ### Recommended Development Process 1. **Start with local development** (`run-local.sh`) for faster iteration -2. **Use Kubernetes deployment** (`deploy.sh`) for testing production-like environments +2. **Use Kubernetes deployment** (`run-k8s.sh`) for testing production-like environments 3. **Keep configurations in router folder** as source of truth 4. **Generate supergraph** with `./compose.sh` before deployments 5. **Transform URLs** during deployment (localhost → Kubernetes service URLs) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..1ad86ab --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,277 @@ +# Apollo Supergraph - Architecture Guide + +Technical details and architecture explanations for the Apollo Supergraph project. + +## 🏗️ Architecture Overview + +This project demonstrates an Apollo Supergraph with: + +- **Apollo Router** - GraphQL gateway that routes requests to subgraphs +- **Subgraphs** - Three GraphQL services (Products, Reviews, Users) in a monolithic Node.js application +- **Multiple deployment modes** - Local development and Kubernetes deployment + +## 🔄 Deployment Modes + +### Local Development Architecture + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Apollo Router │ │ Subgraphs │ │ Development │ +│ (Docker) │◄──►│ (Node.js) │ │ Environment │ +│ Port 4000 │ │ Port 4001 │ │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +**Components:** +- **Apollo Router**: Runs in Docker container on port 4000 +- **Subgraphs**: Runs as Node.js process on port 4001 +- **Communication**: Direct localhost connections +- **Supergraph**: Generated with localhost URLs + +**Benefits:** +- ✅ Fast startup and development cycle +- ✅ Direct access to logs and debugging +- ✅ No resource constraints +- ✅ Simple cleanup (Ctrl+C) + +### Kubernetes Deployment Architecture + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Ingress │ │ Apollo Router │ │ Subgraphs │ +│ Controller │◄──►│ (K8s Pods) │◄──►│ (K8s Pods) │ +│ Port 80 │ │ Port 4000 │ │ Port 4001 │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +**Components:** +- **Ingress Controller**: Nginx ingress for external access +- **Apollo Router**: Runs in Kubernetes pods on port 4000 +- **Subgraphs**: Runs in Kubernetes pods on port 4001 +- **Services**: ClusterIP services for internal communication +- **Supergraph**: Generated with Kubernetes service URLs + +**Benefits:** +- ✅ Production-like environment +- ✅ Scalable and resilient +- ✅ Resource management +- ✅ Service discovery + +## 📁 Project Structure + +``` +example-local-kubernetes-supergraph/ +├── router/ # Apollo Router configuration +│ ├── router.yaml # Router configuration +│ ├── supergraph.yaml # Supergraph composition config +│ ├── supergraph.graphql # Generated supergraph schema +│ ├── compose.sh # Generate supergraph schema +│ └── .env # Apollo Studio credentials +├── subgraphs/ # GraphQL subgraphs +│ ├── products/ # Products subgraph +│ │ ├── schema.graphql # GraphQL schema +│ │ ├── resolvers.js # Resolver functions +│ │ └── data.js # Mock data +│ ├── reviews/ # Reviews subgraph +│ ├── users/ # Users subgraph +│ ├── subgraphs.js # Subgraph registration +│ ├── index.js # Main application +│ ├── package.json # Dependencies +│ └── Dockerfile # Container image +├── k8s/ # Kubernetes manifests +│ ├── namespace.yaml # Kubernetes namespace +│ ├── subgraphs-deployment-clusterip.yaml # Subgraphs deployment +│ ├── router-deployment-clusterip.yaml # Router deployment +│ └── ingress.yaml # Ingress configuration +├── scripts/ # Internal utilities +│ ├── utils.sh # Shared utilities +│ ├── test-utils.sh # Test operations +│ ├── port-forward-utils.sh # Centralized port forwarding management +│ └── build-validate.sh # Build validation +├── run-local.sh # Local development +├── run-k8s.sh # Kubernetes deployment +├── test-router.sh # Router testing +├── test-k8s.sh # Deployment validation +├── setup-minikube.sh # Minikube setup +├── setup-env.sh # Environment setup +├── cleanup-k8s.sh # Kubernetes cleanup +└── kill-minikube.sh # Minikube cleanup +``` + +## 🔧 Technical Details + +### Supergraph Composition + +The supergraph is composed using Apollo Rover: + +```bash +# Generate supergraph from subgraph schemas +cd router +rover supergraph compose --config supergraph.yaml +``` + +**supergraph.yaml:** +```yaml +federation_version: =2.5.0 +subgraphs: + products: + routing_url: http://localhost:4001/products/graphql + schema: + subgraph_url: http://localhost:4001/products/graphql + reviews: + routing_url: http://localhost:4001/reviews/graphql + schema: + subgraph_url: http://localhost:4001/reviews/graphql + users: + routing_url: http://localhost:4001/users/graphql + schema: + subgraph_url: http://localhost:4001/users/graphql +``` + +### URL Transformation + +For Kubernetes deployment, localhost URLs are transformed to Kubernetes service URLs: + +**Local Development:** +``` +http://localhost:4001/products/graphql +``` + +**Kubernetes Deployment:** +``` +http://subgraphs-service.apollo-supergraph.svc.cluster.local:4001/products/graphql +``` + +### Kubernetes Services + +**Subgraphs Service:** +```yaml +apiVersion: v1 +kind: Service +metadata: + name: subgraphs-service + namespace: apollo-supergraph +spec: + selector: + app: subgraphs + ports: + - port: 4001 + targetPort: 4001 + type: ClusterIP +``` + +**Router Service:** +```yaml +apiVersion: v1 +kind: Service +metadata: + name: apollo-router-service + namespace: apollo-supergraph +spec: + selector: + app: apollo-router + ports: + - name: graphql + port: 4000 + targetPort: 4000 + - name: health + port: 8088 + targetPort: 8088 + type: ClusterIP +``` + +## 🔍 Debugging Commands + +### Local Development + +```bash +# View subgraphs logs +cd subgraphs && npm start + +# View router logs +docker logs + +# Test subgraphs directly +curl -X POST http://localhost:4001/products/graphql \ + -H "Content-Type: application/json" \ + -d '{"query":"{ searchProducts { id title } }"}' + +# Test router +curl -X POST http://localhost:4000/graphql \ + -H "Content-Type: application/json" \ + -d '{"query":"{ searchProducts { id title } }"}' +``` + +### Kubernetes Deployment + +```bash +# View all resources +kubectl get all -n apollo-supergraph + +# View pods +kubectl get pods -n apollo-supergraph + +# View services +kubectl get svc -n apollo-supergraph + +# View router logs +kubectl logs -f deployment/apollo-router -n apollo-supergraph + +# View subgraphs logs +kubectl logs -f deployment/subgraphs -n apollo-supergraph + +# Describe pod for troubleshooting +kubectl describe pod -n apollo-supergraph + +# Port forward for direct access +kubectl port-forward svc/apollo-router-service 4000:4000 -n apollo-supergraph +kubectl port-forward svc/subgraphs-service 4001:4001 -n apollo-supergraph + +# Check ingress +kubectl get ingress -n apollo-supergraph +kubectl describe ingress apollo-router-ingress -n apollo-supergraph +``` + +### Network Connectivity + +```bash +# Test service connectivity +kubectl run test-pod --image=busybox --rm -it --restart=Never -n apollo-supergraph -- \ + wget -qO- http://subgraphs-service:4001/products/graphql + +# Check DNS resolution +kubectl run test-pod --image=busybox --rm -it --restart=Never -n apollo-supergraph -- \ + nslookup subgraphs-service.apollo-supergraph.svc.cluster.local +``` + +## 🔐 Security Considerations + +### Apollo Studio Credentials + +- **Environment Variables**: Stored in `router/.env` +- **Kubernetes**: Passed as environment variables (not secrets for demo) +- **Production**: Use Kubernetes Secrets for sensitive data + +### Network Security + +- **Local Development**: Direct localhost access +- **Kubernetes**: ClusterIP services (internal only) +- **External Access**: Via Ingress controller + +## 📊 Performance Considerations + +### Resource Limits + +**Default Limits:** +- **Router**: 256MB RAM, 200m CPU +- **Subgraphs**: 256MB RAM, 200m CPU + +**Scaling:** +- **Horizontal**: Multiple replicas via `--replicas` flag +- **Vertical**: Adjust resource limits in deployment files + +### Optimization + +- **Supergraph Caching**: Router caches composed supergraph +- **Connection Pooling**: Router manages connections to subgraphs +- **Load Balancing**: Kubernetes services distribute load across pods diff --git a/README-K8S.md b/README-K8S.md deleted file mode 100644 index ab44881..0000000 --- a/README-K8S.md +++ /dev/null @@ -1,220 +0,0 @@ -# Apollo Supergraph - Kubernetes Deployment Guide - -This guide provides detailed instructions for deploying the Apollo Supergraph (Router + Subgraphs) to a local minikube Kubernetes cluster. - -**For general information and local development, see the main [README.md](README.md).** -**For detailed setup and configuration instructions, see [SETUP.md](SETUP.md).** - -## Architecture Overview - -The Kubernetes deployment consists of: - -1. **Apollo Router** - GraphQL gateway that routes requests to subgraphs -2. **Subgraphs** - Monolithic Node.js application containing three GraphQL subgraphs: - - Products - - Reviews - - Users -3. **Ingress** - Nginx ingress controller for external access -4. **Services** - Kubernetes services for internal communication - -## Quick Start - -### Setup minikube - -```bash -# Setup minikube with required addons -./setup-minikube.sh -``` - -### Deploy the applications - -Deploy the Apollo Supergraph (router + subgraphs): - -```bash -# Deploy Apollo Supergraph -./run-k8s.sh - -# Deploy with custom number of replicas -./run-k8s.sh --replicas 3 - -# Show help -./run-k8s.sh --help -``` - -### Access the applications - -After deployment, you have several options to access the Apollo Router: - -**Option 1: Minikube Tunnel (Recommended)** -```bash -# Start minikube tunnel (keep this running in a terminal) -minikube tunnel - -# Access via minikube IP -minikube ip # Get the IP (e.g., 192.168.49.2) -# Then access: http://192.168.49.2:4000/graphql -``` - -**Option 2: Ingress with /etc/hosts** -```bash -# Get minikube IP -minikube ip - -# Add to /etc/hosts (replace with actual IP) -echo "$(minikube ip) apollo-router.local" | sudo tee -a /etc/hosts -``` - -Then access: -- **Apollo Router**: http://apollo-router.local -- **Health Check**: http://apollo-router.local:8088 - -**Option 3: Port Forwarding** -```bash -# Forward router to localhost -kubectl port-forward svc/apollo-router-service 4000:4000 -n apollo-supergraph - -# Then access: http://localhost:4000/graphql -``` - -**Note**: If using minikube tunnel, keep the terminal window open while accessing the services. - -## Deployment Configuration - -### Apollo Supergraph Deployment -- **Namespace**: `apollo-supergraph` -- **Components**: Router + Subgraphs + Ingress -- **Service Type**: ClusterIP (internal communication) -- **Replicas**: 2 each (configurable) -- **Access**: Via Ingress (apollo-router.local) or port forwarding - -## Security Best Practices - -- **Never commit secrets to version control**: The `.env` file is already in `.gitignore` -- **Use Kubernetes Secrets for production**: For production deployments, use Kubernetes Secrets instead of environment variables -- **Rotate API keys regularly**: Keep your Apollo Studio API keys secure and rotate them periodically -- **Limit access**: Only grant necessary permissions to your Apollo Studio API keys - -## Testing - -### Kubernetes Testing - -```bash -# Test Apollo Supergraph deployment -./test-k8s.sh - -# Show help -./test-k8s.sh --help -``` - -## Cleanup - -```bash -# Clean up deployment -./cleanup-k8s.sh - -# Stop and delete minikube cluster -./kill-minikube.sh -``` - -## Troubleshooting - -### Common Issues - -1. **Apollo Router fails to start with "no valid license" error**: The router requires valid Apollo Studio credentials. You have two options: - - **Option A: Use valid Apollo Studio credentials** - ```bash - # Edit router/.env with your actual credentials - APOLLO_GRAPH_REF=your-actual-graph-name@your-variant - APOLLO_KEY=service:your-actual-graph-name:your-actual-api-key - ``` - - **Option B: Run subgraphs locally first** - ```bash - # Start subgraphs locally - cd subgraphs && npm start - - # Then deploy router (configured to connect to localhost:4001) - ./run-k8s.sh - ``` - -2. **NodePort services not accessible**: Start minikube tunnel for NodePort access: - ```bash - minikube tunnel - ``` - -3. **Image pull errors**: Make sure you're using minikube's Docker daemon: - ```bash - eval $(minikube docker-env) - ``` - -4. **Ingress not working**: Check if ingress controller is running: - ```bash - kubectl get pods -n ingress-nginx - ``` - -5. **Service connectivity**: Check if services are properly configured: - ```bash - kubectl get svc -n - kubectl describe svc -n - ``` - -6. **Pod startup issues**: Check pod events and logs: - ```bash - kubectl describe pod -n - kubectl logs -n - ``` - -7. **Router not connecting to subgraphs**: Make sure subgraphs are running: - ```bash - # Check if subgraphs are running - kubectl get pods -n apollo-supergraph - - # If not running, redeploy - ./run-k8s.sh - ``` - -### Resource Requirements - -The deployment requires: -- **minikube**: 4GB RAM, 2 CPUs, 20GB disk -- **Applications**: 256MB RAM, 200m CPU per pod - -### Performance Tuning - -For better performance: -- Increase resource limits in deployment files -- Add horizontal pod autoscalers -- Configure pod disruption budgets -- Use persistent volumes for logs - -## File Structure - -``` -k8s/ -├── namespace.yaml # Kubernetes namespace -├── configmaps.yaml # Router config and supergraph schema -├── subgraphs-deployment.yaml # Subgraphs deployment and service -├── router-deployment.yaml # Apollo Router deployment and service -└── ingress.yaml # Ingress configuration - -run-k8s.sh # Kubernetes deployment script -cleanup-k8s.sh # Kubernetes cleanup script -test-k8s.sh # Kubernetes test script -setup-minikube.sh # Minikube setup script -subgraphs/Dockerfile # Subgraphs Docker image -``` - -## Script Options Summary - -### run-k8s.sh -- `./run-k8s.sh` - Deploy Apollo Supergraph (router + subgraphs) -- `./run-k8s.sh --replicas N` - Deploy with N replicas -- `./run-k8s.sh --help` - Show help - -### cleanup-k8s.sh -- `./cleanup-k8s.sh` - Clean up Apollo Supergraph namespace - -### test-k8s.sh -- `./test-k8s.sh` - Test Apollo Supergraph deployment -- `./test-k8s.sh --help` - Show help diff --git a/README.md b/README.md index f681d82..3e11da0 100644 --- a/README.md +++ b/README.md @@ -2,108 +2,42 @@ A complete example of deploying an Apollo Supergraph (Router + Subgraphs) for both local development and Kubernetes deployment. -## 🚀 Quick Start +## 🚀 What is this? -### Option 1: Local Development (Recommended for Development) +This project demonstrates how to set up and deploy an Apollo Supergraph with: -Run the Apollo Supergraph locally without Kubernetes: +- **Apollo Router** - GraphQL gateway that routes requests to subgraphs +- **Subgraphs** - Three GraphQL services (Products, Reviews, Users) in a monolithic Node.js application +- **Multiple deployment options** - Local development and Kubernetes deployment -```bash -# Run both subgraphs and router -./run-local.sh +## 🎯 Quick Start +**📖 [SETUP.md](SETUP.md) contains the complete guide to get this running.** -# Show help -./run-local.sh --help -``` +The setup guide includes: +- ✅ Prerequisites and installation +- ✅ Local development setup +- ✅ Kubernetes deployment +- ✅ Testing and validation +- ✅ Troubleshooting -**Benefits of local development:** -- ✅ **Faster startup** - No Kubernetes overhead -- ✅ **Easier debugging** - Direct access to logs -- ✅ **No resource constraints** - Runs directly on your machine -- ✅ **Simple cleanup** - Just Ctrl+C to stop +## 🏗️ Architecture -### Option 2: Kubernetes Deployment (Recommended for Testing Production-like Environment) - -#### Setup minikube - -```bash -# Setup minikube with required addons -./setup-minikube.sh -``` - -#### Deploy the applications - -```bash -# Deploy Apollo Supergraph with 2 replicas (default) -./run-k8s.sh - -# Deploy with custom number of replicas -./run-k8s.sh --replicas 3 - -# Show help -./run-k8s.sh --help -``` - -## 📋 Prerequisites - -- [Node.js](https://nodejs.org/) (for subgraphs) -- [Docker](https://docs.docker.com/get-docker/) (for Kubernetes deployment) -- [minikube](https://minikube.sigs.k8s.io/docs/start/) (for Kubernetes deployment) -- [kubectl](https://kubernetes.io/docs/tasks/tools/) (for Kubernetes deployment) - -## 🔧 Configuration - -Set up your Apollo Studio environment: - -```bash -# Set up Apollo Studio credentials (safe - won't overwrite existing) -./setup-env.sh -``` - -## 🧪 Testing - -### Local Testing - -```bash -# Run all local tests -./test-local.sh - -# Test specific components -./test-local.sh --subgraphs # Test subgraphs only -./test-local.sh --composition # Test supergraph composition only -./test-local.sh --docker # Test Docker builds only -./test-local.sh --router # Test router only -``` - -### Kubernetes Testing - -```bash -# Test Apollo Supergraph deployment -./test-k8s.sh -``` - -## 🧹 Cleanup +This project supports two deployment modes: ### Local Development -```bash -# Stop all services -Ctrl+C (in the terminal running run-local.sh) -``` +- Subgraphs run as Node.js processes +- Router runs in Docker +- Direct localhost access +- Fast development cycle ### Kubernetes Deployment -```bash -# Clean up deployment -./cleanup-k8s.sh - -# Stop and delete minikube cluster -./kill-minikube.sh -``` - -## 📚 Documentation +- Subgraphs run in Kubernetes pods +- Router runs in Kubernetes pods +- Production-like environment +- Scalable and resilient -- **[SETUP.md](SETUP.md)** - Detailed setup and configuration instructions -- **[README-K8S.md](README-K8S.md)** - Kubernetes-specific deployment details +**📋 [ARCHITECTURE.md](ARCHITECTURE.md) explains the technical details and differences.** ## 🔗 Links diff --git a/SETUP.md b/SETUP.md index 1294ed4..dcf5f1e 100644 --- a/SETUP.md +++ b/SETUP.md @@ -1,196 +1,330 @@ -# Apollo Supergraph - Setup & Configuration Guide +# Apollo Supergraph - Setup Guide -This guide provides detailed setup and configuration instructions for the Apollo Supergraph project. +Complete guide to set up and run the Apollo Supergraph locally and in Kubernetes. -## ⚠️ CRITICAL SECURITY WARNING +## 📋 Prerequisites -**NEVER overwrite existing `.env` files!** If `router/.env` already exists, it contains real Apollo Studio credentials. Always check if the file exists before copying templates. +- [Node.js](https://nodejs.org/) (for subgraphs) +- [Docker](https://docs.docker.com/get-docker/) (for Kubernetes deployment) +- [minikube](https://minikube.sigs.k8s.io/docs/start/) (for Kubernetes deployment) +- [kubectl](https://kubernetes.io/docs/tasks/tools/) (for Kubernetes deployment) -**For AI Assistants**: Before running any setup commands, always check if `router/.env` exists and contains real credentials. If it does, DO NOT overwrite it with template files. +## 🚀 Quick Start -## 🚨 CRITICAL: Supergraph File Protection +### 1. Environment Setup -**NEVER modify `router/supergraph.graphql`!** This file is automatically generated from `router/supergraph.yaml` using the `compose.sh` script. +```bash +# Set up Apollo Studio credentials (safe - won't overwrite existing) +./setup-env.sh +``` -**For AI Assistants**: -- The `router/supergraph.graphql` file is generated, not edited manually -- To regenerate: `cd router && ./compose.sh` -- For Kubernetes deployment, URLs are transformed during deployment (localhost → Kubernetes service URLs) -- Never commit Kubernetes URLs to the supergraph.graphql file +### 2. Choose Your Deployment Option -## 📋 Prerequisites +#### Option A: Local Development (Recommended for Development) -### Required (Manual Installation) -- [Node.js](https://nodejs.org/) (for subgraphs) +```bash +# Run both subgraphs and router +./run-local.sh + +# Test the router +./test-router.sh products +``` + +**Benefits:** +- ✅ **Faster startup** - No Kubernetes overhead +- ✅ **Easier debugging** - Direct access to logs +- ✅ **No resource constraints** - Runs directly on your machine +- ✅ **Simple cleanup** - Just Ctrl+C to stop + +#### Option B: Kubernetes Deployment (Recommended for Testing Production-like Environment) -### Required (Auto-installed) -- **Rover** - Apollo CLI tool (installed automatically by scripts) -- **Apollo Studio credentials** - Set up with `./setup-env.sh` +```bash +# Setup minikube with required addons +./setup-minikube.sh -### Required (Kubernetes Deployment Only) -- [Docker](https://docs.docker.com/get-docker/) -- [minikube](https://minikube.sigs.k8s.io/docs/start/) -- [kubectl](https://kubernetes.io/docs/tasks/tools/) +# Deploy Apollo Supergraph with 2 replicas (default) +./run-k8s.sh -## 🔧 Configuration +# Validate the deployment worked +./test-k8s.sh -### Apollo Studio Credentials +# Test specific router functionality +./test-router.sh products +``` -Set up your Apollo Studio environment: +## 🧪 Testing + +### Router Testing (After Deployment) ```bash -# Set up Apollo Studio credentials (safe - won't overwrite existing) -./setup-env.sh +# Test Apollo Router functionality +./test-router.sh + +# Test specific functionality +./test-router.sh products # Test searchProducts query +./test-router.sh status # Show router status +./test-router.sh health # Test health endpoint + +# Show all available tests +./test-router.sh --help ``` -This will: -- Create `router/.env` from template (if it doesn't exist) -- Show current credentials (if already configured) -- Provide step-by-step instructions for getting credentials +**When to use**: After deployment is running, to test specific router features or debug issues. + +### Deployment Validation (After Running run-k8s.sh) -**Manual setup (alternative):** ```bash -# Safely create .env file (won't overwrite existing) -if [ ! -f "router/.env" ]; then cp router/env.example router/.env; fi +# Test the entire Kubernetes deployment +./test-k8s.sh ``` -Then edit `router/.env` with your actual Apollo Studio credentials: +**When to use**: After running `./run-k8s.sh`, to validate that the entire deployment (subgraphs + router) is working correctly. + +### Test Script Differences + +| Script | Purpose | Scope | When to Use | +|--------|---------|-------|-------------| +| `./test-k8s.sh` | **Deployment validation** | Full deployment (subgraphs + router) | After `./run-k8s.sh` | +| `./test-router.sh` | **Router testing** | Router only | When router is running | +| `./status-k8s.sh` | **Kubernetes status** | K8s resources in minikube | Check deployment status | + +### Kubernetes Status Monitoring + +The `status-k8s.sh` script provides comprehensive status information about your Kubernetes deployment: ```bash -# Your Apollo Graph reference (format: graph-name@variant) -APOLLO_GRAPH_REF=your-graph-name@your-variant +# Show basic status +./status-k8s.sh + +# Show detailed information +./status-k8s.sh --detailed + +# Show only pod status +./status-k8s.sh --pods -# Your Apollo Studio API key -APOLLO_KEY=service:your-graph-name:your-api-key +# Show only service status +./status-k8s.sh --services + +# Show only ingress status +./status-k8s.sh --ingress ``` -### Environment Setup for Kubernetes +**What it shows:** +- ✅ Pod status and health +- ✅ Service endpoints +- ✅ Deployment status +- ✅ Ingress configuration +- ✅ Port forwarding status +- ✅ Access URLs +- ✅ Resource usage (with --detailed) -Before deploying to Kubernetes, you need to configure your Apollo Studio credentials: +## 🧹 Cleanup -1. **Create environment file** (ONLY if it doesn't already exist): - ```bash - # ⚠️ CRITICAL: Check if .env already exists before copying template - if [ ! -f "router/.env" ]; then - cp router/env.example router/.env - else - echo "⚠️ router/.env already exists - DO NOT overwrite existing credentials!" - fi - ``` +### Local Development +```bash +# Stop all services +Ctrl+C (in the terminal running run-local.sh) +``` -2. **Edit the `.env` file** with your actual Apollo Studio credentials: - ```bash - # Your Apollo Graph reference (format: graph-name@variant) - APOLLO_GRAPH_REF=your-graph-name@your-variant - - # Your Apollo Studio API key - APOLLO_KEY=service:your-graph-name:your-api-key - ``` - - **Important**: - - The `.env` file should contain simple key-value pairs without `export` statements - - **NEVER overwrite an existing `.env` file** - it may contain real credentials - - If you accidentally overwrite credentials, you'll need to recreate them from Apollo Studio +### Kubernetes Deployment +```bash +# Clean up deployment +./cleanup-k8s.sh -## 🧪 Testing +# Stop and delete minikube cluster +./kill-minikube.sh +``` -### Local Testing +--- -Test your setup locally without requiring minikube: +## 🔧 Advanced Options + +
+Custom Replicas ```bash -# Run all local tests -./test-local.sh +# Deploy with custom number of replicas +./run-k8s.sh --replicas 3 + +# Show all options +./run-k8s.sh --help +``` -# Test specific components -./test-local.sh --subgraphs # Test subgraphs only -./test-local.sh --composition # Test supergraph composition only -./test-local.sh --docker # Test Docker builds only -./test-local.sh --router # Test router only +
-# Show help -./test-local.sh --help +
+Port Forwarding Management + +The project includes improved port forwarding utilities that handle starting, stopping, and monitoring port forwarding automatically: + +```bash +# Check port forwarding status +./scripts/port-forward-utils.sh status + +# Start all port forwarding (router + subgraphs) +./scripts/port-forward-utils.sh start + +# Start only router port forwarding +./scripts/port-forward-utils.sh router + +# Start only subgraphs port forwarding +./scripts/port-forward-utils.sh subgraphs + +# Stop all port forwarding +./scripts/port-forward-utils.sh stop ``` -### Kubernetes Testing +**Benefits of the new port forwarding system:** +- ✅ **Automatic PID tracking** - No need to manually track process IDs +- ✅ **Conflict prevention** - Won't start duplicate port forwarding +- ✅ **Automatic cleanup** - Stops port forwarding when scripts exit +- ✅ **Status monitoring** - Easy to check what's currently running +- ✅ **Centralized management** - Single utility for all port forwarding needs +**Manual port forwarding (alternative):** ```bash -# Test Apollo Supergraph deployment -./test-k8s.sh +# Start port forwarding for Apollo Router +kubectl port-forward svc/apollo-router-service 4000:4000 -n apollo-supergraph -# Show help -./test-k8s.sh --help +# Start port forwarding for subgraphs +kubectl port-forward svc/subgraphs-service 4001:4001 -n apollo-supergraph ``` -### Manual Testing +
-#### Local Development +
+Access URLs +After deployment, the Apollo Router is accessible at: + +- **GraphQL Endpoint**: http://localhost:4000/graphql +- **Health Check**: http://localhost:4000/health + +For direct access to subgraphs: ```bash -# Test the GraphQL API -curl -X POST http://localhost:4000/graphql \ - -H "Content-Type: application/json" \ - -d '{"query":"{ searchProducts { id title price } }"}' +# Port forward subgraphs service +kubectl port-forward svc/subgraphs-service 4001:4001 -n apollo-supergraph ``` -#### Kubernetes Deployment +Then access: http://localhost:4001 + +
+ +
+Useful Commands ```bash -# Port forward to access services -kubectl port-forward svc/apollo-router-service 4000:4000 -n apollo-supergraph +# View pods +kubectl get pods -n apollo-supergraph + +# View services +kubectl get svc -n apollo-supergraph + +# View router logs +kubectl logs -f deployment/apollo-router -n apollo-supergraph + +# View subgraphs logs +kubectl logs -f deployment/subgraphs -n apollo-supergraph + +# Port forwarding management +./scripts/port-forward-utils.sh status +./scripts/port-forward-utils.sh start +./scripts/port-forward-utils.sh stop + +# Manual port forwarding (alternative) +kubectl port-forward svc/subgraphs-service 4001:4001 -n apollo-supergraph +``` + +
+ +--- + +## 🚨 Troubleshooting + +
+Common Issues + +### Apollo Router fails to start with "no valid license" error + +The router requires valid Apollo Studio credentials. You have two options: + +**Option A: Use valid Apollo Studio credentials** +```bash +# Edit router/.env with your actual credentials +APOLLO_GRAPH_REF=your-actual-graph-name@your-variant +APOLLO_KEY=service:your-actual-graph-name:your-actual-api-key +``` + +**Option B: Run subgraphs locally first** +```bash +# Start subgraphs locally +cd subgraphs && npm start + +# Then deploy router (configured to connect to localhost:4001) +./run-k8s.sh +``` + +### NodePort services not accessible + +Start minikube tunnel for NodePort access: +```bash +minikube tunnel +``` + +### Image pull errors + +Make sure you're using minikube's Docker daemon: +```bash +eval $(minikube docker-env) +``` + +### Ingress not working + +Check if ingress controller is running: +```bash +kubectl get pods -n ingress-nginx +``` + +### Service connectivity + +Check if services are properly configured: +```bash +kubectl get svc -n apollo-supergraph +kubectl describe svc apollo-router-service -n apollo-supergraph +``` + +### Pod startup issues + +Check pod events and logs: +```bash +kubectl describe pod -n apollo-supergraph +kubectl logs -n apollo-supergraph +``` + +### Router not connecting to subgraphs + +Make sure subgraphs are running: +```bash +# Check if subgraphs are running +kubectl get pods -n apollo-supergraph + +# If not running, redeploy +./run-k8s.sh +``` + +
+ +
+Resource Requirements + +The deployment requires: +- **minikube**: 4GB RAM, 2 CPUs, 20GB disk +- **Applications**: 256MB RAM, 200m CPU per pod + +For better performance: +- Increase resource limits in deployment files +- Add horizontal pod autoscalers +- Configure pod disruption budgets +- Use persistent volumes for logs -# Test the GraphQL API -curl -X POST http://localhost:4000/graphql \ - -H "Content-Type: application/json" \ - -d '{"query":"{ searchProducts { id title price } }"}' -``` - -### Automated Testing - -This repository includes comprehensive GitHub Actions workflows that automatically test: - -1. **Subgraphs Testing** - Build and test the subgraphs application -2. **Supergraph Composition** - Verify that the supergraph can be composed correctly -3. **Docker Builds** - Ensure Docker images can be built successfully -4. **Subgraphs Functionality** - Test that subgraphs respond correctly to GraphQL queries -5. **Apollo Router** - Test the router with the composed supergraph -6. **Helper Scripts** - Validate script syntax and help options -7. **Kubernetes Manifests** - Verify that all K8s manifests are valid -8. **Full K8s Deployment** - Test the complete deployment to a kind cluster - -The workflows run on: -- Pull requests to the main branch -- Pushes to the main branch -- Manual triggers (for the K8s deployment test) - -To view test results, check the "Actions" tab in the GitHub repository. - -## 📁 Project Structure - -``` -├── router/ # Apollo Router configuration -│ ├── router.yaml # Router configuration -│ ├── supergraph.yaml # Supergraph composition config -│ ├── supergraph.graphql # Generated supergraph schema -│ ├── compose.sh # Generate supergraph schema -│ └── rover-dev.sh # Run router locally -├── subgraphs/ # GraphQL subgraphs -│ ├── products/ # Products subgraph -│ ├── reviews/ # Reviews subgraph -│ └── users/ # Users subgraph -├── k8s/ # Kubernetes manifests -├── scripts/ # Shared utility functions -├── run-local.sh # Run locally without Kubernetes -├── run-k8s.sh # Deploy to Kubernetes -├── test-local.sh # Test local setup -├── test-k8s.sh # Test Kubernetes deployment -├── cleanup-k8s.sh # Clean up Kubernetes resources -└── kill-minikube.sh # Stop and delete minikube cluster -``` - -## 🔗 Links - -- [GraphOS Enterprise]: https://www.apollographql.com/docs/graphos/enterprise -- [Rover]: https://www.apollographql.com/docs/rover/commands/dev -- [minikube]: https://minikube.sigs.k8s.io/docs/start/?arch=%2Fmacos%2Farm64%2Fstable%2Fhomebrew -- [Kubernetes Deployment Guide](README-K8S.md) - Detailed Kubernetes deployment instructions +
diff --git a/cleanup-k8s.sh b/cleanup-k8s.sh index c24e306..0dc80ff 100755 --- a/cleanup-k8s.sh +++ b/cleanup-k8s.sh @@ -5,6 +5,7 @@ set -e # Source shared utilities SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/scripts/utils.sh" +source "$SCRIPT_DIR/scripts/config.sh" show_script_header "Cleanup" "Cleaning up Apollo Supergraph from minikube" @@ -23,15 +24,21 @@ cleanup_namespace() { } # Clean up the main namespace -cleanup_namespace "apollo-supergraph" "Apollo Supergraph" +cleanup_namespace "$(get_k8s_namespace)" "Apollo Supergraph" print_success "Cleanup completed successfully!" echo "" echo "📋 Cleanup Summary:" -echo " - Cleaned namespace: apollo-supergraph" +echo " - Cleaned namespace: $(get_k8s_namespace)" echo "" echo "🔍 Verify cleanup:" echo " - View all namespaces: kubectl get namespaces" echo " - View all pods: kubectl get pods --all-namespaces" +echo "" +print_warning "Note: Minikube is still running!" +echo " - Only the Apollo Supergraph pods and namespace were deleted" +echo " - To stop minikube completely, run: ./kill-minikube.sh" +echo " - To restart the deployment, run: ./run-k8s.sh" + show_script_footer "Cleanup" diff --git a/k8s-status.sh b/k8s-status.sh new file mode 100755 index 0000000..303c393 --- /dev/null +++ b/k8s-status.sh @@ -0,0 +1,225 @@ +#!/bin/bash + +# Source shared utilities +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/scripts/utils.sh" ]; then + source "$SCRIPT_DIR/scripts/utils.sh" +else + # Fallback if utils.sh not found + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + BLUE='\033[0;34m' + NC='\033[0m' + + print_status() { echo -e "${BLUE}[INFO]${NC} $1"; } + print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } + print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } + print_error() { echo -e "${RED}[ERROR]${NC} $1"; } +fi + +# Function to show usage +show_usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo " -d, --detailed Show detailed resource information" + echo " -p, --pods Show only pod status" + echo " -s, --services Show only service status" + echo " -i, --ingress Show only ingress status" + echo " -f, --fast Fast mode (skip minikube checks)" + echo "" + echo "Examples:" + echo " $0 # Show basic status" + echo " $0 --detailed # Show detailed information" + echo " $0 --pods # Show only pod status" + echo " $0 --fast # Fast mode (port forwarding only)" + echo " $0 --help # Show this help message" +} + +# Parse command line arguments +DETAILED=false +SHOW_PODS=false +SHOW_SERVICES=false +SHOW_INGRESS=false +FAST_MODE=false + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -d|--detailed) + DETAILED=true + shift + ;; + -p|--pods) + SHOW_PODS=true + shift + ;; + -s|--services) + SHOW_SERVICES=true + shift + ;; + -i|--ingress) + SHOW_INGRESS=true + shift + ;; + -f|--fast) + FAST_MODE=true + shift + ;; + -*) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + *) + print_error "Unknown argument: $1" + show_usage + exit 1 + ;; + esac +done + +echo "=============================================================================" +echo "Apollo Supergraph - Kubernetes Status" +echo "=============================================================================" +echo "Checking Apollo Supergraph resources in minikube" +echo "=============================================================================" +echo "" + +# Check if minikube is running (skip in fast mode) +if [ "$FAST_MODE" = false ]; then + print_status "Checking minikube status..." + if run_with_timeout 5 minikube status | grep -q "Running" 2>/dev/null; then + print_success "Minikube is running" + MINIKUBE_IP=$(run_with_timeout 3 minikube ip 2>/dev/null || echo "unknown") + print_status "Minikube IP: $MINIKUBE_IP" + else + print_error "Minikube is not running or not accessible" + echo " Run: minikube start" + echo " or run: ./setup-minikube.sh" + echo "" + echo "🌐 Access URLs (if services are running locally):" + echo " Router GraphQL: http://localhost:4000/graphql" + echo " Router Health: http://localhost:4000/health" + echo " Subgraphs: http://localhost:4001" + echo "" + echo "=============================================================================" + echo "Kubernetes Status completed!" + echo "=============================================================================" + exit 0 + fi + + # Check if namespace exists + NAMESPACE="apollo-supergraph" + print_status "Checking namespace..." + if run_with_timeout 5 kubectl get namespace "$NAMESPACE" > /dev/null 2>&1; then + print_success "Namespace '$NAMESPACE' exists" + else + print_warning "Namespace '$NAMESPACE' does not exist" + echo " Run: ./run-k8s.sh to deploy the supergraph" + echo "" + echo "🌐 Access URLs (if services are running locally):" + echo " Router GraphQL: http://localhost:4000/graphql" + echo " Router Health: http://localhost:4000/health" + echo " Subgraphs: http://localhost:4001" + echo "" + echo "=============================================================================" + echo "Kubernetes Status completed!" + echo "=============================================================================" + exit 0 + fi + echo "" +fi + +# Show pod status +if [ "$SHOW_PODS" = true ] || [ "$FAST_MODE" = false ]; then + echo "📦 Pod Status:" + echo "" + run_with_timeout 10 kubectl get pods -n "$NAMESPACE" || echo "No pods found or timeout" + echo "" +fi + +# Show service status +if [ "$SHOW_SERVICES" = true ] || [ "$FAST_MODE" = false ]; then + echo "🔌 Service Status:" + echo "" + run_with_timeout 10 kubectl get services -n "$NAMESPACE" || echo "No services found or timeout" + echo "" +fi + +# Show deployment status +if [ "$FAST_MODE" = false ]; then + echo "🚀 Deployment Status:" + echo "" + run_with_timeout 10 kubectl get deployments -n "$NAMESPACE" || echo "No deployments found or timeout" + echo "" +fi + +# Show ingress status +if [ "$SHOW_INGRESS" = true ] || [ "$FAST_MODE" = false ]; then + echo "🌐 Ingress Status:" + echo "" + run_with_timeout 10 kubectl get ingress -n "$NAMESPACE" || echo "No ingress found or timeout" + echo "" +fi + +# Show port forwarding status (always show this) +echo "🔗 Port Forwarding Status:" +echo "" +if lsof -i :4000 > /dev/null 2>&1; then + print_success "Port 4000 is being forwarded (router)" +else + print_warning "Port 4000 is not being forwarded" +fi + +if lsof -i :4001 > /dev/null 2>&1; then + print_success "Port 4001 is being forwarded (subgraphs)" +else + print_warning "Port 4001 is not being forwarded" +fi +echo "" + +# Show access URLs +echo "🌐 Access URLs:" +echo "" +echo " Router GraphQL: http://localhost:4000/graphql" +echo " Router Health: http://localhost:4000/health" +echo " Subgraphs: http://localhost:4001" +echo " Minikube Dashboard: minikube dashboard" +echo "" + +# Show detailed information if requested +if [ "$DETAILED" = true ] && [ "$FAST_MODE" = false ]; then + echo "📊 Detailed Information:" + echo "" + + # Show recent events + echo "📋 Recent Events:" + run_with_timeout 5 kubectl get events -n "$NAMESPACE" --sort-by='.lastTimestamp' | tail -5 || echo "Events not available" + echo "" + + # Show resource usage (if metrics server available) + echo "💾 Resource Usage:" + run_with_timeout 5 kubectl top pods -n "$NAMESPACE" || echo "Metrics server not available" + echo "" +fi + +# Show manual commands +if [ "$FAST_MODE" = false ]; then + echo "🔍 Manual Status Commands:" + echo " - Check minikube: minikube status" + echo " - View pods: kubectl get pods -n apollo-supergraph" + echo " - View services: kubectl get svc -n apollo-supergraph" + echo " - View deployments: kubectl get deployments -n apollo-supergraph" + echo " - Test router: ./test-router.sh" + echo "" +fi + +echo "=============================================================================" +echo "Kubernetes Status completed successfully!" +echo "=============================================================================" diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f6cebbb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,33 @@ +{ + "name": "example-local-kubernetes-supergraph", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "ws": "^8.18.3" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..31fef03 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ws": "^8.18.3" + } +} diff --git a/router/configuration_schema.json b/router/configuration_schema.json index 97f7716..dd95510 100644 --- a/router/configuration_schema.json +++ b/router/configuration_schema.json @@ -1803,15 +1803,15 @@ "default": { "supergraph": true, "query_planning": true, + "connect": true, "router": true, - "execution": true, + "connect_request": true, "http_request": true, + "execution": true, "request": true, - "parse_query": true, "subgraph": true, - "connect": true, "subgraph_request": true, - "connect_request": true + "parse_query": true }, "type": "object", "additionalProperties": { diff --git a/router/helm-install.sh b/router/helm-install.sh old mode 100755 new mode 100644 diff --git a/router/router.yaml b/router/router.yaml index 87fb3dd..69f206d 100644 --- a/router/router.yaml +++ b/router/router.yaml @@ -1,23 +1,30 @@ # $schema: configuration_schema.json +homepage: + enabled: false + sandbox: enabled: true supergraph: introspection: true - listen: 0.0.0.0:${env.PORT:-4000} + listen: 0.0.0.0:4000 path: /graphql -homepage: - enabled: false +cors: + allow_any_origin: true include_subgraph_errors: all: true -telemetry: - instrumentation: - spans: - mode: spec_compliant +subscription: + enabled: true + mode: + passthrough: + subgraphs: + reviews: + protocol: graphql_ws + # Enable health check endpoint on all interfaces health_check: diff --git a/router/rover-dev.sh b/router/rover-dev.sh index 2fa6c09..629af76 100755 --- a/router/rover-dev.sh +++ b/router/rover-dev.sh @@ -6,4 +6,4 @@ source .env rover dev \ --supergraph-config supergraph.yaml \ --router-config router.yaml \ - --accept-license + --elv2-license accept diff --git a/router/supergraph.graphql b/router/supergraph.graphql index e42c771..b4c19cb 100644 --- a/router/supergraph.graphql +++ b/router/supergraph.graphql @@ -3,6 +3,7 @@ schema @link(url: "https://specs.apollo.dev/join/v0.5", for: EXECUTION) { query: Query + subscription: Subscription } directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION @@ -108,11 +109,8 @@ type Query """ product(id: ID!): Product @join__field(graph: PRODUCTS) - """ - Get the current user from our fake "auth" headers - Set the "x-user-id" header to the user id. - """ - user: User @join__field(graph: USERS) + """Get a user by ID""" + user(id: ID!): User @join__field(graph: USERS) allUsers: [User] @join__field(graph: USERS) } @@ -132,6 +130,12 @@ type Review product: Product } +type Subscription + @join__type(graph: REVIEWS) +{ + reviewAdded: Review +} + """An user account in our system""" type User @join__type(graph: REVIEWS, key: "id", resolvable: false) diff --git a/run-k8s.sh b/run-k8s.sh index dd2f8bf..52b7207 100755 --- a/run-k8s.sh +++ b/run-k8s.sh @@ -5,6 +5,7 @@ set -e # Source shared utilities SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/scripts/utils.sh" +source "$SCRIPT_DIR/scripts/config.sh" # Function to show usage show_usage() { @@ -22,9 +23,21 @@ show_usage() { } # Default values -NAMESPACE="apollo-supergraph" +NAMESPACE=$(get_k8s_namespace) SERVICE_TYPE="ClusterIP" REPLICAS=2 +PORT_FORWARD_PID="" + +# Cleanup function to stop port forwarding +cleanup() { + if [ -n "$PORT_FORWARD_PID" ] && kill -0 "$PORT_FORWARD_PID" 2>/dev/null; then + print_status "Stopping port forwarding (PID: $PORT_FORWARD_PID)..." + kill "$PORT_FORWARD_PID" 2>/dev/null || true + fi +} + +# Set trap to cleanup on script exit +trap cleanup EXIT # Parse command line arguments while [[ $# -gt 0 ]]; do @@ -133,7 +146,7 @@ cd router # Generate supergraph with localhost URLs first ./compose.sh # Create a temporary copy with Kubernetes URLs -sed 's|http://localhost:4001|http://subgraphs-service.apollo-supergraph.svc.cluster.local:4001|g' supergraph.graphql > supergraph-k8s.graphql +sed "s|http://localhost:$SUBGRAPHS_PORT|http://$(get_subgraphs_service_name).$(get_k8s_namespace).svc.cluster.local:$SUBGRAPHS_PORT|g" supergraph.graphql > supergraph-k8s.graphql cd .. # Create supergraph-schema ConfigMap from the Kubernetes version @@ -170,6 +183,28 @@ sleep 10 # Get minikube IP MINIKUBE_IP=$(get_minikube_ip) +# Start port forwarding automatically +print_status "Starting port forwarding for Apollo Router..." +print_status "Running: ./scripts/port-forward-utils.sh" +source ./scripts/port-forward-utils.sh && start_router_port_forward + +# Start port forwarding for subgraphs +print_status "Starting port forwarding for subgraphs..." +source ./scripts/port-forward-utils.sh && start_subgraphs_port_forward + +# Wait a moment for port forward to establish +sleep 3 + +# Source test utilities for health check +source "./scripts/test-utils.sh" + +# Check if port forward is working +if test_router_health > /dev/null 2>&1; then + print_success "Port forwarding established successfully!" +else + print_warning "Port forwarding may still be establishing. Please wait a moment and try accessing $(get_router_graphql_url)" +fi + print_success "Deployment completed successfully!" echo "" echo "📋 Deployment Summary:" @@ -180,19 +215,19 @@ echo " - Apollo Router: ${ROUTER_REPLICAS} replica(s)" echo "" echo "🌐 Access your applications:" -echo " - Apollo Router: http://apollo-router.local (add to /etc/hosts: $MINIKUBE_IP apollo-router.local)" -echo " - Router Health: http://$MINIKUBE_IP:$(kubectl get svc apollo-router-service -n $NAMESPACE -o jsonpath='{.spec.ports[?(@.name=="health")].nodePort}')" -echo "" -print_warning "Don't forget to add the following line to your /etc/hosts file:" -echo " $MINIKUBE_IP apollo-router.local" +echo " - Apollo Router: $(get_router_graphql_url) (port forwarding active)" +echo " - Router Health: $(get_router_health_url)" +echo " - Subgraphs: $(get_subgraphs_url) (port forwarding active)" echo "" echo "🔍 Useful commands:" echo " - View pods: kubectl get pods -n $NAMESPACE" echo " - View services: kubectl get svc -n $NAMESPACE" echo " - View router logs: kubectl logs -f deployment/apollo-router -n $NAMESPACE" -echo " - Port forward router: kubectl port-forward svc/apollo-router-service 4000:4000 -n $NAMESPACE" echo " - View subgraphs logs: kubectl logs -f deployment/subgraphs -n $NAMESPACE" -echo " - Port forward subgraphs: kubectl port-forward svc/subgraphs-service 4001:4001 -n $NAMESPACE" +echo " - Stop port forwarding: ./scripts/port-forward-utils.sh stop" +echo " - Restart port forwarding: ./scripts/port-forward-utils.sh start" +echo " - Test router: ./test-router.sh" +echo " - Check status: ./status-k8s.sh" show_script_footer "Apollo Supergraph Deployment" diff --git a/run-local.sh b/run-local.sh index 0aa4ff5..75ef132 100755 --- a/run-local.sh +++ b/run-local.sh @@ -1,5 +1,10 @@ #!/bin/bash +# Source shared utilities +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/scripts/utils.sh" +source "$SCRIPT_DIR/scripts/config.sh" + # ============================================================================= # Apollo Supergraph - Local Development Script (No Kubernetes) # ============================================================================= @@ -142,21 +147,45 @@ if [ "$RUN_SUBGRAPHS" = true ]; then sleep 5 # Test if subgraphs are responding - for i in {1..10}; do - if curl -s http://localhost:4001/products/graphql > /dev/null 2>&1; then + for i in {1..15}; do + if curl -s "$(get_subgraphs_products_url)" > /dev/null 2>&1 && \ + curl -s "$(get_subgraphs_reviews_url)" > /dev/null 2>&1 && \ + curl -s "$(get_subgraphs_users_url)" > /dev/null 2>&1; then print_success "Subgraphs are ready!" break fi - if [ $i -eq 10 ]; then + if [ $i -eq 15 ]; then print_error "Subgraphs failed to start properly" exit 1 fi - sleep 2 + print_status "Waiting for subgraphs... (attempt $i/15)" + sleep 3 done + + # Regenerate supergraph schema after subgraphs are ready + print_status "Regenerating supergraph schema..." + cd router + ./compose.sh + cd .. + print_success "Supergraph schema regenerated" fi # Run router if requested if [ "$RUN_ROUTER" = true ]; then + # Check if subgraphs are running when using router-only mode + if [ "$RUN_SUBGRAPHS" = false ]; then + print_status "Checking if subgraphs are running..." + if ! curl -s "$(get_subgraphs_products_url)" > /dev/null 2>&1; then + print_error "Subgraphs are not running on $(get_subgraphs_url)" + print_error "Please start the subgraphs first:" + print_error " ./run-local.sh --subgraphs-only" + print_error " or" + print_error " ./run-local.sh" + exit 1 + fi + print_success "Subgraphs are running" + fi + print_status "Starting Apollo Router..." cd router ./rover-dev.sh & @@ -169,14 +198,16 @@ if [ "$RUN_ROUTER" = true ]; then # Test if router is responding for i in {1..10}; do - if curl -s http://localhost:8088/health > /dev/null 2>&1; then + if curl -s "$(get_router_health_url)" > /dev/null 2>&1; then print_success "Apollo Router is ready!" break fi if [ $i -eq 10 ]; then print_error "Apollo Router failed to start properly" + print_error "Check router logs for more details" exit 1 fi + print_status "Waiting for router... (attempt $i/10)" sleep 2 done fi @@ -186,22 +217,22 @@ echo "" echo "📋 Service Status:" if [ "$RUN_SUBGRAPHS" = true ]; then - echo " ✅ Subgraphs: http://localhost:4001" - echo " - Products: http://localhost:4001/products/graphql" - echo " - Reviews: http://localhost:4001/reviews/graphql" - echo " - Users: http://localhost:4001/users/graphql" + echo " ✅ Subgraphs: $(get_subgraphs_url)" + echo " - Products: $(get_subgraphs_products_url)" + echo " - Reviews: $(get_subgraphs_reviews_url)" + echo " - Users: $(get_subgraphs_users_url)" fi if [ "$RUN_ROUTER" = true ]; then - echo " ✅ Apollo Router: http://localhost:4000/graphql" - echo " ✅ Router Health: http://localhost:8088/health" + echo " ✅ Apollo Router: $(get_router_graphql_url)" + echo " ✅ Router Health: $(get_router_health_url)" fi echo "" echo "🧪 Test the GraphQL API:" -echo " curl -X POST http://localhost:4000/graphql \\" +echo " curl -X POST $(get_router_graphql_url) \\" echo " -H \"Content-Type: application/json\" \\" -echo " -d '{\"query\":\"{ products { id title price } }\"}'" +echo " -d '{\"query\":\"{ searchProducts { id title price } }\"}'" echo "" echo "🔍 Useful commands:" echo " - View subgraphs logs: tail -f subgraphs/logs/*" diff --git a/test-local.sh b/scripts/build-validate.sh similarity index 94% rename from test-local.sh rename to scripts/build-validate.sh index ef93904..7f34725 100755 --- a/test-local.sh +++ b/scripts/build-validate.sh @@ -1,11 +1,11 @@ #!/bin/bash # ============================================================================= -# Apollo Supergraph - Local Test Script +# Apollo Supergraph - Build Validation Script # ============================================================================= # -# This script runs local tests to verify the Apollo Supergraph setup -# without requiring minikube or Kubernetes. +# This script validates the build process and components of the Apollo Supergraph +# without requiring minikube or Kubernetes. It's used for development validation. # # ============================================================================= @@ -13,9 +13,9 @@ set -e # Source shared utilities SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/scripts/utils.sh" +source "$SCRIPT_DIR/utils.sh" -show_script_header "Local Testing" "Testing Apollo Supergraph components locally" +show_script_header "Build Validation" "Validating Apollo Supergraph build components" # Function to show usage show_usage() { @@ -339,17 +339,17 @@ if [ "$TEST_ROUTER" = true ]; then # Wait for router to start sleep 15 + # Source test utilities for router testing + source "$SCRIPT_DIR/test-utils.sh" + # Test router endpoint - curl -X POST http://localhost:4000/graphql \ - -H "Content-Type: application/json" \ - -d '{"query":"{ searchProducts { id title price } }"}' \ - --max-time 10 \ - --retry 3 \ - --retry-delay 2 > /dev/null || { + if test_search_products > /dev/null 2>&1; then + print_success "Router endpoint test passed" + else print_error "Router endpoint test failed" docker logs router-test exit 1 - } + fi cd .. print_success "Router test passed" @@ -379,4 +379,4 @@ if [ "$TEST_ROUTER" = true ]; then echo " ✅ Router and subgraphs functionality" fi -show_script_footer "Local Testing" +show_script_footer "Build Validation" diff --git a/scripts/config.sh b/scripts/config.sh new file mode 100644 index 0000000..6b5c717 --- /dev/null +++ b/scripts/config.sh @@ -0,0 +1,194 @@ +#!/bin/bash + +# ============================================================================= +# Apollo Supergraph - Configuration Constants +# ============================================================================= +# +# This script contains all configuration constants used across the project: +# - Port numbers +# - URLs and endpoints +# - Namespace names +# - Service names +# - File paths +# +# ============================================================================= + +# ============================================================================= +# Port Configuration +# ============================================================================= + +# Router ports +ROUTER_GRAPHQL_PORT=4000 +ROUTER_HEALTH_PORT=8088 + +# Subgraphs ports +SUBGRAPHS_PORT=4001 + +# ============================================================================= +# URL Configuration +# ============================================================================= + +# Router URLs +ROUTER_GRAPHQL_URL="http://localhost:${ROUTER_GRAPHQL_PORT}/graphql" +ROUTER_HEALTH_URL="http://localhost:${ROUTER_HEALTH_PORT}/health" + +# Subgraphs URLs +SUBGRAPHS_URL="http://localhost:${SUBGRAPHS_PORT}" +SUBGRAPHS_PRODUCTS_URL="${SUBGRAPHS_URL}/products/graphql" +SUBGRAPHS_REVIEWS_URL="${SUBGRAPHS_URL}/reviews/graphql" +SUBGRAPHS_USERS_URL="${SUBGRAPHS_URL}/users/graphql" + +# ============================================================================= +# Kubernetes Configuration +# ============================================================================= + +# Namespace +K8S_NAMESPACE="apollo-supergraph" + +# Service names +K8S_ROUTER_SERVICE="apollo-router-service" +K8S_SUBGRAPHS_SERVICE="subgraphs-service" + +# Deployment names +K8S_ROUTER_DEPLOYMENT="apollo-router" +K8S_SUBGRAPHS_DEPLOYMENT="subgraphs" + +# Ingress +K8S_INGRESS_HOST="apollo-router.local" + +# ============================================================================= +# File Paths +# ============================================================================= + +# Router files +ROUTER_DIR="router" +ROUTER_CONFIG_FILE="${ROUTER_DIR}/router.yaml" +ROUTER_SUPERGRAPH_CONFIG="${ROUTER_DIR}/supergraph.yaml" +ROUTER_SUPERGRAPH_SCHEMA="${ROUTER_DIR}/supergraph.graphql" + +# Subgraphs files +SUBGRAPHS_DIR="subgraphs" +SUBGRAPHS_PRODUCTS_SCHEMA="${SUBGRAPHS_DIR}/products/schema.graphql" +SUBGRAPHS_REVIEWS_SCHEMA="${SUBGRAPHS_DIR}/reviews/schema.graphql" +SUBGRAPHS_USERS_SCHEMA="${SUBGRAPHS_DIR}/users/schema.graphql" + +# Kubernetes manifests +K8S_DIR="k8s" +K8S_NAMESPACE_FILE="${K8S_DIR}/namespace.yaml" +K8S_ROUTER_DEPLOYMENT_FILE="${K8S_DIR}/router-deployment-clusterip.yaml" +K8S_SUBGRAPHS_DEPLOYMENT_FILE="${K8S_DIR}/subgraphs-deployment-clusterip.yaml" +K8S_INGRESS_FILE="${K8S_DIR}/ingress.yaml" + +# ============================================================================= +# Utility Functions +# ============================================================================= + +# Function to get router GraphQL URL +get_router_graphql_url() { + echo "$ROUTER_GRAPHQL_URL" +} + +# Function to get router health URL +get_router_health_url() { + echo "$ROUTER_HEALTH_URL" +} + +# Function to get subgraphs URL +get_subgraphs_url() { + echo "$SUBGRAPHS_URL" +} + +# Function to get subgraphs products URL +get_subgraphs_products_url() { + echo "$SUBGRAPHS_PRODUCTS_URL" +} + +# Function to get subgraphs reviews URL +get_subgraphs_reviews_url() { + echo "$SUBGRAPHS_REVIEWS_URL" +} + +# Function to get subgraphs users URL +get_subgraphs_users_url() { + echo "$SUBGRAPHS_USERS_URL" +} + +# Function to get Kubernetes namespace +get_k8s_namespace() { + echo "$K8S_NAMESPACE" +} + +# Function to get router service name +get_router_service_name() { + echo "$K8S_ROUTER_SERVICE" +} + +# Function to get subgraphs service name +get_subgraphs_service_name() { + echo "$K8S_SUBGRAPHS_SERVICE" +} + +# Function to get router deployment name +get_router_deployment_name() { + echo "$K8S_ROUTER_DEPLOYMENT" +} + +# Function to get subgraphs deployment name +get_subgraphs_deployment_name() { + echo "$K8S_SUBGRAPHS_DEPLOYMENT" +} + +# Function to check if port is being forwarded +is_port_forwarded() { + local port=$1 + lsof -i :"$port" > /dev/null 2>&1 +} + +# Function to check if router is accessible +is_router_accessible() { + curl -s "$ROUTER_HEALTH_URL" > /dev/null 2>&1 +} + +# Function to check if subgraphs are accessible +is_subgraphs_accessible() { + curl -s "$SUBGRAPHS_PRODUCTS_URL" > /dev/null 2>&1 +} + +# Function to print all configuration +print_config() { + echo "=============================================================================" + echo "Apollo Supergraph - Configuration" + echo "=============================================================================" + echo "" + echo "📡 Ports:" + echo " Router GraphQL: $ROUTER_GRAPHQL_PORT" + echo " Router Health: $ROUTER_HEALTH_PORT" + echo " Subgraphs: $SUBGRAPHS_PORT" + echo "" + echo "🌐 URLs:" + echo " Router GraphQL: $ROUTER_GRAPHQL_URL" + echo " Router Health: $ROUTER_HEALTH_URL" + echo " Subgraphs: $SUBGRAPHS_URL" + echo " - Products: $SUBGRAPHS_PRODUCTS_URL" + echo " - Reviews: $SUBGRAPHS_REVIEWS_URL" + echo " - Users: $SUBGRAPHS_USERS_URL" + echo "" + echo "☸️ Kubernetes:" + echo " Namespace: $K8S_NAMESPACE" + echo " Router Service: $K8S_ROUTER_SERVICE" + echo " Subgraphs Service: $K8S_SUBGRAPHS_SERVICE" + echo " Router Deployment: $K8S_ROUTER_DEPLOYMENT" + echo " Subgraphs Deployment: $K8S_SUBGRAPHS_DEPLOYMENT" + echo " Ingress Host: $K8S_INGRESS_HOST" + echo "" + echo "📁 Files:" + echo " Router Config: $ROUTER_CONFIG_FILE" + echo " Supergraph Config: $ROUTER_SUPERGRAPH_CONFIG" + echo " Supergraph Schema: $ROUTER_SUPERGRAPH_SCHEMA" + echo " K8s Namespace: $K8S_NAMESPACE_FILE" + echo " K8s Router: $K8S_ROUTER_DEPLOYMENT_FILE" + echo " K8s Subgraphs: $K8S_SUBGRAPHS_DEPLOYMENT_FILE" + echo " K8s Ingress: $K8S_INGRESS_FILE" + echo "" + echo "=============================================================================" +} diff --git a/scripts/port-forward-utils.sh b/scripts/port-forward-utils.sh new file mode 100755 index 0000000..c71c7e1 --- /dev/null +++ b/scripts/port-forward-utils.sh @@ -0,0 +1,251 @@ +#!/bin/bash + +# ============================================================================= +# Apollo Supergraph - Port Forwarding Utilities +# ============================================================================= +# +# This script manages port forwarding for the Apollo Router and subgraphs +# in Kubernetes deployments. +# +# ============================================================================= + +# Source shared utilities +if [ -z "$SCRIPT_DIR" ]; then + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +fi +source "$SCRIPT_DIR/scripts/utils.sh" +source "$SCRIPT_DIR/scripts/config.sh" + +# Configuration (now from config) +NAMESPACE=$(get_k8s_namespace) +PID_FILE_DIR="/tmp/apollo-supergraph" + +# Create PID file directory if it doesn't exist +ensure_pid_dir() { + ensure_directory "$PID_FILE_DIR" +} + +# Get PID file path for a service +get_pid_file() { + local service="$1" + echo "$PID_FILE_DIR/${service}-port-forward.pid" +} + +# Check if port forwarding is already running for a service +is_port_forward_running() { + local service="$1" + local pid_file=$(get_pid_file "$service") + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + return 0 # Running + else + # PID file exists but process is dead, clean it up + rm -f "$pid_file" + fi + fi + return 1 # Not running +} + +# Start port forwarding for a service +start_port_forward() { + local service="$1" + local port="$2" + local pid_file=$(get_pid_file "$service") + + ensure_pid_dir + + # Check if already running + if is_port_forward_running "$service"; then + local pid=$(cat "$pid_file") + print_warning "Port forwarding for $service is already running (PID: $pid)" + return 0 + fi + + # Check if service exists + if ! kubectl get svc "${service}-service" -n "$NAMESPACE" > /dev/null 2>&1; then + print_error "Service ${service}-service not found in namespace $NAMESPACE" + return 1 + fi + + # Start port forwarding + print_status "Starting port forwarding for $service on port $port..." + kubectl port-forward "svc/${service}-service" "$port:$port" -n "$NAMESPACE" > /dev/null 2>&1 & + local pid=$! + + # Save PID + echo "$pid" > "$pid_file" + + # Wait a moment for port forward to establish + sleep 2 + + # Verify it's working + if kill -0 "$pid" 2>/dev/null; then + print_success "Port forwarding for $service started (PID: $pid)" + return 0 + else + print_error "Failed to start port forwarding for $service" + rm -f "$pid_file" + return 1 + fi +} + +# Stop port forwarding for a service +stop_port_forward() { + local service="$1" + local pid_file=$(get_pid_file "$service") + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + print_status "Stopping port forwarding for $service (PID: $pid)..." + kill "$pid" 2>/dev/null || true + sleep 1 + if ! kill -0 "$pid" 2>/dev/null; then + print_success "Port forwarding for $service stopped" + else + print_warning "Port forwarding for $service may still be running" + fi + fi + rm -f "$pid_file" + else + print_warning "No port forwarding found for $service" + fi +} + +# Stop all port forwarding +stop_all_port_forward() { + print_status "Stopping all port forwarding..." + stop_port_forward "apollo-router" + stop_port_forward "subgraphs" + print_success "All port forwarding stopped" +} + +# Check if a port is listening +is_port_listening() { + local port="$1" + lsof -i ":$port" > /dev/null 2>&1 +} + +# Wait for port forwarding to be ready +wait_for_port_forward() { + local service="$1" + local port="$2" + local max_attempts=30 + local attempt=1 + + print_status "Waiting for $service port forwarding to be ready..." + + while [ $attempt -le $max_attempts ]; do + if is_port_listening "$port"; then + print_success "$service port forwarding is ready" + return 0 + fi + + sleep 1 + attempt=$((attempt + 1)) + done + + print_error "$service port forwarding failed to become ready after $max_attempts seconds" + return 1 +} + +# Start router port forwarding +start_router_port_forward() { + start_port_forward "apollo-router" "$ROUTER_GRAPHQL_PORT" + if [ $? -eq 0 ]; then + wait_for_port_forward "apollo-router" "$ROUTER_GRAPHQL_PORT" + fi +} + +# Start subgraphs port forwarding +start_subgraphs_port_forward() { + start_port_forward "subgraphs" "$SUBGRAPHS_PORT" + if [ $? -eq 0 ]; then + wait_for_port_forward "subgraphs" "$SUBGRAPHS_PORT" + fi +} + +# Start all port forwarding +start_all_port_forward() { + print_status "Starting all port forwarding..." + start_router_port_forward + start_subgraphs_port_forward + print_success "All port forwarding started" +} + +# Show port forwarding status +show_port_forward_status() { + print_status "Port Forwarding Status:" + echo "" + + # Check router + if is_port_forward_running "apollo-router"; then + local pid=$(cat $(get_pid_file "apollo-router")) + print_success "Router: Running (PID: $pid) - http://localhost:$ROUTER_GRAPHQL_PORT" + else + print_error "Router: Not running" + fi + + # Check subgraphs + if is_port_forward_running "subgraphs"; then + local pid=$(cat $(get_pid_file "subgraphs")) + print_success "Subgraphs: Running (PID: $pid) - http://localhost:$SUBGRAPHS_PORT" + else + print_error "Subgraphs: Not running" + fi + + echo "" + print_status "Useful commands:" + echo " - Start all: source scripts/port-forward-utils.sh && start_all_port_forward" + echo " - Stop all: source scripts/port-forward-utils.sh && stop_all_port_forward" + echo " - Status: source scripts/port-forward-utils.sh && show_port_forward_status" +} + +# Cleanup function for script exit +cleanup_on_exit() { + # Only cleanup if this script is being run directly + if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + stop_all_port_forward + fi +} + +# Set trap for cleanup +trap cleanup_on_exit EXIT + +# If script is run directly, show status +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + if [ $# -eq 0 ]; then + show_port_forward_status + else + case "$1" in + "start") + start_all_port_forward + ;; + "stop") + stop_all_port_forward + ;; + "status") + show_port_forward_status + ;; + "router") + start_router_port_forward + ;; + "subgraphs") + start_subgraphs_port_forward + ;; + *) + echo "Usage: $0 [start|stop|status|router|subgraphs]" + echo "" + echo "Commands:" + echo " start - Start all port forwarding" + echo " stop - Stop all port forwarding" + echo " status - Show port forwarding status" + echo " router - Start router port forwarding only" + echo " subgraphs - Start subgraphs port forwarding only" + exit 1 + ;; + esac + fi +fi diff --git a/scripts/test-utils.sh b/scripts/test-utils.sh new file mode 100755 index 0000000..ccaccbc --- /dev/null +++ b/scripts/test-utils.sh @@ -0,0 +1,340 @@ +#!/bin/bash + +# ============================================================================= +# Apollo Supergraph - Test Utilities +# ============================================================================= +# +# This script contains common test operations and curl commands used across +# multiple scripts for testing the Apollo Router and subgraphs. +# +# ============================================================================= + +# Source shared utilities +if [ -z "$SCRIPT_DIR" ]; then + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +fi + +# If SCRIPT_DIR is the root directory, we need to source from scripts subdirectory +if [ "$(basename "$SCRIPT_DIR")" = "scripts" ]; then + source "$SCRIPT_DIR/utils.sh" + source "$SCRIPT_DIR/config.sh" +else + source "$SCRIPT_DIR/scripts/utils.sh" + source "$SCRIPT_DIR/scripts/config.sh" +fi + +# Default values (now from config) +ROUTER_URL=$(get_router_graphql_url | sed 's|/graphql$||') +HEALTH_ENDPOINT=$(get_router_health_url) +GRAPHQL_ENDPOINT=$(get_router_graphql_url) + +# Function to test if port forwarding is working +test_port_forward() { + local url="$1" + local description="$2" + + if curl -s "$url" > /dev/null 2>&1; then + print_success "$description is accessible" + return 0 + else + print_error "$description is not accessible" + return 1 + fi +} + +# Function to test router health endpoint +test_router_health() { + print_status "Testing router health endpoint..." + test_port_forward "$HEALTH_ENDPOINT" "Router health endpoint" +} + +# Function to test GraphQL introspection +test_graphql_introspection() { + print_status "Testing GraphQL introspection..." + + local response=$(curl -s -X POST "$GRAPHQL_ENDPOINT" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ __schema { types { name } } }"}') + + if echo "$response" | grep -q '"data"' && echo "$response" | grep -q '"__schema"'; then + print_success "GraphQL introspection working" + return 0 + else + print_error "GraphQL introspection failed" + return 1 + fi +} + +# Function to test available queries +test_available_queries() { + print_status "Testing available queries..." + + local response=$(curl -s -X POST "$GRAPHQL_ENDPOINT" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ __schema { queryType { fields { name } } } }"}') + + if echo "$response" | grep -q '"data"'; then + print_success "Available queries retrieved" + echo "$response" | grep -o '"name":"[^"]*"' | sed 's/"name":"//g' | sed 's/"//g' + return 0 + else + print_error "Failed to retrieve available queries" + return 1 + fi +} + +# Function to test searchProducts query +test_search_products() { + print_status "Testing searchProducts query..." + + local response=$(curl -s -X POST "$GRAPHQL_ENDPOINT" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ searchProducts { id title price } }"}') + + if echo "$response" | grep -q '"data"' && echo "$response" | grep -q '"searchProducts"'; then + print_success "searchProducts query working" + # Show first few products + echo "$response" | grep -o '"title":"[^"]*"' | head -3 | sed 's/"title":"//g' | sed 's/"//g' + return 0 + else + print_error "searchProducts query failed" + return 1 + fi +} + +# Function to test product details +test_product_details() { + print_status "Testing product details query..." + + local response=$(curl -s -X POST "$GRAPHQL_ENDPOINT" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ __type(name: \"Product\") { fields { name } } }"}') + + if echo "$response" | grep -q '"data"' && echo "$response" | grep -q '"fields"'; then + print_success "Product schema retrieved" + echo "$response" | grep -o '"name":"[^"]*"' | sed 's/"name":"//g' | sed 's/"//g' + return 0 + else + print_error "Failed to retrieve product schema" + return 1 + fi +} + +# Function to test user query +test_user_query() { + print_status "Testing user query..." + + local response=$(curl -s -X POST "$GRAPHQL_ENDPOINT" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ user(id: \"user:1\") { id username } }"}') + + if echo "$response" | grep -q '"data"' && echo "$response" | grep -q '"user"'; then + print_success "User query working" + return 0 + else + print_error "User query failed" + return 1 + fi +} + +# Function to test all users query +test_all_users() { + print_status "Testing allUsers query..." + + local response=$(curl -s -X POST "$GRAPHQL_ENDPOINT" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ allUsers { id username } }"}') + + if echo "$response" | grep -q '"data"' && echo "$response" | grep -q '"allUsers"'; then + print_success "allUsers query working" + return 0 + else + print_error "allUsers query failed" + return 1 + fi +} + +# Function to run comprehensive router test +test_router_comprehensive() { + print_status "Running comprehensive router tests..." + echo "" + + local all_passed=true + + # Test health endpoint + if ! test_router_health; then + all_passed=false + fi + echo "" + + # Test GraphQL introspection + if ! test_graphql_introspection; then + all_passed=false + fi + echo "" + + # Test available queries + if ! test_available_queries; then + all_passed=false + fi + echo "" + + # Test searchProducts + if ! test_search_products; then + all_passed=false + fi + echo "" + + # Test product schema + if ! test_product_details; then + all_passed=false + fi + echo "" + + # Test user queries + if ! test_user_query; then + all_passed=false + fi + echo "" + + if ! test_all_users; then + all_passed=false + fi + echo "" + + if [ "$all_passed" = true ]; then + print_success "All router tests passed! 🎉" + return 0 + else + print_error "Some router tests failed! ❌" + return 1 + fi +} + +# Function to test basic infrastructure +test_basic_infrastructure() { + print_status "Testing basic infrastructure..." + echo "" + + local all_passed=true + + # Test if we can source the utils + if source "$SCRIPT_DIR/utils.sh" > /dev/null 2>&1; then + print_success "Utils sourced successfully" + else + print_error "Failed to source utils" + all_passed=false + fi + + # Test if we can source test-utils + if source "$SCRIPT_DIR/test-utils.sh" > /dev/null 2>&1; then + print_success "Test-utils sourced successfully" + else + print_error "Failed to source test-utils" + all_passed=false + fi + + echo "" + print_status "Testing router health..." + + # Test router health + if curl -s "$(get_router_health_url)" > /dev/null 2>&1; then + print_success "Router health check passed" + else + print_error "Router health check failed" + all_passed=false + fi + + echo "" + + if [ "$all_passed" = true ]; then + print_success "Basic infrastructure tests passed! ✅" + return 0 + else + print_error "Basic infrastructure tests failed! ❌" + return 1 + fi +} + +# Function to test if port 4000 is listening +test_port_4000_listening() { + if lsof -i :4000 > /dev/null 2>&1; then + print_success "Port 4000 is listening" + return 0 + else + print_error "Port 4000 is not listening" + return 1 + fi +} + +# Function to show router status +show_router_status() { + print_status "Router Status:" + echo " - URL: $ROUTER_URL" + echo " - Health: $HEALTH_ENDPOINT" + echo " - GraphQL: $GRAPHQL_ENDPOINT" + echo "" + + if test_port_4000_listening; then + if test_router_health; then + print_success "Router is running and healthy" + else + print_warning "Router is running but health check failed" + fi + else + print_error "Router is not accessible" + fi +} + +# Function to run a specific test +run_test() { + case "$1" in + "basic") + test_basic_infrastructure + ;; + "health") + test_router_health + ;; + "introspection") + test_graphql_introspection + ;; + "queries") + test_available_queries + ;; + "products") + test_search_products + ;; + "product-schema") + test_product_details + ;; + "user") + test_user_query + ;; + "users") + test_all_users + ;; + "port") + test_port_4000_listening + ;; + "status") + show_router_status + ;; + "all"|"comprehensive") + test_router_comprehensive + ;; + *) + print_error "Unknown test: $1" + echo "Available tests: basic, health, introspection, queries, products, product-schema, user, users, port, status, all" + return 1 + ;; + esac +} + +# If script is run directly, run comprehensive test +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + if [ $# -eq 0 ]; then + test_router_comprehensive + else + run_test "$1" + fi +fi diff --git a/scripts/utils.sh b/scripts/utils.sh index 318a0c9..927b9bf 100755 --- a/scripts/utils.sh +++ b/scripts/utils.sh @@ -174,3 +174,44 @@ show_script_footer() { echo "$script_name completed successfully!" echo "=============================================================================" } + +# Cross-platform timeout function +run_with_timeout() { + local timeout_seconds=$1 + shift + local cmd=("$@") + + # Check if timeout command is available (Linux) + if command -v timeout >/dev/null 2>&1; then + timeout "${timeout_seconds}s" "${cmd[@]}" 2>/dev/null + return $? + fi + + # macOS fallback using background process and kill + local pid + # Suppress job control messages + set +m + "${cmd[@]}" 2>/dev/null & + pid=$! + + # Wait for the specified timeout + local elapsed=0 + while [ $elapsed -lt $timeout_seconds ]; do + if ! kill -0 $pid 2>/dev/null; then + # Process completed + wait $pid 2>/dev/null + set -m + return $? + fi + sleep 1 + elapsed=$((elapsed + 1)) + done + + # Timeout reached, kill the process + kill -TERM $pid 2>/dev/null + sleep 1 + kill -KILL $pid 2>/dev/null 2>/dev/null + wait $pid 2>/dev/null + set -m + return 124 # Exit code for timeout +} diff --git a/validate-external-access.sh b/scripts/validate-external-access.sh similarity index 71% rename from validate-external-access.sh rename to scripts/validate-external-access.sh index 58f8ab6..2c8dcad 100755 --- a/validate-external-access.sh +++ b/scripts/validate-external-access.sh @@ -1,35 +1,23 @@ #!/bin/bash -set -e - -echo "🔍 Validating external access to subgraphs deployment..." - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Function to print colored output -print_status() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -print_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -print_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -print_error() { - echo -e "${RED}[ERROR]${NC} $1" -} +# ============================================================================= +# Apollo Supergraph - External Access Validation +# ============================================================================= +# +# This script validates that the Apollo Supergraph is accessible from external +# sources (outside of Kubernetes cluster). +# +# ============================================================================= + +# Source shared utilities +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/utils.sh" +source "$SCRIPT_DIR/config.sh" + +show_script_header "External Access Validation" "Validating external access to subgraphs deployment" # Check if namespace exists -if ! kubectl get namespace subgraphs-only > /dev/null 2>&1; then +if ! namespace_exists "subgraphs-only"; then print_error "Namespace subgraphs-only does not exist. Please deploy first:" echo " ./deploy-subgraphs-only.sh" exit 1 @@ -61,7 +49,7 @@ PF_PID=$! sleep 5 # Test health endpoint -if curl -s http://localhost:4001/health > /dev/null; then +if curl -s "$(get_subgraphs_url)/health" > /dev/null; then print_success "Health endpoint accessible via port-forward" else print_error "Health endpoint not accessible via port-forward" @@ -73,7 +61,7 @@ fi print_status "Testing GraphQL endpoints via port-forward..." # Test products -if curl -s -X POST http://localhost:4001/products/graphql \ +if curl -s -X POST "$(get_subgraphs_products_url)" \ -H "Content-Type: application/json" \ -d '{"query":"{ searchProducts { id title } }"}' | grep -q "data"; then print_success "Products subgraph working via port-forward" @@ -84,7 +72,7 @@ else fi # Test users -if curl -s -X POST http://localhost:4001/users/graphql \ +if curl -s -X POST "$(get_subgraphs_users_url)" \ -H "Content-Type: application/json" \ -d '{"query":"{ allUsers { id } }"}' | grep -q "data"; then print_success "Users subgraph working via port-forward" @@ -118,11 +106,11 @@ echo "" echo "📋 Access Methods:" echo "" echo "🔧 Method 1: Port Forwarding (Recommended for development)" -echo " kubectl port-forward svc/subgraphs-service 4001:4001 -n subgraphs-only" +echo " kubectl port-forward svc/subgraphs-service $SUBGRAPHS_PORT:$SUBGRAPHS_PORT -n subgraphs-only" echo " Then access:" -echo " - Health: http://localhost:4001/health" -echo " - Products: http://localhost:4001/products/graphql" -echo " - Users: http://localhost:4001/users/graphql" +echo " - Health: $(get_subgraphs_url)/health" +echo " - Products: $(get_subgraphs_products_url)" +echo " - Users: $(get_subgraphs_users_url)" echo "" echo "🌐 Method 2: Minikube Service (For browser access)" echo " minikube service subgraphs-service -n subgraphs-only" @@ -130,14 +118,14 @@ echo " This will open your browser to the service" echo "" echo "🧪 Test Commands:" echo " # Health check" -echo " curl http://localhost:4001/health" +echo " curl $(get_subgraphs_url)/health" echo "" echo " # Products query" -echo " curl -X POST http://localhost:4001/products/graphql \\" +echo " curl -X POST $(get_subgraphs_products_url) \\" echo " -H \"Content-Type: application/json\" \\" echo " -d '{\"query\":\"{ searchProducts { id title price } }\"}'" echo "" echo " # Users query" -echo " curl -X POST http://localhost:4001/users/graphql \\" +echo " curl -X POST $(get_subgraphs_users_url) \\" echo " -H \"Content-Type: application/json\" \\" echo " -d '{\"query\":\"{ allUsers { id } }\"}'" diff --git a/setup-env.sh b/setup-env.sh index 4f8e06f..0e16aed 100755 --- a/setup-env.sh +++ b/setup-env.sh @@ -16,6 +16,46 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/scripts/utils.sh" +# Help function +show_help() { + cat << EOF +Usage: $0 [OPTIONS] + +Setup Apollo Studio environment for the Apollo Router. + +OPTIONS: + -h, --help Show this help message and exit + +EXAMPLES: + $0 # Setup environment with default settings + $0 --help # Show this help message + +DESCRIPTION: + This script sets up the Apollo Studio environment by: + - Creating router/.env from router/env.example template + - Providing instructions for getting Apollo Studio credentials + - Only creates the file if it doesn't already exist + + After setup, you'll need to add your actual Apollo Studio credentials + to the router/.env file. + +EOF + exit 0 +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_help + ;; + *) + echo "Unknown option: $1" + show_help + ;; + esac +done + ENV_FILE="router/.env" TEMPLATE_FILE="router/env.example" diff --git a/setup-minikube.sh b/setup-minikube.sh index 53f018c..ebc5209 100755 --- a/setup-minikube.sh +++ b/setup-minikube.sh @@ -6,6 +6,45 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/scripts/utils.sh" +# Help function +show_help() { + cat << EOF +Usage: $0 [OPTIONS] + +Setup minikube for Apollo Supergraph deployment. + +OPTIONS: + -h, --help Show this help message and exit + +EXAMPLES: + $0 # Setup minikube with default settings + $0 --help # Show this help message + +DESCRIPTION: + This script sets up a minikube cluster with: + - 4GB memory, 2 CPUs, 20GB disk + - Ingress controller enabled + - Metrics server enabled + + After setup, you can run ./run-k8s.sh to deploy the supergraph. + +EOF + exit 0 +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_help + ;; + *) + echo "Unknown option: $1" + show_help + ;; + esac +done + show_script_header "Minikube Setup" "Setting up minikube for Apollo Supergraph deployment" # Validate required tools @@ -49,7 +88,7 @@ echo " - Ingress controller enabled" echo " - Metrics server enabled" echo "" echo "🚀 You can now run the deployment:" -echo " ./deploy.sh" +echo " ./run-k8s.sh" echo "" echo "🔍 Useful commands:" echo " - Open minikube dashboard: minikube dashboard" diff --git a/status-k8s.sh b/status-k8s.sh new file mode 100755 index 0000000..4ba2cb4 --- /dev/null +++ b/status-k8s.sh @@ -0,0 +1,216 @@ +#!/bin/bash + +# Source shared utilities +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/scripts/utils.sh" +source "$SCRIPT_DIR/scripts/config.sh" + + + +# Function to show usage +show_usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo " -d, --detailed Show detailed resource information" + echo " -p, --pods Show only pod status" + echo " -s, --services Show only service status" + echo " -i, --ingress Show only ingress status" + echo " -f, --fast Fast mode (skip minikube checks)" + echo " -c, --config Show configuration" + echo "" + echo "Examples:" + echo " $0 # Show basic status" + echo " $0 --detailed # Show detailed information" + echo " $0 --pods # Show only pod status" + echo " $0 --fast # Fast mode (port forwarding only)" + echo " $0 --config # Show configuration" + echo " $0 --help # Show this help message" +} + +# Parse command line arguments +DETAILED=false +SHOW_PODS=false +SHOW_SERVICES=false +SHOW_INGRESS=false +FAST_MODE=false +SHOW_CONFIG=false + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -d|--detailed) + DETAILED=true + shift + ;; + -p|--pods) + SHOW_PODS=true + shift + ;; + -s|--services) + SHOW_SERVICES=true + shift + ;; + -i|--ingress) + SHOW_INGRESS=true + shift + ;; + -f|--fast) + FAST_MODE=true + shift + ;; + -c|--config) + SHOW_CONFIG=true + shift + ;; + -*) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + *) + print_error "Unknown argument: $1" + show_usage + exit 1 + ;; + esac +done + +# Show configuration if requested +if [ "$SHOW_CONFIG" = true ]; then + print_config + exit 0 +fi + +show_script_header "Kubernetes Status" "Checking Apollo Supergraph resources in minikube" + +# Check if minikube is running (skip in fast mode) +if [ "$FAST_MODE" = false ]; then + print_status "Checking minikube status..." + if minikube_is_running; then + print_success "Minikube is running" + MINIKUBE_IP=$(get_minikube_ip) + print_status "Minikube IP: $MINIKUBE_IP" + else + print_error "Minikube is not running. Please start minikube first:" + echo " minikube start" + echo " or run: ./setup-minikube.sh" + echo "" + echo "🌐 Access URLs (if services are running locally):" + echo " Router GraphQL: $(get_router_graphql_url)" + echo " Router Health: $(get_router_health_url)" + echo " Subgraphs: $(get_subgraphs_url)" + echo "" + show_script_footer "Kubernetes Status" + exit 0 + fi + + # Check if namespace exists + NAMESPACE=$(get_k8s_namespace) + print_status "Checking namespace..." + if namespace_exists "$NAMESPACE"; then + print_success "Namespace '$NAMESPACE' exists" + else + print_warning "Namespace '$NAMESPACE' does not exist" + echo " Run: ./run-k8s.sh to deploy the supergraph" + echo "" + echo "🌐 Access URLs (if services are running locally):" + echo " Router GraphQL: $(get_router_graphql_url)" + echo " Router Health: $(get_router_health_url)" + echo " Subgraphs: $(get_subgraphs_url)" + echo "" + show_script_footer "Kubernetes Status" + exit 0 + fi + echo "" +fi + +# Show pod status +if [ "$SHOW_PODS" = true ] || [ "$FAST_MODE" = false ]; then + print_status "Pod Status:" + echo "" + run_with_timeout 10 kubectl get pods -n "$NAMESPACE" || echo "No pods found or timeout" + echo "" +fi + +# Show service status +if [ "$SHOW_SERVICES" = true ] || [ "$FAST_MODE" = false ]; then + print_status "Service Status:" + echo "" + run_with_timeout 10 kubectl get services -n "$NAMESPACE" || echo "No services found or timeout" + echo "" +fi + +# Show deployment status +if [ "$FAST_MODE" = false ]; then + print_status "Deployment Status:" + echo "" + run_with_timeout 10 kubectl get deployments -n "$NAMESPACE" || echo "No deployments found or timeout" + echo "" +fi + +# Show ingress status +if [ "$SHOW_INGRESS" = true ] || [ "$FAST_MODE" = false ]; then + print_status "Ingress Status:" + echo "" + run_with_timeout 10 kubectl get ingress -n "$NAMESPACE" || echo "No ingress found or timeout" + echo "" +fi + +# Show port forwarding status (always show this) +print_status "Port Forwarding Status:" +echo "" +if is_port_forwarded "$ROUTER_GRAPHQL_PORT"; then + print_success "Port $ROUTER_GRAPHQL_PORT is being forwarded (router)" +else + print_warning "Port $ROUTER_GRAPHQL_PORT is not being forwarded" +fi + +if is_port_forwarded "$SUBGRAPHS_PORT"; then + print_success "Port $SUBGRAPHS_PORT is being forwarded (subgraphs)" +else + print_warning "Port $SUBGRAPHS_PORT is not being forwarded" +fi +echo "" + +# Show access URLs +print_status "Access URLs:" +echo "" +echo " Router GraphQL: $(get_router_graphql_url)" +echo " Router Health: $(get_router_health_url)" +echo " Subgraphs: $(get_subgraphs_url)" +echo " Minikube Dashboard: minikube dashboard" +echo "" + +# Show detailed information if requested +if [ "$DETAILED" = true ] && [ "$FAST_MODE" = false ]; then + print_status "Detailed Information:" + echo "" + + # Show recent events + print_status "Recent Events:" + run_with_timeout 5 kubectl get events -n "$NAMESPACE" --sort-by='.lastTimestamp' | tail -5 || echo "Events not available" + echo "" + + # Show resource usage (if metrics server available) + print_status "Resource Usage:" + run_with_timeout 5 kubectl top pods -n "$NAMESPACE" || echo "Metrics server not available" + echo "" +fi + +# Show manual commands +if [ "$FAST_MODE" = false ]; then + print_status "Manual Status Commands:" + echo " - Check minikube: minikube status" + echo " - View pods: kubectl get pods -n $NAMESPACE" + echo " - View services: kubectl get svc -n $NAMESPACE" + echo " - View deployments: kubectl get deployments -n $NAMESPACE" + echo " - Test router: ./test-router.sh" + echo "" +fi + +show_script_footer "Kubernetes Status" diff --git a/subgraphs/package-lock.json b/subgraphs/package-lock.json index 9c51ace..8ae6d55 100644 --- a/subgraphs/package-lock.json +++ b/subgraphs/package-lock.json @@ -10,10 +10,13 @@ "@apollo/server": "^5.0.0", "@apollo/subgraph": "^2.11.2", "@as-integrations/express5": "^1.1.2", + "@graphql-tools/schema": "^9.0.25", "body-parser": "^2.2.0", "cors": "^2.8.5", "express": "^5.1.0", - "graphql": "^16.11.0" + "graphql": "^16.11.0", + "graphql-ws": "6.0.6", + "ws": "8.18.3" }, "devDependencies": { "@graphql-tools/mock": "^9.0.25", @@ -1135,6 +1138,36 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, + "node_modules/graphql-ws": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-6.0.6.tgz", + "integrity": "sha512-zgfER9s+ftkGKUZgc0xbx8T7/HMO4AV5/YuYiFc+AtgcO5T0v8AxYYNQ+ltzuzDZgNkYJaFspm5MMYLjQzrkmw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@fastify/websocket": "^10 || ^11", + "crossws": "~0.3", + "graphql": "^15.10.1 || ^16", + "uWebSockets.js": "^20", + "ws": "^8" + }, + "peerDependenciesMeta": { + "@fastify/websocket": { + "optional": true + }, + "crossws": { + "optional": true + }, + "uWebSockets.js": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2080,6 +2113,27 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } } } diff --git a/subgraphs/package.json b/subgraphs/package.json index ed7d6df..d9e3e3b 100644 --- a/subgraphs/package.json +++ b/subgraphs/package.json @@ -11,10 +11,13 @@ "@as-integrations/express5": "^1.1.2", "@apollo/server": "^5.0.0", "@apollo/subgraph": "^2.11.2", + "@graphql-tools/schema": "^9.0.25", "body-parser": "^2.2.0", "cors": "^2.8.5", "express": "^5.1.0", - "graphql": "^16.11.0" + "graphql": "^16.11.0", + "graphql-ws": "^6.0.6", + "ws": "^8.18.3" }, "devDependencies": { "@graphql-tools/mock": "^9.0.25", diff --git a/subgraphs/reviews/resolvers.js b/subgraphs/reviews/resolvers.js index ccf3c00..e157943 100644 --- a/subgraphs/reviews/resolvers.js +++ b/subgraphs/reviews/resolvers.js @@ -11,5 +11,23 @@ export const resolvers = { }, Product: { reviews: (parent) => getReviewsByProductUpc(parent.upc) - } + }, + Subscription: { + reviewAdded: { + subscribe: async function* () { + // Log the pod ID when subscription starts + const podId = process.env.HOSTNAME || 'unknown'; + console.log(`🚀 [${podId}] Reviews subscription connection established`); + + let count = 0; + while (true) { + const review = REVIEWS[count++]; + console.log(`📤 [${podId}] Sending review: ${review.id} - ${review.title}`); + yield { reviewAdded: review }; + await new Promise((resolve) => setTimeout(resolve, 3000)); + if (count === REVIEWS.length) count = 0; + } + }, + }, + }, }; diff --git a/subgraphs/reviews/schema.graphql b/subgraphs/reviews/schema.graphql index 3e31af3..d89890e 100644 --- a/subgraphs/reviews/schema.graphql +++ b/subgraphs/reviews/schema.graphql @@ -23,3 +23,7 @@ type Product @key(fields: "id") { type User @key(fields: "id", resolvable: false) { id: ID! } + +type Subscription { + reviewAdded: Review +} diff --git a/subgraphs/subgraphs.js b/subgraphs/subgraphs.js index adf94b8..2e09158 100644 --- a/subgraphs/subgraphs.js +++ b/subgraphs/subgraphs.js @@ -6,6 +6,8 @@ import express from 'express'; import http from 'http'; import cors from 'cors'; import bodyParser from 'body-parser'; +import { WebSocketServer } from "ws"; +import { useServer } from "graphql-ws/use/ws"; import { getProductsSchema } from './products/subgraph.js'; import { getReviewsSchema } from './reviews/subgraph.js'; import { getUsersSchema } from './users/subgraph.js'; @@ -19,6 +21,7 @@ export const LOCAL_SUBGRAPH_CONFIG = [ { name: 'reviews', getSchema: getReviewsSchema, + subscriptions: true }, { name: 'users', @@ -31,6 +34,10 @@ const getLocalSubgraphConfig = (subgraphName) => LOCAL_SUBGRAPH_CONFIG.find(it => it.name === subgraphName); export const startSubgraphs = async (httpPort) => { + // Log pod ID for Kubernetes identification + const podId = process.env.HOSTNAME || 'unknown'; + console.log(`🚀 [${podId}] Starting subgraphs server...`); + // Create a monolith express app for all subgraphs const app = express(); const httpServer = http.createServer(app); @@ -54,6 +61,45 @@ export const startSubgraphs = async (httpPort) => { schema = subgraphConfig.getSchema(); } + const path = `/${subgraphConfig.name}/graphql`; + + let wsPlugin = {}; + if (subgraphConfig.subscriptions === true) { + // Create WebSocket server for this subgraph + const wsServer = new WebSocketServer({ + server: httpServer, + path, + }); + + // Add WebSocket connection logging + wsServer.on('connection', (socket, request) => { + console.log(`🔌 [${podId}] WebSocket connection established for [${subgraphConfig.name}] subgraph`); + console.log(`🔌 [${podId}] Client IP: ${request.socket.remoteAddress}`); + + socket.on('close', () => { + console.log(`🔌 [${podId}] WebSocket connection closed for [${subgraphConfig.name}] subgraph`); + }); + + socket.on('error', (error) => { + console.log(`❌ [${podId}] WebSocket error for [${subgraphConfig.name}] subgraph:`, error.message); + }); + }); + + const serverCleanup = useServer({ schema }, wsServer); + wsPlugin = { + async serverWillStart() { + return { + async drainServer() { + await serverCleanup.dispose(); + }, + }; + }, + }; + console.log(`🚀 [${podId}] Setting up WebSocket server for [${subgraphConfig.name}] subgraph at ws://localhost:${serverPort}${path}`); + } + + console.log(`🚀 [${podId}] Setting up HTTP server for [${subgraphConfig.name}] subgraph at http://localhost:${serverPort}${path}`); + const server = new ApolloServer({ schema, // For a real subgraph introspection should remain off, but for demo we enabled @@ -62,13 +108,13 @@ export const startSubgraphs = async (httpPort) => { csrfPrevention: false, plugins: [ ApolloServerPluginDrainHttpServer({ httpServer }), - ApolloServerPluginUsageReportingDisabled() + ApolloServerPluginUsageReportingDisabled(), + wsPlugin ] }); await server.start(); - const path = `/${subgraphConfig.name}/graphql`; app.use( path, cors(), @@ -79,10 +125,9 @@ export const startSubgraphs = async (httpPort) => { } }) ); - - console.log(`Setting up [${subgraphConfig.name}] subgraph at http://localhost:${serverPort}${path}`); } // Start entire monolith at given port await new Promise((resolve) => httpServer.listen({ port: serverPort }, resolve)); + console.log(`🚀 [${podId}] All subgraphs started and listening on port ${serverPort}`); }; diff --git a/subgraphs/users/resolvers.js b/subgraphs/users/resolvers.js index 7f88b36..6eb2f78 100644 --- a/subgraphs/users/resolvers.js +++ b/subgraphs/users/resolvers.js @@ -5,12 +5,11 @@ const getUserById = (id) => USERS.find((it) => it.id === id); export const resolvers = { Query: { - user(_, __, context) { - const userId = context.headers["x-user-id"]; - const user = getUserById(userId); + user(_, { id }, context) { + const user = getUserById(id); if (!user) { - throw new GraphQLError("Could not locate user by id. Please specify a valid `x-user-id` header like `user:1`"); + throw new GraphQLError(`Could not locate user by id: ${id}`); } return user; @@ -24,5 +23,5 @@ export const resolvers = { return getUserById(ref.id); }, loyaltyPoints: () => Math.floor(Math.random() * 20) - }, + } }; diff --git a/subgraphs/users/schema.graphql b/subgraphs/users/schema.graphql index 9841a5d..068d00a 100644 --- a/subgraphs/users/schema.graphql +++ b/subgraphs/users/schema.graphql @@ -3,10 +3,9 @@ extend schema type Query { """ - Get the current user from our fake "auth" headers - Set the "x-user-id" header to the user id. + Get a user by ID """ - user: User + user(id: ID!): User allUsers: [User] } diff --git a/test-k8s.sh b/test-k8s.sh index 43137b8..fc28bf9 100755 --- a/test-k8s.sh +++ b/test-k8s.sh @@ -5,6 +5,7 @@ set -e # Source shared utilities SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/scripts/utils.sh" +source "$SCRIPT_DIR/scripts/config.sh" # Function to show usage show_usage() { @@ -33,10 +34,11 @@ while [[ $# -gt 0 ]]; do esac done -NAMESPACE="apollo-supergraph" - show_script_header "Apollo Supergraph Kubernetes Testing" "Testing Apollo Supergraph deployment in minikube" +# Get namespace +NAMESPACE=$(get_k8s_namespace) + # Check if namespace exists if ! namespace_exists "$NAMESPACE"; then print_error "Namespace $NAMESPACE does not exist. Please deploy first:" @@ -62,33 +64,35 @@ fi print_status "Checking service status..." kubectl get svc -n $NAMESPACE +# Source port forwarding utilities +source "$SCRIPT_DIR/scripts/port-forward-utils.sh" + # Test subgraphs print_status "Testing subgraphs health..." -# Test via port-forward -kubectl port-forward svc/subgraphs-service 4001:4001 -n $NAMESPACE & -PF_PID=$! - -# Wait for port-forward to be ready -sleep 5 - -if curl -s http://localhost:4001/products/graphql > /dev/null; then - print_success "Subgraphs are responding" +# Start subgraphs port forwarding +if start_subgraphs_port_forward; then + if curl -s "$(get_subgraphs_products_url)" > /dev/null; then + print_success "Subgraphs are responding" + else + print_error "Subgraphs are not responding" + stop_port_forward "subgraphs" + exit 1 + fi + + # Test GraphQL query to subgraphs + print_status "Testing GraphQL query to subgraphs..." + RESPONSE=$(curl -s -X POST "$(get_subgraphs_products_url)" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ searchProducts { id title price } }"}') + + # Stop subgraphs port forwarding + stop_port_forward "subgraphs" else - print_error "Subgraphs are not responding" - kill $PF_PID 2>/dev/null || true + print_error "Failed to start subgraphs port forwarding" exit 1 fi -# Test GraphQL query to subgraphs -print_status "Testing GraphQL query to subgraphs..." -RESPONSE=$(curl -s -X POST http://localhost:4001/products/graphql \ - -H "Content-Type: application/json" \ - -d '{"query":"{ searchProducts { id title price } }"}') - -# Stop port-forward -kill $PF_PID 2>/dev/null || true - if echo "$RESPONSE" | grep -q "data"; then print_success "GraphQL query to subgraphs successful" echo "Response: $RESPONSE" | head -c 200 @@ -99,36 +103,36 @@ else exit 1 fi +# Source test utilities for router testing +source "$SCRIPT_DIR/scripts/test-utils.sh" + # Test router print_status "Testing router health..." -# Wait for port-forward to be ready -sleep 5 - -if curl -s http://localhost:4000 > /dev/null; then - print_success "Router is responding" +# Start router port forwarding +if start_router_port_forward; then + if test_router_health > /dev/null 2>&1; then + print_success "Router is responding" + else + print_error "Router is not responding" + stop_port_forward "apollo-router" + exit 1 + fi + + # Test GraphQL query to router + print_status "Testing GraphQL query to router..." + if test_search_products > /dev/null 2>&1; then + print_success "GraphQL query to router successful" + else + print_error "GraphQL query to router failed" + stop_port_forward "apollo-router" + exit 1 + fi + + # Stop router port forwarding + stop_port_forward "apollo-router" else - print_error "Router is not responding" - kill $PF_PID 2>/dev/null || true - exit 1 -fi - -# Test GraphQL query to router -print_status "Testing GraphQL query to router..." -RESPONSE=$(curl -s -X POST http://localhost:4000/graphql \ - -H "Content-Type: application/json" \ - -d '{"query":"{ searchProducts { id title price } }"}') - -# Stop port-forward -kill $PF_PID 2>/dev/null || true - -if echo "$RESPONSE" | grep -q "data"; then - print_success "GraphQL query to router successful" - echo "Response: $RESPONSE" | head -c 200 - echo "..." -else - print_error "GraphQL query to router failed" - echo "Response: $RESPONSE" + print_error "Failed to start router port forwarding" exit 1 fi @@ -145,7 +149,7 @@ echo " ✅ GraphQL queries to router work" echo "" echo "🌐 Your deployment is ready!" -echo " - Router: kubectl port-forward svc/apollo-router-service 4000:4000 -n $NAMESPACE" -echo " - Subgraphs: kubectl port-forward svc/subgraphs-service 4001:4001 -n $NAMESPACE" +echo " - Router: kubectl port-forward svc/$(get_router_service_name) $ROUTER_GRAPHQL_PORT:$ROUTER_GRAPHQL_PORT -n $NAMESPACE" +echo " - Subgraphs: kubectl port-forward svc/$(get_subgraphs_service_name) $SUBGRAPHS_PORT:$SUBGRAPHS_PORT -n $NAMESPACE" show_script_footer "Kubernetes Testing" diff --git a/test-router.sh b/test-router.sh new file mode 100755 index 0000000..3138960 --- /dev/null +++ b/test-router.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +set -e + +# Source shared utilities +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/scripts/utils.sh" +source "$SCRIPT_DIR/scripts/test-utils.sh" + +# Function to show usage +show_usage() { + echo "Usage: $0 [OPTIONS] [TEST_NAME]" + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo "" + echo "Test Names:" + echo " basic Test basic infrastructure (scripts, health)" + echo " health Test router health endpoint" + echo " introspection Test GraphQL introspection" + echo " queries Test available queries" + echo " products Test searchProducts query" + echo " product-schema Test product schema" + echo " user Test user query" + echo " users Test allUsers query" + echo " port Test if port 4000 is listening" + echo " status Show router status" + echo " all (default) Run all tests" + echo "" + echo "Examples:" + echo " $0 # Run all tests" + echo " $0 basic # Test basic infrastructure" + echo " $0 products # Test only searchProducts query" + echo " $0 status # Show router status" + echo " $0 --help # Show this help message" +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -*) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + *) + TEST_NAME="$1" + shift + ;; + esac +done + +show_script_header "Apollo Router Testing" "Testing Apollo Router functionality" + +# Check if minikube is running +if ! minikube_is_running; then + print_error "Minikube is not running. Please start minikube first:" + echo " minikube start" + echo " or run: ./setup-minikube.sh" + exit 1 +fi + +print_success "Minikube is running" + +# Run the specified test or all tests +if [ -n "$TEST_NAME" ]; then + run_test "$TEST_NAME" +else + run_test "all" +fi + +show_script_footer "Apollo Router Testing"