Skip to content

Commit 8f2f325

Browse files
authored
Add lsif upload command. (#47)
1 parent 303d765 commit 8f2f325

3 files changed

Lines changed: 214 additions & 0 deletions

File tree

cmd/src/lsif.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
)
7+
8+
var lsifCommands commander
9+
10+
func init() {
11+
usage := `'src lsif' is a tool that manages LSIF data on a Sourcegraph instance.
12+
13+
Usage:
14+
15+
src lsif command [command options]
16+
17+
The commands are:
18+
19+
upload uploads an LSIF dump file
20+
21+
Use "src lsif [command] -h" for more information about a command.
22+
`
23+
24+
flagSet := flag.NewFlagSet("lsif", flag.ExitOnError)
25+
handler := func(args []string) error {
26+
lsifCommands.run(flagSet, "src lsif", usage, args)
27+
return nil
28+
}
29+
30+
// Register the command.
31+
commands = append(commands, &command{
32+
flagSet: flagSet,
33+
aliases: []string{"lsif"},
34+
handler: handler,
35+
usageFunc: func() {
36+
fmt.Println(usage)
37+
},
38+
})
39+
}

cmd/src/lsif_upload.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"compress/gzip"
6+
"flag"
7+
"fmt"
8+
"io"
9+
"io/ioutil"
10+
"net/http"
11+
"net/url"
12+
"os"
13+
14+
"github.com/kballard/go-shellquote"
15+
"github.com/mattn/go-isatty"
16+
)
17+
18+
func init() {
19+
usage := `
20+
Examples:
21+
22+
Upload an LSIF dump:
23+
24+
$ src lsif upload -repo=FOO -commit=BAR -upload-token=BAZ -file=data.lsif
25+
26+
`
27+
28+
flagSet := flag.NewFlagSet("upload", flag.ExitOnError)
29+
usageFunc := func() {
30+
fmt.Fprintf(flag.CommandLine.Output(), "Usage of 'src lsif %s':\n", flagSet.Name())
31+
flagSet.PrintDefaults()
32+
fmt.Println(usage)
33+
}
34+
var (
35+
repoFlag = flagSet.String("repo", "", `The name of the repository. (required)`)
36+
commitFlag = flagSet.String("commit", "", `The 40-character hash of the commit. (required)`)
37+
fileFlag = flagSet.String("file", "", `The path to the LSIF dump file. (required)`)
38+
uploadTokenFlag = flagSet.String("upload-token", "", `The LSIF upload token for the given repository. (required for Sourcegraph.com only)`)
39+
apiFlags = newAPIFlags(flagSet)
40+
)
41+
42+
handler := func(args []string) error {
43+
flagSet.Parse(args)
44+
45+
ensureSet := func(value *string, argName string) {
46+
if value == nil || *value == "" {
47+
fmt.Printf("src lsif: no %s supplied\n", argName)
48+
fmt.Printf("Run 'src lsif help' for usage.\n")
49+
os.Exit(1)
50+
}
51+
}
52+
53+
ensureSet(repoFlag, "repository")
54+
ensureSet(commitFlag, "commit")
55+
ensureSet(fileFlag, "dump file")
56+
57+
// First, build the URL which is used to both make the request
58+
// and to emit a cURL command. This is a little different than
59+
// the rest of the commands as it does not use a GraphQL endpoint,
60+
// using the path and query string instead of the body.
61+
62+
qs := url.Values{}
63+
qs.Add("repository", *repoFlag)
64+
qs.Add("commit", *commitFlag)
65+
if *uploadTokenFlag != "" {
66+
qs.Add("upload_token", *uploadTokenFlag)
67+
}
68+
69+
url, err := url.Parse(cfg.Endpoint + "/.api/lsif/upload")
70+
if err != nil {
71+
return err
72+
}
73+
url.RawQuery = qs.Encode()
74+
75+
// Emit a cURL command. This is also a bit different than the rest
76+
// of the commands as it uploads a file rather than just sending a
77+
// JSON-encoded body.
78+
//
79+
// Because we compress the body before sending it to the API below,
80+
// we need to pipe the output of gzip into the cURL command.
81+
82+
if *apiFlags.getCurl {
83+
curl := fmt.Sprintf("gzip -c %s | curl \\\n", shellquote.Join(*fileFlag))
84+
curl += fmt.Sprintf(" -X POST \\\n")
85+
if cfg.AccessToken != "" {
86+
curl += fmt.Sprintf(" %s \\\n", shellquote.Join("-H", "Authorization: token "+cfg.AccessToken))
87+
}
88+
89+
curl += fmt.Sprintf(" %s \\\n", shellquote.Join("-H", "Content-Type: application/x-ndjson+lsif"))
90+
curl += fmt.Sprintf(" %s \\\n", shellquote.Join(url.String()))
91+
curl += fmt.Sprintf(" %s", shellquote.Join("--data-binary", "@-"))
92+
93+
fmt.Println(curl)
94+
return nil
95+
}
96+
97+
f, err := os.Open(*fileFlag)
98+
if err != nil {
99+
return err
100+
}
101+
defer f.Close()
102+
103+
// compress the file
104+
pr, ch := gzipReader(f)
105+
106+
// Create the HTTP request.
107+
req, err := http.NewRequest("POST", url.String(), pr)
108+
if err != nil {
109+
return err
110+
}
111+
112+
req.Header.Set("Content-Type", "application/x-ndjson+lsif")
113+
if cfg.AccessToken != "" {
114+
req.Header.Set("Authorization", "token "+cfg.AccessToken)
115+
}
116+
117+
// Perform the request.
118+
resp, err := http.DefaultClient.Do(req)
119+
if err != nil {
120+
return err
121+
}
122+
defer resp.Body.Close()
123+
124+
// See if we had a reader error
125+
if err := <-ch; err != nil {
126+
return err
127+
}
128+
129+
// Our request may have failed before the reaching GraphQL endpoint, so
130+
// confirm the status code. You can test this easily with e.g. an invalid
131+
// endpoint like -endpoint=https://google.com
132+
if resp.StatusCode != http.StatusOK {
133+
if resp.StatusCode == http.StatusUnauthorized && isatty.IsCygwinTerminal(os.Stdout.Fd()) {
134+
fmt.Println("You may need to specify or update your access token to use this endpoint.")
135+
fmt.Println("See https://github.com/sourcegraph/src-cli#authentication")
136+
fmt.Println("")
137+
}
138+
body, err := ioutil.ReadAll(resp.Body)
139+
if err != nil {
140+
return err
141+
}
142+
return fmt.Errorf("error: %s\n\n%s", resp.Status, body)
143+
}
144+
145+
fmt.Printf("LSIF dump uploaded.\n")
146+
return nil
147+
}
148+
149+
// Register the command.
150+
lsifCommands = append(lsifCommands, &command{
151+
flagSet: flagSet,
152+
handler: handler,
153+
usageFunc: usageFunc,
154+
})
155+
}
156+
157+
func gzipReader(r io.Reader) (io.Reader, <-chan error) {
158+
ch := make(chan error)
159+
br := bufio.NewReader(r)
160+
pr, pw := io.Pipe()
161+
gw := gzip.NewWriter(pw)
162+
163+
go func() {
164+
defer close(ch)
165+
defer pw.Close() // must be closed 2nd
166+
defer gw.Close() // must be closed 1st
167+
168+
if _, err := br.WriteTo(gw); err != nil {
169+
ch <- err
170+
}
171+
}()
172+
173+
return pr, ch
174+
}

cmd/src/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ The commands are:
3333
config manages global, org, and user settings
3434
extsvc manages external services
3535
extensions,ext manages extensions (experimental)
36+
lsif manages LSIF data
3637
3738
Use "src [command] -h" for more information about a command.
3839

0 commit comments

Comments
 (0)