@@ -0,0 +1,127 @@
1+// Copyright 2022 The Parca Authors
2+// Licensed under the Apache License, Version 2.0 (the "License");
3+// you may not use this file except in compliance with the License.
4+// You may obtain a copy of the License at
5+//
6+// http://www.apache.org/licenses/LICENSE-2.0
7+//
8+// Unless required by applicable law or agreed to in writing, software
9+// distributed under the License is distributed on an "AS IS" BASIS,
10+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+// See the License for the specific language governing permissions and
12+// limitations under the License.
13+14+package debuginfo
15+16+import (
17+"fmt"
18+"io"
19+"net/http"
20+"net/url"
21+"path"
22+"time"
23+24+"github.com/go-kit/log"
25+"github.com/go-kit/log/level"
26+"github.com/thanos-io/thanos/pkg/objstore"
27+"github.com/thanos-io/thanos/pkg/objstore/client"
28+"golang.org/x/net/context"
29+"gopkg.in/yaml.v2"
30+)
31+32+type DebugInfodClient interface {
33+GetDebugInfo(ctx context.Context, buildid string) (io.ReadCloser, error)
34+}
35+36+type HTTPDebugInfodClient struct {
37+logger log.Logger
38+UpstreamServer *url.URL
39+timeoutDuration time.Duration
40+}
41+42+type DebugInfodClientObjectStorageCache struct {
43+logger log.Logger
44+client DebugInfodClient
45+bucket objstore.Bucket
46+}
47+48+func NewHTTPDebugInfodClient(logger log.Logger, serverURL string, timeoutDuration time.Duration) (*HTTPDebugInfodClient, error) {
49+parsedURL, err := url.Parse(serverURL)
50+if err != nil {
51+return nil, err
52+ }
53+return &HTTPDebugInfodClient{
54+logger: logger,
55+UpstreamServer: parsedURL,
56+timeoutDuration: timeoutDuration,
57+ }, nil
58+}
59+60+func NewDebugInfodClientWithObjectStorageCache(logger log.Logger, config *Config, h DebugInfodClient) (*DebugInfodClientObjectStorageCache, error) {
61+cfg, err := yaml.Marshal(config.Bucket)
62+if err != nil {
63+return nil, fmt.Errorf("marshal content of debuginfod object storage configuration: %w", err)
64+ }
65+66+bucket, err := client.NewBucket(logger, cfg, nil, "parca")
67+if err != nil {
68+return nil, fmt.Errorf("instantiate debuginfod object storage: %w", err)
69+ }
70+return &DebugInfodClientObjectStorageCache{
71+logger: logger,
72+client: h,
73+bucket: bucket,
74+ }, nil
75+}
76+77+func (c *DebugInfodClientObjectStorageCache) GetDebugInfo(ctx context.Context, buildID string) (io.ReadCloser, error) {
78+path := "/debuginfod-cache/" + buildID + "/debuginfo"
79+80+if exists, _ := c.bucket.Exists(ctx, path); exists {
81+debuginfoFile, err := c.bucket.Get(ctx, path)
82+if err != nil {
83+return nil, fmt.Errorf("failed to download object file from debuginfod cache: build_id: %v: %w", buildID, err)
84+ }
85+return debuginfoFile, nil
86+ }
87+88+debugInfo, err := c.client.GetDebugInfo(ctx, buildID)
89+if err != nil {
90+return nil, ErrDebugInfoNotFound
91+ }
92+93+err = c.bucket.Upload(ctx, path, debugInfo)
94+if err != nil {
95+return nil, fmt.Errorf("failed to upload to debuginfod cache: %w", err)
96+ }
97+98+debugInfoReader, err := c.bucket.Get(ctx, path)
99+if err != nil {
100+return nil, fmt.Errorf("failed to download object file from debuginfod cache: build_id: %v: %w", buildID, err)
101+ }
102+103+return debugInfoReader, nil
104+}
105+106+func (c *HTTPDebugInfodClient) GetDebugInfo(ctx context.Context, buildID string) (io.ReadCloser, error) {
107+buildIDURL := *c.UpstreamServer
108+buildIDURL.Path = path.Join(buildIDURL.Path, buildID, "debuginfo")
109+110+ctx, cancel := context.WithTimeout(ctx, c.timeoutDuration)
111+defer cancel()
112+113+req, err := http.NewRequestWithContext(ctx, "GET", buildIDURL.String(), nil)
114+if err != nil {
115+level.Debug(c.logger).Log("msg", "failed to create new HTTP request", "err", err)
116+return nil, err
117+ }
118+119+client := http.DefaultClient
120+resp, err := client.Do(req)
121+if err != nil {
122+level.Debug(c.logger).Log("msg", "object not found in public server", "object", buildID, "err", err)
123+return nil, ErrDebugInfoNotFound
124+ }
125+126+return resp.Body, nil
127+}