mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-26 07:58:14 +00:00
fix(ci): use Terraform provider for ISO upload, fix port/lockfile issues
- Use proxmox_virtual_environment_file resource to upload the auto-install ISO via the API. terraform destroy now cleans up both the VM and ISO. - Remove manual upload-to-pve.sh and cleanup-pve-storage.sh scripts. - Commit .terraform.lock.hcl for reproducible CI builds. - Replace iso_file_id variable with iso_local_path and iso_storage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,23 +85,6 @@ jobs:
|
||||
${SCRIPTS_DIR}/first-boot.sh \
|
||||
"/tmp/${{ steps.iso.outputs.filename }}"
|
||||
|
||||
- name: Upload ISO to PVE storage
|
||||
if: inputs.skip_provision != true
|
||||
id: upload
|
||||
shell: bash
|
||||
env:
|
||||
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
|
||||
run: |
|
||||
bash ${SCRIPTS_DIR}/upload-to-pve.sh \
|
||||
"$(echo '${{ secrets.PVE_ENDPOINT }}' | sed -E 's|https?://||;s|:.*||')" \
|
||||
"${PVE_API_TOKEN}" \
|
||||
"${{ secrets.PVE_TARGET_NODE }}" \
|
||||
"local" \
|
||||
"/tmp/${{ steps.iso.outputs.filename }}" \
|
||||
"iso" | tee /tmp/upload-output.txt
|
||||
ISO_VOLID=$(tail -1 /tmp/upload-output.txt)
|
||||
echo "iso_volid=${ISO_VOLID}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Terraform init
|
||||
if: inputs.skip_provision != true
|
||||
shell: bash
|
||||
@@ -117,7 +100,7 @@ jobs:
|
||||
TF_VAR_proxmox_endpoint: ${{ secrets.PVE_ENDPOINT }}
|
||||
TF_VAR_proxmox_api_token: ${{ secrets.PVE_API_TOKEN }}
|
||||
TF_VAR_target_node: ${{ secrets.PVE_TARGET_NODE }}
|
||||
TF_VAR_iso_file_id: ${{ steps.upload.outputs.iso_volid }}
|
||||
TF_VAR_iso_local_path: /tmp/${{ steps.iso.outputs.filename }}
|
||||
TF_VAR_test_vm_password: ${{ env.PVE_PASSWORD }}
|
||||
run: |
|
||||
terraform apply -auto-approve -input=false
|
||||
@@ -236,18 +219,6 @@ jobs:
|
||||
TF_VAR_proxmox_endpoint: ${{ secrets.PVE_ENDPOINT }}
|
||||
TF_VAR_proxmox_api_token: ${{ secrets.PVE_API_TOKEN }}
|
||||
TF_VAR_target_node: ${{ secrets.PVE_TARGET_NODE }}
|
||||
TF_VAR_iso_file_id: ${{ steps.upload.outputs.iso_volid }}
|
||||
TF_VAR_iso_local_path: /tmp/${{ steps.iso.outputs.filename }}
|
||||
TF_VAR_test_vm_password: ${{ env.PVE_PASSWORD }}
|
||||
run: terraform destroy -auto-approve -input=false
|
||||
|
||||
- name: Clean up uploaded ISO
|
||||
if: always() && inputs.skip_provision != true
|
||||
shell: bash
|
||||
env:
|
||||
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
|
||||
run: |
|
||||
PARENT_HOST=$(echo '${{ secrets.PVE_ENDPOINT }}' | sed -E 's|https?://||;s|:.*||')
|
||||
bash ${SCRIPTS_DIR}/cleanup-pve-storage.sh \
|
||||
"${PARENT_HOST}" \
|
||||
"${PVE_API_TOKEN}" \
|
||||
"${{ steps.upload.outputs.iso_volid }}" || true
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
.terraform/
|
||||
*.tfvars
|
||||
!terraform.tfvars.example
|
||||
.terraform.lock.hcl
|
||||
# Keep .terraform.lock.hcl tracked for reproducible CI builds
|
||||
.api-token
|
||||
.build/
|
||||
|
||||
Generated
+63
@@ -0,0 +1,63 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/bpg/proxmox" {
|
||||
version = "0.98.1"
|
||||
constraints = ">= 0.70.0"
|
||||
hashes = [
|
||||
"h1:oegYetLP9VqFSnRfHeUQnm6v58dH/VT4ivJSXg4RLnI=",
|
||||
"zh:165705d0a0db92ccc5b1d23b0033cc87011f1151f4f178ad9df61473c3ca30b5",
|
||||
"zh:3ecab62be80d89cf8f019ab23809254f854eb5a443682a1a22a52e0d80b1273f",
|
||||
"zh:48b520a3f5b8cccca66e0dc7b1e264e01d2f35ef8a9bbfc6b02964cd7235914b",
|
||||
"zh:7fa7e166b924b5192ff963f47a56772c7b34f0004d825d830c3122eab970534f",
|
||||
"zh:804bdf5353d3a3ad9d06e6faada1b8600af8ecfd1e5b23280a81e62b0cb7037d",
|
||||
"zh:87ebe8b56a895870ebc0d353fba5ebbb4615401dc3f53a2e157f158cb9025d2d",
|
||||
"zh:9b0c8e02c036152bcb2f54c354da4647d10d016396e5ca2b70084a8bd5546970",
|
||||
"zh:a0039465282dc75ac9c53750b61ba4038d56bed545cb39df669b6216bdff82da",
|
||||
"zh:bbc2c187c8283b6a5f44cbd65f4b6e5d534a3be1eeb1a7c8caaaa6f1a7e48e76",
|
||||
"zh:d04aed773582e95c8d7610833e85b204fe8c01c09c94e7128914c8e22b9218c7",
|
||||
"zh:d7a1433a7d6f1b158f499a93d3fb1825eec73eac4c61b00a39f2f5e95ae0965a",
|
||||
"zh:df46cb59fa1ddda335934720ad22d8205bb8902ce780ca32205f1622f1fa1649",
|
||||
"zh:e100ad439313db667a5c4ca53ae4c54d8b1c72299a2240a1493823cb89cb0781",
|
||||
"zh:f26e0763dbe6a6b2195c94b44696f2110f7f55433dc142839be16b9697fa5597",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/local" {
|
||||
version = "2.7.0"
|
||||
constraints = ">= 2.0.0"
|
||||
hashes = [
|
||||
"h1:sSwlfp2etjCaE9hIF7bJBDjRIhDCVFglEOVyiCI7vgs=",
|
||||
"zh:261fec71bca13e0a7812dc0d8ae9af2b4326b24d9b2e9beab3d2400fab5c5f9a",
|
||||
"zh:308da3b5376a9ede815042deec5af1050ec96a5a5410a2206ae847d82070a23e",
|
||||
"zh:3d056924c420464dc8aba10e1915956b2e5c4d55b11ffff79aa8be563fbfe298",
|
||||
"zh:643256547b155459c45e0a3e8aab0570db59923c68daf2086be63c444c8c445b",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:7aa4d0b853f84205e8cf79f30c9b2c562afbfa63592f7231b6637e5d7a6b5b27",
|
||||
"zh:7dc251bbc487d58a6ab7f5b07ec9edc630edb45d89b761dba28e0e2ba6b1c11f",
|
||||
"zh:7ee0ca546cd065030039168d780a15cbbf1765a4c70cd56d394734ab112c93da",
|
||||
"zh:b1d5d80abb1906e6c6b3685a52a0192b4ca6525fe090881c64ec6f67794b1300",
|
||||
"zh:d81ea9856d61db3148a4fc6c375bf387a721d78fc1fea7a8823a027272a47a78",
|
||||
"zh:df0a1f0afc947b8bfc88617c1ad07a689ce3bd1a29fd97318392e6bdd32b230b",
|
||||
"zh:dfbcad800240e0c68c43e0866f2a751cff09777375ec701918881acf67a268da",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/null" {
|
||||
version = "3.2.4"
|
||||
hashes = [
|
||||
"h1:L5V05xwp/Gto1leRryuesxjMfgZwjb7oool4WS1UEFQ=",
|
||||
"zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43",
|
||||
"zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a",
|
||||
"zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991",
|
||||
"zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f",
|
||||
"zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e",
|
||||
"zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615",
|
||||
"zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442",
|
||||
"zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5",
|
||||
"zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f",
|
||||
"zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f",
|
||||
]
|
||||
}
|
||||
@@ -14,6 +14,16 @@ provider "proxmox" {
|
||||
insecure = var.proxmox_insecure
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_file" "auto_iso" {
|
||||
content_type = "iso"
|
||||
datastore_id = var.iso_storage
|
||||
node_name = var.target_node
|
||||
|
||||
source_file {
|
||||
path = var.iso_local_path
|
||||
}
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_vm" "nested_pve" {
|
||||
name = var.vm_name
|
||||
node_name = var.target_node
|
||||
@@ -45,7 +55,7 @@ resource "proxmox_virtual_environment_vm" "nested_pve" {
|
||||
}
|
||||
|
||||
cdrom {
|
||||
file_id = var.iso_file_id
|
||||
file_id = proxmox_virtual_environment_file.auto_iso.id
|
||||
interface = "ide2"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Remove an uploaded ISO from PVE storage.
|
||||
#
|
||||
# Usage: cleanup-pve-storage.sh <pve-host> <api-token> <volume-id>
|
||||
# e.g.: cleanup-pve-storage.sh 172.16.100.150 "root@pam!tok=secret" "local:iso/proxmox-ve_9.1-1-auto.iso"
|
||||
set -euo pipefail
|
||||
|
||||
PVE_HOST="$1"
|
||||
API_TOKEN="$2"
|
||||
VOLUME_ID="$3"
|
||||
|
||||
# Extract node name from API
|
||||
NODE=$(curl -sk -H "Authorization: PVEAPIToken=${API_TOKEN}" \
|
||||
"https://${PVE_HOST}/api2/json/nodes" \
|
||||
| python3 -c "import json,sys; print(json.load(sys.stdin)['data'][0]['node'])")
|
||||
|
||||
echo "Deleting ${VOLUME_ID} from node ${NODE}..."
|
||||
ENCODED_VOLID=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${VOLUME_ID}', safe=''))")
|
||||
|
||||
RESPONSE=$(curl -sk -X DELETE \
|
||||
-H "Authorization: PVEAPIToken=${API_TOKEN}" \
|
||||
"https://${PVE_HOST}/api2/json/nodes/${NODE}/storage/local/content/${ENCODED_VOLID}")
|
||||
|
||||
echo "Response: $RESPONSE"
|
||||
echo "Cleanup complete."
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Upload a file to PVE storage via the REST API.
|
||||
#
|
||||
# Usage: upload-to-pve.sh <pve-host> <api-token> <node> <storage> <file-path> <content-type>
|
||||
# content-type: "iso" or "vztmpl"
|
||||
set -euo pipefail
|
||||
|
||||
PVE_HOST="$1"
|
||||
API_TOKEN="$2"
|
||||
NODE="$3"
|
||||
STORAGE="$4"
|
||||
FILE_PATH="$5"
|
||||
CONTENT_TYPE="${6:-iso}"
|
||||
|
||||
FILENAME=$(basename "$FILE_PATH")
|
||||
API_URL="https://${PVE_HOST}/api2/json/nodes/${NODE}/storage/${STORAGE}/upload"
|
||||
|
||||
# Check if file already exists on storage
|
||||
echo "Checking if ${FILENAME} already exists on ${STORAGE}..."
|
||||
EXISTING=$(curl -sk \
|
||||
-H "Authorization: PVEAPIToken=${API_TOKEN}" \
|
||||
"https://${PVE_HOST}/api2/json/nodes/${NODE}/storage/${STORAGE}/content" \
|
||||
| python3 -c "
|
||||
import json, sys
|
||||
data = json.load(sys.stdin).get('data', [])
|
||||
for item in data:
|
||||
if item.get('volid', '').endswith('/${FILENAME}'):
|
||||
print(item['volid'])
|
||||
break
|
||||
" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo "Already exists: $EXISTING (skipping upload)"
|
||||
echo "$EXISTING"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Uploading ${FILENAME} ($(du -h "$FILE_PATH" | cut -f1)) to ${STORAGE}:${CONTENT_TYPE}/..."
|
||||
RESPONSE=$(curl -sk --progress-bar \
|
||||
-H "Authorization: PVEAPIToken=${API_TOKEN}" \
|
||||
-F "content=${CONTENT_TYPE}" \
|
||||
-F "filename=@${FILE_PATH}" \
|
||||
"$API_URL")
|
||||
|
||||
UPID=$(echo "$RESPONSE" | python3 -c "import json,sys; print(json.load(sys.stdin)['data'])" 2>/dev/null || true)
|
||||
if [ -z "$UPID" ]; then
|
||||
echo "ERROR: Upload failed. Response: $RESPONSE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Upload started: $UPID"
|
||||
|
||||
# Wait for upload task to complete
|
||||
echo "Waiting for upload task..."
|
||||
for i in $(seq 1 60); do
|
||||
STATUS=$(curl -sk \
|
||||
-H "Authorization: PVEAPIToken=${API_TOKEN}" \
|
||||
"https://${PVE_HOST}/api2/json/nodes/${NODE}/tasks/${UPID}/status" \
|
||||
| python3 -c "import json,sys; d=json.load(sys.stdin)['data']; print(d.get('status','unknown'))" 2>/dev/null || echo "unknown")
|
||||
if [ "$STATUS" = "stopped" ]; then
|
||||
echo "Upload complete: ${STORAGE}:${CONTENT_TYPE}/${FILENAME}"
|
||||
echo "${STORAGE}:${CONTENT_TYPE}/${FILENAME}"
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "ERROR: Upload task did not complete within timeout" >&2
|
||||
exit 1
|
||||
@@ -3,12 +3,13 @@ proxmox_endpoint = "https://pve.example.com:8006"
|
||||
proxmox_api_token = "root@pam!terraform=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
target_node = "pve"
|
||||
|
||||
# Auto-install ISO (prepared by scripts/prepare-auto-iso.sh, then uploaded)
|
||||
iso_file_id = "local:iso/proxmox-ve_9.1-1-auto.iso"
|
||||
disk_storage = "local-lvm"
|
||||
# Local path to the prepared auto-install ISO (output of prepare-auto-iso.sh)
|
||||
iso_local_path = "/tmp/proxmox-ve_9.1-1-auto.iso"
|
||||
disk_storage = "local-lvm"
|
||||
|
||||
# Optional overrides
|
||||
# cores = 4
|
||||
# memory = 8192
|
||||
# disk_size = 64
|
||||
# network_bridge = "vmbr0"
|
||||
# iso_storage = "local"
|
||||
|
||||
@@ -56,11 +56,17 @@ variable "disk_storage" {
|
||||
default = "nas-iscsi-lvm"
|
||||
}
|
||||
|
||||
variable "iso_file_id" {
|
||||
description = "Proxmox file ID of the auto-install PVE ISO (e.g. local:iso/proxmox-ve_9.1-1-auto.iso)"
|
||||
variable "iso_local_path" {
|
||||
description = "Local path to the prepared auto-install PVE ISO on the runner"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "iso_storage" {
|
||||
description = "Proxmox storage pool for uploading the ISO (must accept ISO content type)"
|
||||
type = string
|
||||
default = "local"
|
||||
}
|
||||
|
||||
variable "network_bridge" {
|
||||
description = "Network bridge on the host to attach the nested PVE VM to"
|
||||
type = string
|
||||
|
||||
Reference in New Issue
Block a user