mirror of
https://github.com/go-task/task.git
synced 2024-12-04 10:24:45 +02:00
feat: option to ensure variable is within the list of values (#1827)
This commit is contained in:
parent
9a7e79258c
commit
a35910429c
@ -32,6 +32,7 @@ const (
|
||||
CodeTaskCalledTooManyTimes
|
||||
CodeTaskCancelled
|
||||
CodeTaskMissingRequiredVars
|
||||
CodeTaskNotAllowedVars
|
||||
)
|
||||
|
||||
// TaskError extends the standard error interface with a Code method. This code will
|
||||
|
@ -158,3 +158,29 @@ func (err *TaskMissingRequiredVars) Error() string {
|
||||
func (err *TaskMissingRequiredVars) Code() int {
|
||||
return CodeTaskMissingRequiredVars
|
||||
}
|
||||
|
||||
type NotAllowedVar struct {
|
||||
Value string
|
||||
Enum []string
|
||||
Name string
|
||||
}
|
||||
|
||||
type TaskNotAllowedVars struct {
|
||||
TaskName string
|
||||
NotAllowedVars []NotAllowedVar
|
||||
}
|
||||
|
||||
func (err *TaskNotAllowedVars) Error() string {
|
||||
var builder strings.Builder
|
||||
|
||||
builder.WriteString(fmt.Sprintf("task: Task %q cancelled because it is missing required variables:\n", err.TaskName))
|
||||
for _, s := range err.NotAllowedVars {
|
||||
builder.WriteString(fmt.Sprintf(" - %s has an invalid value : '%s' (allowed values : %v)\n", s.Name, s.Value, s.Enum))
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func (err *TaskNotAllowedVars) Code() int {
|
||||
return CodeTaskNotAllowedVars
|
||||
}
|
||||
|
23
requires.go
23
requires.go
@ -1,6 +1,8 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
@ -16,9 +18,19 @@ func (e *Executor) areTaskRequiredVarsSet(t *ast.Task, call *ast.Call) error {
|
||||
}
|
||||
|
||||
var missingVars []string
|
||||
var notAllowedValuesVars []errors.NotAllowedVar
|
||||
for _, requiredVar := range t.Requires.Vars {
|
||||
if !vars.Exists(requiredVar) {
|
||||
missingVars = append(missingVars, requiredVar)
|
||||
value, isString := vars.Get(requiredVar.Name).Value.(string)
|
||||
if !vars.Exists(requiredVar.Name) {
|
||||
missingVars = append(missingVars, requiredVar.Name)
|
||||
} else {
|
||||
if isString && requiredVar.Enum != nil && !slices.Contains(requiredVar.Enum, value) {
|
||||
notAllowedValuesVars = append(notAllowedValuesVars, errors.NotAllowedVar{
|
||||
Value: value,
|
||||
Enum: requiredVar.Enum,
|
||||
Name: requiredVar.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,5 +41,12 @@ func (e *Executor) areTaskRequiredVarsSet(t *ast.Task, call *ast.Call) error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(notAllowedValuesVars) > 0 {
|
||||
return &errors.TaskNotAllowedVars{
|
||||
TaskName: t.Name(),
|
||||
NotAllowedVars: notAllowedValuesVars,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
33
task_test.go
33
task_test.go
@ -155,6 +155,39 @@ func TestVars(t *testing.T) {
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestRequires(t *testing.T) {
|
||||
const dir = "testdata/requires"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
|
||||
require.NoError(t, e.Setup())
|
||||
require.ErrorContains(t, e.Run(context.Background(), &ast.Call{Task: "missing-var"}), "task: Task \"missing-var\" cancelled because it is missing required variables: foo")
|
||||
buff.Reset()
|
||||
require.NoError(t, e.Setup())
|
||||
|
||||
vars := &ast.Vars{}
|
||||
vars.Set("foo", ast.Var{Value: "bar"})
|
||||
require.NoError(t, e.Run(context.Background(), &ast.Call{
|
||||
Task: "missing-var",
|
||||
Vars: vars,
|
||||
}))
|
||||
buff.Reset()
|
||||
|
||||
require.NoError(t, e.Setup())
|
||||
require.ErrorContains(t, e.Run(context.Background(), &ast.Call{Task: "validation-var", Vars: vars}), "task: Task \"validation-var\" cancelled because it is missing required variables:\n - foo has an invalid value : 'bar' (allowed values : [one two])")
|
||||
buff.Reset()
|
||||
|
||||
require.NoError(t, e.Setup())
|
||||
vars.Set("foo", ast.Var{Value: "one"})
|
||||
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "validation-var", Vars: vars}))
|
||||
buff.Reset()
|
||||
}
|
||||
|
||||
func TestSpecialVars(t *testing.T) {
|
||||
const dir = "testdata/special_vars"
|
||||
const subdir = "testdata/special_vars/subdir"
|
||||
|
@ -1,10 +1,15 @@
|
||||
package ast
|
||||
|
||||
import "github.com/go-task/task/v3/internal/deepcopy"
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
)
|
||||
|
||||
// Requires represents a set of required variables necessary for a task to run
|
||||
type Requires struct {
|
||||
Vars []string
|
||||
Vars []*VarsWithValidation
|
||||
}
|
||||
|
||||
func (r *Requires) DeepCopy() *Requires {
|
||||
@ -16,3 +21,47 @@ func (r *Requires) DeepCopy() *Requires {
|
||||
Vars: deepcopy.Slice(r.Vars),
|
||||
}
|
||||
}
|
||||
|
||||
type VarsWithValidation struct {
|
||||
Name string
|
||||
Enum []string
|
||||
}
|
||||
|
||||
func (v *VarsWithValidation) DeepCopy() *VarsWithValidation {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
return &VarsWithValidation{
|
||||
Name: v.Name,
|
||||
Enum: v.Enum,
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||
func (v *VarsWithValidation) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.ScalarNode:
|
||||
var cmd string
|
||||
if err := node.Decode(&cmd); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Name = cmd
|
||||
v.Enum = nil
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
var vv struct {
|
||||
Name string
|
||||
Enum []string
|
||||
}
|
||||
if err := node.Decode(&vv); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Name = vv.Name
|
||||
v.Enum = vv.Enum
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("requires")
|
||||
}
|
||||
|
18
testdata/requires/Taskfile.yml
vendored
Normal file
18
testdata/requires/Taskfile.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
- task: missing-var
|
||||
|
||||
missing-var:
|
||||
requires:
|
||||
vars:
|
||||
- foo
|
||||
cmd: echo "{{.foo}}"
|
||||
|
||||
|
||||
validation-var:
|
||||
requires:
|
||||
vars:
|
||||
- name: foo
|
||||
enum: ['one', 'two']
|
@ -62,25 +62,26 @@ four groups with the following ranges:
|
||||
|
||||
A full list of the exit codes and their descriptions can be found below:
|
||||
|
||||
| Code | Description |
|
||||
| ---- | ------------------------------------------------------------ |
|
||||
| 0 | Success |
|
||||
| 1 | An unknown error occurred |
|
||||
| 100 | No Taskfile was found |
|
||||
| 101 | A Taskfile already exists when trying to initialize one |
|
||||
| 102 | The Taskfile is invalid or cannot be parsed |
|
||||
| 103 | A remote Taskfile could not be downloaded |
|
||||
| 104 | A remote Taskfile was not trusted by the user |
|
||||
| 105 | A remote Taskfile was could not be fetched securely |
|
||||
| 106 | No cache was found for a remote Taskfile in offline mode |
|
||||
| 107 | No schema version was defined in the Taskfile |
|
||||
| 200 | The specified task could not be found |
|
||||
| 201 | An error occurred while executing a command inside of a task |
|
||||
| 202 | The user tried to invoke a task that is internal |
|
||||
| 203 | There a multiple tasks with the same name or alias |
|
||||
| 204 | A task was called too many times |
|
||||
| 205 | A task was cancelled by the user |
|
||||
| 206 | A task was not executed due to missing required variables |
|
||||
| Code | Description |
|
||||
|------|---------------------------------------------------------------------|
|
||||
| 0 | Success |
|
||||
| 1 | An unknown error occurred |
|
||||
| 100 | No Taskfile was found |
|
||||
| 101 | A Taskfile already exists when trying to initialize one |
|
||||
| 102 | The Taskfile is invalid or cannot be parsed |
|
||||
| 103 | A remote Taskfile could not be downloaded |
|
||||
| 104 | A remote Taskfile was not trusted by the user |
|
||||
| 105 | A remote Taskfile was could not be fetched securely |
|
||||
| 106 | No cache was found for a remote Taskfile in offline mode |
|
||||
| 107 | No schema version was defined in the Taskfile |
|
||||
| 200 | The specified task could not be found |
|
||||
| 201 | An error occurred while executing a command inside of a task |
|
||||
| 202 | The user tried to invoke a task that is internal |
|
||||
| 203 | There a multiple tasks with the same name or alias |
|
||||
| 204 | A task was called too many times |
|
||||
| 205 | A task was cancelled by the user |
|
||||
| 206 | A task was not executed due to missing required variables |
|
||||
| 207 | A task was not executed due to a variable having an incorrect value |
|
||||
|
||||
These codes can also be found in the repository in
|
||||
[`errors/errors.go`](https://github.com/go-task/task/blob/main/errors/errors.go).
|
||||
|
@ -1060,6 +1060,40 @@ tasks:
|
||||
vars: [IMAGE_NAME, IMAGE_TAG]
|
||||
```
|
||||
|
||||
### Ensuring required variables have allowed values
|
||||
|
||||
If you want to ensure that a variable is set to one of a predefined set of valid values before executing a task, you can use requires.
|
||||
This is particularly useful when there are strict requirements for what values a variable can take, and you want to provide clear feedback to the user when an invalid value is detected.
|
||||
|
||||
To use `requires`, you specify an array of allowed values in the vars sub-section under requires. Task will check if the variable is set to one of the allowed values.
|
||||
If the variable does not match any of these values, the task will raise an error and stop execution.
|
||||
|
||||
This check applies both to user-defined variables and environment variables.
|
||||
|
||||
Example of using `requires`:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
deploy:
|
||||
cmds:
|
||||
- echo "deploying to {{.ENV}}"
|
||||
|
||||
requires:
|
||||
vars:
|
||||
- name: ENV
|
||||
enum: [dev, beta, prod]
|
||||
```
|
||||
|
||||
If `ENV` is not one of 'dev', 'beta' or 'prod' an error will be raised.
|
||||
|
||||
:::note
|
||||
|
||||
This is supported only for string variables.
|
||||
|
||||
:::
|
||||
|
||||
## Variables
|
||||
|
||||
Task allows you to set variables using the `vars` keyword. The following
|
||||
|
@ -558,7 +558,19 @@
|
||||
"description": "List of variables that must be defined for the task to run",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"enum": { "type": "array",
|
||||
"items": { "type": "string" } }
|
||||
},
|
||||
"required": ["name", "enum"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user