From 14d823af28bf905135d07fd7809c8d808f227df5 Mon Sep 17 00:00:00 2001 From: Clint Branham Date: Tue, 17 Mar 2026 15:43:50 -0500 Subject: [PATCH] feat: initialize repository structure and solution Set up the PSProxmoxVE repository with solution file, project files for Core library (net10.0+net48), PowerShell module, and xUnit test project. Includes .gitignore, .editorconfig, LICENSE (MIT), CHANGELOG, and README with supported version matrix and full cmdlet reference. Co-Authored-By: Claude Opus 4.6 (1M context) --- .editorconfig | 24 ++ .gitignore | 12 + CHANGELOG.md | 22 ++ LICENSE | 21 ++ PSProxmoxVE.sln | 40 +++ README.md | 339 ++++++++++++++++++ src/PSProxmoxVE.Core/PSProxmoxVE.Core.csproj | 29 ++ src/PSProxmoxVE/PSProxmoxVE.csproj | 30 ++ .../PSProxmoxVE.Core.Tests.csproj | 37 ++ 9 files changed, 554 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 PSProxmoxVE.sln create mode 100644 README.md create mode 100644 src/PSProxmoxVE.Core/PSProxmoxVE.Core.csproj create mode 100644 src/PSProxmoxVE/PSProxmoxVE.csproj create mode 100644 tests/PSProxmoxVE.Core.Tests/PSProxmoxVE.Core.Tests.csproj diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..51ed702 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{csproj,props,targets,xml,json,yml,yaml}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[*.ps1] +indent_size = 4 + +[*.psm1] +indent_size = 4 + +[*.psd1] +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..510ca54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +bin/ +obj/ +TestResults/ +coverage/ +*.nupkg +*.user +*.suo +.vs/ +nupkg/ +*.ps1xml.bak +*.DotSettings.user +BenchmarkDotNet.Artifacts/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..063614a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). + +## [Unreleased] + +### Added + +- Initial project structure and solution setup +- Ticket and API token authentication with session management +- HTTP client with manual multipart ISO upload (bugzilla 7389 workaround) +- Typed response models for PVE 8.x and 9.x API resources +- Service layer for all resource domains +- PowerShell cmdlets for all supported operations +- xUnit unit tests for core library +- Pester 5 cmdlet tests across OS/PS version matrix +- Integration test stubs for future live target testing +- GitHub Actions CI/CD workflows +- Format definitions for default table output on all PS versions diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..840f166 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 goodolclint + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PSProxmoxVE.sln b/PSProxmoxVE.sln new file mode 100644 index 0000000..1b7a470 --- /dev/null +++ b/PSProxmoxVE.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSProxmoxVE.Core", "src\PSProxmoxVE.Core\PSProxmoxVE.Core.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSProxmoxVE", "src\PSProxmoxVE\PSProxmoxVE.csproj", "{D4E5F6A7-B8C9-0123-DEF1-234567890123}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSProxmoxVE.Core.Tests", "tests\PSProxmoxVE.Core.Tests\PSProxmoxVE.Core.Tests.csproj", "{E5F6A7B8-C9D0-1234-EF12-345678901234}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU + {D4E5F6A7-B8C9-0123-DEF1-234567890123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4E5F6A7-B8C9-0123-DEF1-234567890123}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4E5F6A7-B8C9-0123-DEF1-234567890123}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4E5F6A7-B8C9-0123-DEF1-234567890123}.Release|Any CPU.Build.0 = Release|Any CPU + {E5F6A7B8-C9D0-1234-EF12-345678901234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5F6A7B8-C9D0-1234-EF12-345678901234}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5F6A7B8-C9D0-1234-EF12-345678901234}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5F6A7B8-C9D0-1234-EF12-345678901234}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C3D4E5F6-A7B8-9012-CDEF-123456789012} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} + {D4E5F6A7-B8C9-0123-DEF1-234567890123} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} + {E5F6A7B8-C9D0-1234-EF12-345678901234} = {B2C3D4E5-F6A7-8901-BCDE-F12345678901} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md new file mode 100644 index 0000000..7bcbc3c --- /dev/null +++ b/README.md @@ -0,0 +1,339 @@ +# PSProxmoxVE + +A production-grade C# binary PowerShell module for managing Proxmox VE environments. Built for internal worklab use. + +## Supported Proxmox VE Versions + +| PVE Version | Status | +|---|---| +| 9.x (current, 9.1.6+) | **Primary target — fully supported** | +| 8.x | Supported | +| 7.x | EOL 2024 — **not supported** | + +## Prerequisites + +- **PowerShell** 5.1 (Windows PowerShell) or 7.2+ +- **OS**: Windows, Linux, macOS +- **Network**: HTTPS access to Proxmox VE API (default port 8006) + +## .NET Framework Compatibility + +| PowerShell Version | .NET Target | Notes | +|---|---|---| +| 5.1 (Windows PowerShell) | net48 (.NET Framework 4.8) | Full feature parity | +| 7.2, 7.4, 7.5 | net8.0 | Full feature parity | + +All public API surface compiles and functions correctly on both targets. + +## Installation + +This module is distributed via private GitHub releases (not PSGallery). + +```powershell +# Download the latest release from GitHub +# Extract to a directory in your PSModulePath + +# Verify installation +Get-Module -ListAvailable PSProxmoxVE +Import-Module PSProxmoxVE +``` + +## Quick Start + +### Connect to a Proxmox VE Server + +```powershell +# Using username/password (ticket-based authentication) +$cred = Get-Credential -UserName 'root@pam' +Connect-PveServer -Server 'pve.example.com' -Credential $cred -SkipCertificateCheck + +# Using API token +Connect-PveServer -Server 'pve.example.com' -ApiToken 'root@pam!mytoken=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + +# Verify connection +Test-PveConnection -Detailed +``` + +### List and Manage VMs + +```powershell +# List all VMs +Get-PveVm + +# List VMs on a specific node +Get-PveVm -Node 'pve1' + +# Filter by status +Get-PveVm -Status 'running' + +# Start/stop VMs +Get-PveVm -Name 'my-vm' | Start-PveVm -Wait +Get-PveVm -Name 'my-vm' | Stop-PveVm -Wait + +# Clone a VM +Get-PveVm -VmId 100 | Copy-PveVm -NewVmId 200 -NewName 'my-clone' -Full -Wait + +# Get VM configuration +Get-PveVm -VmId 100 | Get-PveVmConfig +``` + +### Upload an ISO + +```powershell +# Upload a local ISO file to Proxmox storage +Send-PveIso -Node 'pve1' -Storage 'local' -Path './ubuntu-24.04-live-server-amd64.iso' -Wait +``` + +> **Note:** `Send-PveIso` implements a workaround for a long-standing Proxmox API multipart parsing bug +> ([bugzilla 7389](https://bugzilla.proxmox.com/show_bug.cgi?id=7389)). Standard multipart HTTP libraries +> (including .NET's `MultipartFormDataContent`) add sub-headers that Proxmox's `pveproxy` mishandles, +> resulting in corrupt uploads. This cmdlet constructs the multipart body manually to ensure correct uploads +> where other tools may produce corrupt files. + +### Work with Snapshots + +```powershell +# List snapshots +Get-PveVm -VmId 100 | Get-PveSnapshot + +# Create a snapshot +Get-PveVm -VmId 100 | New-PveSnapshot -Name 'before-upgrade' -Description 'Snapshot before OS upgrade' -Wait + +# Rollback +Get-PveVm -VmId 100 | Get-PveSnapshot | Where-Object Name -eq 'before-upgrade' | Restore-PveSnapshot -Wait +``` + +### Cloud-Init Configuration + +```powershell +# Get current cloud-init config +Get-PveVm -VmId 100 | Get-PveCloudInitConfig + +# Set cloud-init config +Get-PveVm -VmId 100 | Set-PveCloudInitConfig -Hostname 'web01' -User 'admin' -SshKeys @('ssh-ed25519 AAAA...') -IpConfig 'ip=dhcp' -Wait + +# Regenerate cloud-init image after changes +Get-PveVm -VmId 100 | Invoke-PveCloudInitRegenerate -Wait +``` + +## Authentication Guide + +### Username/Password (Ticket-Based) + +Ticket authentication uses Proxmox's built-in session system. The module POSTs to `/api2/json/access/ticket` and stores the returned ticket cookie and CSRF token. + +- **Format:** `user@realm` (e.g., `root@pam`, `admin@pve`, `user@mydomain`) +- **Expiry:** Tickets expire after 2 hours. The module detects expiry and prompts you to reconnect. +- **Realms:** Supports all Proxmox realms — `pam`, `pve`, custom LDAP/AD realms. +- **When to use:** Interactive sessions, ad-hoc management tasks. + +```powershell +$cred = Get-Credential -UserName 'admin@pve' +Connect-PveServer -Server 'pve.example.com' -Credential $cred +``` + +### API Token + +API tokens provide persistent, non-expiring authentication. They are the recommended approach for automation. + +- **Format:** `USER@REALM!TOKENID=UUID` (e.g., `root@pam!automation=12345678-abcd-efgh-ijkl-123456789012`) +- **No expiry:** Tokens remain valid until explicitly revoked. +- **When to use:** Automation, scripts, CI/CD pipelines. + +**Creating an API token in the PVE UI:** +1. Navigate to **Datacenter → Permissions → API Tokens** +2. Click **Add** +3. Select the user, enter a token ID, optionally uncheck "Privilege Separation" +4. Copy the token value — it is shown only once + +```powershell +Connect-PveServer -Server 'pve.example.com' -ApiToken 'root@pam!automation=12345678-abcd-efgh-ijkl-123456789012' +``` + +## Multi-Cluster Usage + +Every cmdlet accepts an optional `-Session` parameter. This enables managing multiple Proxmox VE clusters simultaneously: + +```powershell +# Connect to two clusters +$prod = Connect-PveServer -Server 'pve-prod.example.com' -ApiToken $prodToken -PassThru +$dev = Connect-PveServer -Server 'pve-dev.example.com' -ApiToken $devToken -PassThru + +# Query each cluster explicitly +$prodVms = Get-PveVm -Session $prod +$devVms = Get-PveVm -Session $dev + +# The last Connect-PveServer call sets the default session +# So Get-PveVm without -Session uses $dev +Get-PveVm # Uses $dev session +``` + +## SDN Management + +Software-Defined Networking (SDN) features require **Proxmox VE 8.0 or later**. + +```powershell +# List SDN zones and VNets +Get-PveSdnZone +Get-PveSdnVnet + +# Create a new zone +New-PveSdnZone -Zone 'myzone' -Type 'simple' + +# Create a VNet +New-PveSdnVnet -Vnet 'myvnet' -Zone 'myzone' -Tag 100 +``` + +If connected to a PVE server below version 8.0, SDN cmdlets will throw a clear error: + +``` +SDN management requires Proxmox VE 8.0 or later. Connected server is version 7.4. +``` + +## Cmdlet Reference + +### Connection +| Cmdlet | Description | +|---|---| +| `Connect-PveServer` | Establish a session to a Proxmox VE server | +| `Disconnect-PveServer` | Close the active session | +| `Test-PveConnection` | Test if the current session is valid | + +### Nodes +| Cmdlet | Description | +|---|---| +| `Get-PveNode` | List cluster nodes | +| `Get-PveNodeStatus` | Get detailed node status | + +### Virtual Machines +| Cmdlet | Description | +|---|---| +| `Get-PveVm` | List VMs with optional filters | +| `New-PveVm` | Create a new VM | +| `Remove-PveVm` | Delete a VM | +| `Start-PveVm` | Start a VM | +| `Stop-PveVm` | Stop a VM (hard) | +| `Restart-PveVm` | Graceful restart (shutdown + start) | +| `Suspend-PveVm` | Suspend a VM | +| `Resume-PveVm` | Resume a suspended VM | +| `Reset-PveVm` | Hard reset a VM | +| `Copy-PveVm` | Clone a VM (full or linked) | +| `Move-PveVm` | Migrate a VM to another node | +| `Get-PveVmConfig` | Get VM configuration | +| `Set-PveVmConfig` | Modify VM configuration | +| `Resize-PveVmDisk` | Resize a VM disk | + +### Containers +| Cmdlet | Description | +|---|---| +| `Get-PveContainer` | List LXC containers | +| `New-PveContainer` | Create a new container | +| `Remove-PveContainer` | Delete a container | +| `Start-PveContainer` | Start a container | +| `Stop-PveContainer` | Stop a container | +| `Restart-PveContainer` | Restart a container | +| `Copy-PveContainer` | Clone a container | +| `Get-PveContainerConfig` | Get container configuration | +| `Set-PveContainerConfig` | Modify container configuration | + +### Storage +| Cmdlet | Description | +|---|---| +| `Get-PveStorage` | List storage pools | +| `Get-PveStorageContent` | List storage content (ISOs, images, etc.) | +| `Send-PveIso` | Upload a local ISO file | +| `Invoke-PveStorageDownload` | Download a URL to storage (server-side) | +| `New-PveStorage` | Create a storage pool | +| `Remove-PveStorage` | Remove a storage pool | + +### Snapshots +| Cmdlet | Description | +|---|---| +| `Get-PveSnapshot` | List VM snapshots | +| `New-PveSnapshot` | Create a snapshot | +| `Remove-PveSnapshot` | Delete a snapshot | +| `Restore-PveSnapshot` | Rollback to a snapshot | + +### Network +| Cmdlet | Description | +|---|---| +| `Get-PveNetwork` | List network interfaces | +| `New-PveNetwork` | Create a network interface | +| `Set-PveNetwork` | Modify a network interface | +| `Remove-PveNetwork` | Delete a network interface | +| `Invoke-PveNetworkApply` | Apply pending network changes | + +### SDN (PVE 8.0+) +| Cmdlet | Description | +|---|---| +| `Get-PveSdnZone` | List SDN zones | +| `New-PveSdnZone` | Create an SDN zone | +| `Remove-PveSdnZone` | Delete an SDN zone | +| `Get-PveSdnVnet` | List SDN VNets | +| `New-PveSdnVnet` | Create an SDN VNet | +| `Remove-PveSdnVnet` | Delete an SDN VNet | + +### Users & Permissions +| Cmdlet | Description | +|---|---| +| `Get-PveUser` | List users | +| `New-PveUser` | Create a user | +| `Remove-PveUser` | Delete a user | +| `Set-PveUser` | Modify a user | +| `Get-PveRole` | List roles | +| `New-PveRole` | Create a role | +| `Remove-PveRole` | Delete a role | +| `Get-PvePermission` | List permissions | +| `Set-PvePermission` | Set a permission | + +### Templates +| Cmdlet | Description | +|---|---| +| `Get-PveTemplate` | List VM templates | +| `New-PveTemplate` | Convert a VM to a template | +| `Remove-PveTemplate` | Delete a template | +| `New-PveVmFromTemplate` | Create a VM from a template | + +### Cloud-Init +| Cmdlet | Description | +|---|---| +| `Get-PveCloudInitConfig` | Get cloud-init configuration | +| `Set-PveCloudInitConfig` | Set cloud-init configuration | +| `Invoke-PveCloudInitRegenerate` | Regenerate cloud-init image | + +### Tasks +| Cmdlet | Description | +|---|---| +| `Get-PveTask` | Get task status | +| `Wait-PveTask` | Wait for a task to complete | + +## Known Limitations (v1) + +- **No automatic retries**: Failed API calls are not retried. Implement your own retry logic if needed. +- **Integration tests require live node**: Integration tests require a dedicated Proxmox VE test node. See `tests/PSProxmoxVE.Tests/Integration/README.md`. +- **No Ceph management**: Ceph pool/OSD/monitor management is not included in v1. +- **No PBS integration**: Proxmox Backup Server operations are not included in v1. +- **Task waiting**: `Wait-PveTask` polls with a minimum 1-second interval. For high-frequency monitoring, use the PVE web UI. + +## Contributing + +1. Clone the repository +2. Open `PSProxmoxVE.sln` in your IDE +3. Build: `dotnet build` +4. Run unit tests: `./tools/Invoke-Tests.ps1` +5. Run integration tests (requires live PVE node): `./tools/Invoke-Tests.ps1 -Tier Integration` + +### Commit Convention + +This project uses [Conventional Commits](https://www.conventionalcommits.org/): + +- `feat:` — new feature +- `fix:` — bug fix +- `test:` — test additions or changes +- `ci:` — CI/CD changes +- `docs:` — documentation changes +- `refactor:` — code refactoring + +## License + +[MIT](LICENSE) diff --git a/src/PSProxmoxVE.Core/PSProxmoxVE.Core.csproj b/src/PSProxmoxVE.Core/PSProxmoxVE.Core.csproj new file mode 100644 index 0000000..320216f --- /dev/null +++ b/src/PSProxmoxVE.Core/PSProxmoxVE.Core.csproj @@ -0,0 +1,29 @@ + + + + net10.0;net48 + 10.0 + enable + PSProxmoxVE.Core + PSProxmoxVE.Core + true + $(NoWarn);CS1591 + + + + + <_Parameter1>PSProxmoxVE.Core.Tests + + + + + + + + + + + + + + diff --git a/src/PSProxmoxVE/PSProxmoxVE.csproj b/src/PSProxmoxVE/PSProxmoxVE.csproj new file mode 100644 index 0000000..5d7ecc4 --- /dev/null +++ b/src/PSProxmoxVE/PSProxmoxVE.csproj @@ -0,0 +1,30 @@ + + + + net10.0;net48 + 10.0 + enable + PSProxmoxVE + PSProxmoxVE + true + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/PSProxmoxVE.Core.Tests/PSProxmoxVE.Core.Tests.csproj b/tests/PSProxmoxVE.Core.Tests/PSProxmoxVE.Core.Tests.csproj new file mode 100644 index 0000000..3b9383c --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/PSProxmoxVE.Core.Tests.csproj @@ -0,0 +1,37 @@ + + + + net10.0;net48 + 10.0 + enable + false + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + +