mirror of
https://github.com/siderolabs/omni.git
synced 2025-08-06 17:46:59 +02:00
Then another Tool can represent it in a nice way. This will be a good education tool for the start, and then we can add monitoring there too. Signed-off-by: Artem Chernyshev <artem.chernyshev@talos-systems.com>
260 lines
6.0 KiB
Go
260 lines
6.0 KiB
Go
// Copyright (c) 2025 Sidero Labs, Inc.
|
|
//
|
|
// Use of this software is governed by the Business Source License
|
|
// included in the LICENSE file.
|
|
|
|
//go:build sidero.debug
|
|
|
|
package grpc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/cosi-project/runtime/pkg/controller"
|
|
"github.com/cosi-project/runtime/pkg/resource"
|
|
"github.com/cosi-project/runtime/pkg/resource/protobuf"
|
|
"github.com/cosi-project/runtime/pkg/state"
|
|
"github.com/siderolabs/gen/xslices"
|
|
"github.com/siderolabs/talos/pkg/machinery/proto"
|
|
|
|
"github.com/siderolabs/omni/client/api/omni/resources"
|
|
"github.com/siderolabs/omni/internal/pkg/auth"
|
|
"github.com/siderolabs/omni/internal/pkg/auth/actor"
|
|
"github.com/siderolabs/omni/internal/pkg/auth/role"
|
|
)
|
|
|
|
func (s *ResourceServer) Controllers(ctx context.Context, _ *resources.ControllersRequest) (*resources.ControllersResponse, error) {
|
|
_, err := auth.CheckGRPC(ctx, auth.WithRole(role.Admin))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
graph, err := s.runtime.GetDependencyGraph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp := &resources.ControllersResponse{}
|
|
|
|
visited := map[string]struct{}{}
|
|
|
|
for _, edge := range graph.Edges {
|
|
if _, ok := visited[edge.ControllerName]; ok {
|
|
continue
|
|
}
|
|
|
|
resp.Controllers = append(resp.Controllers, edge.ControllerName)
|
|
|
|
visited[edge.ControllerName] = struct{}{}
|
|
}
|
|
|
|
slices.Sort(resp.Controllers)
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (s *ResourceServer) DependencyGraph(ctx context.Context, req *resources.DependencyGraphRequest) (*resources.DependencyGraphResponse, error) {
|
|
_, err := auth.CheckGRPC(ctx, auth.WithRole(role.Admin))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
graph, err := s.runtime.GetDependencyGraph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filterControllers := xslices.ToSet(req.Controllers)
|
|
|
|
res := &resources.DependencyGraphResponse{}
|
|
|
|
nodesMap := map[string]string{}
|
|
|
|
genNodeID := func(label string) {
|
|
id := fmt.Sprintf("n_%s", label)
|
|
|
|
nodesMap[label] = id
|
|
}
|
|
|
|
addNode := func(edge controller.DependencyEdge, t resources.DependencyGraphResponse_Node_Type) error {
|
|
var label string
|
|
|
|
switch t {
|
|
case resources.DependencyGraphResponse_Node_RESOURCE:
|
|
label = edge.ResourceType
|
|
case resources.DependencyGraphResponse_Node_CONTROLLER:
|
|
label = edge.ControllerName
|
|
case resources.DependencyGraphResponse_Node_UNKNOWN:
|
|
return errors.New("unknown node type")
|
|
}
|
|
|
|
id, ok := nodesMap[label]
|
|
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
labels []string
|
|
fields []string
|
|
)
|
|
|
|
if t == resources.DependencyGraphResponse_Node_RESOURCE {
|
|
ctx := actor.MarkContextAsInternalActor(ctx)
|
|
|
|
var resources []resource.Resource
|
|
|
|
if edge.ResourceID != "" {
|
|
res, err := s.state.Get(ctx, resource.NewMetadata(edge.ResourceNamespace, edge.ResourceType, edge.ResourceID, resource.VersionUndefined))
|
|
if err != nil && !state.IsNotFoundError(err) {
|
|
return err
|
|
}
|
|
|
|
if err == nil {
|
|
resources = append(resources, res)
|
|
}
|
|
} else {
|
|
response, err := s.state.List(ctx, resource.NewMetadata(edge.ResourceNamespace, edge.ResourceType, "", resource.VersionUndefined))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resources = response.Items
|
|
}
|
|
|
|
labelsMap := map[string]struct{}{}
|
|
|
|
for _, res := range resources {
|
|
for _, key := range res.Metadata().Labels().Keys() {
|
|
labelsMap[key] = struct{}{}
|
|
}
|
|
}
|
|
|
|
labels = slices.Sorted(maps.Keys(labelsMap))
|
|
|
|
res, err := protobuf.CreateResource(edge.ResourceType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if reflect.ValueOf(res.Spec()).Kind() == reflect.Ptr {
|
|
err = json.Unmarshal([]byte("{}"), res.Spec())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
type protobufWrapper interface {
|
|
GetValue() proto.Message
|
|
}
|
|
|
|
if message, ok := res.Spec().(protobufWrapper); ok {
|
|
v := reflect.TypeOf(message.GetValue()).Elem()
|
|
|
|
for i := range v.NumField() {
|
|
field := v.Field(i)
|
|
|
|
f := field.Tag.Get("json")
|
|
if f == "" {
|
|
continue
|
|
}
|
|
|
|
fields = append(fields, strings.Split(f, ",")[0])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
res.Nodes = append(res.Nodes, &resources.DependencyGraphResponse_Node{
|
|
Id: id,
|
|
Label: label,
|
|
Type: t,
|
|
Labels: labels,
|
|
Fields: fields,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
addEdge := func(edge controller.DependencyEdge) error {
|
|
var (
|
|
source string
|
|
target string
|
|
edgeType controller.DependencyEdgeType
|
|
)
|
|
|
|
switch edge.EdgeType {
|
|
case controller.EdgeOutputExclusive:
|
|
source = nodesMap[edge.ControllerName]
|
|
target = nodesMap[edge.ResourceType]
|
|
edgeType = edge.EdgeType
|
|
case controller.EdgeOutputShared:
|
|
source = nodesMap[edge.ControllerName]
|
|
target = nodesMap[edge.ResourceType]
|
|
edgeType = edge.EdgeType
|
|
case controller.EdgeInputStrong, controller.EdgeInputQPrimary:
|
|
source = nodesMap[edge.ResourceType]
|
|
target = nodesMap[edge.ControllerName]
|
|
edgeType = edge.EdgeType
|
|
case controller.EdgeInputWeak, controller.EdgeInputQMapped:
|
|
source = nodesMap[edge.ResourceType]
|
|
target = nodesMap[edge.ControllerName]
|
|
edgeType = edge.EdgeType
|
|
case controller.EdgeInputDestroyReady, controller.EdgeInputQMappedDestroyReady:
|
|
if req.ShowDestroyReady {
|
|
source = nodesMap[edge.ResourceType]
|
|
target = nodesMap[edge.ControllerName]
|
|
edgeType = edge.EdgeType
|
|
}
|
|
}
|
|
|
|
if source == "" || target == "" {
|
|
return nil
|
|
}
|
|
|
|
res.Edges = append(res.Edges,
|
|
&resources.DependencyGraphResponse_Edge{
|
|
Id: fmt.Sprintf("e_%s->%s", source, target),
|
|
Source: source,
|
|
Target: target,
|
|
EdgeType: int32(edgeType),
|
|
},
|
|
)
|
|
|
|
if err = addNode(edge, resources.DependencyGraphResponse_Node_CONTROLLER); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = addNode(edge, resources.DependencyGraphResponse_Node_RESOURCE); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
for _, edge := range graph.Edges {
|
|
if len(filterControllers) != 0 {
|
|
if _, ok := filterControllers[edge.ControllerName]; !ok {
|
|
continue
|
|
}
|
|
}
|
|
|
|
genNodeID(edge.ControllerName)
|
|
genNodeID(edge.ResourceType)
|
|
}
|
|
|
|
for _, edge := range graph.Edges {
|
|
if err = addEdge(edge); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|