Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 17 additions & 89 deletions .github/workflows/helm-test.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: Helm Chart Test

permissions:
contents: read

on:
push:
branches: [main]
Expand Down Expand Up @@ -29,12 +32,17 @@ jobs:
run: helm lint chart/

e2e:
name: Install on Kind
name: E2E Tests
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: "1.25"
cache-dependency-path: chart/test/go.sum

- uses: azure/setup-helm@v4

- uses: helm/kind-action@v1
Expand All @@ -46,98 +54,18 @@ jobs:
docker build -t ghcr.io/flanksource/facet:test .
kind load docker-image ghcr.io/flanksource/facet:test --name ct

- name: Install chart
run: |
helm install facet chart/ \
--set image.tag=test \
--set image.pullPolicy=Never \
--set facet.templatesDir=/app/examples \
--wait --timeout 120s
- name: Run e2e tests
working-directory: chart/test
env:
IMAGE_TAG: test
run: go test -v -timeout 15m ./...

- name: Debug pod logs on failure
if: failure()
run: |
echo "=== Pod status ==="
kubectl get pods -l app.kubernetes.io/name=facet -o wide
kubectl get pods -A -o wide
echo "=== Pod describe ==="
kubectl describe pods -l app.kubernetes.io/name=facet
kubectl describe pods -l app.kubernetes.io/name=facet -A
echo "=== Pod logs ==="
kubectl logs -l app.kubernetes.io/name=facet --tail=100 || true

- name: Verify healthz
run: |
kubectl port-forward svc/facet 3000:3000 &
sleep 3
curl -sf http://localhost:3000/healthz | tee /dev/stderr | grep -q '"status":"ok"'

- name: Verify render endpoint
run: |
HTTP_CODE=$(curl -s -w '%{http_code}' -X POST http://localhost:3000/render \
-H 'Content-Type: application/json' \
-d '{"template":"SimpleReport","format":"html","data":{"title":"CI Test","sections":[]}}' \
-o /tmp/render-output.html)
echo "HTTP status: $HTTP_CODE"
if [ "$HTTP_CODE" -ge 400 ]; then
echo "ERROR: render failed with HTTP $HTTP_CODE"
cat /tmp/render-output.html
echo ""
echo "=== Pod logs ==="
kubectl logs -l app.kubernetes.io/name=facet --tail=50 || true
exit 1
fi
SIZE=$(stat -c%s /tmp/render-output.html)
echo "Rendered HTML size: ${SIZE} bytes"
if [ "$SIZE" -lt 100 ]; then
echo "ERROR: rendered output too small"
cat /tmp/render-output.html
exit 1
fi
echo "Render test passed"

- name: Verify inline template PDF render
run: |
# Create a minimal inline template
TMPDIR=$(mktemp -d)
cat > "${TMPDIR}/Template.tsx" <<'TEMPLATE'
import React from 'react';
export default function Template({ data }: { data: any }) {
return (
<html>
<body>
<h1 style={{ color: '#2563eb' }}>{data.title || 'Inline Test'}</h1>
<p>Rendered from an inline template archive.</p>
</body>
</html>
);
}
TEMPLATE

# Package as tar.gz and send via multipart upload
tar -czf "${TMPDIR}/template.tar.gz" -C "${TMPDIR}" Template.tsx

HTTP_CODE=$(curl -s -w '%{http_code}' -X POST http://localhost:3000/render \
-F "archive=@${TMPDIR}/template.tar.gz" \
-F 'data={"title":"CI Inline PDF Test"}' \
-F 'options={"format":"pdf"}' \
-o /tmp/inline-output.pdf)
echo "HTTP status: $HTTP_CODE"
if [ "$HTTP_CODE" -ge 400 ]; then
echo "ERROR: inline PDF render failed with HTTP $HTTP_CODE"
cat /tmp/inline-output.pdf
echo ""
kubectl logs -l app.kubernetes.io/name=facet --tail=50 || true
exit 1
fi

# Validate PDF with ImageMagick (runs inside the pod since magick is in the image)
kubectl cp /tmp/inline-output.pdf $(kubectl get pod -l app.kubernetes.io/name=facet -o jsonpath='{.items[0].metadata.name}'):/tmp/test.pdf
POD=$(kubectl get pod -l app.kubernetes.io/name=facet -o jsonpath='{.items[0].metadata.name}')
kubectl exec "$POD" -- identify /tmp/test.pdf

SIZE=$(stat -c%s /tmp/inline-output.pdf)
echo "Inline PDF size: ${SIZE} bytes"
if [ "$SIZE" -lt 1000 ]; then
echo "ERROR: PDF too small, likely invalid"
exit 1
fi
echo "Inline template PDF render test passed"
kubectl logs -l app.kubernetes.io/name=facet -A --tail=100 || true
9 changes: 8 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,14 @@ RUN apt-get update && apt-get install -y \
curl \
unzip \
imagemagick \
bubblewrap \
socat \
ripgrep \
&& rm -rf /var/lib/apt/lists/*

# Install sandbox-runtime for template execution isolation
RUN npm install -g @anthropic-ai/sandbox-runtime

# Allow ImageMagick to read/write PDFs (blocked by default policy)
RUN sed -i 's/rights="none" pattern="PDF"/rights="read|write" pattern="PDF"/' /etc/ImageMagick-6/policy.xml 2>/dev/null || true

Expand Down Expand Up @@ -96,7 +102,8 @@ RUN cd /app && npm pack --pack-destination /app/ 2>/dev/null
ENV FACET_PACKAGE_PATH=/app/facet.tgz
RUN mv /app/flanksource-facet-*.tgz /app/facet.tgz

RUN mkdir -p /workspace /templates
RUN mkdir -p /workspace /templates /etc/facet
COPY srt-settings.json /etc/facet/srt-settings.json

# Set default working directory
WORKDIR /workspace
Expand Down
1 change: 1 addition & 0 deletions chart/.helmignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
*.swp
*.bak
*.tmp
test/
20 changes: 20 additions & 0 deletions chart/templates/configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{- if .Values.sandbox.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "facet.fullname" . }}-srt-settings
labels:
{{- include "facet.labels" . | nindent 4 }}
data:
srt-settings.json: |
{
"enableWeakerNestedSandbox": true,
"network": {
"allowedDomains": {{ .Values.sandbox.extraAllowedDomains | toJson }}
},
"filesystem": {
"allowWrite": ["/tmp", "/workspace", "/templates"],
"denyRead": ["/etc/shadow", "/root/.ssh"]
}
}
{{- end }}
20 changes: 20 additions & 0 deletions chart/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ spec:
{{- if .Values.facet.verbose }}
- --verbose
{{- end }}
{{- if .Values.sandbox.enabled }}
- --sandbox
- {{ .Values.sandbox.settingsPath }}
{{- end }}
ports:
- name: http
containerPort: 3000
Expand All @@ -58,6 +62,16 @@ spec:
port: http
initialDelaySeconds: 5
periodSeconds: 5
{{- if .Values.sandbox.enabled }}
securityContext:
capabilities:
add:
- SYS_ADMIN
volumeMounts:
- name: srt-settings
mountPath: /etc/facet
readOnly: true
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
Expand All @@ -66,3 +80,9 @@ spec:
env:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.sandbox.enabled }}
volumes:
- name: srt-settings
configMap:
name: {{ include "facet.fullname" . }}-srt-settings
{{- end }}
1 change: 1 addition & 0 deletions chart/test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.test
5 changes: 5 additions & 0 deletions chart/test/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: "3"
tasks:
test:
cmds:
- go run github.com/onsi/ginkgo/v2/ginkgo -v ./...
Loading
Loading