Skip to content

Commit 73ef3db

Browse files
Add tests for source symbol extraction
Adds tests for go, java, python, rust source symbol extraction. Reference: #29 Signed-off-by: Ayan Sinha Mahapatra <ayansmahapatra@gmail.com>
1 parent b4b54a4 commit 73ef3db

9 files changed

Lines changed: 3289 additions & 0 deletions

File tree

Lines changed: 397 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,397 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"crypto/tls"
6+
"crypto/x509"
7+
"net"
8+
"net/url"
9+
"os"
10+
"time"
11+
12+
contentapi "github.com/containerd/containerd/api/services/content/v1"
13+
"github.com/containerd/containerd/v2/defaults"
14+
controlapi "github.com/moby/buildkit/api/services/control"
15+
"github.com/moby/buildkit/client/connhelper"
16+
"github.com/moby/buildkit/session"
17+
"github.com/moby/buildkit/session/grpchijack"
18+
"github.com/moby/buildkit/util/appdefaults"
19+
"github.com/moby/buildkit/util/grpcerrors"
20+
"github.com/moby/buildkit/util/tracing"
21+
"github.com/moby/buildkit/util/tracing/otlptracegrpc"
22+
"github.com/pkg/errors"
23+
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
24+
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
25+
"go.opentelemetry.io/otel/propagation"
26+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
27+
"go.opentelemetry.io/otel/trace"
28+
"google.golang.org/grpc"
29+
"google.golang.org/grpc/codes"
30+
"google.golang.org/grpc/credentials"
31+
"google.golang.org/grpc/credentials/insecure"
32+
)
33+
34+
type Client struct {
35+
conn *grpc.ClientConn
36+
sessionDialer func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error)
37+
}
38+
39+
type ClientOpt interface {
40+
isClientOpt()
41+
}
42+
43+
// New returns a new buildkit client. Address can be empty for the system-default address.
44+
func New(ctx context.Context, address string, opts ...ClientOpt) (*Client, error) {
45+
gopts := []grpc.DialOption{
46+
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),
47+
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize)),
48+
}
49+
needDialer := true
50+
51+
var customTracer bool // allows manually setting disabling tracing even if tracer in context
52+
var tracerProvider trace.TracerProvider
53+
var tracerDelegate TracerDelegate
54+
var sessionDialer func(context.Context, string, map[string][]string) (net.Conn, error)
55+
var customDialOptions []grpc.DialOption
56+
var creds *withCredentials
57+
58+
for _, o := range opts {
59+
if credInfo, ok := o.(*withCredentials); ok {
60+
if creds == nil {
61+
creds = &withCredentials{}
62+
}
63+
creds = creds.merge(credInfo)
64+
}
65+
if wt, ok := o.(*withTracer); ok {
66+
customTracer = true
67+
tracerProvider = wt.tp
68+
}
69+
if wd, ok := o.(*withDialer); ok {
70+
gopts = append(gopts, grpc.WithContextDialer(wd.dialer))
71+
needDialer = false
72+
}
73+
if wt, ok := o.(*withTracerDelegate); ok {
74+
tracerDelegate = wt
75+
}
76+
if sd, ok := o.(*withSessionDialer); ok {
77+
sessionDialer = sd.dialer
78+
}
79+
if opt, ok := o.(*withGRPCDialOption); ok {
80+
customDialOptions = append(customDialOptions, opt.opt)
81+
}
82+
}
83+
84+
if creds == nil {
85+
gopts = append(gopts, grpc.WithTransportCredentials(insecure.NewCredentials()))
86+
} else {
87+
credOpts, err := loadCredentials(creds)
88+
if err != nil {
89+
return nil, err
90+
}
91+
gopts = append(gopts, credOpts)
92+
}
93+
94+
if !customTracer {
95+
if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() {
96+
tracerProvider = span.TracerProvider()
97+
}
98+
}
99+
100+
if tracerProvider != nil {
101+
gopts = append(gopts, grpc.WithStatsHandler(
102+
tracing.ClientStatsHandler(
103+
otelgrpc.WithTracerProvider(tracerProvider),
104+
otelgrpc.WithPropagators(
105+
propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}),
106+
),
107+
),
108+
))
109+
}
110+
111+
if needDialer {
112+
dialFn, err := resolveDialer(address)
113+
if err != nil {
114+
return nil, err
115+
}
116+
if dialFn != nil {
117+
gopts = append(gopts, grpc.WithContextDialer(dialFn))
118+
}
119+
}
120+
if address == "" {
121+
address = appdefaults.Address
122+
}
123+
uri, err := url.Parse(address)
124+
if err != nil {
125+
return nil, err
126+
}
127+
128+
// Setting :authority pseudo header
129+
// - HTTP/2 (RFC7540) defines :authority pseudo header includes
130+
// the authority portion of target URI but it must not include
131+
// userinfo part (i.e. url.Host).
132+
// ref: https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.3
133+
// - However, when TLS specified, grpc-go requires it must match
134+
// with its servername specified for certificate validation.
135+
var authority string
136+
if creds != nil && creds.serverName != "" {
137+
authority = creds.serverName
138+
}
139+
if authority == "" {
140+
// authority as hostname from target address
141+
authority = uri.Host
142+
}
143+
if uri.Scheme == "tcp" {
144+
// remove tcp scheme from address, since default dialer doesn't expect that
145+
// name resolution is done by grpc according to the following spec: https://github.com/grpc/grpc/blob/master/doc/naming.md
146+
address = uri.Host
147+
}
148+
149+
gopts = append(gopts, grpc.WithAuthority(authority))
150+
gopts = append(gopts, grpc.WithUnaryInterceptor(grpcerrors.UnaryClientInterceptor))
151+
gopts = append(gopts, grpc.WithStreamInterceptor(grpcerrors.StreamClientInterceptor))
152+
gopts = append(gopts, customDialOptions...)
153+
154+
// ignore SA1019 NewClient has different behavior and needs to be tested
155+
//nolint:staticcheck
156+
conn, err := grpc.DialContext(ctx, address, gopts...)
157+
if err != nil {
158+
return nil, errors.Wrapf(err, "failed to dial %q . make sure buildkitd is running", address)
159+
}
160+
161+
c := &Client{
162+
conn: conn,
163+
sessionDialer: sessionDialer,
164+
}
165+
166+
if tracerDelegate != nil {
167+
_ = c.setupDelegatedTracing(ctx, tracerDelegate) // ignore error
168+
}
169+
170+
return c, nil
171+
}
172+
173+
func (c *Client) setupDelegatedTracing(ctx context.Context, td TracerDelegate) error {
174+
pd := otlptracegrpc.NewClient(c.conn)
175+
e, err := otlptrace.New(ctx, pd)
176+
if err != nil {
177+
return nil
178+
}
179+
return td.SetSpanExporter(ctx, e)
180+
}
181+
182+
func (c *Client) ControlClient() controlapi.ControlClient {
183+
return controlapi.NewControlClient(c.conn)
184+
}
185+
186+
func (c *Client) ContentClient() contentapi.ContentClient {
187+
return contentapi.NewContentClient(c.conn)
188+
}
189+
190+
func (c *Client) Dialer() session.Dialer {
191+
return grpchijack.Dialer(c.ControlClient())
192+
}
193+
194+
func (c *Client) Wait(ctx context.Context) error {
195+
for {
196+
_, err := c.ControlClient().Info(ctx, &controlapi.InfoRequest{})
197+
if err == nil {
198+
return nil
199+
}
200+
201+
switch code := grpcerrors.Code(err); code {
202+
case codes.Unavailable:
203+
case codes.Unimplemented:
204+
// only buildkit v0.11+ supports the info api, but an unimplemented
205+
// response error is still a response so we can ignore it
206+
return nil
207+
default:
208+
return err
209+
}
210+
211+
select {
212+
case <-ctx.Done():
213+
return context.Cause(ctx)
214+
case <-time.After(time.Second):
215+
}
216+
c.conn.ResetConnectBackoff()
217+
}
218+
}
219+
220+
func (c *Client) Close() error {
221+
return c.conn.Close()
222+
}
223+
224+
type withDialer struct {
225+
dialer func(context.Context, string) (net.Conn, error)
226+
}
227+
228+
func (*withDialer) isClientOpt() {}
229+
230+
func WithContextDialer(df func(context.Context, string) (net.Conn, error)) ClientOpt {
231+
return &withDialer{dialer: df}
232+
}
233+
234+
type withCredentials struct {
235+
// server options
236+
serverName string
237+
caCert string
238+
caCertSystem bool
239+
240+
// client options
241+
cert string
242+
key string
243+
}
244+
245+
func (opts *withCredentials) merge(opts2 *withCredentials) *withCredentials {
246+
result := *opts
247+
if opts2 == nil {
248+
return &result
249+
}
250+
251+
// server options
252+
if opts2.serverName != "" {
253+
result.serverName = opts2.serverName
254+
}
255+
if opts2.caCert != "" {
256+
result.caCert = opts2.caCert
257+
}
258+
if opts2.caCertSystem {
259+
result.caCertSystem = opts2.caCertSystem
260+
}
261+
262+
// client options
263+
if opts2.cert != "" {
264+
result.cert = opts2.cert
265+
}
266+
if opts2.key != "" {
267+
result.key = opts2.key
268+
}
269+
270+
return &result
271+
}
272+
273+
func (*withCredentials) isClientOpt() {}
274+
275+
// WithCredentials configures the TLS parameters of the client.
276+
// Arguments:
277+
// * cert: specifies the filepath of the client certificate
278+
// * key: specifies the filepath of the client key
279+
func WithCredentials(cert, key string) ClientOpt {
280+
return &withCredentials{
281+
cert: cert,
282+
key: key,
283+
}
284+
}
285+
286+
// WithServerConfig configures the TLS parameters to connect to the server.
287+
// Arguments:
288+
// * serverName: specifies the server name to verify the hostname
289+
// * caCert: specifies the filepath of the CA certificate
290+
func WithServerConfig(serverName, caCert string) ClientOpt {
291+
return &withCredentials{
292+
serverName: serverName,
293+
caCert: caCert,
294+
}
295+
}
296+
297+
// WithServerConfigSystem configures the TLS parameters to connect to the
298+
// server, using the system's certificate pool.
299+
func WithServerConfigSystem(serverName string) ClientOpt {
300+
return &withCredentials{
301+
serverName: serverName,
302+
caCertSystem: true,
303+
}
304+
}
305+
306+
func loadCredentials(opts *withCredentials) (grpc.DialOption, error) {
307+
cfg := &tls.Config{}
308+
309+
if opts.caCertSystem {
310+
cfg.RootCAs, _ = x509.SystemCertPool()
311+
}
312+
if cfg.RootCAs == nil {
313+
cfg.RootCAs = x509.NewCertPool()
314+
}
315+
316+
if opts.caCert != "" {
317+
ca, err := os.ReadFile(opts.caCert)
318+
if err != nil {
319+
return nil, errors.Wrap(err, "could not read ca certificate")
320+
}
321+
if ok := cfg.RootCAs.AppendCertsFromPEM(ca); !ok {
322+
return nil, errors.New("failed to append ca certs")
323+
}
324+
}
325+
326+
if opts.serverName != "" {
327+
cfg.ServerName = opts.serverName
328+
}
329+
330+
// we will produce an error if the user forgot about either cert or key if at least one is specified
331+
if opts.cert != "" || opts.key != "" {
332+
cert, err := tls.LoadX509KeyPair(opts.cert, opts.key)
333+
if err != nil {
334+
return nil, errors.Wrap(err, "could not read certificate/key")
335+
}
336+
cfg.Certificates = append(cfg.Certificates, cert)
337+
}
338+
339+
return grpc.WithTransportCredentials(credentials.NewTLS(cfg)), nil
340+
}
341+
342+
func WithTracerProvider(t trace.TracerProvider) ClientOpt {
343+
return &withTracer{t}
344+
}
345+
346+
type withTracer struct {
347+
tp trace.TracerProvider
348+
}
349+
350+
func (w *withTracer) isClientOpt() {}
351+
352+
type TracerDelegate interface {
353+
SetSpanExporter(context.Context, sdktrace.SpanExporter) error
354+
}
355+
356+
func WithTracerDelegate(td TracerDelegate) ClientOpt {
357+
return &withTracerDelegate{
358+
TracerDelegate: td,
359+
}
360+
}
361+
362+
type withTracerDelegate struct {
363+
TracerDelegate
364+
}
365+
366+
func (w *withTracerDelegate) isClientOpt() {}
367+
368+
func WithSessionDialer(dialer func(context.Context, string, map[string][]string) (net.Conn, error)) ClientOpt {
369+
return &withSessionDialer{dialer}
370+
}
371+
372+
type withSessionDialer struct {
373+
dialer func(context.Context, string, map[string][]string) (net.Conn, error)
374+
}
375+
376+
func (w *withSessionDialer) isClientOpt() {}
377+
378+
func resolveDialer(address string) (func(context.Context, string) (net.Conn, error), error) {
379+
ch, err := connhelper.GetConnectionHelper(address)
380+
if err != nil {
381+
return nil, err
382+
}
383+
if ch != nil {
384+
return ch.ContextDialer, nil
385+
}
386+
return nil, nil
387+
}
388+
389+
type withGRPCDialOption struct {
390+
opt grpc.DialOption
391+
}
392+
393+
func (*withGRPCDialOption) isClientOpt() {}
394+
395+
func WithGRPCDialOption(opt grpc.DialOption) ClientOpt {
396+
return &withGRPCDialOption{opt}
397+
}

0 commit comments

Comments
 (0)