kvm2: domain and volume XML, metadata element and NoCloud seed builders
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
package kvm2
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider"
|
||||
)
|
||||
|
||||
// The GC mark lives in the domain's <metadata> as one element in kvm2's own
|
||||
// namespace (spec entry 8). The namespace is what selects it in
|
||||
// DomainGetMetadata; libvirt 11.6 hands the element back without the xmlns
|
||||
// declaration, so parsing matches on the local name only.
|
||||
const (
|
||||
metadataURI = "https://gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/kvm2"
|
||||
metadataLocalName = "instance"
|
||||
)
|
||||
|
||||
// instanceMetadata is the mark as it is written and read back. The fields
|
||||
// are the element's text as is; managed and createdAt interpret them.
|
||||
type instanceMetadata struct {
|
||||
XMLName xml.Name
|
||||
Managed string `xml:"managed"`
|
||||
UID string `xml:"uid"`
|
||||
CreatedAt string `xml:"createdAt"`
|
||||
}
|
||||
|
||||
func newInstanceMetadata(uid string, createdAt time.Time) instanceMetadata {
|
||||
return instanceMetadata{
|
||||
XMLName: xml.Name{Space: metadataURI, Local: metadataLocalName},
|
||||
Managed: provider.LabelManagedYes,
|
||||
UID: uid,
|
||||
CreatedAt: createdAt.UTC().Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
func (m instanceMetadata) managed() bool {
|
||||
return m.Managed == provider.LabelManagedYes
|
||||
}
|
||||
|
||||
// createdAt returns the zero time when the element carries no parsable
|
||||
// timestamp; GC treats that as old, which the spec accepts.
|
||||
func (m instanceMetadata) createdAt() time.Time {
|
||||
t, err := time.Parse(time.RFC3339, m.CreatedAt)
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// marshalInstanceMetadata renders the namespaced element that goes under
|
||||
// the domain's <metadata>.
|
||||
func marshalInstanceMetadata(m instanceMetadata) (string, error) {
|
||||
m.XMLName = xml.Name{Space: metadataURI, Local: metadataLocalName}
|
||||
out, err := xml.Marshal(m)
|
||||
if err != nil {
|
||||
return "", permanentf("marshalling instance metadata: %v", err)
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
// parseInstanceMetadata reads the element back, with or without its xmlns
|
||||
// declaration. Anything that is not an <instance> element is ErrPermanent:
|
||||
// the domain's metadata is corrupt and no retry fixes it.
|
||||
func parseInstanceMetadata(s string) (instanceMetadata, error) {
|
||||
var m instanceMetadata
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return instanceMetadata{}, permanentf("parsing instance metadata: empty document")
|
||||
}
|
||||
if err := xml.Unmarshal([]byte(s), &m); err != nil {
|
||||
return instanceMetadata{}, permanentf("parsing instance metadata: %v", err)
|
||||
}
|
||||
if m.XMLName.Local != metadataLocalName {
|
||||
return instanceMetadata{}, permanentf("parsing instance metadata: root element is <%s>, want <%s>", m.XMLName.Local, metadataLocalName)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
Reference in New Issue
Block a user