Skip to content

Commit ec4e164

Browse files
authored
Merge pull request #36 from fossas/feat/selfupdate
feat(update): #23 add fossa update command
2 parents 89a8aa0 + da90056 commit ec4e164

9 files changed

Lines changed: 193 additions & 17 deletions

File tree

.circleci/config.yml

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,42 @@ jobs:
66
working_directory: /home/fossa/go/src/github.com/fossas/fossa-cli
77
environment:
88
TEST_RESULTS: /tmp/test-results
9+
ARTIFACTS: /tmp/artifacts
910
steps:
1011
- checkout
11-
- run: mkdir -p $TEST_RESULTS
12+
- run:
13+
name: Make folders
14+
command: |
15+
mkdir -p $TEST_RESULTS
16+
mkdir -p $ARTIFACTS
1217
- restore_cache:
1318
keys:
14-
- gopkg-cache-{{ checksum "Gopkg.lock" }}
19+
- v1-gopkg-cache-{{ checksum "Gopkg.lock" }}
1520
- run:
1621
name: Compile CLI
1722
command: |
1823
# Invalidate pkg cache for FOSSA CLI binary
19-
rm -rf pkg/linux_amd64/github.com/fossas/fossa-cli
24+
rm -rf /home/fossa/go/pkg/linux_amd64/github.com/fossas/fossa-cli
2025
dep ensure
2126
make
27+
- save_cache:
28+
key: v1-gopkg-cache-{{ checksum "Gopkg.lock" }}
29+
paths:
30+
- "/home/fossa/go/pkg"
31+
- "/home/fossa/go/src/github.com/fossas/fossa-cli/vendor"
2232
- run:
2333
name: Run integration test
2434
command: |
2535
go test ./build
2636
- run:
2737
name: Run FOSSA build
2838
command: |
29-
fossa
39+
fossa --debug 2>$ARTIFACTS/fossa-build-stderr
3040
- run:
3141
name: Run FOSSA license check
3242
command: |
33-
fossa test
34-
- save_cache:
35-
key: gopkg-cache-{{ checksum "Gopkg.lock" }}
36-
paths:
37-
- "/home/fossa/go/pkg"
43+
fossa test --debug 2>$ARTIFACTS/fossa-test-stderr
3844
- store_test_results:
3945
path: /tmp/test-results
46+
- store_artifacts:
47+
path: /tmp/artifacts

Gopkg.lock

Lines changed: 51 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/golang.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,10 +496,12 @@ func (builder *GoBuilder) Analyze(m module.Module, allowUnresolved bool) ([]modu
496496
depSet := make(map[GoPkg]bool)
497497
projectImports := strings.TrimPrefix(projectFolder, filepath.Join(os.Getenv("GOPATH"), "src")+string(filepath.Separator))
498498
for _, dep := range traced {
499+
goLogger.Debugf("Resolving raw import: %s", dep.ImportPath)
499500
// Strip out `/vendor/` weirdness in import paths.
500501
const vendorPrefix = "/vendor/"
501502
vendoredPathSections := strings.Split(dep.ImportPath, vendorPrefix)
502503
importPath := vendoredPathSections[len(vendoredPathSections)-1]
504+
goLogger.Debugf("Resolving import: %s", importPath)
503505

504506
// Get revisions (often these are scoped to repository, not package)
505507
project, err := findRevision(lockfileVersions, importPath)
@@ -518,7 +520,7 @@ func (builder *GoBuilder) Analyze(m module.Module, allowUnresolved bool) ([]modu
518520
goLogger.Debugf("$GOPATH: %#v", os.Getenv("GOPATH"))
519521
goLogger.Debugf("Project folder relative to $GOPATH: %#v", projectImports)
520522
goLogger.Debugf("Lockfile versions: %#v", lockfileVersions)
521-
return nil, fmt.Errorf("could not resolve import: %#v", dep.ImportPath)
523+
return nil, fmt.Errorf("could not resolve import: %#v", importPath)
522524
}
523525
} else {
524526
depSet[GoPkg{ImportPath: project, Version: lockfileVersions[project], isInternal: dep.isInternal}] = true

cmd/fossa/common.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,48 @@ package main
33
import (
44
"bytes"
55
"fmt"
6+
"io"
67
"io/ioutil"
8+
"net"
79
"net/http"
10+
"net/url"
11+
"time"
812

913
logging "github.com/op/go-logging"
1014
)
1115

1216
var commonLogger = logging.MustGetLogger("common")
17+
var client = http.Client{
18+
Timeout: 10 * time.Second,
19+
}
20+
21+
func isTimeout(err error) bool {
22+
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
23+
return true
24+
}
25+
if urlErr, ok := err.(*url.Error); ok && urlErr.Err == io.EOF {
26+
return true
27+
}
28+
return false
29+
}
1330

1431
// Common utilities among commands
15-
func makeAPIRequest(method, url, apiKey string, payload []byte) ([]byte, error) {
16-
commonLogger.Debugf("Making API request %#v %#v %#v %#v", method, url, apiKey, string(payload))
17-
req, err := http.NewRequest(method, url, bytes.NewReader(payload))
32+
func makeAPIRequest(method, endpoint, apiKey string, payload []byte) ([]byte, error) {
33+
commonLogger.Debugf("Making API request %#v %#v %#v %#v", method, endpoint, apiKey, string(payload))
34+
req, err := http.NewRequest(method, endpoint, bytes.NewReader(payload))
1835
if err != nil {
1936
return nil, fmt.Errorf("could not construct API HTTP request: %s", err.Error())
2037
}
38+
req.Close = true
2139
req.Header.Set("Authorization", "token "+apiKey)
2240
req.Header.Set("Content-Type", "application/json")
2341

24-
resp, err := http.DefaultClient.Do(req)
42+
resp, err := client.Do(req)
2543
if err != nil {
44+
if isTimeout(err) {
45+
commonLogger.Debugf("API request timed out")
46+
return nil, err
47+
}
2648
return nil, fmt.Errorf("failed to send API HTTP request: %s", err.Error())
2749
}
2850
defer resp.Body.Close()

cmd/fossa/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ func initialize(c *cli.Context) (cliConfig, error) {
253253
} else {
254254
formatter := logging.MustStringFormatter(`%{color}%{level}%{color:reset} %{message}`)
255255
stderrBackend := logging.AddModuleLevel(logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), formatter))
256-
stderrBackend.SetLevel(logging.WARNING, "")
256+
stderrBackend.SetLevel(logging.INFO, "")
257257
logging.SetBackend(stderrBackend)
258258
}
259259

cmd/fossa/main.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ func main() {
110110
cli.BoolFlag{Name: "debug", Usage: debugUsage},
111111
},
112112
},
113+
{
114+
Name: "update",
115+
Usage: "Updates `fossa` to the latest version",
116+
Action: updateCmd,
117+
Flags: []cli.Flag{
118+
cli.BoolFlag{Name: "debug", Usage: debugUsage},
119+
},
120+
},
113121
}
114122

115123
app.Run(os.Args)
@@ -244,6 +252,10 @@ func defaultCmd(c *cli.Context) {
244252
mainLogger.Fatalf("Could not load configuration: %s", err.Error())
245253
}
246254

255+
if ok, err := checkUpdate(); err == nil && ok {
256+
mainLogger.Noticef("An update is available for this CLI; run `fossa update` to get the latest version.")
257+
}
258+
247259
if len(config.modules) == 0 {
248260
mainLogger.Fatal("No modules specified for analysis.")
249261
}

cmd/fossa/test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ func getBuild(endpoint, apiKey, project, revision string) (buildResponse, error)
4545

4646
testLogger.Debugf("Making Builds API request to: %#v", url)
4747
res, err := makeAPIRequest(http.MethodPut, url, apiKey, nil)
48+
if isTimeout(err) {
49+
return buildResponse{}, err
50+
}
4851
if err != nil {
4952
return buildResponse{}, fmt.Errorf("could not make FOSSA build request: %s", err)
5053
}
@@ -104,6 +107,10 @@ func doTest(s *spinner.Spinner, race chan testResult, endpoint, apiKey, project,
104107
buildLoop:
105108
for {
106109
build, err := getBuild(endpoint, apiKey, project, revision)
110+
if isTimeout(err) {
111+
time.Sleep(pollRequestDelay)
112+
continue
113+
}
107114
if err != nil {
108115
race <- testResult{err: err, issues: nil}
109116
return

cmd/fossa/update.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/blang/semver"
8+
logging "github.com/op/go-logging"
9+
"github.com/rhysd/go-github-selfupdate/selfupdate"
10+
"github.com/urfave/cli"
11+
)
12+
13+
const updateEndpoint = "fossas/fossa-cli"
14+
15+
var updateLogger = logging.MustGetLogger("update")
16+
17+
func getSemver(v string) string {
18+
if v[0] != 'v' {
19+
return ""
20+
}
21+
return v[1:]
22+
}
23+
24+
func checkUpdate() (bool, error) {
25+
latest, found, err := selfupdate.DetectLatest(updateEndpoint)
26+
if err != nil {
27+
return false, fmt.Errorf("could not check for updates: %s", err.Error())
28+
}
29+
30+
parsedVersion := getSemver(version)
31+
updateLogger.Debugf("checking version for updates (%s -> %s)", version, parsedVersion)
32+
v, err := semver.Parse(parsedVersion)
33+
if err != nil {
34+
return false, errors.New("invalid version (are you using a development binary?)")
35+
}
36+
if !found || latest.Version.Equals(v) {
37+
return false, nil
38+
}
39+
return true, nil
40+
}
41+
42+
func doSelfUpdate() error {
43+
parsedVersion := getSemver(version)
44+
v, err := semver.Parse(parsedVersion)
45+
if err != nil {
46+
return errors.New("invalid version (are you using a development binary?)")
47+
}
48+
latest, err := selfupdate.UpdateSelf(v, updateEndpoint)
49+
if err != nil {
50+
return fmt.Errorf("could not update: %s", err.Error())
51+
}
52+
if latest.Version.Equals(v) {
53+
return errors.New("no update required")
54+
}
55+
updateLogger.Debugf("updating binary versions (%s -> %s)", version, latest.Version)
56+
return nil
57+
}
58+
59+
func updateCmd(c *cli.Context) {
60+
ok, err := checkUpdate()
61+
if err != nil {
62+
updateLogger.Fatalf("Unable to update: %s", err.Error())
63+
}
64+
if !ok {
65+
updateLogger.Fatalf("No updates available")
66+
}
67+
68+
if err := doSelfUpdate(); err != nil {
69+
updateLogger.Fatalf("Update failed: %s", err.Error())
70+
}
71+
72+
updateLogger.Info("fossa has been updated; run `fossa -v` to view the current version")
73+
}

cmd/fossa/upload.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,14 @@ func doUpload(config cliConfig, results []normalizedModule) (string, error) {
142142
analysisLogger.Debugf("Sending build data to <%#v>", postURL)
143143

144144
req, _ := http.NewRequest("POST", postURL, bytes.NewReader(buildData))
145+
req.Close = true
146+
145147
req.Header.Set("Authorization", "token "+config.apiKey)
146148
req.Header.Set("Content-Type", "application/json")
147149

148150
resp, err := http.DefaultClient.Do(req)
149151
if err != nil {
150-
return "", fmt.Errorf("could not begin upload: %#v", err)
152+
return "", fmt.Errorf("could not begin upload: %s", err.Error())
151153
}
152154
defer resp.Body.Close()
153155
responseBytes, _ := ioutil.ReadAll(resp.Body)

0 commit comments

Comments
 (0)