blob: a41c7329af8d68a52fc61385c80f479c8a6b84ae [file]
package deepvalidation
import (
"context"
"fmt"
"net/http"
"os"
"path"
"strings"
"sync"
"time"
buildbucketpb "go.chromium.org/luci/buildbucket/proto"
"go.chromium.org/luci/common/errors"
"go.skia.org/infra/autoroll/go/codereview"
"go.skia.org/infra/autoroll/go/config"
"go.skia.org/infra/autoroll/go/repo_manager/child"
"go.skia.org/infra/autoroll/go/repo_manager/child/revision_filter"
"go.skia.org/infra/autoroll/go/repo_manager/common/version_file_common"
"go.skia.org/infra/autoroll/go/repo_manager/parent"
"go.skia.org/infra/autoroll/go/revision"
"go.skia.org/infra/autoroll/go/roller"
"go.skia.org/infra/go/buildbucket"
"go.skia.org/infra/go/cipd"
"go.skia.org/infra/go/docker"
"go.skia.org/infra/go/exec"
"go.skia.org/infra/go/gerrit"
"go.skia.org/infra/go/git"
"go.skia.org/infra/go/git/git_common"
"go.skia.org/infra/go/github"
"go.skia.org/infra/go/gitiles"
"go.skia.org/infra/go/progress"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog/sklogimpl"
)
// DeepValidate performs more in-depth validation of the config file,
// including checks requiring HTTP requests and possibly authentication to
// determine, for example, whether the configured repos, CIPD packages, etc,
// actually exist. This builds on the simple Validate() methods of the config
// structs.
func DeepValidate(ctx context.Context, client, githubHttpClient *http.Client, c *config.Config) error {
bbClient := buildbucket.NewClient(client)
cipdClient, err := cipd.NewClient(ctx, "", cipd.DefaultServiceURL)
if err != nil {
return skerr.Wrap(err)
}
dockerClient, err := docker.NewClient(ctx)
if err != nil {
return skerr.Wrap(err)
}
dv := &deepvalidator{
bbClient: bbClient,
client: client,
cipdClient: cipdClient,
dockerClient: dockerClient,
githubHttpClient: githubHttpClient,
}
ch := make(chan error)
go func() {
ch <- dv.deepValidate(ctx, c)
}()
tick := time.Tick(30 * time.Second)
for {
select {
case err := <-ch:
return err
case <-tick:
fmt.Printf("Still validating %s\n", c.RollerName)
}
}
}
// numMultiValidateWorkers is the size of the worker pool used in
// DeepValidateMulti.
const numMultiValidateWorkers = 10
// DeepValidateMulti validates multiple config files.
func DeepValidateMulti(ctx context.Context, client, githubHttpClient *http.Client, configs map[string]*config.Config) error {
// Override logging to reduce noise from the packages we use.
unSuppressLogs := sklogimpl.SuppressLogs()
defer unSuppressLogs()
type configToValidate struct {
filename string
config *config.Config
}
configsToValidate := make(chan *configToValidate)
go func() {
for file, cfg := range configs {
configsToValidate <- &configToValidate{
filename: file,
config: cfg,
}
}
close(configsToValidate)
}()
pt := progress.New(int64(len(configs)))
pt.AtInterval(ctx, 5*time.Second, func(count, total int64) {
fmt.Printf("Validated %d of %d\n", count, total)
})
var wg sync.WaitGroup
results := sync.Map{}
for range numMultiValidateWorkers {
wg.Add(1)
go func() {
defer wg.Done()
for cfg := range configsToValidate {
if err := DeepValidate(ctx, client, githubHttpClient, cfg.config); err != nil {
results.Store(cfg.filename, err)
}
pt.Inc(1)
}
}()
}
wg.Wait()
var combinedErr error
results.Range(func(file, err any) bool {
combinedErr = errors.Append(combinedErr, skerr.Fmt("Deep validation failed for %s: %s", file, err))
return true
})
return combinedErr
}
// deepvalidator is a helper for running deep validation which wraps up shared
// elements needed by most validation functions.
type deepvalidator struct {
bbClient buildbucket.BuildBucketInterface
client *http.Client
cipdClient cipd.CIPDClient
dockerClient docker.Client
githubHttpClient *http.Client
}
// deepValidate performs deep validation of the Config, making external
// network requests as needed.
func (dv *deepvalidator) deepValidate(ctx context.Context, c *config.Config) error {
if c.GetGerrit() != nil {
if err := dv.gerritConfig(ctx, c.GetGerrit()); err != nil {
return skerr.Wrap(err)
}
}
if c.GetGithub() != nil {
if err := dv.gitHubConfig(ctx, c.GetGithub()); err != nil {
return skerr.Wrap(err)
}
}
for _, reviewer := range c.Reviewer {
if err := dv.reviewer(ctx, reviewer); err != nil {
return skerr.Wrap(err)
}
}
// CommitMsg should never be nil, but that case would be detected by normal
// validation. We allow it to be nil here to simplify testing.
if c.CommitMsg != nil {
if err := dv.commitMsg(ctx, c.CommitMsg); err != nil {
return skerr.Wrap(err)
}
}
var getFileParent, getFileChild version_file_common.GetFileFunc
// RepoManager should never be nil, but that case would be detected by
// normal validation. We allow it to be nil here to simplify testing.
if c.RepoManager != nil {
var err error
switch rm := c.RepoManager.(type) {
case *config.Config_AndroidRepoManager:
getFileParent, getFileChild, err = dv.androidRepoManagerConfig(ctx, rm.AndroidRepoManager)
case *config.Config_CommandRepoManager:
err = dv.commandRepoManagerConfig(ctx, rm.CommandRepoManager)
case *config.Config_FreetypeRepoManager:
getFileParent, getFileChild, err = dv.freeTypeRepoManagerConfig(ctx, rm.FreetypeRepoManager)
case *config.Config_Google3RepoManager:
err = dv.google3RepoManagerConfig(ctx, rm.Google3RepoManager)
case *config.Config_ParentChildRepoManager:
getFileParent, getFileChild, err = dv.parentChildRepoManagerConfig(ctx, rm.ParentChildRepoManager)
default:
return skerr.Fmt("Unknown repo manager type: %s", rm)
}
if err != nil {
return skerr.Wrap(err)
}
}
for _, td := range c.TransitiveDeps {
if err := dv.transitiveDepConfig(ctx, td, getFileParent, getFileChild); err != nil {
return skerr.Wrap(err)
}
}
return nil
}
// gerritConfig performs validation of the GerritConfig, making
// external network requests as needed.
func (dv *deepvalidator) gerritConfig(ctx context.Context, c *config.GerritConfig) error {
cfg, ok := codereview.GerritConfigs[c.Config]
if !ok {
return skerr.Fmt("failed deep-validating GerritConfig: unknown config: %v", c.Config)
}
g, err := gerrit.NewGerritWithConfig(cfg, c.Url, dv.client)
if err != nil {
return skerr.Wrapf(err, "failed deep-validating GerritConfig")
}
results, err := g.Search(ctx, 1, false, gerrit.SearchProject(c.Project))
if err != nil {
return skerr.Wrapf(err, "failed deep-validating GerritConfig")
}
if len(results) == 0 {
return skerr.Fmt("failed deep-validating GerritConfig: no changes found in project %q", c.Project)
}
return nil
}
// gitHubConfig performs validation of the GitHubConfig, making
// external network requests as needed.
func (dv *deepvalidator) gitHubConfig(ctx context.Context, c *config.GitHubConfig) error {
gh, err := github.NewGitHub(ctx, c.RepoOwner, c.RepoName, dv.githubHttpClient)
if err != nil {
return skerr.Wrapf(err, "failed deep-validating GitHubConfig for %s/%s", c.RepoOwner, c.RepoName)
}
// Just perform an arbitrary read request which uses the configured owner
// and repo name.
if _, err := gh.ListOpenPullRequests(); err != nil {
return skerr.Wrapf(err, "failed deep-validating GitHubConfig for %s/%s", c.RepoOwner, c.RepoName)
}
return nil
}
// transitiveDepConfig performs validation of the TransitiveDepConfig,
// making external network requests as needed.
func (dv *deepvalidator) transitiveDepConfig(ctx context.Context, c *config.TransitiveDepConfig, getFileParent, getFileChild version_file_common.GetFileFunc) error {
if err := dv.versionFileConfig(ctx, c.Parent, getFileParent); err != nil {
return skerr.Wrap(err)
}
if err := dv.versionFileConfig(ctx, c.Child, getFileChild); err != nil {
return skerr.Wrap(err)
}
return nil
}
// versionFileConfig performs validation of the VersionFileConfig,
// making external network requests as needed.
func (dv *deepvalidator) versionFileConfig(ctx context.Context, c *config.VersionFileConfig, getFile version_file_common.GetFileFunc) error {
if getFile == nil {
return skerr.Fmt("failed to deep-validate VersionFileConfig: have no way to retrieve files!")
}
deps, _, err := version_file_common.GetPinnedRevs(ctx, []*config.VersionFileConfig{c}, getFile)
if err != nil {
return skerr.Wrapf(err, "failed to deep-validate VersionFileConfig")
}
if _, ok := deps[c.Id]; !ok {
return skerr.Wrapf(err, "failed to deep-validate VersionFileConfig: dependency %q does not exist in %s", c.Id, c.File[0].Path)
}
return nil
}
func makeGitilesGetFileFunc(repo gitiles.GitilesRepo, branch string) version_file_common.GetFileFunc {
return func(ctx context.Context, path string) (string, error) {
b, err := repo.ReadFileAtRef(ctx, path, branch)
return string(b), err
}
}
func (dv *deepvalidator) makeGitilesGetFileFuncFromConfig(c *config.GitilesConfig) (version_file_common.GetFileFunc, error) {
repo, err := gitiles.NewRepoWithClient(c.RepoUrl, dv.client)
if err != nil {
return nil, skerr.Wrap(err)
}
return makeGitilesGetFileFunc(repo, c.Branch), nil
}
// deepValidateGitilesRepo performs deep validation of a Gitiles repo.
func (dv *deepvalidator) deepValidateGitilesRepo(ctx context.Context, repoUrl, branch string) (version_file_common.GetFileFunc, *revision.Revision, error) {
repo, err := gitiles.NewRepoWithClient(repoUrl, dv.client)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
details, err := repo.Details(ctx, branch)
if err != nil {
return nil, nil, skerr.Wrapf(err, "failed to resolve branch %q of repo %q", branch, repoUrl)
}
tipRev := revision.FromLongCommit("", "", details)
return makeGitilesGetFileFunc(repo, tipRev.Id), tipRev, nil
}
// deepValidateGitHubRepo performs deep validation of a GitHub repo.
func (dv *deepvalidator) deepValidateGitHubRepo(ctx context.Context, repoUrl, branch string) (version_file_common.GetFileFunc, *revision.Revision, error) {
repoOwner, repoName, err := github.ParseRepoOwnerAndName(repoUrl)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
gh, err := github.NewGitHub(ctx, repoOwner, repoName, dv.githubHttpClient)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
ref, err := gh.GetReference(repoOwner, repoName, git_common.RefsHeadsPrefix+branch)
if err != nil {
return nil, nil, skerr.Wrapf(err, "failed GetReference for %s/%s @ %s", repoOwner, repoName, branch)
}
commit, err := gh.GetCommit(*ref.Object.SHA)
if err != nil {
return nil, nil, skerr.Wrapf(err, "failed GetCommit for %s/%s @ %s", repoOwner, repoName, *ref.Object.SHA)
}
tipRev := revision.FromLongCommit("", "", github.CommitToLongCommit(commit))
return func(ctx context.Context, path string) (string, error) {
return gh.ReadRawFile(*ref.Object.SHA, path)
}, tipRev, nil
}
// deepValidateGitRepo performs deep validation of an arbitrary Git repo.
func (dv *deepvalidator) deepValidateGitRepo(ctx context.Context, repoUrl, branch string) (version_file_common.GetFileFunc, *revision.Revision, error) {
if strings.Contains(repoUrl, "googlesource") {
return dv.deepValidateGitilesRepo(ctx, repoUrl, branch)
} else if strings.Contains(repoUrl, "github") {
return dv.deepValidateGitHubRepo(ctx, repoUrl, branch)
} else {
return nil, nil, skerr.Fmt("unknown git repo source for %q", repoUrl)
}
}
// google3RepoManagerConfig performs validation of the
// Google3RepoManagerConfig, making external network requests as needed.
func (dv *deepvalidator) google3RepoManagerConfig(ctx context.Context, c *config.Google3RepoManagerConfig) error {
if _, _, err := dv.deepValidateGitRepo(ctx, c.ChildRepo, c.ChildBranch); err != nil {
return skerr.Wrapf(err, "failed deep-validating Google3RepoManagerConfig")
}
return nil
}
// androidRepoManagerConfig performs validation of the
// AndroidRepoManagerConfig, making external network requests as needed.
func (dv *deepvalidator) androidRepoManagerConfig(ctx context.Context, c *config.AndroidRepoManagerConfig) (version_file_common.GetFileFunc, version_file_common.GetFileFunc, error) {
getFileParent, _, err := dv.deepValidateGitRepo(ctx, c.ParentRepoUrl, c.ParentBranch)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
getFileChild, tipRev, err := dv.deepValidateGitRepo(ctx, c.ChildRepoUrl, c.ChildBranch)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
if c.PreUploadCommands != nil {
if err := dv.preUploadConfig(ctx, c.PreUploadCommands, tipRev, getFileParent); err != nil {
return nil, nil, skerr.Wrap(err)
}
}
return getFileParent, getFileChild, nil
}
// commandRepoManagerConfig performs validation of the
// CommandRepoManagerConfig, making external network requests as needed.
func (dv *deepvalidator) commandRepoManagerConfig(ctx context.Context, c *config.CommandRepoManagerConfig) error {
getFile, _, err := dv.gitCheckoutConfig(ctx, c.GitCheckout)
if err != nil {
return skerr.Wrap(err)
}
tipRev := makeFakeRevision()
if err := dv.commandRepoManagerConfig_CommandConfig(ctx, c.GetTipRev, tipRev, getFile); err != nil {
return skerr.Wrap(err)
}
if err := dv.commandRepoManagerConfig_CommandConfig(ctx, c.GetPinnedRev, tipRev, getFile); err != nil {
return skerr.Wrap(err)
}
if err := dv.commandRepoManagerConfig_CommandConfig(ctx, c.SetPinnedRev, tipRev, getFile); err != nil {
return skerr.Wrap(err)
}
return nil
}
func fakePlaceholders(tipRev *revision.Revision) parent.Placeholders {
return parent.Placeholders{
CIPDRoot: "/fake/cipd/root",
ParentRepoDir: "/fake/parent/repo/dir",
PathVar: os.Getenv("PATH"),
RollingFromID: "rolling-from",
RollingToID: tipRev.Id,
}
}
func deepValidateCommand(ctx context.Context, cmd []string, cwd string, env []string, tipRev *revision.Revision, getFile version_file_common.GetFileFunc) error {
placeholders := fakePlaceholders(tipRev)
ctx = exec.WithOverrideExecutableExists(ctx, func(fp string) bool {
if path.Base(fp) == fp {
// We're expecting to find the executable in PATH. If any
// subdirectory of the CIPD root is in PATH, we can't confirm
// whether the executable exists without downloading the packages,
// so we have to assume it'll be present when the roller actually
// runs.
var pathVar string
for _, envVar := range env {
split := strings.Split(envVar, "=")
if len(split) == 2 && split[0] == "PATH" {
pathVar = split[1]
break
}
}
for _, entry := range strings.Split(pathVar, string(os.PathSeparator)) {
if strings.HasPrefix(entry, placeholders.CIPDRoot) {
return true
}
}
}
if !path.IsAbs(fp) {
// We're looking in the checkout dir.
fp = path.Join(cwd, fp)
_, err := getFile(ctx, fp)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to retrieve file %q: %s\n", fp, err)
}
return err == nil
}
if strings.HasPrefix(fp, placeholders.CIPDRoot+"/") {
// The command is an absolute path within a CIPD package. We can't
// confirm whether the executable exists without downloading the
// packages, so we have to assume it'll be present when the roller
// actually runs.
return true
}
// Fall back to the default.
return exec.DefaultExecutableExists(fp) == nil
})
_, err := placeholders.Command(ctx, cmd, cwd, env)
if err != nil {
return skerr.Wrap(err)
}
return nil
}
// commandRepoManagerConfig_CommandConfig performs validation of the
// CommandRepoManagerConfig_CommandConfig, making external network requests as
// needed.
func (dv *deepvalidator) commandRepoManagerConfig_CommandConfig(ctx context.Context, c *config.CommandRepoManagerConfig_CommandConfig, tipRev *revision.Revision, getFile version_file_common.GetFileFunc) error {
return deepValidateCommand(ctx, c.Command, c.Dir, c.Env, tipRev, getFile)
}
// freeTypeRepoManagerConfig performs validation of the
// FreeTypeRepoManagerConfig, making external network requests as needed.
func (dv *deepvalidator) freeTypeRepoManagerConfig(ctx context.Context, c *config.FreeTypeRepoManagerConfig) (version_file_common.GetFileFunc, version_file_common.GetFileFunc, error) {
getFileChild, _, err := dv.gitilesChildConfig(ctx, c.Child)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
getFileParent, err := dv.freeTypeParentConfig(ctx, c.Parent, getFileChild)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
return getFileParent, getFileChild, nil
}
// parentChildRepoManagerConfig performs validation of the
// ParentChildRepoManagerConfig, making external network requests as needed.
func (dv *deepvalidator) parentChildRepoManagerConfig(ctx context.Context, c *config.ParentChildRepoManagerConfig) (version_file_common.GetFileFunc, version_file_common.GetFileFunc, error) {
var getFileParent, getFileChild version_file_common.GetFileFunc
var tipRev *revision.Revision
var err error
switch child := c.Child.(type) {
case *config.ParentChildRepoManagerConfig_CipdChild:
tipRev, err = dv.cipdChildConfig(ctx, child.CipdChild)
case *config.ParentChildRepoManagerConfig_FuchsiaSdkChild:
tipRev, err = dv.fuchsiaSDKChildConfig(ctx, child.FuchsiaSdkChild)
case *config.ParentChildRepoManagerConfig_GitCheckoutChild:
getFileChild, tipRev, err = dv.gitCheckoutChildConfig(ctx, child.GitCheckoutChild)
case *config.ParentChildRepoManagerConfig_GitCheckoutGithubChild:
getFileChild, tipRev, err = dv.gitCheckoutGitHubChildConfig(ctx, child.GitCheckoutGithubChild)
case *config.ParentChildRepoManagerConfig_GitilesChild:
getFileChild, tipRev, err = dv.gitilesChildConfig(ctx, child.GitilesChild)
case *config.ParentChildRepoManagerConfig_SemverGcsChild:
tipRev, err = dv.semVerGCSChildConfig(ctx, child.SemverGcsChild)
case *config.ParentChildRepoManagerConfig_DockerChild:
tipRev, err = dv.dockerChildConfig(ctx, child.DockerChild)
case *config.ParentChildRepoManagerConfig_GitSemverChild:
getFileChild, tipRev, err = dv.gitSemVerChildConfig(ctx, child.GitSemverChild)
default:
return nil, nil, skerr.Fmt("Unknown child type: %s", child)
}
if err != nil {
return nil, nil, skerr.Wrap(err)
}
switch parent := c.Parent.(type) {
case *config.ParentChildRepoManagerConfig_CopyParent:
getFileParent, err = dv.copyParentConfig(ctx, parent.CopyParent, getFileChild)
case *config.ParentChildRepoManagerConfig_DepsLocalGithubParent:
getFileParent, err = dv.depsLocalGitHubParentConfig(ctx, parent.DepsLocalGithubParent, tipRev, getFileChild)
case *config.ParentChildRepoManagerConfig_DepsLocalGerritParent:
getFileParent, err = dv.depsLocalGerritParentConfig(ctx, parent.DepsLocalGerritParent, tipRev, getFileChild)
case *config.ParentChildRepoManagerConfig_GitCheckoutGithubFileParent:
getFileParent, err = dv.gitCheckoutGitHubFileParentConfig(ctx, parent.GitCheckoutGithubFileParent, tipRev, getFileChild)
case *config.ParentChildRepoManagerConfig_GitCheckoutGerritParent:
getFileParent, err = dv.gitCheckoutGerritParentConfig(ctx, parent.GitCheckoutGerritParent, tipRev, getFileChild)
case *config.ParentChildRepoManagerConfig_GitilesParent:
getFileParent, err = dv.gitilesParentConfig(ctx, parent.GitilesParent, getFileChild)
case *config.ParentChildRepoManagerConfig_GoModGerritParent:
getFileParent, err = dv.goModGerritParentConfig(ctx, parent.GoModGerritParent, tipRev)
default:
return nil, nil, skerr.Fmt("Unknown parent type: %s", parent)
}
if err != nil {
return nil, nil, skerr.Wrap(err)
}
for _, rf := range c.GetBuildbucketRevisionFilter() {
if err := dv.buildbucketRevisionFilterConfig(ctx, rf, tipRev); err != nil {
return nil, nil, skerr.Wrap(err)
}
}
for _, rf := range c.GetCipdRevisionFilter() {
if err := dv.cipdRevisionFilterConfig(ctx, rf, tipRev); err != nil {
return nil, nil, skerr.Wrap(err)
}
}
return getFileParent, getFileChild, nil
}
// gitCheckoutGitHubChildConfig performs validation of the
// GitCheckoutGitHubChildConfig, making external network requests as needed.
func (dv *deepvalidator) gitCheckoutGitHubChildConfig(ctx context.Context, c *config.GitCheckoutGitHubChildConfig) (version_file_common.GetFileFunc, *revision.Revision, error) {
return dv.gitCheckoutChildConfig(ctx, c.GitCheckout)
}
// gitCheckoutConfig performs validation of the GitCheckoutConfig,
// making external network requests as needed.
func (dv *deepvalidator) gitCheckoutConfig(ctx context.Context, c *config.GitCheckoutConfig) (version_file_common.GetFileFunc, *revision.Revision, error) {
getFile, tipRev, err := dv.deepValidateGitRepo(ctx, c.RepoUrl, c.Branch)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
for _, dep := range c.Dependencies {
if err := dv.versionFileConfig(ctx, dep, getFile); err != nil {
return nil, nil, skerr.Wrap(err)
}
}
return getFile, tipRev, nil
}
// gitilesChildConfig performs validation of the GitilesChildConfig,
// making external network requests as needed.
func (dv *deepvalidator) gitilesChildConfig(ctx context.Context, c *config.GitilesChildConfig) (version_file_common.GetFileFunc, *revision.Revision, error) {
repo, err := gitiles.NewRepoWithClient(c.Gitiles.RepoUrl, dv.client)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
child, err := child.NewGitiles(ctx, c, repo)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
tipRev, err := child.GetTipRevision(ctx)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
if _, _, err := child.Update(ctx, tipRev); err != nil {
return nil, nil, skerr.Wrap(err)
}
getFile, err := dv.makeGitilesGetFileFuncFromConfig(c.Gitiles)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
return getFile, tipRev, nil
}
// gitSemVerChildConfig performs validation of the GitSemVerChildConfig,
// making external network requests as needed.
func (dv *deepvalidator) gitSemVerChildConfig(ctx context.Context, c *config.GitSemVerChildConfig) (version_file_common.GetFileFunc, *revision.Revision, error) {
repo, err := gitiles.NewRepoWithClient(c.Gitiles.RepoUrl, dv.client)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
child, err := child.NewGitSemVerChild(ctx, c, repo)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
tipRev, err := child.GetTipRevision(ctx)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
if _, _, err := child.Update(ctx, tipRev); err != nil {
return nil, nil, skerr.Wrap(err)
}
getFile, err := dv.makeGitilesGetFileFuncFromConfig(c.Gitiles)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
return getFile, tipRev, nil
}
// freeTypeParentConfig performs validation of the
// FreeTypeParentConfig, making external network requests as needed.
func (dv *deepvalidator) freeTypeParentConfig(ctx context.Context, c *config.FreeTypeParentConfig, getFileChild version_file_common.GetFileFunc) (version_file_common.GetFileFunc, error) {
// FreeType parent creates a local child repo, but we can get the
// information we need by simply validating the Gitiles part of it.
return dv.gitilesParentConfig(ctx, c.Gitiles, getFileChild)
}
// gitilesParentConfig performs validation of the GitilesParentConfig,
// making external network requests as needed.
func (dv *deepvalidator) gitilesParentConfig(ctx context.Context, c *config.GitilesParentConfig, getFileChild version_file_common.GetFileFunc) (version_file_common.GetFileFunc, error) {
repo, err := gitiles.NewRepoWithClient(c.Gitiles.RepoUrl, dv.client)
if err != nil {
return nil, skerr.Wrap(err)
}
p, err := parent.NewGitilesFile(ctx, c, repo, nil, "")
if err != nil {
return nil, skerr.Wrap(err)
}
if _, err := p.Update(ctx); err != nil {
return nil, skerr.Wrap(err)
}
if err := dv.gerritConfig(ctx, c.Gerrit); err != nil {
return nil, skerr.Wrap(err)
}
getFileParent, err := dv.makeGitilesGetFileFuncFromConfig(c.Gitiles)
if err != nil {
return nil, skerr.Wrap(err)
}
if err := dv.dependencyConfig(ctx, c.Dep, getFileParent, getFileChild); err != nil {
return nil, skerr.Wrap(err)
}
return getFileParent, nil
}
// goModGerritParentConfig performs validation of the
// GoModGerritParentConfig, making external network requests as needed.
func (dv *deepvalidator) goModGerritParentConfig(ctx context.Context, c *config.GoModGerritParentConfig, tipRev *revision.Revision) (version_file_common.GetFileFunc, error) {
if err := dv.gerritConfig(ctx, c.Gerrit); err != nil {
return nil, skerr.Wrap(err)
}
return dv.goModParentConfig(ctx, c.GoMod, tipRev)
}
// goModParentConfig performs validation of the GoModParentConfig,
// making external network requests as needed.
func (dv *deepvalidator) goModParentConfig(ctx context.Context, c *config.GoModParentConfig, tipRev *revision.Revision) (version_file_common.GetFileFunc, error) {
parentGetFile, _, err := dv.gitCheckoutConfig(ctx, c.GitCheckout)
if err != nil {
return nil, skerr.Wrap(err)
}
if c.PreUploadCommands != nil {
if err := dv.preUploadConfig(ctx, c.PreUploadCommands, tipRev, parentGetFile); err != nil {
return nil, skerr.Wrap(err)
}
}
return parentGetFile, nil
}
// dependencyConfig performs validation of the DependencyConfig, making
// external network requests as needed.
func (dv *deepvalidator) dependencyConfig(ctx context.Context, c *config.DependencyConfig, getFileParent, getFileChild version_file_common.GetFileFunc) error {
if err := dv.versionFileConfig(ctx, c.Primary, getFileParent); err != nil {
return skerr.Wrap(err)
}
for _, dep := range c.Transitive {
if err := dv.transitiveDepConfig(ctx, dep, getFileParent, getFileChild); err != nil {
return skerr.Wrap(err)
}
}
return nil
}
// fuchsiaSDKChildConfig performs validation of the
// FuchsiaSDKChildConfig, making external network requests as needed.
func (dv *deepvalidator) fuchsiaSDKChildConfig(ctx context.Context, c *config.FuchsiaSDKChildConfig) (*revision.Revision, error) {
fuchsiaChild, err := child.NewFuchsiaSDK(ctx, c, dv.client)
if err != nil {
return nil, skerr.Wrapf(err, "failed to create fuchsia sdk child")
}
// Create a fake revision to pass into Update. We don't have a real
// last-rolled revision, but Update requires one.
fakeRev := makeFakeRevision()
tipRev, _, err := fuchsiaChild.Update(ctx, fakeRev)
if err != nil {
return nil, skerr.Wrapf(err, "failed to update fuchsia sdk child")
}
return tipRev, nil
}
// cipdChildConfig performs validation of the CIPDChildConfig, making
// external network requests as needed.
func (dv *deepvalidator) cipdChildConfig(ctx context.Context, c *config.CIPDChildConfig) (*revision.Revision, error) {
var repo gitiles.GitilesRepo
if c.GitilesRepo != "" {
r, err := gitiles.NewRepoWithClient(c.GitilesRepo, dv.client)
if err != nil {
return nil, skerr.Wrap(err)
}
repo = r
} else if c.SourceRepo != nil {
r, err := gitiles.NewRepoWithClient(c.SourceRepo.RepoUrl, dv.client)
if err != nil {
return nil, skerr.Wrap(err)
}
repo = r
}
cipdChild, err := child.NewCIPD(ctx, c, dv.cipdClient, repo, "")
if err != nil {
return nil, skerr.Wrapf(err, "failed to create cipd child")
}
// Create a fake revision to pass into Update. We don't have a real
// last-rolled revision, but Update requires one.
fakeRev := makeFakeRevision()
if repo != nil {
// TODO(borenet): Can we rely on main being present?
head, err := repo.Details(ctx, git.MainBranch)
if err != nil {
return nil, skerr.Wrap(err)
}
details, err := repo.Details(ctx, head.Parents[0])
if err != nil {
return nil, skerr.Wrap(err)
}
fakeRev = revision.FromLongCommit("", "", details)
fakeRev.Id = child.CIPDGitRevisionTag(fakeRev.Id)
}
tipRev, _, err := cipdChild.Update(ctx, fakeRev)
if err != nil {
return nil, skerr.Wrap(err)
}
return tipRev, nil
}
// gitCheckoutChildConfig performs validation of the
// GitCheckoutChildConfig, making external network requests as needed.
func (dv *deepvalidator) gitCheckoutChildConfig(ctx context.Context, c *config.GitCheckoutChildConfig) (version_file_common.GetFileFunc, *revision.Revision, error) {
getFile, tipRev, err := dv.gitCheckoutConfig(ctx, c.GitCheckout)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
return getFile, tipRev, nil
}
// semVerGCSChildConfig performs validation of the
// SemVerGCSChildConfig, making external network requests as needed.
func (dv *deepvalidator) semVerGCSChildConfig(ctx context.Context, c *config.SemVerGCSChildConfig) (*revision.Revision, error) {
semverChild, err := child.NewSemVerGCS(ctx, c, dv.client)
if err != nil {
return nil, skerr.Wrapf(err, "failed to create semver gcs child")
}
// Create a fake revision to pass into Update. We don't have a real
// last-rolled revision, but Update requires one.
fakeRev := makeFakeRevision()
tipRev, _, err := semverChild.Update(ctx, fakeRev)
if err != nil {
return nil, skerr.Wrapf(err, "failed to update semver gcs child")
}
if tipRev.Id == fakeRev.Id {
return nil, skerr.Fmt("found no matching objects in %s/%s", c.Gcs.GcsBucket, c.Gcs.GcsPath)
}
return tipRev, nil
}
// copyParentConfig performs validation of the CopyParentConfig, making
// external network requests as needed.
func (dv *deepvalidator) copyParentConfig(ctx context.Context, c *config.CopyParentConfig, getFileChild version_file_common.GetFileFunc) (version_file_common.GetFileFunc, error) {
repo, err := gitiles.NewRepoWithClient(c.Gitiles.Gitiles.RepoUrl, dv.client)
if err != nil {
return nil, skerr.Wrap(err)
}
p, err := parent.NewCopy(ctx, c, repo, nil, "", nil)
if err != nil {
return nil, skerr.Wrap(err)
}
if _, err := p.Update(ctx); err != nil {
return nil, skerr.Wrap(err)
}
// Update() may not check the presence of the copied files; go ahead and
// do that here.
getFileParent, err := dv.makeGitilesGetFileFuncFromConfig(c.Gitiles.Gitiles)
if err != nil {
return nil, skerr.Wrap(err)
}
for _, cpy := range c.Copies {
if err := dv.copyParentConfig_CopyEntry(ctx, cpy, getFileParent, getFileChild); err != nil {
return nil, skerr.Wrap(err)
}
}
return getFileParent, nil
}
// copyParentConfig_CopyEntry performs validation of the
// CopyParentConfig_CopyEntry, making external network requests as needed.
func (dv *deepvalidator) copyParentConfig_CopyEntry(ctx context.Context, c *config.CopyParentConfig_CopyEntry, getFileParent, getFileChild version_file_common.GetFileFunc) error {
if _, err := getFileChild(ctx, c.SrcRelPath); err != nil {
return skerr.Wrapf(err, "failed to read src %q", c.SrcRelPath)
}
if _, err := getFileParent(ctx, c.DstRelPath); err != nil {
return skerr.Wrapf(err, "failed to read dst %q", c.DstRelPath)
}
return nil
}
// depsLocalGitHubParentConfig performs validation of the
// DEPSLocalGitHubParentConfig, making external network requests as needed.
func (dv *deepvalidator) depsLocalGitHubParentConfig(ctx context.Context, c *config.DEPSLocalGitHubParentConfig, tipRev *revision.Revision, getFileChild version_file_common.GetFileFunc) (version_file_common.GetFileFunc, error) {
if err := dv.gitHubConfig(ctx, c.Github); err != nil {
return nil, skerr.Wrap(err)
}
if _, _, err := dv.deepValidateGitHubRepo(ctx, c.ForkRepoUrl, git.MainBranch); err != nil {
return nil, skerr.Wrap(err)
}
return dv.depsLocalParentConfig(ctx, c.DepsLocal, tipRev, getFileChild)
}
// depsLocalParentConfig performs validation of the
// DEPSLocalParentConfig, making external network requests as needed.
func (dv *deepvalidator) depsLocalParentConfig(ctx context.Context, c *config.DEPSLocalParentConfig, tipRev *revision.Revision, getFileChild version_file_common.GetFileFunc) (version_file_common.GetFileFunc, error) {
getFileParent, err := dv.gitCheckoutParentConfig(ctx, c.GitCheckout, getFileChild)
if err != nil {
return nil, skerr.Wrap(err)
}
if c.PreUploadCommands != nil {
if err := dv.preUploadConfig(ctx, c.PreUploadCommands, tipRev, getFileParent); err != nil {
return nil, skerr.Wrap(err)
}
}
return getFileParent, nil
}
// gitCheckoutParentConfig performs validation of the
// GitCheckoutParentConfig, making external network requests as needed.
func (dv *deepvalidator) gitCheckoutParentConfig(ctx context.Context, c *config.GitCheckoutParentConfig, getFileChild version_file_common.GetFileFunc) (version_file_common.GetFileFunc, error) {
getFileParent, _, err := dv.gitCheckoutConfig(ctx, c.GitCheckout)
if err != nil {
return nil, skerr.Wrap(err)
}
if err := dv.dependencyConfig(ctx, c.Dep, getFileParent, getFileChild); err != nil {
return nil, skerr.Wrap(err)
}
return getFileParent, nil
}
// depsLocalGerritParentConfig performs validation of the
// DEPSLocalGerritParentConfig, making external network requests as needed.
func (dv *deepvalidator) depsLocalGerritParentConfig(ctx context.Context, c *config.DEPSLocalGerritParentConfig, tipRev *revision.Revision, getFileChild version_file_common.GetFileFunc) (version_file_common.GetFileFunc, error) {
if err := dv.gerritConfig(ctx, c.Gerrit); err != nil {
return nil, skerr.Wrap(err)
}
return dv.depsLocalParentConfig(ctx, c.DepsLocal, tipRev, getFileChild)
}
// gitCheckoutGitHubFileParentConfig performs validation of the
// GitCheckoutGitHubFileParentConfig, making external network requests as needed.
func (dv *deepvalidator) gitCheckoutGitHubFileParentConfig(ctx context.Context, c *config.GitCheckoutGitHubFileParentConfig, tipRev *revision.Revision, getFileChild version_file_common.GetFileFunc) (version_file_common.GetFileFunc, error) {
getFileParent, err := dv.gitCheckoutGitHubParentConfig(ctx, c.GitCheckout, getFileChild)
if err != nil {
return nil, skerr.Wrap(err)
}
if c.PreUploadCommands != nil {
if err := dv.preUploadConfig(ctx, c.PreUploadCommands, tipRev, getFileParent); err != nil {
return nil, skerr.Wrap(err)
}
}
return getFileParent, nil
}
// gitCheckoutGitHubParentConfig performs validation of the
// GitCheckoutGitHubParentConfig, making external network requests as needed.
func (dv *deepvalidator) gitCheckoutGitHubParentConfig(ctx context.Context, c *config.GitCheckoutGitHubParentConfig, getFileChild version_file_common.GetFileFunc) (version_file_common.GetFileFunc, error) {
// TODO(borenet): Can we rely on main being present?
if _, _, err := dv.deepValidateGitHubRepo(ctx, c.ForkRepoUrl, git.MainBranch); err != nil {
return nil, skerr.Wrap(err)
}
return dv.gitCheckoutParentConfig(ctx, c.GitCheckout, getFileChild)
}
// gitCheckoutGerritParentConfig performs validation of the
// GitCheckoutGerritParentConfig, making external network requests as needed.
func (dv *deepvalidator) gitCheckoutGerritParentConfig(ctx context.Context, c *config.GitCheckoutGerritParentConfig, tipRev *revision.Revision, getFileChild version_file_common.GetFileFunc) (version_file_common.GetFileFunc, error) {
getFileParent, err := dv.gitCheckoutParentConfig(ctx, c.GitCheckout, getFileChild)
if err != nil {
return nil, skerr.Wrap(err)
}
if c.PreUploadCommands != nil {
if err := dv.preUploadConfig(ctx, c.PreUploadCommands, tipRev, getFileParent); err != nil {
return nil, skerr.Wrap(err)
}
}
return getFileParent, nil
}
// buildbucketRevisionFilterConfig performs validation of the
// BuildbucketRevisionFilterConfig, making external network requests as needed.
func (dv *deepvalidator) buildbucketRevisionFilterConfig(ctx context.Context, c *config.BuildbucketRevisionFilterConfig, rev *revision.Revision) error {
filter, err := revision_filter.NewBuildbucketRevisionFilter(dv.bbClient, c)
if err != nil {
return skerr.Wrap(err)
}
if _, err := filter.Skip(ctx, *rev); err != nil {
return skerr.Wrap(err)
}
return nil
}
// cipdRevisionFilterConfig performs validation of the
// CIPDRevisionFilterConfig, making external network requests as needed.
func (dv *deepvalidator) cipdRevisionFilterConfig(ctx context.Context, c *config.CIPDRevisionFilterConfig, rev *revision.Revision) error {
filter, err := revision_filter.NewCIPDRevisionFilter(dv.cipdClient, c)
if err != nil {
return skerr.Wrap(err)
}
// Note: there could still be a problem here even if no error is returned;
// if skipReason is not empty, it could indicate that the filter is in some
// way invalid.
if _, err := filter.Skip(ctx, *rev); err != nil {
return skerr.Wrap(err)
}
return nil
}
// dockerChildConfig performs validation of the DockerChildConfig,
// making external network requests as needed.
func (dv *deepvalidator) dockerChildConfig(ctx context.Context, c *config.DockerChildConfig) (*revision.Revision, error) {
dockerChild, err := child.NewDocker(ctx, c, dv.dockerClient)
if err != nil {
return nil, skerr.Wrapf(err, "failed to create docker child")
}
// Create a fake revision to pass into Update. We don't have a real
// last-rolled revision, but Update requires one.
fakeRev := makeFakeRevision()
tipRev, _, err := dockerChild.Update(ctx, fakeRev)
if err != nil {
return nil, skerr.Wrapf(err, "failed to get revision for tag %q", c.Tag)
}
return tipRev, nil
}
// preUploadConfig performs validation of the PreUploadConfig, making
// external network requests as needed.
func (dv *deepvalidator) preUploadConfig(ctx context.Context, c *config.PreUploadConfig, tipRev *revision.Revision, getFile version_file_common.GetFileFunc) error {
// Note: we don't directly call Placeholders.Command (or
// Placeholders.PreUploadConfig) because that's done in deepValidateCommand.
cipdPkgs, err := fakePlaceholders(tipRev).CIPDPackages(c.CipdPackage)
if err != nil {
return skerr.Wrap(err)
}
for _, pkg := range cipdPkgs {
if _, err := dv.cipdClient.ResolveVersion(ctx, pkg.Name, pkg.Version); err != nil {
return skerr.Wrapf(err, "failed deep-validating CIPDChildConfig")
}
}
for _, cmd := range c.Command {
// TODO(borenet): Callers generally pass in depot tools env to
// RunPreUploadStep.
cmdEnv := exec.MergeEnv(os.Environ(), cmd.Env)
if err := deepValidateCommand(ctx, strings.Fields(cmd.Command), cmd.Cwd, cmdEnv, tipRev, getFile); err != nil {
return skerr.Wrap(err)
}
}
return nil
}
// reviewer performs validation of a reviewer, making external network requests
// as needed.
func (dv *deepvalidator) reviewer(ctx context.Context, reviewer string) error {
_, err := roller.GetReviewersFromURLOrEmail(dv.client, reviewer)
return skerr.Wrap(err)
}
// cqExtraTrybot performs validation of the cqExtraTrybot, making external
// network requests as needed.
func (dv *deepvalidator) cqExtraTrybot(ctx context.Context, trybot string) error {
project, bucket, builders, err := config.ParseTrybotName(trybot)
if err != nil {
return skerr.Wrap(err)
}
if project == "skia" {
// Skia is a special case: we use dynamic builders, which won't have a
// definition that can be found via GetBuilder. Here we just have to
// assume that the builder exists.
return nil
}
for _, builder := range builders {
bbBuilder, err := dv.bbClient.GetBuilder(ctx, &buildbucketpb.GetBuilderRequest{
Id: &buildbucketpb.BuilderID{
Project: project,
Bucket: bucket,
Builder: builder,
},
})
if project == "skia" {
// Skia is a special case: we use dynamic builders, which won't have
// a definition that can be found via GetBuilder. Here we just have
// to assume that the builder exists.
return nil
}
if err != nil {
return skerr.Wrapf(err, "failed to retrieve buildbucket builder for %q (project %q bucket %q builder %q)", trybot, project, bucket, builder)
} else if bbBuilder == nil {
return skerr.Fmt("no buildbucket builder for %q (project %q bucket %q builder %q)", trybot, project, bucket, builder)
}
}
return nil
}
// commitMsg performs validation of the CommitMsg, making external network
// requests as needed.
func (dv *deepvalidator) commitMsg(ctx context.Context, c *config.CommitMsgConfig) error {
for _, trybot := range c.CqExtraTrybots {
if err := dv.cqExtraTrybot(ctx, trybot); err != nil {
return skerr.Wrap(err)
}
}
return nil
}
// makeFakeRevision can be used wherever we need to pass in a Revision but the
// actual value is not important.
func makeFakeRevision() *revision.Revision {
return &revision.Revision{
Id: "fake",
}
}