1
0
mirror of https://github.com/go-task/task.git synced 2025-01-20 04:59:37 +02:00

feat: add ability to specify which vars are required (#1204)

This commit is contained in:
Ben Coleman 2023-06-30 02:13:41 +01:00 committed by GitHub
parent f346015d8c
commit 307f39cee3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 165 additions and 3 deletions

View File

@ -76,6 +76,7 @@ A full list of the exit codes and their descriptions can be found below:
| 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 |
These codes can also be found in the repository in
[`errors/errors.go`](https://github.com/go-task/task/blob/main/errors/errors.go).
@ -219,7 +220,9 @@ vars:
| `sources` | `[]string` | | A list of sources to check before running this task. Relevant for `checksum` and `timestamp` methods. Can be file paths or star globs. |
| `generates` | `[]string` | | A list of files meant to be generated by this task. Relevant for `timestamp` method. Can be file paths or star globs. |
| `status` | `[]string` | | A list of commands to check if this task should run. The task is skipped otherwise. This overrides `method`, `sources` and `generates`. |
| `requires` | `[]string` | | A list of variables which should be set if this task is to run, if any of these variables are unset the task will error and not run. |
| `preconditions` | [`[]Precondition`](#precondition) | | A list of commands to check if this task should run. If a condition is not met, the task will error. |
| `requires` | [`Requires`](#requires) | | A list of required variables which should be set if this task is to run, if any variables listed are unset the task will error and not run. |
| `dir` | `string` | | The directory in which this task should run. Defaults to the current working directory. |
| `vars` | [`map[string]Variable`](#variable) | | A set of variables that can be used in the task. |
| `env` | [`map[string]Variable`](#variable) | | A set of environment variables that will be made available to shell commands. |
@ -322,3 +325,9 @@ tasks:
```
:::
#### Requires
| Attribute | Type | Default | Description |
| --------- | ---------- | ------- | -------------------------------------------------------------------------------------------------- |
| `vars` | `[]string` | | List of variable or environment variable names that must be set if this task is to execute and run |

View File

@ -876,6 +876,48 @@ tasks:
- sleep 5 # long operation like installing packages
```
### Ensuring required variables are set
If you want to check that certain variables are set before running a task then
you can use `requires`. This is useful when might not be clear to users which
variables are needed, or if you want clear message about what is required. Also
some tasks could have dangerous side effects if run with un-set variables.
Using `requires` you specify an array of strings in the `vars` sub-section
under `requires`, these strings are variable names which are checked prior to
running the task. If any variables are un-set the the task will error and not
run.
Environmental variables are also checked.
Syntax:
```yaml
requires:
vars: [] # Array of strings
```
:::note
Variables set to empty zero length strings, will pass the `requires` check.
:::
Example of using `requires`:
```yaml
version: '3'
tasks:
docker-build:
cmds:
- 'docker build . -t {{.IMAGE_NAME}}:{{.IMAGE_TAG}}'
# Make sure these variables are set before running
requires:
vars: [IMAGE_NAME, IMAGE_TAG]
```
## Variables
When doing interpolation of variables, Task will look for the below. They are

View File

@ -184,6 +184,10 @@
"items": {
"type": "string"
}
},
"requires": {
"description": "A list of variables which should be set if this task is to run, if any of these variables are unset the task will error and not run",
"$ref": "#/definitions/3/requires_obj"
}
}
},
@ -208,7 +212,21 @@
},
"set": {
"type": "string",
"enum": ["allexport", "a", "errexit", "e", "noexec", "n", "noglob", "f", "nounset", "u", "xtrace", "x", "pipefail"]
"enum": [
"allexport",
"a",
"errexit",
"e",
"noexec",
"n",
"noglob",
"f",
"nounset",
"u",
"xtrace",
"x",
"pipefail"
]
},
"shopt": {
"type": "string",
@ -352,6 +370,18 @@
}
}
}
},
"requires_obj": {
"type": "object",
"properties": {
"vars": {
"description": "List of variables that must be defined for the task to run",
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},

View File

@ -23,6 +23,7 @@ const (
CodeTaskNameConflict
CodeTaskCalledTooManyTimes
CodeTaskCancelled
CodeTaskMissingRequiredVars
)
// TaskError extends the standard error interface with a Code method. This code will

View File

@ -130,3 +130,21 @@ func (err *TaskCancelledNoTerminalError) Error() string {
func (err *TaskCancelledNoTerminalError) Code() int {
return CodeTaskCancelled
}
// TaskMissingRequiredVars is returned when a task is missing required variables.
type TaskMissingRequiredVars struct {
TaskName string
MissingVars []string
}
func (err *TaskMissingRequiredVars) Error() string {
return fmt.Sprintf(
`task: Task %q cancelled because it is missing required variables: %s`,
err.TaskName,
strings.Join(err.MissingVars, ", "),
)
}
func (err *TaskMissingRequiredVars) Code() int {
return CodeTaskMissingRequiredVars
}

35
requires.go Normal file
View File

@ -0,0 +1,35 @@
package task
import (
"context"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/taskfile"
)
func (e *Executor) areTaskRequiredVarsSet(ctx context.Context, t *taskfile.Task, call taskfile.Call) error {
if t.Requires == nil || len(t.Requires.Vars) == 0 {
return nil
}
vars, err := e.Compiler.GetVariables(t, call)
if err != nil {
return err
}
var missingVars []string
for _, requiredVar := range t.Requires.Vars {
if !vars.Exists(requiredVar) {
missingVars = append(missingVars, requiredVar)
}
}
if len(missingVars) > 0 {
return &errors.TaskMissingRequiredVars{
TaskName: t.Name(),
MissingVars: missingVars,
}
}
return nil
}

View File

@ -186,6 +186,10 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
return err
}
if err := e.areTaskRequiredVarsSet(ctx, t, call); err != nil {
return err
}
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
if err != nil {
return err

18
taskfile/requires.go Normal file
View File

@ -0,0 +1,18 @@
package taskfile
import "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
}
func (r *Requires) DeepCopy() *Requires {
if r == nil {
return nil
}
return &Requires{
Vars: deepcopy.Slice(r.Vars),
}
}

View File

@ -17,6 +17,7 @@ type Task struct {
Desc string
Prompt string
Summary string
Requires *Requires
Aliases []string
Sources []string
Generates []string
@ -99,6 +100,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
IgnoreError bool `yaml:"ignore_error"`
Run string
Platforms []*Platform
Requires *Requires
}
if err := node.Decode(&task); err != nil {
return err
@ -135,6 +137,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
t.IgnoreError = task.IgnoreError
t.Run = task.Run
t.Platforms = task.Platforms
t.Requires = task.Requires
return nil
}
@ -178,6 +181,7 @@ func (t *Task) DeepCopy() *Task {
IncludedTaskfile: t.IncludedTaskfile.DeepCopy(),
Platforms: deepcopy.Slice(t.Platforms),
Location: t.Location.DeepCopy(),
Requires: t.Requires.DeepCopy(),
}
return c
}

View File

@ -68,6 +68,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
IncludedTaskfileVars: origTask.IncludedTaskfileVars,
Platforms: origTask.Platforms,
Location: origTask.Location,
Requires: origTask.Requires,
}
new.Dir, err = execext.Expand(new.Dir)
if err != nil {