blob: 3cf6f4daaaa259f527672e83377335938243fbfa [file] [log] [blame]
// 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.
// This file contains the CLI application for COS Changelog.
// Project information available at go/cos-changelog
//
// This application is responsible for:
// 1. Accepting user input and creating the authenticator object used for
// queries
// 2. Calling function in the Changelog package, converting the output
// to json, and writing the result into a file
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"time"
"cos.googlesource.com/cos/tools.git/src/pkg/changelog"
"cos.googlesource.com/cos/tools.git/src/pkg/findbuild"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"github.com/urfave/cli/v2"
"go.chromium.org/luci/common/api/gerrit"
log "github.com/sirupsen/logrus"
)
const (
externalGerritURL = "https://cos-review.googlesource.com"
fallbackGerritURL = "https://chromium-review.googlesource.com"
externalGoBURL = "cos.googlesource.com"
externalManifestRepo = "cos/manifest-snapshots"
)
func getHTTPClient() (*http.Client, error) {
log.Debug("Creating HTTP client")
creds, err := google.FindDefaultCredentials(context.Background(), gerrit.OAuthScope)
if err != nil {
return nil, fmt.Errorf("no application default credentials found - run `gcloud auth application-default login` and try again")
}
return oauth2.NewClient(oauth2.NoContext, creds.TokenSource), nil
}
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, "", " ")
if err != nil {
return fmt.Errorf("writeChangelogAsJSON: error marshalling changelog from: %s to: %s\n%v", source, target, err)
}
if err = ioutil.WriteFile(fileName, jsonData, 0644); err != nil {
return fmt.Errorf("writeChangelogAsJSON: error writing changelog to file: %s\n%v", fileName, err)
}
return nil
}
func generateChangelog(source, target, instance, manifestRepo string) error {
start := time.Now()
httpClient, err := getHTTPClient()
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, "", -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)
}
if err := writeChangelogAsJSON(source, target, sourceToTargetChanges); err != nil {
log.Errorf("generateChangelog: error writing first changelog with source: %s and target: %s\n%v\n",
source, target, err)
}
if err := writeChangelogAsJSON(target, source, targetToSourceChanges); err != nil {
log.Errorf("generateChangelog: Error writing second changelog with source: %s and target: %s\n%v\n",
target, source, err)
}
log.Infof("Retrieved changelog in %s\n", time.Since(start))
return nil
}
func getBuildForCL(gerrit, fallback, gob, manifestRepo, targetCL string) error {
httpClient, err := getHTTPClient()
if err != nil {
return fmt.Errorf("error creating http client: %v", err)
}
req := &findbuild.BuildRequest{
HTTPClient: httpClient,
GerritHost: gerrit,
GitilesHost: gob,
ManifestRepo: manifestRepo,
CL: targetCL,
}
buildData, clErr := findbuild.FindBuild(req)
if clErr != nil && clErr.HTTPCode() == "404" {
log.Debugf("Query failed on Gerrit url %s and Gitiles url %s, retrying with fallback urls", externalGerritURL, externalGoBURL)
fallbackReq := &findbuild.BuildRequest{
HTTPClient: httpClient,
GerritHost: fallback,
GitilesHost: gob,
ManifestRepo: manifestRepo,
CL: targetCL,
}
buildData, clErr = findbuild.FindBuild(fallbackReq)
}
if clErr != nil {
return clErr
}
fmt.Printf("Build: %s\n", buildData.BuildNum)
return nil
}
func main() {
var mode, gobURL, gerritURL, fallbackURL, manifestRepo string
var debug bool
app := &cli.App{
Name: "changelogctl",
Usage: "get commits between builds or first build containing CL",
Description: fmt.Sprintf("%s\n %s",
"changelog usage: ./changelogctl -m changelog [build-number || image-name] [build-number || image-name]",
"findbuild usage: ./changelogctl -m findbuild [CL-number || commit-SHA]",
),
Flags: []cli.Flag{
&cli.StringFlag{
Name: "mode",
Value: "",
Aliases: []string{"m"},
Usage: "Specify query mode. Acceptable values: changelog | findbuild",
Destination: &mode,
Required: true,
},
&cli.StringFlag{
Name: "gerrit",
Value: externalGerritURL,
Usage: "Gerrit `URL` to query from",
Destination: &gerritURL,
},
&cli.StringFlag{
Name: "fallback",
Value: fallbackGerritURL,
Usage: "Fallback Gerrit `URL` to query from",
Destination: &fallbackURL,
},
&cli.StringFlag{
Name: "gob",
Value: externalGoBURL,
Usage: "Git on Borg `URL` to query from",
Destination: &gobURL,
},
&cli.StringFlag{
Name: "repo",
Value: externalManifestRepo,
Aliases: []string{"r"},
Usage: "`REPO` containing Manifest file",
Destination: &manifestRepo,
},
&cli.BoolFlag{
Name: "debug",
Value: false,
Aliases: []string{"d"},
Usage: "Toggle debug messages",
Destination: &debug,
},
},
Action: func(c *cli.Context) error {
if debug {
log.SetLevel(log.DebugLevel)
}
switch mode {
case "findbuild":
if c.NArg() != 1 {
return errors.New("must specify CL number (ex. 3280) or commit SHA (ex. 18d4ce48c1dc2f530120f85973fec348367f78a0)")
}
targetCL := c.Args().Get(0)
return getBuildForCL(gerritURL, fallbackURL, gobURL, manifestRepo, targetCL)
case "changelog":
if c.NArg() != 2 {
return errors.New("must specify two build numbers (ex. 13310.1034.0) or image names (ex. cos-rc-85-13310-1034-0) to retrieve changelog")
}
source := c.Args().Get(0)
target := c.Args().Get(1)
return generateChangelog(source, target, gobURL, manifestRepo)
default:
return fmt.Errorf("please specify either \"findbuild\" or \"changelog\" mode")
}
},
}
err := app.Run(os.Args)
if err != nil {
log.Errorf("main: error running app with arguments: %v:\n%v", os.Args, err)
os.Exit(1)
}
}