Files
egress-proxies-operator/internal/provider/kvm/metadata.go
T
kacerrandClaude Fable 5.1 162607dfe2 Add KVM provider pure builders: domain XML, volumes, seed ISO, metadata
Introduces internal/provider/kvm with the side-effect-free pieces of the
provider: providerID format/parse, the namespaced <metadata> element that
carries the GC contract, q35 domain XML over pool volumes, volume XML for
overlays and raw seeds, the NoCloud seed ISO built with kdomanski/iso9660,
and config defaults. All unit-tested.

Step 2 of docs/plans/2026-09-04-1404-kvm-provider.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 19:13:51 +00:00

58 lines
1.9 KiB
Go

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
// <metadata>. 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 may re-serialise it with a namespace prefix rather than the default
// namespace it was defined with; encoding/xml matches on namespace URI, so
// both spellings decode.
func parseInstanceMetadata(s string) (instanceMetadata, error) {
var m instanceMetadata
if err := xml.Unmarshal([]byte(s), &m); err != nil {
return instanceMetadata{}, fmt.Errorf("parsing domain metadata: %w", err)
}
return m, 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
}