diff --git a/.github/workflows/test-traefik-ingress.yml b/.github/workflows/test-traefik-ingress.yml new file mode 100644 index 0000000..714661a --- /dev/null +++ b/.github/workflows/test-traefik-ingress.yml @@ -0,0 +1,224 @@ +name: Test Traefik Ingress + +on: + pull_request: + branches: + - main + workflow_dispatch: + inputs: + debug_enabled: + description: 'Run workflow with debug logging' + required: false + default: true + type: boolean + +jobs: + test-traefik-ingress: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@v3 + with: + version: v3.10.3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install test dependencies + run: | + npm install -g wscat + sudo apt-get update && sudo apt-get install -y curl + + - name: Create kind config + run: | + cat <<-EOF > kind-config.yaml + kind: Cluster + apiVersion: kind.x-k8s.io/v1alpha4 + nodes: + - role: control-plane + extraPortMappings: + - containerPort: 30080 + hostPort: 30080 + protocol: TCP + - containerPort: 443 + hostPort: 443 + protocol: TCP + EOF + + - name: Debug kind config + if: ${{ inputs.debug_enabled }} + run: cat kind-config.yaml + + - name: Create kind cluster + uses: helm/kind-action@v1.9.0 + with: + wait: 600s + config: kind-config.yaml + + - name: Install Traefik + run: | + helm repo add traefik https://traefik.github.io/charts + helm repo update + helm install traefik traefik/traefik \ + --set installCRDs=true \ + --set ports.web.port=8000 \ + --set ports.web.exposedPort=80 \ + --set ports.web.nodePort=30080 \ + --set service.type=NodePort \ + --set ingressClass.enabled=true \ + --set ingressClass.isDefaultClass=true + + - name: Wait for Traefik + run: | + echo "Waiting for Traefik pods..." + kubectl wait --namespace default \ + --for=condition=ready pod \ + --selector=app.kubernetes.io/name=traefik \ + --timeout=90s + + - name: Create test values for traefik + run: | + mkdir -p debug + cat < debug/traefik-values.yaml + global: + ingress: + enabled: true + className: traefik + classType: traefik + websocketPrefix: /websocket + backendPrefix: /v2 + frontendPrefix: / + frontend: + enabled: true + backend: + enabled: true + websocket: + enabled: true + EOF + + - name: Debug - Show test values + if: ${{ inputs.debug_enabled }} + run: cat debug/traefik-values.yaml + + - name: Install Keep chart + run: | + helm install keep ./charts/keep -f debug/traefik-values.yaml + + - name: Wait for all pods + run: | + echo "Waiting for all pods to be ready..." + kubectl wait --for=condition=ready pod --all -n default --timeout=180s + + echo "Checking pod status..." + kubectl get pods -n default + + echo "Checking ingress status..." + kubectl get ingress -n default + + echo "Waiting additional 30s for services to stabilize..." + sleep 30 + + - name: Debug - Show resources + if: ${{ inputs.debug_enabled }} + run: | + echo "๐Ÿ” Checking all resources..." + kubectl get pods -A + kubectl get svc -A + kubectl get ingress -A + kubectl describe ingress -A + + - name: Test endpoints + run: | + INGRESS_IP="127.0.0.1" + APP_NAME=$(helm list -n default -o json | jq -r '.[0].name') + MAX_RETRIES=5 + RETRY_DELAY=10 + + test_endpoint() { + local url=$1 + local expected_code=$2 + local headers=${3:-""} + + for ((i=1; i<=MAX_RETRIES; i++)); do + echo "Attempt $i of $MAX_RETRIES for $url" + echo "Headers being sent: $headers" + + if [ -n "$headers" ]; then + echo "Full curl command: curl -v $headers \"$url\"" + RESP=$(curl -v $headers "$url" 2>&1) + else + echo "Full curl command: curl -v \"$url\"" + RESP=$(curl -v "$url" 2>&1) + fi + + # Extract response code, handling connection failures + RESP_CODE=$(echo "$RESP" | grep "< HTTP" | awk '{print $3}') + if [ -z "$RESP_CODE" ]; then + echo "โš ๏ธ No response code received - connection may have failed" + echo -e "\n๐Ÿ” Response Details:" + echo "------------------------" + echo "Response code: Connection failed" + echo -e "\n๐Ÿ“‹ Response Headers:" + echo "$RESP" | grep -E "^< " || echo "No headers found" + echo -e "\n๐Ÿ“ Response Body:" + echo "$RESP" | sed -n '/^* Connected/,/^* Connection/!p' | grep -v "^[*<>]" || echo "No body found" + echo "------------------------" + + if [ "$i" -lt "$MAX_RETRIES" ]; then + echo "โณ Waiting ${RETRY_DELAY}s before next attempt..." + sleep "$RETRY_DELAY" + continue + fi + echo "โŒ Failed to establish connection after $MAX_RETRIES attempts" + return 1 + fi + + echo -e "\n๐Ÿ” Response Details:" + echo "------------------------" + echo "Response code: $RESP_CODE" + echo -e "\n๐Ÿ“‹ Response Headers:" + echo "$RESP" | grep -E "^< " || echo "No headers found" + echo -e "\n๐Ÿ“ Response Body:" + echo "$RESP" | sed -n '/^* Connected/,/^* Connection/!p' | grep -v "^[*<>]" || echo "No body found" + echo "------------------------" + + if [ "$RESP_CODE" -eq "$expected_code" ]; then + echo "โœ… Expected response code $expected_code received" + return 0 + fi + + if [ "$i" -lt "$MAX_RETRIES" ]; then + echo "โณ Waiting ${RETRY_DELAY}s before next attempt..." + sleep "$RETRY_DELAY" + fi + done + echo "โŒ Failed to get expected response code $expected_code after $MAX_RETRIES attempts" + return 1 + } + + echo "๐ŸŒ Testing frontend endpoint..." + test_endpoint "http://$INGRESS_IP:30080/" 307 || exit 1 + + echo "๐Ÿ”Œ Testing backend endpoint..." + test_endpoint "http://$INGRESS_IP:30080/v2/docs" 200 || exit 1 + + echo "๐Ÿ”„ Testing websocket endpoint..." + test_endpoint "http://$INGRESS_IP:30080/websocket/" 200 || exit 1 + + - name: Debug - Show logs on failure + if: ${{ failure() && inputs.debug_enabled }} + run: | + echo "๐Ÿ“œ Traefik Controller logs:" + kubectl logs -l app.kubernetes.io/name=traefik --tail=100 + + echo "Application pods logs:" + for pod in $(kubectl get pods -n default -l app.kubernetes.io/instance=keep -o name); do + echo "Logs for $pod:" + kubectl logs $pod --tail=100 + done \ No newline at end of file diff --git a/charts/keep/Chart.yaml b/charts/keep/Chart.yaml index 58c4986..3a23322 100644 --- a/charts/keep/Chart.yaml +++ b/charts/keep/Chart.yaml @@ -1,10 +1,10 @@ apiVersion: v2 name: keep -version: 0.1.45 +version: 0.1.47 description: Keep Helm Chart type: application icon: https://platform.keephq.dev/_next/image?url=%2Fkeep.png&w=48&q=75 -appVersion: 0.30.7 +appVersion: 0.31.7 deprecated: false annotations: app: keep diff --git a/charts/keep/templates/ingress-traefik-middleware.yaml b/charts/keep/templates/ingress-traefik-middleware.yaml new file mode 100644 index 0000000..5681bc2 --- /dev/null +++ b/charts/keep/templates/ingress-traefik-middleware.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.global.ingress.enabled (or (eq .Values.global.ingress.className "traefik") (eq .Values.global.ingress.classType "traefik")) }} +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: strip-prefix + namespace: {{ .Release.Namespace }} + labels: + {{- include "keep.labels" . | nindent 4 }} +spec: + stripPrefix: + prefixes: + - {{ .Values.global.ingress.backendPrefix }} + - {{ .Values.global.ingress.websocketPrefix }} +{{- end }} \ No newline at end of file diff --git a/charts/keep/templates/ingress-traefik.yaml b/charts/keep/templates/ingress-traefik.yaml new file mode 100644 index 0000000..ebd6735 --- /dev/null +++ b/charts/keep/templates/ingress-traefik.yaml @@ -0,0 +1,96 @@ +{{- if and .Values.global.ingress.enabled (or (eq .Values.global.ingress.className "traefik") (eq .Values.global.ingress.classType "traefik")) }} +{{- $fullName := include "keep.fullname" . }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }}-ingress + labels: + {{- include "keep.labels" . | nindent 4 }} + annotations: + traefik.ingress.kubernetes.io/router.middlewares: {{ .Release.Namespace }}-strip-prefix@kubernetescrd + traefik.ingress.kubernetes.io/router.entrypoints: web + traefik.ingress.kubernetes.io/timeouts.idle: "3600s" + traefik.ingress.kubernetes.io/timeouts.connect: "3600s" + traefik.ingress.kubernetes.io/timeouts.read: "3600s" + traefik.ingress.kubernetes.io/timeouts.write: "3600s" + + # WebSocket configuration + traefik.ingress.kubernetes.io/websockets.enabled: "true" + {{- with .Values.global.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + +spec: + ingressClassName: traefik + {{- if .Values.global.ingress.tls }} + tls: + {{- range .Values.global.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- if .Values.global.ingress.hosts }} + {{- range .Values.global.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- if $.Values.websocket.enabled }} + - path: {{ $.Values.global.ingress.websocketPrefix }} + pathType: Prefix + backend: + service: + name: {{ $fullName }}-websocket + port: + number: {{ $.Values.websocket.service.port }} + {{- end }} + {{- if $.Values.backend.enabled }} + - path: {{ $.Values.global.ingress.backendPrefix }} + pathType: Prefix + backend: + service: + name: {{ $fullName }}-backend + port: + number: {{ $.Values.backend.service.port }} + {{- end }} + - path: "/" + pathType: Prefix + backend: + service: + name: {{ $fullName }}-frontend + port: + number: {{ $.Values.frontend.service.port }} + {{- end }} + {{- else }} + - http: + paths: + {{- if $.Values.websocket.enabled }} + - path: {{ $.Values.global.ingress.websocketPrefix }} + pathType: Prefix + backend: + service: + name: {{ $fullName }}-websocket + port: + number: {{ $.Values.websocket.service.port }} + {{- end }} + {{- if $.Values.backend.enabled }} + - path: {{ $.Values.global.ingress.backendPrefix }} + pathType: Prefix + backend: + service: + name: {{ $fullName }}-backend + port: + number: {{ $.Values.backend.service.port }} + {{- end }} + - path: "/" + pathType: Prefix + backend: + service: + name: {{ $fullName }}-frontend + port: + number: {{ $.Values.frontend.service.port }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/local_test_haproxy.sh b/local_test_haproxy.sh index 7d3daf7..0ba45bf 100644 --- a/local_test_haproxy.sh +++ b/local_test_haproxy.sh @@ -10,7 +10,7 @@ global: annotations: ingress.class: haproxy websocketPrefix: /websocket - backendPrefix: /v2/docs + backendPrefix: /v2 frontendPrefix: / frontend: enabled: true diff --git a/local_test_nginx.sh b/local_test_nginx.sh index 457e10c..ea4eeca 100644 --- a/local_test_nginx.sh +++ b/local_test_nginx.sh @@ -9,7 +9,7 @@ global: className: nginx classType: nginx websocketPrefix: /websocket - backendPrefix: /v2/docs + backendPrefix: /v2 frontendPrefix: / frontend: enabled: true diff --git a/local_test_traefik.sh b/local_test_traefik.sh new file mode 100644 index 0000000..f252f25 --- /dev/null +++ b/local_test_traefik.sh @@ -0,0 +1,162 @@ +#!/bin/bash + +# Create a debug folder and values file +mkdir -p debug +cat < debug/traefik-values.yaml +global: + ingress: + enabled: true + className: traefik + classType: traefik + websocketPrefix: /websocket + backendPrefix: /v2 + frontendPrefix: / +frontend: + enabled: true +backend: + enabled: true +websocket: + enabled: true +EOF + +# Create Kind cluster with ingress ports exposed +cat < debug/kind-config.yaml +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: +- role: control-plane + extraPortMappings: + - containerPort: 30080 + hostPort: 30080 + protocol: TCP + - containerPort: 443 + hostPort: 443 + protocol: TCP +EOF + +# Function to check service/pod status +check_status() { + echo "๐Ÿ” Checking all resources..." + kubectl get pods -A + kubectl get svc -A + kubectl get ingress -A +} + +# Function to check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Check required tools +for tool in kind kubectl helm wscat curl; do + if ! command_exists "$tool"; then + echo "โŒ Required tool not found: $tool" + exit 1 + fi +done + +# Create cluster +echo "๐Ÿš€ Creating Kind cluster..." +kind create cluster --config debug/kind-config.yaml + +# Install Traefik +echo "๐Ÿ“ฆ Adding Traefik helm repo..." +helm repo add traefik https://traefik.github.io/charts +helm repo update + +echo "๐Ÿ”ง Installing Traefik..." +helm install traefik traefik/traefik \ + --set installCRDs=true \ + --set ports.web.port=8000 \ + --set ports.web.exposedPort=80 \ + --set ports.web.nodePort=30080 \ + --set service.type=NodePort \ + --set ingressClass.enabled=true \ + --set ingressClass.isDefaultClass=true + +# Wait for Traefik controller +echo "โณ Waiting for Traefik controller..." +kubectl wait --namespace default \ + --for=condition=ready pod \ + --selector=app.kubernetes.io/name=traefik \ + --timeout=90s + +# Create middleware for path stripping +echo "๐Ÿ”ง Creating Traefik middleware..." +echo "โณ Waiting for Traefik CRDs to be available..." +kubectl wait --for condition=established --timeout=60s crd/middlewares.traefik.io + +# Install your chart +echo "๐Ÿ“Š Installing Keep chart..." +helm install keep ./charts/keep -f debug/traefik-values.yaml + +# Wait for all Keep components to be ready +for component in frontend backend websocket database; do + echo "โณ Waiting for Keep $component to be ready..." + kubectl wait --namespace default \ + --for=condition=ready pod \ + --selector=app.kubernetes.io/instance=keep,keep-component=$component \ + --timeout=120s +done + +# Check status +check_status + +# Function to test endpoint with retry +test_endpoint() { + local url=$1 + local expected=$2 + local max_attempts=5 + local attempt=1 + + while [ $attempt -le $max_attempts ]; do + echo "Attempt $attempt of $max_attempts..." + RESP=$(curl -s "$url") + if [[ $RESP == *"$expected"* ]]; then + echo "โœ… Response contains expected content" + return 0 + fi + echo "โณ Waiting before retry..." + sleep 5 + ((attempt++)) + done + echo "โŒ Failed to get expected response after $max_attempts attempts" + return 1 +} + +# Test endpoints +echo "๐Ÿงช Testing endpoints..." + +echo -e "\nFrontend (/) - Testing redirect:" +RESP=$(curl -s -I http://localhost/) +echo "$RESP" | grep "HTTP" +LOCATION=$(echo "$RESP" | grep -i "location") +echo "$LOCATION" + +echo -e "\nBackend (/v2/docs) - Testing API docs:" +test_endpoint "http://localhost/v2/docs" "Rest API powering" + +echo -e "\nWebSocket (/websocket) - Testing connection:" +timeout 5 wscat -c "ws://localhost/websocket/app/keepappkey?protocol=7&client=js&version=8.3.0&flash=false" || true + +# Show Traefik dashboard +echo -e "\n๐Ÿ“Š Traefik Dashboard available at: http://localhost:8080/dashboard/" + +# Show recent logs +echo -e "\n๐Ÿ“œ Traefik controller logs:" +kubectl logs -l app.kubernetes.io/name=traefik --tail=50 + +# Keep script running for debugging +echo -e "\n๐Ÿ”„ Debug session active. Press Ctrl+C to cleanup." +echo "๐Ÿ“ Quick commands:" +echo " - kubectl get pods -A" +echo " - kubectl describe ingress" +echo " - kubectl logs " +echo " - kubectl port-forward svc/traefik 8080:8080" +echo " - curl -v http://localhost/" +echo " - curl -v http://localhost/v2/docs/" +echo " - curl -v -H \"Upgrade: websocket\" -H \"Connection: Upgrade\" http://localhost/websocket/" + +# Cleanup on exit +trap "kind delete cluster" EXIT +read -r -d '' _