Files
egress-proxies-operator/internal/provider/proxmox/seed.go
T
kacerrandClaude Opus 5.5 34f132d421 proxmox: Create by cloning a template, seed upload, config, start
Create validates placement, then under the per-name lock adopts the VM a
previous attempt left (by name, or at a taken VMID after the ownership
check of spec entry 2) or full-clones the template onto the UID's
deterministic VMID with the mark in the clone call. It then uploads the
seed over any existing file, writes cores, memory, agent, tag, ide2 and
net0 when configured, grows the boot disk when diskGiB is larger, starts,
and waits for running/running.

waitUnlocked no longer waits on a suspended lock: a hibernated VM is
judged at once, so ours is Permanent and a foreign one is probed past.
The template is checked only on the allocation path, and its shape
(net0, boot disk size) is refused before any clone exists. Spec entries
2, 4 and 10 updated to match.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 15:32:31 +00:00

66 lines
2.4 KiB
Go

package proxmox
import (
"bytes"
"context"
"errors"
"fmt"
"github.com/go-logr/logr"
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider"
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider/nocloud"
)
// seedISOName is the seed's file name on the ISO storage. Upload refuses a
// name without the .iso suffix (F12), and GC's blind spot is documented by
// this pattern (spec entry 11).
func seedISOName(name string) string {
return name + "-seed.iso"
}
// seedVolID is the seed's volume id, as ide2 references it and as the
// storage content delete takes it.
func seedVolID(storage, name string) string {
return storage + ":iso/" + seedISOName(name)
}
// buildSeed renders the NoCloud seed for req in memory (spec entry 5): a
// seed is tens of KiB. Nothing about its content is logged or put in an
// error. Only an empty name is Permanent; a staging failure (no /tmp, a
// full emptyDir) is left unclassified, so classify makes it Transient and
// the reconciler retries instead of latching the Proxy.
func (p *Provider) buildSeed(req provider.CreateRequest) (*bytes.Buffer, error) {
vendorData, err := nocloud.BuildVendorData(p.cfg.VendorData, *p.cfg.InstallGuestAgent)
if err != nil {
return nil, fmt.Errorf("building vendor-data for %s: %w", req.Name, err)
}
var iso bytes.Buffer
if err := nocloud.BuildSeedISO(req.Name, req.CloudInit, vendorData, &iso); err != nil {
if errors.Is(err, nocloud.ErrEmptyName) {
return nil, permanentf("building seed ISO: %v", err)
}
return nil, fmt.Errorf("building seed ISO for %s: %w", req.Name, err)
}
return &iso, nil
}
// uploadSeed puts a freshly built seed on the VM's node and waits for the
// copy (spec entry 5). An upload overwrites a file of the same name (F12),
// so what a cut-off attempt left is replaced without a delete first.
func (p *Provider) uploadSeed(ctx context.Context, log logr.Logger, node string, req provider.CreateRequest) error {
iso, err := p.buildSeed(req)
if err != nil {
return err
}
volid := seedVolID(p.cfg.ISOStorage, req.Name)
upid, err := p.api.UploadISO(ctx, node, p.cfg.ISOStorage, seedISOName(req.Name), iso, int64(iso.Len()))
if err != nil {
return fmt.Errorf("uploading seed %s: %w", volid, err)
}
if err := p.waitTask(ctx, log, upid, p.budgets.upload); err != nil {
return fmt.Errorf("uploading seed %s: %w", volid, err)
}
return nil
}