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
4 changes: 0 additions & 4 deletions .github/workflows/auto-assign.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@ name: Auto Assign
on:
issues:
types: [opened]
pull_request:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: 'Auto-assign issue'
uses: pozil/auto-assign-issue@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
assignees: mariomeyer
numOfAssignee: 1
5 changes: 5 additions & 0 deletions src/Console/Concerns/InteractsWithDockerComposeServices.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ trait InteractsWithDockerComposeServices
'rabbitmq',
'selenium',
'soketi',
'gotenberg',
];

/**
Expand Down Expand Up @@ -329,6 +330,10 @@ protected function replaceEnvVariables(string $project, array $services)
$environment = str_replace('RABBITMQ_HOST=127.0.0.1', 'RABBITMQ_HOST=rabbitmq', $environment);
}

if (in_array('gotenberg', $services)) {
$environment .= "\nGOTENBERG_URL=http://gotenberg:3000\n";
}

$environment = str_replace('# PHP_CLI_SERVER_WORKERS=4', 'PHP_CLI_SERVER_WORKERS=4', $environment);

file_put_contents($this->laravel->basePath('.env'), $environment);
Expand Down
11 changes: 11 additions & 0 deletions stubs/gotenberg.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
gotenberg:
image: 'gotenberg/gotenberg:8'
restart: unless-stopped
ports:
- '${SAIL_IP:-172.20.0.10}:${FORWARD_GOTENBERG_PORT:-3000}:3000'
networks:
- sail
healthcheck:
test: ['CMD', 'curl', '--fail', 'http://localhost:3000/health']
retries: 3
timeout: 5s
7 changes: 7 additions & 0 deletions stubs/helm/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,19 @@ opt in via `redis.useSentinel: true` in values.yaml.
{{- $redis := dict -}}
{{- $s3 := dict -}}
{{- $logging := dict -}}
{{- $gotenberg := dict -}}
{{- if .main -}}
{{- $database = .main.database | default dict -}}
{{- $redis = .main.redis | default dict -}}
{{- $s3 = .main.s3 | default dict -}}
{{- $logging = .main.logging | default dict -}}
{{- $gotenberg = .main.gotenberg | default dict -}}
{{- else -}}
{{- $database = .Values.database | default dict -}}
{{- $redis = .Values.redis | default dict -}}
{{- $s3 = .Values.s3 | default dict -}}
{{- $logging = .Values.logging | default dict -}}
{{- $gotenberg = .Values.gotenberg | default dict -}}
{{- end }}
- name: SAIL_LOG_MODE
value: {{ $logging.mode | default "both" | quote }}
Expand Down Expand Up @@ -271,6 +274,10 @@ opt in via `redis.useSentinel: true` in values.yaml.
value: {{ $s3.url }}
{{- end }}
{{- end }}
{{- if $gotenberg.enabled }}
- name: GOTENBERG_URL
value: {{ printf "http://%s-gotenberg:3000" (include "sail.name" .) | quote }}
{{- end }}
{{- end -}}

{{/*
Expand Down
1 change: 1 addition & 0 deletions stubs/helm/templates/deployment-web.stub
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"secret" (.Values.secret | default dict)
"name" (.Values.name | default "website")
"typesense" (.Values.typesense | default dict)
"gotenberg" (.Values.gotenberg | default dict)
"database" (.Values.database | default dict)
"redis" (.Values.redis | default dict)
"s3" (.Values.s3 | default dict)
Expand Down
1 change: 1 addition & 0 deletions stubs/helm/templates/deployment-worker.stub
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"secret" (.Values.secret | default dict)
"name" (.Values.name | default "website")
"typesense" (.Values.typesense | default dict)
"gotenberg" (.Values.gotenberg | default dict)
"database" (.Values.database | default dict)
"redis" (.Values.redis | default dict)
"s3" (.Values.s3 | default dict)
Expand Down
60 changes: 60 additions & 0 deletions stubs/helm/templates/gotenberg.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{{- $gotenberg := .Values.gotenberg | default dict }}
{{- if $gotenberg.enabled }}
{{- $appName := include "sail.name" . }}
{{- $gotenbergName := printf "%s-gotenberg" $appName }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $gotenbergName }}
labels:
{{- include "sail.labels" . | nindent 4 }}
app.kubernetes.io/component: gotenberg
spec:
replicas: {{ $gotenberg.replicas | default 1 }}
selector:
matchLabels:
app.kubernetes.io/name: {{ $gotenbergName }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ $gotenbergName }}
app.kubernetes.io/component: gotenberg
spec:
containers:
- name: gotenberg
image: {{ $gotenberg.image | default "gotenberg/gotenberg:8" }}
args: ["gotenberg", "--api-timeout={{ $gotenberg.apiTimeout | default "60s" }}"]
ports:
- containerPort: 3000
name: http
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 15
periodSeconds: 30
resources:
{{- toYaml ($gotenberg.resources | default (dict "requests" (dict "cpu" "100m" "memory" "256Mi") "limits" (dict "memory" "1Gi"))) | nindent 12 }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ $gotenbergName }}
labels:
{{- include "sail.labels" . | nindent 4 }}
app.kubernetes.io/component: gotenberg
spec:
selector:
app.kubernetes.io/name: {{ $gotenbergName }}
ports:
- port: 3000
targetPort: 3000
name: http
{{- end }}
9 changes: 9 additions & 0 deletions stubs/helm/values.stub
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,15 @@ typesense:
storageSize: 2Gi
storageClassName: ""

# Gotenberg HTML→PDF rendering sidecar (deploys Gotenberg when enabled).
# Stateless and internal-only — no PVC, no ingress. When enabled, the web,
# worker, and scheduler pods receive GOTENBERG_URL=http://{appname}-gotenberg:3000.
gotenberg:
enabled: true
image: gotenberg/gotenberg:8
replicas: 1
apiTimeout: 60s

# Ingress settings (e.g., for Traefik or NGINX)
ingress:
enabled: true
Expand Down
21 changes: 21 additions & 0 deletions tests/Feature/BuildCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,30 @@

use Illuminate\Support\Facades\File;
use Laravel\Sail\Tests\TestCase;
use Symfony\Component\Yaml\Yaml;

class BuildCommandTest extends TestCase
{
public function test_gotenberg_compose_stub_is_valid_and_internal_only(): void
{
$stub = realpath(__DIR__.'/../../stubs/gotenberg.stub');
$this->assertNotFalse($stub, 'stubs/gotenberg.stub must exist');

$parsed = Yaml::parseFile($stub);
$this->assertArrayHasKey('gotenberg', $parsed, 'stub must define a top-level "gotenberg" service');

$service = $parsed['gotenberg'];
$this->assertSame('gotenberg/gotenberg:8', $service['image']);
$this->assertContains('sail', $service['networks'], 'gotenberg must join the sail network');
$this->assertArrayHasKey('healthcheck', $service, 'gotenberg must define a /health healthcheck');
$this->assertStringContainsString('/health', implode(' ', $service['healthcheck']['test']));

// Stateless sidecar: no volumes (it must not appear in the volume-creation
// allowlist in InteractsWithDockerComposeServices either).
$this->assertArrayNotHasKey('volumes', $service, 'gotenberg is stateless and must not declare volumes');
}


protected string $testBasePath;

protected function setUp(): void
Expand Down
75 changes: 75 additions & 0 deletions tests/Feature/HelmTemplateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,81 @@ public function test_top_level_resources_used_when_tier_unset(): void
);
}

public function test_gotenberg_deployment_and_service_render_when_enabled(): void
{
// values.stub ships gotenberg.enabled: true, so the baseline render
// already includes it; assert explicitly anyway for clarity.
$out = $this->renderChart(['gotenberg' => ['enabled' => true]]);

$this->assertMatchesRegularExpression(
'/kind:\s*Deployment\n[^-]*?name:\s*testapp-gotenberg/s',
$out,
'Expected a Deployment named testapp-gotenberg'
);
$this->assertMatchesRegularExpression(
'/kind:\s*Service\n[^-]*?name:\s*testapp-gotenberg/s',
$out,
'Expected a Service named testapp-gotenberg'
);

// Grab the gotenberg Deployment specifically. Matching on a bare substring
// would wrongly hit the web Deployment, whose env block contains the string
// "testapp-gotenberg" inside the GOTENBERG_URL value — so match the
// metadata name on its own line instead.
$gotenberg = '';
foreach (preg_split("/^---\s*\n/m", $out) as $doc) {
if (preg_match('/kind:\s*Deployment/', $doc) && preg_match('/^\s*name:\s*testapp-gotenberg\s*$/m', $doc)) {
$gotenberg = $doc;
break;
}
}
$this->assertStringContainsString('image: gotenberg/gotenberg:8', $gotenberg);
$this->assertStringContainsString('path: /health', $gotenberg);
$this->assertStringContainsString('containerPort: 3000', $gotenberg);
}

public function test_gotenberg_omitted_when_disabled(): void
{
$out = $this->renderChart(['gotenberg' => ['enabled' => false]]);

$this->assertStringNotContainsString('testapp-gotenberg', $out,
'Gotenberg Deployment/Service must not render when gotenberg.enabled is false');
}

public function test_gotenberg_renders_when_values_is_null(): void
{
// Regression: `gotenberg: null` in consumer values would panic helm template
// (nil pointer evaluating .Values.gotenberg.enabled) if the template didn't
// bind `.Values.gotenberg | default dict` before dereferencing. Mirrors the
// existing app:null guard. The chart must still render and simply omit gotenberg.
$out = $this->renderChart(['gotenberg' => null]);

$this->assertStringContainsString('kind: Deployment', $out, 'chart must still render with gotenberg: null');
$this->assertStringNotContainsString('testapp-gotenberg', $out,
'gotenberg must be omitted (not error) when its values block is null');
}

public function test_gotenberg_url_wired_into_all_laravel_pods(): void
{
$out = $this->renderChart(['gotenberg' => ['enabled' => true]]);

// GOTENBERG_URL flows through the shared sail.laravelEnv helper, so it
// appears on web, worker, and scheduler — the same three tiers as SAIL_LOG_*.
$this->assertSame(
3,
preg_match_all('#name:\s*GOTENBERG_URL\s*\n\s*value:\s*"http://testapp-gotenberg:3000"#', $out),
'GOTENBERG_URL=http://testapp-gotenberg:3000 must appear on web, worker, and scheduler'
);
}

public function test_gotenberg_url_absent_when_disabled(): void
{
$out = $this->renderChart(['gotenberg' => ['enabled' => false]]);

$this->assertSame(0, preg_match_all('/name:\s*GOTENBERG_URL/', $out),
'GOTENBERG_URL must not be wired into any pod when gotenberg is disabled');
}

/**
* Returns the YAML document with the matching `kind:` from a multi-doc helm
* template render. Documents are separated by `^---`.
Expand Down
Loading