├── .gitignore ├── Dockerfile ├── README.md ├── assets └── gopherhat.jpg ├── check ├── check.go ├── go_vet.go ├── gocyclo.go ├── gofmt.go ├── goget.go ├── goget_test.go ├── golint.go └── utils.go ├── containers.yaml ├── main.go ├── scripts ├── config.sh └── restart-containers.sh └── templates └── home.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | repos 3 | go_report 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest 2 | 3 | RUN mkdir -p /go/src/github.com/gophergala/go_report 4 | WORKDIR /go/src/github.com/gophergala/go_report 5 | ENV PATH /go/bin:$PATH 6 | COPY . /go/src/github.com/gophergala/go_report 7 | RUN apt-get -t lenny-backports install bzr 8 | RUN go get golang.org/x/tools/cmd/goimports 9 | RUN go get github.com/fzipp/gocyclo 10 | RUN go get github.com/golang/lint/golint 11 | RUN go get golang.org/x/tools/cmd/vet 12 | RUN go get gopkg.in/mgo.v2 13 | RUN go-wrapper install 14 | CMD ["go-wrapper", "run"] 15 | #ONBUILD COPY . /go/src/github.com/gophergala/go_report 16 | #ONBUILD RUN echo '/go/src/github.com/gophergala/go_report' > .godir 17 | #ONBUILD RUN go install github.com/gophergala/go_report 18 | #ONBUILD RUN go-wrapper install golint 19 | #ONBUILD RUN go-wrapper install 20 | #ENTRYPOINT /go/bin/go_report 21 | EXPOSE 8080 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Report Card 2 | 3 | ### Moved to https://github.com/gojp/goreportcard 4 | 5 | Seen here is the submission for the Gopher Gala 2015. Further development is being continued at the URL above, please open PRs and issues there. Thanks! 6 | 7 | ****** 8 | ****** 9 | ****** 10 | 11 | A web application that generates a report on the quality of an open source go project. It uses several measures, including `gofmt`, `go vet`, `go lint` and `gocyclo`. 12 | 13 | #### Live demo: [http://goreportcard.com](http://goreportcard.com) 14 | 15 | Or here's the result for this repo: [http://goreportcard.com/report/gophergala/go_report](http://goreportcard.com/report/gophergala/go_report) 16 | 17 | ![Screenshot of Go Report Card in action](https://cloud.githubusercontent.com/assets/1121616/5891942/a2a38b8e-a4f1-11e4-82e3-29b25137f09b.png) 18 | 19 | ****** 20 | 21 | A submission for [Gopher Gala](http://gophergala.com/) Hackathon 2015. 22 | -------------------------------------------------------------------------------- /assets/gopherhat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophergala/go_report/d736a29ce89590ceed93395e07a87187a736183a/assets/gopherhat.jpg -------------------------------------------------------------------------------- /check/check.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | type Check interface { 4 | Name() string 5 | Description() string 6 | // Percentage returns the passing percentage of the check, 7 | // as well as a map of filename to output 8 | Percentage() (float64, []FileSummary, error) 9 | } 10 | -------------------------------------------------------------------------------- /check/go_vet.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | type GoVet struct { 4 | Dir string 5 | Filenames []string 6 | } 7 | 8 | func (g GoVet) Name() string { 9 | return "go_vet" 10 | } 11 | 12 | // Percentage returns the percentage of .go files that pass go vet 13 | func (g GoVet) Percentage() (float64, []FileSummary, error) { 14 | return GoTool(g.Dir, g.Filenames, []string{"go", "tool", "vet"}) 15 | } 16 | 17 | // Description returns the description of go lint 18 | func (g GoVet) Description() string { 19 | return `go vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string.` 20 | } 21 | -------------------------------------------------------------------------------- /check/gocyclo.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | type GoCyclo struct { 4 | Dir string 5 | Filenames []string 6 | } 7 | 8 | func (g GoCyclo) Name() string { 9 | return "gocyclo" 10 | } 11 | 12 | // Percentage returns the percentage of .go files that pass gofmt 13 | func (g GoCyclo) Percentage() (float64, []FileSummary, error) { 14 | return GoTool(g.Dir, g.Filenames, []string{"gocyclo", "-over", "10"}) 15 | } 16 | 17 | // Description returns the description of GoCyclo 18 | func (g GoCyclo) Description() string { 19 | return `Gocyclo calculates cyclomatic complexities of functions in Go source code. 20 | 21 | The cyclomatic complexity of a function is calculated according to the following rules: 22 | 23 | 1 is the base complexity of a function 24 | +1 for each 'if', 'for', 'case', '&&' or '||'` 25 | } 26 | -------------------------------------------------------------------------------- /check/gofmt.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | type GoFmt struct { 4 | Dir string 5 | Filenames []string 6 | } 7 | 8 | func (g GoFmt) Name() string { 9 | return "gofmt" 10 | } 11 | 12 | // Percentage returns the percentage of .go files that pass gofmt 13 | func (g GoFmt) Percentage() (float64, []FileSummary, error) { 14 | return GoTool(g.Dir, g.Filenames, []string{"gofmt", "-s", "-l"}) 15 | } 16 | 17 | // Description returns the description of gofmt 18 | func (g GoFmt) Description() string { 19 | return `Gofmt formats Go programs. We run gofmt -s on your code, where -s is for the "simplify" command` 20 | } 21 | -------------------------------------------------------------------------------- /check/goget.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "os/exec" 7 | ) 8 | 9 | func isOk(err error) bool { 10 | if err == nil { 11 | return true 12 | } else { 13 | return false 14 | } 15 | } 16 | 17 | type Package struct { 18 | Dir string 19 | DepsErrors []*PackageError 20 | } 21 | 22 | // A PackageError describes an error loading information about a package. 23 | type PackageError struct { 24 | ImportStack []string // shortest path from package named on command line to this one 25 | Pos string // position of error 26 | Err string // the error itself 27 | isImportCycle bool // the error is an import cycle 28 | hard bool // whether the error is soft or hard; soft errors are ignored in some places 29 | } 30 | 31 | func (p *Package) GetDependencies() error { 32 | depList := p.ListPendingDependencies() 33 | for _, dep := range depList { 34 | err := GetPackage(dep) 35 | if !isOk(err) { 36 | return err 37 | } 38 | } 39 | return nil 40 | } 41 | 42 | func (p *Package) ListPendingDependencies() []string { 43 | out, err := exec.Command("go", "list", "-json", p.Dir).Output() 44 | if !isOk(err) { 45 | log.Fatal(err) 46 | } 47 | pInfo := Package{} 48 | err = json.Unmarshal(out, &pInfo) 49 | if !isOk(err) { 50 | log.Fatal(err) 51 | } 52 | pList := []string{} 53 | for _, depErr := range pInfo.DepsErrors { 54 | pList = append(pList, depErr.ImportStack[len(depErr.ImportStack)-1]) 55 | } 56 | return pList 57 | } 58 | 59 | func GetPackage(path string) error { 60 | _, err := exec.Command("go", "get", path).CombinedOutput() 61 | return err 62 | } 63 | -------------------------------------------------------------------------------- /check/goget_test.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetPackage(t *testing.T) { 9 | //working example 10 | path := "github.com/nsf/gocode" 11 | err := GetPackage(path) 12 | if err != nil { 13 | t.Error(err) 14 | } 15 | 16 | path = "githubcom/nsf/gocode" 17 | err = GetPackage(path) 18 | if err == nil { 19 | t.Error("Should fail for non-existent repo") 20 | } 21 | } 22 | 23 | func TestListPendingDependencies(t *testing.T) { 24 | path := "./test/missing_dependencies" 25 | p := Package{Dir: path} 26 | 27 | dList := p.ListPendingDependencies() 28 | if len(dList) == 0 { 29 | t.Error("Dependency list should be non-empty") 30 | } 31 | for _, dep := range dList { 32 | fmt.Println(string(dep)) 33 | } 34 | } 35 | 36 | func TestGetDependencies(t *testing.T) { 37 | path := "./test/missing_dependencies" 38 | p := Package{Dir: path} 39 | err := p.GetDependencies() 40 | if err != nil { 41 | t.Error(err) 42 | } 43 | 44 | dList := p.ListPendingDependencies() 45 | if len(dList) > 0 { 46 | t.Error("There should be no more dependencies") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /check/golint.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | type GoLint struct { 4 | Dir string 5 | Filenames []string 6 | } 7 | 8 | func (g GoLint) Name() string { 9 | return "golint" 10 | } 11 | 12 | // Percentage returns the percentage of .go files that pass golint 13 | func (g GoLint) Percentage() (float64, []FileSummary, error) { 14 | return GoTool(g.Dir, g.Filenames, []string{"golint"}) 15 | } 16 | 17 | // Description returns the description of go lint 18 | func (g GoLint) Description() string { 19 | return `Golint is a linter for Go source code.` 20 | } 21 | -------------------------------------------------------------------------------- /check/utils.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "reflect" 10 | "strconv" 11 | "strings" 12 | "syscall" 13 | ) 14 | 15 | // GoFiles returns a slice of Go filenames 16 | // in a given directory. 17 | func GoFiles(dir string) ([]string, error) { 18 | var filenames []string 19 | visit := func(fp string, fi os.FileInfo, err error) error { 20 | if strings.Contains(fp, "Godeps") { 21 | return nil 22 | } 23 | if err != nil { 24 | fmt.Println(err) // can't walk here, 25 | return nil // but continue walking elsewhere 26 | } 27 | if fi.IsDir() { 28 | return nil // not a file. ignore. 29 | } 30 | ext := filepath.Ext(fi.Name()) 31 | if ext == ".go" { 32 | filenames = append(filenames, fp) 33 | } 34 | return nil 35 | } 36 | 37 | err := filepath.Walk(dir, visit) 38 | 39 | return filenames, err 40 | } 41 | 42 | // lineCount returns the number of lines in a given file 43 | func lineCount(filepath string) (int, error) { 44 | out, err := exec.Command("wc", "-l", filepath).Output() 45 | if err != nil { 46 | return 0, err 47 | } 48 | // wc output is like: 999 filename.go 49 | count, err := strconv.Atoi(strings.Split(strings.TrimSpace(string(out)), " ")[0]) 50 | if err != nil { 51 | return 0, err 52 | } 53 | 54 | return count, nil 55 | } 56 | 57 | type Error struct { 58 | LineNumber int `json:"line_number"` 59 | ErrorString string `json:"error_string"` 60 | } 61 | 62 | type FileSummary struct { 63 | Filename string `json:"filename"` 64 | FileURL string `json:"file_url"` 65 | Errors []Error `json:"errors"` 66 | } 67 | 68 | // ByFilename implements sort.Interface for []Person based on 69 | // the Age field. 70 | type ByFilename []FileSummary 71 | 72 | func (a ByFilename) Len() int { return len(a) } 73 | func (a ByFilename) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 74 | func (a ByFilename) Less(i, j int) bool { return a[i].Filename < a[j].Filename } 75 | 76 | func getFileSummary(filename, dir, cmd, out string) (FileSummary, error) { 77 | filename = strings.TrimPrefix(filename, "repos/src") 78 | githubLink := strings.TrimPrefix(dir, "repos/src") 79 | fileURL := "https://" + strings.TrimPrefix(dir, "repos/src/") + "/blob/master" + strings.TrimPrefix(filename, githubLink) 80 | fs := FileSummary{ 81 | Filename: filename, 82 | FileURL: fileURL, 83 | } 84 | split := strings.Split(string(out), "\n") 85 | for _, sp := range split[0 : len(split)-1] { 86 | parts := strings.Split(sp, ":") 87 | msg := sp 88 | if cmd != "gocyclo" { 89 | msg = parts[len(parts)-1] 90 | } 91 | e := Error{ErrorString: msg} 92 | switch cmd { 93 | case "golint", "gocyclo", "vet": 94 | ln, err := strconv.Atoi(strings.Split(sp, ":")[1]) 95 | if err != nil { 96 | return fs, err 97 | } 98 | e.LineNumber = ln 99 | } 100 | 101 | fs.Errors = append(fs.Errors, e) 102 | } 103 | 104 | return fs, nil 105 | } 106 | 107 | // GoTool runs a given go command (for example gofmt, go tool vet) 108 | // on a directory 109 | func GoTool(dir string, filenames, command []string) (float64, []FileSummary, error) { 110 | var failed = []FileSummary{} 111 | for _, fi := range filenames { 112 | params := command[1:] 113 | params = append(params, fi) 114 | 115 | cmd := exec.Command(command[0], params...) 116 | stdout, err := cmd.StdoutPipe() 117 | if err != nil { 118 | return 0, []FileSummary{}, nil 119 | } 120 | 121 | stderr, err := cmd.StderrPipe() 122 | if err != nil { 123 | return 0, []FileSummary{}, nil 124 | } 125 | 126 | err = cmd.Start() 127 | if err != nil { 128 | return 0, []FileSummary{}, nil 129 | } 130 | 131 | out, err := ioutil.ReadAll(stdout) 132 | if err != nil { 133 | return 0, []FileSummary{}, nil 134 | } 135 | 136 | errout, err := ioutil.ReadAll(stderr) 137 | if err != nil { 138 | return 0, []FileSummary{}, nil 139 | } 140 | 141 | if string(out) != "" { 142 | fs, err := getFileSummary(fi, dir, command[0], string(out)) 143 | if err != nil { 144 | return 0, []FileSummary{}, nil 145 | } 146 | failed = append(failed, fs) 147 | } 148 | 149 | // go vet logs to stderr 150 | if string(errout) != "" { 151 | cmd := command[0] 152 | if reflect.DeepEqual(command, []string{"go", "tool", "vet"}) { 153 | cmd = "vet" 154 | } 155 | fs, err := getFileSummary(fi, dir, cmd, string(errout)) 156 | if err != nil { 157 | return 0, []FileSummary{}, nil 158 | } 159 | failed = append(failed, fs) 160 | } 161 | 162 | err = cmd.Wait() 163 | if exitErr, ok := err.(*exec.ExitError); ok { 164 | // The program has exited with an exit code != 0 165 | 166 | if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 167 | // some commands exit 1 when files fail to pass (for example go vet) 168 | if status.ExitStatus() != 1 { 169 | return 0, failed, err 170 | // return 0, Error{}, err 171 | } 172 | } 173 | } 174 | 175 | } 176 | 177 | if len(filenames) == 1 { 178 | lc, err := lineCount(filenames[0]) 179 | if err != nil { 180 | return 0, failed, err 181 | } 182 | 183 | var errors int 184 | if len(failed) != 0 { 185 | errors = len(failed[0].Errors) 186 | } 187 | 188 | return float64(lc-errors) / float64(lc), failed, nil 189 | } 190 | 191 | return float64(len(filenames)-len(failed)) / float64(len(filenames)), failed, nil 192 | } 193 | -------------------------------------------------------------------------------- /containers.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta2 2 | containers: 3 | - name: goreport 4 | image: legogris/goreport 5 | ports: 6 | - name: http 7 | hostPort: 80 8 | containerPort: 8080 9 | 10 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/exec" 10 | "regexp" 11 | "strings" 12 | "time" 13 | 14 | "github.com/gophergala/go_report/check" 15 | "gopkg.in/mgo.v2" 16 | "labix.org/v2/mgo/bson" 17 | ) 18 | 19 | var ( 20 | mongoURL = "mongodb://localhost:27017" 21 | mongoDatabase = "goreportcard" 22 | mongoCollection = "reports" 23 | ) 24 | 25 | func getMongoCollection() (*mgo.Collection, error) { 26 | session, err := mgo.Dial(mongoURL) 27 | if err != nil { 28 | return nil, err 29 | } 30 | c := session.DB(mongoDatabase).C(mongoCollection) 31 | return c, nil 32 | } 33 | 34 | func homeHandler(w http.ResponseWriter, r *http.Request) { 35 | log.Println("Serving home page") 36 | if r.URL.Path[1:] == "" { 37 | http.ServeFile(w, r, "templates/home.html") 38 | } else { 39 | http.NotFound(w, r) 40 | } 41 | } 42 | 43 | func assetsHandler(w http.ResponseWriter, r *http.Request) { 44 | log.Println("Serving " + r.URL.Path[1:]) 45 | http.ServeFile(w, r, r.URL.Path[1:]) 46 | } 47 | 48 | func orgRepoNames(url string) (string, string) { 49 | dir := strings.TrimSuffix(url, ".git") 50 | split := strings.Split(dir, "/") 51 | org := split[len(split)-2] 52 | repoName := split[len(split)-1] 53 | 54 | return org, repoName 55 | } 56 | 57 | func dirName(url string) string { 58 | org, repoName := orgRepoNames(url) 59 | 60 | return fmt.Sprintf("repos/src/github.com/%s/%s", org, repoName) 61 | } 62 | 63 | func clone(url string) error { 64 | org, _ := orgRepoNames(url) 65 | if err := os.Mkdir(fmt.Sprintf("repos/src/github.com/%s", org), 0755); err != nil && !os.IsExist(err) { 66 | return fmt.Errorf("could not create dir: %v", err) 67 | } 68 | dir := dirName(url) 69 | _, err := os.Stat(dir) 70 | if os.IsNotExist(err) { 71 | cmd := exec.Command("git", "clone", "--depth", "1", "--single-branch", url, dir) 72 | if err := cmd.Run(); err != nil { 73 | return fmt.Errorf("could not run git clone: %v", err) 74 | } 75 | } else if err != nil { 76 | return fmt.Errorf("could not stat dir: %v", err) 77 | } else { 78 | cmd := exec.Command("git", "-C", dir, "pull") 79 | if err := cmd.Run(); err != nil { 80 | return fmt.Errorf("could not pull repo: %v", err) 81 | } 82 | } 83 | 84 | return nil 85 | } 86 | 87 | type score struct { 88 | Name string `json:"name"` 89 | Description string `json:"description"` 90 | FileSummaries []check.FileSummary `json:"file_summaries"` 91 | Percentage float64 `json:"percentage"` 92 | } 93 | 94 | type checksResp struct { 95 | Checks []score `json:"checks"` 96 | Average float64 `json:"average"` 97 | Files int `json:"files"` 98 | Issues int `json:"issues"` 99 | Repo string `json:"repo"` 100 | LastRefresh time.Time `json:"last_refresh"` 101 | } 102 | 103 | func checkHandler(w http.ResponseWriter, r *http.Request) { 104 | repo := r.FormValue("repo") 105 | url := repo 106 | if !strings.HasPrefix(url, "https://github.com/") { 107 | url = "https://github.com/" + url 108 | } 109 | 110 | w.Header().Set("Content-Type", "application/json") 111 | 112 | // if this is a GET request, fetch from cached version in mongo 113 | if r.Method == "GET" { 114 | // try and fetch from mongo 115 | coll, err := getMongoCollection() 116 | if err != nil { 117 | log.Println("Failed to get mongo collection during GET: ", err) 118 | } else { 119 | resp := checksResp{} 120 | err := coll.Find(bson.M{"repo": repo}).One(&resp) 121 | if err != nil { 122 | log.Println("Failed to fetch from mongo: ", err) 123 | } else { 124 | resp.LastRefresh = resp.LastRefresh.UTC() 125 | b, err := json.Marshal(resp) 126 | if err != nil { 127 | log.Println("ERROR: could not marshal json:", err) 128 | http.Error(w, err.Error(), 500) 129 | return 130 | } 131 | w.Write(b) 132 | log.Println("Loaded from cache!", repo) 133 | return 134 | } 135 | } 136 | } 137 | 138 | err := clone(url) 139 | if err != nil { 140 | log.Println("ERROR: could not clone repo: ", err) 141 | http.Error(w, fmt.Sprintf("Could not clone repo: %v", err), 500) 142 | return 143 | } 144 | 145 | dir := dirName(url) 146 | filenames, err := check.GoFiles(dir) 147 | if err != nil { 148 | log.Println("ERROR: could not get filenames: ", err) 149 | http.Error(w, fmt.Sprintf("Could not get filenames: %v", err), 500) 150 | return 151 | } 152 | checks := []check.Check{check.GoFmt{Dir: dir, Filenames: filenames}, 153 | check.GoVet{Dir: dir, Filenames: filenames}, 154 | check.GoLint{Dir: dir, Filenames: filenames}, 155 | check.GoCyclo{Dir: dir, Filenames: filenames}, 156 | } 157 | 158 | ch := make(chan score) 159 | for _, c := range checks { 160 | go func(c check.Check) { 161 | p, summaries, err := c.Percentage() 162 | if err != nil { 163 | log.Printf("ERROR: (%s) %v", c.Name(), err) 164 | } 165 | s := score{ 166 | Name: c.Name(), 167 | Description: c.Description(), 168 | FileSummaries: summaries, 169 | Percentage: p, 170 | } 171 | ch <- s 172 | }(c) 173 | } 174 | 175 | resp := checksResp{Repo: repo, 176 | Files: len(filenames), 177 | LastRefresh: time.Now().UTC()} 178 | var avg float64 179 | var issues int = 0 180 | for i := 0; i < len(checks); i++ { 181 | s := <-ch 182 | resp.Checks = append(resp.Checks, s) 183 | avg += s.Percentage 184 | for _, fs := range s.FileSummaries { 185 | issues += len(fs.Errors) 186 | } 187 | } 188 | 189 | resp.Average = avg / float64(len(checks)) 190 | resp.Issues = issues 191 | 192 | b, err := json.Marshal(resp) 193 | if err != nil { 194 | log.Println("ERROR: could not marshal json:", err) 195 | http.Error(w, err.Error(), 500) 196 | return 197 | } 198 | w.Write(b) 199 | 200 | // write to mongo 201 | coll, err := getMongoCollection() 202 | if err != nil { 203 | log.Println("Failed to get mongo collection: ", err) 204 | } else { 205 | log.Println("Writing to mongo...") 206 | _, err := coll.Upsert(bson.M{"repo": repo}, resp) 207 | if err != nil { 208 | log.Println("Mongo writing error:", err) 209 | } 210 | } 211 | } 212 | 213 | func reportHandler(w http.ResponseWriter, r *http.Request, org, repo string) { 214 | http.ServeFile(w, r, "templates/home.html") 215 | } 216 | 217 | func makeReportHandler(fn func(http.ResponseWriter, *http.Request, string, string)) http.HandlerFunc { 218 | return func(w http.ResponseWriter, r *http.Request) { 219 | validPath := regexp.MustCompile(`^/report/([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)$`) 220 | 221 | m := validPath.FindStringSubmatch(r.URL.Path) 222 | if m == nil { 223 | http.NotFound(w, r) 224 | return 225 | } 226 | fn(w, r, m[1], m[2]) 227 | } 228 | } 229 | 230 | func main() { 231 | if err := os.MkdirAll("repos/src/github.com", 0755); err != nil && !os.IsExist(err) { 232 | log.Fatal("ERROR: could not create repos dir: ", err) 233 | } 234 | 235 | http.HandleFunc("/assets/", assetsHandler) 236 | http.HandleFunc("/checks", checkHandler) 237 | http.HandleFunc("/report/", makeReportHandler(reportHandler)) 238 | http.HandleFunc("/", homeHandler) 239 | 240 | fmt.Println("Running on :8080...") 241 | log.Fatal(http.ListenAndServe(":8080", nil)) 242 | } 243 | -------------------------------------------------------------------------------- /scripts/config.sh: -------------------------------------------------------------------------------- 1 | # This file holds common parameters for the VM start/stop scripts 2 | 3 | VM_NAME=goreport 4 | ZONE=asia-east1-a 5 | MACHINE_TYPE=f1-micro 6 | 7 | function wait_vm_ready() { 8 | VM_IP=$(gcloud compute instances list ${VM_NAME} --format=text | grep natIP | cut -f 2 -d ':' | tr -d ' ') 9 | echo 10 | echo "Waiting for VM to be ready on ${VM_IP}." 11 | echo 12 | echo " This will continually check to see if the app is up and " 13 | echo " running. If something goes wrong this could hang forever." 14 | echo 15 | until $(curl --output /dev/null --silent --head --max-time 1 --fail "http://${VM_IP}"); do 16 | printf "." 17 | sleep 2 18 | done 19 | 20 | echo 21 | echo "Container VM is now up and running" 22 | echo 23 | echo " http://${VM_IP}" 24 | echo 25 | } 26 | -------------------------------------------------------------------------------- /scripts/restart-containers.sh: -------------------------------------------------------------------------------- 1 | 2 | #! /bin/bash 3 | # Modified from https://github.com/GoogleCloudPlatform/container-vm-guestbook-redis-python/blob/master/config.sh 4 | # Run from project root 5 | 6 | set -e 7 | 8 | source scripts/config.sh 9 | 10 | 11 | echo 12 | echo "Setting new containers into VM" 13 | gcloud compute instances add-metadata --zone ${ZONE} ${VM_NAME} \ 14 | --metadata-from-file google-container-manifest=containers.yaml 15 | 16 | echo 17 | echo "Rebooting VM to restart containers" 18 | gcloud compute instances reset --zone ${ZONE} ${VM_NAME} 19 | 20 | wait_vm_ready 21 | -------------------------------------------------------------------------------- /templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Go Report Card | A Gopher Gala Hackathon Product 8 | 9 | 10 | 20 | 142 | 143 | 144 |
145 |
146 |
147 |
148 |
149 |
150 |

Go Report Card

151 |

Enter the Github URL below to generate a report on the quality of the Go code in the project

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 | 186 |
187 | 202 | 203 | 204 | 212 | 222 | 236 | 262 | 265 | 443 | 444 | 445 | --------------------------------------------------------------------------------