├── .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 | 
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 |
Enter the Github URL below to generate a report on the quality of the Go code in the project
152 |