forked from sourcegraph/sourcegraph-public-snapshot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinternal_client.go
More file actions
390 lines (341 loc) · 12.2 KB
/
internal_client.go
File metadata and controls
390 lines (341 loc) · 12.2 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
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
package api
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/sourcegraph/sourcegraph/pkg/env"
"github.com/sourcegraph/sourcegraph/pkg/inventory"
"github.com/sourcegraph/sourcegraph/pkg/jsonc"
"github.com/sourcegraph/sourcegraph/pkg/txemail/txtypes"
"github.com/sourcegraph/sourcegraph/schema"
"golang.org/x/net/context/ctxhttp"
log15 "gopkg.in/inconshreveable/log15.v2"
)
var frontendInternal = env.Get("SRC_FRONTEND_INTERNAL", "sourcegraph-frontend-internal", "HTTP address for internal frontend HTTP API.")
type internalClient struct {
// URL is the root to the internal API frontend server.
URL string
}
var InternalClient = &internalClient{URL: "http://" + frontendInternal}
// WaitForFrontend should be called by services that intend to wait for the
// frontend to start. It uses a 5s timeout with the given context, and logs an
// error if it fails.
func WaitForFrontend(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := InternalClient.RetryPingUntilAvailable(ctx); err != nil {
log15.Warn("frontend not available at startup (will periodically try to reconnect)", "err", err)
}
}
// RetryPingUntilAvailable retries a noop request to the internal API until it is able to reach
// the endpoint, indicating that the endpoint is available.
func (c *internalClient) RetryPingUntilAvailable(ctx context.Context) error {
ping := func(ctx context.Context) error {
resp, err := ctxhttp.Get(ctx, nil, c.URL+"/.internal/ping")
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("ping: bad HTTP response status %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if want := "pong"; string(body) != want {
const max = 15
if len(body) > max {
body = body[:max]
}
return fmt.Errorf("ping: bad HTTP response body %q (want %q)", body, want)
}
return nil
}
var lastErr error
for {
err := ping(ctx)
if err != nil {
if ctx.Err() != nil {
return fmt.Errorf("frontend API not reachable: %s (last error: %v)", err, lastErr)
}
// Keep trying.
lastErr = err
time.Sleep(250 * time.Millisecond)
continue
}
break
}
return nil
}
type SavedQueryIDSpec struct {
Subject SettingsSubject
Key string
}
// ConfigSavedQuery is the JSON shape of a saved query entry in the JSON configuration
// (i.e., an entry in the {"search.savedQueries": [...]} array).
type ConfigSavedQuery struct {
Key string `json:"key,omitempty"`
Description string `json:"description"`
Query string `json:"query"`
ShowOnHomepage bool `json:"showOnHomepage"`
Notify bool `json:"notify,omitempty"`
NotifySlack bool `json:"notifySlack,omitempty"`
}
func (sq ConfigSavedQuery) Equals(other ConfigSavedQuery) bool {
a, _ := json.Marshal(sq)
b, _ := json.Marshal(other)
return bytes.Equal(a, b)
}
// PartialConfigSavedQueries is the JSON configuration shape, including only the
// search.savedQueries section.
type PartialConfigSavedQueries struct {
SavedQueries []ConfigSavedQuery `json:"search.savedQueries"`
}
// SavedQuerySpecAndConfig represents a saved query configuration its unique ID.
type SavedQuerySpecAndConfig struct {
Spec SavedQueryIDSpec
Config ConfigSavedQuery
}
// SavedQueriesListAll lists all saved queries, from every user, org, etc.
func (c *internalClient) SavedQueriesListAll(ctx context.Context) (map[SavedQueryIDSpec]ConfigSavedQuery, error) {
var result []SavedQuerySpecAndConfig
err := c.postInternal(ctx, "saved-queries/list-all", nil, &result)
if err != nil {
return nil, err
}
m := map[SavedQueryIDSpec]ConfigSavedQuery{}
for _, r := range result {
m[r.Spec] = r.Config
}
return m, nil
}
// SavedQueryInfo represents information about a saved query that was executed.
type SavedQueryInfo struct {
// Query is the search query in question.
Query string
// LastExecuted is the timestamp of the last time that the search query was
// executed.
LastExecuted time.Time
// LatestResult is the timestamp of the latest-known result for the search
// query. Therefore, searching `after:<LatestResult>` will return the new
// search results not yet seen.
LatestResult time.Time
// ExecDuration is the amount of time it took for the query to execute.
ExecDuration time.Duration
}
// SavedQueriesGetInfo gets the info from the DB for the given saved query. nil
// is returned if there is no existing info for the saved query.
func (c *internalClient) SavedQueriesGetInfo(ctx context.Context, query string) (*SavedQueryInfo, error) {
var result *SavedQueryInfo
err := c.postInternal(ctx, "saved-queries/get-info", query, &result)
if err != nil {
return nil, err
}
return result, nil
}
// SavedQueriesSetInfo sets the info in the DB for the given query.
func (c *internalClient) SavedQueriesSetInfo(ctx context.Context, info *SavedQueryInfo) error {
return c.postInternal(ctx, "saved-queries/set-info", info, nil)
}
func (c *internalClient) SavedQueriesDeleteInfo(ctx context.Context, query string) error {
return c.postInternal(ctx, "saved-queries/delete-info", query, nil)
}
func (c *internalClient) SettingsGetForSubject(ctx context.Context, subject SettingsSubject) (parsed *schema.Settings, settings *Settings, err error) {
err = c.postInternal(ctx, "settings/get-for-subject", subject, &settings)
if err == nil {
err = jsonc.Unmarshal(settings.Contents, &parsed)
}
return parsed, settings, err
}
var MockOrgsListUsers func(orgID int32) (users []int32, err error)
func (c *internalClient) OrgsListUsers(ctx context.Context, orgID int32) (users []int32, err error) {
if MockOrgsListUsers != nil {
return MockOrgsListUsers(orgID)
}
err = c.postInternal(ctx, "orgs/list-users", orgID, &users)
if err != nil {
return nil, err
}
return users, nil
}
func (c *internalClient) OrgsGetByName(ctx context.Context, orgName string) (orgID *int32, err error) {
err = c.postInternal(ctx, "orgs/get-by-name", orgName, &orgID)
if err != nil {
return nil, err
}
return orgID, nil
}
func (c *internalClient) UsersGetByUsername(ctx context.Context, username string) (user *int32, err error) {
err = c.postInternal(ctx, "users/get-by-username", username, &user)
if err != nil {
return nil, err
}
return user, nil
}
func (c *internalClient) UserEmailsGetEmail(ctx context.Context, userID int32) (email *string, err error) {
err = c.postInternal(ctx, "user-emails/get-email", userID, &email)
if err != nil {
return nil, err
}
return email, nil
}
// TODO(slimsag): In the future, once we're no longer using environment
// variables to build ExternalURL, remove this in favor of services just reading it
// directly from the configuration file.
//
// TODO(slimsag): needs cleanup as part of upcoming configuration refactor.
func (c *internalClient) ExternalURL(ctx context.Context) (string, error) {
var externalURL string
err := c.postInternal(ctx, "app-url", nil, &externalURL)
if err != nil {
return "", err
}
return externalURL, nil
}
func (c *internalClient) GitServerAddrs(ctx context.Context) ([]string, error) {
var gitServerAddrs []string
err := c.postInternal(ctx, "git-server-addrs", nil, &gitServerAddrs)
if err != nil {
return nil, err
}
return gitServerAddrs, nil
}
// TODO(slimsag): needs cleanup as part of upcoming configuration refactor.
func (c *internalClient) CanSendEmail(ctx context.Context) (canSendEmail bool, err error) {
err = c.postInternal(ctx, "can-send-email", nil, &canSendEmail)
if err != nil {
return false, err
}
return canSendEmail, nil
}
// TODO(slimsag): needs cleanup as part of upcoming configuration refactor.
func (c *internalClient) SendEmail(ctx context.Context, message txtypes.Message) error {
return c.postInternal(ctx, "send-email", &message, nil)
}
func (c *internalClient) DefsRefreshIndex(ctx context.Context, repo RepoName, commitID CommitID) error {
return c.postInternal(ctx, "defs/refresh-index", &DefsRefreshIndexRequest{
RepoName: repo,
CommitID: commitID,
}, nil)
}
func (c *internalClient) PkgsRefreshIndex(ctx context.Context, repo RepoName, commitID CommitID) error {
return c.postInternal(ctx, "pkgs/refresh-index", &PkgsRefreshIndexRequest{
RepoName: repo,
CommitID: commitID,
}, nil)
}
func (c *internalClient) ReposCreateIfNotExists(ctx context.Context, op RepoCreateOrUpdateRequest) (*Repo, error) {
var repo Repo
err := c.postInternal(ctx, "repos/create-if-not-exists", op, &repo)
if err != nil {
return nil, err
}
return &repo, nil
}
// ReposListEnabled returns a list of all enabled repository names.
func (c *internalClient) ReposListEnabled(ctx context.Context) ([]RepoName, error) {
var names []RepoName
err := c.postInternal(ctx, "repos/list-enabled", nil, &names)
return names, err
}
func (c *internalClient) ConfigurationRawJSON(ctx context.Context) (string, error) {
var rawJSON string
err := c.postInternal(ctx, "configuration/raw-json", nil, &rawJSON)
return rawJSON, err
}
func (c *internalClient) ReposUpdateMetadata(ctx context.Context, repo RepoName, description string, fork bool, archived bool) error {
return c.postInternal(ctx, "repos/update-metadata", ReposUpdateMetadataRequest{
RepoName: repo,
Description: description,
Fork: fork,
Archived: archived,
}, nil)
}
func (c *internalClient) ReposUpdateIndex(ctx context.Context, repo RepoID, commitID CommitID, lang string) error {
return c.postInternal(ctx, "repos/update-index", RepoUpdateIndexRequest{
RepoID: repo,
CommitID: commitID,
Language: lang,
}, nil)
}
func (c *internalClient) ReposGetByName(ctx context.Context, repoName RepoName) (*Repo, error) {
var repo Repo
err := c.postInternal(ctx, "repos/"+string(repoName), nil, &repo)
if err != nil {
return nil, err
}
return &repo, nil
}
func (c *internalClient) ReposGetInventoryUncached(ctx context.Context, repo RepoID, commitID CommitID) (*inventory.Inventory, error) {
var inv inventory.Inventory
err := c.postInternal(ctx, "repos/inventory-uncached", ReposGetInventoryUncachedRequest{Repo: repo, CommitID: commitID}, &inv)
if err != nil {
return nil, err
}
return &inv, nil
}
func (c *internalClient) ReposGetInventory(ctx context.Context, repo RepoID, commitID CommitID) (*inventory.Inventory, error) {
var inv inventory.Inventory
err := c.postInternal(ctx, "repos/inventory", ReposGetInventoryRequest{Repo: repo, CommitID: commitID}, &inv)
if err != nil {
return nil, err
}
return &inv, nil
}
func (c *internalClient) PhabricatorRepoCreate(ctx context.Context, repo RepoName, callsign, url string) error {
return c.postInternal(ctx, "phabricator/repo-create", PhabricatorRepoCreateRequest{
RepoName: repo,
Callsign: callsign,
URL: url,
}, nil)
}
func (c *internalClient) LogTelemetry(ctx context.Context, env string, reqBody interface{}) error {
return c.postInternal(ctx, "telemetry/log/v1/"+env, reqBody, nil)
}
// postInternal sends an HTTP post request to the internal route.
func (c *internalClient) postInternal(ctx context.Context, route string, reqBody, respBody interface{}) error {
return c.post(ctx, "/.internal/"+route, reqBody, respBody)
}
// post sends an HTTP post request to the provided route. If reqBody is
// non-nil it will Marshal it as JSON and set that as the Request body. If
// respBody is non-nil the response body will be JSON unmarshalled to resp.
func (c *internalClient) post(ctx context.Context, route string, reqBody, respBody interface{}) error {
var data []byte
if reqBody != nil {
var err error
data, err = json.Marshal(reqBody)
if err != nil {
return err
}
}
resp, err := ctxhttp.Post(ctx, nil, c.URL+route, "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAPIResponse(resp); err != nil {
return err
}
if respBody != nil {
return json.NewDecoder(resp.Body).Decode(respBody)
}
return nil
}
func checkAPIResponse(resp *http.Response) error {
if 200 > resp.StatusCode || resp.StatusCode > 299 {
buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
b := buf.Bytes()
errString := string(b)
if errString != "" {
return fmt.Errorf("internal API response error code %d: %s (%s)", resp.StatusCode, errString, resp.Request.URL)
}
return fmt.Errorf("internal API response error code %d (%s)", resp.StatusCode, resp.Request.URL)
}
return nil
}