From 54e3f52b4cb024c82adb843c9f41504ec8cb4907 Mon Sep 17 00:00:00 2001 From: splatypus-bot <282974456+splatypus-bot@users.noreply.github.com> Date: Sat, 9 May 2026 00:18:33 -0400 Subject: [PATCH 1/2] feat(vsphere): add per-component credential tooling (story #41) - upi/vsphere/per-component-credentials/create-roles.sh: govc script creates four vCenter roles with exact canonical privilege sets (19/6/3/2) - upi/vsphere/per-component-credentials/create-roles.ps1: PowerCLI equivalent - upi/vsphere/per-component-credentials/generate-credentials.sh: generates ~/.vsphere/credentials template with 0600/0700 permissions - docs/user/vsphere/per-component-credentials-troubleshooting.md: covers all four error scenarios with govc commands and UI paths - 15 tests: 10 script content (AC1) + 5 guide structural (AC3), all pass Co-Authored-By: Claude Sonnet 4.6 (1M context) --- ...r-component-credentials-troubleshooting.md | 183 ++++++++++++++++++ .../vsphere/troubleshooting_guide_test.go | 99 ++++++++++ .../create-roles.ps1 | 82 ++++++++ .../per-component-credentials/create-roles.sh | 76 ++++++++ .../generate-credentials.sh | 71 +++++++ .../per-component-credentials/roles_test.go | 145 ++++++++++++++ 6 files changed, 656 insertions(+) create mode 100644 docs/user/vsphere/per-component-credentials-troubleshooting.md create mode 100644 docs/user/vsphere/troubleshooting_guide_test.go create mode 100644 upi/vsphere/per-component-credentials/create-roles.ps1 create mode 100644 upi/vsphere/per-component-credentials/create-roles.sh create mode 100644 upi/vsphere/per-component-credentials/generate-credentials.sh create mode 100644 upi/vsphere/per-component-credentials/roles_test.go diff --git a/docs/user/vsphere/per-component-credentials-troubleshooting.md b/docs/user/vsphere/per-component-credentials-troubleshooting.md new file mode 100644 index 00000000000..4250dbd17be --- /dev/null +++ b/docs/user/vsphere/per-component-credentials-troubleshooting.md @@ -0,0 +1,183 @@ +# Troubleshooting: Per-Component vCenter Credentials + +This guide covers the four error scenarios you may encounter when using per-component +vCenter credentials during OpenShift installation. + +--- + +## 1. Missing Privilege + +**Error example:** + +``` +Credential validation failed for machineAPI on vcenter1.example.com: missing privileges: [VirtualMachine.Inventory.Create] +``` + +The service account assigned to that component is missing one or more required vCenter privileges. + +### Remediation + +Identify the missing privilege from the error message and grant it to the role using +one of the following methods. + +**Using govc:** + +```bash +# List current privileges on the role +govc role.ls openshift-vsphere-machineapi + +# Add a missing privilege to the role +govc role.update openshift-vsphere-machineapi VirtualMachine.Inventory.Create + +# Verify the permission assignment for the user +govc permissions.ls / +``` + +**Using the vCenter UI:** + +1. Navigate to **Administration > Access Control > Roles**. +2. Select the role (e.g. `openshift-vsphere-machineapi`). +3. Click **Edit** and find the missing privilege in the hierarchy. +4. Enable the checkbox and click **Save**. + +### Canonical privilege sets per component + +The table below lists every required privilege. Use it to cross-check your roles. + +#### machineAPI (19 privileges) + +| Privilege | +|-----------| +| `Datastore.AllocateSpace` | +| `Network.Assign` | +| `Resource.AssignVMToPool` | +| `VirtualMachine.Config.AddExistingDisk` | +| `VirtualMachine.Config.AddNewDisk` | +| `VirtualMachine.Config.AddRemoveDevice` | +| `VirtualMachine.Config.AdvancedConfig` | +| `VirtualMachine.Config.CPUCount` | +| `VirtualMachine.Config.DiskExtend` | +| `VirtualMachine.Config.EditDevice` | +| `VirtualMachine.Config.Memory` | +| `VirtualMachine.Config.RemoveDisk` | +| `VirtualMachine.Config.Resource` | +| `VirtualMachine.Config.Settings` | +| `VirtualMachine.Interact.PowerOff` | +| `VirtualMachine.Interact.PowerOn` | +| `VirtualMachine.Interact.Reset` | +| `VirtualMachine.Inventory.Create` | +| `VirtualMachine.Inventory.Delete` | + +#### csiDriver (6 privileges) + +| Privilege | +|-----------| +| `Datastore.AllocateSpace` | +| `Datastore.FileManagement` | +| `StoragePod.Config` | +| `VirtualMachine.Config.AddExistingDisk` | +| `VirtualMachine.Config.AddNewDisk` | +| `VirtualMachine.Config.RemoveDisk` | + +#### cloudController (3 privileges) + +| Privilege | +|-----------| +| `System.Read` | +| `System.View` | +| `VirtualMachine.Inventory.Create` | + +#### diagnostics (2 privileges) + +| Privilege | +|-----------| +| `Sessions.ValidateSession` | +| `StorageProfile.View` | + +--- + +## 2. Authentication Failure + +**Error example:** + +``` +Credential validation failed for machineAPI on vcenter1.example.com: authentication error: 535 5.7.8 Error: authentication credentials invalid +``` + +The username or password for a component service account is incorrect. + +### Remediation + +Verify the credentials are valid by testing them with `govc`: + +```bash +export GOVC_URL=vcenter1.example.com +export GOVC_USERNAME=svc-machineapi@vsphere.local +export GOVC_PASSWORD= + +govc about +``` + +If `govc about` returns an error, the credentials are invalid. Check that: + +- `GOVC_USERNAME` matches the account name exactly (including domain suffix, e.g. `@vsphere.local`). +- `GOVC_PASSWORD` does not contain shell metacharacters that could be misinterpreted; quote it. +- The account is not locked out in vCenter (**Administration > Single Sign On > Users and Groups**). + +Update `~/.vsphere/credentials` with the corrected values and re-run the installer. + +--- + +## 3. File Permission Error + +**Error example:** + +``` +credentials file ~/.vsphere/credentials has insecure permissions 0644; expected 0600 +``` + +The credentials file must be readable only by the current user to protect passwords. + +### Remediation + +```bash +chmod 0600 ~/.vsphere/credentials +chmod 0700 ~/.vsphere +``` + +Verify the result: + +```bash +ls -la ~/.vsphere/credentials +# Expected: -rw------- 1 ... +``` + +If the file was accidentally committed to version control or copied with broad permissions, +rotate all passwords referenced in the file before correcting permissions. + +--- + +## 4. Partial Configuration + +**Error example:** + +``` +install-config.yaml: componentCredentials.machineAPI is set but componentCredentials.csiDriver is missing +``` + +A partial configuration — where some but not all required components have credentials — is rejected +to prevent runtime failures caused by missing credentials discovered only after installation. + +### Remediation + +Either provide credentials for **all** components listed in `~/.vsphere/credentials`, or +remove the `componentCredentials` block entirely to fall back to a single shared credential. + +Use `generate-credentials.sh` to create a complete template: + +```bash +export VSPHERE_HOSTNAMES="vcenter1.example.com" +bash upi/vsphere/per-component-credentials/generate-credentials.sh +``` + +Fill in all four password fields in the generated file before running the installer. diff --git a/docs/user/vsphere/troubleshooting_guide_test.go b/docs/user/vsphere/troubleshooting_guide_test.go new file mode 100644 index 00000000000..f8576882a6d --- /dev/null +++ b/docs/user/vsphere/troubleshooting_guide_test.go @@ -0,0 +1,99 @@ +package vsphere_test + +import ( + "os" + "strings" + "testing" +) + +var guidePrivileges = []string{ + // machineAPI (19) + "Datastore.AllocateSpace", + "Network.Assign", + "Resource.AssignVMToPool", + "VirtualMachine.Config.AddExistingDisk", + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.AddRemoveDevice", + "VirtualMachine.Config.AdvancedConfig", + "VirtualMachine.Config.CPUCount", + "VirtualMachine.Config.DiskExtend", + "VirtualMachine.Config.EditDevice", + "VirtualMachine.Config.Memory", + "VirtualMachine.Config.RemoveDisk", + "VirtualMachine.Config.Resource", + "VirtualMachine.Config.Settings", + "VirtualMachine.Interact.PowerOff", + "VirtualMachine.Interact.PowerOn", + "VirtualMachine.Interact.Reset", + "VirtualMachine.Inventory.Create", + "VirtualMachine.Inventory.Delete", + // csiDriver (6) + "Datastore.FileManagement", + "StoragePod.Config", + // cloudController (3) + "System.Read", + "System.View", + // diagnostics (2) + "Sessions.ValidateSession", + "StorageProfile.View", +} + +const guidePath = "per-component-credentials-troubleshooting.md" + +func readGuide(t *testing.T) string { + t.Helper() + data, err := os.ReadFile(guidePath) + if err != nil { + t.Fatalf("read %s: %v", guidePath, err) + } + return string(data) +} + +func TestTroubleshootingGuide_CoversAllFourErrorScenarios(t *testing.T) { + content := strings.ToLower(readGuide(t)) + scenarios := []struct { + keyword, label string + }{ + {"missing privilege", "missing privileges scenario"}, + {"authentication", "authentication failure scenario"}, + {"file permission", "file permissions scenario"}, + {"partial", "partial configuration scenario"}, + } + for _, s := range scenarios { + if !strings.Contains(content, s.keyword) { + t.Errorf("troubleshooting guide missing %s (keyword: %q)", s.label, s.keyword) + } + } +} + +func TestTroubleshootingGuide_HasGovcCommandOrUIPathForMissingPrivilege(t *testing.T) { + content := readGuide(t) + hasGovcCmd := strings.Contains(content, "govc role.update") || strings.Contains(content, "govc permissions.set") + hasUIPath := strings.Contains(content, "Administration") && strings.Contains(content, "Access Control") + if !hasGovcCmd && !hasUIPath { + t.Error("troubleshooting guide lacks both govc command and vCenter UI navigation path for granting privileges") + } +} + +func TestTroubleshootingGuide_AllCanonicalPrivilegesIndexed(t *testing.T) { + content := readGuide(t) + for _, priv := range guidePrivileges { + if !strings.Contains(content, priv) { + t.Errorf("troubleshooting guide does not index privilege %q", priv) + } + } +} + +func TestTroubleshootingGuide_AuthFailureHasCredentialCheckCommand(t *testing.T) { + content := readGuide(t) + if !strings.Contains(content, "govc about") && !strings.Contains(content, "GOVC_USERNAME") { + t.Error("authentication failure section lacks govc credential verification command (e.g., govc about)") + } +} + +func TestTroubleshootingGuide_FilePermissionSectionHasChmodCommand(t *testing.T) { + content := readGuide(t) + if !strings.Contains(content, "chmod 0600") && !strings.Contains(content, "chmod 600") { + t.Error("file permissions section does not provide chmod remediation command") + } +} diff --git a/upi/vsphere/per-component-credentials/create-roles.ps1 b/upi/vsphere/per-component-credentials/create-roles.ps1 new file mode 100644 index 00000000000..4f52190d75f --- /dev/null +++ b/upi/vsphere/per-component-credentials/create-roles.ps1 @@ -0,0 +1,82 @@ +#!/usr/bin/pwsh +# create-roles.ps1 — create per-component vCenter roles for OpenShift installation (PowerCLI). +# +# Requirements: +# VMware PowerCLI module (Install-Module -Name VMware.PowerCLI) +# +# Usage: +# Connect-VIServer -Server vcenter.example.com -User administrator@vsphere.local -Password secret +# .\create-roles.ps1 + +param( + [string]$Server = $env:GOVC_URL +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +# -------------------------------------------------------------------------- +# openshift-vsphere-machineapi — 19 privileges required by the Machine API +# -------------------------------------------------------------------------- +$machineAPIPrivileges = @( + "Datastore.AllocateSpace", + "Network.Assign", + "Resource.AssignVMToPool", + "VirtualMachine.Config.AddExistingDisk", + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.AddRemoveDevice", + "VirtualMachine.Config.AdvancedConfig", + "VirtualMachine.Config.CPUCount", + "VirtualMachine.Config.DiskExtend", + "VirtualMachine.Config.EditDevice", + "VirtualMachine.Config.Memory", + "VirtualMachine.Config.RemoveDisk", + "VirtualMachine.Config.Resource", + "VirtualMachine.Config.Settings", + "VirtualMachine.Interact.PowerOff", + "VirtualMachine.Interact.PowerOn", + "VirtualMachine.Interact.Reset", + "VirtualMachine.Inventory.Create", + "VirtualMachine.Inventory.Delete" +) +New-VIRole -Name "openshift-vsphere-machineapi" -Privilege (Get-VIPrivilege -Id $machineAPIPrivileges) +Write-Host "Created role: openshift-vsphere-machineapi (19 privileges)" + +# -------------------------------------------------------------------------- +# openshift-vsphere-csidriver — 6 privileges required by the CSI driver +# -------------------------------------------------------------------------- +$csiDriverPrivileges = @( + "Datastore.AllocateSpace", + "Datastore.FileManagement", + "StoragePod.Config", + "VirtualMachine.Config.AddExistingDisk", + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.RemoveDisk" +) +New-VIRole -Name "openshift-vsphere-csidriver" -Privilege (Get-VIPrivilege -Id $csiDriverPrivileges) +Write-Host "Created role: openshift-vsphere-csidriver (6 privileges)" + +# -------------------------------------------------------------------------- +# openshift-vsphere-cloudcontroller — 3 privileges required by Cloud Controller Manager +# -------------------------------------------------------------------------- +$cloudControllerPrivileges = @( + "System.Read", + "System.View", + "VirtualMachine.Inventory.Create" +) +New-VIRole -Name "openshift-vsphere-cloudcontroller" -Privilege (Get-VIPrivilege -Id $cloudControllerPrivileges) +Write-Host "Created role: openshift-vsphere-cloudcontroller (3 privileges)" + +# -------------------------------------------------------------------------- +# openshift-vsphere-diagnostics — 2 privileges required by vSphere Problem Detector +# -------------------------------------------------------------------------- +$diagnosticsPrivileges = @( + "Sessions.ValidateSession", + "StorageProfile.View" +) +New-VIRole -Name "openshift-vsphere-diagnostics" -Privilege (Get-VIPrivilege -Id $diagnosticsPrivileges) +Write-Host "Created role: openshift-vsphere-diagnostics (2 privileges)" + +Write-Host "" +Write-Host "All four per-component vCenter roles created successfully." +Write-Host "Assign each role to the corresponding service account on the appropriate vCenter objects." diff --git a/upi/vsphere/per-component-credentials/create-roles.sh b/upi/vsphere/per-component-credentials/create-roles.sh new file mode 100644 index 00000000000..5c20c458b26 --- /dev/null +++ b/upi/vsphere/per-component-credentials/create-roles.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# create-roles.sh — create per-component vCenter roles for OpenShift installation. +# +# Requirements: +# govc (https://github.com/vmware/govmomi/tree/main/govc) +# GOVC_URL, GOVC_USERNAME, GOVC_PASSWORD environment variables set. +# +# Usage: +# export GOVC_URL=vcenter.example.com +# export GOVC_USERNAME=administrator@vsphere.local +# export GOVC_PASSWORD=secret +# bash create-roles.sh + +set -euo pipefail + +# -------------------------------------------------------------------------- +# openshift-vsphere-machineapi — 19 privileges required by the Machine API +# -------------------------------------------------------------------------- +govc role.create openshift-vsphere-machineapi \ + Datastore.AllocateSpace \ + Network.Assign \ + Resource.AssignVMToPool \ + VirtualMachine.Config.AddExistingDisk \ + VirtualMachine.Config.AddNewDisk \ + VirtualMachine.Config.AddRemoveDevice \ + VirtualMachine.Config.AdvancedConfig \ + VirtualMachine.Config.CPUCount \ + VirtualMachine.Config.DiskExtend \ + VirtualMachine.Config.EditDevice \ + VirtualMachine.Config.Memory \ + VirtualMachine.Config.RemoveDisk \ + VirtualMachine.Config.Resource \ + VirtualMachine.Config.Settings \ + VirtualMachine.Interact.PowerOff \ + VirtualMachine.Interact.PowerOn \ + VirtualMachine.Interact.Reset \ + VirtualMachine.Inventory.Create \ + VirtualMachine.Inventory.Delete + +echo "Created role: openshift-vsphere-machineapi (19 privileges)" + +# -------------------------------------------------------------------------- +# openshift-vsphere-csidriver — 6 privileges required by the CSI driver +# -------------------------------------------------------------------------- +govc role.create openshift-vsphere-csidriver \ + Datastore.AllocateSpace \ + Datastore.FileManagement \ + StoragePod.Config \ + VirtualMachine.Config.AddExistingDisk \ + VirtualMachine.Config.AddNewDisk \ + VirtualMachine.Config.RemoveDisk + +echo "Created role: openshift-vsphere-csidriver (6 privileges)" + +# -------------------------------------------------------------------------- +# openshift-vsphere-cloudcontroller — 3 privileges required by Cloud Controller Manager +# -------------------------------------------------------------------------- +govc role.create openshift-vsphere-cloudcontroller \ + System.Read \ + System.View \ + VirtualMachine.Inventory.Create + +echo "Created role: openshift-vsphere-cloudcontroller (3 privileges)" + +# -------------------------------------------------------------------------- +# openshift-vsphere-diagnostics — 2 privileges required by the vSphere Problem Detector +# -------------------------------------------------------------------------- +govc role.create openshift-vsphere-diagnostics \ + Sessions.ValidateSession \ + StorageProfile.View + +echo "Created role: openshift-vsphere-diagnostics (2 privileges)" + +echo "" +echo "All four per-component vCenter roles created successfully." +echo "Assign each role to the corresponding service account on the appropriate vCenter objects." diff --git a/upi/vsphere/per-component-credentials/generate-credentials.sh b/upi/vsphere/per-component-credentials/generate-credentials.sh new file mode 100644 index 00000000000..0de366ca0cf --- /dev/null +++ b/upi/vsphere/per-component-credentials/generate-credentials.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# generate-credentials.sh — generate a ~/.vsphere/credentials template for per-component credentials. +# +# Usage: +# bash generate-credentials.sh +# +# Environment variables: +# VSPHERE_HOSTNAMES comma-separated vCenter FQDNs (e.g. "vc1.example.com,vc2.example.com") +# MACHINEAPI_USERNAME username for the Machine API service account +# CSIDRIVER_USERNAME username for the CSI driver service account +# CLOUDCONTROLLER_USERNAME username for the Cloud Controller Manager service account +# DIAGNOSTICS_USERNAME username for the vSphere Problem Detector service account +# +# Output: +# ~/.vsphere/credentials with permissions 0600 (directory 0700). + +set -euo pipefail + +: "${VSPHERE_HOSTNAMES:?VSPHERE_HOSTNAMES must be set (comma-separated list of vCenter FQDNs)}" + +MACHINEAPI_USERNAME="${MACHINEAPI_USERNAME:-svc-machineapi@vsphere.local}" +CSIDRIVER_USERNAME="${CSIDRIVER_USERNAME:-svc-csidriver@vsphere.local}" +CLOUDCONTROLLER_USERNAME="${CLOUDCONTROLLER_USERNAME:-svc-cloudcontroller@vsphere.local}" +DIAGNOSTICS_USERNAME="${DIAGNOSTICS_USERNAME:-svc-diagnostics@vsphere.local}" + +OUTPUT_DIR="${HOME}/.vsphere" +OUTPUT_FILE="${OUTPUT_DIR}/credentials" + +mkdir -p "${OUTPUT_DIR}" +chmod 0700 "${OUTPUT_DIR}" + +IFS=',' read -ra VCENTER_LIST <<< "${VSPHERE_HOSTNAMES}" +if [[ ${#VCENTER_LIST[@]} -eq 0 || -z "${VCENTER_LIST[0]}" ]]; then + echo "ERROR: VSPHERE_HOSTNAMES is empty; at least one vCenter FQDN is required" >&2 + exit 1 +fi + +{ + echo "# ~/.vsphere/credentials — per-component vCenter credential file" + echo "# Generated by generate-credentials.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "# Fill in the password fields before use." + echo "" + + for VCENTER in "${VCENTER_LIST[@]}"; do + VCENTER="$(echo "${VCENTER}" | xargs)" # trim whitespace + if [[ -z "${VCENTER}" ]]; then + continue + fi + + cat < "${OUTPUT_FILE}" + +chmod 0600 "${OUTPUT_FILE}" + +echo "Credentials template written to: ${OUTPUT_FILE}" +echo "IMPORTANT: fill in the password fields before running the installer." diff --git a/upi/vsphere/per-component-credentials/roles_test.go b/upi/vsphere/per-component-credentials/roles_test.go new file mode 100644 index 00000000000..f9f53b4a661 --- /dev/null +++ b/upi/vsphere/per-component-credentials/roles_test.go @@ -0,0 +1,145 @@ +package percomponentcredentials_test + +import ( + "os" + "strings" + "testing" +) + +var machineAPIPrivileges = []string{ + "Datastore.AllocateSpace", + "Network.Assign", + "Resource.AssignVMToPool", + "VirtualMachine.Config.AddExistingDisk", + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.AddRemoveDevice", + "VirtualMachine.Config.AdvancedConfig", + "VirtualMachine.Config.CPUCount", + "VirtualMachine.Config.DiskExtend", + "VirtualMachine.Config.EditDevice", + "VirtualMachine.Config.Memory", + "VirtualMachine.Config.RemoveDisk", + "VirtualMachine.Config.Resource", + "VirtualMachine.Config.Settings", + "VirtualMachine.Interact.PowerOff", + "VirtualMachine.Interact.PowerOn", + "VirtualMachine.Interact.Reset", + "VirtualMachine.Inventory.Create", + "VirtualMachine.Inventory.Delete", +} + +var csiDriverPrivileges = []string{ + "Datastore.AllocateSpace", + "Datastore.FileManagement", + "StoragePod.Config", + "VirtualMachine.Config.AddExistingDisk", + "VirtualMachine.Config.AddNewDisk", + "VirtualMachine.Config.RemoveDisk", +} + +var cloudControllerPrivileges = []string{ + "System.Read", + "System.View", + "VirtualMachine.Inventory.Create", +} + +var diagnosticsPrivileges = []string{ + "Sessions.ValidateSession", + "StorageProfile.View", +} + +func readScript(t *testing.T, path string) string { + t.Helper() + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + return string(data) +} + +const govcScript = "create-roles.sh" + +func TestGovcScript_MachineAPI_RoleExists(t *testing.T) { + if !strings.Contains(readScript(t, govcScript), "openshift-vsphere-machineapi") { + t.Error("govc script does not define role openshift-vsphere-machineapi") + } +} + +func TestGovcScript_CSIDriver_RoleExists(t *testing.T) { + if !strings.Contains(readScript(t, govcScript), "openshift-vsphere-csidriver") { + t.Error("govc script does not define role openshift-vsphere-csidriver") + } +} + +func TestGovcScript_CloudController_RoleExists(t *testing.T) { + if !strings.Contains(readScript(t, govcScript), "openshift-vsphere-cloudcontroller") { + t.Error("govc script does not define role openshift-vsphere-cloudcontroller") + } +} + +func TestGovcScript_Diagnostics_RoleExists(t *testing.T) { + if !strings.Contains(readScript(t, govcScript), "openshift-vsphere-diagnostics") { + t.Error("govc script does not define role openshift-vsphere-diagnostics") + } +} + +func TestGovcScript_MachineAPI_AllPrivilegesPresent(t *testing.T) { + content := readScript(t, govcScript) + for _, priv := range machineAPIPrivileges { + if !strings.Contains(content, priv) { + t.Errorf("govc script missing machineAPI privilege %q", priv) + } + } +} + +func TestGovcScript_CSIDriver_AllPrivilegesPresent(t *testing.T) { + content := readScript(t, govcScript) + for _, priv := range csiDriverPrivileges { + if !strings.Contains(content, priv) { + t.Errorf("govc script missing csiDriver privilege %q", priv) + } + } +} + +func TestGovcScript_CloudController_AllPrivilegesPresent(t *testing.T) { + content := readScript(t, govcScript) + for _, priv := range cloudControllerPrivileges { + if !strings.Contains(content, priv) { + t.Errorf("govc script missing cloudController privilege %q", priv) + } + } +} + +func TestGovcScript_Diagnostics_AllPrivilegesPresent(t *testing.T) { + content := readScript(t, govcScript) + for _, priv := range diagnosticsPrivileges { + if !strings.Contains(content, priv) { + t.Errorf("govc script missing diagnostics privilege %q", priv) + } + } +} + +const powercliScript = "create-roles.ps1" + +func TestPowerCLIScript_AllFourRolesPresent(t *testing.T) { + content := readScript(t, powercliScript) + for _, name := range []string{ + "openshift-vsphere-machineapi", + "openshift-vsphere-csidriver", + "openshift-vsphere-cloudcontroller", + "openshift-vsphere-diagnostics", + } { + if !strings.Contains(content, name) { + t.Errorf("PowerCLI script missing role name %q", name) + } + } +} + +func TestPowerCLIScript_MachineAPI_AllPrivilegesPresent(t *testing.T) { + content := readScript(t, powercliScript) + for _, priv := range machineAPIPrivileges { + if !strings.Contains(content, priv) { + t.Errorf("PowerCLI script missing machineAPI privilege %q", priv) + } + } +} From a0426c4513de9c73de326a6d0535738832647af2 Mon Sep 17 00:00:00 2001 From: splatypus-bot <282974456+splatypus-bot@users.noreply.github.com> Date: Mon, 11 May 2026 10:35:01 -0400 Subject: [PATCH 2/2] fix(vsphere): address CodeRabbit feedback on per-component credential tooling - Add language tags to fenced error-example code blocks in per-component-credentials-troubleshooting.md to satisfy MD040 - Fix generate-credentials.sh: build a trimmed/filtered vCenter list before validating, so leading commas and whitespace-only tokens are handled correctly instead of producing a false error or silent empty credentials body Co-Authored-By: Claude Sonnet 4.6 (1M context) --- ...er-component-credentials-troubleshooting.md | 8 ++++---- .../generate-credentials.sh | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/user/vsphere/per-component-credentials-troubleshooting.md b/docs/user/vsphere/per-component-credentials-troubleshooting.md index 4250dbd17be..17ef91b5145 100644 --- a/docs/user/vsphere/per-component-credentials-troubleshooting.md +++ b/docs/user/vsphere/per-component-credentials-troubleshooting.md @@ -9,7 +9,7 @@ vCenter credentials during OpenShift installation. **Error example:** -``` +```text Credential validation failed for machineAPI on vcenter1.example.com: missing privileges: [VirtualMachine.Inventory.Create] ``` @@ -100,7 +100,7 @@ The table below lists every required privilege. Use it to cross-check your roles **Error example:** -``` +```text Credential validation failed for machineAPI on vcenter1.example.com: authentication error: 535 5.7.8 Error: authentication credentials invalid ``` @@ -132,7 +132,7 @@ Update `~/.vsphere/credentials` with the corrected values and re-run the install **Error example:** -``` +```text credentials file ~/.vsphere/credentials has insecure permissions 0644; expected 0600 ``` @@ -161,7 +161,7 @@ rotate all passwords referenced in the file before correcting permissions. **Error example:** -``` +```text install-config.yaml: componentCredentials.machineAPI is set but componentCredentials.csiDriver is missing ``` diff --git a/upi/vsphere/per-component-credentials/generate-credentials.sh b/upi/vsphere/per-component-credentials/generate-credentials.sh index 0de366ca0cf..4140061d0cc 100644 --- a/upi/vsphere/per-component-credentials/generate-credentials.sh +++ b/upi/vsphere/per-component-credentials/generate-credentials.sh @@ -30,8 +30,15 @@ mkdir -p "${OUTPUT_DIR}" chmod 0700 "${OUTPUT_DIR}" IFS=',' read -ra VCENTER_LIST <<< "${VSPHERE_HOSTNAMES}" -if [[ ${#VCENTER_LIST[@]} -eq 0 || -z "${VCENTER_LIST[0]}" ]]; then - echo "ERROR: VSPHERE_HOSTNAMES is empty; at least one vCenter FQDN is required" >&2 + +FILTERED_VCENTER_LIST=() +for VCENTER in "${VCENTER_LIST[@]}"; do + VCENTER="$(echo "${VCENTER}" | xargs)" # trim whitespace + [[ -n "${VCENTER}" ]] && FILTERED_VCENTER_LIST+=("${VCENTER}") +done + +if [[ ${#FILTERED_VCENTER_LIST[@]} -eq 0 ]]; then + echo "ERROR: VSPHERE_HOSTNAMES contains no valid vCenter FQDNs (got empty or whitespace-only tokens)" >&2 exit 1 fi @@ -41,12 +48,7 @@ fi echo "# Fill in the password fields before use." echo "" - for VCENTER in "${VCENTER_LIST[@]}"; do - VCENTER="$(echo "${VCENTER}" | xargs)" # trim whitespace - if [[ -z "${VCENTER}" ]]; then - continue - fi - + for VCENTER in "${FILTERED_VCENTER_LIST[@]}"; do cat <