linkerd2/controller/api/util/api_utils.go

208 lines
5.2 KiB
Go

package util
import (
"errors"
"strings"
"time"
pb "github.com/runconduit/conduit/controller/gen/public"
"github.com/runconduit/conduit/pkg/k8s"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
/*
Shared utilities for interacting with the controller public api
*/
var (
defaultMetricTimeWindow = "1m"
// ValidTargets specifies resource types allowed as a target:
// target resource on an inbound query
// target resource on an outbound 'to' query
// destination resource on an outbound 'from' query
ValidTargets = []string{
k8s.Deployments,
k8s.Namespaces,
k8s.Pods,
k8s.ReplicationControllers,
}
// ValidDestinations specifies resource types allowed as a destination:
// destination resource on an outbound 'to' query
// target resource on an outbound 'from' query
ValidDestinations = []string{
k8s.Deployments,
k8s.Namespaces,
k8s.Pods,
k8s.ReplicationControllers,
k8s.Services,
}
)
type StatSummaryRequestParams struct {
TimeWindow string
Namespace string
ResourceType string
ResourceName string
ToNamespace string
ToType string
ToName string
FromNamespace string
FromType string
FromName string
}
// GRPCError generates a gRPC error code, as defined in
// google.golang.org/grpc/status.
// If the error is nil or already a gRPC error, return the error.
// If the error is of type k8s.io/apimachinery/pkg/apis/meta/v1#StatusReason,
// attempt to map the reason to a gRPC error.
func GRPCError(err error) error {
if err != nil && status.Code(err) == codes.Unknown {
code := codes.Internal
switch k8sErrors.ReasonForError(err) {
case metav1.StatusReasonUnknown:
code = codes.Unknown
case metav1.StatusReasonUnauthorized, metav1.StatusReasonForbidden:
code = codes.PermissionDenied
case metav1.StatusReasonNotFound:
code = codes.NotFound
case metav1.StatusReasonAlreadyExists:
code = codes.AlreadyExists
case metav1.StatusReasonInvalid:
code = codes.InvalidArgument
case metav1.StatusReasonExpired:
code = codes.DeadlineExceeded
case metav1.StatusReasonServiceUnavailable:
code = codes.Unavailable
}
err = status.Error(code, err.Error())
}
return err
}
func BuildStatSummaryRequest(p StatSummaryRequestParams) (*pb.StatSummaryRequest, error) {
window := defaultMetricTimeWindow
if p.TimeWindow != "" {
_, err := time.ParseDuration(p.TimeWindow)
if err != nil {
return nil, err
}
window = p.TimeWindow
}
resourceType, err := k8s.CanonicalKubernetesNameFromFriendlyName(p.ResourceType)
if err != nil {
return nil, err
}
statRequest := &pb.StatSummaryRequest{
Selector: &pb.ResourceSelection{
Resource: &pb.Resource{
Namespace: p.Namespace,
Name: p.ResourceName,
Type: resourceType,
},
},
TimeWindow: window,
}
if p.ToName != "" || p.ToType != "" || p.ToNamespace != "" {
if p.ToNamespace == "" {
p.ToNamespace = p.Namespace
}
if p.ToType == "" {
p.ToType = resourceType
}
toType, err := k8s.CanonicalKubernetesNameFromFriendlyName(p.ToType)
if err != nil {
return nil, err
}
toResource := pb.StatSummaryRequest_ToResource{
ToResource: &pb.Resource{
Namespace: p.ToNamespace,
Type: toType,
Name: p.ToName,
},
}
statRequest.Outbound = &toResource
}
if p.FromName != "" || p.FromType != "" || p.FromNamespace != "" {
if p.FromNamespace == "" {
p.FromNamespace = p.Namespace
}
if p.FromType == "" {
p.FromType = resourceType
}
fromType, err := k8s.CanonicalKubernetesNameFromFriendlyName(p.FromType)
if err != nil {
return nil, err
}
fromResource := pb.StatSummaryRequest_FromResource{
FromResource: &pb.Resource{
Namespace: p.FromNamespace,
Type: fromType,
Name: p.FromName,
},
}
statRequest.Outbound = &fromResource
}
return statRequest, nil
}
// BuildResource parses input strings, typically from CLI flags, to build a
// Resource object for use in the Conduit Public API.
func BuildResource(namespace string, args ...string) (pb.Resource, error) {
switch len(args) {
case 0:
return pb.Resource{}, errors.New("No resource arguments provided")
case 1:
elems := strings.Split(args[0], "/")
switch len(elems) {
case 1:
// --namespace my-ns deploy
return buildResource(namespace, elems[0], "")
case 2:
// --namespace my-ns deploy/foo
return buildResource(namespace, elems[0], elems[1])
default:
return pb.Resource{}, errors.New("Invalid resource string: " + args[0])
}
case 2:
// --namespace my-ns deploy foo
return buildResource(namespace, args[0], args[1])
default:
return pb.Resource{}, errors.New("Too many arguments provided for resource: " + strings.Join(args, "/"))
}
}
func buildResource(namespace string, resType string, name string) (pb.Resource, error) {
canonicalType, err := k8s.CanonicalKubernetesNameFromFriendlyName(resType)
if err != nil {
return pb.Resource{}, err
}
if canonicalType == k8s.Namespaces {
// ignore --namespace flags if type is namespace
namespace = ""
}
return pb.Resource{
Namespace: namespace,
Type: canonicalType,
Name: name,
}, nil
}