package kvm import ( "encoding/xml" "fmt" "time" "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider" ) // metadataURI namespaces the operator's element inside the domain's // . libvirt has no labels or tags, but it stores any namespaced XML // there and hands it back through DomainGetMetadata, which is how the GC // contract (managed flag + owning UID) and the creation time survive across // operator restarts. const metadataURI = "https://gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/kvm" type instanceMetadata struct { XMLName xml.Name `xml:"https://gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/kvm instance"` Managed string `xml:"managed"` UID string `xml:"uid"` CreatedAt string `xml:"createdAt"` } func newInstanceMetadata(uid string, createdAt time.Time) instanceMetadata { return instanceMetadata{ Managed: provider.LabelManagedYes, UID: uid, CreatedAt: createdAt.UTC().Format(time.RFC3339), } } // parseInstanceMetadata decodes the element as DomainGetMetadata returns it. // libvirt selects the element by namespace URI but serialises it without the // declaration (observed on libvirt 11.6: a bare ), and other // versions may keep a default or prefixed form. The URI is already checked // by the lookup, so only the local name is matched here. func parseInstanceMetadata(s string) (instanceMetadata, error) { var raw struct { XMLName xml.Name `xml:"instance"` instanceMetadata } if err := xml.Unmarshal([]byte(s), &raw); err != nil { return instanceMetadata{}, fmt.Errorf("parsing domain metadata: %w", err) } return raw.instanceMetadata, nil } func (m instanceMetadata) managed() bool { return m.Managed == provider.LabelManagedYes } // createdAt returns the zero time on a malformed timestamp: orphan GC treats // zero as "old", so a corrupt value can never protect a domain forever. func (m instanceMetadata) createdAt() time.Time { t, err := time.Parse(time.RFC3339, m.CreatedAt) if err != nil { return time.Time{} } return t }