node_http.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. package taskfile
  2. import (
  3. "context"
  4. "io"
  5. "net/http"
  6. "net/url"
  7. "path/filepath"
  8. "time"
  9. "github.com/go-task/task/v3/errors"
  10. "github.com/go-task/task/v3/internal/execext"
  11. "github.com/go-task/task/v3/internal/filepathext"
  12. "github.com/go-task/task/v3/internal/logger"
  13. )
  14. // An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
  15. type HTTPNode struct {
  16. *BaseNode
  17. URL *url.URL
  18. logger *logger.Logger
  19. timeout time.Duration
  20. }
  21. func NewHTTPNode(
  22. l *logger.Logger,
  23. entrypoint string,
  24. dir string,
  25. insecure bool,
  26. timeout time.Duration,
  27. opts ...NodeOption,
  28. ) (*HTTPNode, error) {
  29. base := NewBaseNode(dir, opts...)
  30. url, err := url.Parse(entrypoint)
  31. if err != nil {
  32. return nil, err
  33. }
  34. if url.Scheme == "http" && !insecure {
  35. return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
  36. }
  37. return &HTTPNode{
  38. BaseNode: base,
  39. URL: url,
  40. timeout: timeout,
  41. logger: l,
  42. }, nil
  43. }
  44. func (node *HTTPNode) Location() string {
  45. return node.URL.String()
  46. }
  47. func (node *HTTPNode) Remote() bool {
  48. return true
  49. }
  50. func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
  51. url, err := RemoteExists(ctx, node.logger, node.URL, node.timeout)
  52. if err != nil {
  53. return nil, err
  54. }
  55. node.URL = url
  56. req, err := http.NewRequest("GET", node.URL.String(), nil)
  57. if err != nil {
  58. return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
  59. }
  60. resp, err := http.DefaultClient.Do(req.WithContext(ctx))
  61. if err != nil {
  62. if errors.Is(err, context.DeadlineExceeded) {
  63. return nil, &errors.TaskfileNetworkTimeoutError{URI: node.URL.String(), Timeout: node.timeout}
  64. }
  65. return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
  66. }
  67. defer resp.Body.Close()
  68. if resp.StatusCode != http.StatusOK {
  69. return nil, errors.TaskfileFetchFailedError{
  70. URI: node.URL.String(),
  71. HTTPStatusCode: resp.StatusCode,
  72. }
  73. }
  74. // Read the entire response body
  75. b, err := io.ReadAll(resp.Body)
  76. if err != nil {
  77. return nil, err
  78. }
  79. return b, nil
  80. }
  81. func (node *HTTPNode) ResolveEntrypoint(entrypoint string) (string, error) {
  82. ref, err := url.Parse(entrypoint)
  83. if err != nil {
  84. return "", err
  85. }
  86. return node.URL.ResolveReference(ref).String(), nil
  87. }
  88. func (node *HTTPNode) ResolveDir(dir string) (string, error) {
  89. path, err := execext.Expand(dir)
  90. if err != nil {
  91. return "", err
  92. }
  93. if filepathext.IsAbs(path) {
  94. return path, nil
  95. }
  96. // NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
  97. // This means that files are included relative to one another
  98. entrypointDir := filepath.Dir(node.Dir())
  99. return filepathext.SmartJoin(entrypointDir, path), nil
  100. }
  101. func (node *HTTPNode) FilenameAndLastDir() (string, string) {
  102. dir, filename := filepath.Split(node.URL.Path)
  103. return filepath.Base(dir), filename
  104. }