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>
58 lines
1.9 KiB
Go
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
|
|
}
|