github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/pkg/jsonnet/jpath/dirs.go (about)

     1  package jpath
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"runtime"
     7  )
     8  
     9  // Dirs returns the project-root (root) and environment directory (base)
    10  func Dirs(path string) (root string, base string, err error) {
    11  	root, err = FindRoot(path)
    12  	if err != nil {
    13  		return "", "", err
    14  	}
    15  
    16  	base, err = FindBase(path, root)
    17  	if err != nil {
    18  		return root, "", err
    19  	}
    20  
    21  	return root, base, err
    22  }
    23  
    24  // FindRoot returns the absolute path of the project root, being the directory
    25  // that directly holds `tkrc.yaml` if it exists, otherwise the directory that
    26  // directly holds `jsonnetfile.json`
    27  func FindRoot(path string) (dir string, err error) {
    28  	start, err := FsDir(path)
    29  	if err != nil {
    30  		return "", err
    31  	}
    32  
    33  	// root path based on os
    34  	stop := "/"
    35  	if runtime.GOOS == "windows" {
    36  		stop = filepath.VolumeName(start) + "\\"
    37  	}
    38  
    39  	// try tkrc.yaml first
    40  	root, err := FindParentFile("tkrc.yaml", start, stop)
    41  	if err == nil {
    42  		return root, nil
    43  	}
    44  
    45  	// otherwise use jsonnetfile.json
    46  	root, err = FindParentFile("jsonnetfile.json", start, stop)
    47  	if _, ok := err.(ErrorFileNotFound); ok {
    48  		return "", ErrorNoRoot
    49  	} else if err != nil {
    50  		return "", err
    51  	}
    52  
    53  	return root, nil
    54  }
    55  
    56  // FindBase returns the absolute path of the environments base directory, the
    57  // one which directly holds the entrypoint file.
    58  func FindBase(path string, root string) (string, error) {
    59  	dir, err := FsDir(path)
    60  	if err != nil {
    61  		return "", err
    62  	}
    63  
    64  	filename, err := Filename(path)
    65  	if err != nil {
    66  		return "", err
    67  	}
    68  
    69  	base, err := FindParentFile(filename, dir, root)
    70  
    71  	if _, ok := err.(ErrorFileNotFound); ok {
    72  		return "", ErrorNoBase{filename: filename}
    73  	} else if err != nil {
    74  		return "", err
    75  	}
    76  
    77  	return base, nil
    78  }
    79  
    80  // FindParentFile traverses the parent directory tree for the given `file`,
    81  // starting from `start` and ending in `stop`. If the file is not found an error is returned.
    82  func FindParentFile(file, start, stop string) (string, error) {
    83  	files, err := os.ReadDir(start)
    84  	if err != nil {
    85  		return "", err
    86  	}
    87  
    88  	if dirContainsFile(files, file) {
    89  		return start, nil
    90  	} else if start == stop {
    91  		return "", ErrorFileNotFound{file}
    92  	}
    93  	return FindParentFile(file, filepath.Dir(start), stop)
    94  }
    95  
    96  // dirContainsFile returns whether a file is included in a directory.
    97  func dirContainsFile(files []os.DirEntry, filename string) bool {
    98  	for _, f := range files {
    99  		if f.Name() == filename {
   100  			return true
   101  		}
   102  	}
   103  	return false
   104  }
   105  
   106  // FsDir returns the most inner directory of path, as reported by the local
   107  // filesystem
   108  func FsDir(path string) (string, error) {
   109  	path, err := filepath.Abs(path)
   110  	if err != nil {
   111  		return "", err
   112  	}
   113  
   114  	fi, err := os.Stat(path)
   115  	if err != nil {
   116  		return "", err
   117  	}
   118  
   119  	if fi.IsDir() {
   120  		return path, nil
   121  	}
   122  
   123  	return filepath.Dir(path), nil
   124  }