package proxmox import ( "context" "strings" "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider" ) // proxyTag is the tag Create sets for the Proxmox UI; ListByTag uses it, // with the name prefix provider.NameFromUID gives, only to pick candidates. // The mark decides. const ( proxyTag = "proxy_operator" proxyNamePrefix = "proxy-" ) // ListByTag is spec entry 12: one resources call covers every node, then // one config read per candidate for the mark. GC reads only ID, UID and // CreatedAt (contract ยง2.4), so no status or agent call is made. The // resources call failing fails the list; one unreadable candidate is // skipped, unless ctx ended, which fails the list rather than pass off a // partial view as complete. func (p *Provider) ListByTag(ctx context.Context) ([]provider.Instance, error) { log := p.logger(ctx) rs, err := p.api.ClusterResources(ctx) if err != nil { return nil, p.wrapErr("list", "", err) } var ( out []provider.Instance candidates, skipped int ) for _, r := range rs { if !isCandidate(r) { continue } candidates++ vlog := log.WithValues("vmid", r.VMID, "name", r.Name, "node", r.Node) cfg, err := p.api.VMConfig(ctx, r.Node, r.VMID) if err != nil { if ctx.Err() != nil { return nil, p.wrapErr("list", "", err) } skipped++ vlog.V(1).Info("skipping unreadable proxmox vm", "error", err.Error()) continue } mark := parseDescription(cfg["description"]) if !mark.Managed { continue } inst := provider.Instance{ID: formatProviderID(r.VMID, r.Name), UID: mark.UID, CreatedAt: mark.CreatedAt} vlog.V(2).Info("proxmox listed instance", "id", inst.ID, "uid", inst.UID, "createdAt", inst.CreatedAt) out = append(out, inst) } log.V(1).Info("proxmox vms listed", "vms", len(rs), "candidates", candidates, "managed", len(out), "skipped", skipped) return out, nil } // isCandidate picks the resource entries worth a config read: QEMU VMs, not // templates, named, and either tagged or named like a proxy. A nameless // entry is a clone in progress, whose Create is still running. func isCandidate(r vmResource) bool { if r.Type != "qemu" || r.Template != 0 || r.Name == "" { return false } return strings.HasPrefix(r.Name, proxyNamePrefix) || hasTag(r.Tags, proxyTag) } // hasTag reports whether tags, as Proxmox lists them (";"-separated, though // "," and spaces are accepted on input), contains tag. Proxmox folds tags to // lower case. func hasTag(tags, tag string) bool { for _, t := range strings.FieldsFunc(tags, func(r rune) bool { return r == ';' || r == ',' || r == ' ' }) { if strings.EqualFold(t, tag) { return true } } return false }