| // Copyright 2020 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package controllers |
| |
| import ( |
| "fmt" |
| "net/http" |
| "os" |
| "strconv" |
| "strings" |
| "text/template" |
| |
| "cos.googlesource.com/cos/tools/src/pkg/changelog" |
| |
| log "github.com/sirupsen/logrus" |
| ) |
| |
| const ( |
| subjectLen int = 100 |
| ) |
| |
| var ( |
| internalInstance string |
| internalManifestRepo string |
| externalInstance string |
| externalManifestRepo string |
| envQuerySize string |
| staticBasePath string |
| indexTemplate *template.Template |
| changelogTemplate *template.Template |
| promptLoginTemplate *template.Template |
| ) |
| |
| func init() { |
| internalInstance = os.Getenv("COS_INTERNAL_INSTANCE") |
| internalManifestRepo = os.Getenv("COS_INTERNAL_MANIFEST_REPO") |
| externalInstance = os.Getenv("COS_EXTERNAL_INSTANCE") |
| externalManifestRepo = os.Getenv("COS_EXTERNAL_MANIFEST_REPO") |
| envQuerySize = getIntVerifiedEnv("CHANGELOG_QUERY_SIZE") |
| staticBasePath = os.Getenv("STATIC_BASE_PATH") |
| indexTemplate = template.Must(template.ParseFiles(staticBasePath + "templates/index.html")) |
| changelogTemplate = template.Must(template.ParseFiles(staticBasePath + "templates/changelog.html")) |
| promptLoginTemplate = template.Must(template.ParseFiles(staticBasePath + "templates/promptLogin.html")) |
| } |
| |
| type changelogData struct { |
| Instance string |
| Source string |
| Target string |
| Additions map[string]*changelog.RepoLog |
| Removals map[string]*changelog.RepoLog |
| Internal bool |
| } |
| |
| type changelogPage struct { |
| Source string |
| Target string |
| QuerySize string |
| RepoTables []*repoTable |
| Internal bool |
| } |
| |
| type repoTable struct { |
| Name string |
| Additions []*repoTableEntry |
| Removals []*repoTableEntry |
| AdditionsLink string |
| RemovalsLink string |
| } |
| |
| type repoTableEntry struct { |
| IsAddition bool |
| SHA *shaAttr |
| Subject string |
| Bugs []*bugAttr |
| AuthorName string |
| CommitterName string |
| CommitTime string |
| ReleaseNote string |
| } |
| |
| type shaAttr struct { |
| Name string |
| URL string |
| } |
| |
| type bugAttr struct { |
| Name string |
| URL string |
| } |
| |
| type promptLoginPage struct { |
| ActivePage string |
| } |
| |
| // getIntVerifiedEnv retrieves an environment variable but checks that it can be |
| // converted to int first |
| func getIntVerifiedEnv(envName string) string { |
| output := os.Getenv(envName) |
| if _, err := strconv.Atoi(output); err != nil { |
| log.Errorf("getEnvAsInt: Failed to parse env variable %s with value %s: %v", |
| envName, os.Getenv(output), err) |
| } |
| return output |
| } |
| |
| func gobCommitLink(instance, repo, SHA string) string { |
| return fmt.Sprintf("https://%s/%s/+/%s", instance, repo, SHA) |
| } |
| |
| func gobDiffLink(instance, repo, sourceSHA, targetSHA string) string { |
| return fmt.Sprintf("https://%s/%s/+log/%s..%s?n=10000", instance, repo, sourceSHA, targetSHA) |
| } |
| |
| func createRepoTableEntry(instance string, repo string, commit *changelog.Commit, isAddition bool) *repoTableEntry { |
| entry := new(repoTableEntry) |
| entry.IsAddition = isAddition |
| entry.SHA = &shaAttr{Name: commit.SHA[:8], URL: gobCommitLink(instance, repo, commit.SHA)} |
| entry.Subject = commit.Subject |
| if len(entry.Subject) > subjectLen { |
| entry.Subject = entry.Subject[:subjectLen] |
| } |
| entry.Bugs = make([]*bugAttr, len(commit.Bugs)) |
| for i, bugURL := range commit.Bugs { |
| name := bugURL[strings.Index(bugURL, "/")+1:] |
| entry.Bugs[i] = &bugAttr{Name: name, URL: "http://" + bugURL} |
| } |
| entry.AuthorName = commit.AuthorName |
| entry.CommitterName = commit.CommitterName |
| entry.CommitTime = commit.CommitTime |
| entry.ReleaseNote = commit.ReleaseNote |
| return entry |
| } |
| |
| func createChangelogPage(data changelogData) *changelogPage { |
| page := &changelogPage{Source: data.Source, Target: data.Target, QuerySize: envQuerySize, Internal: data.Internal} |
| for repoName, repoLog := range data.Additions { |
| table := &repoTable{Name: repoName} |
| for _, commit := range repoLog.Commits { |
| tableEntry := createRepoTableEntry(data.Instance, repoName, commit, true) |
| table.Additions = append(table.Additions, tableEntry) |
| } |
| if _, ok := data.Removals[repoName]; ok { |
| for _, commit := range data.Removals[repoName].Commits { |
| tableEntry := createRepoTableEntry(data.Instance, repoName, commit, false) |
| table.Removals = append(table.Removals, tableEntry) |
| } |
| if data.Removals[repoName].HasMoreCommits { |
| table.RemovalsLink = gobDiffLink(data.Instance, repoName, repoLog.TargetSHA, repoLog.SourceSHA) |
| } |
| } |
| if repoLog.HasMoreCommits { |
| table.AdditionsLink = gobDiffLink(data.Instance, repoName, repoLog.SourceSHA, repoLog.TargetSHA) |
| } |
| page.RepoTables = append(page.RepoTables, table) |
| } |
| // Add remaining repos that had removals but no additions |
| for repoName, repoLog := range data.Removals { |
| if _, ok := data.Additions[repoName]; ok { |
| continue |
| } |
| table := &repoTable{Name: repoName} |
| for _, commit := range repoLog.Commits { |
| tableEntry := createRepoTableEntry(data.Instance, repoName, commit, false) |
| table.Removals = append(table.Removals, tableEntry) |
| } |
| page.RepoTables = append(page.RepoTables, table) |
| if repoLog.HasMoreCommits { |
| table.RemovalsLink = gobDiffLink(data.Instance, repoName, repoLog.TargetSHA, repoLog.SourceSHA) |
| } |
| } |
| return page |
| } |
| |
| // HandleIndex serves the home page |
| func HandleIndex(w http.ResponseWriter, r *http.Request) { |
| indexTemplate.Execute(w, nil) |
| } |
| |
| // HandleChangelog serves the changelog page |
| func HandleChangelog(w http.ResponseWriter, r *http.Request) { |
| if err := r.ParseForm(); err != nil { |
| changelogTemplate.Execute(w, &changelogPage{QuerySize: envQuerySize}) |
| return |
| } |
| httpClient, err := HTTPClient(r) |
| if err != nil { |
| log.Debug(err) |
| err = promptLoginTemplate.Execute(w, &promptLoginPage{ActivePage: "changelog"}) |
| if err != nil { |
| log.Errorf("HandleChangelog: error executing promptLogin template: %v", err) |
| } |
| return |
| } |
| source := r.FormValue("source") |
| target := r.FormValue("target") |
| // If no source/target values specified in request, display empty changelog page |
| if source == "" || target == "" { |
| changelogTemplate.Execute(w, &changelogPage{QuerySize: envQuerySize, Internal: true}) |
| return |
| } |
| querySize, err := strconv.Atoi(r.FormValue("n")) |
| if err != nil { |
| querySize, _ = strconv.Atoi(envQuerySize) |
| } |
| internal, instance, manifestRepo := false, externalInstance, externalManifestRepo |
| if r.FormValue("internal") == "true" { |
| internal, instance, manifestRepo = true, internalInstance, internalManifestRepo |
| } |
| added, removed, err := changelog.Changelog(httpClient, source, target, instance, manifestRepo, querySize) |
| if err != nil { |
| log.Errorf("HandleChangelog: error retrieving changelog between builds %s and %s on GoB instance: %s with manifest repository: %s\n%v\n", |
| source, target, externalInstance, externalManifestRepo, err) |
| http.Error(w, err.Error(), http.StatusInternalServerError) |
| return |
| } |
| page := createChangelogPage(changelogData{ |
| Instance: instance, |
| Source: source, |
| Target: target, |
| Additions: added, |
| Removals: removed, |
| Internal: internal, |
| }) |
| err = changelogTemplate.Execute(w, page) |
| if err != nil { |
| log.Errorf("HandleChangelog: error executing changelog template: %v", err) |
| } |
| } |