Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@ func TestIncludesRemote(t *testing.T) {
enableExperimentForTest(t, &experiments.RemoteTaskfiles, 1)

dir := "testdata/includes_remote"
os.RemoveAll(filepath.Join(dir, ".task", "remote"))

srv := httptest.NewServer(http.FileServer(http.Dir(dir)))
defer srv.Close()
Expand Down Expand Up @@ -777,8 +778,8 @@ func TestIncludesRemote(t *testing.T) {
},
}

for j, e := range executors {
t.Run(fmt.Sprint(j), func(t *testing.T) {
for _, e := range executors {
t.Run(e.name, func(t *testing.T) {
require.NoError(t, e.executor.Setup())

for k, taskCall := range taskCalls {
Expand Down
16 changes: 8 additions & 8 deletions taskfile/node_base.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package taskfile

type (
NodeOption func(*BaseNode)
// BaseNode is a generic node that implements the Parent() methods of the
NodeOption func(*baseNode)
// baseNode is a generic node that implements the Parent() methods of the
// NodeReader interface. It does not implement the Read() method and it
// designed to be embedded in other node types so that this boilerplate code
// does not need to be repeated.
BaseNode struct {
baseNode struct {
parent Node
dir string
}
)

func NewBaseNode(dir string, opts ...NodeOption) *BaseNode {
node := &BaseNode{
func NewBaseNode(dir string, opts ...NodeOption) *baseNode {
node := &baseNode{
parent: nil,
dir: dir,
}
Expand All @@ -27,15 +27,15 @@ func NewBaseNode(dir string, opts ...NodeOption) *BaseNode {
}

func WithParent(parent Node) NodeOption {
return func(node *BaseNode) {
return func(node *baseNode) {
node.parent = parent
}
}

func (node *BaseNode) Parent() Node {
func (node *baseNode) Parent() Node {
return node.parent
}

func (node *BaseNode) Dir() string {
func (node *baseNode) Dir() string {
return node.dir
}
4 changes: 2 additions & 2 deletions taskfile/node_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import (
const remoteCacheDir = "remote"

type CacheNode struct {
*BaseNode
*baseNode
source RemoteNode
}

func NewCacheNode(source RemoteNode, dir string) *CacheNode {
return &CacheNode{
BaseNode: &BaseNode{
baseNode: &baseNode{
dir: filepath.Join(dir, remoteCacheDir),
},
source: source,
Expand Down
14 changes: 7 additions & 7 deletions taskfile/node_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import (

// A FileNode is a node that reads a taskfile from the local filesystem.
type FileNode struct {
*BaseNode
Entrypoint string
*baseNode
entrypoint string
}

func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
Expand All @@ -25,13 +25,13 @@ func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error)
return nil, err
}
return &FileNode{
BaseNode: base,
Entrypoint: entrypoint,
baseNode: base,
entrypoint: entrypoint,
}, nil
}

func (node *FileNode) Location() string {
return node.Entrypoint
return node.entrypoint
}

func (node *FileNode) Read() ([]byte, error) {
Expand Down Expand Up @@ -63,7 +63,7 @@ func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) {

// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
// This means that files are included relative to one another
entrypointDir := filepath.Dir(node.Entrypoint)
entrypointDir := filepath.Dir(node.entrypoint)
return filepathext.SmartJoin(entrypointDir, path), nil
}

Expand All @@ -79,6 +79,6 @@ func (node *FileNode) ResolveDir(dir string) (string, error) {

// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
// This means that files are included relative to one another
entrypointDir := filepath.Dir(node.Entrypoint)
entrypointDir := filepath.Dir(node.entrypoint)
return filepathext.SmartJoin(entrypointDir, path), nil
}
41 changes: 25 additions & 16 deletions taskfile/node_git.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import (

// An GitNode is a node that reads a Taskfile from a remote location via Git.
type GitNode struct {
*BaseNode
URL *url.URL
*baseNode
url *url.URL
rawUrl string
ref string
path string
Expand All @@ -40,23 +40,20 @@ func NewGitNode(
return nil, err
}

basePath, path := func() (string, string) {
x := strings.Split(u.Path, "//")
return x[0], x[1]
}()
basePath, path := splitURLOnDoubleSlash(u)
ref := u.Query().Get("ref")

rawUrl := u.String()
rawUrl := u.Redacted()

u.RawQuery = ""
u.Path = basePath

if u.Scheme == "http" && !insecure {
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
return nil, &errors.TaskfileNotSecureError{URI: u.Redacted()}
}
return &GitNode{
BaseNode: base,
URL: u,
baseNode: base,
url: u,
rawUrl: rawUrl,
ref: ref,
path: path,
Expand All @@ -79,7 +76,7 @@ func (node *GitNode) ReadContext(_ context.Context) ([]byte, error) {
fs := memfs.New()
storer := memory.NewStorage()
_, err := git.Clone(storer, fs, &git.CloneOptions{
URL: node.URL.String(),
URL: node.url.String(),
ReferenceName: plumbing.ReferenceName(node.ref),
SingleBranch: true,
Depth: 1,
Expand All @@ -102,7 +99,7 @@ func (node *GitNode) ReadContext(_ context.Context) ([]byte, error) {

func (node *GitNode) ResolveEntrypoint(entrypoint string) (string, error) {
dir, _ := filepath.Split(node.path)
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.URL, filepath.Join(dir, entrypoint))
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.url, filepath.Join(dir, entrypoint))
if node.ref != "" {
return fmt.Sprintf("%s?ref=%s", resolvedEntrypoint, node.ref), nil
}
Expand All @@ -127,11 +124,23 @@ func (node *GitNode) ResolveDir(dir string) (string, error) {

func (node *GitNode) CacheKey() string {
checksum := strings.TrimRight(checksum([]byte(node.Location())), "=")
prefix := filepath.Base(filepath.Dir(node.path))
lastDir := filepath.Base(node.path)
lastDir := filepath.Base(filepath.Dir(node.path))
prefix := filepath.Base(node.path)
// Means it's not "", nor "." nor "/", so it's a valid directory
if len(lastDir) > 1 {
prefix = fmt.Sprintf("%s-%s", lastDir, prefix)
prefix = fmt.Sprintf("%s.%s", lastDir, prefix)
}
return fmt.Sprintf("git.%s.%s.%s", node.url.Host, prefix, checksum)
}

func splitURLOnDoubleSlash(u *url.URL) (string, string) {
x := strings.Split(u.Path, "//")
switch len(x) {
case 0:
return "", ""
case 1:
return x[0], ""
default:
return x[0], x[1]
}
return fmt.Sprintf("%s.%s", prefix, checksum)
}
53 changes: 32 additions & 21 deletions taskfile/node_git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGitNode_ssh(t *testing.T) {
Expand All @@ -13,8 +14,8 @@ func TestGitNode_ssh(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "main", node.ref)
assert.Equal(t, "Taskfile.yml", node.path)
assert.Equal(t, "ssh://[email protected]/foo/bar.git//Taskfile.yml?ref=main", node.rawUrl)
assert.Equal(t, "ssh://[email protected]/foo/bar.git", node.URL.String())
assert.Equal(t, "ssh://[email protected]/foo/bar.git//Taskfile.yml?ref=main", node.Location())
assert.Equal(t, "ssh://[email protected]/foo/bar.git", node.url.String())
entrypoint, err := node.ResolveEntrypoint("common.yml")
assert.NoError(t, err)
assert.Equal(t, "ssh://[email protected]/foo/bar.git//common.yml?ref=main", entrypoint)
Expand All @@ -27,8 +28,8 @@ func TestGitNode_sshWithDir(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "main", node.ref)
assert.Equal(t, "directory/Taskfile.yml", node.path)
assert.Equal(t, "ssh://[email protected]/foo/bar.git//directory/Taskfile.yml?ref=main", node.rawUrl)
assert.Equal(t, "ssh://[email protected]/foo/bar.git", node.URL.String())
assert.Equal(t, "ssh://[email protected]/foo/bar.git//directory/Taskfile.yml?ref=main", node.Location())
assert.Equal(t, "ssh://[email protected]/foo/bar.git", node.url.String())
entrypoint, err := node.ResolveEntrypoint("common.yml")
assert.NoError(t, err)
assert.Equal(t, "ssh://[email protected]/foo/bar.git//directory/common.yml?ref=main", entrypoint)
Expand All @@ -41,8 +42,8 @@ func TestGitNode_https(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "main", node.ref)
assert.Equal(t, "Taskfile.yml", node.path)
assert.Equal(t, "https://github.com/foo/bar.git//Taskfile.yml?ref=main", node.rawUrl)
assert.Equal(t, "https://github.com/foo/bar.git", node.URL.String())
assert.Equal(t, "https://github.com/foo/bar.git//Taskfile.yml?ref=main", node.Location())
assert.Equal(t, "https://github.com/foo/bar.git", node.url.String())
entrypoint, err := node.ResolveEntrypoint("common.yml")
assert.NoError(t, err)
assert.Equal(t, "https://github.com/foo/bar.git//common.yml?ref=main", entrypoint)
Expand All @@ -55,8 +56,8 @@ func TestGitNode_httpsWithDir(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "main", node.ref)
assert.Equal(t, "directory/Taskfile.yml", node.path)
assert.Equal(t, "https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main", node.rawUrl)
assert.Equal(t, "https://github.com/foo/bar.git", node.URL.String())
assert.Equal(t, "https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main", node.Location())
assert.Equal(t, "https://github.com/foo/bar.git", node.url.String())
entrypoint, err := node.ResolveEntrypoint("common.yml")
assert.NoError(t, err)
assert.Equal(t, "https://github.com/foo/bar.git//directory/common.yml?ref=main", entrypoint)
Expand All @@ -65,18 +66,28 @@ func TestGitNode_httpsWithDir(t *testing.T) {
func TestGitNode_CacheKey(t *testing.T) {
t.Parallel()

node, err := NewGitNode("https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main", "", false)
assert.NoError(t, err)
key := node.CacheKey()
assert.Equal(t, "Taskfile.yml-directory.f1ddddac425a538870230a3e38fc0cded4ec5da250797b6cab62c82477718fbb", key)
tests := []struct {
entrypoint string
expectedKey string
}{
{
entrypoint: "https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main",
expectedKey: "git.github.com.directory.Taskfile.yml.f1ddddac425a538870230a3e38fc0cded4ec5da250797b6cab62c82477718fbb",
},
{
entrypoint: "https://github.com/foo/bar.git//Taskfile.yml?ref=main",
expectedKey: "git.github.com.Taskfile.yml.39d28c1ff36f973705ae188b991258bbabaffd6d60bcdde9693d157d00d5e3a4",
},
{
entrypoint: "https://github.com/foo/bar.git//multiple/directory/Taskfile.yml?ref=main",
expectedKey: "git.github.com.directory.Taskfile.yml.1b6d145e01406dcc6c0aa572e5a5d1333be1ccf2cae96d18296d725d86197d31",
},
}

node, err = NewGitNode("https://github.com/foo/bar.git//Taskfile.yml?ref=main", "", false)
assert.NoError(t, err)
key = node.CacheKey()
assert.Equal(t, "Taskfile.yml-..39d28c1ff36f973705ae188b991258bbabaffd6d60bcdde9693d157d00d5e3a4", key)

node, err = NewGitNode("https://github.com/foo/bar.git//multiple/directory/Taskfile.yml?ref=main", "", false)
assert.NoError(t, err)
key = node.CacheKey()
assert.Equal(t, "Taskfile.yml-directory.1b6d145e01406dcc6c0aa572e5a5d1333be1ccf2cae96d18296d725d86197d31", key)
for _, tt := range tests {
node, err := NewGitNode(tt.entrypoint, "", false)
require.NoError(t, err)
key := node.CacheKey()
assert.Equal(t, tt.expectedKey, key)
}
}
34 changes: 15 additions & 19 deletions taskfile/node_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ import (

// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
type HTTPNode struct {
*BaseNode
URL *url.URL // stores url pointing actual remote file. (e.g. with Taskfile.yml)
entrypoint string // stores entrypoint url. used for building graph vertices.
*baseNode
url *url.URL // stores url pointing actual remote file. (e.g. with Taskfile.yml)
}

func NewHTTPNode(
Expand All @@ -33,46 +32,43 @@ func NewHTTPNode(
return nil, err
}
if url.Scheme == "http" && !insecure {
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
return nil, &errors.TaskfileNotSecureError{URI: url.Redacted()}
}

return &HTTPNode{
BaseNode: base,
URL: url,
entrypoint: entrypoint,
baseNode: base,
url: url,
}, nil
}

func (node *HTTPNode) Location() string {
return node.entrypoint
return node.url.Redacted()
}

func (node *HTTPNode) Read() ([]byte, error) {
return node.ReadContext(context.Background())
}

func (node *HTTPNode) ReadContext(ctx context.Context) ([]byte, error) {
url, err := RemoteExists(ctx, node.URL)
url, err := RemoteExists(ctx, *node.url)
if err != nil {
return nil, err
}
node.URL = url
req, err := http.NewRequest("GET", node.URL.String(), nil)
req, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
return nil, errors.TaskfileFetchFailedError{URI: node.Location()}
}

resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
if ctx.Err() != nil {
return nil, err
}
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
return nil, errors.TaskfileFetchFailedError{URI: node.Location()}
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.TaskfileFetchFailedError{
URI: node.URL.String(),
URI: node.Location(),
HTTPStatusCode: resp.StatusCode,
}
}
Expand All @@ -91,7 +87,7 @@ func (node *HTTPNode) ResolveEntrypoint(entrypoint string) (string, error) {
if err != nil {
return "", err
}
return node.URL.ResolveReference(ref).String(), nil
return node.url.ResolveReference(ref).String(), nil
}

func (node *HTTPNode) ResolveDir(dir string) (string, error) {
Expand All @@ -116,12 +112,12 @@ func (node *HTTPNode) ResolveDir(dir string) (string, error) {

func (node *HTTPNode) CacheKey() string {
checksum := strings.TrimRight(checksum([]byte(node.Location())), "=")
dir, filename := filepath.Split(node.entrypoint)
dir, filename := filepath.Split(node.url.Path)
lastDir := filepath.Base(dir)
prefix := filename
// Means it's not "", nor "." nor "/", so it's a valid directory
if len(lastDir) > 1 {
prefix = fmt.Sprintf("%s-%s", lastDir, filename)
prefix = fmt.Sprintf("%s.%s", lastDir, filename)
}
return fmt.Sprintf("%s.%s", prefix, checksum)
return fmt.Sprintf("http.%s.%s.%s", node.url.Host, prefix, checksum)
}
Loading
Loading