Skip to content

fix: enforce Swarm service name length limits#364

Merged
moizpgedge merged 2 commits intomainfrom
feat/PLAT-562/Enforce-63-char-limit-on-Docker-Swarm-service-names
Apr 24, 2026
Merged

fix: enforce Swarm service name length limits#364
moizpgedge merged 2 commits intomainfrom
feat/PLAT-562/Enforce-63-char-limit-on-Docker-Swarm-service-names

Conversation

@moizpgedge
Copy link
Copy Markdown
Contributor

@moizpgedge moizpgedge commented Apr 22, 2026

Docker Swarm rejects service names longer than 63 characters.
ServiceInstanceName produces {databaseID}-{serviceID}-{8charHash},
consuming 10 characters of that budget, leaving 53 for the two IDs
combined. Previously there was no guard — oversized IDs passed API
validation, entered the workflow, and failed deep in the orchestrator
with an opaque Docker error.

Two constraints are now enforced at the API validation layer:

  • Identifier max length tightened from 63 → 36 characters (regex,
    Goa design, and error message all updated).
  • Cross-field budget check added to validateServiceSpec:
    len(databaseID) + len(serviceID) must be ≤ 53. Applies to both
    create-database and update-database.

New tests: TestValidateID covers the 36-char boundary and all
invalid formats; TestValidateServiceSpec_NameBudget covers the
combined budget at, below, and above the limit.

PLAT-562

Summary

Docker Swarm enforces a 63-character limit on service names. Because
service names are generated as {databaseID}-{serviceID}-{8charHash},
the combined budget for the two IDs is 53 characters. This PR adds
validation that catches oversized IDs at the API layer before any
workflow starts, replacing a silent orchestrator failure with a clear
400 error.

Changes

  • Identifier max length tightened from 63 → 36 characters — regex
    pattern, Goa design type, and error message all updated; Goa code
    regenerated
  • validateServiceSpec now receives databaseID and rejects requests
    where len(databaseID) + len(serviceID) > 53
  • validateDatabaseSpec and validateDatabaseUpdate updated to pass
    the database ID through to validateServiceSpec

Testing

# Unit tests
go test ./server/internal/api/apiv1/... -run TestValidateServiceSpec_NameBudget -v
go test ./server/internal/utils/... -run TestValidateID -v

**# Full suite
go test ./...**
  • Test 1 — DB ID > 36 chars rejected → HTTP 400
  • Test 2 — Service ID > 36 chars rejected → HTTP 400
  • Test 3 — Combined > 53 rejected (each valid alone) → HTTP 400
  • Test 4 — Combined exactly 53 accepted → HTTP 200
  • Test 5 — Short IDs regression check → HTTP 200
  • Test 6a — update-database budget overflow rejected → HTTP 400
  • Test 6b — update-database within budget accepted → HTTP 200
  • Test 7a — UUID DB ID + short service ID accepted → HTTP 200
  • Test 7b — UUID DB ID + long service ID rejected → HTTP 400

Docker Swarm rejects service names longer than 63 characters.
`ServiceInstanceName` produces `{databaseID}-{serviceID}-{8charHash}`,
consuming 10 characters of that budget, leaving 53 for the two IDs
combined. Previously there was no guard — oversized IDs passed API
validation, entered the workflow, and failed deep in the orchestrator
with an opaque Docker error.

Two constraints are now enforced at the API validation layer:

- `Identifier` max length tightened from 63 → 36 characters (regex,
  Goa design, and error message all updated).
- Cross-field budget check added to `validateServiceSpec`:
  `len(databaseID) + len(serviceID)` must be ≤ 53. Applies to both
  `create-database` and `update-database`.

New tests: `TestValidateID` covers the 36-char boundary and all
invalid formats; `TestValidateServiceSpec_NameBudget` covers the
combined budget at, below, and above the limit.

PLAT-562
@moizpgedge moizpgedge marked this pull request as draft April 22, 2026 18:51
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@moizpgedge has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 48 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 50 minutes and 48 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c54287c0-68f5-446f-98c8-f303403f8165

📥 Commits

Reviewing files that changed from the base of the PR and between c410a98 and 1be8d28.

📒 Files selected for processing (1)
  • server/internal/utils/utils.go
📝 Walkthrough

Walkthrough

This change tightens identifier length limits from 1–63 to 1–36 characters and threads a databaseID into service validation to enforce a Docker Swarm name budget: the concatenation of databaseID + service_id must not exceed 53 characters.

Changes

Cohort / File(s) Summary
Identifier constraint
api/apiv1/design/common.go, server/internal/utils/utils.go
Reduced max identifier length from 63 → 36; updated human-readable error text, regex/id pattern, and DSL validator to enforce 1–36 characters.
Validation plumbing & rule
server/internal/api/apiv1/convert.go, server/internal/api/apiv1/validate.go
Threaded databaseID through validateDatabaseSpecvalidateServiceSpec; added check that databaseID + service_id length must be ≤ 53 and returns a path-scoped validation error for service_id when exceeded.
Tests
server/internal/api/apiv1/validate_test.go, server/internal/utils/utils_test.go
Updated test calls to pass databaseID; added TestValidateServiceSpec_NameBudget for combined-length boundary and TestValidateID covering valid/invalid IDs and max-length behavior.

Poem

🐰
Thirty-six hops, neat and spry,
Names trimmed short beneath the sky,
Database plus service aligned,
Fifty-three's the limit we find,
A little rabbit cheers: "Nice and tidy!" 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The title clearly and concisely summarizes the main change: enforcing Docker Swarm service name length limits, which is the core focus of all modifications across the codebase.
Description check ✅ Passed The PR description provides a comprehensive summary, detailed changes list, testing instructions with specific test commands, a checklist with required items completed, and reviewer notes. The issue link (PLAT-562) is clearly referenced. All required template sections are present and well-populated.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/PLAT-562/Enforce-63-char-limit-on-Docker-Swarm-service-names

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 and usage tips.

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented Apr 22, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes. Give us feedback

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
server/internal/api/apiv1/validate_test.go (1)

1882-1911: Nice boundary coverage; consider also asserting the error path is tied to service_id.

The 53/54 boundary assertions are good. Optional: assert the error message is attached to the service_id field path (e.g., assert.ErrorContains(t, err, "service_id:")) so a future refactor that moves the check or changes its path is caught.

Proposed tweak
-	err = errors.Join(validateServiceSpec(baseSvc(svcID27), nil, false, dbID27, testDBUsers)...)
-	assert.ErrorContains(t, err, "database ID and service ID combined must not exceed 53 characters (got 54)")
+	err = errors.Join(validateServiceSpec(baseSvc(svcID27), nil, false, dbID27, testDBUsers)...)
+	assert.ErrorContains(t, err, "service_id:")
+	assert.ErrorContains(t, err, "must not exceed 53 characters")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/internal/api/apiv1/validate_test.go` around lines 1882 - 1911, Update
TestValidateServiceSpec_NameBudget to assert that the failure for the 54-char
combined ID is tied to the service_id path: after calling validateServiceSpec
for the dbID27 case (using baseSvc(svcID27) and testDBUsers) capture the joined
error (err) and add an assertion like assert.ErrorContains(t, err,
"service_id:") so the test ensures the validation error is reported on the
service_id field; reference validateServiceSpec and
TestValidateServiceSpec_NameBudget when locating where to add this extra
assertion.
server/internal/api/apiv1/validate.go (1)

313-317: Budget check looks correct; small UX tweak on the error message.

The arithmetic is right: {databaseID}-{serviceID}-{8hash}len(db)+len(svc)+10 ≤ 63len(db)+len(svc) ≤ 53. Two minor suggestions:

  1. Include the current database ID length in the error so users understand why a short service_id still fails: e.g. "database ID (%d) + service ID (%d) must be ≤ 53 characters (Docker Swarm service name limit)".
  2. Since databaseID is a server-owned value on create-database (UUID = 36 chars), users writing a 20-char service_id will be rejected with a message that blames only the combined length. Surfacing the db-id length makes it clearer that they have only 53 - len(databaseID) characters available.
Proposed message tweak
-		err := fmt.Errorf("database ID and service ID combined must not exceed 53 characters (got %d)", len(databaseID)+len(string(svc.ServiceID)))
+		err := fmt.Errorf("database ID (%d chars) and service ID (%d chars) combined must not exceed 53 characters — required by Docker Swarm service name limit",
+			len(databaseID), len(string(svc.ServiceID)))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/internal/api/apiv1/validate.go` around lines 313 - 317, Update the
validation error message emitted in the check that enforces
"{databaseID}-{serviceID}-{8charHash}" ≤ 63 (the block using databaseID,
svc.ServiceID, serviceIDPath and newValidationError): include the current
lengths of databaseID and serviceID and the remaining allowed characters so
users understand the constraint (e.g. "database ID (%d) + service ID (%d) must
be ≤ 53 characters (Docker Swarm service name limit); you have %d characters
left for service_id"), and keep the error constructed via
newValidationError(serviceIDPath) as before.
server/internal/utils/utils_test.go (1)

73-110: Good boundary coverage; consider adding an explicit 36-char UUID-lookalike mix and lower-bound length-1 is already covered.

Nit: "abcdefghijklmnopqrstuvwxyz1234567890" (36) and the UUID (36) both exercise the max — clear. You may also want one 35-char case and/or a hyphen-ending/starting test that's currently covered by leading/trailing-hyphen invalids.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/internal/utils/utils_test.go` around lines 73 - 110, Add an explicit
35-character positive test case to TestValidateID so the off-by-one lower/upper
boundary is exercised separately from the existing 36-char cases: update the
valid slice inside TestValidateID to include a 35-char string (e.g., a mix of
letters/digits, distinct from the 36-char UUID and the other 36-char sample) and
keep the existing invalid leading/trailing-hyphen and 37-char negative cases
unchanged; this ensures ValidateID is tested for length==35 as well as the
36-char max.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/apiv1/design/common.go`:
- Around line 8-15: The change tightens the Identifier max length to 36 which is
a breaking change for existing databases; revert or avoid enforcing 36 during
updates by restoring g.MaxLength(63) for the shared Identifier definition and
instead enforce the 36-char limit only for new creations (e.g., add a separate
validation path or check in the create handler that validates Identifier ≤ 36),
or modify validateDatabaseUpdate to allow existing database_id values >36 while
still rejecting new/changed IDs >36; update references to Identifier,
validateDatabaseUpdate, and any create/update handlers so create operations
enforce 36 chars but existing database_id values are accepted during
update-database validation.

---

Nitpick comments:
In `@server/internal/api/apiv1/validate_test.go`:
- Around line 1882-1911: Update TestValidateServiceSpec_NameBudget to assert
that the failure for the 54-char combined ID is tied to the service_id path:
after calling validateServiceSpec for the dbID27 case (using baseSvc(svcID27)
and testDBUsers) capture the joined error (err) and add an assertion like
assert.ErrorContains(t, err, "service_id:") so the test ensures the validation
error is reported on the service_id field; reference validateServiceSpec and
TestValidateServiceSpec_NameBudget when locating where to add this extra
assertion.

In `@server/internal/api/apiv1/validate.go`:
- Around line 313-317: Update the validation error message emitted in the check
that enforces "{databaseID}-{serviceID}-{8charHash}" ≤ 63 (the block using
databaseID, svc.ServiceID, serviceIDPath and newValidationError): include the
current lengths of databaseID and serviceID and the remaining allowed characters
so users understand the constraint (e.g. "database ID (%d) + service ID (%d)
must be ≤ 53 characters (Docker Swarm service name limit); you have %d
characters left for service_id"), and keep the error constructed via
newValidationError(serviceIDPath) as before.

In `@server/internal/utils/utils_test.go`:
- Around line 73-110: Add an explicit 35-character positive test case to
TestValidateID so the off-by-one lower/upper boundary is exercised separately
from the existing 36-char cases: update the valid slice inside TestValidateID to
include a 35-char string (e.g., a mix of letters/digits, distinct from the
36-char UUID and the other 36-char sample) and keep the existing invalid
leading/trailing-hyphen and 37-char negative cases unchanged; this ensures
ValidateID is tested for length==35 as well as the 36-char max.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 81cd2824-2822-4b21-bdbc-4dbd766edd42

📥 Commits

Reviewing files that changed from the base of the PR and between 7297fab and 3b98b40.

⛔ Files ignored due to path filters (9)
  • api/apiv1/gen/control_plane/service.go is excluded by !**/gen/**
  • api/apiv1/gen/http/control_plane/client/cli.go is excluded by !**/gen/**
  • api/apiv1/gen/http/control_plane/client/types.go is excluded by !**/gen/**
  • api/apiv1/gen/http/control_plane/server/encode_decode.go is excluded by !**/gen/**
  • api/apiv1/gen/http/control_plane/server/types.go is excluded by !**/gen/**
  • api/apiv1/gen/http/openapi.json is excluded by !**/gen/**
  • api/apiv1/gen/http/openapi.yaml is excluded by !**/gen/**
  • api/apiv1/gen/http/openapi3.json is excluded by !**/gen/**
  • api/apiv1/gen/http/openapi3.yaml is excluded by !**/gen/**
📒 Files selected for processing (6)
  • api/apiv1/design/common.go
  • server/internal/api/apiv1/convert.go
  • server/internal/api/apiv1/validate.go
  • server/internal/api/apiv1/validate_test.go
  • server/internal/utils/utils.go
  • server/internal/utils/utils_test.go

Comment thread api/apiv1/design/common.go
@moizpgedge moizpgedge marked this pull request as ready for review April 22, 2026 19:07
@moizpgedge moizpgedge force-pushed the feat/PLAT-562/Enforce-63-char-limit-on-Docker-Swarm-service-names branch from 3b98b40 to c410a98 Compare April 22, 2026 20:19
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/internal/utils/utils.go`:
- Around line 129-130: Update the validation error text in ErrInvalidIdentifier
to mention digits/numbers as allowed characters to match the regex idPattern;
locate the ErrInvalidIdentifier declaration and change its message to include
"digits" or "numbers" (e.g., "contain only lowercase letters, digits and
hyphens") while keeping the rest of the constraints (1-36 chars, start/end with
letter or number, no consecutive hyphens) unchanged.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f19f7d69-caec-4b56-b5d3-6af91c3d991f

📥 Commits

Reviewing files that changed from the base of the PR and between 3b98b40 and c410a98.

⛔ Files ignored due to path filters (9)
  • api/apiv1/gen/control_plane/service.go is excluded by !**/gen/**
  • api/apiv1/gen/http/control_plane/client/cli.go is excluded by !**/gen/**
  • api/apiv1/gen/http/control_plane/client/types.go is excluded by !**/gen/**
  • api/apiv1/gen/http/control_plane/server/encode_decode.go is excluded by !**/gen/**
  • api/apiv1/gen/http/control_plane/server/types.go is excluded by !**/gen/**
  • api/apiv1/gen/http/openapi.json is excluded by !**/gen/**
  • api/apiv1/gen/http/openapi.yaml is excluded by !**/gen/**
  • api/apiv1/gen/http/openapi3.json is excluded by !**/gen/**
  • api/apiv1/gen/http/openapi3.yaml is excluded by !**/gen/**
📒 Files selected for processing (6)
  • api/apiv1/design/common.go
  • server/internal/api/apiv1/convert.go
  • server/internal/api/apiv1/validate.go
  • server/internal/api/apiv1/validate_test.go
  • server/internal/utils/utils.go
  • server/internal/utils/utils_test.go
✅ Files skipped from review due to trivial changes (2)
  • api/apiv1/design/common.go
  • server/internal/utils/utils_test.go

Comment thread server/internal/utils/utils.go Outdated
@moizpgedge moizpgedge merged commit e011d68 into main Apr 24, 2026
3 checks passed
@jason-lynch jason-lynch deleted the feat/PLAT-562/Enforce-63-char-limit-on-Docker-Swarm-service-names branch April 27, 2026 18:19
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.

2 participants