/
reflect.go
145 lines (135 loc) · 4.09 KB
/
reflect.go
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Hubble
package reflect
import (
"context"
"encoding/json"
"fmt"
"github.com/cilium/hubble/cmd/common/config"
"github.com/cilium/hubble/cmd/common/conn"
"github.com/cilium/hubble/cmd/common/template"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"google.golang.org/grpc"
rpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/descriptorpb"
)
// New returns the reflect command.
func New(vp *viper.Viper) *cobra.Command {
reflectCmd := &cobra.Command{
Use: "reflect",
Short: "Use gRPC reflection to explore Hubble's API",
RunE: func(cmd *cobra.Command, _ []string) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hubbleConn, err := conn.New(ctx, vp.GetString(config.KeyServer), vp.GetDuration(config.KeyTimeout))
if err != nil {
return err
}
defer hubbleConn.Close()
return runReflect(ctx, cmd, hubbleConn)
},
Hidden: true,
}
// add config.ServerFlags to the help template as these flags are used by
// this command
template.RegisterFlagSets(reflectCmd, config.ServerFlags)
return reflectCmd
}
func runReflect(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) (err error) {
client, err := rpb.NewServerReflectionClient(conn).ServerReflectionInfo(ctx)
if err != nil {
return err
}
req := rpb.ServerReflectionRequest{
MessageRequest: &rpb.ServerReflectionRequest_ListServices{},
}
if err := client.Send(&req); err != nil {
return err
}
res, err := client.Recv()
if err != nil {
return err
}
services, ok := res.GetMessageResponse().(*rpb.ServerReflectionResponse_ListServicesResponse)
if !ok {
return fmt.Errorf("unexpected response: %v", res)
}
// same proto file can be imported multiple times from different places. keep track of proto filenames
// so that we don't print them multiple times.
visited := make(map[string]struct{})
encoder := json.NewEncoder(cmd.OutOrStdout())
for _, svc := range services.ListServicesResponse.GetService() {
if svc.GetName() == "grpc.reflection.v1alpha.ServerReflection" || svc.GetName() == "grpc.health.v1.Health" {
continue
}
err = client.Send(&rpb.ServerReflectionRequest{
MessageRequest: &rpb.ServerReflectionRequest_FileContainingSymbol{
FileContainingSymbol: svc.GetName(),
},
})
if err != nil {
return err
}
res, err := client.Recv()
if err != nil {
return err
}
files, ok := res.GetMessageResponse().(*rpb.ServerReflectionResponse_FileDescriptorResponse)
if !ok {
return fmt.Errorf("unexpected response: %v", res)
}
if err := handleDescriptorResponse(visited, client, files, encoder); err != nil {
return err
}
}
return nil
}
func handleDescriptorResponse(
visited map[string]struct{},
client rpb.ServerReflection_ServerReflectionInfoClient,
resp *rpb.ServerReflectionResponse_FileDescriptorResponse,
encoder *json.Encoder,
) error {
for _, r := range resp.FileDescriptorResponse.GetFileDescriptorProto() {
desc := descriptorpb.FileDescriptorProto{}
if err := proto.Unmarshal(r, &desc); err != nil {
return err
}
if _, ok := visited[desc.GetName()]; !ok {
visited[desc.GetName()] = struct{}{}
if err := encoder.Encode(&desc); err != nil {
return err
}
}
for _, dep := range desc.GetDependency() {
if err := resolveDependency(visited, client, dep, encoder); err != nil {
return err
}
}
}
return nil
}
func resolveDependency(
visited map[string]struct{},
client rpb.ServerReflection_ServerReflectionInfoClient,
filename string,
encoder *json.Encoder,
) error {
req := rpb.ServerReflectionRequest{
MessageRequest: &rpb.ServerReflectionRequest_FileByFilename{FileByFilename: filename},
}
if err := client.Send(&req); err != nil {
return err
}
res, err := client.Recv()
if err != nil {
return err
}
files, ok := res.GetMessageResponse().(*rpb.ServerReflectionResponse_FileDescriptorResponse)
if !ok {
return fmt.Errorf("unexpected response: %v", res.GetMessageResponse())
}
return handleDescriptorResponse(visited, client, files, encoder)
}