Repository navigation
Expand file tree
/
Copy pathproto.go
More file actions
103 lines (87 loc) · 2.34 KB
/
Copy pathproto.go
File metadata and controls
103 lines (87 loc) · 2.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package profiles
import (
"fmt"
"io"
"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, w io.Writer) error {
input, err := readFile(fileName)
if err != nil {
return err
}
parser := proto.NewParser(input)
profile, err := protoToServiceProfile(parser, namespace, name, clusterDomain)
if err != nil {
return err
}
return writeProfile(*profile, w)
}
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
}