Skip to content

Add custom SSH port support for dynamic and static nodepools#2026

Merged
Despire merged 26 commits intomasterfrom
feature/custom-ssh-port
Apr 1, 2026
Merged

Add custom SSH port support for dynamic and static nodepools#2026
Despire merged 26 commits intomasterfrom
feature/custom-ssh-port

Conversation

@m-brando
Copy link
Copy Markdown
Contributor

@m-brando m-brando commented Mar 30, 2026

  • Adds configurable SSH port support for static node pools, replacing the hardcoded port 22 -> optional sshPort field in the manifest
  • New dynamic nodepools (created via new terraform templates) use port 22522 by default, with the port confirmed from terraform output
  • Existing dynamic nodepools retain port 22 for backward compatibility via state transfer
  • Terraform templates output [IP, port] tuples; old templates outputting just IP. This will be handled in ansibler and kube-eleven stage as ssh port 22 for VMs
  • With this PR comes new templates Custom ssh port in terraform template claudie-config#32. But changes in PR also works with old templates. Migration from old to new templates also tested
  • This PR replaces the earlier PR Feature/custom ssh port nodepool #1997
  • Closes Feature: IP address pool #1837

Summary by CodeRabbit

  • New Features

    • Added configurable SSH port support for node pools, enabling SSH connections through non-standard ports. Defaults to port 22 when not specified.
    • Infrastructure provisioning and management services now fully support custom SSH port configurations for cluster operations.
  • Documentation

    • Updated API documentation, deployment examples, and platform-specific guides with the new optional sshPort field for node pool specifications.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR adds support for custom SSH ports on both static and dynamic nodepools throughout the Claudie platform, defaulting to port 22 when unspecified. Changes include API definitions, protobuf schemas, Ansible inventory templates, and service implementations.

Changes

Cohort / File(s) Summary
Documentation
docs/input-manifest/api-reference.md, docs/input-manifest/example.md, docs/input-manifest/providers/on-prem.md
Added documentation for the new optional sshPort field in static nodepool specifications, describing default behavior (port 22) and providing configuration examples.
API & CRD Definitions
internal/api/crd/inputmanifest/v1beta1/inputmanifest_types.go, internal/api/manifest/manifest.go, manifests/claudie/crd/claudie.io_inputmanifests.yaml, proto/spec/nodepool.proto
Added SshPort field to StaticNodePool and DynamicNodePool with validation constraints (minimum: 1, maximum: 65535) and kubebuilder annotations.
Nodepool Core Logic
internal/nodepools/nodepools.go, internal/api/manifest/utils.go
Introduced DefaultSSHPort constant, SSHPort() helper function for port resolution, and updated RandomNodePublicEndpoint() to return four values including computed SSH port.
Ansible Inventory Templates
services/ansibler/templates/all-node-inventory.goini, services/ansibler/templates/k8s-inventory.goini, services/ansibler/templates/lb-inventory.goini, services/ansibler/templates/proxy-envs.goini
Updated all inventory templates to explicitly set ansible_port from dynamic or static nodepool SSH port values instead of relying on defaults.
Service Controllers & Operators
services/claudie-operator/pkg/controller/controler_helpers.go, services/kube-eleven/internal/worker/service/internal/kube-eleven/kube_eleven.go, services/kube-eleven/internal/worker/service/internal/kube-eleven/types.go, services/kube-eleven/templates/kubeone.tpl
Extended manifest construction and cluster node discovery to populate and propagate SshPort field in nodepool configuration.
Manager & Terraformer Services
services/manager/internal/service/existing_state.go, services/manager/internal/service/healthcheck.go, services/terraformer/internal/worker/service/internal/cluster-builder/cluster_builder.go, services/terraformer/internal/worker/service/internal/templates/structures.go
Updated state transfer, health checks, and cluster building to handle custom SSH ports; added port extraction from Terraform node outputs and dynamic port assignment.
Testing Framework
services/testing-framework/utils.go
Modified Wireguard setup validation to compute and use per-nodepool SSH ports instead of hardcoded port 22.

Possibly related PRs

Suggested labels

feature, refresh-docs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The PR implements custom SSH port support for nodepools, which is related to issue #1837's goal of mitigating blacklisted IPs by enabling flexible network configurations, but does not directly address the core requirement of maintaining an IP address pool. Clarify how custom SSH ports directly support the IP pool feature described in #1837, or verify if this PR is intended as a prerequisite/foundational work for that feature.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add custom SSH port support for dynamic and static nodepools' accurately reflects the primary change: adding configurable SSH port support across nodepools instead of using hardcoded port 22.
Out of Scope Changes check ✅ Passed All changes are focused on adding SSH port configurability to nodepools, Terraform output parsing, Ansible templates, and kube-eleven/terraformer services—all supporting the custom SSH port feature described in objectives.
Docstring Coverage ✅ Passed Docstring coverage is 81.25% which is sufficient. The required threshold is 80.00%.

✏️ 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 feature/custom-ssh-port

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.

Copy link
Copy Markdown
Contributor

@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: 5

🧹 Nitpick comments (2)
proto/spec/nodepool.proto (1)

111-112: Consider documenting the accepted sshPort range next to the new proto fields.

You already document the 0 -> 22 fallback; adding expected bounds (e.g., 1-65535) here would reduce ambiguity across downstream consumers.

Also applies to: 144-145

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

In `@proto/spec/nodepool.proto` around lines 111 - 112, Update the proto field
comments to explicitly document the valid port range for sshPort and the other
similar fields (e.g., the field at lines 144-145) by stating that 0 falls back
to 22 and valid values are 1-65535 (or 0 for fallback), e.g., "SSH port for the
nodes in this node pool. Default 0 means port 22; valid range 1-65535 (0 =>
22)". Apply the same wording pattern to the other proto field referenced so
downstream consumers have unambiguous bounds.
services/terraformer/internal/worker/service/internal/cluster-builder/cluster_builder.go (1)

308-318: Consider using a map for deduplication.

The slices.Contains check inside the loop is O(n²). While nodepool counts are typically small, a map-based approach would be more idiomatic for collecting unique values.

♻️ Optional refactor using a map
 func sshPorts(pools []*spec.NodePool) []int32 {
-	var ports []int32
+	seen := make(map[int32]struct{})
 	for _, np := range pools {
 		port := nodepools.SSHPort(np)
-		if !slices.Contains(ports, port) {
-			ports = append(ports, port)
-		}
+		seen[port] = struct{}{}
+	}
+	ports := make([]int32, 0, len(seen))
+	for p := range seen {
+		ports = append(ports, p)
 	}
 	return ports
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/terraformer/internal/worker/service/internal/cluster-builder/cluster_builder.go`
around lines 308 - 318, The sshPorts function currently uses slices.Contains
inside a loop causing O(n²) behavior; change it to use a seen map (e.g.,
map[int32]struct{}) to deduplicate SSH ports: iterate pools, get port via
nodepools.SSHPort(np), check seen[port], if not present set
seen[port]=struct{}{} and append port to ports slice (this preserves first-seen
order and is O(n)). Update the sshPorts function to use this map-based
deduplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/api/crd/inputmanifest/v1beta1/inputmanifest_types.go`:
- Around line 126-128: The SshPort field lacks CRD validation so out-of-range
port values can be accepted; add kubebuilder validation markers to the SshPort
field in inputmanifest_types.go (the SshPort int32 `json:"sshPort,omitempty"`
declaration) by adding +kubebuilder:validation:Minimum=1 and
+kubebuilder:validation:Maximum=65535 above the field (preserving the existing
+optional comment) so the generated CRD enforces valid TCP port bounds.

In `@internal/api/manifest/manifest.go`:
- Around line 239-241: The SshPort field currently uses validate:"omitempty"
which permits out-of-range integers; update the struct tag on SshPort to
constrain values to the valid TCP port range (1–65535) by replacing or extending
the validate tag (e.g., validate:"omitempty,min=1,max=65535" or validator
equivalent such as gte/lte) on the SshPort int32 field in manifest.go so
negative values and values >65535 are rejected while preserving yaml/json tags.

In `@internal/api/manifest/utils.go`:
- Line 405: When building the stored spec where SshPort is set (the struct
literal that assigns SshPort: nodePool.SshPort), normalize the value so a zero
is converted to the documented static default 22 before persisting; update the
assignment to use a normalized value (e.g., computedPort := nodePool.SshPort; if
computedPort == 0 { computedPort = 22 }) and then assign SshPort: computedPort
so downstream consumers never see 0.

In
`@services/kube-eleven/internal/worker/service/internal/kube-eleven/kube_eleven.go`:
- Around line 202-203: The code is passing raw
nodepool.GetDynamicNodePool().SshPort (which is 0 when unset) into the template,
producing invalid SSH ports; normalize the value first by reading SshPort from
nodepool.GetDynamicNodePool(), set it to 22 when it is 0, and then pass that
normalized variable into the struct/template (do this for the SshPort assignment
around the SshPort: ... occurrence and the other occurrence at lines 213-214).
Ensure you reference and replace direct uses of
nodepool.GetDynamicNodePool().SshPort with the normalized local variable before
writing template data.

In `@services/manager/internal/service/rolling_update.go`:
- Line 62: The code unconditionally sets inner.SshPort =
nodepools.ClaudieSSHPort during the rolling-update clone which forces port 22522
for legacy pools; change the assignment to only apply when the cloned nodepool's
SSH port is unset/zero (i.e., if inner.SshPort == 0) so existing ports (like 22)
are preserved during clone; locate the assignment to inner.SshPort and replace
it with a conditional that keeps the original value unless it's missing, using
the symbols inner.SshPort and nodepools.ClaudieSSHPort.

---

Nitpick comments:
In `@proto/spec/nodepool.proto`:
- Around line 111-112: Update the proto field comments to explicitly document
the valid port range for sshPort and the other similar fields (e.g., the field
at lines 144-145) by stating that 0 falls back to 22 and valid values are
1-65535 (or 0 for fallback), e.g., "SSH port for the nodes in this node pool.
Default 0 means port 22; valid range 1-65535 (0 => 22)". Apply the same wording
pattern to the other proto field referenced so downstream consumers have
unambiguous bounds.

In
`@services/terraformer/internal/worker/service/internal/cluster-builder/cluster_builder.go`:
- Around line 308-318: The sshPorts function currently uses slices.Contains
inside a loop causing O(n²) behavior; change it to use a seen map (e.g.,
map[int32]struct{}) to deduplicate SSH ports: iterate pools, get port via
nodepools.SSHPort(np), check seen[port], if not present set
seen[port]=struct{}{} and append port to ports slice (this preserves first-seen
order and is O(n)). Update the sshPorts function to use this map-based
deduplication.
🪄 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: 1fda5339-715b-43f2-859f-d456fce61db3

📥 Commits

Reviewing files that changed from the base of the PR and between fea63ed and b83b736.

⛔ Files ignored due to path filters (1)
  • proto/pb/spec/nodepool.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (23)
  • docs/input-manifest/api-reference.md
  • docs/input-manifest/example.md
  • docs/input-manifest/providers/on-prem.md
  • internal/api/crd/inputmanifest/v1beta1/inputmanifest_types.go
  • internal/api/manifest/manifest.go
  • internal/api/manifest/utils.go
  • internal/nodepools/nodepools.go
  • manifests/claudie/crd/claudie.io_inputmanifests.yaml
  • proto/spec/nodepool.proto
  • services/ansibler/templates/all-node-inventory.goini
  • services/ansibler/templates/k8s-inventory.goini
  • services/ansibler/templates/lb-inventory.goini
  • services/ansibler/templates/proxy-envs.goini
  • services/claudie-operator/pkg/controller/controler_helpers.go
  • services/kube-eleven/internal/worker/service/internal/kube-eleven/kube_eleven.go
  • services/kube-eleven/internal/worker/service/internal/kube-eleven/types.go
  • services/kube-eleven/templates/kubeone.tpl
  • services/manager/internal/service/existing_state.go
  • services/manager/internal/service/healthcheck.go
  • services/manager/internal/service/rolling_update.go
  • services/terraformer/internal/worker/service/internal/cluster-builder/cluster_builder.go
  • services/terraformer/internal/worker/service/internal/templates/structures.go
  • services/testing-framework/utils.go

Comment thread internal/api/crd/inputmanifest/v1beta1/inputmanifest_types.go
Comment thread internal/api/manifest/manifest.go Outdated
Comment thread internal/api/manifest/utils.go Outdated
Comment thread services/kube-eleven/internal/worker/service/internal/kube-eleven/kube_eleven.go Outdated
Comment thread services/manager/internal/service/rolling_update.go Outdated
@m-brando m-brando added the test-set-ordinary Will select ordinary test-sets that tests the general functionally of building/modifying clusters label Mar 31, 2026
Copy link
Copy Markdown
Contributor

@Despire Despire left a comment

Choose a reason for hiding this comment

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

LGMT

@Despire Despire added this pull request to the merge queue Apr 1, 2026
Merged via the queue into master with commit b96cc1c Apr 1, 2026
@Despire Despire deleted the feature/custom-ssh-port branch April 1, 2026 07:07
@coderabbitai coderabbitai Bot mentioned this pull request Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test-set-ordinary Will select ordinary test-sets that tests the general functionally of building/modifying clusters

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: IP address pool

2 participants