diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index db9b9d8..335aaac 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -8,6 +8,13 @@ "husky" ], "rollForward": false + }, + "microsoft.openapi.kiota": { + "version": "1.31.1", + "commands": [ + "kiota" + ], + "rollForward": false } } } \ No newline at end of file diff --git a/.fallout/.gitignore b/.fallout/.gitignore new file mode 100644 index 0000000..36445e5 --- /dev/null +++ b/.fallout/.gitignore @@ -0,0 +1 @@ +/temp diff --git a/.fallout/build.schema.json b/.fallout/build.schema.json new file mode 100644 index 0000000..635b424 --- /dev/null +++ b/.fallout/build.schema.json @@ -0,0 +1,130 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "Host": { + "type": "string", + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitbucket", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "ExecutableTarget": { + "type": "string", + "enum": [ + "Clean", + "Compile", + "Pack", + "Publish", + "Test" + ] + }, + "Verbosity": { + "type": "string", + "description": "", + "enum": [ + "Verbose", + "Normal", + "Minimal", + "Quiet" + ] + }, + "FalloutBuild": { + "properties": { + "Continue": { + "type": "boolean", + "description": "Indicates to continue a previously failed build attempt" + }, + "Help": { + "type": "boolean", + "description": "Shows the help text for this build assembly" + }, + "Host": { + "description": "Host for execution. Default is 'automatic'", + "$ref": "#/definitions/Host" + }, + "NoLogo": { + "type": "boolean", + "description": "Disables displaying the NUKE logo" + }, + "Partition": { + "type": "string", + "description": "Partition to use on CI" + }, + "Plan": { + "type": "boolean", + "description": "Shows the execution plan (HTML)" + }, + "Profile": { + "type": "array", + "description": "Defines the profiles to load", + "items": { + "type": "string" + } + }, + "Root": { + "type": "string", + "description": "Root directory during build execution" + }, + "Skip": { + "type": "array", + "description": "List of targets to be skipped. Empty list skips all dependencies", + "items": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Target": { + "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", + "items": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Verbosity": { + "description": "Logging verbosity during build execution. Default is 'Normal'", + "$ref": "#/definitions/Verbosity" + }, + "BuildProjectFile": { + "type": [ + "null", + "string" + ], + "description": "Path to the build project (.csproj) relative to the repository root. Defaults to 'build/_build.csproj' when unset. Read by the Fallout global tool's in-tool runner." + } + } + } + }, + "allOf": [ + { + "properties": { + "NuGetApiKey": { + "type": "string", + "description": "API key for the push. In CI this is the short-lived key from NuGet/login (Trusted Publishing), never a stored secret" + }, + "NuGetSource": { + "type": "string", + "description": "nuget.org push source" + }, + "VersionSuffix": { + "type": "string", + "description": "Prerelease suffix appended to VersionPrefix (e.g. 'preview.42'). Empty → stable" + } + } + }, + { + "$ref": "#/definitions/FalloutBuild" + } + ] +} diff --git a/.fallout/parameters.json b/.fallout/parameters.json new file mode 100644 index 0000000..2a134ba --- /dev/null +++ b/.fallout/parameters.json @@ -0,0 +1,3 @@ +{ + "$schema": "build.schema.json" +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59b770f..85d0a3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,9 +6,10 @@ on: pull_request: branches: [main] +# Build + test only. Publishing (prerelease on main, stable on v* tag) lives in +# publish.yml and uses nuget.org Trusted Publishing (OIDC) — no packages: write here. permissions: contents: read - packages: write jobs: build: @@ -20,28 +21,15 @@ jobs: with: dotnet-version: '10.0.x' - # Kiota (pinned in .config/dotnet-tools.json) — needed by the regenerate - # step that runs during the ProxmoxSharp.Api build. - - name: Restore tools - run: dotnet tool restore - - # Build regenerates the ProxmoxSharp.Api client from the pinned schema - # (Generated/ is gitignored), then compiles everything. - - name: Build - run: dotnet build -c Release - - # Live integration tests skip automatically without a secrets.env. - - name: Test - run: dotnet test -c Release --no-build - - # On every push to main, publish a PRERELEASE to GitHub Packages so other - # projects can reference the latest build (e.g. 0.1.0-preview.42). Stable - # versions are published from the publish.yml workflow on a v* tag. - - name: Publish prerelease - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: | - dotnet pack -c Release -o artifacts --version-suffix "preview.${{ github.run_number }}" - dotnet nuget push "artifacts/*.nupkg" \ - --source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ - --api-key "${{ secrets.GITHUB_TOKEN }}" \ - --skip-duplicate --no-symbols + # Runs the Fallout build (Compile → Test). Compile regenerates the + # ProxmoxSharp.Api client from the pinned schema via Kiota (Generated/ is + # gitignored), then compiles everything; Test skips live integration tests + # automatically without a secrets.env. + # + # GITHUB_PACKAGES_PAT authenticates the Fallout-build package feed (nuget.config) + # so the build/ project can restore Fallout.*. PACKAGES_PAT is a PAT with + # read:packages on the Fallout-build org. + - name: Build & test (Fallout) + env: + GITHUB_PACKAGES_PAT: ${{ secrets.PACKAGES_PAT }} + run: ./build.sh Test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ca802bf..d03ab3e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,21 +1,33 @@ name: publish -# Publishes both packages to GitHub Packages: -# ProxmoxSharp.Api (version = Proxmox API release, e.g. 9.2.2) -# ProxmoxSharp (independent SemVer, e.g. 0.1.0) -# Versions come from the csprojs; a `v*` tag (the library release) just triggers it. +# Publishes the three packages to nuget.org via Trusted Publishing (OIDC — no +# stored API key): +# Chrison.ProxmoxSharp.Api (version tracks the Proxmox API release, e.g. 9.2.2) +# Chrison.ProxmoxSharp (independent SemVer, e.g. 0.2.0) +# Chrison.ProxmoxSharp.Cli (dotnet tool; co-versioned with the library) +# Stable versions come from the csprojs' VersionPrefix; a `v*` tag just triggers a +# stable publish. A push to main publishes a PRERELEASE (…-preview.N). +# +# Requires a nuget.org Trusted Publishing policy for this repo + this workflow file +# (publish.yml), and the NUGET_USER secret (nuget.org profile name). GITHUB_PACKAGES_PAT +# (from PACKAGES_PAT) only authenticates the Fallout-build feed for the build/ project. on: push: + branches: [main] tags: ['v*'] workflow_dispatch: permissions: + id-token: write # enable GitHub OIDC token issuance (Trusted Publishing) contents: read - packages: write jobs: publish: runs-on: ubuntu-latest + # Must match the nuget.org Trusted Publishing policy's Environment field — the + # OIDC token only carries the environment claim when the job declares it here, + # and the policy rejects the exchange otherwise. + environment: Nuget.org steps: - uses: actions/checkout@v4 @@ -23,17 +35,31 @@ jobs: with: dotnet-version: '10.0.x' - - name: Restore tools - run: dotnet tool restore + # Stable on a v* tag (version = csproj VersionPrefix); prerelease otherwise. + - name: Compute version suffix + id: ver + run: | + if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then + echo "suffix=" >> "$GITHUB_OUTPUT" + else + echo "suffix=preview.${{ github.run_number }}" >> "$GITHUB_OUTPUT" + fi - # Pack regenerates ProxmoxSharp.Api from the schema, then packs the two - # packable projects (SchemaGen + Tests are IsPackable=false). - - name: Pack - run: dotnet pack -c Release -o artifacts + # Exchange the OIDC token for a short-lived (1h) nuget.org API key. + - name: NuGet login (OIDC → temporary API key) + uses: NuGet/login@v1 + id: login + with: + user: ${{ secrets.NUGET_USER }} - - name: Push to GitHub Packages - run: > - dotnet nuget push "artifacts/*.nupkg" - --source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" - --api-key "${{ secrets.GITHUB_TOKEN }}" - --skip-duplicate --no-symbols + # Fallout Pack + Publish. GITHUB_PACKAGES_PAT authenticates the Fallout-build + # feed (build/ project restore); the push uses the short-lived nuget.org key. + - name: Publish (Fallout → nuget.org) + env: + GITHUB_PACKAGES_PAT: ${{ secrets.PACKAGES_PAT }} + run: | + ARGS=(Publish --nuget-api-key "${{ steps.login.outputs.NUGET_API_KEY }}") + if [[ -n "${{ steps.ver.outputs.suffix }}" ]]; then + ARGS+=(--version-suffix "${{ steps.ver.outputs.suffix }}") + fi + ./build.sh "${ARGS[@]}" diff --git a/README.md b/README.md index 3a4385b..9c956ef 100644 --- a/README.md +++ b/README.md @@ -41,14 +41,18 @@ touching the API version; bump the API when you regenerate from a new Proxmox re ## Build +Built with [Fallout](https://github.com/Fallout-build/Fallout) (Chris's C#/.NET +build system, a NUKE successor). Targets: `Compile` → `Test` → `Pack` → `Publish`. + ```bash -dotnet tool restore # restore Kiota (the build invokes it to regenerate) -dotnet build # regenerates ProxmoxSharp.Api from the schema if it changed, then compiles -dotnet test +./build.sh # default: Test (Compile regenerates ProxmoxSharp.Api from the schema, then compiles) +./build.sh Pack # produce the Chrison.* nupkgs into artifacts/ ``` -CI (`.github/workflows/ci.yml`) does the same on push/PR — a clean checkout -regenerates the client fresh. +Requires the .NET 10 SDK (see `global.json`) and `GITHUB_PACKAGES_PAT` in the +environment (a PAT with `read:packages` on the Fallout-build org — the build restores +`Fallout.*` from that feed, see `nuget.config`). CI (`.github/workflows/ci.yml`) runs +`./build.sh Test` on push/PR — a clean checkout regenerates the client fresh. ## Use it @@ -65,7 +69,7 @@ the shell. It's self-contained (bundles the library + generated client), so inst needs only the `ProxmoxSharp.Cli` package, not its dependencies. ```bash -dotnet tool install -g ProxmoxSharp.Cli # from the GitHub Packages feed (read:packages) +dotnet tool install -g Chrison.ProxmoxSharp.Cli # from nuget.org (public) export PROXMOX_BASE_URL="https://192.168.179.3:8006/api2/json" export PROXMOX_TOKEN_ID="root@pam!token" @@ -96,23 +100,31 @@ pwsh scripts/new-dev-token.ps1 -Node hpe-01 ## Packages -Published to **GitHub Packages**: +Published to **nuget.org** (public) under the `Chrison.*` prefix, via **Trusted +Publishing** (OIDC — no stored API key): + +| ID | Versioning | +| --- | --- | +| `Chrison.ProxmoxSharp` | independent SemVer (`0.2.0`) | +| `Chrison.ProxmoxSharp.Api` | tracks the Proxmox API release (`9.2.2`) | +| `Chrison.ProxmoxSharp.Cli` | `dotnet` tool, co-versioned with the library | | Trigger | Versions | Workflow | | --- | --- | --- | -| push to `main` | **prerelease** — `…-preview.` (e.g. `0.1.0-preview.42`) | `ci.yml` | -| `v*` tag | **stable** — from `VersionPrefix` (`0.1.0` / `9.2.2`) | `publish.yml` | +| push to `main` | **prerelease** — `…-preview.` | `publish.yml` | +| `v*` tag | **stable** — from `VersionPrefix` | `publish.yml` | So every merge to `main` ships a referenceable prerelease; tagging cuts a stable -release. `ProxmoxSharp.Api` versions to the Proxmox release (e.g. `9.2.2`), -`ProxmoxSharp` to its own SemVer, and the library depends on the matching `.Api`. +release. The library depends on the matching `.Api`. IDs use the `Chrison.*` prefix +because the bare `ProxmoxSharp` ID is taken on nuget.org by an unrelated project; +assembly names and namespaces stay `ProxmoxSharp`, so `using ProxmoxSharp;` is unchanged. -Consuming needs the GitHub feed (`nuget.config`) + a PAT with `read:packages`. -To track the latest prerelease, float it: ``. +Consume from nuget.org like any public package. To track the latest prerelease, float it: +``. ## Status M1–M5 done. Generated client reads the live cluster; `ProxmoxDiscovery` produces -a structured `ClusterSnapshot` (nodes → LXC/QEMU/storage/network); both packages -publish to GitHub Packages. Coverage: `/version,/nodes,/cluster,/storage,/access` +a structured `ClusterSnapshot` (nodes → LXC/QEMU/storage/network); the packages +publish to nuget.org. Coverage: `/version,/nodes,/cluster,/storage,/access` (338 GET ops). Next: the hub consumes the package; BL-014 CLI; write path (BL-010). diff --git a/build.ps1 b/build.ps1 new file mode 100755 index 0000000..52e0d58 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh +# ProxmoxSharp build entrypoint (Fallout build). Requires the .NET 10 SDK on PATH +# (see global.json) and, for restoring the Fallout.* build packages, GITHUB_PACKAGES_PAT +# in the environment (a PAT with read:packages on the Fallout-build org; see nuget.config). +# +# ./build.ps1 # default target: Test +# ./build.ps1 Pack --version-suffix preview.42 +# ./build.ps1 Publish --nuget-api-key +$ErrorActionPreference = 'Stop' +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +& dotnet run --project "$ScriptDir/build/_build.csproj" -- $args +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..f334ed5 --- /dev/null +++ b/build.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# ProxmoxSharp build entrypoint (Fallout build). Requires the .NET 10 SDK on PATH +# (see global.json) and, for restoring the Fallout.* build packages, GITHUB_PACKAGES_PAT +# in the environment (a PAT with read:packages on the Fallout-build org; see nuget.config). +# +# ./build.sh # default target: Test +# ./build.sh Pack --version-suffix preview.42 +# ./build.sh Publish --nuget-api-key +set -eo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec dotnet run --project "$SCRIPT_DIR/build/_build.csproj" -- "$@" diff --git a/build/Build.cs b/build/Build.cs new file mode 100644 index 0000000..1f276a9 --- /dev/null +++ b/build/Build.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using Fallout.Common; +using Fallout.Common.IO; +using Fallout.Common.Tooling; +using Serilog; + +// ProxmoxSharp build pipeline — Fallout build (NUKE successor), per ADR-0001. +// +// Wraps the native `dotnet` toolchain (build regenerates ProxmoxSharp.Api from the +// pinned Proxmox schema via Kiota, then compiles). Targets: +// Compile → dotnet build the solution (-c Release) +// Test → dotnet test (--no-build) +// Pack → dotnet pack the 3 packable projects into artifacts/ (optional --version-suffix) +// Publish → dotnet nuget push artifacts/*.nupkg to nuget.org (Trusted Publishing key) +// +// ./build.sh # default: Test +// ./build.sh Pack --version-suffix preview.42 +// ./build.sh Publish --nuget-api-key # key minted by NuGet/login OIDC in CI +class Build : FalloutBuild +{ + public static int Main() => Execute(x => x.Test); + + const string Configuration = "Release"; + + [Parameter("Prerelease suffix appended to VersionPrefix (e.g. 'preview.42'). Empty → stable.")] + readonly string VersionSuffix; + + [Parameter("nuget.org push source.")] + readonly string NuGetSource = "https://api.nuget.org/v3/index.json"; + + [Parameter("API key for the push. In CI this is the short-lived key from NuGet/login (Trusted Publishing), never a stored secret.")] + readonly string NuGetApiKey; + + AbsolutePath Solution => RootDirectory / "ProxmoxSharp.slnx"; + AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; + + // Build the command into a plain string first: passing an interpolated string + // *directly* to StartProcess binds Fallout's ArgumentStringHandler, which + // auto-quotes each interpolation hole — collapsing a multi-token argument list + // into one quoted arg. A pre-built string takes the plain overload. + void Dotnet(string arguments) + { + string command = arguments; + ProcessTasks.StartProcess("dotnet", command, workingDirectory: RootDirectory).AssertZeroExitCode(); + } + + Target Clean => _ => _ + .Executes(() => + { + if (Directory.Exists(ArtifactsDirectory)) + Directory.Delete(ArtifactsDirectory, recursive: true); + }); + + // `dotnet build` triggers ProxmoxSharp.Api's GenerateProxmoxClient target, which + // runs `dotnet tool restore` (Kiota, pinned in .config/dotnet-tools.json) and + // regenerates Generated/ (gitignored) from the pinned schema before compiling. + Target Compile => _ => _ + .Executes(() => Dotnet($"build {Solution} -c {Configuration} --nologo")); + + // Live integration tests skip automatically without a secrets.env. + Target Test => _ => _ + .DependsOn(Compile) + .Executes(() => Dotnet($"test {Solution} -c {Configuration} --no-build --nologo")); + + // Packs the 3 packable projects (ProxmoxSharp, .Api, .Cli — SchemaGen/Tests are + // IsPackable=false). Packs fresh (no --no-build) so a -preview.N suffix stamps the + // assemblies too. Package IDs carry the Chrison.* prefix (see the csprojs). + Target Pack => _ => _ + .DependsOn(Clean, Test) + .Executes(() => + { + string suffix = string.IsNullOrWhiteSpace(VersionSuffix) ? "" : $" --version-suffix {VersionSuffix}"; + Dotnet($"pack {Solution} -c {Configuration} -o {ArtifactsDirectory}{suffix}"); + }); + + // Pushes every .nupkg in artifacts/ to nuget.org. --skip-duplicate makes re-runs + // idempotent; --no-symbols because symbols are embedded (DebugType=embedded). + Target Publish => _ => _ + .DependsOn(Pack) + .Executes(() => + { + if (string.IsNullOrWhiteSpace(NuGetApiKey)) + throw new Exception("Missing --nuget-api-key. In CI this is minted by NuGet/login (Trusted Publishing); locally, pass a temporary key."); + Log.Information("Pushing packages from {Artifacts} to {Source}", ArtifactsDirectory, NuGetSource); + AbsolutePath glob = ArtifactsDirectory / "*.nupkg"; + Dotnet($"nuget push {glob} --source {NuGetSource} --api-key {NuGetApiKey} --skip-duplicate --no-symbols"); + }); +} diff --git a/build/_build.csproj b/build/_build.csproj new file mode 100644 index 0000000..21b16fb --- /dev/null +++ b/build/_build.csproj @@ -0,0 +1,28 @@ + + + + + + Exe + net10.0 + disable + disable + + false + $(NoWarn);CS0649;CS0169 + + false + + + + + + + + + + diff --git a/nuget.config b/nuget.config index f7e46a4..7082505 100644 --- a/nuget.config +++ b/nuget.config @@ -3,8 +3,29 @@ - - + + + + + + + + + + + + + + + + + + diff --git a/src/ProxmoxSharp.Api/ProxmoxSharp.Api.csproj b/src/ProxmoxSharp.Api/ProxmoxSharp.Api.csproj index 452c1fe..f1d2649 100644 --- a/src/ProxmoxSharp.Api/ProxmoxSharp.Api.csproj +++ b/src/ProxmoxSharp.Api/ProxmoxSharp.Api.csproj @@ -7,6 +7,10 @@ Generator-only fixes without a PVE bump → 4th-part revision (9.2.2.1). --> 9.2.2 + + Chrison.ProxmoxSharp.Api Generated Proxmox VE API client (Kiota). Version tracks the PVE API release. diff --git a/src/ProxmoxSharp.Cli/ProxmoxSharp.Cli.csproj b/src/ProxmoxSharp.Cli/ProxmoxSharp.Cli.csproj index 54af2f6..32566ab 100644 --- a/src/ProxmoxSharp.Cli/ProxmoxSharp.Cli.csproj +++ b/src/ProxmoxSharp.Cli/ProxmoxSharp.Cli.csproj @@ -6,10 +6,12 @@ enable enable - + true proxmoxsharp - ProxmoxSharp.Cli + Chrison.ProxmoxSharp.Cli 0.1.0 CLI for ProxmoxSharp — read and discover Proxmox VE from the shell. diff --git a/src/ProxmoxSharp/ProxmoxSharp.csproj b/src/ProxmoxSharp/ProxmoxSharp.csproj index 1fbad0e..f7990db 100644 --- a/src/ProxmoxSharp/ProxmoxSharp.csproj +++ b/src/ProxmoxSharp/ProxmoxSharp.csproj @@ -11,7 +11,12 @@ 0.1.0 stable in NuGet ordering — a `0.1.0-preview.*` float would otherwise resolve the stale 0.1.0 stable (prereleases sort below their release). --> 0.2.0 - C# client for Proxmox VE — hand-written runtime over the generated ProxmoxSharp.Api. + + Chrison.ProxmoxSharp + C# client for Proxmox VE — hand-written runtime over the generated Chrison.ProxmoxSharp.Api.