From 6ed7d362848f9a69c939f5987b360ef2667e0c39 Mon Sep 17 00:00:00 2001 From: Ignacio Olave Date: Mon, 11 Aug 2025 11:40:39 -0400 Subject: [PATCH 01/40] chore: removed unused files --- .testcoverage.yml | 50 ----------------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 .testcoverage.yml diff --git a/.testcoverage.yml b/.testcoverage.yml deleted file mode 100644 index 015bdd9..0000000 --- a/.testcoverage.yml +++ /dev/null @@ -1,50 +0,0 @@ -# (mandatory) -# Path to coverprofile file (output of `go test -coverprofile` command). -# -# For cases where there are many coverage profiles, such as when running -# unit tests and integration tests separately, you can combine all those -# profiles into one. In this case, the profile should have a comma-separated list -# of profile files, e.g., 'cover_unit.out,cover_integration.out'. -profile: cover.out - -# (optional; but recommended to set) -# When specified reported file paths will not contain local prefix in the output -#local-prefix: "github.com/org/project" - -# Holds coverage thresholds percentages, values should be in range [0-100] -threshold: - # (optional; default 0) - # The minimum coverage that each file should have - #file: 70 - - # (optional; default 0) - # The minimum coverage that each package should have - #package: 80 - - # (optional; default 0) - # The minimum total coverage project should have - total: 85 - -# Holds regexp rules which will override thresholds for matched files or packages -# using their paths. -# -# First rule from this list that matches file or package is going to apply -# new threshold to it. If project has multiple rules that match same path, -# override rules should be listed in order from specific to more general rules. -#override: - # Increase coverage threshold to 100% for `foo` package - # (default is 80, as configured above in this example) -#- threshold: 100 -# path: ^pkg/lib/foo$ - -# Holds regexp rules which will exclude matched files or packages -# from coverage statistics -exclude: - # Exclude files or packages matching their paths - paths: - - \.pb\.go$ # excludes all protobuf generated files - # - ^pkg/bar # exclude package `pkg/bar` - -# NOTES: -# - symbol `/` in all path regexps will be replaced by current OS file path separator -# to properly work on Windows From 54eaa27475b35093c6bd6de05a85ba039ab68f74 Mon Sep 17 00:00:00 2001 From: Ignacio Olave Date: Mon, 11 Aug 2025 13:07:55 -0400 Subject: [PATCH 02/40] chore: simplified docs generation --- .github/workflows/deploy-docs.yml | 4 +- Makefile | 18 ++----- scripts/generate-docs.sh | 87 +++++++++++++++++++++++++++++-- scripts/install-docs-deps.sh | 15 ------ scripts/preview-docs.sh | 4 -- 5 files changed, 88 insertions(+), 40 deletions(-) mode change 100644 => 100755 scripts/generate-docs.sh delete mode 100755 scripts/install-docs-deps.sh delete mode 100644 scripts/preview-docs.sh diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 2cac7a9..b494d3c 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -30,10 +30,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Install docs dependencies - run: make install-docs-dependencies - name: Generate docs - run: make generate-docs + run: make docs - name: Setup Pages id: pages uses: actions/configure-pages@v5 diff --git a/Makefile b/Makefile index bb825a7..fbbf363 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,5 @@ GOBIN ?= $$(go env GOPATH)/bin -.PHONY: install-docs-dependencies -install-docs-dependencies: - ./scripts/install-docs-deps.sh - -.phony: install-dependencies -install-dependencies: install-docs-dependencies - go mod tidy - .PHONY: test test: go test -v ./... @@ -16,13 +8,13 @@ test: coverage: ./scripts/coverage.sh -.PHONY: generate-docs -generate-docs: install-docs-dependencies - bash ./scripts/generate-docs.sh +.PHONY: docs +docs: + ./scripts/generate-docs.sh .PHONY: preview-docs -preview-docs: install-docs-dependencies generate-docs - bash ./scripts/preview-docs.sh +preview-docs: + ./scripts/generate-docs.sh -p .PHONY: build build: install-dependencies diff --git a/scripts/generate-docs.sh b/scripts/generate-docs.sh old mode 100644 new mode 100755 index c6b8d72..e588492 --- a/scripts/generate-docs.sh +++ b/scripts/generate-docs.sh @@ -1,8 +1,85 @@ +#!/bin/bash + +# set opts +PREVIEW=false +while getopts ":p" opt; do + case $opt in + p) + PREVIEW=true + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + +# set a variable to know this script path +SCRIPT_PATH="$( cd "$(dirname "$0")" ; pwd -P )" +ROOT="$(realpath $SCRIPT_PATH/..)" + +################################################################################ +## Build api-wrapper docs +################################################################################ +which swag &> /dev/null +if [ "$?" -ne "0" ]; then + set -e + echo "info: installing swag" + go install github.com/swaggo/swag/cmd/swag@v1.16.6 + set +e +fi +swag init \ + -d $ROOT \ + -g ./cmd/pve_api_wrapper/pve_api_wrapper.go \ + --exclude ./ \ + --parseInternal \ + -o $ROOT/docs/api-wrapper \ + +################################################################################ +## Build go-client docs and generate site +################################################################################ +# Check if $TMPDIR is set +if [ -z "$TMPDIR" ]; then + echo "warn: TMPDIR is not set, using /tmp" + TMPDIR="/tmp" +fi + +# check if TMPDIR is exists +if [ ! -d "$TMPDIR" ]; then + echo "error: TMPDIR '$TMPDIR' does not exist" + exit 1 +fi + VENV="${TMPDIR}/venv/go-proxmox" +mkdir -p $VENV +python3 -m venv ${VENV} source "${VENV}/bin/activate" -rm -rf ./docs/go-client/pkg -mkdir -p ./docs/go-client/pkg/ -go run ./cmd/gomarkdoc/main.go -swag init -g ./cmd/pve_api_wrapper/pve_api_wrapper.go --parseInternal -o ./docs/api-wrapper -mkdocs build + +PIP_DEPS=" \ + mkdocs-material \ + pymdown-extensions \ + markdown-callouts \ + mkdocs-render-swagger-plugin \ +" + +# iterate over the list of dependencies +for dep in $PIP_DEPS; do + pip3 show $dep &> /dev/null + if [ "$?" -ne "0" ]; then + echo "info: installing $dep" + pip3 install $dep + fi +done + +rm -rf $ROOT/docs/go-client/pkg +mkdir -p $ROOT/docs/go-client/pkg +go run $ROOT/cmd/gomarkdoc/main.go +mkdocs build -f $ROOT/mkdocs.yml + +################################################################################ +## Preview docs +################################################################################ +if [ "$PREVIEW" == "true" ]; then + mkdocs serve -f $ROOT/mkdocs.yml +fi diff --git a/scripts/install-docs-deps.sh b/scripts/install-docs-deps.sh deleted file mode 100755 index 5999003..0000000 --- a/scripts/install-docs-deps.sh +++ /dev/null @@ -1,15 +0,0 @@ -VENV="${TMPDIR}/venv/go-proxmox" -mkdir -p $VENV - -python3 -m venv ${VENV} -source "${VENV}/bin/activate" - -pip3 install \ - mkdocs \ - mkdocs-material \ - pymdown-extensions \ - markdown-callouts \ - mkdocs-render-swagger-plugin - -go install github.com/swaggo/swag/cmd/swag@latest - diff --git a/scripts/preview-docs.sh b/scripts/preview-docs.sh deleted file mode 100644 index f85367b..0000000 --- a/scripts/preview-docs.sh +++ /dev/null @@ -1,4 +0,0 @@ -VENV="${TMPDIR}/venv/go-proxmox" - -source "${VENV}/bin/activate" -mkdocs serve From cf5d068442f691bf3cd67530721ab4b0fc4853f0 Mon Sep 17 00:00:00 2001 From: Ignacio Olave Date: Mon, 11 Aug 2025 13:10:07 -0400 Subject: [PATCH 03/40] docs: renamed install script to install-paw --- docs/api-wrapper/index.md | 6 +++--- mkdocs.yml | 2 +- scripts/{install.sh => install-paw.sh} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename scripts/{install.sh => install-paw.sh} (100%) diff --git a/docs/api-wrapper/index.md b/docs/api-wrapper/index.md index 4991483..e6ad466 100644 --- a/docs/api-wrapper/index.md +++ b/docs/api-wrapper/index.md @@ -10,10 +10,10 @@ The pve api wrapper is an http server ment to be installed on the proxmox host s ## Installation ### Latest release -The installation script installs the `pve-api-wrapper` binary into `/usr/local/bin`. +The installation script installs the latest `pve-api-wrapper` binary into `/usr/local/bin`. ```bash -curl https://raw.githubusercontent.com/iolave/go-proxmox/refs/tags/latest/scripts/install.sh | bash +curl https://raw.githubusercontent.com/iolave/go-proxmox/refs/tags/latest/scripts/install-paw.sh | bash ``` _Inspect the installation script code [here]._ @@ -51,7 +51,7 @@ pve-api-wrapper [--version] [--pve-host PVE-HOST] [--pve-port PVE-PORT] [--host sudo pve-api-wrapper service install ``` -[here]: https://github.com/iolave/go-proxmox/blob/latest/scripts/install.sh +[here]: https://github.com/iolave/go-proxmox/blob/latest/scripts/install-paw.sh [reference]: https://go-proxmox.iolave.com/api-wrapper/reference/ ### PVE API wrapper #### Fixed - PVE original passthrough handler now properly includes the request query params (resolves [#4](https://github.com/iolave/go-proxmox/issues/4)). + ### PVE API client + #### Added -- Added a Core Service to the client: - - Implements `GET /api2/json/version` as `GetVersion`. +- Added the `api` package that contains raw proxmox api implementations for: + - `/api2/json/access` + - `/api2/json/cluster` #### Changed - Renamed `pve.PVE` to `pve.Client`. - -#### Removed -- Removed client `GetVersion` method. - +- Moved the `GetVersion` method from `pkg/pve` to `pkg/pve/core`. ## [v0.7.1] ### PVE API client diff --git a/README.md b/README.md index 3cf96a9..5d9a566 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,73 @@ > [!WARNING] > All versions released prior to `v1.0.0` are to be considered [breaking changes](https://semver.org/#how-do-i-know-when-to-release-100) -go-proxmox is a set of tools I built in order to manage proxmox instances remotely without too much effort. +A fully-featured set of tools for Proxmox Virtual Environment. +This repository provides three layers: -## Tools -- Proxmox go client: To see a list of all implemented api endpoints and more information, click [here]. -- Proxmox cli (not available yet): Proxmox remote management tool. -- Proxmox [api wrapper]: Proxmox api wrapper that adds missing features to proxmox default one. +- Low-level 1:1 API implementation: minimal abstractions, closely matching the upstream Proxmox REST API. To see a list of all implemented api endpoints and more information, click [here]. +- [API Wrapper] (binary): exposes additional features and allows original api calls to be passed through. +- High-level, ergonomic client – a set of well-documented Go interfaces that smooths over quirks of the raw API and integrates extensions provided by the wrapper. [here]: https://go-proxmox.iolave.com/go-client/ -[api wrapper]: https://go-proxmox.iolave.com/api-wrapper/ -[telmate/terraform-provider-proxmox]: https://github.com/Telmate/terraform-provider-proxmox +[API Wrapper]: https://go-proxmox.iolave.com/api-wrapper/ + +## Features +### 1:1 Proxmox API Implementation +- Direct mapping of Proxmox endpoints, parameters, and responses. +- As close to upstream behavior as possible. +- Useful for users who need full control or raw input/output. +- Unclear or inconsistent API behaviors are intentionally preserved (mirrors the original). + +### API Wrapper +Small binary that exposes: + +- Original API. +- Custom API with missing functionality from the native one. + +It is designed to be deployed on a Proxmox node. + +### High-level Client +- Human-friendly Go API. +- Strong typing and validation where the raw API is ambiguous. +- Includes wrapper-only features + +## Project Structure +```text +. +├── pkg/ # Packages that can be imported by other projects +│ │ +│ ├── api/ # Proxmox API 1:1 implementation +│ ├── helpers/ # General purpose helpers +│ └── pve/ # High-level Proxmox API client +│ +├── cmd/ # Command line tools +│ │ +│ ├── pve-api-wrapper/ # Proxmox API wrapper +│ └── gomarkdoc/ # Documentation generator +│ +├── internal/ # API Wrapper internals +├── scripts/ # Shell scripts, mostly for development and Makefile +├── docs/ # MKDocs documentation +├── README.md # This file +├── Makefile # Makefile for building, testing and documentation generation +├── LICENSE # License +├── go.mod # Go module definition +├── go.sum # Go module checksums +└── mkdocs.yml # MKDocs configuration +``` + +## Documentation +- The go client documentation is available [here]. +- The API wrapper documentation is available [here](https://go-proxmox.iolave.com/api-wrapper/). + +## Contributing +Contributions are welcome! + +Please: + +- Open an issue before large changes. +- Keep PRs focused and well‑scoped. +- Include relevant tests. + +[here]: https://go-proxmox.iolave.com/go-client/ +[API Wrapper]: https://go-proxmox.iolave.com/api-wrapper/ diff --git a/cmd/gomarkdoc/main.go b/cmd/gomarkdoc/main.go index 0c3d98a..44a59b6 100644 --- a/cmd/gomarkdoc/main.go +++ b/cmd/gomarkdoc/main.go @@ -13,10 +13,9 @@ import ( func main() { packages := []string{ + "api", "pve", "helpers", - "cloudflare", - "errors", } for i := 0; i < len(packages); i++ { diff --git a/docs/go-client/implemented-endpoints.md b/docs/go-client/implemented-endpoints.md index e412670..b133828 100644 --- a/docs/go-client/implemented-endpoints.md +++ b/docs/go-client/implemented-endpoints.md @@ -1,22 +1,21 @@ -This is a development-purposed list to keep track of the implemented proxmox [endpoints](https://pve.proxmox.com/pve-docs/api-viewer/) as a callable golang func but **not** necessarily as a CLI command. +This is a development-purposed list to keep track of the implemented proxmox [endpoints](https://pve.proxmox.com/pve-docs/api-viewer/) as a callable golang func within the `pkg/api` package. | Symbol | Description | |:------:|:-----------:| |:material-close:|Not implemented| -|:material-check:|Partially implemented (notes will be added in the docs)| -|:material-check-all:|Fully implemented| +|:material-check:|Implemented| ## PVE Core | Path | GET | POST | PUT | DELETE | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/version` |:material-check-all:| +| `/version` |:material-check:| ## Access | Path | GET | POST | PUT | DELETE | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| | `/access/acl` |:material-close:||:material-close:| | `/access/password` |||:material-close:| -| `/access/permissions` |:material-check-all:| +| `/access/permissions` |:material-close:| | `/access/ticket` |:material-close:|:material-close:| ### Users @@ -65,171 +64,171 @@ This is a development-purposed list to keep track of the implemented proxmox [en ## Cluster | Path | GET | POST | PUT | DELETE | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster` |:material-close:| -| `/cluster/log` |:material-close:| -| `/cluster/nextid` |:material-check-all:| -| `/cluster/options` |:material-close:||:material-close:| +| `/cluster` |:material-check:| +| `/cluster/log` |:material-check:| +| `/cluster/nextid` |:material-check:| +| `/cluster/options` |:material-check:||:material-check:| | `/cluster/resources` |:material-check:| -| `/cluster/status` |:material-close:| -| `/cluster/tasks` |:material-close:| +| `/cluster/status` |:material-check:| +| `/cluster/tasks` |:material-check:| ### Acme | Path | GET | POST | PUT | DELETE | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/acme` |:material-close:| -| `/cluster/acme/challenge-schema` |:material-close:| -| `/cluster/acme/directories` |:material-close:| -| `/cluster/acme/meta` |:material-close:| -| `/cluster/acme/tos` |:material-close:| -| `/cluster/acme/account` |:material-close:|:material-close:| -| `/cluster/acme/account/:name` |:material-close:||:material-close:|:material-close:| -| `/cluster/acme/plugins` |:material-close:|:material-close:| -| `/cluster/acme/plugins/:id` |:material-close:||:material-close:|:material-close:| +| `/cluster/acme` |:material-check:| +| `/cluster/acme/challenge-schema` |:material-check:| +| `/cluster/acme/directories` |:material-check:| +| `/cluster/acme/meta` |:material-check:| +| `/cluster/acme/tos` |:material-check:| +| `/cluster/acme/account` |:material-check:|:material-check:| +| `/cluster/acme/account/:name` |:material-check:||:material-check:|:material-check:| +| `/cluster/acme/plugins` |:material-check:|:material-check:| +| `/cluster/acme/plugins/:id` |:material-check:||:material-check:|:material-check:| ### Backup | Path | GET | POST | PUT | DELETE | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/backup` |:material-close:|:material-close:| -| `/cluster/backup/:id` |:material-close:||:material-close:|:material-close:| -| `/cluster/backup/:id/included_volumes` |:material-close:| +| `/cluster/backup` |:material-check:|:material-check:| +| `/cluster/backup/:id` |:material-check:||:material-check:|:material-check:| +| `/cluster/backup/:id/included_volumes` |:material-check:| ### Backup info | path | get | post | put | delete | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/backup-info` |:material-close:| -| `/cluster/backup-info/not-backed-up` |:material-close:| +| `/cluster/backup-info` |:material-check:| +| `/cluster/backup-info/not-backed-up` |:material-check:| ### Ceph | path | get | post | put | delete | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/ceph` |:material-close:| -| `/cluster/ceph/metadata` |:material-close:| -| `/cluster/ceph/status` |:material-close:| -| `/cluster/ceph/flags` |:material-close:||:material-close:| -| `/cluster/ceph/flags/:flag` |:material-close:||:material-close:| +| `/cluster/ceph` |:material-check:| +| `/cluster/ceph/metadata` |:material-check:| +| `/cluster/ceph/status` |:material-check:| +| `/cluster/ceph/flags` |:material-check:||:material-check:| +| `/cluster/ceph/flags/:flag` |:material-check:||:material-check:| ### Config | path | get | post | put | delete | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/config` |:material-close:|:material-close:| -| `/cluster/config/apiversion` |:material-close:| -| `/cluster/config/join` |:material-close:|:material-close:| -| `/cluster/config/qdevice` |:material-close:|:material-close:| -| `/cluster/config/totem` |:material-close:| -| `/cluster/config/nodes` |:material-close:| -| `/cluster/config/nodes/:node` ||:material-close:||:material-close:| +| `/cluster/config` |:material-check:|:material-check:| +| `/cluster/config/apiversion` |:material-check:| +| `/cluster/config/join` |:material-check:|:material-check:| +| `/cluster/config/qdevice` |:material-check:|:material-check:| +| `/cluster/config/totem` |:material-check:| +| `/cluster/config/nodes` |:material-check:| +| `/cluster/config/nodes/:node` ||:material-check:||:material-check:| ### Firewall | path | get | post | put | delete | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/firewall` |:material-close:| -| `/cluster/firewall/macros` |:material-close:| -| `/cluster/firewall/options` |:material-close:||:material-close:| -| `/cluster/firewall/refs` |:material-close:| -| `/cluster/firewall/aliases` |:material-check-all:|:material-check-all:| -| `/cluster/firewall/aliases/:name` |:material-check-all:||:material-check-all:|:material-check-all:| -| `/cluster/firewall/groups` |:material-close:|:material-close:| -| `/cluster/firewall/groups/:group` |:material-close:|:material-close:||:material-close:| -| `/cluster/firewall/groups/:group/:pos` |:material-close:||:material-close:|:material-close:| -| `/cluster/firewall/ipset` |:material-check-all:|:material-close:| -| `/cluster/firewall/ipset/:name` |:material-close:|:material-close:||:material-close:| -| `/cluster/firewall/ipset/:name/:cidr` |:material-close:||:material-close:|:material-close:| -| `/cluster/firewall/rules` |:material-check-all:|:material-close:| -| `/cluster/firewall/rules/:pos` |:material-close:||:material-close:|:material-close:| +| `/cluster/firewall` |:material-check:| +| `/cluster/firewall/macros` |:material-check:| +| `/cluster/firewall/options` |:material-check:||:material-check:| +| `/cluster/firewall/refs` |:material-check:| +| `/cluster/firewall/aliases` |:material-check:|:material-check:| +| `/cluster/firewall/aliases/:name` |:material-check:||:material-check:|:material-check:| +| `/cluster/firewall/groups` |:material-check:|:material-check:| +| `/cluster/firewall/groups/:group` |:material-check:|:material-check:||:material-check:| +| `/cluster/firewall/groups/:group/:pos` |:material-check:||:material-check:|:material-check:| +| `/cluster/firewall/ipset` |:material-check:|:material-check:| +| `/cluster/firewall/ipset/:name` |:material-check:|:material-check:||:material-check:| +| `/cluster/firewall/ipset/:name/:cidr` |:material-check:||:material-check:|:material-check:| +| `/cluster/firewall/rules` |:material-check:|:material-check:| +| `/cluster/firewall/rules/:pos` |:material-check:||:material-check:|:material-check:| ### High availability | path | get | post | put | delete | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/ha` |:material-close:| -| `/cluster/ha/groups` |:material-close:|:material-close:| -| `/cluster/ha/groups/:group` |:material-close:||:material-close:|:material-close:| -| `/cluster/ha/resources` |:material-close:|:material-close:| -| `/cluster/ha/resources/:sid` |:material-close:||:material-close:|:material-close:| -| `/cluster/ha/resources/:sid/migrate` ||:material-close:| -| `/cluster/ha/resources/:sid/relocate` ||:material-close:| -| `/cluster/ha/status` |:material-close:| -| `/cluster/ha/status/current` |:material-close:| -| `/cluster/ha/status/manager_status` |:material-close:| +| `/cluster/ha` |:material-check:| +| `/cluster/ha/groups` |:material-check:|:material-check:| +| `/cluster/ha/groups/:group` |:material-check:||:material-check:|:material-check:| +| `/cluster/ha/resources` |:material-check:|:material-check:| +| `/cluster/ha/resources/:sid` |:material-check:||:material-check:|:material-check:| +| `/cluster/ha/resources/:sid/migrate` ||:material-check:| +| `/cluster/ha/resources/:sid/relocate` ||:material-check:| +| `/cluster/ha/status` |:material-check:| +| `/cluster/ha/status/current` |:material-check:| +| `/cluster/ha/status/manager_status` |:material-check:| ### Jobs | path | get | post | put | delete | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/jobs` |:material-close:| -| `/cluster/jobs/schedule-analyze` |:material-close:| -| `/cluster/jobs/realm-sync` |:material-close:| -| `/cluster/jobs/realm-sync/:id` |:material-close:|:material-close:|:material-close:|:material-close:| +| `/cluster/jobs` |:material-check:| +| `/cluster/jobs/schedule-analyze` |:material-check:| +| `/cluster/jobs/realm-sync` |:material-check:| +| `/cluster/jobs/realm-sync/:id` |:material-check:|:material-check:|:material-check:|:material-check:| ### Mapping | path | get | post | put | delete | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/mapping` |:material-close:| -| `/cluster/mapping/pci` |:material-close:|:material-close:| -| `/cluster/mapping/pci/:id` |:material-close:||:material-close:|:material-close:| -| `/cluster/mapping/usb` |:material-close:|:material-close:| -| `/cluster/mapping/usb/:id` |:material-close:||:material-close:|:material-close:| +| `/cluster/mapping` |:material-check:| +| `/cluster/mapping/pci` |:material-check:|:material-check:| +| `/cluster/mapping/pci/:id` |:material-check:||:material-check:|:material-check:| +| `/cluster/mapping/usb` |:material-check:|:material-check:| +| `/cluster/mapping/usb/:id` |:material-check:||:material-check:|:material-check:| ### Metrics | path | get | post | put | delete | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/metrics` |:material-close:| -| `/cluster/metrics/export` |:material-close:| -| `/cluster/metrics/server` |:material-close:| -| `/cluster/metrics/server/:id` |:material-close:|:material-close:|:material-close:|:material-close:| +| `/cluster/metrics` |:material-check:| +| `/cluster/metrics/export` |:material-check:| +| `/cluster/metrics/server` |:material-check:| +| `/cluster/metrics/server/:id` |:material-check:|:material-check:|:material-check:|:material-check:| ### Notifications | path | get | post | put | delete | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/notifications` |:material-close:| -| `/cluster/notifications/matcher-field-values` |:material-close:| -| `/cluster/notifications/matcher-fields` |:material-close:| -| `/cluster/notifications/endpoints` |:material-close:| -| `/cluster/notifications/endpoints/gotify` |:material-close:|:material-close:| -| `/cluster/notifications/endpoints/gotify/:name` |:material-close:||:material-close:|:material-close:| -| `/cluster/notifications/endpoints/sendmail` |:material-close:|:material-close:| -| `/cluster/notifications/endpoints/sendmail/:name` |:material-close:||:material-close:|:material-close:| -| `/cluster/notifications/endpoints/smpt` |:material-close:|:material-close:| -| `/cluster/notifications/endpoints/smpt/:name` |:material-close:||:material-close:|:material-close:| -| `/cluster/notifications/endpoints/webhook` |:material-close:|:material-close:| -| `/cluster/notifications/endpoints/webhook/:name` |:material-close:||:material-close:|:material-close:| -| `/cluster/notifications/matchers` |:material-close:|:material-close:| -| `/cluster/notifications/matchers/:name` |:material-close:||:material-close:|:material-close:| -| `/cluster/notifications/targets` |:material-close:| -| `/cluster/notifications/targets/:name` |:material-close:||:material-close:|:material-close:| +| `/cluster/notifications` |:material-check:| +| `/cluster/notifications/matcher-field-values` |:material-check:| +| `/cluster/notifications/matcher-fields` |:material-check:| +| `/cluster/notifications/endpoints` |:material-check:| +| `/cluster/notifications/endpoints/gotify` |:material-check:|:material-check:| +| `/cluster/notifications/endpoints/gotify/:name` |:material-check:||:material-check:|:material-check:| +| `/cluster/notifications/endpoints/sendmail` |:material-check:|:material-check:| +| `/cluster/notifications/endpoints/sendmail/:name` |:material-check:||:material-check:|:material-check:| +| `/cluster/notifications/endpoints/smpt` |:material-check:|:material-check:| +| `/cluster/notifications/endpoints/smpt/:name` |:material-check:||:material-check:|:material-check:| +| `/cluster/notifications/endpoints/webhook` |:material-check:|:material-check:| +| `/cluster/notifications/endpoints/webhook/:name` |:material-check:||:material-check:|:material-check:| +| `/cluster/notifications/matchers` |:material-check:|:material-check:| +| `/cluster/notifications/matchers/:name` |:material-check:||:material-check:|:material-check:| +| `/cluster/notifications/targets` |:material-check:| +| `/cluster/notifications/targets/:name` |:material-check:||:material-check:|:material-check:| ### Replication | path | get | post | put | delete | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/replication` |:material-close:|:material-close:| -| `/cluster/replication/:id` |:material-close:||:material-close:|:material-close:| +| `/cluster/replication` |:material-check:|:material-check:| +| `/cluster/replication/:id` |:material-check:||:material-check:|:material-check:| ### SDN | path | get | post | put | delete | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/cluster/sdn` |:material-close:||:material-close:| -| `/cluster/sdn/controllers` |:material-close:|:material-close:| -| `/cluster/sdn/controllers/:controller` |:material-close:||:material-close:|:material-close:| -| `/cluster/sdn/dns` |:material-close:|:material-close:| -| `/cluster/sdn/dns/:dns` |:material-close:||:material-close:|:material-close:| -| `/cluster/sdn/ipams` |:material-close:|:material-close:| -| `/cluster/sdn/ipams/:ipam` |:material-close:||:material-close:|:material-close:| -| `/cluster/sdn/vnets` |:material-close:|:material-close:| -| `/cluster/sdn/vnets/:vnet` |:material-close:||:material-close:|:material-close:| -| `/cluster/sdn/vnets/:vnet/ips` ||:material-close:|:material-close:|:material-close:| -| `/cluster/sdn/vnets/:vnet/firewall` |:material-close:| -| `/cluster/sdn/vnets/:vnet/firewall/options` |:material-close:||:material-close:| -| `/cluster/sdn/vnets/:vnet/firewall/rules` |:material-close:|:material-close:| -| `/cluster/sdn/vnets/:vnet/firewall/rules/:pos` |:material-close:||:material-close:|:material-close:| -| `/cluster/sdn/vnets/:vnet/subnets` |:material-close:|:material-close:| -| `/cluster/sdn/vnets/:vnet/subnets/:subnet` |:material-close:||:material-close:|:material-close:| -| `/cluster/sdn/zones` |:material-close:|:material-close:| -| `/cluster/sdn/zones/:zone` |:material-close:||:material-close:|:material-close:| +| `/cluster/sdn` |:material-check:||:material-check:| +| `/cluster/sdn/controllers` |:material-check:|:material-check:| +| `/cluster/sdn/controllers/:controller` |:material-check:||:material-check:|:material-check:| +| `/cluster/sdn/dns` |:material-check:|:material-check:| +| `/cluster/sdn/dns/:dns` |:material-check:||:material-check:|:material-check:| +| `/cluster/sdn/ipams` |:material-check:|:material-check:| +| `/cluster/sdn/ipams/:ipam` |:material-check:||:material-check:|:material-check:| +| `/cluster/sdn/vnets` |:material-check:|:material-check:| +| `/cluster/sdn/vnets/:vnet` |:material-check:||:material-check:|:material-check:| +| `/cluster/sdn/vnets/:vnet/ips` ||:material-check:|:material-check:|:material-check:| +| `/cluster/sdn/vnets/:vnet/firewall` |:material-check:| +| `/cluster/sdn/vnets/:vnet/firewall/options` |:material-check:||:material-check:| +| `/cluster/sdn/vnets/:vnet/firewall/rules` |:material-check:|:material-check:| +| `/cluster/sdn/vnets/:vnet/firewall/rules/:pos` |:material-check:||:material-check:|:material-check:| +| `/cluster/sdn/vnets/:vnet/subnets` |:material-check:|:material-check:| +| `/cluster/sdn/vnets/:vnet/subnets/:subnet` |:material-check:||:material-check:|:material-check:| +| `/cluster/sdn/zones` |:material-check:|:material-check:| +| `/cluster/sdn/zones/:zone` |:material-check:||:material-check:|:material-check:| ## Nodes | Path | GET | POST | PUT | DELETE | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/nodes` |:material-check-all:| -| `/nodes/:node` |:material-check-all:| +| `/nodes` |:material-close:| +| `/nodes/:node` |:material-close:| | `/nodes/:node/aplinfo` |:material-close:|:material-close:| | `/nodes/:node/config` |:material-close:||:material-close:| | `/nodes/:node/dns` |:material-close:||:material-close:| @@ -259,11 +258,11 @@ This is a development-purposed list to keep track of the implemented proxmox [en ### Node: apt | Path | GET | POST | PUT | DELETE | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/nodes/:node/apt` |:material-check-all:| -| `/nodes/:node/apt/changelog` |:material-check-all:| -| `/nodes/:node/apt/repositories` |:material-check-all:|:material-check-all:|:material-check-all:| -| `/nodes/:node/apt/update` |:material-check:|:material-check-all:| -| `/nodes/:node/apt/versions` |:material-check:| +| `/nodes/:node/apt` |:material-close:| +| `/nodes/:node/apt/changelog` |:material-close:| +| `/nodes/:node/apt/repositories` |:material-close:|:material-close:|:material-close:| +| `/nodes/:node/apt/update` |:material-close:|:material-close:| +| `/nodes/:node/apt/versions` |:material-close:| ### Node: Capabilities | Path | GET | POST | PUT | DELETE | @@ -340,9 +339,9 @@ This is a development-purposed list to keep track of the implemented proxmox [en | Path | GET | POST | PUT | DELETE | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| | `/nodes/:node/firewall` |:material-close:| -| `/nodes/:node/firewall/rules` |:material-check-all:|:material-check-all:| -| `/nodes/:node/firewall/rules/:pos` |:material-check-all:||:material-close:|:material-check:| -| `/nodes/:node/firewall/log` |:material-check:| +| `/nodes/:node/firewall/rules` |:material-close:|:material-close:| +| `/nodes/:node/firewall/rules/:pos` |:material-close:||:material-close:|:material-close:| +| `/nodes/:node/firewall/log` |:material-close:| | `/nodes/:node/firewall/options` |:material-close:||:material-close:| ### Node: Hardware @@ -356,8 +355,8 @@ This is a development-purposed list to keep track of the implemented proxmox [en ### Node: lxc | Path | GET | POST | PUT | DELETE | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/nodes/:node/lxc` |:material-check-all:|:material-check:| -| `/nodes/:node/lxc/:vmid` |:material-check-all:| | |:material-check-all:| +| `/nodes/:node/lxc` |:material-close:|:material-close:| +| `/nodes/:node/lxc/:vmid` |:material-close:| | |:material-close:| | `/nodes/:node/lxc/:vmid/firewall` |:material-close:| | `/nodes/:node/lxc/:vmid/firewall/log` |:material-close:| | `/nodes/:node/lxc/:vmid/firewall/option` |:material-close:||:material-close:| @@ -374,17 +373,17 @@ This is a development-purposed list to keep track of the implemented proxmox [en | `/nodes/:node/lxc/:vmid/snapshot/:name/config` |:material-close:||:material-close:| | `/nodes/:node/lxc/:vmid/snapshot/:name/rollback` ||:material-close:| | `/nodes/:node/lxc/:vmid/status` |:material-close:| -| `/nodes/:node/lxc/:vmid/status/current` |:material-check-all:| -| `/nodes/:node/lxc/:vmid/status/reboot` ||:material-check-all:| -| `/nodes/:node/lxc/:vmid/status/resume` ||:material-check-all:| -| `/nodes/:check-all/lxc/:vmid/status/shutdown` ||:material-check-all:| -| `/nodes/:node/lxc/:vmid/status/start` ||:material-check-all:| -| `/nodes/:check-all/lxc/:vmid/status/stop` ||:material-check-all:| -| `/nodes/:node/lxc/:vmid/status/suspend` ||:material-check-all:| +| `/nodes/:node/lxc/:vmid/status/current` |:material-close:| +| `/nodes/:node/lxc/:vmid/status/reboot` ||:material-close:| +| `/nodes/:node/lxc/:vmid/status/resume` ||:material-close:| +| `/nodes/:node/lxc/:vmid/status/shutdown` ||:material-close:| +| `/nodes/:node/lxc/:vmid/status/start` ||:material-close:| +| `/nodes/:node/lxc/:vmid/status/stop` ||:material-close:| +| `/nodes/:node/lxc/:vmid/status/suspend` ||:material-close:| | `/nodes/:node/lxc/:vmid/clone` ||:material-close:| | `/nodes/:node/lxc/:vmid/config` |:material-close:||:material-close:| | `/nodes/:node/lxc/:vmid/feature` |:material-close:| -| `/nodes/:node/lxc/:vmid/interfaces` |:material-check-all:| +| `/nodes/:node/lxc/:vmid/interfaces` |:material-close:| | `/nodes/:node/lxc/:vmid/migrate` ||:material-close:| | `/nodes/:node/lxc/:vmid/move_volume` ||:material-close:| | `/nodes/:node/lxc/:vmid/mtunnel` ||:material-close:| @@ -528,14 +527,14 @@ This is a development-purposed list to keep track of the implemented proxmox [en ### Node: Storage | Path | GET | POST | PUT | DELETE | |-------------------------------------------------------------|:------------------:|:--------------:|:-----:|:-:| -| `/nodes/:node/storage` |:material-check-all:| +| `/nodes/:node/storage` |:material-close:| | `/nodes/:node/storage/:storage` |:material-close:| -| `/nodes/:node/storage/:storage/content` |:material-close:|:material-check-all:| +| `/nodes/:node/storage/:storage/content` |:material-close:|:material-close:| | `/nodes/:node/storage/:storage/content/:volume` |:material-close:|:material-close:|:material-close:|:material-close:| | `/nodes/:node/storage/:storage/file-restore` |:material-close:|:material-close:|:material-close:|:material-close:| | `/nodes/:node/storage/:storage/file-restore/download` |:material-close:| | `/nodes/:node/storage/:storage/file-restore/list` |:material-close:| -| `/nodes/:node/storage/:storage/download-url` ||:material-check:| +| `/nodes/:node/storage/:storage/download-url` ||:material-close:| | `/nodes/:node/storage/:storage/import-metadata` |:material-close:| | `/nodes/:node/storage/:storage/prunebackups` |:material-close:|||:material-close:| | `/nodes/:node/storage/:storage/rdd` |:material-close:| diff --git a/docs/go-client/index.md b/docs/go-client/index.md index 579da56..c342b67 100644 --- a/docs/go-client/index.md +++ b/docs/go-client/index.md @@ -1,7 +1,7 @@ # Go client ## Install ```bash -go get github.com/iolave/go-proxmox@v0.5.0 +go get github.com/iolave/go-proxmox@v0.8.0 ``` ## Environment variables diff --git a/mkdocs.yml b/mkdocs.yml index 280cb97..a5ea08d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -71,11 +71,9 @@ nav: - Go client: - go-client/index.md - Reference: - - pkg: - - go-client/pkg/cloudflare.md - - go-client/pkg/helpers.md - - go-client/pkg/pve.md - - go-client/pkg/errors.md + - pkg/api: go-client/pkg/api.md + - pkg/pve: go-client/pkg/pve.md + - pkg/helpers: go-client/pkg/helpers.md - Implemented endpoints: go-client/implemented-endpoints.md - API Wrapper: - api-wrapper/index.md From 4122265953b3562c0c52d7732ace0b724dc23268 Mon Sep 17 00:00:00 2001 From: Ignacio Olave Date: Thu, 11 Dec 2025 18:14:07 -0300 Subject: [PATCH 40/40] feat: the api pkg now have built-in credentials --- docs/go-client/index.md | 96 +++++++++++++++++++++++++++----------- mkdocs.yml | 7 +-- pkg/api/api.go | 67 +++++++++++++++----------- pkg/api/api_credentials.go | 92 ++++++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+), 58 deletions(-) create mode 100644 pkg/api/api_credentials.go diff --git a/docs/go-client/index.md b/docs/go-client/index.md index c342b67..e34befd 100644 --- a/docs/go-client/index.md +++ b/docs/go-client/index.md @@ -15,62 +15,104 @@ The following environment variables can be used to interact both with the cli an |PROXMOX_TOKEN |Proxmox VE generated token |X |- | ## Getting started -First, import the [pve package]: +First, import the [api package]: ```go -import "github.com/iolave/go-proxmox/pkg/pve" +import "github.com/iolave/go-proxmox/pkg/api" ``` In order to create a new pve api you can either use environment variables or the built-in [credentials] constructors. === "Using environment variables" ```go - config := pve.Config{ + creds, err := api.NewTokenCreds("root@pam", "TOKEN_NAME", "UUID_TOKEN") + if err != nil { + // handle error + panic(err) + } + + config := api.Config{ Host: "pve.example.com", Port: 8006, InsecureSkipVerify: true, + Credentials: creds, } - api, err := pve.New() + c, err := api.New(config) ``` -=== "Using the built-in credentials constructors" +=== "Using token credentials" ```go - creds, err := pve.NewEnvCreds() - // Or: - // creds := pve.NewTokenCreds("root@pam", "TOKEN_NAME", "UUID_TOKEN") + creds := api.NewTokenCreds("root@pam", "TOKEN_NAME", "UUID_TOKEN") - if err != nil { - // handle the error as you please - } - - config := pve.Config{ + config := api.Config{ Host: "pve.example.com", Port: 8006, InsecureSkipVerify: true, + Credentials: creds, } - api, err := pve.NewWithCredentials(config, creds) + c, err := api.New(config) ``` -### Connecting to a proxmox instance secured by Cloudflare -If you are exposing your pve instance through proxmox zero trust, you need to setup an [application] within cloudflare and generate a [service token] for that application. +### Connecting to a proxmox instance using custom headers +If for some reason you need to add custom headers to your requests in order to reach the proxmox instance, you can do so by passing a map of custom headers to the [api.Config] struct. -Once you have your service token id and secret, add them your `pve.Config`: ```go -import "github.com/iolave/go-proxmox/pkg/cloudflare" +creds := api.NewTokenCreds("root@pam", "TOKEN_NAME", "UUID_TOKEN") -//... - -config := pve.Config{ +config := api.Config{ Host: "pve.example.com", - Port: 8006, - InsecureSkipVerify: true, - CfServiceToken: cloudflare.NewServiceToken("token-id.access", "token-secret"), + Port: 8006, + InsecureSkipVerify: true, + Credentials: creds, + CustomHeaders: http.Header{ + "x-api-key": []string{"my-api-key"}, + }, } + +c, err := api.New(config) ``` +## Using the go-proxmox services +The go-proxmox services are available under the `pkg/pve` package. Next, there's an example of how to use the core service. + +```go +package main + +import ( + "fmt" + "github.com/iolave/go-proxmox/pkg/api" + "github.com/iolave/go-proxmox/pkg/pve/core" +) + +func main() { + creds := api.NewTokenCreds("root@pam", "TOKEN_NAME", "UUID_TOKEN") + + config := api.Config{ + Host: "pve.example.com", + Port: 8006, + InsecureSkipVerify: true, + Credentials: creds, + CustomHeaders: http.Header{ + "x-api-key": []string{"my-api-key"}, + }, + } + + c, err := api.New(config) + + cs := core.New(c) + + // Get the version of the proxmox instance + res, err := cs.GetVersion() + if err != nil { + // handle error + panic(err) + } + + fmt.Println(res) +} +``` -[pve package]: https://go-proxmox.iolave.com/go-client/pkg/pve/ -[credentials]: https://go-proxmox.iolave.com/go-client/pkg/pve/#type-credentials -[service token]: https://developers.cloudflare.com/cloudflare-one/identity/service-tokens +[api package]: https://go-proxmox.iolave.com/go-client/pkg/api/ +[credentials]: https://go-proxmox.iolave.com/go-client/pkg/api/#type-credentials [application]: https://developers.cloudflare.com/cloudflare-one/applications/ diff --git a/mkdocs.yml b/mkdocs.yml index a5ea08d..5bfbffb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -71,9 +71,10 @@ nav: - Go client: - go-client/index.md - Reference: - - pkg/api: go-client/pkg/api.md - - pkg/pve: go-client/pkg/pve.md - - pkg/helpers: go-client/pkg/helpers.md + - pkg: + - api: go-client/pkg/api.md + - pve: go-client/pkg/pve.md + - helpers: go-client/pkg/helpers.md - Implemented endpoints: go-client/implemented-endpoints.md - API Wrapper: - api-wrapper/index.md diff --git a/pkg/api/api.go b/pkg/api/api.go index f9a7401..f6eaeef 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -18,13 +18,17 @@ import ( // API is an http client used to send requests // to the proxmox api. type API struct { + // cfg is the proxmox api configuration. + cfg Config + // httpc is the underlying http client used // to send requests to the proxmox api. httpc *http.Client +} +type Config struct { // CustomHeaders is a map of custom headers - // to be sent with each request, authorization - // should be added to this map. + // to be sent with each request. CustomHeaders http.Header // Proto is the protocol used to send requests @@ -38,43 +42,41 @@ type API struct { // Port is the port used to send requests // to the proxmox api. Port int `validate:"required"` + + // Credentials is the proxmox api credentials. + Credentials *Credentials `validate:"required"` + + // InsecureSkipVerify is a flag that indicates + // whether the client should skip verifying the + // server's certificate chain and host name. + // It is used to disable SSL certificate verification. + InsecureSkipVerify bool } -// New returns a new HTTPClient. +// New returns a new Proxmox API client. It returns an +// error when the config is invalid. // -// proto is the protocol used to send requests -// and it's allowed values are http or https. -// -// It returns an error when the proto is not -// supported or when the host or port is not -// set/valid. -// -// Any error returned is of type [errors].Error. -// -// It also initializes custom httpin directives. +// - Any error returned is of type [errors].Error. +// - It also initializes custom httpin directives. // // [errors]: https://pkg.go.dev/github.com/iolave/go-errors -func New( - proto string, - host string, - port int, - insecureSkipVerify bool, -) (*API, error) { +func New(cfg Config) (*API, error) { httpinInit() transport := &http.Transport{ TLSClientConfig: &tls.Config{ - InsecureSkipVerify: insecureSkipVerify, + InsecureSkipVerify: cfg.InsecureSkipVerify, }, } httpc := &http.Client{Transport: transport} + if cfg.CustomHeaders == nil { + cfg.CustomHeaders = http.Header{} + } + c := &API{ - httpc: httpc, - CustomHeaders: http.Header{}, - Proto: proto, - Host: host, - Port: port, + httpc: httpc, + cfg: cfg, } validate := validator.New( @@ -121,14 +123,14 @@ type PVERequest struct { Result any } -// sendPVERequest sends a request to the proxmox api. It returns +// SendPVERequest sends a request to the proxmox api. It returns // an error when the request fails. // // Any error returned is of type [errors].*HTTPError. // // [errors]: https://pkg.go.dev/github.com/iolave/go-errors func (c API) SendPVERequest(pvereq PVERequest) error { - base := fmt.Sprintf("%s://%s:%d", c.Proto, c.Host, c.Port) + base := fmt.Sprintf("%s://%s:%d", c.cfg.Proto, c.cfg.Host, c.cfg.Port) url, err := url.JoinPath(base, pvereq.Path) if err != nil { return errors.NewInternalServerError( @@ -153,10 +155,19 @@ func (c API) SendPVERequest(pvereq PVERequest) error { } // Add the custom headers to the request - for k, v := range c.CustomHeaders { + for k, v := range c.cfg.CustomHeaders { req.Header[k] = v } + auth, err := c.cfg.Credentials.getAuthorization() + if err != nil { + return errors.NewInternalServerError( + "failed to get authorization header", + err, + ) + } + req.Header.Set("Authorization", auth) + // If the request has additional payload, // a clone of the request is created in // order to parse the form data and populate diff --git a/pkg/api/api_credentials.go b/pkg/api/api_credentials.go new file mode 100644 index 0000000..10675d3 --- /dev/null +++ b/pkg/api/api_credentials.go @@ -0,0 +1,92 @@ +package api + +import ( + "errors" + "fmt" + "os" +) + +// Proxmox api client available credential types +type CredentialType int + +// TODO: Add CREDENTIALS_PASSWORD support +const ( + CREDENTIALS_TOKEN CredentialType = iota + CREDENTIALS_PASSWORD +) + +// Credentials error messages. +const ( + CREDENTIALS_NOT_DETECTED_ERROR = "credentials could not be detected from env" + CREDENTIALS_NOT_SUPPORTED_ERROR = "credentials type not supported yet" + CREDENTIALS_MISSING_REQUEST_ERROR = "*http.Request parameter is nil" +) + +// Credentials store proxmox api credentials. +type Credentials struct { + credType CredentialType + username string + tokenName string + token string +} + +// getAuthorization builds the authorization header based on the +// credentials type. +// +// It returns an error if the credentials type is not supported. +func (c Credentials) getAuthorization() (string, error) { + switch c.credType { + case CREDENTIALS_TOKEN: + auth := fmt.Sprintf("PVEAPIToken=%s!%s=%s", + c.username, + c.tokenName, + c.token, + ) + return auth, nil + default: + return "", errors.New(CREDENTIALS_NOT_SUPPORTED_ERROR) + } +} + +// NewTokenCreds returns a struct containing proxmox token +// credentials that can be passed to the [New] function. +// +// To create a pve token, read the [docs]. +// +// [docs]: https://pve.proxmox.com/wiki/Proxmox_VE_API#API_Tokens +func NewTokenCreds(user, tokenName, token string) *Credentials { + return &Credentials{ + credType: CREDENTIALS_TOKEN, + username: user, + tokenName: tokenName, + token: token, + } +} + +// NewEnvCreds get [environment variables] values and detects +// the type of credentials based on which envs are configured. +// +// It returns an error when a credential type is not detected. +// +// [environment variables]: https://go-proxmox.iolave.com/getting-started/#enviroment-variables +func NewEnvCreds() (*Credentials, error) { + username := os.Getenv("PROXMOX_USERNAME") + password := os.Getenv("PROXMOX_PASSWORD") + tokenName := os.Getenv("PROXMOX_TOKEN_NAME") + token := os.Getenv("PROXMOX_TOKEN") + + // If no username is found + if username == "" { + return nil, errors.New(CREDENTIALS_NOT_DETECTED_ERROR) + } + + if tokenName != "" && token != "" { + return NewTokenCreds(username, tokenName, token), nil + } + + if password != "" { + return nil, errors.New(CREDENTIALS_NOT_SUPPORTED_ERROR) + } + + return nil, errors.New(CREDENTIALS_NOT_DETECTED_ERROR) +}