Skip to content

fix: apply Easypanel config changes via redeploy - #4

Merged
igun997 merged 1 commit into
masterfrom
fix/easypanel-config-redeploy-and-compose
Aug 4, 2026
Merged

fix: apply Easypanel config changes via redeploy#4
igun997 merged 1 commit into
masterfrom
fix/easypanel-config-redeploy-and-compose

Conversation

@igun997

@igun997 igun997 commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Root cause

Two separate failures formed one config-application bug:

  1. services.compose.updateSourceInline expects content; CLI/test sent composeContent, causing 400 ... zodErrors.content: Required.
  2. Easypanel mutation routes save env/source/domain config, but runtime + generated Traefik/compose override config changes only on deploy. Domain was added after first compose deploy, so Traefik returned 502 indefinitely while WordPress/Apache was healthy.

Minimal live proof:

create compose -> set source -> deploy -> create domain
GET pods-route-probe... => 502 for 60s+
services.compose.deployService => 200 in 0.8s
GET 5s later => 200 (traefik/whoami response)

Fix

  • App/compose env, source, image/build, deploy/resource, and domain mutations now automatically apply required deploy/restart behavior.
  • Compose deployment runs once for internal-service discovery, creates domains, then deploys once more to generate routing.
  • Domain deletion resolves authoritative destination before mutation, preserves legacy domain_id-only clients, validates stale caller context, retries deploy once, supports idempotent partial-success recovery, and returns explicit manual-deploy guidance if application fails.
  • CLI panel timeout raised from 30s to configurable EASYPANEL_HTTP_TIMEOUT (default 5m); reusable client raised from 60s. Compose pulls can block for minutes.

Other panel API drift fixed during schema audit

  • app Git/GitHub: ref + required path instead of unsupported branch
  • compose Git: required rootPath
  • domains: required wildcard; documented port/HTTPS/compose flags now implemented
  • updateDeploy: nested deploy object, not top-level replicas
  • logs: real routes are logs.queryServiceLogs / queryComposeServiceLogs; Loki-shaped response parsed into flat text + structured protobuf entries without float timestamp loss
  • nested tRPC/zod errors now surface as readable errors
  • compose updateResources/updateDeploy rejected with actionable guidance (panel routes do not exist)

Security hardening

Old line-prefix ports: sanitizer could be bypassed by quoted keys or inline mappings. New structural YAML sanitizer removes short/long-form/quoted/merged ports, preserves expose, rejects invalid shapes, and rejects network_mode: host.

Protobuf additions (backward wire-compatible)

  • GetLogsRequest: service type, compose service, limit
  • GetLogsResponse: structured LogEntry list while retaining flat logs
  • Domain requests: optional redeploy context; old RemoveDomain clients still supported by server-side target inference

Tests / evidence

Fresh local verification:

go test ./... -count=1 (without live env): PASS
go vet ./...: PASS
go build ./...: PASS

Live Easypanel E2E:

TestComposeFullStack       WordPress -> HTTP 200 after required second deploy
TestGRPC_DeployComposeE2E  sanitized ports + compose domain -> HTTP 200
TestGRPC_DeployContainerE2E default/custom domains -> HTTP 200; legacy RemoveDomain pass
TestGRPC_UpdateResources   resources + deploy config apply pass
pods leftovers: []

Added regression tests for structural Compose sanitization, host networking, YAML merge-key ports, invalid shapes, nil resources, legacy/compose domain target resolution, nanosecond log ordering, Loki flattening, and nested zod errors.

Final independent code review: no P0/P1 release blockers.

Summary by CodeRabbit

  • New Features
    • Domain management now supports app and Compose services, custom ports, HTTPS, wildcard domains, paths, and certificate resolvers.
    • Log retrieval supports service filtering, line limits, structured entries, and chronological output.
    • Configuration and source updates can automatically redeploy services.
  • Bug Fixes
    • Improved validation and clearer reporting for invalid configurations, unsupported operations, and deployment failures.
    • Compose deployments now reject unsafe networking and published ports.
  • Documentation
    • Added guidance for deployment timeouts and expanded service, domain, and log command documentation.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@igun997, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bcfba5bf-57dc-4a70-b8d6-9f8ed63c076c

📥 Commits

Reviewing files that changed from the base of the PR and between 336dd7d and 37ab25e.

⛔ Files ignored due to path filters (2)
  • go.sum is excluded by !**/*.sum
  • proto/paas.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (17)
  • .env.example
  • README.md
  • cmd/domains.go
  • cmd/panel_client.go
  • cmd/services.go
  • cmd/services_test.go
  • compose_test.go
  • go.mod
  • grpc_e2e_test.go
  • internal/easypanel/client.go
  • internal/easypanel/client_test.go
  • internal/server/compose_test.go
  • internal/server/logs.go
  • internal/server/logs_test.go
  • internal/server/paas.go
  • paas_test.go
  • proto/paas.proto
📝 Walkthrough

Walkthrough

The PR adds configurable EasyPanel HTTP timeouts, service-specific configuration and redeployment flows, Compose YAML sanitization, expanded domain routing, and structured log retrieval across the protocol, server, CLI, tests, and documentation.

Changes

Deployment, routing, and observability

Layer / File(s) Summary
Client contracts and error handling
.env.example, cmd/panel_client.go, internal/easypanel/client.go, internal/easypanel/client_test.go, README.md
HTTP timeouts now support EASYPANEL_HTTP_TIMEOUT with a five-minute default. API errors now expose sorted field errors and parse nested response envelopes.
Compose validation and deployment
internal/server/paas.go, internal/server/compose_test.go, compose_test.go, go.mod
Compose YAML is validated and sanitized structurally. Published ports and host networking are rejected or removed before deployment.
Domain lifecycle and redeployment
proto/paas.proto, cmd/domains.go, internal/server/paas.go, compose_test.go, paas_test.go, grpc_e2e_test.go, README.md
Domain operations now support app and Compose targets, routing options, authoritative target resolution, retries, and post-mutation redeployment.
Service configuration and redeployment
cmd/services.go, internal/server/paas.go, README.md, internal/server/compose_test.go
Configuration updates now validate service types, use corrected request fields, and redeploy supported services after persistence.
Structured log retrieval
proto/paas.proto, cmd/services.go, internal/server/logs.go, internal/server/paas.go, internal/server/logs_test.go, cmd/services_test.go, README.md
Log retrieval now supports service-specific routes, Compose filtering, limits, structured LogEntry records, chronological ordering, and flat text output.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

Domain mutation flow

sequenceDiagram
  participant CLI as domains create/remove
  participant PaaS as PaaS server
  participant EasyPanel as EasyPanel API
  CLI->>PaaS: submit domain request
  PaaS->>EasyPanel: create or delete domain
  PaaS->>EasyPanel: redeploy app or Compose service
  EasyPanel-->>PaaS: deployment result
  PaaS-->>CLI: domain and deployment status
Loading

Structured log flow

sequenceDiagram
  participant Client as GetLogs client
  participant PaaS as PaaS server
  participant EasyPanel as EasyPanel log route
  Client->>PaaS: request service logs and limit
  PaaS->>EasyPanel: query app or Compose logs
  EasyPanel-->>PaaS: Loki-shaped log response
  PaaS-->>Client: LogEntry records and flat text
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: applying EasyPanel configuration changes through redeployment.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/easypanel-config-redeploy-and-compose

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

316-317: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the new Compose rejections and the RemoveDomain behavior.

sanitizeCompose now does more than strip ports:. It rejects content without a top-level services mapping, rejects a service entry that is not a mapping, and rejects network_mode: host, all with InvalidArgument. Add these rules here. Also update the RemoveDomain row at line 185, because that RPC now redeploys routing and can return Aborted after a successful deletion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 316 - 317, Update the README’s Compose Port Security
section to document that sanitizeCompose rejects missing top-level services,
non-mapping service entries, and network_mode: host with InvalidArgument, in
addition to stripping ports. Revise the RemoveDomain table entry to state that
it redeploys routing and may return Aborted after a successful deletion.
🧹 Nitpick comments (3)
internal/server/paas.go (1)

768-774: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse the unsupported-type helper.

Lines 768-771 repeat the pattern of requireDeployConfigSupport. Only the route name and the remedy text differ. Extend the helper to take the missing route name, then call it here so all unsupported-compose errors stay consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/server/paas.go` around lines 768 - 774, Update
requireDeployConfigSupport to accept the missing route name and use it to
construct the unsupported-service error while preserving the existing remedy
text. Replace the inline compose check in the surrounding handler with a call to
requireDeployConfigSupport for the compose resource-update route, keeping the
existing resources validation unchanged.
cmd/domains.go (1)

135-144: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Add a delay between the two redeploy attempts.

The loop retries immediately after a failure, so a transient panel error is likely to repeat. RemoveDomain in internal/server/paas.go waits one second between attempts. Match that behavior.

♻️ Proposed change
 		for attempt := 0; attempt < 2; attempt++ {
 			deployErr = redeployAfterConfig(c, args[0], args[1], svcType)
 			if deployErr == nil {
 				break
 			}
+			if attempt == 0 {
+				time.Sleep(time.Second)
+			}
 		}

Add time to the imports.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/domains.go` around lines 135 - 144, Update the retry loop around
redeployAfterConfig in the domain removal flow to wait one second before the
second attempt, matching RemoveDomain’s retry behavior. Add the required time
dependency and preserve the existing two-attempt limit and error handling.
compose_test.go (1)

29-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Skip instead of falling back to a hardcoded panel domain.

Line 33 returns a specific panel host when DEFAULT_DOMAIN is unset. The test then sends requests to that third-party panel. paas_test.go already solves this with defaultPanelDomainOrSkip. Reuse the skip behavior here.

♻️ Proposed change
-func panelDomain() string {
-	if v := os.Getenv("DEFAULT_DOMAIN"); v != "" {
-		return v
-	}
-	return "cv911b.easypanel.host"
-}
+func panelDomain(t *testing.T) string {
+	t.Helper()
+	v := os.Getenv("DEFAULT_DOMAIN")
+	if v == "" {
+		t.Skip("DEFAULT_DOMAIN not set")
+	}
+	return v
+}

Update the two call sites at lines 194 and 347 to pass t.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@compose_test.go` around lines 29 - 34, Update panelDomain and its call sites
to use the existing defaultPanelDomainOrSkip behavior from paas_test.go instead
of returning the hardcoded cv911b.easypanel.host fallback. Pass t at both
callers around the existing panel-domain usages so tests skip when
DEFAULT_DOMAIN is unset.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/domains.go`:
- Around line 74-94: Update the destination construction in the domains create
flow to use the parsed path variable for destination.path instead of a hardcoded
root path. Validate svcType before invoking redeployAfterConfig, accepting only
app or compose and rejecting unknown --type values; preserve composeService’s
existing behavior of forcing compose.

In `@cmd/services.go`:
- Around line 304-308: Update the command flow around redeployAfterConfig so the
success message distinguishes whether deployment actually ran: retain “Env
updated and deployed” for app and compose services, and print an update-only
message for other service types where redeployAfterConfig performs no
deployment. Use the service type already available from args[2] and keep the
existing error handling unchanged.

In `@compose_test.go`:
- Around line 54-59: Update the poll loop around client.Get to build and execute
an HTTP request using the test context, satisfying noctx, and handle
request-construction or execution errors through the existing lastErr flow.
Before closing the successful response body, drain it with io.Copy and handle
any resulting errors so errcheck is satisfied and connections can be reused; add
the required context and io imports.

In `@internal/server/paas.go`:
- Around line 743-753: Update ScaleService to fetch the current inspect deploy
object before calling services.%s.updateDeploy, then merge only req.Replicas
into that object while preserving existing deploy settings such as zeroDowntime
and command. Pass the merged deploy payload to s.ep.Call, keeping the existing
deploy-config validation and scaling route intact.

---

Outside diff comments:
In `@README.md`:
- Around line 316-317: Update the README’s Compose Port Security section to
document that sanitizeCompose rejects missing top-level services, non-mapping
service entries, and network_mode: host with InvalidArgument, in addition to
stripping ports. Revise the RemoveDomain table entry to state that it redeploys
routing and may return Aborted after a successful deletion.

---

Nitpick comments:
In `@cmd/domains.go`:
- Around line 135-144: Update the retry loop around redeployAfterConfig in the
domain removal flow to wait one second before the second attempt, matching
RemoveDomain’s retry behavior. Add the required time dependency and preserve the
existing two-attempt limit and error handling.

In `@compose_test.go`:
- Around line 29-34: Update panelDomain and its call sites to use the existing
defaultPanelDomainOrSkip behavior from paas_test.go instead of returning the
hardcoded cv911b.easypanel.host fallback. Pass t at both callers around the
existing panel-domain usages so tests skip when DEFAULT_DOMAIN is unset.

In `@internal/server/paas.go`:
- Around line 768-774: Update requireDeployConfigSupport to accept the missing
route name and use it to construct the unsupported-service error while
preserving the existing remedy text. Replace the inline compose check in the
surrounding handler with a call to requireDeployConfigSupport for the compose
resource-update route, keeping the existing resources validation unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4df99f93-ee6c-4326-9eef-d698d08c0cb7

📥 Commits

Reviewing files that changed from the base of the PR and between 24f6f79 and 336dd7d.

⛔ Files ignored due to path filters (2)
  • go.sum is excluded by !**/*.sum
  • proto/paas.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (17)
  • .env.example
  • README.md
  • cmd/domains.go
  • cmd/panel_client.go
  • cmd/services.go
  • cmd/services_test.go
  • compose_test.go
  • go.mod
  • grpc_e2e_test.go
  • internal/easypanel/client.go
  • internal/easypanel/client_test.go
  • internal/server/compose_test.go
  • internal/server/logs.go
  • internal/server/logs_test.go
  • internal/server/paas.go
  • paas_test.go
  • proto/paas.proto

Comment thread cmd/domains.go
Comment thread cmd/services.go
Comment thread compose_test.go Outdated
Comment on lines +54 to +59
resp, err := client.Get(url)
if err != nil {
lastErr = err
} else {
last = resp.StatusCode
resp.Body.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Satisfy the noctx and errcheck linters in the poll loop.

golangci-lint flags client.Get at line 54 and the unchecked resp.Body.Close at line 59. Build a request with the test context and drain the body before closing. Draining also allows connection reuse across poll attempts.

🔧 Proposed change
 	for time.Now().Before(deadline) {
-		resp, err := client.Get(url)
+		req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
+		if reqErr != nil {
+			t.Fatalf("build request for %s: %v", url, reqErr)
+		}
+		resp, err := client.Do(req)
 		if err != nil {
 			lastErr = err
 		} else {
 			last = resp.StatusCode
-			resp.Body.Close()
+			_, _ = io.Copy(io.Discard, resp.Body)
+			_ = resp.Body.Close()
 			if resp.StatusCode < 400 {
 				return resp.StatusCode
 			}
 		}

Add context and io to the imports.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
resp, err := client.Get(url)
if err != nil {
lastErr = err
} else {
last = resp.StatusCode
resp.Body.Close()
req, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
if reqErr != nil {
t.Fatalf("build request for %s: %v", url, reqErr)
}
resp, err := client.Do(req)
if err != nil {
lastErr = err
} else {
last = resp.StatusCode
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
if resp.StatusCode < 400 {
return resp.StatusCode
}
}
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 59-59: Error return value of resp.Body.Close is not checked

(errcheck)


[error] 54-54: (*net/http.Client).Get must not be called. use (*net/http.Client).Do(*http.Request)

(noctx)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@compose_test.go` around lines 54 - 59, Update the poll loop around client.Get
to build and execute an HTTP request using the test context, satisfying noctx,
and handle request-construction or execution errors through the existing lastErr
flow. Before closing the successful response body, drain it with io.Copy and
handle any resulting errors so errcheck is satisfied and connections can be
reused; add the required context and io imports.

Source: Linters/SAST tools

Comment thread internal/server/paas.go
Root causes:
- updateSourceInline expects `content`, not `composeContent`
- Easypanel persists env/source/domain config but regenerates runtime/Traefik
  overrides only on deploy; domains created after initial compose deploy stayed 502
- compose test published host ports owned by Traefik
- panel deploy calls can exceed old 30/60s client timeouts

Changes:
- automatically deploy app/compose after env, source, build, deploy/resource,
  and domain mutations; compose creation deploys once for service discovery and
  once after domains
- resolve domain destination before deletion, preserve legacy ID-only RPC,
  validate stale caller context, retry routing deploy, and report partial success
- fix CLI payload schema: content/ref/path/rootPath/wildcard/nested deploy
- replace text ports stripping with structural YAML sanitization; reject host network
- use real logs.queryServiceLogs/queryComposeServiceLogs routes and parse Loki
  entries without timestamp precision loss
- raise configurable panel timeout to 5m and surface nested zod field errors
- extend GetLogs/Domain protobuf messages and regenerate Go bindings
- replace sleep-only/false-positive E2E checks with routed 2xx/3xx polling

Verified against live panel: WordPress compose HTTP 200 after post-domain redeploy;
gRPC app+compose deployment, resource/deploy updates, legacy RemoveDomain all pass.
@igun997
igun997 force-pushed the fix/easypanel-config-redeploy-and-compose branch from 336dd7d to 37ab25e Compare August 4, 2026 11:28
@igun997
igun997 merged commit 2703c81 into master Aug 4, 2026
4 checks passed
@igun997
igun997 deleted the fix/easypanel-config-redeploy-and-compose branch August 4, 2026 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant