Skip to content

Fix Dell OME authentication token handling - #136

Open
stefanhipfel wants to merge 10 commits into
mainfrom
fix-dell-ome-auth
Open

Fix Dell OME authentication token handling#136
stefanhipfel wants to merge 10 commits into
mainfrom
fix-dell-ome-auth

Conversation

@stefanhipfel

@stefanhipfel stefanhipfel commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

OME returns the session token in the X-Auth-Token response header — not the JSON body. Subsequent requests must also carry it via X-Auth-Token, not Authorization: Bearer.

Signed-off-by: Stefan Hipfel stefan.hipfel@sap.com

Summary by CodeRabbit

  • New Features

    • Added support for authentication via custom token header names and Basic Auth.
    • Enhanced Dell discovery payloads and server/device handling, including OData pagination metadata.
  • Bug Fixes

    • Improved authentication/token handling across vendors and test sessions.
    • Updated HPE and Lenovo import/unmanage flows to handle paging, identifiers, accepted response codes, and hostname/FQDN matching more reliably.
    • Server deletion/import now better tolerates active-profile scenarios and continues where appropriate.

…ions

kubebuilder edit overwrites dist/chart/templates/manager/manager.yaml on
every run. Add patches to the helm target to re-apply the POD_NAMESPACE
env fieldRef (required for telemetry/leader-election namespace resolution)
and the ports values template after regeneration.

Signed-off-by: Stefan Hipfel <stefan.hipfel@sap.com>
Instead of patching manager.yaml after kubebuilder regenerates it,
set POD_NAMESPACE as a default in values.yaml so make helm produces
a stable, committable result without any post-processing.

Also remove the ports customization from values.yaml since container
port declarations are informational only — the Service handles routing.

Signed-off-by: Stefan Hipfel <stefan.hipfel@sap.com>
… header

OME returns the session token in the X-Auth-Token response header, not the
JSON body. Subsequent requests must also send the token via X-Auth-Token
rather than Authorization: Bearer.

Signed-off-by: Stefan Hipfel <stefan.hipfel@sap.com>
@stefanhipfel
stefanhipfel requested a review from a team as a code owner August 4, 2026 15:45
@github-actions github-actions Bot added the bug Something isn't working label Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 47 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d40bdcfc-81d5-4b58-a68a-37024ef8ac1c

📥 Commits

Reviewing files that changed from the base of the PR and between b7bf9ac and 7e1e56f.

📒 Files selected for processing (1)
  • internal/hwmgr/dell.go
📝 Walkthrough

Walkthrough

The hardware manager clients now use updated token handling, paginated inventory lookup, and revised discovery and removal requests. The vendor console controller now matches hostnames in more than one form and skips active-profile removal errors.

Changes

Hardware manager updates

Layer / File(s) Summary
Token auth transport
internal/hwmgr/httpclient.go
The shared client now supports Basic Auth and custom token headers.
Dell session and inventory
internal/hwmgr/dell.go, internal/hwmgr/mock/server.go
Dell auth reads X-Auth-Token from the session response. The mock server returns that header. Dell listing now follows pagination and maps Dell device records into shared devices.
Dell removal and discovery payloads
internal/hwmgr/dell.go
Dell discovery payloads now include WSMAN and REDFISH credentials, target flags, scheduling, group creation, and trap settings. Server removal now uses paginated lookup and DELETE on the device endpoint.
HPE inventory and active profile errors
internal/hwmgr/types.go, internal/hwmgr/hpe.go
The shared error set now includes ErrServerHasActiveProfile. HPE import, removal, and listing now use the updated request and paging flow.
Lenovo discovery, unmanage, and job lookup
internal/hwmgr/lenovo.go
Lenovo discovery and unmanage requests now use array payloads and broader success codes. Hostname resolution now uses FQDN and domain data. Job polling now requests the expanded node fields.
Controller hostname and removal handling
internal/controller/vendorconsole/console_controller.go
Managed-server lookup and status checks now match full and short hostnames. Removal now skips active-profile errors.

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

Suggested labels: api-change, size/XL

Suggested reviewers: afritzler

Sequence Diagram(s)

sequenceDiagram
  participant DellClient
  participant HttpClient
  participant OME
  participant MockServer
  DellClient->>HttpClient: authenticate session request
  HttpClient->>OME: POST SessionService/Sessions
  OME-->>HttpClient: 201 + X-Auth-Token
  HttpClient->>DellClient: store token
  DellClient->>OME: follow paginated device queries
  OME-->>DellClient: Dell device pages
  MockServer-->>HttpClient: X-Auth-Token for session tests
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the authentication fix but omits the required Proposed Changes and Fixes sections from the template. Add the required Proposed Changes section with bullets and include the applicable issue reference after Fixes #.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: Dell OME authentication token handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-dell-ome-auth

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.

Required to keep controller tests passing after the Dell OME auth fix.

Signed-off-by: Stefan Hipfel <stefan.hipfel@sap.com>
LXCA does not issue session tokens — it requires HTTP Basic Auth on every
request. Also fixes JoinPath calls that incorrectly URL-encoded query string
separators, causing 404s on /nodes and /manageRequest endpoints.

Signed-off-by: Stefan Hipfel <stefan.hipfel@sap.com>

@xkonni xkonni left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

just one thing, lgtm otherwise.

Comment thread internal/hwmgr/dell.go
return "", fmt.Errorf("error executing auth request: %w", err)
}
defer res.Body.Close() //nolint:errcheck
if res.StatusCode != http.StatusCreated {

@xkonni xkonni Aug 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

add _, _ = io.ReadAll(res.Body) after defer ...

The body must be read before the deferred close to drain the connection back to the pool — same pattern as DoRequest in httpclient.go

@stefanhipfel stefanhipfel Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok, for the first reconcile or token refresh that is :)

- Dell OME: extract session token from X-Auth-Token response header;
  use DELETE /Devices({id}) for removal; fix DeviceName field mapping;
  add pagination for full device list; fix WSMAN/REDFISH discovery payload
- HPE OneView: use Hostname field for iLO IP; paginate ListServers;
  use Name field as Hostname; skip removal of servers with active profiles
- Lenovo LXCA: switch to Basic Auth per-request (no session tokens);
  fix JSON array wrapping for manage/unmanage requests; fix unmanage
  payload structure (endpoints wrapper); fix FQDN field mapping;
  fix JoinPath query string encoding

Signed-off-by: Stefan Hipfel <stefan.hipfel@sap.com>
LXCA sometimes returns only the short hostname in the FQDN field.
Construct the full FQDN from hostname + domainName when needed, so
ListServers returns FQDNs that match what the controller passes from
bmc.Spec.Hostname.

Signed-off-by: Stefan Hipfel <stefan.hipfel@sap.com>
Some Lenovo servers in LXCA have empty domainName and no FQDN — LXCA
discovers these from the BMC and some BMCs are not configured with a
domain. Index managedMap by both FQDN and short name so servers are
correctly identified as managed regardless of what LXCA returns.

Signed-off-by: Stefan Hipfel <stefan.hipfel@sap.com>
The session POST and subsequent API calls share the same httpClient
transport pool (keyed by host:port). Draining the body before close
returns the TCP connection to the pool, avoiding a new handshake on
the first API call after authentication.

Signed-off-by: Stefan Hipfel <stefan.hipfel@sap.com>
…ncile

Signed-off-by: Stefan Hipfel <stefan.hipfel@sap.com>

@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 (2)
internal/hwmgr/dell.go (1)

278-302: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use the paginated lookup in ListServers.

ListServers makes one request and returns only the first page. listAllServers is called only by RemoveServer. The controller uses ListServers, so devices after the first Dell page are reported as absent and can be imported again.

Proposed fix
 func (c *DellClient) ListServers() ([]Device, error) {
-	serversURL := c.client.parsedURL.JoinPath("/api/DeviceService/Devices")
-	// one-page request and mapping
+	return c.listAllServers()
 }
🤖 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/hwmgr/dell.go` around lines 278 - 302, Update ListServers to use the
existing listAllServers pagination helper instead of issuing a single Devices
request and unmarshalling only its first page. Preserve the current Device
mapping and error behavior while ensuring all Dell pages are returned to
controller callers.
internal/hwmgr/lenovo.go (1)

191-212: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Send the async discovery payload as an array.

ImportServer serializes the discovery request as []ServerManageRequest, but ImportServerAsync serializes one ServerManageRequest. The controller uses ImportServerAsync, so this path sends a payload that does not match the new array-based Lenovo discovery contract.

Proposed fix
- payloadBytes, err := json.Marshal(discoveryPayload)
+ payloadBytes, err := json.Marshal([]ServerManageRequest{discoveryPayload})
🤖 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/hwmgr/lenovo.go` around lines 191 - 212, Update ImportServerAsync to
serialize the discovery payload as an array of ServerManageRequest, matching the
payload shape used by ImportServer and the Lenovo discovery contract; preserve
the existing request creation and error handling.
🧹 Nitpick comments (1)
internal/controller/vendorconsole/console_controller.go (1)

321-324: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the required Kubernetes logging style.

Initialize a local context logger once in delete and use it here. Change the message to past tense and name the object type, for example: Controller skipped server removal because the server had an active profile. Keep the server key-value pair.

[details]

Suggested logging change
+ log := log.FromContext(ctx)
...
- log.FromContext(ctx).Info("Skipping server removal: has active profile", "server", server.Name)
+ log.Info("Controller skipped server removal because the server had an active profile", "server", server.Name)

Update the other log.FromContext(ctx) calls in delete to use the local log variable.

As per coding guidelines, Kubernetes logging messages must start with a capital letter, do not end with a period, use active voice and past tense, name the object type, and keep key-value pairs balanced.

🤖 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/controller/vendorconsole/console_controller.go` around lines 321 -
324, Update the delete method to initialize one local context logger and replace
all log.FromContext(ctx) calls with it. In the ErrServerHasActiveProfile branch,
use a capitalized, active, past-tense message naming the server object, preserve
the server key-value pair, and keep the message without a trailing period.

Source: Coding guidelines

🤖 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 `@internal/hwmgr/dell.go`:
- Around line 269-270: Update the pagination handling around page.NextLink to
resolve the next link against the current request URL using
req.URL.ResolveReference, rather than manually concatenating the configured
scheme and host. Validate the resolved URL against the configured Console
endpoint and reject it when its scheme or host differs before following the
link.

In `@internal/hwmgr/lenovo.go`:
- Around line 166-183: Introduce and reuse a single safe hostname resolver in
the Lenovo manager for both inventory device construction and job polling
matching. Update the logic around device creation to avoid generating a hostname
beginning with "." when HostName is empty, falling back to the node name as
appropriate, and update the polling comparison near the job-status handling to
resolve the hostname instead of using raw node.FQDN. Keep hostname matching
consistent across inventory and asynchronous imports.
- Around line 69-70: Update the Lenovo client initialization and GetAuthToken
flow to use Lenovo session-token authentication instead of forcing Basic Auth.
Ensure GetAuthToken validates an existing token, creates a new session token
when absent or invalid, and returns the resulting token for Secret persistence;
remove the unconditional empty-token return while preserving the client setup in
the surrounding constructor.
- Around line 69-70: Update NewClient after parsing Endpoint to validate that
the URL scheme is HTTPS before enabling console credentials or returning the
client; reject non-HTTPS endpoints, including HTTP, while preserving valid HTTPS
initialization.

---

Outside diff comments:
In `@internal/hwmgr/dell.go`:
- Around line 278-302: Update ListServers to use the existing listAllServers
pagination helper instead of issuing a single Devices request and unmarshalling
only its first page. Preserve the current Device mapping and error behavior
while ensuring all Dell pages are returned to controller callers.

In `@internal/hwmgr/lenovo.go`:
- Around line 191-212: Update ImportServerAsync to serialize the discovery
payload as an array of ServerManageRequest, matching the payload shape used by
ImportServer and the Lenovo discovery contract; preserve the existing request
creation and error handling.

---

Nitpick comments:
In `@internal/controller/vendorconsole/console_controller.go`:
- Around line 321-324: Update the delete method to initialize one local context
logger and replace all log.FromContext(ctx) calls with it. In the
ErrServerHasActiveProfile branch, use a capitalized, active, past-tense message
naming the server object, preserve the server key-value pair, and keep the
message without a trailing period.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c179d311-67ca-4e69-ad99-c69d6b8eb1c3

📥 Commits

Reviewing files that changed from the base of the PR and between dfb01cf and b7bf9ac.

📒 Files selected for processing (6)
  • internal/controller/vendorconsole/console_controller.go
  • internal/hwmgr/dell.go
  • internal/hwmgr/hpe.go
  • internal/hwmgr/httpclient.go
  • internal/hwmgr/lenovo.go
  • internal/hwmgr/types.go

Comment thread internal/hwmgr/dell.go
Comment on lines +269 to +270
if page.NextLink != "" {
nextURL = c.client.parsedURL.Scheme + "://" + c.client.parsedURL.Host + page.NextLink

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)dell\.go$|internal/hwmgr' | head -100

echo "== outline dell.go =="
ast-grep outline internal/hwmgr/dell.go --view expanded || true

echo "== relevant dell.go lines =="
sed -n '1,360p' internal/hwmgr/dell.go | cat -n

echo "== package imports/url usage =="
rg -n "url|DoRequest|NextLink|nextLink|RemoveServer|ImportServer|GetAuthToken" internal/hwmgr/dell.go

Repository: ironcore-dev/metal-maintenance-operator

Length of output: 19198


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== httpclient.go outline and relevant sections =="
ast-grep outline internal/hwmgr/httpclient.go --view expanded || true
sed -n '1,260p' internal/hwmgr/httpclient.go | cat -n

echo "== DoRequest callers and req.URL usage =="
rg -n "DoRequest\\(|req\\.URL|parsedURL" internal/hwmgr -S

echo "== URL construction probes =="
cat > /tmp/url_probe.go <<'GO'
package main

import (
	"fmt"
	"net/url"
)

func main() {
	base := "https://192.168.0.10:1337/base/api/DeviceService/Devices"
	baseURL, _ := url.Parse(base)
	cases := []string{"?$skip=1", "/?$skip=1", "http://evil.example/next", "http://192.168.0.10:1337/base/api/DeviceService/Devices?$skip=1"}
	for _, nextLink := range cases {
		badPrefix := baseURL.Scheme + "://" + baseURL.Host + nextLink
		nextRef, _ := url.Parse(nextLink)
		resolved := baseURL.ResolveReference(nextRef).String()
		offOrigin := false
		if resolvedScheme, err := url.Parse(nextRef.Scheme + "://example.invalid" + nextRef.Path); err == nil {
			offOrigin = resolvedScheme.Scheme == "http" && resolvedScheme.Host == "example.invalid"
		}
		fmt.Printf("nextLink=%s\nbadPrefix=%s\nresolved=%s\noffOrigin=%s\n", nextLink, badPrefix, resolved, nextRef.Host)
	}
}
ГО
go run /tmp/url_probe.go

echo "== tests around Dell list/remove pagination =="
rg -n "listAllServers|RemoveServer|NextLink|`@odata`\\.(count|context)|page\\.NextLink" internal/hwmgr -S

Repository: ironcore-dev/metal-maintenance-operator

Length of output: 9725


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Go package modules/imports =="
git ls-files | rg '(^go\.mod$|go\.sum$)' && cat go.mod | head -20

echo "== URL construction probes =="
python3 - <<'PY'
from urllib.parse import urljoin

base = "https://192.168.0.10:1337/base/api/DeviceService/Devices"
case_prefix = lambda s: "https://192.168.0.10:1337" + s
cases = {
    "query-relative": "?$skip=1",
    "path-relative": "/?$skip=1",
    "absolute-origin": "http://evil.example/next",
    "absolute-same-origin": "http://192.168.0.10:1337/base/api/DeviceService/Devices?$skip=1",
}
for name, next_link in cases.items():
    print(f"{name}:")
    print(f"  current_prefix: {case_prefix(next_link)}")
    print(f"  urljoin(base, next_link): {urljoin(base, next_link)}")
PY

Repository: ironcore-dev/metal-maintenance-operator

Length of output: 1590


Resolve @odata.nextLink against the current request URL.

Replace the scheme/host prefix at internal/hwmgr/dell.go:270 with req.URL.ResolveReference(page.NextLink), then reject resolved URLs whose scheme or host differs from the configured Console endpoint. This avoids invalid pagination targets for query-relative and path-relative next links and prevents following off-origin next links.

🤖 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/hwmgr/dell.go` around lines 269 - 270, Update the pagination
handling around page.NextLink to resolve the next link against the current
request URL using req.URL.ResolveReference, rather than manually concatenating
the configured scheme and host. Validate the resolved URL against the configured
Console endpoint and reject it when its scheme or host differs before following
the link.

Comment thread internal/hwmgr/lenovo.go
Comment on lines +69 to 70
c.client.basicAuth = true
return c, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Restore the required Lenovo session-token flow.

Line 69 forces Basic Auth for every request. Lines 186-188 always return an empty token. This client cannot validate or create the required Lenovo session token, and it cannot return a token for Secret persistence.

As per coding guidelines, “Dell and Lenovo use session tokens” and each vendor GetAuthToken() must validate or create a token when needed.

Also applies to: 186-188

🤖 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/hwmgr/lenovo.go` around lines 69 - 70, Update the Lenovo client
initialization and GetAuthToken flow to use Lenovo session-token authentication
instead of forcing Basic Auth. Ensure GetAuthToken validates an existing token,
creates a new session token when absent or invalid, and returns the resulting
token for Secret persistence; remove the unconditional empty-token return while
preserving the client setup in the surrounding constructor.

Source: Coding guidelines


🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline internal/hwmgr/httpclient.go --items all
rg -n -C 5 'func NewClient|parsedURL|\.Scheme|https|basicAuth|SetBasicAuth' internal/hwmgr/httpclient.go

Repository: ironcore-dev/metal-maintenance-operator

Length of output: 2703


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '49,72p' internal/hwmgr/httpclient.go
rg -n -C 4 'NewClient\(\)|NewLenovoClient|&LenovoClient|Username:|Password:|Endpoint:' internal/hwmgr internal/controller internal -g '*.go'

Repository: ironcore-dev/metal-maintenance-operator

Length of output: 13128


Sensitive Data Exposure (CWE-319): Cleartext Transmission of Sensitive Information

Reachability: Internal

Reachability path
● Entry
  internal/hwmgr/dell.go:118
  ImportServer
│
▼
● Sink
  internal/hwmgr/lenovo.go

Reject non-HTTPS endpoints before using console credentials.

NewClient parses Endpoint but does not enforce https, while Lenovo Basic requests send c.username and c.password via SetBasicAuth. If a Console URL uses http, credentials are sent over an unencrypted connection. Add an HTTPS-only validation check after parsing the URL.

🤖 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/hwmgr/lenovo.go` around lines 69 - 70, Update NewClient after
parsing Endpoint to validate that the URL scheme is HTTPS before enabling
console credentials or returning the client; reject non-HTTPS endpoints,
including HTTP, while preserving valid HTTPS initialization.

Comment thread internal/hwmgr/lenovo.go
Comment on lines +166 to 183
fqdn := node.FQDN
if !strings.Contains(fqdn, ".") {
if node.DomainName != "" {
fqdn = node.HostName + "." + node.DomainName
} else {
fqdn = node.Name
}
}
device := Device{
UUID: node.UUID,
Name: node.Name,
Hostname: node.HostName,
Hostname: fqdn,
Model: node.Type,
// HealthStatus mapping can be added based on HealthState
}
devices = append(devices, device)
}

return devices, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use one safe hostname resolver for inventory and job polling.

Lines 166-173 can produce .example.com when HostName is empty and DomainName is set. Line 263 bypasses this fallback logic and compares only raw FQDN. If LXCA returns empty or inconsistent FQDN data, inventory matching can use an invalid hostname and async imports can remain in running state.

Proposed fix
+func nodeHostname(node ServerNode) string {
+	fqdn := strings.TrimSpace(node.FQDN)
+	hostname := strings.TrimSpace(node.HostName)
+	domain := strings.Trim(strings.TrimSpace(node.DomainName), ".")
+
+	if strings.Contains(fqdn, ".") {
+		return fqdn
+	}
+	if hostname != "" && domain != "" {
+		return hostname + "." + domain
+	}
+	if fqdn != "" {
+		return fqdn
+	}
+	if hostname != "" {
+		return hostname
+	}
+	return node.Name
+}
+
-		fqdn := node.FQDN
-		if !strings.Contains(fqdn, ".") {
-			if node.DomainName != "" {
-				fqdn = node.HostName + "." + node.DomainName
-			} else {
-				fqdn = node.Name
-			}
-		}
+		fqdn := nodeHostname(node)
...
-		if node.FQDN == jobID {
+		if nodeHostname(node) == jobID {

Also applies to: 263-263

🤖 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/hwmgr/lenovo.go` around lines 166 - 183, Introduce and reuse a
single safe hostname resolver in the Lenovo manager for both inventory device
construction and job polling matching. Update the logic around device creation
to avoid generating a hostname beginning with "." when HostName is empty,
falling back to the node name as appropriate, and update the polling comparison
near the job-status handling to resolve the hostname instead of using raw
node.FQDN. Keep hostname matching consistent across inventory and asynchronous
imports.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants