mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 17:31:56 +03:00
9335e40d76
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7068 GitOrigin-RevId: 37735c0020df23857015871890d2f6774166d7b1
120 lines
3.9 KiB
Go
120 lines
3.9 KiB
Go
package cli
|
|
|
|
import (
|
|
stderrors "errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/hasura/graphql-engine/cli/v2/internal/errors"
|
|
)
|
|
|
|
// validateDirectory sets execution directory and validate it to see that or any
|
|
// of the parent directory is a valid project directory. A valid project
|
|
// directory contains the following:
|
|
// 1. migrations directory
|
|
// 2. config.yaml file
|
|
// 3. metadata.yaml (optional)
|
|
// If the current directory or any parent directory (upto filesystem root) is
|
|
// found to have these files, ExecutionDirectory is set as that directory.
|
|
func (ec *ExecutionContext) validateDirectory() error {
|
|
var op errors.Op = "cli.ExecutionContext.validateDirectory"
|
|
var err error
|
|
if len(ec.ExecutionDirectory) == 0 {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("error getting current working directory: %w", err))
|
|
}
|
|
ec.ExecutionDirectory = cwd
|
|
} else {
|
|
ec.ExecutionDirectory, err = filepath.Abs(ec.ExecutionDirectory)
|
|
if err != nil {
|
|
return errors.E(op, fmt.Errorf("error finding absolute path for project directory: %w", err))
|
|
}
|
|
}
|
|
|
|
ed, err := os.Stat(ec.ExecutionDirectory)
|
|
if err != nil {
|
|
if stderrors.Is(err, fs.ErrNotExist) {
|
|
return errors.E(op, fmt.Errorf("did not find required directory. use 'init'?: %w", err))
|
|
}
|
|
return errors.E(op, fmt.Errorf("error getting directory details: %w", err))
|
|
}
|
|
if !ed.IsDir() {
|
|
return errors.E(op, fmt.Errorf("'%s' is not a directory: %w", ed.Name(), err))
|
|
}
|
|
// config.yaml
|
|
// migrations/
|
|
// (optional) metadata.yaml
|
|
dir, err := recursivelyValidateDirectory(ec.ExecutionDirectory)
|
|
if err != nil {
|
|
return errors.E("validate: %w", err)
|
|
}
|
|
|
|
ec.ExecutionDirectory = dir
|
|
return nil
|
|
}
|
|
|
|
// filesRequired are the files that are mandatory to qualify for a project
|
|
// directory.
|
|
var filesRequired = []string{
|
|
"config.yaml",
|
|
}
|
|
|
|
// recursivelyValidateDirectory tries to parse 'startFrom' as a project
|
|
// directory by checking for the 'filesRequired'. If the parent of 'startFrom'
|
|
// (nextDir) is filesystem root, error is returned. Otherwise, 'nextDir' is
|
|
// validated, recursively.
|
|
func recursivelyValidateDirectory(startFrom string) (validDir string, err error) {
|
|
var op errors.Op = "cli.recursivelyValidateDirectory"
|
|
err = ValidateDirectory(startFrom)
|
|
if err != nil {
|
|
nextDir := filepath.Dir(startFrom)
|
|
// to catch error gracefully in loop situation
|
|
if nextDir == startFrom {
|
|
return "", errors.E(op, fmt.Errorf("failed recursively find config.yaml: search stopped due to a possible infinite filesystem traversal at %s", nextDir))
|
|
}
|
|
if err := CheckFilesystemBoundary(nextDir); err != nil {
|
|
return nextDir, errors.E(op, fmt.Errorf("cannot find [%s] | search stopped: %w", strings.Join(filesRequired, ", "), err))
|
|
}
|
|
return recursivelyValidateDirectory(nextDir)
|
|
}
|
|
return startFrom, nil
|
|
}
|
|
|
|
// validateDirectory tries to parse dir for the filesRequired and returns error
|
|
// if any one of them is missing.
|
|
func ValidateDirectory(dir string) error {
|
|
var op errors.Op = "cli.ValidateDirectory"
|
|
notFound := []string{}
|
|
for _, f := range filesRequired {
|
|
if _, err := os.Stat(filepath.Join(dir, f)); stderrors.Is(err, fs.ErrNotExist) {
|
|
relpath, e := filepath.Rel(dir, f)
|
|
if e == nil {
|
|
f = relpath
|
|
}
|
|
notFound = append(notFound, f)
|
|
}
|
|
}
|
|
if len(notFound) > 0 {
|
|
return errors.E(op, fmt.Errorf("cannot validate directory '%s': [%s] not found", dir, strings.Join(notFound, ", ")))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CheckFilesystemBiundary returns an error if dir is filesystem root
|
|
func CheckFilesystemBoundary(dir string) error {
|
|
var op errors.Op = "cli.CheckFilesystemBoundary"
|
|
// since filepath.Abs calls filepath.Clean the path is expected to be in "clean" state
|
|
isWindowsRoot, _ := regexp.MatchString(`^[a-zA-Z]:\\$`, dir)
|
|
// return error if filesystem boundary is hit
|
|
if dir == "/" || isWindowsRoot {
|
|
return errors.E(op, "filesystem boundary hit")
|
|
|
|
}
|
|
return nil
|
|
}
|