Skip to content

Commit f1d4afc

Browse files
committed
feat: --download and --offline flags for remote taskfiles
1 parent ef3f5bb commit f1d4afc

File tree

10 files changed

+234
-65
lines changed

10 files changed

+234
-65
lines changed

cmd/task/task.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ var flags struct {
7272
interval time.Duration
7373
global bool
7474
experiments bool
75+
download bool
76+
offline bool
7577
}
7678

7779
func main() {
@@ -142,6 +144,12 @@ func run() error {
142144
pflag.BoolVarP(&flags.forceAll, "force", "f", false, "Forces execution even when the task is up-to-date.")
143145
}
144146

147+
// Remote Taskfiles experiment will adds the "download" and "offline" flags
148+
if experiments.RemoteTaskfiles {
149+
pflag.BoolVar(&flags.download, "download", false, "Downloads a cached version of a remote Taskfile.")
150+
pflag.BoolVar(&flags.offline, "offline", false, "Forces Task to only use local or cached Taskfiles.")
151+
}
152+
145153
pflag.Parse()
146154

147155
if flags.version {
@@ -175,6 +183,10 @@ func run() error {
175183
return nil
176184
}
177185

186+
if flags.download && flags.offline {
187+
return errors.New("task: You can't set both --download and --offline flags")
188+
}
189+
178190
if flags.global && flags.dir != "" {
179191
log.Fatal("task: You can't set both --global and --dir")
180192
return nil
@@ -219,6 +231,8 @@ func run() error {
219231
Force: flags.force,
220232
ForceAll: flags.forceAll,
221233
Insecure: flags.insecure,
234+
Download: flags.download,
235+
Offline: flags.offline,
222236
Watch: flags.watch,
223237
Verbose: flags.verbose,
224238
Silent: flags.silent,

errors/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
CodeTaskfileFetchFailed
1717
CodeTaskfileNotTrusted
1818
CodeTaskfileNotSecure
19+
CodeTaskfileCacheNotFound
1920
)
2021

2122
// Task related exit codes

errors/errors_taskfile.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,20 @@ func (err *TaskfileNotSecureError) Error() string {
103103
func (err *TaskfileNotSecureError) Code() int {
104104
return CodeTaskfileNotSecure
105105
}
106+
107+
// TaskfileCacheNotFound is returned when the user attempts to use an offline
108+
// (cached) Taskfile but the files does not exist in the cache.
109+
type TaskfileCacheNotFound struct {
110+
URI string
111+
}
112+
113+
func (err *TaskfileCacheNotFound) Error() string {
114+
return fmt.Sprintf(
115+
`task: Taskfile %q was not found in the cache. Remove the --offline flag to use a remote copy or download it using the --download flag`,
116+
err.URI,
117+
)
118+
}
119+
120+
func (err *TaskfileCacheNotFound) Code() int {
121+
return CodeTaskfileCacheNotFound
122+
}

setup.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,17 @@ func (e *Executor) setCurrentDir() error {
7373

7474
func (e *Executor) readTaskfile() error {
7575
var err error
76-
e.Taskfile, err = read.Taskfile(&read.FileNode{
77-
Dir: e.Dir,
78-
Entrypoint: e.Entrypoint,
79-
}, e.Insecure, e.TempDir, e.Logger)
76+
e.Taskfile, err = read.Taskfile(
77+
&read.FileNode{
78+
Dir: e.Dir,
79+
Entrypoint: e.Entrypoint,
80+
},
81+
e.Insecure,
82+
e.Download,
83+
e.Offline,
84+
e.TempDir,
85+
e.Logger,
86+
)
8087
if err != nil {
8188
return err
8289
}

task.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ type Executor struct {
5252
Force bool
5353
ForceAll bool
5454
Insecure bool
55+
Download bool
56+
Offline bool
5557
Watch bool
5658
Verbose bool
5759
Silent bool

taskfile/read/cache.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package read
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/base64"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
)
10+
11+
type Cache struct {
12+
dir string
13+
}
14+
15+
func NewCache(dir string) (*Cache, error) {
16+
dir = filepath.Join(dir, "remote")
17+
if err := os.MkdirAll(dir, 0o755); err != nil {
18+
return nil, err
19+
}
20+
return &Cache{
21+
dir: dir,
22+
}, nil
23+
}
24+
25+
func (c *Cache) checksum(b []byte) string {
26+
h := sha256.New()
27+
h.Write(b)
28+
return base64.StdEncoding.EncodeToString(h.Sum(nil))
29+
}
30+
31+
func (c *Cache) write(node Node, b []byte) error {
32+
return os.WriteFile(c.cacheFilePath(node), b, 0o644)
33+
}
34+
35+
func (c *Cache) read(node Node) ([]byte, error) {
36+
return os.ReadFile(c.cacheFilePath(node))
37+
}
38+
39+
func (c *Cache) writeChecksum(node Node, checksum string) error {
40+
return os.WriteFile(c.checksumFilePath(node), []byte(checksum), 0o644)
41+
}
42+
43+
func (c *Cache) readChecksum(node Node) string {
44+
b, _ := os.ReadFile(c.checksumFilePath(node))
45+
return string(b)
46+
}
47+
48+
func (c *Cache) key(node Node) string {
49+
return base64.StdEncoding.EncodeToString([]byte(node.Location()))
50+
}
51+
52+
func (c *Cache) cacheFilePath(node Node) string {
53+
return filepath.Join(c.dir, fmt.Sprintf("%s.yaml", c.key(node)))
54+
}
55+
56+
func (c *Cache) checksumFilePath(node Node) string {
57+
return filepath.Join(c.dir, fmt.Sprintf("%s.checksum", c.key(node)))
58+
}

taskfile/read/node.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,20 @@ import (
1010
)
1111

1212
type Node interface {
13-
Read() (*taskfile.Taskfile, error)
13+
Read() ([]byte, error)
1414
Parent() Node
1515
Optional() bool
1616
Location() string
17+
Remote() bool
1718
}
1819

19-
func NewNodeFromIncludedTaskfile(parent Node, includedTaskfile taskfile.IncludedTaskfile, allowInsecure bool, tempDir string, l *logger.Logger) (Node, error) {
20+
func NewNodeFromIncludedTaskfile(
21+
parent Node,
22+
includedTaskfile taskfile.IncludedTaskfile,
23+
allowInsecure bool,
24+
tempDir string,
25+
l *logger.Logger,
26+
) (Node, error) {
2027
// TODO: Remove this condition when the remote taskfiles experiment is complete
2128
if !experiments.RemoteTaskfiles {
2229
path, err := includedTaskfile.FullTaskfilePath()

taskfile/read/node_file.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
package read
22

33
import (
4+
"io"
45
"os"
56
"path/filepath"
67

7-
"gopkg.in/yaml.v3"
8-
9-
"github.com/go-task/task/v3/errors"
108
"github.com/go-task/task/v3/internal/filepathext"
11-
"github.com/go-task/task/v3/taskfile"
129
)
1310

1411
// A FileNode is a node that reads a taskfile from the local filesystem.
@@ -38,7 +35,11 @@ func (node *FileNode) Location() string {
3835
return filepathext.SmartJoin(node.Dir, node.Entrypoint)
3936
}
4037

41-
func (node *FileNode) Read() (*taskfile.Taskfile, error) {
38+
func (node *FileNode) Remote() bool {
39+
return false
40+
}
41+
42+
func (node *FileNode) Read() ([]byte, error) {
4243
if node.Dir == "" {
4344
d, err := os.Getwd()
4445
if err != nil {
@@ -60,11 +61,5 @@ func (node *FileNode) Read() (*taskfile.Taskfile, error) {
6061
}
6162
defer f.Close()
6263

63-
var t taskfile.Taskfile
64-
if err := yaml.NewDecoder(f).Decode(&t); err != nil {
65-
return nil, &errors.TaskfileInvalidError{URI: filepathext.TryAbsToRel(path), Err: err}
66-
}
67-
68-
t.Location = path
69-
return &t, nil
64+
return io.ReadAll(f)
7065
}

taskfile/read/node_http.go

Lines changed: 6 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,14 @@ package read
33
import (
44
"crypto/sha256"
55
"encoding/base64"
6-
"fmt"
76
"io"
87
"net/http"
98
"net/url"
109
"os"
1110
"path/filepath"
1211

13-
"gopkg.in/yaml.v3"
14-
1512
"github.com/go-task/task/v3/errors"
1613
"github.com/go-task/task/v3/internal/logger"
17-
"github.com/go-task/task/v3/taskfile"
1814
)
1915

2016
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
@@ -45,7 +41,11 @@ func (node *HTTPNode) Location() string {
4541
return node.URL.String()
4642
}
4743

48-
func (node *HTTPNode) Read() (*taskfile.Taskfile, error) {
44+
func (node *HTTPNode) Remote() bool {
45+
return true
46+
}
47+
48+
func (node *HTTPNode) Read() ([]byte, error) {
4949
resp, err := http.Get(node.URL.String())
5050
if err != nil {
5151
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
@@ -65,45 +65,7 @@ func (node *HTTPNode) Read() (*taskfile.Taskfile, error) {
6565
return nil, err
6666
}
6767

68-
// Create a hash of the response body
69-
h := sha256.New()
70-
h.Write(b)
71-
hash := base64.URLEncoding.EncodeToString(h.Sum(nil))
72-
73-
// Get the cached hash
74-
cachedHash, err := node.getHashFromCache()
75-
if errors.Is(err, os.ErrNotExist) {
76-
// If the hash doesn't exist in the cache, prompt the user to continue
77-
if cont, err := node.Logger.Prompt(logger.Yellow, fmt.Sprintf("The task you are attempting to run depends on the remote Taskfile at %q.\n--- Make sure you trust the source of this Taskfile before continuing ---\nContinue?", node.URL.String()), "n", "y", "yes"); err != nil {
78-
return nil, err
79-
} else if !cont {
80-
return nil, &errors.TaskfileNotTrustedError{URI: node.URL.String()}
81-
}
82-
} else if err != nil {
83-
return nil, err
84-
} else if hash != cachedHash {
85-
// If there is a cached hash, but it doesn't match the expected hash, prompt the user to continue
86-
if cont, err := node.Logger.Prompt(logger.Yellow, fmt.Sprintf("The Taskfile at %q has changed since you last used it!\n--- Make sure you trust the source of this Taskfile before continuing ---\nContinue?", node.URL.String()), "n", "y", "yes"); err != nil {
87-
return nil, err
88-
} else if !cont {
89-
return nil, &errors.TaskfileNotTrustedError{URI: node.URL.String()}
90-
}
91-
}
92-
93-
// If the hash has changed (or is new), store it in the cache
94-
if hash != cachedHash {
95-
if err := node.toCache([]byte(hash)); err != nil {
96-
return nil, err
97-
}
98-
}
99-
100-
// Unmarshal the taskfile
101-
var t *taskfile.Taskfile
102-
if err := yaml.Unmarshal(b, &t); err != nil {
103-
return nil, &errors.TaskfileInvalidError{URI: node.URL.String(), Err: err}
104-
}
105-
t.Location = node.URL.String()
106-
return t, nil
68+
return b, nil
10769
}
10870

10971
func (node *HTTPNode) getCachePath() string {

0 commit comments

Comments
 (0)