mirror of
https://github.com/siderolabs/omni.git
synced 2025-08-09 11:06:59 +02:00
Fixes: https://github.com/siderolabs/omni/issues/858 Signed-off-by: Artem Chernyshev <artem.chernyshev@talos-systems.com>
152 lines
3.9 KiB
Go
152 lines
3.9 KiB
Go
// Copyright (c) 2025 Sidero Labs, Inc.
|
|
//
|
|
// Use of this software is governed by the Business Source License
|
|
// included in the LICENSE file.
|
|
|
|
package runtime
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cosi-project/runtime/pkg/resource"
|
|
"github.com/siderolabs/talos/pkg/machinery/api/common"
|
|
"google.golang.org/protobuf/proto"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type protobufWrapper interface {
|
|
GetValue() proto.Message
|
|
}
|
|
|
|
// Version implements a special uint64 type which can be unmarshalled from COSI version that can be string if it's undefined.
|
|
type Version uint64
|
|
|
|
// UnmarshalYAML implements yaml.Unmarshaler.
|
|
func (v *Version) UnmarshalYAML(value *yaml.Node) error {
|
|
if value.Value == resource.VersionUndefined.String() {
|
|
return nil
|
|
}
|
|
|
|
val, err := strconv.ParseUint(value.Value, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*v = Version(val)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Metadata wraps COSI or Talos metadata into a json serializable object.
|
|
type Metadata struct {
|
|
Created time.Time `json:"created" yaml:"created"`
|
|
Updated time.Time `json:"updated" yaml:"updated"`
|
|
Namespace resource.Namespace `json:"namespace" yaml:"namespace"`
|
|
Type resource.Type `json:"type" yaml:"type"`
|
|
ID resource.ID `json:"id" yaml:"id"`
|
|
Owner resource.Owner `json:"owner" yaml:"owner"`
|
|
Phase string `json:"phase" yaml:"phase"`
|
|
Node string `json:"node,omitempty" yaml:"node"`
|
|
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
|
|
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
|
|
Finalizers resource.Finalizers `json:"finalizers,omitempty" yaml:"finalizers,omitempty"`
|
|
Version Version `json:"version" yaml:"version,omitempty"`
|
|
}
|
|
|
|
// Resource wraps Talos and COSI resource response to be encoded as JSON.
|
|
type Resource struct {
|
|
Spec any `yaml:"spec" json:"spec"`
|
|
Resource resource.Resource `yaml:"-" json:"-"`
|
|
ID string `yaml:"-" json:"-"`
|
|
Metadata Metadata `yaml:"metadata" json:"metadata"`
|
|
}
|
|
|
|
// ResourceOptions describes additional resource wrapper options.
|
|
type ResourceOptions struct {
|
|
metadata *common.Metadata
|
|
}
|
|
|
|
// ResourceOption is a single resource creation option.
|
|
type ResourceOption func(*ResourceOptions)
|
|
|
|
// WithMetadata creates resource with Talos metadata.
|
|
func WithMetadata(value *common.Metadata) ResourceOption {
|
|
return func(o *ResourceOptions) {
|
|
o.metadata = value
|
|
}
|
|
}
|
|
|
|
// NewResource creates new resource.
|
|
func NewResource(r resource.Resource, options ...ResourceOption) (*Resource, error) {
|
|
opts := &ResourceOptions{}
|
|
for _, o := range options {
|
|
o(opts)
|
|
}
|
|
|
|
s, err := resource.MarshalYAML(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data, err := yaml.Marshal(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
parts := make([]string, 0, 3)
|
|
if opts.metadata != nil {
|
|
parts = append(parts, opts.metadata.Hostname)
|
|
}
|
|
|
|
parts = append(parts, r.Metadata().Namespace(), r.Metadata().ID())
|
|
|
|
res := &Resource{
|
|
ID: strings.Join(parts, "/"),
|
|
}
|
|
if err = yaml.Unmarshal(data, &res); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if opts.metadata != nil {
|
|
res.Metadata.Node = opts.metadata.Hostname
|
|
}
|
|
|
|
if wrapped, ok := r.Spec().(protobufWrapper); ok {
|
|
res.Spec = wrapped.GetValue()
|
|
}
|
|
|
|
res.Resource = r
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// protoMessage wraps proto.Message to leverage protojson encoder.
|
|
type protoMessage struct {
|
|
Message proto.Message
|
|
}
|
|
|
|
func (r protoMessage) MarshalJSON() ([]byte, error) {
|
|
data, err := MarshalJSON(r.Message)
|
|
|
|
return []byte(data), err
|
|
}
|
|
|
|
// MarshalJSON overrides default marshal behavior replacing spec
|
|
// with the wrapped struct, that uses protojson encoder.
|
|
func (r *Resource) MarshalJSON() ([]byte, error) {
|
|
m, ok := r.Spec.(proto.Message)
|
|
if !ok {
|
|
return json.Marshal(*r)
|
|
}
|
|
|
|
return json.Marshal(Resource{
|
|
Spec: protoMessage{Message: m},
|
|
Metadata: r.Metadata,
|
|
Resource: r.Resource,
|
|
ID: r.ID,
|
|
})
|
|
}
|