Skip to content
Open
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
68 changes: 68 additions & 0 deletions buildcontext/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package buildcontext
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

Expand Down Expand Up @@ -137,6 +138,71 @@ func (r *Resolver) ExpandWildcard(
return ret, nil
}

// resolveLocalRootEarthfile rewrites a Earthfile reference for the root of a project.
func resolveLocalRootEarthfile(ref domain.Reference) domain.Reference {
if ref.IsRemote() ||
ref.IsImportReference() ||
filepath.Clean(ref.GetLocalPath()) != "." ||
strings.HasPrefix(ref.GetName(), DockerfileMetaTarget) {
return ref
}

cwd, err := os.Getwd()
if err != nil {
return ref
}

curr := cwd
for {
if !hasEarthfile(curr) {
parent := filepath.Dir(curr)
if parent == curr {
break
}

curr = parent

continue
}

relPath, err := filepath.Rel(cwd, curr)
if err != nil {
return withLocalPath(ref, curr)
}

relPath = filepath.ToSlash(relPath)
if !strings.HasPrefix(relPath, ".") && !filepath.IsAbs(relPath) {
relPath = "./" + relPath
}

return withLocalPath(ref, relPath)
}

return ref
}

func hasEarthfile(dir string) bool {
fi, err := os.Stat(filepath.Join(dir, Earthfile))
if err != nil {
return false
}

return !fi.IsDir()
}

func withLocalPath(ref domain.Reference, newLocalPath string) domain.Reference {
switch r := ref.(type) {
case domain.Target:
r.LocalPath = newLocalPath
return r
case domain.Command:
r.LocalPath = newLocalPath
return r
default:
return ref
}
}

// Resolve returns resolved context data for a given earth reference. If the reference is a target,
// then the context will include a build context and possibly additional local directories.
func (r *Resolver) Resolve(
Expand All @@ -146,6 +212,8 @@ func (r *Resolver) Resolve(
return nil, errors.Errorf("cannot resolve non-dereferenced import ref %s", ref.String())
}

ref = resolveLocalRootEarthfile(ref)

var (
d *Data
err error
Expand Down
88 changes: 88 additions & 0 deletions buildcontext/resolver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package buildcontext

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"

"github.com/EarthBuild/earthbuild/domain"
)

//nolint:paralleltest
func TestResolveLocalRootEarthfile(t *testing.T) {
t.Run("finds Earthfile in grandparent directory (../../)", func(t *testing.T) {
tmpDir := t.TempDir()

err := os.WriteFile(filepath.Join(tmpDir, "Earthfile"), []byte("VERSION 0.8"), 0o600)
require.NoError(t, err)

subSubDir := filepath.Join(tmpDir, "sub", "subsub")

err = os.MkdirAll(subSubDir, 0o700)
require.NoError(t, err)

t.Chdir(subSubDir)

ref, err := domain.ParseTarget("+foo")
require.NoError(t, err)

resolved := resolveLocalRootEarthfile(ref)

require.Equal(t, "../..", resolved.GetLocalPath())
})

t.Run("finds Earthfile in current directory (.)", func(t *testing.T) {
tmpDir := t.TempDir()

err := os.WriteFile(filepath.Join(tmpDir, "Earthfile"), []byte("VERSION 0.8"), 0o600)
require.NoError(t, err)

t.Chdir(tmpDir)

ref, err := domain.ParseTarget("+foo")
require.NoError(t, err)

resolved := resolveLocalRootEarthfile(ref)

require.Equal(t, ".", resolved.GetLocalPath())
})

t.Run("stops at nearest ancestor Earthfile", func(t *testing.T) {
tmpDir := t.TempDir()

err := os.WriteFile(filepath.Join(tmpDir, "Earthfile"), []byte("VERSION 0.8"), 0o600)
require.NoError(t, err)

withOwnDir := filepath.Join(tmpDir, "sub", "with_own_earthfile")

err = os.MkdirAll(withOwnDir, 0o700)
require.NoError(t, err)

err = os.WriteFile(filepath.Join(withOwnDir, "Earthfile"), []byte("VERSION 0.8"), 0o600)
require.NoError(t, err)

t.Chdir(withOwnDir)

ref, err := domain.ParseTarget("+foo")
require.NoError(t, err)

resolved := resolveLocalRootEarthfile(ref)

require.Equal(t, ".", resolved.GetLocalPath())
})

t.Run("returns original reference when no Earthfile exists in ancestors", func(t *testing.T) {
tmpDir := t.TempDir()

t.Chdir(tmpDir)

ref, err := domain.ParseTarget("+foo")
require.NoError(t, err)

resolved := resolveLocalRootEarthfile(ref)

require.Equal(t, ".", resolved.GetLocalPath())
})
}
Loading