diff --git a/task_test.go b/task_test.go index 599277c8c5..0ddf2ea1c6 100644 --- a/task_test.go +++ b/task_test.go @@ -1527,6 +1527,35 @@ func TestIncludesInterpolation(t *testing.T) { } } +func TestIncludesInvalidTaskfile(t *testing.T) { + const dir = "testdata/includes_invalid_taskfile" + + tests := []struct { + name string + expectedErr string + }{ + {"include_empty_taskfile", "taskfile field in the include cannot be empty"}, + {"include_empty_value", "taskfile of the include cannot be empty"}, + {"include_missing_taskfile", "taskfile field in the include cannot be null"}, + {"include_null_taskfile", "taskfile field in the include cannot be null"}, + {"include_null_value", "value of the include cannot be null"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var buff bytes.Buffer + e := task.Executor{ + Dir: filepath.Join(dir, test.name), + Stdout: &buff, + Stderr: &buff, + Silent: true, + } + err := e.Setup() + assert.ErrorContains(t, err, test.expectedErr) + }) + } +} + func TestIncludedTaskfileVarMerging(t *testing.T) { const dir = "testdata/included_taskfile_var_merging" tests := []struct { diff --git a/taskfile/ast/include.go b/taskfile/ast/include.go index 659070de4a..fb8cacb81e 100644 --- a/taskfile/ast/include.go +++ b/taskfile/ast/include.go @@ -29,13 +29,18 @@ type Includes struct { func (includes *Includes) UnmarshalYAML(node *yaml.Node) error { switch node.Kind { case yaml.MappingNode: - // NOTE(@andreynering): on this style of custom unmarshalling, + // NOTE(@andreynering): on this style of custom unmarshaling, // even number contains the keys, while odd numbers contains // the values. for i := 0; i < len(node.Content); i += 2 { keyNode := node.Content[i] valueNode := node.Content[i+1] + // Ensure the include value is not null, as it must be either a string or an include object. + if valueNode.Kind == yaml.ScalarNode && valueNode.Tag == "!!null" { + return errors.NewTaskfileDecodeError(nil, valueNode).WithMessage("value of the include cannot be null") + } + var v Include if err := valueNode.Decode(&v); err != nil { return errors.NewTaskfileDecodeError(err, node) @@ -67,18 +72,20 @@ func (includes *Includes) Range(f func(k string, v *Include) error) error { func (include *Include) UnmarshalYAML(node *yaml.Node) error { switch node.Kind { - case yaml.ScalarNode: var str string if err := node.Decode(&str); err != nil { return errors.NewTaskfileDecodeError(err, node) } + if str == "" { + return errors.NewTaskfileDecodeError(nil, node).WithMessage("taskfile of the include cannot be empty") + } include.Taskfile = str return nil case yaml.MappingNode: var includedTaskfile struct { - Taskfile string + Taskfile *string Dir string Optional bool Internal bool @@ -89,7 +96,13 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error { if err := node.Decode(&includedTaskfile); err != nil { return errors.NewTaskfileDecodeError(err, node) } - include.Taskfile = includedTaskfile.Taskfile + if includedTaskfile.Taskfile == nil { + return errors.NewTaskfileDecodeError(nil, node).WithMessage("taskfile field in the include cannot be null") + } + if *includedTaskfile.Taskfile == "" { + return errors.NewTaskfileDecodeError(nil, node).WithMessage("taskfile field in the include cannot be empty") + } + include.Taskfile = *includedTaskfile.Taskfile include.Dir = includedTaskfile.Dir include.Optional = includedTaskfile.Optional include.Internal = includedTaskfile.Internal diff --git a/testdata/includes_invalid_taskfile/include_empty_taskfile/Taskfile.yml b/testdata/includes_invalid_taskfile/include_empty_taskfile/Taskfile.yml new file mode 100644 index 0000000000..a4d25a185c --- /dev/null +++ b/testdata/includes_invalid_taskfile/include_empty_taskfile/Taskfile.yml @@ -0,0 +1,10 @@ +version: '3' + +includes: + included: + taskfile: '' + +tasks: + default: + cmds: + - task: included:default diff --git a/testdata/includes_invalid_taskfile/include_empty_value/Taskfile.yml b/testdata/includes_invalid_taskfile/include_empty_value/Taskfile.yml new file mode 100644 index 0000000000..1f9fac9d8a --- /dev/null +++ b/testdata/includes_invalid_taskfile/include_empty_value/Taskfile.yml @@ -0,0 +1,9 @@ +version: '3' + +includes: + included: '' + +tasks: + default: + cmds: + - task: included:default diff --git a/testdata/includes_invalid_taskfile/include_missing_taskfile/Taskfile.yml b/testdata/includes_invalid_taskfile/include_missing_taskfile/Taskfile.yml new file mode 100644 index 0000000000..ddb86f0229 --- /dev/null +++ b/testdata/includes_invalid_taskfile/include_missing_taskfile/Taskfile.yml @@ -0,0 +1,10 @@ +version: '3' + +includes: + included: + dir: '.' + +tasks: + default: + cmds: + - task: included:default diff --git a/testdata/includes_invalid_taskfile/include_null_taskfile/Taskfile.yml b/testdata/includes_invalid_taskfile/include_null_taskfile/Taskfile.yml new file mode 100644 index 0000000000..d8d32fd855 --- /dev/null +++ b/testdata/includes_invalid_taskfile/include_null_taskfile/Taskfile.yml @@ -0,0 +1,10 @@ +version: '3' + +includes: + included: + taskfile: null + +tasks: + default: + cmds: + - task: included:default diff --git a/testdata/includes_invalid_taskfile/include_null_value/Taskfile.yml b/testdata/includes_invalid_taskfile/include_null_value/Taskfile.yml new file mode 100644 index 0000000000..b5344e6328 --- /dev/null +++ b/testdata/includes_invalid_taskfile/include_null_value/Taskfile.yml @@ -0,0 +1,9 @@ +version: '3' + +includes: + included: null + +tasks: + default: + cmds: + - task: included:default diff --git a/website/static/schema.json b/website/static/schema.json index b2b2d875aa..2f605b426b 100644 --- a/website/static/schema.json +++ b/website/static/schema.json @@ -614,14 +614,16 @@ "^.*$": { "anyOf": [ { - "type": "string" + "type": "string", + "minLength": 1 }, { "type": "object", "properties": { "taskfile": { "description": "The path for the Taskfile or directory to be included. If a directory, Task will look for files named `Taskfile.yml` or `Taskfile.yaml` inside that directory. If a relative path, resolved relative to the directory containing the including Taskfile.", - "type": "string" + "type": "string", + "minLength": 1 }, "dir": { "description": "The working directory of the included tasks when run.", @@ -650,7 +652,8 @@ "description": "A set of variables to apply to the included Taskfile.", "$ref": "#/definitions/vars" } - } + }, + "required": ["taskfile"] } ] }