Merge "cos_image_analyzer: Milestone 3 - Rootfs, Stateful, OS-Configs, and Partition-Structure difference"
diff --git a/go.mod b/go.mod
index 35e1b01..83152aa 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/google/go-cmp v0.5.1
github.com/google/subcommands v1.2.0
+ github.com/gorilla/sessions v1.2.0
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.6.0
diff --git a/go.sum b/go.sum
index d6ed7da..a6e9e61 100644
--- a/go.sum
+++ b/go.sum
@@ -52,7 +52,6 @@
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -118,6 +117,10 @@
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
+github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -130,17 +133,13 @@
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -166,7 +165,6 @@
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
diff --git a/src/cmd/changelog-webapp/controllers/authHandlers.go b/src/cmd/changelog-webapp/controllers/authHandlers.go
new file mode 100644
index 0000000..56785cf
--- /dev/null
+++ b/src/cmd/changelog-webapp/controllers/authHandlers.go
@@ -0,0 +1,140 @@
+// 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 (
+ "context"
+ "fmt"
+ "math/rand"
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/gorilla/sessions"
+ log "github.com/sirupsen/logrus"
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
+)
+
+const (
+ // Session variables
+ sessionName = "changelog"
+ sessionKeyLength = 32
+
+ // Oauth state generation variables
+ oauthStateCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
+ oauthStateLength = 16
+)
+
+var config = &oauth2.Config{
+ ClientID: os.Getenv("OAUTH_CLIENT_ID"),
+ ClientSecret: os.Getenv("OAUTH_CLIENT_SECRET"),
+ Endpoint: google.Endpoint,
+ RedirectURL: "https://cos-oss-interns-playground.uc.r.appspot.com/oauth2callback/",
+ Scopes: []string{"https://www.googleapis.com/auth/gerritcodereview"},
+}
+
+var store = sessions.NewCookieStore([]byte(randomString(sessionKeyLength)))
+
+func randomString(stringSize int) string {
+ randWithSeed := rand.New(rand.NewSource(time.Now().UnixNano()))
+ stateArr := make([]byte, stringSize)
+ for i := range stateArr {
+ stateArr[i] = oauthStateCharset[randWithSeed.Intn(len(oauthStateCharset))]
+ }
+ return string(stateArr)
+}
+
+// HTTPClient creates an authorized HTTP Client
+func HTTPClient(r *http.Request) (*http.Client, error) {
+ var parsedExpiry time.Time
+ session, err := store.Get(r, sessionName)
+ if err != nil {
+ return nil, fmt.Errorf("HTTPClient: No session found with sessionName %s", sessionName)
+ }
+ for _, key := range []string{"accessToken", "refreshToken", "tokenType", "expiry"} {
+ if val, ok := session.Values[key]; !ok || val == nil {
+ return nil, fmt.Errorf("HTTPClient: Session missing key %s", key)
+ }
+ }
+ if parsedExpiry, err = time.Parse(time.RFC3339, session.Values["expiry"].(string)); err != nil {
+ return nil, fmt.Errorf("HTTPClient: Token expiry is in an incorrect format")
+ }
+ token := &oauth2.Token{
+ AccessToken: session.Values["accessToken"].(string),
+ RefreshToken: session.Values["refreshToken"].(string),
+ TokenType: session.Values["tokenType"].(string),
+ Expiry: parsedExpiry,
+ }
+ return config.Client(context.Background(), token), nil
+}
+
+// HandleLogin handles login
+func HandleLogin(w http.ResponseWriter, r *http.Request) {
+ state := randomString(oauthStateLength)
+ session, _ := store.Get(r, sessionName)
+ session.Values["oauthState"] = state
+ err := session.Save(r, w)
+ if err != nil {
+ log.Errorf("HandleLogin: Error saving key: %v", err)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ authURL := config.AuthCodeURL(state, oauth2.AccessTypeOffline)
+ http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
+}
+
+// HandleCallback handles callback
+func HandleCallback(w http.ResponseWriter, r *http.Request) {
+ if err := r.ParseForm(); err != nil {
+ log.Errorf("Could not parse query: %v\n", err)
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ authCode := r.FormValue("code")
+ callbackState := r.FormValue("state")
+
+ session, err := store.Get(r, sessionName)
+ if err != nil {
+ log.Errorf("HandleCallback: Error retrieving session: %v", err)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if callbackState != session.Values["oauthState"].(string) {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ token, err := config.Exchange(context.Background(), authCode)
+ if err != nil {
+ log.Errorf("HandleCallback: Error exchanging token: %v", token)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ session.Values["accessToken"] = token.AccessToken
+ session.Values["refreshToken"] = token.RefreshToken
+ session.Values["tokenType"] = token.TokenType
+ session.Values["expiry"] = token.Expiry.Format(time.RFC3339)
+ err = session.Save(r, w)
+ if err != nil {
+ log.Errorf("HandleCallback: Error saving session: %v", err)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ http.Redirect(w, r, "/", http.StatusPermanentRedirect)
+}
diff --git a/src/cmd/changelog-webapp/controllers/pageHandlers.go b/src/cmd/changelog-webapp/controllers/pageHandlers.go
new file mode 100644
index 0000000..2304be7
--- /dev/null
+++ b/src/cmd/changelog-webapp/controllers/pageHandlers.go
@@ -0,0 +1,241 @@
+// 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)
+ }
+}
diff --git a/src/cmd/changelog-webapp/main.go b/src/cmd/changelog-webapp/main.go
new file mode 100644
index 0000000..fb95de8
--- /dev/null
+++ b/src/cmd/changelog-webapp/main.go
@@ -0,0 +1,54 @@
+// 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 main
+
+import (
+ "net/http"
+ "os"
+
+ "cos.googlesource.com/cos/tools/src/cmd/changelog-webapp/controllers"
+
+ log "github.com/sirupsen/logrus"
+)
+
+var (
+ staticBasePath string
+ port string
+)
+
+func init() {
+ staticBasePath = os.Getenv("STATIC_BASE_PATH")
+ port = os.Getenv("PORT")
+}
+
+func main() {
+ log.SetLevel(log.DebugLevel)
+
+ http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticBasePath))))
+ http.HandleFunc("/", controllers.HandleIndex)
+ http.HandleFunc("/login/", controllers.HandleLogin)
+ http.HandleFunc("/changelog/", controllers.HandleChangelog)
+ http.HandleFunc("/oauth2callback/", controllers.HandleCallback)
+
+ if port == "" {
+ port = "8081"
+ log.Printf("Defaulting to port %s", port)
+ }
+
+ log.Printf("Listening on port %s", port)
+ if err := http.ListenAndServe(":"+port, nil); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/src/cmd/changelog-webapp/static/css/base.css b/src/cmd/changelog-webapp/static/css/base.css
new file mode 100644
index 0000000..1180143
--- /dev/null
+++ b/src/cmd/changelog-webapp/static/css/base.css
@@ -0,0 +1,87 @@
+html,
+body {
+ background-color: #f8f8f8;
+ font-family: 'Roboto', 'Noto', sans-serif;
+ font-weight: 300;
+ margin: 0;
+ overflow-x: hidden;
+ padding: 0;
+}
+
+h1 {
+ font-size: 24;
+}
+
+h2 {
+ font-size: 18;
+}
+
+p,
+p a {
+ font-size: 16;
+}
+
+a:link,
+a:visited {
+ color: blue;
+ text-decoration: none;
+}
+
+label {
+ font-size: 16;
+}
+
+.navbar {
+ align-items: center;
+ background-color: #4285f4;
+ display: flex;
+ height: 50px;
+ padding: 0px 25px;
+ width: 100%;
+}
+
+.navbar-title {
+ color: #f8f8f8;
+ font-size: 24;
+ font-weight: 350;
+}
+
+.sidenav {
+ height: 100%;
+ left: 0;
+ margin-top: 50px;
+ overflow-x: hidden;
+ padding-top: 30px;
+ position: fixed;
+ top: 0;
+ width: 120px;
+ z-index: 1;
+}
+
+.sidenav a {
+ color: #272727;
+ display: block;
+ font-size: 16px;
+ padding: 0px 8px 8px 26px;
+ text-decoration: none;
+ transition: 0.2s;
+}
+
+.sidenav a.active {
+ color: #4285f4;
+ font-weight: bold;
+}
+
+.sidenav a:hover {
+ color: #3d3d3d;
+ font-weight: 550;
+}
+
+.main {
+ margin: 30px 25px 0px 155px;
+}
+
+.text-content {
+ margin: auto;
+ max-width: 700px;
+}
\ No newline at end of file
diff --git a/src/cmd/changelog-webapp/static/css/changelog.css b/src/cmd/changelog-webapp/static/css/changelog.css
new file mode 100644
index 0000000..4a7ba7e
--- /dev/null
+++ b/src/cmd/changelog-webapp/static/css/changelog.css
@@ -0,0 +1,110 @@
+th {
+ font-weight: 440;
+ text-align: left;
+ padding-bottom: 0px;
+}
+
+th,
+td {
+ margin-bottom: 5px;
+ padding: 2px;
+}
+
+.changelog-form .text input {
+ height: 24px;
+ margin-bottom: 8px;
+ width: 135px;
+}
+
+.changelog-form .text .submit {
+ height: 24px;
+ width: 59px;
+}
+
+.changelog-form .text label {
+ margin: 4px;
+}
+
+.changelog-form .radio .external {
+ margin-left: 20px;
+}
+
+.sha-legend {
+ margin-top: 10px;
+}
+
+.sha-legend .legend-row {
+ display: flex;
+ flex-flow: row nowrap;
+ font-size: 14px;
+ margin: 3px 0px 3px 4px;
+}
+
+.sha-legend .circle {
+ border-radius: 50%;
+ height: 14px;
+ margin-right: 8px;
+ width: 14px;
+}
+
+.sha-legend .circle.addition {
+ background-color: #adef97;
+}
+
+.sha-legend .circle.removal {
+ background-color: #ffc0c0;
+}
+
+.repo-header {
+ margin: 24px 0px 12px 0px;
+}
+
+.repo-table {
+ border-spacing: 2px;
+ font-size: 14;
+ font-weight: 300;
+ margin: 4px 0px;
+ table-layout: fixed;
+}
+
+.commit-sha {
+ text-align: center;
+ width: 65px;
+}
+
+th.commit-sha {
+ text-align: left;
+}
+
+.addition {
+ background-color: #c2ffae;
+}
+
+.removal {
+ background-color: #ffdada
+}
+
+.commit-subject {
+ width: 520px;
+}
+
+.commit-bugs {
+ width: 115px;
+}
+
+.commit-author {
+ width: 150px;
+}
+
+.commit-committer {
+ width: 150px;
+}
+
+.commit-time {
+ width: 120px;
+}
+
+.gob-link {
+ font-size: 14;
+ padding-left: 2px;
+}
\ No newline at end of file
diff --git a/src/cmd/changelog-webapp/static/templates/changelog.html b/src/cmd/changelog-webapp/static/templates/changelog.html
new file mode 100644
index 0000000..93a3ca0
--- /dev/null
+++ b/src/cmd/changelog-webapp/static/templates/changelog.html
@@ -0,0 +1,122 @@
+<html>
+<head>
+ <meta name="description" content="Google COS build changelog">
+ <link rel="stylesheet" href="/static/css/base.css">
+ <link rel="stylesheet" href="/static/css/changelog.css">
+</head>
+<body>
+ <div class="navbar">
+ <p class="navbar-title">Container Optimized OS</p>
+ </div>
+ <div class="sidenav">
+ <a href="/">Home</a>
+ <a class="active" href="/changelog/">Changelog</a>
+ <a href="/locatecl/">Locate CL</a>
+ <a href="/login/">Login</a>
+ </div>
+ <div class="main">
+ <h1>Search Changelog</h1>
+ <form class="changelog-form" action="/changelog">
+ <div class="text">
+ <label for="source">From </label>
+ {{if (ne .Source "")}}
+ <input type="text" class="source" name="source" placeholder="COS build number" value={{.Source}} autocomplete="off">
+ {{else}}
+ <input type="text" class="source" name="source" placeholder="COS build number" autocomplete="off">
+ {{end}}
+ <label for="target"> to </label>
+ {{if (ne .Target "")}}
+ <input type="text" class="target" name="target" placeholder="COS build number" value={{.Target}} autocomplete="off" required>
+ {{else}}
+ <input type="text" class="target" name="target" placeholder="COS build number" autocomplete="off" required>
+ {{end}}
+ <input type="hidden" name="n" value={{.QuerySize}}>
+ <input class="submit" type="submit" value="Submit"><br>
+ </div>
+ <div class="radio">
+ {{if .Internal}}
+ <input type="radio" class="internal" name="internal" value="true" checked>
+ <label for="internal"> Internal </label>
+ <input type="radio" class="external" name="internal" value="false">
+ <label for="external"> External </label>
+ {{else}}
+ <input type="radio" class="internal" name="internal" value="true">
+ <label for="internal"> Internal </label>
+ <input type="radio" class="external" name="internal" value="false" checked>
+ <label for="external"> External </label>
+ {{end}}
+ </div>
+ </form>
+ {{if (and (ne .Target "") (ne .Source ""))}}
+ <div class="sha-legend">
+ <div class="legend-row">
+ <div class="circle addition"></div>
+ <span>Commits introduced to build {{.Target}} since build {{.Source}}</span><br>
+ </div>
+ <div class="legend-row">
+ <div class="circle removal"></div>
+ <span>Commits introduced to build {{.Source}} since build {{.Target}}</span>
+ </div>
+ </div>
+ {{end}}
+ {{range $table := .RepoTables}}
+ <h2 class="repo-header"> {{$table.Name}} </h2>
+ <table class="repo-table">
+ <tr>
+ <th class="commit-sha">SHA</th>
+ <th class="commit-subject">Subject</th>
+ <th class="commit-bugs">Bugs</th>
+ <th class="commit-author">Author</th>
+ <th class="commit-committer">Committer</th>
+ <th class="commit-time">Date</th>
+ <th class="commit-release-notes">Release Notes</th>
+ </tr>
+ </table>
+ <table class="repo-table">
+ {{range $commit := $table.Additions}}
+ <tr>
+ <td class="commit-sha addition">
+ <a href={{$commit.SHA.URL}} target="_blank">{{$commit.SHA.Name}}</a>
+ </td>
+ <td class="commit-subject">{{$commit.Subject}}</td>
+ <td class="commit-bugs">
+ {{range $bugAttr := $commit.Bugs}}
+ <a href={{$bugAttr.URL}} target="_blank">{{$bugAttr.Name}}</a>
+ {{end}}
+ </td>
+ <td class="commit-author">{{$commit.AuthorName}}</td>
+ <td class="commit-committer">{{$commit.CommitterName}}</td>
+ <td class="commit-time">{{$commit.CommitTime}}</td>
+ <td class="commit-release-notes">{{$commit.ReleaseNote}}</td>
+ </tr>
+ {{end}}
+ </table>
+ {{if (ne $table.AdditionsLink "")}}
+ <a class="gob-link" href={{$table.AdditionsLink}} target="_blank">Show more commits</a>
+ {{end}}
+ <table class="repo-table">
+ {{range $commit := $table.Removals}}
+ <tr>
+ <td class="commit-sha removal">
+ <a href={{$commit.SHA.URL}} target="_blank">{{$commit.SHA.Name}}</a>
+ </td>
+ <td class="commit-subject">{{$commit.Subject}}</td>
+ <td class="commit-bugs">
+ {{range $bugAttr := $commit.Bugs}}
+ <a href={{$bugAttr.URL}} target="_blank">{{$bugAttr.Name}}</a>
+ {{end}}
+ </td>
+ <td class="commit-author">{{$commit.AuthorName}}</td>
+ <td class="commit-committer">{{$commit.CommitterName}}</td>
+ <td class="commit-time">{{$commit.CommitTime}}</td>
+ <td class="commit-release-notes">{{$commit.ReleaseNote}}</td>
+ </tr>
+ {{end}}
+ </table>
+ {{if (ne $table.RemovalsLink "")}}
+ <a class="gob-link" href={{$table.RemovalsLink}} target="_blank">Show more commits</a>
+ {{end}}
+ {{end}}
+ </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cmd/changelog-webapp/static/templates/index.html b/src/cmd/changelog-webapp/static/templates/index.html
new file mode 100644
index 0000000..69e5e69
--- /dev/null
+++ b/src/cmd/changelog-webapp/static/templates/index.html
@@ -0,0 +1,30 @@
+<html>
+<head>
+ <meta name="description" content="Google COS build information">
+ <link rel="stylesheet" href="/static/css/base.css">
+</head>
+<body>
+ <div class="navbar">
+ <p class="navbar-title">Container Optimized OS</p>
+ </div>
+ <div class="sidenav">
+ <a class="active" href="/">Home</a>
+ <a href="/changelog/">Changelog</a>
+ <a href="/locatecl/">Locate CL</a>
+ <a href="/login/">Login</a>
+ </div>
+ <div class="main">
+ <div class="text-content">
+ <h1>Container-Optimized OS</h1>
+ <h2>A small, secure, stand alone VM image for building on top of Google Cloud</h2>
+ <p>Container-Optimized OS is an operating system image for your Compute Engine VMs that is optimized for
+ running
+ Docker containers. With Container-Optimized OS, you can bring up your Docker containers on Google Cloud
+ Platform
+ quickly, efficiently, and securely. Container-Optimized OS is maintained by Google and is based on the
+ open
+ source Chromium OS project.</p>
+ </div>
+ </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cmd/changelog-webapp/static/templates/promptLogin.html b/src/cmd/changelog-webapp/static/templates/promptLogin.html
new file mode 100644
index 0000000..6091473
--- /dev/null
+++ b/src/cmd/changelog-webapp/static/templates/promptLogin.html
@@ -0,0 +1,33 @@
+<html>
+<head>
+ <meta name="description" content="Google COS build information">
+ <link rel="stylesheet" href="/static/css/base.css">
+</head>
+<body>
+ <div class="navbar">
+ <p class="navbar-title">Container Optimized OS</p>
+ </div>
+ <div class="sidenav">
+ <a href="/">Home</a>
+ {{if (eq .ActivePage "changelog")}}
+ <a class="active" href="/changelog/">Changelog</a>
+ {{else}}
+ <a href="/changelog/">Changelog</a>
+ {{end}}
+ {{if (eq .ActivePage "locateCl")}}
+ <a class="active" href="/locatecl/">Locate CL</a>
+ {{else}}
+ <a href="/locatecl/">Locate CL</a>
+ {{end}}
+ <a href="/login/">Login</a>
+ </div>
+ <div class="main">
+ <div class="text-content">
+ <h1>Please sign in to use this feature</h1>
+ <p>This application requires OAuth authentication to communicate with Google Git repositories. Your account data
+ will not be used for any other purposes. To continue, please sign in by clicking <a href="/login/">here</a>.
+ </p>
+ </div>
+ </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/cmd/changelog/main.go b/src/cmd/changelog/main.go
index 3eb0731..4531d36 100755
--- a/src/cmd/changelog/main.go
+++ b/src/cmd/changelog/main.go
@@ -57,7 +57,7 @@
return oauth2.NewClient(oauth2.NoContext, creds.TokenSource), nil
}
-func writeChangelogAsJSON(source string, target string, changes map[string][]*changelog.Commit) error {
+func writeChangelogAsJSON(source string, target string, changes map[string]*changelog.RepoLog) error {
fileName := fmt.Sprintf("%s -> %s.json", source, target)
log.Infof("Writing changelog to %s\n", fileName)
jsonData, err := json.MarshalIndent(changes, "", " ")
@@ -76,7 +76,7 @@
if err != nil {
return fmt.Errorf("generateChangelog: failed to create http client: \n%v", err)
}
- sourceToTargetChanges, targetToSourceChanges, err := changelog.Changelog(httpClient, source, target, instance, manifestRepo)
+ sourceToTargetChanges, targetToSourceChanges, err := changelog.Changelog(httpClient, source, target, instance, manifestRepo, -1)
if err != nil {
return fmt.Errorf("generateChangelog: error retrieving changelog between builds %s and %s on GoB instance: %s with manifest repository: %s\n%v",
source, target, instance, manifestRepo, err)
diff --git a/src/cmd/changelog/main_test.py b/src/cmd/changelog/main_test.py
index 251a9ad..9751fd3 100644
--- a/src/cmd/changelog/main_test.py
+++ b/src/cmd/changelog/main_test.py
@@ -62,8 +62,8 @@
data = json.load(f)
if len(data) == 0:
return False
- for repo, commit_log in data.items():
- for commit in commit_log:
+ for repoName, repoLog in data.items():
+ for commit in repoLog['Commits']:
if not check_commit_schema(commit):
return False
return True
@@ -71,10 +71,15 @@
class TestCLIApplication(unittest.TestCase):
- def test_build(self):
+ def setUp(self):
process = subprocess.run(["go", "build", "-o", "changelog","main.go"])
assert process.returncode == 0
+ def tearDown(self):
+ delete_logs("15000.0.0", "15055.0.0")
+ delete_logs("15050.0.0", "15056.0.0")
+ delete_logs("15056.0.0", "15056.0.0")
+
def test_basic_run(self):
source = "15050.0.0"
target = "15056.0.0"
diff --git a/src/cmd/cos_gpu_installer/internal/commands/install.go b/src/cmd/cos_gpu_installer/internal/commands/install.go
index 75a988c..2ef3611 100644
--- a/src/cmd/cos_gpu_installer/internal/commands/install.go
+++ b/src/cmd/cos_gpu_installer/internal/commands/install.go
@@ -3,6 +3,8 @@
import (
"context"
+ "fmt"
+ "io/ioutil"
"path/filepath"
"flag"
@@ -28,7 +30,7 @@
type InstallCommand struct {
driverVersion string
hostInstallDir string
- enforceSigning bool
+ unsignedDriver bool
internalDownload bool
debug bool
}
@@ -48,8 +50,10 @@
"The GPU driver verion to install. It will install the default GPU driver if the flag is not set explicitly.")
f.StringVar(&c.hostInstallDir, "dir", "/var/lib/nvidia",
"Host directory that GPU drivers should be installed to")
- f.BoolVar(&c.enforceSigning, "enforce-signing", true,
- "Whether to enforce GPU drivers being signed. Setting to false will disable kernel module signing security feature.")
+ f.BoolVar(&c.unsignedDriver, "allow-unsigned-driver", false,
+ "Whether to allow load unsigned GPU drivers. "+
+ "If this flag is set to true, module signing security features must be disabled on the host for driver installation to succeed. "+
+ "This flag is only for debugging.")
// TODO(mikewu): change this flag to a bucket prefix string.
f.BoolVar(&c.internalDownload, "internal-download", false,
"Whether to try to download files from Google internal server. This is only useful for internal developing.")
@@ -67,7 +71,7 @@
log.Infof("Running on COS build id %s", envReader.BuildNumber())
- downloader := &cos.GCSDownloader{envReader, c.internalDownload}
+ downloader := cos.NewGCSDownloader(envReader, c.internalDownload)
if c.driverVersion == "" {
defaultVersion, err := installer.GetDefaultGPUDriverVersion(downloader)
if err != nil {
@@ -78,19 +82,20 @@
}
log.Infof("Installing GPU driver version %s", c.driverVersion)
- if !c.enforceSigning {
- log.Info("Doesn't enforce signing. Need to disable module locking.")
- if err := cos.DisableKernelModuleLocking(); err != nil {
- c.logError(errors.Wrap(err, "failed to configure kernel module locking"))
- return subcommands.ExitFailure
+ if c.unsignedDriver {
+ kernelCmdline, err := ioutil.ReadFile("/proc/cmdline")
+ if err != nil {
+ c.logError(fmt.Errorf("failed to read kernel command line: %v", err))
+ }
+ if !cos.CheckKernelModuleSigning(string(kernelCmdline)) {
+ log.Warning("Current kernel command line does not support unsigned kernel modules. Not enforcing kernel module signing may cause installation fail.")
}
}
-
hostInstallDir := filepath.Join(hostRootPath, c.hostInstallDir)
cacher := installer.NewCacher(hostInstallDir, envReader.BuildNumber(), c.driverVersion)
if isCached, err := cacher.IsCached(); isCached && err == nil {
log.Info("Found cached version, NOT building the drivers.")
- if err := installer.ConfigureCachedInstalltion(hostInstallDir, c.enforceSigning); err != nil {
+ if err := installer.ConfigureCachedInstalltion(hostInstallDir, !c.unsignedDriver); err != nil {
c.logError(errors.Wrap(err, "failed to configure cached installation"))
return subcommands.ExitFailure
}
@@ -121,7 +126,7 @@
}
defer func() { callback <- 0 }()
- if c.enforceSigning {
+ if !c.unsignedDriver {
if err := signing.DownloadDriverSignatures(downloader, c.driverVersion); err != nil {
return errors.Wrap(err, "failed to download driver signature")
}
@@ -140,7 +145,7 @@
return errors.Wrap(err, "failed to install toolchain")
}
- if err := installer.RunDriverInstaller(installerFile, c.enforceSigning); err != nil {
+ if err := installer.RunDriverInstaller(installerFile, !c.unsignedDriver); err != nil {
return errors.Wrap(err, "failed to run GPU driver installer")
}
if err := cacher.Cache(); err != nil {
diff --git a/src/cmd/cos_gpu_installer/internal/commands/list.go b/src/cmd/cos_gpu_installer/internal/commands/list.go
index 63ec447..4b4e095 100644
--- a/src/cmd/cos_gpu_installer/internal/commands/list.go
+++ b/src/cmd/cos_gpu_installer/internal/commands/list.go
@@ -47,7 +47,7 @@
return subcommands.ExitFailure
}
log.Infof("Running on COS build id %s", envReader.BuildNumber())
- downloader := &cos.GCSDownloader{envReader, c.internalDownload}
+ downloader := cos.NewGCSDownloader(envReader, c.internalDownload)
artifacts, err := downloader.ListExtensionArtifacts("gpu")
if err != nil {
c.logError(errors.Wrap(err, "failed to list gpu extension artifacts"))
diff --git a/src/cmd/cos_gpu_installer/internal/installer/installer.go b/src/cmd/cos_gpu_installer/internal/installer/installer.go
index 0b8d6b4..654240c 100644
--- a/src/cmd/cos_gpu_installer/internal/installer/installer.go
+++ b/src/cmd/cos_gpu_installer/internal/installer/installer.go
@@ -22,7 +22,7 @@
const (
gpuInstallDirContainer = "/usr/local/nvidia"
- defaultGPUDriverFile = "default_version"
+ defaultGPUDriverFile = "gpu_default_version"
precompiledInstallerURLFormat = "https://storage.googleapis.com/nvidia-drivers-%s-public/nvidia-cos-project/%s/tesla/%s_00/%s/NVIDIA-Linux-x86_64-%s_%s-%s.cos"
defaultFilePermission = 0755
)
@@ -140,9 +140,8 @@
if needSigned {
// Run installer to compile drivers. Expect the command to fail as the drivers are not signed yet.
- if err := utils.RunCommandAndLogOutput(cmd, true); err != nil {
- return errors.Wrap(err, "failed to run GPU driver installer")
- }
+ utils.RunCommandAndLogOutput(cmd, true)
+
// sign GPU drivers.
kernelFiles, err := ioutil.ReadDir(filepath.Join(extractDir, "kernel"))
if err != nil {
@@ -184,9 +183,9 @@
}
// GetDefaultGPUDriverVersion gets the default GPU driver version.
-func GetDefaultGPUDriverVersion(downloader cos.ExtensionsDownloader) (string, error) {
+func GetDefaultGPUDriverVersion(downloader cos.ArtifactsDownloader) (string, error) {
log.Info("Getting the default GPU driver version")
- content, err := downloader.GetExtensionArtifact(cos.GpuExtension, defaultGPUDriverFile)
+ content, err := downloader.GetArtifact(defaultGPUDriverFile)
if err != nil {
return "", errors.Wrapf(err, "failed to get default GPU driver version")
}
diff --git a/src/pkg/changelog/changelog.go b/src/pkg/changelog/changelog.go
old mode 100755
new mode 100644
index e984581..3b6c686
--- a/src/pkg/changelog/changelog.go
+++ b/src/pkg/changelog/changelog.go
@@ -47,9 +47,9 @@
manifestFileName string = "snapshot.xml"
// These constants are used for exponential increase in Gitiles request size.
- defaultPageSize int32 = 1000
- pageSizeGrowthMultiplier int32 = 5
- maxPageSize int32 = 10000
+ defaultPageSize int = 1000
+ pageSizeGrowthMultiplier int = 5
+ maxPageSize int = 10000
)
type repo struct {
@@ -66,16 +66,26 @@
}
type commitsResult struct {
- RepoURL string
- Commits []*Commit
- Err error
+ RepoURL string
+ Commits []*Commit
+ HasMoreCommits bool
+ Err error
}
type additionsResult struct {
- Additions map[string][]*Commit
+ Additions map[string]*RepoLog
Err error
}
+// limitPageSize will restrict a request page size to min of pageSize (which grows exponentially)
+// or remaining request size
+func limitPageSize(pageSize, requestedSize int) int {
+ if requestedSize == -1 || pageSize <= requestedSize {
+ return pageSize
+ }
+ return requestedSize
+}
+
func gerritClient(httpClient *http.Client, remoteURL string) (gitilesProto.GitilesClient, error) {
log.Debugf("Creating Gerrit client for remote url %s\n", remoteURL)
cl, err := gitilesApi.NewRESTClient(httpClient, remoteURL, true)
@@ -124,7 +134,9 @@
// Extract the "name", "remote", and "revision" attributes from each project tag.
// Some projects do not have a "remote" attribute.
// If this is the case, they should use the default remoteURL.
- remoteMap[""] = remoteMap[root.SelectElement("default").SelectAttr("remote").Value]
+ if root.SelectElement("default").SelectAttr("remote") != nil {
+ remoteMap[""] = remoteMap[root.SelectElement("default").SelectAttr("remote").Value]
+ }
repos := make(map[string]*repo)
for _, project := range root.SelectElements("project") {
repos[project.SelectAttr("name").Value] = &repo{
@@ -158,15 +170,17 @@
}
// commits get all commits that occur between committish and ancestor for a specific repo.
-func commits(client gitilesProto.GitilesClient, repo string, committish string, ancestor string, outputChan chan commitsResult) {
+func commits(client gitilesProto.GitilesClient, repo string, committish string, ancestor string, querySize int, outputChan chan commitsResult) {
log.Debugf("Fetching changelog for repo: %s on committish %s\n", repo, committish)
start := time.Now()
- pageSize := defaultPageSize
+
+ pageSize := limitPageSize(defaultPageSize, querySize)
+ querySize -= pageSize
request := gitilesProto.LogRequest{
Project: repo,
Committish: committish,
ExcludeAncestorsOf: ancestor,
- PageSize: pageSize,
+ PageSize: int32(pageSize),
}
response, err := client.Log(context.Background(), &request)
if err != nil {
@@ -174,6 +188,7 @@
repo, committish, ancestor, err)}
return
}
+
// No nextPageToken means there were less than <defaultPageSize> commits total.
// We can immediately return.
if response.NextPageToken == "" {
@@ -184,21 +199,23 @@
repo, committish, ancestor, err)}
return
}
- outputChan <- commitsResult{RepoURL: repo, Commits: parsedCommits}
+ outputChan <- commitsResult{RepoURL: repo, Commits: parsedCommits, HasMoreCommits: (response.NextPageToken != "")}
return
}
// Retrieve remaining commits using exponential increase in pageSize.
allCommits := response.Log
- for response.NextPageToken != "" {
+ for querySize > 0 && response.NextPageToken != "" {
if pageSize < maxPageSize {
pageSize *= pageSizeGrowthMultiplier
}
+ pageSize = limitPageSize(pageSize, querySize)
+ querySize -= pageSize
request := gitilesProto.LogRequest{
Project: repo,
Committish: committish,
ExcludeAncestorsOf: ancestor,
PageToken: response.NextPageToken,
- PageSize: pageSize,
+ PageSize: int32(pageSize),
}
response, err = client.Log(context.Background(), &request)
if err != nil {
@@ -215,14 +232,14 @@
repo, committish, ancestor, err)}
return
}
- outputChan <- commitsResult{RepoURL: repo, Commits: parsedCommits}
+ outputChan <- commitsResult{RepoURL: repo, Commits: parsedCommits, HasMoreCommits: (response.NextPageToken != "")}
}
// additions retrieves all commits that occured between 2 parsed manifest files for each repo.
// Returns a map of repo name -> list of commits.
-func additions(clients map[string]gitilesProto.GitilesClient, sourceRepos map[string]*repo, targetRepos map[string]*repo, outputChan chan additionsResult) {
+func additions(clients map[string]gitilesProto.GitilesClient, sourceRepos map[string]*repo, targetRepos map[string]*repo, querySize int, outputChan chan additionsResult) {
log.Debug("Retrieving commit additions")
- repoCommits := make(map[string][]*Commit)
+ repoCommits := make(map[string]*RepoLog)
commitsChan := make(chan commitsResult, len(targetRepos))
for repoURL, targetRepoInfo := range targetRepos {
cl := clients[targetRepoInfo.InstanceURL]
@@ -232,7 +249,7 @@
if sourceRepoInfo, ok := sourceRepos[repoURL]; ok {
ancestorCommittish = sourceRepoInfo.Committish
}
- go commits(cl, repoURL, targetRepoInfo.Committish, ancestorCommittish, commitsChan)
+ go commits(cl, repoURL, targetRepoInfo.Committish, ancestorCommittish, querySize, commitsChan)
}
for i := 0; i < len(targetRepos); i++ {
res := <-commitsChan
@@ -240,8 +257,17 @@
outputChan <- additionsResult{Err: res.Err}
return
}
+ sourceSHA := ""
+ if sha, ok := sourceRepos[res.RepoURL]; ok {
+ sourceSHA = sha.Committish
+ }
if len(res.Commits) > 0 {
- repoCommits[res.RepoURL] = res.Commits
+ repoCommits[res.RepoURL] = &RepoLog{
+ Commits: res.Commits,
+ HasMoreCommits: res.HasMoreCommits,
+ SourceSHA: sourceSHA,
+ TargetSHA: targetRepos[res.RepoURL].Committish,
+ }
}
}
outputChan <- additionsResult{Additions: repoCommits}
@@ -257,19 +283,26 @@
// a tag that links directly to snapshot.xml
// Ex. For /refs/tags/15049.0.0, the argument should be 15049.0.0
//
-// The host should be the GoB instance that Manifest files are hosted in
+// host should be the GoB instance that Manifest files are hosted in
// ex. "cos.googlesource.com"
//
-// The repo should be the repository that build manifest files
+// repo should be the repository that build manifest files
// are located, ex. "cos/manifest-snapshots"
//
+// querySize should be the number of commits that should be included in each
+// repository changelog. Specify as -1 to get all commits
+//
// Outputs two changelogs
// The first changelog contains new commits that were added to the target
// build starting from the source build number
//
// The second changelog contains all commits that are present in the source build
// but not present in the target build
-func Changelog(httpClient *http.Client, sourceBuildNum string, targetBuildNum string, host string, repo string) (map[string][]*Commit, map[string][]*Commit, error) {
+func Changelog(httpClient *http.Client, sourceBuildNum string, targetBuildNum string, host string, repo string, querySize int) (map[string]*RepoLog, map[string]*RepoLog, error) {
+ if httpClient == nil {
+ return nil, nil, errors.New("Changelog: httpClient should not be nil")
+ }
+
log.Infof("Retrieving changelog between %s and %s\n", sourceBuildNum, targetBuildNum)
clients := make(map[string]gitilesProto.GitilesClient)
@@ -302,16 +335,16 @@
addChan := make(chan additionsResult, 1)
missChan := make(chan additionsResult, 1)
- go additions(clients, sourceRepos, targetRepos, addChan)
- go additions(clients, targetRepos, sourceRepos, missChan)
- addRes := <-addChan
- if addRes.Err != nil {
- return nil, nil, fmt.Errorf("Changelog: failure when retrieving commit additions:\n%v", err)
- }
+ go additions(clients, sourceRepos, targetRepos, querySize, addChan)
+ go additions(clients, targetRepos, sourceRepos, querySize, missChan)
missRes := <-missChan
if missRes.Err != nil {
return nil, nil, fmt.Errorf("Changelog: failure when retrieving missed commits:\n%v", err)
}
+ addRes := <-addChan
+ if addRes.Err != nil {
+ return nil, nil, fmt.Errorf("Changelog: failure when retrieving commit additions:\n%v", err)
+ }
return addRes.Additions, missRes.Additions, nil
}
diff --git a/src/pkg/changelog/changelog_test.go b/src/pkg/changelog/changelog_test.go
index 6113444..86b6423 100644
--- a/src/pkg/changelog/changelog_test.go
+++ b/src/pkg/changelog/changelog_test.go
@@ -51,9 +51,9 @@
return true
}
-func mappingInLog(log map[string][]*Commit, check []string) bool {
+func repoListInLog(log map[string]*RepoLog, check []string) bool {
for _, check := range check {
- if log, ok := log[check]; !ok || len(log) == 0 {
+ if log, ok := log[check]; !ok || len(log.Commits) == 0 {
return false
}
}
@@ -64,57 +64,68 @@
httpClient, err := getHTTPClient()
// Test invalid source
- additions, misses, err := Changelog(httpClient, "15", "15043.0.0", cosInstance, defaultManifestRepo)
+ additions, removals, err := Changelog(httpClient, "15", "15043.0.0", cosInstance, defaultManifestRepo, -1)
if additions != nil {
t.Errorf("Changelog failed, expected nil additions, got %v", additions)
- } else if misses != nil {
- t.Errorf("Changelog failed, expected nil misses, got %v", misses)
+ } else if removals != nil {
+ t.Errorf("Changelog failed, expected nil removals, got %v", removals)
} else if err == nil {
t.Errorf("Changelog failed, expected error, got nil")
}
// Test invalid target
- additions, misses, err = Changelog(httpClient, "15043.0.0", "abx", cosInstance, defaultManifestRepo)
+ additions, removals, err = Changelog(httpClient, "15043.0.0", "abx", cosInstance, defaultManifestRepo, -1)
if additions != nil {
t.Errorf("Changelog failed, expected nil additions, got %v", additions)
- } else if misses != nil {
- t.Errorf("Changelog failed, expected nil misses, got %v", misses)
+ } else if removals != nil {
+ t.Errorf("Changelog failed, expected nil removals, got %v", removals)
} else if err == nil {
t.Errorf("Changelog failed, expected error, got nil")
}
// Test invalid instance
- additions, misses, err = Changelog(httpClient, "15036.0.0", "15041.0.0", "com", defaultManifestRepo)
+ additions, removals, err = Changelog(httpClient, "15036.0.0", "15041.0.0", "com", defaultManifestRepo, -1)
if additions != nil {
t.Errorf("Changelog failed, expected nil additions, got %v", additions)
- } else if misses != nil {
- t.Errorf("Changelog failed, expected nil misses, got %v", misses)
+ } else if removals != nil {
+ t.Errorf("Changelog failed, expected nil removals, got %v", removals)
} else if err == nil {
t.Errorf("Changelog failed, expected error, got nil")
}
// Test invalid manifest repo
- additions, misses, err = Changelog(httpClient, "15036.0.0", "15041.0.0", cosInstance, "cos/not-a-repo")
+ additions, removals, err = Changelog(httpClient, "15036.0.0", "15041.0.0", cosInstance, "cos/not-a-repo", -1)
if additions != nil {
t.Errorf("Changelog failed, expected nil additions, got %v", additions)
- } else if misses != nil {
- t.Errorf("Changelog failed, expected nil misses, got %v", misses)
+ } else if removals != nil {
+ t.Errorf("Changelog failed, expected nil removals, got %v", removals)
} else if err == nil {
t.Errorf("Changelog failed, expected error, got nil")
}
// Test build number higher than latest release
- additions, misses, err = Changelog(httpClient, "15036.0.0", "99999.0.0", cosInstance, defaultManifestRepo)
+ additions, removals, err = Changelog(httpClient, "15036.0.0", "99999.0.0", cosInstance, defaultManifestRepo, -1)
if additions != nil {
t.Errorf("Changelog failed, expected nil additions, got %v", additions)
- } else if misses != nil {
- t.Errorf("Changelog failed, expected nil misses, got %v", misses)
+ } else if removals != nil {
+ t.Errorf("Changelog failed, expected nil removals, got %v", removals)
} else if err == nil {
t.Errorf("Changelog failed, expected error, got nil")
}
+ // Test manifest with remote urls specified and no default URL
+ additions, removals, err = Changelog(httpClient, "1.0.0", "2.0.0", cosInstance, defaultManifestRepo, -1)
+ if additions == nil {
+ t.Errorf("Changelog failed, expected additions, got nil")
+ } else if removals == nil {
+ t.Errorf("Changelog failed, expected removals, got nil")
+ } else if err != nil {
+ t.Errorf("Changelog failed, expected no error, got %v", err)
+ }
+
// Test 1 build number difference with only 1 repo change between them
// Ensure that commits are correctly inserted in proper order
+ // Check that changelog metadata correctly populated
source := "15050.0.0"
target := "15051.0.0"
expectedCommits := []string{
@@ -227,23 +238,29 @@
"9bc12bb411f357188d008864f80dfba43210b9d8",
"bf0dd3757826b9bc9d7082f5f749ff7615d4bcb3",
}
- additions, misses, err = Changelog(httpClient, source, target, cosInstance, defaultManifestRepo)
+ additions, removals, err = Changelog(httpClient, source, target, cosInstance, defaultManifestRepo, -1)
if err != nil {
t.Errorf("Changelog failed, expected no error, got %v", err)
- } else if len(misses) != 0 {
- t.Errorf("Changelog failed, expected empty misses list, got %v", misses)
+ } else if len(removals) != 0 {
+ t.Errorf("Changelog failed, expected empty removals list, got %v", removals)
} else if len(additions) != 1 {
t.Errorf("Changelog failed, expected only 1 repo in additions, got %v", additions)
- } else if _, ok := additions["cos/overlays/board-overlays"]; !ok {
- t.Errorf("Changelog failed, expected \"cos/overlays/board-overlays\" in additions, got %v", additions)
- } else if changes, _ := additions["cos/overlays/board-overlays"]; len(changes) != 108 {
+ }
+ boardOverlayLog := additions["cos/overlays/board-overlays"]
+ if boardOverlayLog == nil {
+ t.Errorf("Changelog failed, expected cos/overlays/board-overlays in changelog, got nil")
+ } else if changes := boardOverlayLog.Commits; len(changes) != 108 {
t.Errorf("Changelog failed, expected 108 changes for \"cos/overlays/board-overlays\", got %d", len(changes))
- } else if !commitsMatch(additions["cos/overlays/board-overlays"], expectedCommits) {
+ } else if !commitsMatch(boardOverlayLog.Commits, expectedCommits) {
t.Errorf("Changelog failed, Changelog output does not match expected commits or is not sorted")
+ } else if boardOverlayLog.SourceSHA != "612ca5ef5455534127d008e08c65aa29a2fd97a5" {
+ t.Errorf("Changelog failed, expected SourceSHA \"612ca5ef5455534127d008e08c65aa29a2fd97a5\", got %s", boardOverlayLog.SourceSHA)
+ } else if boardOverlayLog.TargetSHA != "6201c49afe667c8fa7796608a4d7162bb3f7f4f4" {
+ t.Errorf("Changelog failed, expected SourceSHA \"6201c49afe667c8fa7796608a4d7162bb3f7f4f4\", got %s", boardOverlayLog.TargetSHA)
}
// Test build numbers further apart from each other with multiple repo differences
- // Also ensures that misses are correctly populated
+ // Also ensures that removals are correctly populated
source = "15020.0.0"
target = "15056.0.0"
additionRepos := []string{
@@ -266,12 +283,89 @@
"mirrors/cros/chromiumos/repohooks",
"mirrors/cros/chromiumos/overlays/portage-stable",
}
- additions, misses, err = Changelog(httpClient, source, target, cosInstance, defaultManifestRepo)
+ additions, removals, err = Changelog(httpClient, source, target, cosInstance, defaultManifestRepo, -1)
if err != nil {
t.Errorf("Changelog failed, expected no error, got %v", err)
- } else if _, ok := misses["third_party/kernel"]; len(misses) != 1 && ok {
- t.Errorf("Changelog failed, expected miss list containing only \"third_party/kernel\", got %v", misses)
- } else if !mappingInLog(additions, additionRepos) {
+ }
+ kernelLog := additions["third_party/kernel"]
+ if len(removals) != 1 || kernelLog == nil {
+ t.Errorf("Changelog failed, expected miss list containing only \"third_party/kernel\", got %v", removals)
+ } else if !repoListInLog(additions, additionRepos) {
t.Errorf("Changelog failed, additions repo output does not match expected repos %v", additionRepos)
}
+
+ // Test changelog returns correct output when given a querySize instead of -1
+ source = "15030.0.0"
+ target = "15050.0.0"
+ querySize := 50
+ additions, removals, err = Changelog(httpClient, source, target, cosInstance, defaultManifestRepo, querySize)
+ if err != nil {
+ t.Errorf("Changelog failed, expected no error, got %v", err)
+ } else if additions == nil {
+ t.Errorf("Changelog failed, non-empty expected additions, got nil")
+ } else if removals == nil {
+ t.Errorf("Changelog failed, non-empty expected removals, got nil")
+ } else if _, ok := additions["third_party/kernel"]; !ok {
+ t.Errorf("Changelog failed, expected repo: third_party/kernel in additions")
+ }
+ for repoName, repoLog := range additions {
+ if repoLog.Commits == nil || len(repoLog.Commits) == 0 {
+ t.Errorf("Changelog failed, expected non-empty additions commits, got nil or empty commits")
+ }
+ if len(repoLog.Commits) > querySize {
+ t.Errorf("Changelog failed, expected %d commits for repo: %s, got: %d", querySize, repoName, len(repoLog.Commits))
+ } else if repoName == "third_party/kernel" && !repoLog.HasMoreCommits {
+ t.Errorf("Changelog failed, expected HasMoreCommits = True for repo: third_party/kernel, got False")
+ } else if repoLog.HasMoreCommits && len(repoLog.Commits) < querySize {
+ t.Errorf("Changelog failed, expected HasMoreCommits = False for repo: %s with %d commits, got True", repoName, len(repoLog.Commits))
+ }
+ }
+
+ // Test changelog handles manifest with non-matching repositories
+ source = "12871.1177.0"
+ target = "12871.1179.0"
+ additions, removals, err = Changelog(httpClient, source, target, cosInstance, defaultManifestRepo, querySize)
+ if err != nil {
+ t.Errorf("Changelog failed, expected no error, got %v", err)
+ } else if len(removals) != 0 {
+ t.Errorf("Changelog failed, expected empty removals, got %v", removals)
+ } else if _, ok := additions["cos/cobble"]; !ok {
+ t.Errorf("Changelog failed, expected repo: third_party/kernel in additions")
+ }
+ for repoName, repoLog := range additions {
+ if repoLog.Commits == nil || len(repoLog.Commits) == 0 {
+ t.Errorf("Changelog failed, expected non-empty additions commits, got nil or empty commits")
+ } else if repoName == "cos/cobble" {
+ if repoLog.HasMoreCommits {
+ t.Errorf("Changelog failed, expected hasMoreCommits = false for repo: cos/cobble, got true")
+ } else if repoLog.SourceSHA != "" {
+ t.Errorf("Changelog failed, expected empty SourceSHA for cos/cobble, got %s", repoLog.SourceSHA)
+ } else if repoLog.TargetSHA != "4ab43f1f86b7099b8ad75cf9615ea1fa155bbd7d" {
+ t.Errorf("Changelog failed, expected TargetSHA: \"4ab43f1f86b7099b8ad75cf9615ea1fa155bbd7d\" for cos/cobble, got %s", repoLog.TargetSHA)
+ }
+ }
+ }
+ source = "12871.1179.0"
+ target = "12871.1177.0"
+ additions, removals, err = Changelog(httpClient, source, target, cosInstance, defaultManifestRepo, querySize)
+ if err != nil {
+ t.Errorf("Changelog failed, expected no error, got %v", err)
+ } else if len(additions) != 0 {
+ t.Errorf("Changelog failed, expected empty additions, got %v", additions)
+ } else if _, ok := removals["cos/cobble"]; !ok {
+ t.Errorf("Changelog failed, expected repo: third_party/kernel in additions")
+ }
+ for repoName, repoLog := range removals {
+ if repoLog.Commits == nil || len(repoLog.Commits) == 0 {
+ t.Errorf("Changelog failed, expected non-empty additions commits, got nil or empty commits")
+ } else if repoName == "cos/cobble" {
+ if repoLog.HasMoreCommits {
+ t.Errorf("Changelog failed, expected hasMoreCommits = false for repo: cos/cobble, got true")
+ } else if repoLog.SourceSHA != "" {
+ t.Errorf("Changelog failed, expected empty SourceSHA for cos/cobble, got %s", repoLog.SourceSHA)
+ } else if repoLog.TargetSHA != "4ab43f1f86b7099b8ad75cf9615ea1fa155bbd7d" {
+ t.Errorf("Changelog failed, expected TargetSHA: \"4ab43f1f86b7099b8ad75cf9615ea1fa155bbd7d\" for cos/cobble, got %s", repoLog.TargetSHA)
+ }
+ }
+ }
}
diff --git a/src/pkg/changelog/gitcommit.go b/src/pkg/changelog/gitcommit.go
index e52b2c7..39d49f4 100755
--- a/src/pkg/changelog/gitcommit.go
+++ b/src/pkg/changelog/gitcommit.go
@@ -18,13 +18,22 @@
"errors"
"regexp"
"strings"
- "time"
"go.chromium.org/luci/common/proto/git"
)
-const bugLinePrefix string = "BUG="
-const releaseNoteLinePrefix string = "RELEASE_NOTE="
+const (
+ bugLinePrefix string = "BUG="
+ releaseNoteLinePrefix string = "RELEASE_NOTE="
+)
+
+// RepoLog contains a changelist for a particular repository
+type RepoLog struct {
+ Commits []*Commit
+ SourceSHA string
+ TargetSHA string
+ HasMoreCommits bool
+}
// Commit is a simplified struct of git.Commit
// Useful for interfaces
@@ -104,7 +113,7 @@
func commitTime(commit *git.Commit) string {
if commit.Committer != nil {
- return commit.Committer.Time.AsTime().Format(time.RFC1123)
+ return commit.Committer.Time.AsTime().Format("Mon, 2 Jan 2006")
}
return "None"
}
diff --git a/src/pkg/changelog/gitcommit_test.go b/src/pkg/changelog/gitcommit_test.go
index 268245d..7c623ac 100644
--- a/src/pkg/changelog/gitcommit_test.go
+++ b/src/pkg/changelog/gitcommit_test.go
@@ -29,7 +29,7 @@
parent string = "7645df3136c5b5e43eb1af182b0c67d78ca2d517"
authorName string = "Austin Yuan"
committerName string = "Boston Yuan"
- timeVal string = "Sat, 01 Feb 2020 08:15:00 UTC"
+ timeVal string = "Sat, 1 Feb 2020"
)
var authorTime time.Time
@@ -339,9 +339,9 @@
case !reflect.DeepEqual(commit.Bugs, test.Bugs[i]):
t.Errorf("exptected bugs %#v, got %#v", test.Bugs[i], commit.Bugs)
case commit.ReleaseNote != test.ReleaseNote[i]:
- t.Errorf("expected release note %s, got %s", test.ReleaseNote, commit.ReleaseNote)
+ t.Errorf("expected release note %s, got %s", test.ReleaseNote[i], commit.ReleaseNote)
case commit.CommitTime != test.CommitTime[i]:
- t.Errorf("expected commit time %s, got %s", test.CommitTime, commit.CommitTime)
+ t.Errorf("expected commit time %s, got %s", test.CommitTime[i], commit.CommitTime)
}
}
})
diff --git a/src/pkg/cos/artifacts.go b/src/pkg/cos/artifacts.go
index 616778e..bb2f0bb 100644
--- a/src/pkg/cos/artifacts.go
+++ b/src/pkg/cos/artifacts.go
@@ -42,6 +42,11 @@
Internal bool
}
+// NewGCSDownloader creates a GCSDownloader instance.
+func NewGCSDownloader(e *EnvReader, i bool) *GCSDownloader {
+ return &GCSDownloader{e, i}
+}
+
// DownloadKernelSrc downloads COS kernel sources to destination directory.
func (d *GCSDownloader) DownloadKernelSrc(destDir string) error {
return d.DownloadArtifact(destDir, kernelSrcArchive)
diff --git a/src/pkg/cos/cos.go b/src/pkg/cos/cos.go
index c431c00..c75f7c5 100644
--- a/src/pkg/cos/cos.go
+++ b/src/pkg/cos/cos.go
@@ -8,7 +8,6 @@
"os/exec"
"path/filepath"
"strings"
- "syscall"
"cos.googlesource.com/cos/tools/src/pkg/utils"
@@ -25,63 +24,20 @@
execCommand = exec.Command
)
-// DisableKernelModuleLocking disables kernel modules signing enforcement and loadpin so that unsigned kernel modules
-// can be loaded to COS kernel.
-func DisableKernelModuleLocking() error {
- log.Info("Checking if third party kernel modules can be installed")
+// CheckKernelModuleSigning checks whether kernel module signing related options present.
+func CheckKernelModuleSigning(kernelCmdline string) bool {
+ log.Info("Checking kernel module signing.")
- mountDir, err := ioutil.TempDir("", "mountdir")
- if err != nil {
- return errors.Wrap(err, "failed to create mount dir")
- }
-
- if err := syscall.Mount(espPartition, mountDir, "vfat", 0, ""); err != nil {
- return errors.Wrap(err, "failed to mount path")
- }
-
- grubCfgPath := filepath.Join(mountDir, "esp/efi/boot/grub.cfg")
- grubCfg, err := ioutil.ReadFile(grubCfgPath)
- if err != nil {
- return errors.Wrapf(err, "failed to read grub config from %s", grubCfgPath)
- }
-
- grubCfgStr := string(grubCfg)
- needReboot := false
for _, kernelOption := range []string{
- "module.sig_enforce",
- "loadpin.enforce",
- "loadpin.enabled",
+ "loadpin.exclude=kernel-module",
+ "modules-load=loadpin_trigger",
+ "module.sig_enforce=1",
} {
- if newGrubCfgStr, needRebootOption := disableKernelOptionFromGrubCfg(kernelOption, grubCfgStr); needRebootOption {
- needReboot = true
- grubCfgStr = newGrubCfgStr
+ if !strings.Contains(kernelCmdline, kernelOption) {
+ return false
}
}
-
- if needReboot {
- log.Info("Modifying grub config to disable module locking.")
- if err := os.Rename(grubCfgPath, grubCfgPath+".orig"); err != nil {
- return errors.Wrapf(err, "failed to rename file %s", grubCfgPath)
- }
- if err := ioutil.WriteFile(grubCfgPath, []byte(grubCfgStr), 0644); err != nil {
- return errors.Wrapf(err, "failed to write to file %s", grubCfgPath)
- }
- } else {
- log.Info("Module locking has been disabled.")
- }
-
- syscall.Sync()
- if err := syscall.Unmount(mountDir, 0); err != nil {
- return err
- }
-
- if needReboot {
- log.Warning("Rebooting")
- if err := syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART); err != nil {
- return errors.Wrap(err, "failed to reboot")
- }
- }
- return nil
+ return true
}
// SetCompilationEnv sets compilation environment variables (e.g. CC, CXX) for third-party kernel module compilation.
@@ -119,15 +75,14 @@
}
if empty, _ := utils.IsDirEmpty(destDir); !empty {
log.Info("Found existing toolchain. Skipping download and installation")
- return nil
- }
+ } else {
+ if err := downloader.DownloadToolchain(destDir); err != nil {
+ return errors.Wrap(err, "failed to download toolchain")
+ }
- if err := downloader.DownloadToolchain(destDir); err != nil {
- return errors.Wrap(err, "failed to download toolchain")
- }
-
- if err := exec.Command("tar", "xf", filepath.Join(destDir, toolchainArchive), "-C", destDir).Run(); err != nil {
- return errors.Wrap(err, "failed to extract toolchain archive tarball")
+ if err := exec.Command("tar", "xf", filepath.Join(destDir, toolchainArchive), "-C", destDir).Run(); err != nil {
+ return errors.Wrap(err, "failed to extract toolchain archive tarball")
+ }
}
log.Info("Configuring environment variables for cross-compilation")
diff --git a/src/pkg/cos/cos_test.go b/src/pkg/cos/cos_test.go
index f031491..e28ac2b 100644
--- a/src/pkg/cos/cos_test.go
+++ b/src/pkg/cos/cos_test.go
@@ -207,55 +207,65 @@
func TestDisableKernelOptionFromGrubCfg(t *testing.T) {
for _, tc := range []struct {
- testName string
- kernelOption string
- grubCfg string
- expectedNewGrubCfg string
- expectedNeedReboot bool
+ testName string
+ kernelCmdLine string
+ expectedReturn bool
}{
{
- "LoadPin",
- "loadpin.enabled",
-
- `BOOT_IMAGE=/syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap ` +
- `loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 ` +
- `systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 ` +
- `dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi root=/dev/dm-0 ` +
- `"dm=1 vroot none ro 1,0 2539520 verity payload=PARTUUID=36547742-9356-EF4E-B9AD-F8DED2F6D087 ` +
- `hashtree=PARTUUID=36547742-9356-EF4E-B9AD-F8DED2F6D087 hashstart=2539520 alg=sha256 ` +
- `root_hexdigest=0ff80250bd97ad47a65e7cd330ab70bcf5013d7a86817dca59fcac77f0ba1a8f ` +
- `salt=414038a6ed9b1f528c327aff4eac16ad5ca4a6699d142ae096e90374af907c34`,
-
- `BOOT_IMAGE=/syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap ` +
- `loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 ` +
- `systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 ` +
- `dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi loadpin.enabled=0 root=/dev/dm-0 ` +
- `"dm=1 vroot none ro 1,0 2539520 verity payload=PARTUUID=36547742-9356-EF4E-B9AD-F8DED2F6D087 ` +
- `hashtree=PARTUUID=36547742-9356-EF4E-B9AD-F8DED2F6D087 hashstart=2539520 alg=sha256 ` +
- `root_hexdigest=0ff80250bd97ad47a65e7cd330ab70bcf5013d7a86817dca59fcac77f0ba1a8f ` +
- `salt=414038a6ed9b1f528c327aff4eac16ad5ca4a6699d142ae096e90374af907c34`,
- true,
+ testName: "OptionsPresent",
+ kernelCmdLine: `BOOT_IMAGE=/syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 ` +
+ `noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false ` +
+ `systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 ` +
+ `dm_verity.dev_wait=1 i915.modeset=1 cros_efi module.sig_enforce=1 modules-load=loadpin_trigger ` +
+ `loadpin.exclude=kernel-module root=/dev/dm-0 "dm=1 vroot none ro 1,0 4077568 verity ` +
+ `payload=PARTUUID=00CE255B-DB42-1E47-A62B-735C7A9A7397 hashtree=PARTUUID=00CE255B-DB42-1E47-A62B-735C7A9A7397 ` +
+ `hashstart=4077568 alg=sha256 root_hexdigest=8a2cfc7097aa7ddfe4101611fad9dd1df59f9c29cfa9b1a5d18f55ae68c9eed5 ` +
+ `salt=65697f247db9275b9e9830d275ca6b830c156187403f6210b2ebcb11c8beaa57"`,
+ expectedReturn: true,
},
{
- "LoadPinEnabled",
- "loadpin.enabled",
- "cros_efi loadpin.enabled=1",
- "cros_efi loadpin.enabled=0",
- true,
+ testName: "MissingLoadpinExclude",
+ kernelCmdLine: `BOOT_IMAGE=/syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 ` +
+ `noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false ` +
+ `systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 ` +
+ `dm_verity.dev_wait=1 i915.modeset=1 cros_efi module.sig_enforce=1 modules-load=loadpin_trigger ` +
+ `root=/dev/dm-0 "dm=1 vroot none ro 1,0 4077568 verity ` +
+ `payload=PARTUUID=00CE255B-DB42-1E47-A62B-735C7A9A7397 hashtree=PARTUUID=00CE255B-DB42-1E47-A62B-735C7A9A7397 ` +
+ `hashstart=4077568 alg=sha256 root_hexdigest=8a2cfc7097aa7ddfe4101611fad9dd1df59f9c29cfa9b1a5d18f55ae68c9eed5 ` +
+ `salt=65697f247db9275b9e9830d275ca6b830c156187403f6210b2ebcb11c8beaa57"`,
+ expectedReturn: false,
},
{
- "LoadPinDisabled",
- "loadpin.enabled",
- "cros_efi loadpin.enabled=0",
- "cros_efi loadpin.enabled=0",
- false,
+ testName: "MissingLoadpinTrigger",
+ kernelCmdLine: `BOOT_IMAGE=/syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 ` +
+ `noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false ` +
+ `systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 ` +
+ `dm_verity.dev_wait=1 i915.modeset=1 cros_efi module.sig_enforce=1 ` +
+ `loadpin.exclude=kernel-module root=/dev/dm-0 "dm=1 vroot none ro 1,0 4077568 verity ` +
+ `payload=PARTUUID=00CE255B-DB42-1E47-A62B-735C7A9A7397 hashtree=PARTUUID=00CE255B-DB42-1E47-A62B-735C7A9A7397 ` +
+ `hashstart=4077568 alg=sha256 root_hexdigest=8a2cfc7097aa7ddfe4101611fad9dd1df59f9c29cfa9b1a5d18f55ae68c9eed5 ` +
+ `salt=65697f247db9275b9e9830d275ca6b830c156187403f6210b2ebcb11c8beaa57"`,
+ expectedReturn: false,
+ },
+ {
+ testName: "MissingSigEnforce",
+ kernelCmdLine: `BOOT_IMAGE=/syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 ` +
+ `noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false ` +
+ `systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 ` +
+ `dm_verity.dev_wait=1 i915.modeset=1 cros_efi modules-load=loadpin_trigger ` +
+ `loadpin.exclude=kernel-module root=/dev/dm-0 "dm=1 vroot none ro 1,0 4077568 verity ` +
+ `payload=PARTUUID=00CE255B-DB42-1E47-A62B-735C7A9A7397 hashtree=PARTUUID=00CE255B-DB42-1E47-A62B-735C7A9A7397 ` +
+ `hashstart=4077568 alg=sha256 root_hexdigest=8a2cfc7097aa7ddfe4101611fad9dd1df59f9c29cfa9b1a5d18f55ae68c9eed5 ` +
+ `salt=65697f247db9275b9e9830d275ca6b830c156187403f6210b2ebcb11c8beaa57"`,
+ expectedReturn: false,
},
} {
- newGrubCfg, needReboot := disableKernelOptionFromGrubCfg(tc.kernelOption, tc.grubCfg)
- if newGrubCfg != tc.expectedNewGrubCfg || needReboot != tc.expectedNeedReboot {
- t.Errorf("%v: Unexpected output:\nexpect grubcfg: %v\ngot grubcfg: %v\nexpect needReboot: %v, got needReboot: %v",
- tc.testName, tc.expectedNewGrubCfg, newGrubCfg, tc.expectedNeedReboot, needReboot)
- }
+ t.Run(tc.testName, func(t *testing.T) {
+ ret := CheckKernelModuleSigning(tc.kernelCmdLine)
+ if ret != tc.expectedReturn {
+ t.Errorf("Unexpected output:%v, expect: %v", ret, tc.expectedReturn)
+ }
+ })
}
}