1
0
mirror of https://github.com/go-task/task.git synced 2025-05-31 23:19:42 +02:00

feat: prefer remote taskfiles over cached ones (#1345)

* feat: prefer remote taskfiles over cached ones

* feat: implemented cache on network timeout

* feat: --download always downloads, but never executes tasks

* feat: --timeout flag

* fix: bug with timeout error handling

* chore: changelog
This commit is contained in:
Pete Davison 2023-11-17 14:51:10 -06:00 committed by GitHub
parent 834babe0ef
commit 546a4d7e46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 102 additions and 51 deletions

View File

@ -2,9 +2,16 @@
## Unreleased ## Unreleased
- The
[Remote Taskfiles experiment](https://taskfile.dev/experiments/remote-taskfiles)
now prefers remote files over cached ones by default (#1317, #1345 by @pd93).
- Added `--timeout` flag to the
[Remote Taskfiles experiment](https://taskfile.dev/experiments/remote-taskfiles)
(#1317, #1345 by @pd93).
- Fix bug where dynamic `vars:` and `env:` were being executed when they should - Fix bug where dynamic `vars:` and `env:` were being executed when they should
actually be skipped by `platforms:` (#1273, #1377 by @andreynering). actually be skipped by `platforms:` (#1273, #1377 by @andreynering).
- Fix `schema.json` to make `silent` valid in `cmds` that use `for` (#1385, #1386 by @iainvm). - Fix `schema.json` to make `silent` valid in `cmds` that use `for` (#1385,
#1386 by @iainvm).
- Add new `--no-status` flag to skip expensive status checks when running - Add new `--no-status` flag to skip expensive status checks when running
`task --list --json` (#1348, #1368 by @amancevice). `task --list --json` (#1348, #1368 by @amancevice).
@ -12,7 +19,7 @@
- Enabled the `--yes` flag for the - Enabled the `--yes` flag for the
[Remote Taskfiles experiment](https://taskfile.dev/experiments/remote-taskfiles) [Remote Taskfiles experiment](https://taskfile.dev/experiments/remote-taskfiles)
(#1344 by @pd93). (#1317, #1344 by @pd93).
- Add ability to set `watch: true` in a task to automatically run it in watch - Add ability to set `watch: true` in a task to automatically run it in watch
mode (#231, #1361 by @andreynering). mode (#231, #1361 by @andreynering).
- Fixed a bug on the watch mode where paths that contained `.git` (like - Fixed a bug on the watch mode where paths that contained `.git` (like
@ -26,8 +33,8 @@
exists to detect recursive calls, but will be removed in favor of a better exists to detect recursive calls, but will be removed in favor of a better
algorithm soon (#1321, #1332). algorithm soon (#1321, #1332).
- Fixed templating on descriptions on `task --list` (#1343 by @blackjid). - Fixed templating on descriptions on `task --list` (#1343 by @blackjid).
- Fixed a bug where precondition errors were incorrectly being printed when - Fixed a bug where precondition errors were incorrectly being printed when task
task execution was aborted (#1337, #1338 by @sylv-io). execution was aborted (#1337, #1338 by @sylv-io).
## v3.30.1 - 2023-09-14 ## v3.30.1 - 2023-09-14

View File

@ -75,6 +75,7 @@ var flags struct {
experiments bool experiments bool
download bool download bool
offline bool offline bool
timeout time.Duration
} }
func main() { func main() {
@ -150,6 +151,7 @@ func run() error {
if experiments.RemoteTaskfiles { if experiments.RemoteTaskfiles {
pflag.BoolVar(&flags.download, "download", false, "Downloads a cached version of a remote Taskfile.") pflag.BoolVar(&flags.download, "download", false, "Downloads a cached version of a remote Taskfile.")
pflag.BoolVar(&flags.offline, "offline", false, "Forces Task to only use local or cached Taskfiles.") pflag.BoolVar(&flags.offline, "offline", false, "Forces Task to only use local or cached Taskfiles.")
pflag.DurationVar(&flags.timeout, "timeout", time.Second*10, "Timeout for downloading remote Taskfiles.")
} }
pflag.Parse() pflag.Parse()
@ -235,6 +237,7 @@ func run() error {
Insecure: flags.insecure, Insecure: flags.insecure,
Download: flags.download, Download: flags.download,
Offline: flags.offline, Offline: flags.offline,
Timeout: flags.timeout,
Watch: flags.watch, Watch: flags.watch,
Verbose: flags.verbose, Verbose: flags.verbose,
Silent: flags.silent, Silent: flags.silent,
@ -270,6 +273,12 @@ func run() error {
return err return err
} }
// If the download flag is specified, we should stop execution as soon as
// taskfile is downloaded
if flags.download {
return nil
}
if listOptions.ShouldListTasks() { if listOptions.ShouldListTasks() {
foundTasks, err := e.ListTasks(listOptions) foundTasks, err := e.ListTasks(listOptions)
if err != nil { if err != nil {
@ -298,9 +307,7 @@ func run() error {
} }
// If there are no calls, run the default task instead // If there are no calls, run the default task instead
// Unless the download flag is specified, in which case we want to download if len(calls) == 0 {
// the Taskfile and do nothing else
if len(calls) == 0 && !flags.download {
calls = append(calls, taskfile.Call{Task: "default", Direct: true}) calls = append(calls, taskfile.Call{Task: "default", Direct: true})
} }

View File

@ -74,16 +74,17 @@ you are doing.
## Caching & Running Offline ## Caching & Running Offline
If for whatever reason, you don't have access to the internet, but you still Whenever you run a remote Taskfile, the latest copy will be downloaded from the
need to be able to run your tasks, you are able to use the `--download` flag to internet and cached locally. If for whatever reason, you lose access to the
store a cached copy of the remote Taskfile. internet, you will still be able to run your tasks by specifying the `--offline`
flag. This will tell Task to use the latest cached version of the file instead
of trying to download it. You are able to use the `--download` flag to update
the cached version of the remote files without running any tasks.
<!-- TODO: The following behavior may change --> By default, Task will timeout requests to download remote files after 10 seconds
and look for a cached copy instead. This timeout can be configured by setting
If Task detects that you have a local copy of the remote Taskfile, it will use the `--timeout` flag and specifying a duration. For example, `--timeout 5s` will
your local copy instead of downloading the remote file. You can force Task to set the timeout to 5 seconds.
work offline by using the `--offline` flag. This will prevent Task from making
any calls to remote sources.
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[remote-taskfiles-experiment]: https://github.com/go-task/task/issues/1317 [remote-taskfiles-experiment]: https://github.com/go-task/task/issues/1317

View File

@ -18,6 +18,7 @@ const (
CodeTaskfileNotSecure CodeTaskfileNotSecure
CodeTaskfileCacheNotFound CodeTaskfileCacheNotFound
CodeTaskfileVersionNotDefined CodeTaskfileVersionNotDefined
CodeTaskfileNetworkTimeout
) )
// Task related exit codes // Task related exit codes

View File

@ -3,6 +3,7 @@ package errors
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"time"
) )
// TaskfileNotFoundError is returned when no appropriate Taskfile is found when // TaskfileNotFoundError is returned when no appropriate Taskfile is found when
@ -137,3 +138,26 @@ func (err *TaskfileVersionNotDefined) Error() string {
func (err *TaskfileVersionNotDefined) Code() int { func (err *TaskfileVersionNotDefined) Code() int {
return CodeTaskfileVersionNotDefined return CodeTaskfileVersionNotDefined
} }
// TaskfileNetworkTimeout is returned when the user attempts to use a remote
// Taskfile but a network connection could not be established within the timeout.
type TaskfileNetworkTimeout struct {
URI string
Timeout time.Duration
CheckedCache bool
}
func (err *TaskfileNetworkTimeout) Error() string {
var cacheText string
if err.CheckedCache {
cacheText = " and no offline copy was found in the cache"
}
return fmt.Sprintf(
`task: Network connection timed out after %s while attempting to download Taskfile %q%s`,
err.Timeout, err.URI, cacheText,
)
}
func (err *TaskfileNetworkTimeout) Code() int {
return CodeTaskfileNetworkTimeout
}

View File

@ -91,6 +91,7 @@ func (e *Executor) readTaskfile() error {
e.Insecure, e.Insecure,
e.Download, e.Download,
e.Offline, e.Offline,
e.Timeout,
e.TempDir, e.TempDir,
e.Logger, e.Logger,
) )

View File

@ -46,6 +46,7 @@ type Executor struct {
Insecure bool Insecure bool
Download bool Download bool
Offline bool Offline bool
Timeout time.Duration
Watch bool Watch bool
Verbose bool Verbose bool
Silent bool Silent bool

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"time"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@ -37,6 +38,7 @@ func readTaskfile(
node Node, node Node,
download, download,
offline bool, offline bool,
timeout time.Duration,
tempDir string, tempDir string,
l *logger.Logger, l *logger.Logger,
) (*taskfile.Taskfile, error) { ) (*taskfile.Taskfile, error) {
@ -51,35 +53,44 @@ func readTaskfile(
} }
} }
// If the file is remote, check if we have a cached copy // If the file is remote and we're in offline mode, check if we have a cached copy
// If we're told to download, skip the cache if node.Remote() && offline {
if node.Remote() && !download { if b, err = cache.read(node); errors.Is(err, os.ErrNotExist) {
if b, err = cache.read(node); !errors.Is(err, os.ErrNotExist) && err != nil {
return nil, err
}
if b != nil {
l.VerboseOutf(logger.Magenta, "task: [%s] Fetched cached copy\n", node.Location())
}
}
// If the file is remote, we found nothing in the cache and we're not
// allowed to download it then we can't do anything.
if node.Remote() && b == nil && offline {
if b == nil && offline {
return nil, &errors.TaskfileCacheNotFound{URI: node.Location()} return nil, &errors.TaskfileCacheNotFound{URI: node.Location()}
} } else if err != nil {
}
// If we still don't have a copy, get the file in the usual way
if b == nil {
b, err = node.Read(context.Background())
if err != nil {
return nil, err return nil, err
} }
l.VerboseOutf(logger.Magenta, "task: [%s] Fetched cached copy\n", node.Location())
} else {
downloaded := false
ctx, cf := context.WithTimeout(context.Background(), timeout)
defer cf()
// Read the file
b, err = node.Read(ctx)
// If we timed out then we likely have a network issue
if node.Remote() && errors.Is(ctx.Err(), context.DeadlineExceeded) {
// If a download was requested, then we can't use a cached copy
if download {
return nil, &errors.TaskfileNetworkTimeout{URI: node.Location(), Timeout: timeout}
}
// Search for any cached copies
if b, err = cache.read(node); errors.Is(err, os.ErrNotExist) {
return nil, &errors.TaskfileNetworkTimeout{URI: node.Location(), Timeout: timeout, CheckedCache: true}
} else if err != nil {
return nil, err
}
l.VerboseOutf(logger.Magenta, "task: [%s] Network timeout. Fetched cached copy\n", node.Location())
} else if err != nil {
return nil, err
} else {
downloaded = true
}
// If the node was remote, we need to check the checksum // If the node was remote, we need to check the checksum
if node.Remote() { if node.Remote() && downloaded {
l.VerboseOutf(logger.Magenta, "task: [%s] Fetched remote copy\n", node.Location()) l.VerboseOutf(logger.Magenta, "task: [%s] Fetched remote copy\n", node.Location())
// Get the checksums // Get the checksums
@ -102,24 +113,21 @@ func readTaskfile(
} }
} }
// If the hash has changed (or is new), store it in the cache // If the hash has changed (or is new)
if checksum != cachedChecksum { if checksum != cachedChecksum {
// Store the checksum
if err := cache.writeChecksum(node, checksum); err != nil { if err := cache.writeChecksum(node, checksum); err != nil {
return nil, err return nil, err
} }
// Cache the file
l.VerboseOutf(logger.Magenta, "task: [%s] Caching downloaded file\n", node.Location())
if err = cache.write(node, b); err != nil {
return nil, err
}
} }
} }
} }
// If the file is remote and we need to cache it
if node.Remote() && download {
l.VerboseOutf(logger.Magenta, "task: [%s] Caching downloaded file\n", node.Location())
// Cache the file for later
if err = cache.write(node, b); err != nil {
return nil, err
}
}
var t taskfile.Taskfile var t taskfile.Taskfile
if err := yaml.Unmarshal(b, &t); err != nil { if err := yaml.Unmarshal(b, &t); err != nil {
return nil, &errors.TaskfileInvalidError{URI: filepathext.TryAbsToRel(node.Location()), Err: err} return nil, &errors.TaskfileInvalidError{URI: filepathext.TryAbsToRel(node.Location()), Err: err}
@ -137,12 +145,13 @@ func Taskfile(
insecure bool, insecure bool,
download bool, download bool,
offline bool, offline bool,
timeout time.Duration,
tempDir string, tempDir string,
l *logger.Logger, l *logger.Logger,
) (*taskfile.Taskfile, error) { ) (*taskfile.Taskfile, error) {
var _taskfile func(Node) (*taskfile.Taskfile, error) var _taskfile func(Node) (*taskfile.Taskfile, error)
_taskfile = func(node Node) (*taskfile.Taskfile, error) { _taskfile = func(node Node) (*taskfile.Taskfile, error) {
t, err := readTaskfile(node, download, offline, tempDir, l) t, err := readTaskfile(node, download, offline, timeout, tempDir, l)
if err != nil { if err != nil {
return nil, err return nil, err
} }