diff --git a/README.md b/README.md index c0eaf5a..6f9619f 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,30 @@ proxmox_host: "192.168.1.100" proxmox_user: "root@pam" ``` +### Batch mode (multiple templates): + +Buat file batch (contoh: `batch.txt`) dengan list config files: +``` +# Ubuntu templates +configs/ubuntu-22.04.yaml +configs/ubuntu-20.04.yaml + +# Debian templates +configs/debian-12.yaml +configs/debian-11.yaml +``` + +Jalankan batch: +```bash +proxmox-cloud-image -batch batch.txt +``` + +**Features:** +- Process multiple config files sekaligus +- Max 3 concurrent jobs (parallel) +- Auto-skip lines yang kosong atau comment (#) +- Summary report di akhir + ## Cloud Image URLs ### Ubuntu diff --git a/batch.example.txt b/batch.example.txt new file mode 100644 index 0000000..a250b18 --- /dev/null +++ b/batch.example.txt @@ -0,0 +1,13 @@ +# Batch file example - one config file per line +# Lines starting with # are comments + +# Ubuntu templates +configs/ubuntu-22.04.yaml +configs/ubuntu-20.04.yaml + +# Debian templates +configs/debian-12.yaml +configs/debian-11.yaml + +# CentOS templates +configs/centos-stream-9.yaml diff --git a/batch.go b/batch.go new file mode 100644 index 0000000..dc4e49f --- /dev/null +++ b/batch.go @@ -0,0 +1,85 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + "sync" +) + +func runBatch(batchFile string) error { + file, err := os.Open(batchFile) + if err != nil { + return fmt.Errorf("failed to open batch file: %w", err) + } + defer file.Close() + + var configFiles []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + configFiles = append(configFiles, line) + } + + if err := scanner.Err(); err != nil { + return fmt.Errorf("failed to read batch file: %w", err) + } + + if len(configFiles) == 0 { + return fmt.Errorf("no config files found in batch file") + } + + fmt.Printf("Found %d config files to process\n\n", len(configFiles)) + + var wg sync.WaitGroup + errors := make(chan error, len(configFiles)) + semaphore := make(chan struct{}, 3) // Max 3 concurrent jobs + + for i, configFile := range configFiles { + wg.Add(1) + go func(index int, cfgFile string) { + defer wg.Done() + semaphore <- struct{}{} + defer func() { <-semaphore }() + + fmt.Printf("[%d/%d] Processing: %s\n", index+1, len(configFiles), cfgFile) + + config := &Config{} + if err := loadConfigFile(cfgFile, config); err != nil { + errors <- fmt.Errorf("[%s] failed to load config: %w", cfgFile, err) + return + } + + if err := run(config); err != nil { + errors <- fmt.Errorf("[%s] failed to create template: %w", cfgFile, err) + return + } + + fmt.Printf("[%d/%d] ✓ Completed: %s\n\n", index+1, len(configFiles), cfgFile) + }(i, configFile) + } + + wg.Wait() + close(errors) + + var batchErrors []error + for err := range errors { + batchErrors = append(batchErrors, err) + } + + if len(batchErrors) > 0 { + fmt.Fprintf(os.Stderr, "\n%d template(s) failed:\n", len(batchErrors)) + for _, err := range batchErrors { + fmt.Fprintf(os.Stderr, " - %v\n", err) + } + return fmt.Errorf("%d template(s) failed", len(batchErrors)) + } + + return nil +} + +// ... existing code ... diff --git a/configs/debian-12.yaml b/configs/debian-12.yaml new file mode 100644 index 0000000..bba1bba --- /dev/null +++ b/configs/debian-12.yaml @@ -0,0 +1,12 @@ +image_url: "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2" +vm_name: "debian-12-template" +vm_id: 0 +storage: "local-lvm" +memory: 2048 +cores: 2 +disk_size: "20G" +bridge: "vmbr0" +vlan_tag: 0 +ssh_key: "/root/.ssh/id_rsa.pub" +proxmox_host: "192.168.1.100" +proxmox_user: "root@pam" diff --git a/configs/ubuntu-20.04.yaml b/configs/ubuntu-20.04.yaml new file mode 100644 index 0000000..98f2e04 --- /dev/null +++ b/configs/ubuntu-20.04.yaml @@ -0,0 +1,12 @@ +image_url: "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img" +vm_name: "ubuntu-20.04-template" +vm_id: 0 +storage: "local-lvm" +memory: 2048 +cores: 2 +disk_size: "20G" +bridge: "vmbr0" +vlan_tag: 0 +ssh_key: "/root/.ssh/id_rsa.pub" +proxmox_host: "192.168.1.100" +proxmox_user: "root@pam" diff --git a/configs/ubuntu-22.04.yaml b/configs/ubuntu-22.04.yaml new file mode 100644 index 0000000..e61fd20 --- /dev/null +++ b/configs/ubuntu-22.04.yaml @@ -0,0 +1,12 @@ +image_url: "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img" +vm_name: "ubuntu-22.04-template" +vm_id: 0 +storage: "local-lvm" +memory: 2048 +cores: 2 +disk_size: "20G" +bridge: "vmbr0" +vlan_tag: 0 +ssh_key: "/root/.ssh/id_rsa.pub" +proxmox_host: "192.168.1.100" +proxmox_user: "root@pam" diff --git a/main.go b/main.go index e349af8..96f9e33 100644 --- a/main.go +++ b/main.go @@ -25,8 +25,10 @@ type Config struct { func main() { config := &Config{} var configFile string + var batchFile string flag.StringVar(&configFile, "config", "", "Config file path (YAML)") + flag.StringVar(&batchFile, "batch", "", "Batch file with multiple config paths (one per line)") flag.StringVar(&config.ImageURL, "image-url", "", "Cloud image URL to download") flag.StringVar(&config.VMName, "vm-name", "cloud-vm", "VM name") flag.IntVar(&config.VMID, "vm-id", 0, "VM ID (0 = auto-find from 10000+)") @@ -43,6 +45,17 @@ func main() { flag.Parse() + // Batch mode + if batchFile != "" { + if err := runBatch(batchFile); err != nil { + fmt.Fprintf(os.Stderr, "Batch error: %v\n", err) + os.Exit(1) + } + fmt.Println("\nAll templates created successfully!") + return + } + + // Single config mode if configFile != "" { if err := loadConfigFile(configFile, config); err != nil { fmt.Fprintf(os.Stderr, "Error loading config file: %v\n", err)