Add option to list storage

This commit is contained in:
2025-11-15 10:47:22 +07:00
parent b9ab1caf02
commit 36fa9644b4
5 changed files with 87 additions and 7 deletions

View File

@@ -160,18 +160,20 @@ proxmox-cloud-image -batch batch.txt
| `-image-url` | - | URL cloud image (required) | | `-image-url` | - | URL cloud image (required) |
| `-vm-name` | cloud-vm | Nama template | | `-vm-name` | cloud-vm | Nama template |
| `-vm-id` | 0 | Template ID (0 = auto-find dari 10000+) | | `-vm-id` | 0 | Template ID (0 = auto-find dari 10000+) |
| `-storage` | local-lvm | Nama storage Proxmox | | `-storage` | auto-detect | Nama storage Proxmox (auto-detect jika kosong) |
| `-memory` | 2048 | Memory dalam MB | | `-memory` | 2048 | Memory dalam MB |
| `-cores` | 2 | Jumlah CPU cores | | `-cores` | 2 | Jumlah CPU cores |
| `-disk-size` | 20G | Ukuran disk | | `-disk-size` | 20G | Ukuran disk |
| `-bridge` | vmbr0 | Network bridge | | `-bridge` | vmbr0 | Network bridge |
| `-vlan-tag` | 0 | VLAN tag (0 = no VLAN) | | `-vlan-tag` | 0 | VLAN tag (0 = no VLAN) |
| `-guest-agent` | false | Enable QEMU guest agent |
| `-firewall` | false | Enable firewall |
| `-ssh-key` | - | Path ke SSH public key | | `-ssh-key` | - | Path ke SSH public key |
| `-proxmox-host` | - | IP/hostname Proxmox (required) | | `-proxmox-host` | - | IP/hostname Proxmox (required) |
| `-proxmox-user` | root@pam | Proxmox user | | `-proxmox-user` | root@pam | Proxmox user |
| `-proxmox-pass` | - | Proxmox password | | `-proxmox-pass` | - | Proxmox password |
| `-guest-agent` | false | Enable QEMU Guest Agent | | `-list-storage` | - | List semua storage yang tersedia |
| `-firewall` | false | Enable Proxmox firewall | | `-ls` | - | Shorthand untuk `-list-storage` |
## How It Works ## How It Works

2
go.mod
View File

@@ -2,4 +2,4 @@ module github.com/othman/proxmox-cloud-image
go 1.22.2 go 1.22.2
require gopkg.in/yaml.v3 v3.0.1 // indirect require gopkg.in/yaml.v3 v3.0.1

1
go.sum
View File

@@ -1,3 +1,4 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

22
main.go
View File

@@ -28,13 +28,16 @@ func main() {
config := &Config{} config := &Config{}
var configFile string var configFile string
var batchFile string var batchFile string
var listStorage bool
flag.StringVar(&configFile, "config", "", "Config file path (YAML)") flag.StringVar(&configFile, "config", "", "Config file path (YAML)")
flag.StringVar(&batchFile, "batch", "", "Batch file with multiple config paths (one per line)") flag.StringVar(&batchFile, "batch", "", "Batch file with multiple config paths (one per line)")
flag.BoolVar(&listStorage, "list-storage", false, "List available storage on Proxmox host")
flag.BoolVar(&listStorage, "ls", false, "List available storage on Proxmox host (shorthand)")
flag.StringVar(&config.ImageURL, "image-url", "", "Cloud image URL to download") flag.StringVar(&config.ImageURL, "image-url", "", "Cloud image URL to download")
flag.StringVar(&config.VMName, "vm-name", "cloud-vm", "VM name") flag.StringVar(&config.VMName, "vm-name", "cloud-vm", "VM name")
flag.IntVar(&config.VMID, "vm-id", 0, "VM ID (0 = auto-find from 10000+)") flag.IntVar(&config.VMID, "vm-id", 0, "VM ID (0 = auto-find from 10000+)")
flag.StringVar(&config.Storage, "storage", "local-lvm", "Proxmox storage name") flag.StringVar(&config.Storage, "storage", "", "Proxmox storage name (auto-detect if empty)")
flag.IntVar(&config.Memory, "memory", 2048, "Memory in MB") flag.IntVar(&config.Memory, "memory", 2048, "Memory in MB")
flag.IntVar(&config.Cores, "cores", 2, "CPU cores") flag.IntVar(&config.Cores, "cores", 2, "CPU cores")
flag.StringVar(&config.DiskSize, "disk-size", "20G", "Disk size (e.g., 20G)") flag.StringVar(&config.DiskSize, "disk-size", "20G", "Disk size (e.g., 20G)")
@@ -44,9 +47,24 @@ func main() {
flag.StringVar(&config.ProxmoxHost, "proxmox-host", "", "Proxmox host (e.g., 192.168.1.100)") flag.StringVar(&config.ProxmoxHost, "proxmox-host", "", "Proxmox host (e.g., 192.168.1.100)")
flag.StringVar(&config.ProxmoxUser, "proxmox-user", "root@pam", "Proxmox user") flag.StringVar(&config.ProxmoxUser, "proxmox-user", "root@pam", "Proxmox user")
flag.StringVar(&config.ProxmoxPass, "proxmox-pass", "", "Proxmox password") flag.StringVar(&config.ProxmoxPass, "proxmox-pass", "", "Proxmox password")
flag.BoolVar(&config.GuestAgent, "guest-agent", false, "Enable QEMU guest agent")
flag.BoolVar(&config.Firewall, "firewall", false, "Enable firewall")
flag.Parse() flag.Parse()
// Handle storage listing
if listStorage || flag.Lookup("ls").Value.(flag.Getter).Get().(bool) {
if config.ProxmoxHost == "" {
fmt.Println("Error: -proxmox-host is required for storage listing")
os.Exit(1)
}
if err := listAvailableStorage(config); err != nil {
fmt.Fprintf(os.Stderr, "Error listing storage: %v\n", err)
os.Exit(1)
}
return
}
// Batch mode // Batch mode
if batchFile != "" { if batchFile != "" {
if err := runBatch(batchFile); err != nil { if err := runBatch(batchFile); err != nil {
@@ -56,8 +74,6 @@ func main() {
fmt.Println("\nAll templates created successfully!") fmt.Println("\nAll templates created successfully!")
return return
} }
// Single config mode
if configFile != "" { if configFile != "" {
if err := loadConfigFile(configFile, config); err != nil { if err := loadConfigFile(configFile, config); err != nil {
fmt.Fprintf(os.Stderr, "Error loading config file: %v\n", err) fmt.Fprintf(os.Stderr, "Error loading config file: %v\n", err)

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
@@ -236,3 +237,63 @@ func createProxmoxVM(config *Config) error {
return nil return nil
} }
func listAvailableStorage(config *Config) error {
fmt.Printf("Detecting available storage on %s...\n", config.ProxmoxHost)
sshCmd := func(args ...string) *exec.Cmd {
fullArgs := []string{
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
fmt.Sprintf("%s@%s", strings.Split(config.ProxmoxUser, "@")[0], config.ProxmoxHost),
}
fullArgs = append(fullArgs, args...)
return exec.Command("ssh", fullArgs...)
}
cmd := sshCmd("pvesm", "status", "--output-format", "json")
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to get storage status: %w", err)
}
// Parse JSON output
var storageList []map[string]interface{}
if err := json.Unmarshal(stdout.Bytes(), &storageList); err != nil {
return fmt.Errorf("failed to parse storage status: %w", err)
}
fmt.Println("\nAvailable storage:")
fmt.Println("================")
found := false
for _, storage := range storageList {
nameVal, ok := storage["storage"]
if !ok {
continue
}
name, _ := nameVal.(string)
typeVal, _ := storage["type"]
typeStr, _ := typeVal.(string)
activeVal, _ := storage["active"]
activeFloat, _ := activeVal.(float64)
if activeFloat == 1 {
fmt.Printf("- %s (%s) - ACTIVE\n", name, typeStr)
found = true
}
}
if !found {
fmt.Println("No active storage found!")
fmt.Println("\nManual check:")
fmt.Println(" ssh root@your-proxmox 'pvesm status'")
}
return nil
}