github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/validator/project_validator.go (about)

     1  package validator
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/evergreen-ci/evergreen"
     9  	"github.com/evergreen-ci/evergreen/model"
    10  	"github.com/evergreen-ci/evergreen/model/distro"
    11  	"github.com/evergreen-ci/evergreen/plugin"
    12  	"github.com/evergreen-ci/evergreen/util"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  type projectValidator func(*model.Project) []ValidationError
    17  
    18  type ValidationErrorLevel int64
    19  
    20  const (
    21  	Error ValidationErrorLevel = iota
    22  	Warning
    23  )
    24  
    25  func (vel ValidationErrorLevel) String() string {
    26  	switch vel {
    27  	case Error:
    28  		return "ERROR"
    29  	case Warning:
    30  		return "WARNING"
    31  	}
    32  	return "?"
    33  }
    34  
    35  type ValidationError struct {
    36  	Level   ValidationErrorLevel `json:"level"`
    37  	Message string               `json:"message"`
    38  }
    39  
    40  // Functions used to validate the syntax of a project configuration file. Any
    41  // validation errors here for remote configuration files are fatal and will
    42  // cause stubs to be created for the project.
    43  var projectSyntaxValidators = []projectValidator{
    44  	ensureHasNecessaryBVFields,
    45  	checkDependencyGraph,
    46  	validatePluginCommands,
    47  	ensureHasNecessaryProjectFields,
    48  	verifyTaskDependencies,
    49  	verifyTaskRequirements,
    50  	validateBVNames,
    51  	validateBVTaskNames,
    52  	checkAllDependenciesSpec,
    53  	validateProjectTaskNames,
    54  	validateProjectTaskIdsAndTags,
    55  }
    56  
    57  // Functions used to validate the semantics of a project configuration file.
    58  // Validations errors here are not fatal. However, it is recommended that the
    59  // suggested corrections are applied.
    60  var projectSemanticValidators = []projectValidator{
    61  	checkTaskCommands,
    62  }
    63  
    64  func (vr ValidationError) Error() string {
    65  	return vr.Message
    66  }
    67  
    68  // create a slice of all valid distro names
    69  func getDistroIds() ([]string, error) {
    70  	// create a slice of all known distros
    71  	distros, err := distro.Find(distro.All)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	distroIds := []string{}
    76  	for _, d := range distros {
    77  		if !util.SliceContains(distroIds, d.Id) {
    78  			distroIds = append(distroIds, d.Id)
    79  		}
    80  	}
    81  	return distroIds, nil
    82  }
    83  
    84  // verify that the project configuration semantics is valid
    85  func CheckProjectSemantics(project *model.Project) []ValidationError {
    86  
    87  	validationErrs := []ValidationError{}
    88  	for _, projectSemanticValidator := range projectSemanticValidators {
    89  		validationErrs = append(validationErrs,
    90  			projectSemanticValidator(project)...)
    91  	}
    92  	return validationErrs
    93  }
    94  
    95  // verify that the project configuration syntax is valid
    96  func CheckProjectSyntax(project *model.Project) ([]ValidationError, error) {
    97  
    98  	validationErrs := []ValidationError{}
    99  	for _, projectSyntaxValidator := range projectSyntaxValidators {
   100  		validationErrs = append(validationErrs,
   101  			projectSyntaxValidator(project)...)
   102  	}
   103  
   104  	// get distroIds for ensureReferentialIntegrity validation
   105  	distroIds, err := getDistroIds()
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	validationErrs = append(validationErrs, ensureReferentialIntegrity(project, distroIds)...)
   110  	return validationErrs, nil
   111  }
   112  
   113  // ensure that if any task spec references 'model.AllDependencies', it
   114  // references no other dependency
   115  func checkAllDependenciesSpec(project *model.Project) []ValidationError {
   116  	errs := []ValidationError{}
   117  	for _, task := range project.Tasks {
   118  		if len(task.DependsOn) > 1 {
   119  			for _, dependency := range task.DependsOn {
   120  				if dependency.Name == model.AllDependencies {
   121  					errs = append(errs,
   122  						ValidationError{
   123  							Message: fmt.Sprintf("task '%v' in project '%v' "+
   124  								"contains the all dependencies (%v)' "+
   125  								"specification and other explicit dependencies",
   126  								project.Identifier, task.Name,
   127  								model.AllDependencies),
   128  						},
   129  					)
   130  				}
   131  			}
   132  		}
   133  	}
   134  	return errs
   135  }
   136  
   137  // Makes sure that the dependencies for the tasks in the project form a
   138  // valid dependency graph (no cycles).
   139  func checkDependencyGraph(project *model.Project) []ValidationError {
   140  	errs := []ValidationError{}
   141  
   142  	// map of task name and variant -> BuildVariantTask
   143  	tasksByNameAndVariant := map[model.TVPair]model.BuildVariantTask{}
   144  
   145  	// generate task nodes for every task and variant combination
   146  	visited := map[model.TVPair]bool{}
   147  	allNodes := []model.TVPair{}
   148  	for _, bv := range project.BuildVariants {
   149  		for _, t := range bv.Tasks {
   150  			t.Populate(project.GetSpecForTask(t.Name))
   151  			node := model.TVPair{bv.Name, t.Name}
   152  
   153  			tasksByNameAndVariant[node] = t
   154  			visited[node] = false
   155  			allNodes = append(allNodes, node)
   156  		}
   157  	}
   158  
   159  	// run through the task nodes, checking their dependency graphs for cycles
   160  	for _, node := range allNodes {
   161  		// the visited nodes
   162  		if err := dependencyCycleExists(node, visited, tasksByNameAndVariant); err != nil {
   163  			errs = append(errs,
   164  				ValidationError{
   165  					Message: fmt.Sprintf(
   166  						"dependency error for '%v' task: %v", node.TaskName, err),
   167  				},
   168  			)
   169  		}
   170  	}
   171  
   172  	return errs
   173  }
   174  
   175  // Helper for checking the dependency graph for cycles.
   176  func dependencyCycleExists(node model.TVPair, visited map[model.TVPair]bool,
   177  	tasksByNameAndVariant map[model.TVPair]model.BuildVariantTask) error {
   178  
   179  	v, ok := visited[node]
   180  	// if the node does not exist, the deps are broken
   181  	if !ok {
   182  		return errors.Errorf("dependency %v is not present in the project config", node)
   183  	}
   184  	// if the task has already been visited, then a cycle certainly exists
   185  	if v {
   186  		return errors.Errorf("dependency %v is part of a dependency cycle", node)
   187  	}
   188  
   189  	visited[node] = true
   190  
   191  	task := tasksByNameAndVariant[node]
   192  	depNodes := []model.TVPair{}
   193  	// build a list of all possible dependency nodes for the task
   194  	for _, dep := range task.DependsOn {
   195  		if dep.Variant != model.AllVariants {
   196  			// handle regular dependencies
   197  			dn := model.TVPair{TaskName: dep.Name}
   198  			if dep.Variant == "" {
   199  				// use the current variant if none is specified
   200  				dn.Variant = node.Variant
   201  			} else {
   202  				dn.Variant = dep.Variant
   203  			}
   204  			// handle * case by grabbing all the variant's tasks that aren't the current one
   205  			if dn.TaskName == model.AllDependencies {
   206  				for n := range visited {
   207  					if n.TaskName != node.TaskName && n.Variant == dn.Variant {
   208  						depNodes = append(depNodes, n)
   209  					}
   210  				}
   211  			} else {
   212  				// normal case: just append the variant
   213  				depNodes = append(depNodes, dn)
   214  			}
   215  		} else {
   216  			// handle the all-variants case by adding all nodes that are
   217  			// of the same task (but not the current node)
   218  			if dep.Name != model.AllDependencies {
   219  				for n := range visited {
   220  					if n.TaskName == dep.Name && (n != node) {
   221  						depNodes = append(depNodes, n)
   222  					}
   223  				}
   224  			} else {
   225  				// edge case where variant and task name are both *
   226  				for n := range visited {
   227  					if n != node {
   228  						depNodes = append(depNodes, n)
   229  					}
   230  				}
   231  			}
   232  		}
   233  	}
   234  
   235  	// for each of the task's dependencies, make a recursive call
   236  	for _, dn := range depNodes {
   237  		if err := dependencyCycleExists(dn, visited, tasksByNameAndVariant); err != nil {
   238  			return err
   239  		}
   240  	}
   241  
   242  	// remove the task from the visited map so that higher-level calls do not see it
   243  	visited[node] = false
   244  
   245  	// no cycle found
   246  	return nil
   247  }
   248  
   249  // Ensures that the project has at least one buildvariant and also that all the
   250  // fields required for any buildvariant definition are present
   251  func ensureHasNecessaryBVFields(project *model.Project) []ValidationError {
   252  	errs := []ValidationError{}
   253  	if len(project.BuildVariants) == 0 {
   254  		return []ValidationError{
   255  			{
   256  				Message: fmt.Sprintf("project '%v' must specify at least one "+
   257  					"buildvariant", project.Identifier),
   258  			},
   259  		}
   260  	}
   261  
   262  	for _, buildVariant := range project.BuildVariants {
   263  		hasTaskWithoutDistro := false
   264  		if buildVariant.Name == "" {
   265  			errs = append(errs,
   266  				ValidationError{
   267  					Message: fmt.Sprintf("project '%v' buildvariant must "+
   268  						"have a name", project.Identifier),
   269  				},
   270  			)
   271  		}
   272  		if len(buildVariant.Tasks) == 0 {
   273  			errs = append(errs,
   274  				ValidationError{
   275  					Message: fmt.Sprintf("buildvariant '%v' in project '%v' "+
   276  						"must have at least one task", buildVariant.Name,
   277  						project.Identifier),
   278  				},
   279  			)
   280  		}
   281  		for _, task := range buildVariant.Tasks {
   282  			if len(task.Distros) == 0 {
   283  				hasTaskWithoutDistro = true
   284  				break
   285  			}
   286  		}
   287  		if hasTaskWithoutDistro && len(buildVariant.RunOn) == 0 {
   288  			errs = append(errs,
   289  				ValidationError{
   290  					Message: fmt.Sprintf("buildvariant '%v' in project '%v' "+
   291  						"must either specify run_on field or have every task "+
   292  						"specify a distro.",
   293  						buildVariant.Name, project.Identifier),
   294  				},
   295  			)
   296  		}
   297  	}
   298  	return errs
   299  }
   300  
   301  // Checks that the basic fields that are required by any project are present.
   302  func ensureHasNecessaryProjectFields(project *model.Project) []ValidationError {
   303  	errs := []ValidationError{}
   304  
   305  	if project.BatchTime < 0 {
   306  		errs = append(errs,
   307  			ValidationError{
   308  				Message: fmt.Sprintf("project '%v' must have a "+
   309  					"non-negative 'batchtime' set", project.Identifier),
   310  			},
   311  		)
   312  	}
   313  
   314  	if project.CommandType != "" {
   315  		if project.CommandType != model.SystemCommandType &&
   316  			project.CommandType != model.TestCommandType {
   317  			errs = append(errs,
   318  				ValidationError{
   319  					Message: fmt.Sprintf("project '%v' contains an invalid "+
   320  						"command type: %v", project.Identifier, project.CommandType),
   321  				},
   322  			)
   323  		}
   324  	}
   325  	return errs
   326  }
   327  
   328  // Ensures that:
   329  // 1. a referenced task within a buildvariant task object exists in
   330  // the set of project tasks
   331  // 2. any referenced distro exists within the current setting's distro directory
   332  func ensureReferentialIntegrity(project *model.Project, distroIds []string) []ValidationError {
   333  	errs := []ValidationError{}
   334  	// create a set of all the task names
   335  	allTaskNames := map[string]bool{}
   336  	for _, task := range project.Tasks {
   337  		allTaskNames[task.Name] = true
   338  	}
   339  
   340  	for _, buildVariant := range project.BuildVariants {
   341  		buildVariantTasks := map[string]bool{}
   342  		for _, task := range buildVariant.Tasks {
   343  			if _, ok := allTaskNames[task.Name]; !ok {
   344  				if task.Name == "" {
   345  					errs = append(errs,
   346  						ValidationError{
   347  							Message: fmt.Sprintf("tasks for buildvariant '%v' "+
   348  								"in project '%v' must each have a name field",
   349  								project.Identifier, buildVariant.Name),
   350  						},
   351  					)
   352  				} else {
   353  					errs = append(errs,
   354  						ValidationError{
   355  							Message: fmt.Sprintf("buildvariant '%v' in "+
   356  								"project '%v' references a non-existent "+
   357  								"task '%v'", buildVariant.Name,
   358  								project.Identifier, task.Name),
   359  						},
   360  					)
   361  				}
   362  			}
   363  			buildVariantTasks[task.Name] = true
   364  			for _, distroId := range task.Distros {
   365  				if !util.SliceContains(distroIds, distroId) {
   366  					errs = append(errs,
   367  						ValidationError{
   368  							Message: fmt.Sprintf("task '%v' in buildvariant "+
   369  								"'%v' in project '%v' references a "+
   370  								"non-existent distro '%v'.\nValid distros "+
   371  								"include: \n\t- %v", task.Name,
   372  								buildVariant.Name, project.Identifier,
   373  								distroId, strings.Join(distroIds, "\n\t- ")),
   374  							Level: Warning,
   375  						},
   376  					)
   377  				}
   378  			}
   379  		}
   380  		for _, distroId := range buildVariant.RunOn {
   381  			if !util.SliceContains(distroIds, distroId) {
   382  				errs = append(errs,
   383  					ValidationError{
   384  						Message: fmt.Sprintf("buildvariant '%v' in project "+
   385  							"'%v' references a non-existent distro '%v'.\n"+
   386  							"Valid distros include: \n\t- %v",
   387  							buildVariant.Name, project.Identifier, distroId,
   388  							strings.Join(distroIds, "\n\t- ")),
   389  						Level: Warning,
   390  					},
   391  				)
   392  			}
   393  		}
   394  	}
   395  	return errs
   396  }
   397  
   398  // Ensures there aren't any duplicate buildvariant names specified in the given
   399  // project
   400  func validateBVNames(project *model.Project) []ValidationError {
   401  	errs := []ValidationError{}
   402  	buildVariantNames := map[string]bool{}
   403  	displayNames := map[string]int{}
   404  
   405  	for _, buildVariant := range project.BuildVariants {
   406  		if _, ok := buildVariantNames[buildVariant.Name]; ok {
   407  			errs = append(errs,
   408  				ValidationError{
   409  					Message: fmt.Sprintf("project '%v' buildvariant '%v' already exists",
   410  						project.Identifier, buildVariant.Name),
   411  				},
   412  			)
   413  		}
   414  		buildVariantNames[buildVariant.Name] = true
   415  		dispName := buildVariant.DisplayName
   416  		if dispName == "" { // Default display name to the actual name (identifier)
   417  			dispName = buildVariant.Name
   418  		}
   419  		displayNames[dispName] = displayNames[dispName] + 1
   420  	}
   421  	// don't bother checking for the warnings if we already found errors
   422  	if len(errs) > 0 {
   423  		return errs
   424  	}
   425  	for k, v := range displayNames {
   426  		if v > 1 {
   427  			errs = append(errs,
   428  				ValidationError{
   429  					Level:   Warning,
   430  					Message: fmt.Sprintf("%v build variants share the same display name: '%v'", v, k),
   431  				},
   432  			)
   433  
   434  		}
   435  	}
   436  	return errs
   437  }
   438  
   439  // Checks each task definitions to determine if a command is specified
   440  func checkTaskCommands(project *model.Project) []ValidationError {
   441  	errs := []ValidationError{}
   442  	for _, task := range project.Tasks {
   443  		if len(task.Commands) == 0 {
   444  			errs = append(errs,
   445  				ValidationError{
   446  					Message: fmt.Sprintf("task '%v' in project '%v' does not "+
   447  						"contain any commands",
   448  						task.Name, project.Identifier),
   449  					Level: Warning,
   450  				},
   451  			)
   452  		}
   453  	}
   454  	return errs
   455  }
   456  
   457  // Ensures there aren't any duplicate task names specified for any buildvariant
   458  // in this project
   459  func validateBVTaskNames(project *model.Project) []ValidationError {
   460  	errs := []ValidationError{}
   461  	for _, buildVariant := range project.BuildVariants {
   462  		buildVariantTasks := map[string]bool{}
   463  		for _, task := range buildVariant.Tasks {
   464  			if _, ok := buildVariantTasks[task.Name]; ok {
   465  				errs = append(errs,
   466  					ValidationError{
   467  						Message: fmt.Sprintf("task '%v' in buildvariant '%v' "+
   468  							"in project '%v' already exists",
   469  							task.Name, buildVariant.Name, project.Identifier),
   470  					},
   471  				)
   472  			}
   473  			buildVariantTasks[task.Name] = true
   474  		}
   475  	}
   476  	return errs
   477  }
   478  
   479  // Helper for validating a set of plugin commands given a project/registry
   480  func validateCommands(section string, project *model.Project, registry plugin.Registry,
   481  	commands []model.PluginCommandConf) []ValidationError {
   482  	errs := []ValidationError{}
   483  
   484  	for _, cmd := range commands {
   485  		command := fmt.Sprintf("'%v' command", cmd.Command)
   486  		_, err := registry.GetCommands(cmd, project.Functions)
   487  		if err != nil {
   488  			if cmd.Function != "" {
   489  				command = fmt.Sprintf("'%v' function", cmd.Function)
   490  			}
   491  			errs = append(errs, ValidationError{Message: fmt.Sprintf("%v section in %v: %v", section, command, err)})
   492  		}
   493  		if cmd.Type != "" {
   494  			if cmd.Type != model.SystemCommandType &&
   495  				cmd.Type != model.TestCommandType {
   496  				msg := fmt.Sprintf("%v section in '%v': invalid command type: '%v'", section, command, cmd.Type)
   497  				errs = append(errs, ValidationError{Message: msg})
   498  			}
   499  		}
   500  	}
   501  	return errs
   502  }
   503  
   504  // Ensures there any plugin commands referenced in a project's configuration
   505  // are specified in a valid format
   506  func validatePluginCommands(project *model.Project) []ValidationError {
   507  	errs := []ValidationError{}
   508  	pluginRegistry := plugin.NewSimpleRegistry()
   509  
   510  	// register the published plugins
   511  	for _, pl := range plugin.CommandPlugins {
   512  		if err := pluginRegistry.Register(pl); err != nil {
   513  			errs = append(errs,
   514  				ValidationError{
   515  					Message: fmt.Sprintf("failed to register plugin %v: %v", pl.Name(), err),
   516  				},
   517  			)
   518  		}
   519  	}
   520  
   521  	seen := make(map[string]bool)
   522  
   523  	// validate each function definition
   524  	for funcName, commands := range project.Functions {
   525  		valErrs := validateCommands("functions", project, pluginRegistry, commands.List())
   526  		for _, err := range valErrs {
   527  			errs = append(errs,
   528  				ValidationError{
   529  					Message: fmt.Sprintf("'%v' project's '%v' definition: %v",
   530  						project.Identifier, funcName, err),
   531  				},
   532  			)
   533  		}
   534  
   535  		for _, c := range commands.List() {
   536  			if c.Function != "" {
   537  				errs = append(errs,
   538  					ValidationError{
   539  						Message: fmt.Sprintf("can not reference a function within a "+
   540  							"function: '%v' referenced within '%v'", c.Function, funcName),
   541  					},
   542  				)
   543  
   544  			}
   545  		}
   546  
   547  		// this checks for duplicate function definitions in the project.
   548  		if seen[funcName] {
   549  			errs = append(errs,
   550  				ValidationError{
   551  					Message: fmt.Sprintf(`project '%v' has duplicate definition of "%v"`,
   552  						project.Identifier, funcName),
   553  				},
   554  			)
   555  		}
   556  		seen[funcName] = true
   557  	}
   558  
   559  	if project.Pre != nil {
   560  		// validate project pre section
   561  		errs = append(errs, validateCommands("pre", project, pluginRegistry, project.Pre.List())...)
   562  	}
   563  
   564  	if project.Post != nil {
   565  		// validate project post section
   566  		errs = append(errs, validateCommands("post", project, pluginRegistry, project.Post.List())...)
   567  	}
   568  
   569  	if project.Timeout != nil {
   570  		// validate project timeout section
   571  		errs = append(errs, validateCommands("timeout", project, pluginRegistry, project.Timeout.List())...)
   572  	}
   573  
   574  	// validate project tasks section
   575  	for _, task := range project.Tasks {
   576  		errs = append(errs, validateCommands("tasks", project, pluginRegistry, task.Commands)...)
   577  	}
   578  	return errs
   579  }
   580  
   581  // Ensures there aren't any duplicate task names for this project
   582  func validateProjectTaskNames(project *model.Project) []ValidationError {
   583  	errs := []ValidationError{}
   584  	// create a map to hold the task names
   585  	taskNames := map[string]bool{}
   586  	for _, task := range project.Tasks {
   587  		if _, ok := taskNames[task.Name]; ok {
   588  			errs = append(errs,
   589  				ValidationError{
   590  					Message: fmt.Sprintf("task '%v' in project '%v' "+
   591  						"already exists", task.Name, project.Identifier),
   592  				},
   593  			)
   594  		}
   595  		taskNames[task.Name] = true
   596  	}
   597  	return errs
   598  }
   599  
   600  // validateProjectTaskIdsAndTags ensures that task tags and ids only contain valid characters
   601  func validateProjectTaskIdsAndTags(project *model.Project) []ValidationError {
   602  	errs := []ValidationError{}
   603  	// create a map to hold the task names
   604  	for _, task := range project.Tasks {
   605  		// check task name
   606  		if i := strings.IndexAny(task.Name, model.InvalidCriterionRunes); i == 0 {
   607  			errs = append(errs, ValidationError{
   608  				Message: fmt.Sprintf("task '%v' has invalid name: starts with invalid character %v",
   609  					task.Name, strconv.QuoteRune(rune(task.Name[0])))})
   610  		}
   611  		// check tag names
   612  		for _, tag := range task.Tags {
   613  			if i := strings.IndexAny(tag, model.InvalidCriterionRunes); i == 0 {
   614  				errs = append(errs, ValidationError{
   615  					Message: fmt.Sprintf("task '%v' has invalid tag '%v': starts with invalid character %v",
   616  						task.Name, tag, strconv.QuoteRune(rune(tag[0])))})
   617  			}
   618  			if i := util.IndexWhiteSpace(tag); i != -1 {
   619  				errs = append(errs, ValidationError{
   620  					Message: fmt.Sprintf("task '%v' has invalid tag '%v': tag contains white space",
   621  						task.Name, tag)})
   622  			}
   623  		}
   624  	}
   625  	return errs
   626  }
   627  
   628  // Makes sure that the dependencies for the tasks have the correct fields,
   629  // and that the fields reference valid tasks.
   630  func verifyTaskRequirements(project *model.Project) []ValidationError {
   631  	errs := []ValidationError{}
   632  	for _, bvt := range project.FindAllBuildVariantTasks() {
   633  		for _, r := range bvt.Requires {
   634  			if project.FindProjectTask(r.Name) == nil {
   635  				if r.Name == model.AllDependencies {
   636  					errs = append(errs, ValidationError{Message: fmt.Sprintf(
   637  						"task '%v': * is not supported for requirement selectors", bvt.Name)})
   638  				} else {
   639  					errs = append(errs,
   640  						ValidationError{Message: fmt.Sprintf(
   641  							"task '%v' requires non-existent task '%v'", bvt.Name, r.Name)})
   642  				}
   643  			}
   644  			if r.Variant != "" && r.Variant != model.AllVariants && project.FindBuildVariant(r.Variant) == nil {
   645  				errs = append(errs, ValidationError{Message: fmt.Sprintf(
   646  					"task '%v' requires non-existent variant '%v'", bvt.Name, r.Variant)})
   647  			}
   648  		}
   649  	}
   650  	return errs
   651  }
   652  
   653  // Makes sure that the dependencies for the tasks have the correct fields,
   654  // and that the fields have valid values
   655  func verifyTaskDependencies(project *model.Project) []ValidationError {
   656  	errs := []ValidationError{}
   657  	// create a set of all the task names
   658  	taskNames := map[string]bool{}
   659  	for _, task := range project.Tasks {
   660  		taskNames[task.Name] = true
   661  	}
   662  
   663  	for _, task := range project.Tasks {
   664  		// create a set of the dependencies, to check for duplicates
   665  		depNames := map[model.TVPair]bool{}
   666  
   667  		for _, dep := range task.DependsOn {
   668  			// make sure the dependency is not specified more than once
   669  			if depNames[model.TVPair{dep.Name, dep.Variant}] {
   670  				errs = append(errs,
   671  					ValidationError{
   672  						Message: fmt.Sprintf("project '%v' contains a "+
   673  							"duplicate dependency '%v' specified for task '%v'",
   674  							project.Identifier, dep.Name, task.Name),
   675  					},
   676  				)
   677  			}
   678  			depNames[model.TVPair{dep.Name, dep.Variant}] = true
   679  
   680  			// check that the status is valid
   681  			switch dep.Status {
   682  			case evergreen.TaskSucceeded, evergreen.TaskFailed, model.AllStatuses, "":
   683  				// these are all valid
   684  			default:
   685  				errs = append(errs,
   686  					ValidationError{
   687  						Message: fmt.Sprintf("project '%v' contains an invalid dependency status for task '%v': %v",
   688  							project.Identifier, task.Name, dep.Status)})
   689  			}
   690  
   691  			// check that name of the dependency task is valid
   692  			if dep.Name != model.AllDependencies && !taskNames[dep.Name] {
   693  				errs = append(errs,
   694  					ValidationError{
   695  						Message: fmt.Sprintf("project '%v' contains a "+
   696  							"non-existent task name '%v' in dependencies for "+
   697  							"task '%v'", project.Identifier, dep.Name,
   698  							task.Name),
   699  					},
   700  				)
   701  			}
   702  		}
   703  	}
   704  	return errs
   705  }