package profiles import ( "fmt" "net/http" "regexp" "strings" "github.com/emicklei/proto" sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // RenderProto reads a protobuf definition file and renders the corresponding // ServiceProfile to a buffer, given a namespace, service, and control plane // namespace. func RenderProto(fileName, namespace, name, clusterDomain string) (*sp.ServiceProfile, error) { input, err := readFile(fileName) if err != nil { return nil, err } parser := proto.NewParser(input) return protoToServiceProfile(parser, namespace, name, clusterDomain) } func protoToServiceProfile(parser *proto.Parser, namespace, name, clusterDomain string) (*sp.ServiceProfile, error) { definition, err := parser.Parse() if err != nil { return nil, err } routes := make([]*sp.RouteSpec, 0) pkg := "" handle := func(visitee proto.Visitee) { switch typed := visitee.(type) { case *proto.Package: pkg = typed.Name case *proto.RPC: if service, ok := typed.Parent.(*proto.Service); ok { var path string switch pkg { case "": path = fmt.Sprintf("/%s/%s", service.Name, typed.Name) default: path = fmt.Sprintf("/%s.%s/%s", pkg, service.Name, typed.Name) } route := &sp.RouteSpec{ Name: typed.Name, Condition: &sp.RequestMatch{ Method: http.MethodPost, PathRegex: regexp.QuoteMeta(path), }, IsRetryable: isMethodRetryable(typed), } routes = append(routes, route) } } } proto.Walk(definition, handle) return &sp.ServiceProfile{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("%s.%s.svc.%s", name, namespace, clusterDomain), Namespace: namespace, }, TypeMeta: ServiceProfileMeta, Spec: sp.ServiceProfileSpec{ Routes: routes, }, }, nil } func isMethodRetryable(rpc *proto.RPC) bool { for _, e := range rpc.Elements { option, ok := e.(*proto.Option) if !ok { // Not an option continue } // method options can be wrapped in parentheses so we trim them away if strings.Trim(option.Name, "()") != "idempotency_level" { // Not an idempotency option continue } return option.Constant.Source == "IDEMPOTENT" || option.Constant.Source == "NO_SIDE_EFFECTS" } return false }