Andrey Smirnov acf1ac0f1a
feat: show human-readable aliases in talosctl get rd
Sample:

```
ID                                                ALIASES
addressspecs.net.talos.dev                        addressspec as
addressstatuses.net.talos.dev                     address addresses addressstatus as
affiliates.cluster.talos.dev                      affiliate
apicertificates.secrets.talos.dev                 apicertificate ac acs
certsans.secrets.talos.dev                        certsan csan csans
cpustats.perf.talos.dev                           cpustat cpus
discoveryconfigs.cluster.talos.dev                discoveryconfig dc dcs
endpoints.kubernetes.talos.dev                    endpoint
etcdrootsecrets.secrets.talos.dev                 etcdrootsecret ers
etcdsecrets.secrets.talos.dev                     etcdsecret es
etcfilespecs.files.talos.dev                      etcfilespec efs
etcfilestatuses.files.talos.dev                   etcfilestatus efs
hardwareaddresses.net.talos.dev                   hardwareaddress ha has
hostnamespecs.net.talos.dev                       hostnamespec hs
hostnamestatuses.net.talos.dev                    hostname hostnamestatus hs
identities.cluster.talos.dev                      identity
kernelparamdefaultspecs.runtime.talos.dev         kernelparamdefaultspec kpds
kernelparamspecs.runtime.talos.dev                kernelparamspec kps
kernelparamstatuses.runtime.talos.dev             sysctls kernelparameters kernelparams kernelparamstatus kps
kubeletconfigs.kubernetes.talos.dev               kubeletconfig kc kcs
kubeletsecrets.secrets.talos.dev                  kubeletsecret ks
kubeletspecs.kubernetes.talos.dev                 kubeletspec ks
kubernetescontrolplaneconfigs.config.talos.dev    kubernetescontrolplaneconfig kcpc kcpcs
kubernetesrootsecrets.secrets.talos.dev           kubernetesrootsecret krs
kubernetessecrets.secrets.talos.dev               kubernetessecret ks
kubespanconfigs.kubespan.talos.dev                kubespanconfig ksc kscs
kubespanendpoints.kubespan.talos.dev              kubespanendpoint kse kses
kubespanidentities.kubespan.talos.dev             kubespanidentity ksi ksis
kubespanpeerspecs.kubespan.talos.dev              kubespanpeerspec ksps
kubespanpeerstatuses.kubespan.talos.dev           kubespanpeerstatus ksps
linkrefreshes.net.talos.dev                       linkrefresh lr lrs
linkspecs.net.talos.dev                           linkspec ls
linkstatuses.net.talos.dev                        link links linkstatus ls
machineconfigs.config.talos.dev                   machineconfig mc mcs
machinetypes.config.talos.dev                     machinetype mt mts
manifests.kubernetes.talos.dev                    manifest
manifeststatuses.kubernetes.talos.dev             manifeststatus ms
members.cluster.talos.dev                         member
memorystats.perf.talos.dev                        memorystat ms
mountstatuses.runtime.talos.dev                   mounts mountstatus ms
namespaces.meta.cosi.dev                          ns namespace
networkstatuses.net.talos.dev                     netstatus netstatuses networkstatus ns
nodeaddresses.net.talos.dev                       nodeaddress na nas
nodeaddressfilters.net.talos.dev                  nodeaddressfilter naf nafs
nodeipconfigs.kubernetes.talos.dev                nodeipconfig nipc nipcs
nodeips.kubernetes.talos.dev                      nodeip nip nips
nodenames.kubernetes.talos.dev                    nodename
operatorspecs.net.talos.dev                       operatorspec os
osrootsecrets.secrets.talos.dev                   osrootsecret osrs
resolverspecs.net.talos.dev                       resolverspec rs
resolverstatuses.net.talos.dev                    resolvers resolverstatus rs
resourcedefinitions.meta.cosi.dev                 resourcedefinition rd rds
routespecs.net.talos.dev                          routespec rs
routestatuses.net.talos.dev                       route routes routestatus rs
secretstatuses.kubernetes.talos.dev               secretstatus ss
services.v1alpha1.talos.dev                       svc service
staticpods.kubernetes.talos.dev                   staticpod sp sps
staticpodstatuses.kubernetes.talos.dev            podstatus staticpodstatus sps
timeserverspecs.net.talos.dev                     timeserverspec tss
timeserverstatuses.net.talos.dev                  timeserver timeservers timeserverstatus tss
timestatuses.v1alpha1.talos.dev                   timestatus ts
```

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
2021-12-17 20:37:37 +03:00

333 lines
7.9 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package resources implements resources API server.
package resources
import (
"context"
"fmt"
"strings"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/resource/meta"
"github.com/cosi-project/runtime/pkg/state"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
yaml "gopkg.in/yaml.v3"
"github.com/talos-systems/talos/pkg/grpc/middleware/authz"
resourceapi "github.com/talos-systems/talos/pkg/machinery/api/resource"
"github.com/talos-systems/talos/pkg/machinery/role"
)
// Server implements ResourceService API.
type Server struct {
resourceapi.UnimplementedResourceServiceServer
Resources state.State
}
func marshalResource(r resource.Resource) (*resourceapi.Resource, error) {
md := &resourceapi.Metadata{
Namespace: r.Metadata().Namespace(),
Type: r.Metadata().Type(),
Id: r.Metadata().ID(),
Version: r.Metadata().Version().String(),
Phase: r.Metadata().Phase().String(),
Owner: r.Metadata().Owner(),
Created: timestamppb.New(r.Metadata().Created()),
Updated: timestamppb.New(r.Metadata().Updated()),
}
for _, fin := range *r.Metadata().Finalizers() {
md.Finalizers = append(md.Finalizers, fin)
}
spec := &resourceapi.Spec{}
if !resource.IsTombstone(r) && r.Spec() != nil {
var err error
spec.Yaml, err = yaml.Marshal(r.Spec())
if err != nil {
return nil, err
}
}
return &resourceapi.Resource{
Metadata: md,
Spec: spec,
}, nil
}
type resourceKind struct {
Namespace resource.Namespace
Type resource.Type
}
//nolint:gocyclo
func (s *Server) resolveResourceKind(ctx context.Context, kind *resourceKind) (*meta.ResourceDefinition, error) {
registeredResources, err := s.Resources.List(ctx, resource.NewMetadata(meta.NamespaceName, meta.ResourceDefinitionType, "", resource.VersionUndefined))
if err != nil {
return nil, err
}
matched := []*meta.ResourceDefinition{}
for _, item := range registeredResources.Items {
rd, ok := item.(*meta.ResourceDefinition)
if !ok {
return nil, fmt.Errorf("unexpected resource definition type")
}
if strings.EqualFold(rd.Metadata().ID(), kind.Type) {
matched = append(matched, rd)
continue
}
spec := rd.Spec().(meta.ResourceDefinitionSpec) //nolint:errcheck,forcetypeassert
for _, alias := range spec.AllAliases {
if strings.EqualFold(alias, kind.Type) {
matched = append(matched, rd)
break
}
}
}
switch {
case len(matched) == 1:
kind.Type = matched[0].Spec().(meta.ResourceDefinitionSpec).Type
if kind.Namespace == "" {
kind.Namespace = matched[0].Spec().(meta.ResourceDefinitionSpec).DefaultNamespace
}
return matched[0], nil
case len(matched) > 1:
matchedTypes := make([]string, len(matched))
for i := range matched {
matchedTypes[i] = matched[i].Metadata().ID()
}
return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("resource type %q is ambiguous: %v", kind.Type, matchedTypes))
default:
return nil, status.Error(codes.NotFound, fmt.Sprintf("resource %q is not registered", kind.Type))
}
}
func (s *Server) checkReadAccess(ctx context.Context, kind *resourceKind, rd *meta.ResourceDefinition) error {
roles := authz.GetRoles(ctx)
spec := rd.Spec().(meta.ResourceDefinitionSpec) //nolint:errcheck,forcetypeassert
switch spec.Sensitivity {
case meta.Sensitive:
if !roles.Includes(role.Admin) {
return authz.ErrNotAuthorized
}
case meta.NonSensitive:
// nothing
default:
return fmt.Errorf("unexpected sensitivity %q", spec.Sensitivity)
}
registeredNamespaces, err := s.Resources.List(ctx, resource.NewMetadata(meta.NamespaceName, meta.NamespaceType, "", resource.VersionUndefined))
if err != nil {
return err
}
for _, ns := range registeredNamespaces.Items {
if ns.Metadata().ID() == kind.Namespace {
return nil
}
}
return status.Error(codes.NotFound, fmt.Sprintf("namespace %q is not registered", kind.Namespace))
}
// Get implements resource.ResourceServiceServer interface.
func (s *Server) Get(ctx context.Context, in *resourceapi.GetRequest) (*resourceapi.GetResponse, error) {
kind := &resourceKind{
Namespace: in.GetNamespace(),
Type: in.GetType(),
}
rd, err := s.resolveResourceKind(ctx, kind)
if err != nil {
return nil, err
}
if err = s.checkReadAccess(ctx, kind, rd); err != nil {
return nil, err
}
r, err := s.Resources.Get(ctx, resource.NewMetadata(kind.Namespace, kind.Type, in.GetId(), resource.VersionUndefined))
if err != nil {
if state.IsNotFoundError(err) {
return nil, status.Error(codes.NotFound, err.Error())
}
return nil, err
}
protoD, err := marshalResource(rd)
if err != nil {
return nil, err
}
protoR, err := marshalResource(r)
if err != nil {
return nil, err
}
return &resourceapi.GetResponse{
Messages: []*resourceapi.Get{
{
Definition: protoD,
Resource: protoR,
},
},
}, nil
}
// List implements resource.ResourceServiceServer interface.
func (s *Server) List(in *resourceapi.ListRequest, srv resourceapi.ResourceService_ListServer) error {
kind := &resourceKind{
Namespace: in.GetNamespace(),
Type: in.GetType(),
}
rd, err := s.resolveResourceKind(srv.Context(), kind)
if err != nil {
return err
}
if err = s.checkReadAccess(srv.Context(), kind, rd); err != nil {
return err
}
list, err := s.Resources.List(srv.Context(), resource.NewMetadata(kind.Namespace, kind.Type, "", resource.VersionUndefined))
if err != nil {
return err
}
protoD, err := marshalResource(rd)
if err != nil {
return err
}
if err = srv.Send(&resourceapi.ListResponse{
Definition: protoD,
}); err != nil {
return err
}
for _, r := range list.Items {
protoR, err := marshalResource(r)
if err != nil {
return err
}
if err = srv.Send(&resourceapi.ListResponse{
Resource: protoR,
}); err != nil {
return err
}
}
return nil
}
// Watch implements resource.ResourceServiceServer interface.
//
//nolint:gocyclo
func (s *Server) Watch(in *resourceapi.WatchRequest, srv resourceapi.ResourceService_WatchServer) error {
kind := &resourceKind{
Namespace: in.GetNamespace(),
Type: in.GetType(),
}
rd, err := s.resolveResourceKind(srv.Context(), kind)
if err != nil {
return err
}
if err = s.checkReadAccess(srv.Context(), kind, rd); err != nil {
return err
}
protoD, err := marshalResource(rd)
if err != nil {
return err
}
if err = srv.Send(&resourceapi.WatchResponse{
Definition: protoD,
}); err != nil {
return err
}
ctx, cancel := context.WithCancel(srv.Context())
defer cancel()
eventCh := make(chan state.Event)
md := resource.NewMetadata(kind.Namespace, kind.Type, in.GetId(), resource.VersionUndefined)
if in.GetId() == "" {
opts := []state.WatchKindOption{}
if in.TailEvents > 0 {
opts = append(opts, state.WithKindTailEvents(int(in.TailEvents)))
} else {
opts = append(opts, state.WithBootstrapContents(true))
}
err = s.Resources.WatchKind(ctx, md, eventCh, opts...)
} else {
opts := []state.WatchOption{}
if in.TailEvents > 0 {
opts = append(opts, state.WithTailEvents(int(in.TailEvents)))
}
err = s.Resources.Watch(ctx, md, eventCh, opts...)
}
if err != nil {
return fmt.Errorf("error setting up watch: %w", err)
}
for event := range eventCh {
protoR, err := marshalResource(event.Resource)
if err != nil {
return err
}
resp := &resourceapi.WatchResponse{
Resource: protoR,
}
switch event.Type {
case state.Created:
resp.EventType = resourceapi.EventType_CREATED
case state.Updated:
resp.EventType = resourceapi.EventType_UPDATED
case state.Destroyed:
resp.EventType = resourceapi.EventType_DESTROYED
}
if err = srv.Send(resp); err != nil {
return err
}
}
return nil
}