123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- 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
- }
- }
|