| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package config |
| |
| import ( |
| "fmt" |
| "path/filepath" |
| "strings" |
| |
| "go.chromium.org/luci/common/errors" |
| |
| admin "infra/tricium/api/admin/v1" |
| tricium "infra/tricium/api/v1" |
| ) |
| |
| // Generate generates a Tricium workflow based on the provided configs and |
| // paths to analyze. |
| // |
| // Previously a workflow would be a tree including isolators. Now, all |
| // functions should be recipe-based analyzers and workflows just have a |
| // list of workers, filtered based on path filters, although in practice, |
| // most path filtering will happen inside recipes. |
| func Generate(sc *tricium.ServiceConfig, pc *tricium.ProjectConfig, |
| files []*tricium.Data_File, gitRef, gitURL string) (*admin.Workflow, error) { |
| mergedFunctions, err := mergeSelectedFunctions(sc, pc) |
| if err != nil { |
| return nil, errors.Annotate(err, "failed to merge function definitions").Err() |
| } |
| var workers []*admin.Worker |
| functions := []*tricium.Function{} |
| for _, s := range pc.Selections { |
| f, ok := mergedFunctions[s.Function] |
| if !ok { |
| return nil, errors.Annotate(err, "failed to lookup project function").Err() |
| } |
| functions = append(functions, f) |
| shouldInclude, err := includeFunction(f, files) |
| if err != nil { |
| return nil, errors.Annotate(err, "failed include function check").Err() |
| } |
| if shouldInclude { |
| w, err := createWorker(s, sc, f, gitRef, gitURL) |
| if err != nil { |
| // The worker will fail creation if it is a legacy |
| // (non-recipe-based) analyzer. Just skip such workers to help |
| // ease the transition. |
| continue |
| } |
| workers = append(workers, w) |
| } |
| } |
| |
| return &admin.Workflow{ |
| Workers: workers, |
| BuildbucketServerHost: sc.BuildbucketServerHost, |
| Functions: functions, |
| }, nil |
| } |
| |
| // includeFunction checks if an analyzer should be included based on paths. |
| // |
| // The paths are checked against the path filters included for the function. If |
| // there are no path filters or no paths, then the function is included |
| // without further checking. With both paths and path filters, there needs to |
| // be at least one path match for the function to be included. |
| // |
| // The path filter only applies to the last part of the path. |
| // |
| // Also, path filters are only provided for analyzers; analyzer functions are |
| // always included regardless of path matching. |
| func includeFunction(f *tricium.Function, files []*tricium.Data_File) (bool, error) { |
| if len(files) == 0 || len(f.PathFilters) == 0 { |
| return true, nil |
| } |
| for _, file := range files { |
| p := file.Path |
| for _, filter := range f.PathFilters { |
| ok, err := filepath.Match(filter, filepath.Base(p)) |
| if err != nil { |
| return false, errors.Reason("failed to check path filter %s for path %s", filter, p).Err() |
| } |
| if ok { |
| return true, nil |
| } |
| } |
| } |
| return false, nil |
| } |
| |
| // createWorker creates a worker from the provided function, selection and |
| // service config. |
| // |
| // The provided function is assumed to be verified. |
| func createWorker(s *tricium.Selection, sc *tricium.ServiceConfig, f *tricium.Function, |
| gitRef, gitURL string) (*admin.Worker, error) { |
| i := tricium.LookupImplForPlatform(f, s.Platform) // If verified, there should be an Impl. |
| // The separator character for worker names is underscore, so this |
| // character shouldn't appear in function or platform names. This |
| // is also checked in config validation. |
| workerName := fmt.Sprintf("%s_%s", s.Function, s.Platform) |
| if strings.Contains(s.Function, "_") || strings.Contains(s.Platform.String(), "_") { |
| return nil, errors.Reason("invalid name when making worker %q", workerName).Err() |
| } |
| w := &admin.Worker{ |
| Name: workerName, |
| Needs: f.Needs, |
| Provides: f.Provides, |
| NeedsForPlatform: i.NeedsForPlatform, |
| ProvidesForPlatform: i.ProvidesForPlatform, |
| RuntimePlatform: i.RuntimePlatform, |
| } |
| switch ii := i.Impl.(type) { |
| case *tricium.Impl_Recipe: |
| w.Impl = &admin.Worker_Recipe{Recipe: ii.Recipe} |
| case nil: |
| return nil, errors.Reason("missing Impl when constructing worker %s", w.Name).Err() |
| default: |
| return nil, errors.Reason("Impl.Impl has unexpected type %T", ii).Err() |
| } |
| return w, nil |
| } |