-
-
Notifications
You must be signed in to change notification settings - Fork 641
/
Copy pathtaskfile.go
161 lines (144 loc) · 4.9 KB
/
taskfile.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package taskfile
import (
"context"
"net/http"
"net/url"
"os"
"path/filepath"
"slices"
"strings"
"time"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/sysinfo"
)
var (
defaultTaskfiles = []string{
"Taskfile.yml",
"taskfile.yml",
"Taskfile.yaml",
"taskfile.yaml",
"Taskfile.dist.yml",
"taskfile.dist.yml",
"Taskfile.dist.yaml",
"taskfile.dist.yaml",
}
allowedContentTypes = []string{
"text/plain",
"text/yaml",
"text/x-yaml",
"application/yaml",
"application/x-yaml",
}
)
// RemoteExists will check if a file at the given URL Exists. If it does, it
// will return its URL. If it does not, it will search the search for any files
// at the given URL with any of the default Taskfile files names. If any of
// these match a file, the first matching path will be returned. If no files are
// found, an error will be returned.
func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL, timeout time.Duration) (*url.URL, error) {
// Create a new HEAD request for the given URL to check if the resource exists
req, err := http.NewRequest("HEAD", u.String(), nil)
if err != nil {
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
}
// Request the given URL
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return nil, &errors.TaskfileNetworkTimeoutError{URI: u.String(), Timeout: timeout}
}
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
}
defer resp.Body.Close()
// If the request was successful and the content type is allowed, return the
// URL The content type check is to avoid downloading files that are not
// Taskfiles It means we can try other files instead of downloading
// something that is definitely not a Taskfile
contentType := resp.Header.Get("Content-Type")
if resp.StatusCode == http.StatusOK && slices.ContainsFunc(allowedContentTypes, func(s string) bool {
return strings.Contains(contentType, s)
}) {
return u, nil
}
// If the request was not successful, append the default Taskfile names to
// the URL and return the URL of the first successful request
for _, taskfile := range defaultTaskfiles {
// Fixes a bug with JoinPath where a leading slash is not added to the
// path if it is empty
if u.Path == "" {
u.Path = "/"
}
alt := u.JoinPath(taskfile)
req.URL = alt
// Try the alternative URL
resp, err = http.DefaultClient.Do(req)
if err != nil {
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
}
defer resp.Body.Close()
// If the request was successful, return the URL
if resp.StatusCode == http.StatusOK {
l.VerboseOutf(logger.Magenta, "task: [%s] Not found - Using alternative (%s)\n", alt.String(), taskfile)
return alt, nil
}
}
return nil, errors.TaskfileNotFoundError{URI: u.String(), Walk: false}
}
// Exists will check if a file at the given path Exists. If it does, it will
// return the path to it. If it does not, it will search for any files at the
// given path with any of the default Taskfile files names. If any of these
// match a file, the first matching path will be returned. If no files are
// found, an error will be returned.
func Exists(l *logger.Logger, path string) (string, error) {
fi, err := os.Stat(path)
if err != nil {
return "", err
}
if fi.Mode().IsRegular() ||
fi.Mode()&os.ModeDevice != 0 ||
fi.Mode()&os.ModeSymlink != 0 ||
fi.Mode()&os.ModeNamedPipe != 0 {
return filepath.Abs(path)
}
for _, taskfile := range defaultTaskfiles {
alt := filepathext.SmartJoin(path, taskfile)
if _, err := os.Stat(alt); err == nil {
l.VerboseOutf(logger.Magenta, "task: [%s] Not found - Using alternative (%s)\n", path, taskfile)
return filepath.Abs(alt)
}
}
return "", errors.TaskfileNotFoundError{URI: path, Walk: false}
}
// ExistsWalk will check if a file at the given path exists by calling the
// exists function. If a file is not found, it will walk up the directory tree
// calling the exists function until it finds a file or reaches the root
// directory. On supported operating systems, it will also check if the user ID
// of the directory changes and abort if it does.
func ExistsWalk(l *logger.Logger, path string) (string, error) {
origPath := path
owner, err := sysinfo.Owner(path)
if err != nil {
return "", err
}
for {
fpath, err := Exists(l, path)
if err == nil {
return fpath, nil
}
// Get the parent path/user id
parentPath := filepath.Dir(path)
parentOwner, err := sysinfo.Owner(parentPath)
if err != nil {
return "", err
}
// Error if we reached the root directory and still haven't found a file
// OR if the user id of the directory changes
if path == parentPath || (parentOwner != owner) {
return "", errors.TaskfileNotFoundError{URI: origPath, Walk: false}
}
owner = parentOwner
path = parentPath
}
}