Add weekly CIPD package roller
Change-Id: I402c937f2d8d89640059648122e01e0254be89d0
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/254579
Commit-Queue: Eric Boren <borenet@google.com>
Reviewed-by: Ravi Mistry <rmistry@google.com>
diff --git a/cipd.ensure b/cipd.ensure
index c10d810..ff5a70c 100644
--- a/cipd.ensure
+++ b/cipd.ensure
@@ -1,5 +1,4 @@
# This file specifies the CIPD packages and versions used in this repo.
-# TODO(borenet): These versions should be auto-rolled.
# The CIPD server to use.
$ServiceURL https://chrome-infra-packages.appspot.com/
diff --git a/go/cipd/cipd.go b/go/cipd/cipd.go
index f495d20..39b9e4c 100644
--- a/go/cipd/cipd.go
+++ b/go/cipd/cipd.go
@@ -9,12 +9,15 @@
import (
"context"
"fmt"
+ "io"
"net/http"
"go.chromium.org/luci/cipd/client/cipd"
+ "go.chromium.org/luci/cipd/client/cipd/ensure"
"go.chromium.org/luci/cipd/common"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
+ "go.skia.org/infra/go/util"
)
const (
@@ -65,6 +68,13 @@
return fmt.Sprintf("%s:%s:%s", p.Path, p.Name, p.Version)
}
+// PackageSlice is used for sorting packages by name.
+type PackageSlice []*Package
+
+func (s PackageSlice) Len() int { return len(s) }
+func (s PackageSlice) Less(i, j int) bool { return s[i].Name < s[j].Name }
+func (s PackageSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
// GetPackage returns the definition for the package with the given name, or an
// error if the package does not exist in the registry.
func GetPackage(pkg string) (*Package, error) {
@@ -106,6 +116,34 @@
return cipdClient.Ensure(ctx, packages...)
}
+// ParseEnsureFile parses a CIPD ensure file and returns a slice of Packages.
+func ParseEnsureFile(file string) ([]*Package, error) {
+ var ensureFile *ensure.File
+ if err := util.WithReadFile(file, func(r io.Reader) error {
+ f, err := ensure.ParseFile(r)
+ if err == nil {
+ ensureFile = f
+ }
+ return err
+ }); err != nil {
+ return nil, skerr.Wrapf(err, "Failed to parse CIPD ensure file %s", file)
+ }
+ var rv []*Package
+ for subdir, pkgSlice := range ensureFile.PackagesBySubdir {
+ if subdir == "" {
+ subdir = "."
+ }
+ for _, pkg := range pkgSlice {
+ rv = append(rv, &Package{
+ Path: subdir,
+ Name: pkg.PackageTemplate,
+ Version: pkg.UnresolvedVersion,
+ })
+ }
+ }
+ return rv, nil
+}
+
// CIPDClient is the interface for interactions with the CIPD API.
type CIPDClient interface {
cipd.Client
diff --git a/go/cipd/gen_versions.go b/go/cipd/gen_versions.go
index b2fa657..e5c57f8 100644
--- a/go/cipd/gen_versions.go
+++ b/go/cipd/gen_versions.go
@@ -18,7 +18,6 @@
"sort"
"strings"
- "go.chromium.org/luci/cipd/client/cipd/ensure"
"go.skia.org/infra/go/cipd"
"go.skia.org/infra/go/exec"
"go.skia.org/infra/go/sklog"
@@ -41,74 +40,50 @@
pkgDir := path.Dir(filename)
rootDir := path.Join(pkgDir, "..", "..")
+ // Read packages from cipd.ensure.
+ pkgs, err := cipd.ParseEnsureFile(filepath.Join(rootDir, "cipd.ensure"))
+ if err != nil {
+ sklog.Fatal(err)
+ }
+
// List the assets.
assetsDir := path.Join(rootDir, "infra", "bots", "assets")
entries, err := ioutil.ReadDir(assetsDir)
if err != nil {
sklog.Fatal(err)
}
- pkgs := map[string]*cipd.Package{}
for _, e := range entries {
if e.IsDir() {
contents, err := ioutil.ReadFile(path.Join(assetsDir, e.Name(), "VERSION"))
if err == nil {
name := e.Name()
fullName := fmt.Sprintf("skia/bots/%s", name)
- pkgs[fullName] = &cipd.Package{
+ pkgs = append(pkgs, &cipd.Package{
Path: name,
Name: fullName,
Version: cipd.VersionTag(strings.TrimSpace(string(contents))),
- }
+ })
} else if !os.IsNotExist(err) {
sklog.Fatal(err)
}
}
}
- // Read packages from cipd.ensure.
- var ensureFile *ensure.File
- if err := util.WithReadFile(filepath.Join(rootDir, "cipd.ensure"), func(r io.Reader) error {
- f, err := ensure.ParseFile(r)
- if err == nil {
- ensureFile = f
- }
- return err
- }); err != nil {
- sklog.Fatal(err)
- }
- for subdir, pkgSlice := range ensureFile.PackagesBySubdir {
- if subdir == "" {
- subdir = "."
- }
- for _, pkg := range pkgSlice {
- pkgs[pkg.PackageTemplate] = &cipd.Package{
- Path: subdir,
- Name: pkg.PackageTemplate,
- Version: pkg.UnresolvedVersion,
- }
- }
- }
-
// Write the file.
- pkgNames := make([]string, 0, len(pkgs))
- for name := range pkgs {
- pkgNames = append(pkgNames, name)
- }
- sort.Strings(pkgNames)
+ sort.Sort(cipd.PackageSlice(pkgs))
targetFile := path.Join(pkgDir, TARGET_FILE)
if err := util.WithWriteFile(targetFile, func(w io.Writer) error {
_, err := w.Write([]byte(HEADER))
if err != nil {
return err
}
- for _, name := range pkgNames {
- pkg := pkgs[name]
+ for _, pkg := range pkgs {
_, err := fmt.Fprintf(w, fmt.Sprintf(` "%s": &Package{
Path: "%s",
Name: "%s",
Version: "%s",
},
-`, name, pkg.Path, pkg.Name, pkg.Version))
+`, pkg.Name, pkg.Path, pkg.Name, pkg.Version))
if err != nil {
return err
}
diff --git a/infra/bots/gen_tasks.go b/infra/bots/gen_tasks.go
index d8668f5..983f110 100644
--- a/infra/bots/gen_tasks.go
+++ b/infra/bots/gen_tasks.go
@@ -54,6 +54,7 @@
// Top-level list of all Jobs to run at each commit.
JOBS = []string{
"Housekeeper-Nightly-UpdateGoDeps",
+ "Housekeeper-Weekly-UpdateCIPDPackages",
"Housekeeper-OnDemand-Presubmit",
"Infra-PerCommit-Build",
"Infra-PerCommit-Small",
@@ -401,6 +402,41 @@
return name
}
+func updateCIPDPackages(b *specs.TasksCfgBuilder, name string) string {
+ cipd := append([]*specs.CipdPackage{}, specs.CIPD_PKGS_GIT...)
+ cipd = append(cipd, b.MustGetCipdPackageFromAsset("protoc"))
+
+ machineType := MACHINE_TYPE_MEDIUM
+ t := &specs.TaskSpec{
+ CipdPackages: cipd,
+ Command: []string{
+ "./roll_cipd_packages",
+ "--project_id", "skia-swarming-bots",
+ "--task_id", specs.PLACEHOLDER_TASK_ID,
+ "--task_name", name,
+ "--workdir", ".",
+ "--gerrit_project", "buildbot",
+ "--gerrit_url", "https://skia-review.googlesource.com",
+ "--repo", specs.PLACEHOLDER_REPO,
+ "--reviewers", "borenet@google.com",
+ "--revision", specs.PLACEHOLDER_REVISION,
+ "--patch_issue", specs.PLACEHOLDER_ISSUE,
+ "--patch_set", specs.PLACEHOLDER_PATCHSET,
+ "--patch_server", specs.PLACEHOLDER_CODEREVIEW_SERVER,
+ "--alsologtostderr",
+ },
+ Dependencies: []string{buildTaskDrivers(b, "Linux", "x86_64")},
+ Dimensions: linuxGceDimensions(machineType),
+ EnvPrefixes: map[string][]string{
+ "PATH": {"cipd_bin_packages", "cipd_bin_packages/bin", "go/go/bin"},
+ },
+ Isolate: "empty.isolate",
+ ServiceAccount: SERVICE_ACCOUNT_RECREATE_SKPS,
+ }
+ b.MustAddTask(name, t)
+ return name
+}
+
// process generates Tasks and Jobs for the given Job name.
func process(b *specs.TasksCfgBuilder, name string) {
var priority float64 // Leave as default for most jobs.
@@ -412,6 +448,9 @@
} else if strings.Contains(name, "UpdateGoDeps") {
// Update Go deps bot.
deps = append(deps, updateGoDeps(b, name))
+ } else if strings.Contains(name, "UpdateCIPDPackages") {
+ // Update CIPD packages bot.
+ deps = append(deps, updateCIPDPackages(b, name))
} else {
// Infra tests.
if strings.Contains(name, "Infra-PerCommit") {
diff --git a/infra/bots/task_drivers/roll_cipd_packages/roll_cipd_packages.go b/infra/bots/task_drivers/roll_cipd_packages/roll_cipd_packages.go
new file mode 100644
index 0000000..98045ee
--- /dev/null
+++ b/infra/bots/task_drivers/roll_cipd_packages/roll_cipd_packages.go
@@ -0,0 +1,229 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "path"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "go.skia.org/infra/go/auth"
+ "go.skia.org/infra/go/cipd"
+ "go.skia.org/infra/go/gitiles"
+ "go.skia.org/infra/go/sklog"
+ "go.skia.org/infra/go/vcsinfo"
+ "go.skia.org/infra/task_driver/go/lib/auth_steps"
+ "go.skia.org/infra/task_driver/go/lib/checkout"
+ "go.skia.org/infra/task_driver/go/lib/gerrit_steps"
+ "go.skia.org/infra/task_driver/go/lib/golang"
+ "go.skia.org/infra/task_driver/go/lib/os_steps"
+ "go.skia.org/infra/task_driver/go/td"
+)
+
+const (
+ // Tag indicating the most recently uploaded version of a CIPD package.
+ TAG_LATEST = "latest"
+
+ // Tag prefixes.
+ TAG_PREFIX_VERSION = "version:"
+ TAG_PREFIX_REPO = "git_repository:"
+ TAG_PREFIX_REVISION = "git_revision:"
+)
+
+var (
+ // Required properties for this task.
+ gerritProject = flag.String("gerrit_project", "", "Gerrit project name.")
+ gerritUrl = flag.String("gerrit_url", "", "URL of the Gerrit server.")
+ projectId = flag.String("project_id", "", "ID of the Google Cloud project.")
+ reviewers = flag.String("reviewers", "", "Comma-separated list of emails to review the CL.")
+ taskId = flag.String("task_id", "", "ID of this task.")
+ taskName = flag.String("task_name", "", "Name of the task.")
+ workdir = flag.String("workdir", ".", "Working directory")
+
+ checkoutFlags = checkout.SetupFlags(nil)
+
+ // Optional flags.
+ local = flag.Bool("local", false, "True if running locally (as opposed to on the bots)")
+ output = flag.String("o", "", "If provided, dump a JSON blob of step data to the given file. Prints to stdout if '-' is given.")
+)
+
+func main() {
+ // Setup.
+ ctx := td.StartRun(projectId, taskId, taskName, output, local)
+ defer td.EndRun(ctx)
+
+ rs, err := checkout.GetRepoState(checkoutFlags)
+ if err != nil {
+ td.Fatal(ctx, err)
+ }
+ if *gerritProject == "" {
+ td.Fatalf(ctx, "--gerrit_project is required.")
+ }
+ if *gerritUrl == "" {
+ td.Fatalf(ctx, "--gerrit_url is required.")
+ }
+
+ wd, err := os_steps.Abs(ctx, *workdir)
+ if err != nil {
+ td.Fatal(ctx, err)
+ }
+
+ // Check out the code.
+ co, err := checkout.EnsureGitCheckout(ctx, path.Join(wd, "repo"), rs)
+ if err != nil {
+ td.Fatal(ctx, err)
+ }
+
+ // Setup go.
+ ctx = golang.WithEnv(ctx, wd)
+
+ // Read packages from cipd.ensure.
+ ensureFile := filepath.Join(co.Dir(), "cipd.ensure")
+ var pkgs []*cipd.Package
+ if err := td.Do(ctx, td.Props("Read cipd.ensure").Infra(), func(ctx context.Context) error {
+ pkgs, err = cipd.ParseEnsureFile(ensureFile)
+ if err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ td.Fatal(ctx, err)
+ }
+ sort.Sort(cipd.PackageSlice(pkgs))
+
+ // Find the latest versions of the desired packages.
+ c, err := auth_steps.InitHttpClient(ctx, *local, auth.SCOPE_USERINFO_EMAIL, auth.SCOPE_GERRIT)
+ if err != nil {
+ td.Fatal(ctx, err)
+ }
+ var cc *cipd.Client
+ if err := td.Do(ctx, td.Props("Create CIPD client").Infra(), func(ctx context.Context) error {
+ cc, err = cipd.NewClient(c, *workdir)
+ if err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ td.Fatal(ctx, err)
+ }
+ newVersions := make(map[*cipd.Package]string, len(pkgs))
+ if err := td.Do(ctx, td.Props("Get latest package versions").Infra(), func(ctx context.Context) error {
+ for _, pkg := range pkgs {
+ // Fill in the placeholders.
+ name := pkg.Name
+ for placeholder, val := range map[string]string{
+ "arch": "amd64",
+ "os": "linux",
+ "platform": "linux-amd64",
+ } {
+ name = strings.ReplaceAll(name, fmt.Sprintf("${%s}", placeholder), val)
+ }
+ if err := td.Do(ctx, td.Props(fmt.Sprintf("Find latest %s", name)).Infra(), func(ctx context.Context) error {
+ // Find the latest version of the package.
+ pin, err := cc.ResolveVersion(ctx, name, TAG_LATEST)
+ if err != nil {
+ return err
+ }
+ // Retrieve details of the package instance, including the full
+ // set of refs and tags.
+ desc, err := cc.Describe(ctx, name, pin.InstanceID)
+ if err != nil {
+ return err
+ }
+ newVersionTag := ""
+ tags := make([]string, 0, len(desc.Tags))
+ var repos []string
+ var revs []string
+ for _, tag := range desc.Tags {
+ tags = append(tags, tag.Tag)
+
+ // First preference: "version"
+ if strings.HasPrefix(tag.Tag, TAG_PREFIX_VERSION) {
+ newVersionTag = tag.Tag
+ break
+ }
+ // Fall back to choosing the most recent
+ // tagged commit based on repo+revision.
+ if strings.HasPrefix(tag.Tag, TAG_PREFIX_REPO) {
+ repos = append(repos, strings.TrimPrefix(tag.Tag, TAG_PREFIX_REPO))
+ }
+ if strings.HasPrefix(tag.Tag, TAG_PREFIX_REVISION) {
+ revs = append(revs, strings.TrimPrefix(tag.Tag, TAG_PREFIX_REVISION))
+ }
+ }
+ if newVersionTag == "" {
+ // If more than one repo is listed, we need to match the
+ // git_revision to the correct repo in order to obtain the
+ // timestamp.
+ // TODO(borenet): Is there ever more than one git_repository?
+ commits := make([]*vcsinfo.LongCommit, 0, len(revs))
+ for _, repo := range repos {
+ r := gitiles.NewRepo(repo, c)
+ for _, rev := range revs {
+ // Ignore any error, in case we're looking
+ // at the wrong repo.
+ details, err := r.Details(ctx, rev)
+ if err == nil {
+ // Sanity check.
+ if details.Hash == rev {
+ commits = append(commits, details)
+ } else {
+ sklog.Errorf("Retrieved commit details do not match git_revision tag: expect %q but got %q", rev, details.Hash)
+ }
+ }
+ }
+ }
+ if len(commits) > 0 {
+ // Sort by timestamp, most recent first.
+ sort.Sort(vcsinfo.LongCommitSlice(commits))
+ newVersionTag = TAG_PREFIX_REVISION + commits[0].Hash
+ }
+ }
+ if newVersionTag == "" {
+ return fmt.Errorf("Unable to find a valid version tag in %+v", tags)
+ }
+ newVersions[pkg] = newVersionTag
+ return nil
+ }); err != nil {
+ return err
+ }
+ }
+ return nil
+ }); err != nil {
+ td.Fatal(ctx, err)
+ }
+
+ // Write the new ensure file; we read the original and find-and-replace
+ // the package versions.
+ ensureBytes, err := os_steps.ReadFile(ctx, ensureFile)
+ if err != nil {
+ td.Fatal(ctx, err)
+ }
+ oldLines := strings.Split(string(ensureBytes), "\n")
+ newLines := make([]string, 0, len(oldLines))
+ for _, line := range oldLines {
+ for _, pkg := range pkgs {
+ if strings.HasPrefix(line, pkg.Name) {
+ line = strings.ReplaceAll(line, pkg.Version, newVersions[pkg])
+ break
+ }
+ }
+ newLines = append(newLines, line)
+ }
+ if err := os_steps.WriteFile(ctx, ensureFile, []byte(strings.Join(newLines, "\n")), os.ModePerm); err != nil {
+ td.Fatal(ctx, err)
+ }
+
+ // If we changed anything, upload a CL.
+ g, err := gerrit_steps.Init(ctx, *local, wd, *gerritUrl)
+ if err != nil {
+ td.Fatal(ctx, err)
+ }
+ isTryJob := *local || rs.Issue != ""
+ if err := gerrit_steps.UploadCL(ctx, g, co, *gerritProject, "master", rs.Revision, "Update CIPD Packages", strings.Split(*reviewers, ","), isTryJob); err != nil {
+ td.Fatal(ctx, err)
+ }
+}
diff --git a/infra/bots/task_drivers/update_go_deps/update_go_deps.go b/infra/bots/task_drivers/update_go_deps/update_go_deps.go
index 114c5ea..bc4449c 100644
--- a/infra/bots/task_drivers/update_go_deps/update_go_deps.go
+++ b/infra/bots/task_drivers/update_go_deps/update_go_deps.go
@@ -1,12 +1,10 @@
package main
import (
- "context"
"flag"
"path"
"strings"
- "go.skia.org/infra/go/gerrit"
"go.skia.org/infra/task_driver/go/lib/checkout"
"go.skia.org/infra/task_driver/go/lib/gerrit_steps"
"go.skia.org/infra/task_driver/go/lib/golang"
@@ -90,46 +88,12 @@
}
// If we changed anything, upload a CL.
- diff, err := co.Git(ctx, "diff", "--name-only")
+ g, err := gerrit_steps.Init(ctx, *local, wd, *gerritUrl)
if err != nil {
td.Fatal(ctx, err)
}
- diff = strings.TrimSpace(diff)
- modFiles := strings.Split(diff, "\n")
- if len(modFiles) > 0 && diff != "" {
- g, err := gerrit_steps.Init(ctx, *local, wd, *gerritUrl)
- if err != nil {
- td.Fatal(ctx, err)
- }
- if err := td.Do(ctx, td.Props("Upload CL").Infra(), func(ctx context.Context) error {
- ci, err := gerrit.CreateAndEditChange(ctx, g, *gerritProject, "master", "Update Go deps", rs.Revision, func(ctx context.Context, g gerrit.GerritInterface, ci *gerrit.ChangeInfo) error {
- for _, f := range modFiles {
- contents, err := os_steps.ReadFile(ctx, path.Join(co.Dir(), f))
- if err != nil {
- return err
- }
- if err := g.EditFile(ctx, ci, f, string(contents)); err != nil {
- return err
- }
- }
- return nil
- })
- if err != nil {
- return err
- }
- var labels map[string]int
- if !*local && rs.Issue == "" {
- labels = map[string]int{
- gerrit.CODEREVIEW_LABEL: gerrit.CODEREVIEW_LABEL_APPROVE,
- gerrit.COMMITQUEUE_LABEL: gerrit.COMMITQUEUE_LABEL_SUBMIT,
- }
- }
- if err := g.SetReview(ctx, ci, "Ready for review.", labels, strings.Split(*reviewers, ",")); err != nil {
- return err
- }
- return nil
- }); err != nil {
- td.Fatal(ctx, err)
- }
+ isTryJob := *local || rs.Issue != ""
+ if err := gerrit_steps.UploadCL(ctx, g, co, *gerritProject, "master", rs.Revision, "Update Go Deps", strings.Split(*reviewers, ","), isTryJob); err != nil {
+ td.Fatal(ctx, err)
}
}
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index 8b7e3aa..7740a45 100755
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -13,6 +13,11 @@
],
"trigger": "on demand"
},
+ "Housekeeper-Weekly-UpdateCIPDPackages": {
+ "tasks": [
+ "Housekeeper-Weekly-UpdateCIPDPackages"
+ ]
+ },
"Infra-Experimental-Small-Linux": {
"tasks": [
"Infra-Experimental-Small-Linux"
@@ -391,6 +396,77 @@
"idempotent": true,
"isolate": "recipes.isolate"
},
+ "Housekeeper-Weekly-UpdateCIPDPackages": {
+ "cipd_packages": [
+ {
+ "name": "infra/git/${platform}",
+ "path": "cipd_bin_packages",
+ "version": "version:2.23.0.chromium16"
+ },
+ {
+ "name": "infra/tools/git/${platform}",
+ "path": "cipd_bin_packages",
+ "version": "git_revision:0275b342af7f4ef18f4513f80d3b0e5c1bb3fb6c"
+ },
+ {
+ "name": "infra/tools/luci/git-credential-luci/${platform}",
+ "path": "cipd_bin_packages",
+ "version": "git_revision:2c805f1c716f6c5ad2126b27ec88b8585a09481e"
+ },
+ {
+ "name": "skia/bots/protoc",
+ "path": "protoc",
+ "version": "version:0"
+ }
+ ],
+ "command": [
+ "./roll_cipd_packages",
+ "--project_id",
+ "skia-swarming-bots",
+ "--task_id",
+ "<(TASK_ID)",
+ "--task_name",
+ "Housekeeper-Weekly-UpdateCIPDPackages",
+ "--workdir",
+ ".",
+ "--gerrit_project",
+ "buildbot",
+ "--gerrit_url",
+ "https://skia-review.googlesource.com",
+ "--repo",
+ "<(REPO)",
+ "--reviewers",
+ "borenet@google.com",
+ "--revision",
+ "<(REVISION)",
+ "--patch_issue",
+ "<(ISSUE)",
+ "--patch_set",
+ "<(PATCHSET)",
+ "--patch_server",
+ "<(CODEREVIEW_SERVER)",
+ "--alsologtostderr"
+ ],
+ "dependencies": [
+ "Housekeeper-PerCommit-BuildTaskDrivers-Linux-x86_64"
+ ],
+ "dimensions": [
+ "pool:Skia",
+ "os:Debian-9.8",
+ "gpu:none",
+ "cpu:x86-64-Haswell_GCE",
+ "machine_type:n1-standard-16"
+ ],
+ "env_prefixes": {
+ "PATH": [
+ "cipd_bin_packages",
+ "cipd_bin_packages/bin",
+ "go/go/bin"
+ ]
+ },
+ "isolate": "empty.isolate",
+ "service_account": "skia-recreate-skps@skia-swarming-bots.iam.gserviceaccount.com"
+ },
"Infra-Experimental-Small-Linux": {
"caches": [
{
diff --git a/task_driver/go/lib/auth_steps/auth_steps.go b/task_driver/go/lib/auth_steps/auth_steps.go
index 5d02a92..bb31677 100644
--- a/task_driver/go/lib/auth_steps/auth_steps.go
+++ b/task_driver/go/lib/auth_steps/auth_steps.go
@@ -7,8 +7,10 @@
import (
"context"
+ "net/http"
"go.skia.org/infra/go/auth"
+ "go.skia.org/infra/go/httputils"
"go.skia.org/infra/task_driver/go/td"
"golang.org/x/oauth2"
)
@@ -26,3 +28,15 @@
})
return ts, err
}
+
+func HttpClient(ctx context.Context, ts oauth2.TokenSource) *http.Client {
+ return td.HttpClient(ctx, httputils.DefaultClientConfig().WithTokenSource(ts).With2xxOnly().Client())
+}
+
+func InitHttpClient(ctx context.Context, local bool, scopes ...string) (*http.Client, error) {
+ ts, err := Init(ctx, local, scopes...)
+ if err != nil {
+ return nil, err
+ }
+ return HttpClient(ctx, ts), nil
+}
diff --git a/task_driver/go/lib/gerrit_steps/gerrit_steps.go b/task_driver/go/lib/gerrit_steps/gerrit_steps.go
index da379b5..e659c23 100644
--- a/task_driver/go/lib/gerrit_steps/gerrit_steps.go
+++ b/task_driver/go/lib/gerrit_steps/gerrit_steps.go
@@ -7,9 +7,13 @@
import (
"context"
+ "path"
+ "strings"
"go.skia.org/infra/go/gerrit"
+ "go.skia.org/infra/go/git"
"go.skia.org/infra/task_driver/go/lib/git_steps"
+ "go.skia.org/infra/task_driver/go/lib/os_steps"
"go.skia.org/infra/task_driver/go/td"
)
@@ -28,3 +32,47 @@
})
return rv, err
}
+
+// UploadCL uploads a CL containing any changes to the given git.Checkout. This
+// is a no-op if there are no changes.
+func UploadCL(ctx context.Context, g gerrit.GerritInterface, co *git.Checkout, project, branch, baseRevision, commitMsg string, reviewers []string, isTryJob bool) error {
+ diff, err := co.Git(ctx, "diff", "--name-only")
+ if err != nil {
+ return err
+ }
+ diff = strings.TrimSpace(diff)
+ modFiles := strings.Split(diff, "\n")
+ if len(modFiles) > 0 && diff != "" {
+ if err := td.Do(ctx, td.Props("Upload CL").Infra(), func(ctx context.Context) error {
+ ci, err := gerrit.CreateAndEditChange(ctx, g, project, branch, commitMsg, baseRevision, func(ctx context.Context, g gerrit.GerritInterface, ci *gerrit.ChangeInfo) error {
+ for _, f := range modFiles {
+ contents, err := os_steps.ReadFile(ctx, path.Join(co.Dir(), f))
+ if err != nil {
+ return err
+ }
+ if err := g.EditFile(ctx, ci, f, string(contents)); err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ var labels map[string]int
+ if !isTryJob {
+ labels = map[string]int{
+ gerrit.CODEREVIEW_LABEL: gerrit.CODEREVIEW_LABEL_APPROVE,
+ gerrit.COMMITQUEUE_LABEL: gerrit.COMMITQUEUE_LABEL_SUBMIT,
+ }
+ }
+ if err := g.SetReview(ctx, ci, "Ready for review.", labels, reviewers); err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/task_driver/go/lib/os_steps/os_steps.go b/task_driver/go/lib/os_steps/os_steps.go
index 4b43d28..751527f 100644
--- a/task_driver/go/lib/os_steps/os_steps.go
+++ b/task_driver/go/lib/os_steps/os_steps.go
@@ -72,6 +72,13 @@
return rv, err
}
+// WriteFile is a wrapper for ioutil.WriteFile.
+func WriteFile(ctx context.Context, path string, data []byte, perm os.FileMode) error {
+ return td.Do(ctx, td.Props(fmt.Sprintf("Write %s", path)).Infra(), func(context.Context) error {
+ return ioutil.WriteFile(path, data, perm)
+ })
+}
+
// Which returns the result of "which <exe>" (or "where <exe>" on Windows).
func Which(ctx context.Context, exe string) (string, error) {
var rv string