github.com/bilus/oya@v0.0.3-0.20190301162104-da4acbd394c6/pkg/raw/oyafile.go (about)

     1  package raw
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  
    12  	"github.com/bilus/oya/pkg/secrets"
    13  	log "github.com/sirupsen/logrus"
    14  	yaml "gopkg.in/yaml.v2"
    15  )
    16  
    17  const DefaultName = "Oyafile"
    18  
    19  // Oyafile represents an unparsed Oyafile.
    20  type Oyafile struct {
    21  	Path    string // Path contains normalized absolute path.
    22  	RootDir string // RootDir is the absolute, normalized path to the project root directory.
    23  	file    []byte // file contains Oyafile contents.
    24  }
    25  
    26  // DecodedOyafile is an Oyafile that has been loaded from YAML
    27  // but hasn't been parsed yet.
    28  type DecodedOyafile map[string]interface{}
    29  
    30  func (o *DecodedOyafile) Merge(values map[string]interface{}) {
    31  	for k, v := range values {
    32  		(*o)[k] = v
    33  	}
    34  }
    35  
    36  func Load(oyafilePath, rootDir string) (*Oyafile, bool, error) {
    37  	raw, err := New(oyafilePath, rootDir)
    38  	if err != nil {
    39  		return nil, false, nil
    40  	}
    41  	return raw, true, nil
    42  }
    43  
    44  func LoadFromDir(dirPath, rootDir string) (*Oyafile, bool, error) {
    45  	oyafilePath := fullPath(dirPath, "")
    46  	fi, err := os.Stat(oyafilePath)
    47  	if err != nil {
    48  		if os.IsNotExist(err) {
    49  			return nil, false, nil
    50  		}
    51  		return nil, false, err
    52  	}
    53  	if fi.IsDir() {
    54  		return nil, false, nil
    55  	}
    56  	return Load(oyafilePath, rootDir)
    57  }
    58  
    59  func New(oyafilePath, rootDir string) (*Oyafile, error) {
    60  	file, err := ioutil.ReadFile(oyafilePath)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	return &Oyafile{
    65  		Path:    oyafilePath,
    66  		RootDir: rootDir,
    67  		file:    file,
    68  	}, nil
    69  }
    70  
    71  func (raw *Oyafile) Decode() (DecodedOyafile, error) {
    72  	// YAML parser does not handle files without at least one node.
    73  	empty, err := isEmptyYAML(raw.Path)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	if empty {
    78  		return make(DecodedOyafile), nil
    79  	}
    80  	decodedOyafile, err := decodeYaml(raw.file)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	secs, err := secrets.Decrypt(raw.RootDir)
    85  	if err != nil {
    86  		if log.GetLevel() == log.DebugLevel {
    87  			log.Debug(fmt.Sprintf("Secrets could not be loaded at %v. %v", raw.RootDir, err))
    88  		}
    89  	} else {
    90  		if len(secs) > 0 {
    91  			decodedSecrets, err := decodeYaml(secs)
    92  			if err != nil {
    93  				log.Warn(fmt.Sprintf("Secrets could not be loaded at %v. %v", raw.RootDir, err))
    94  			}
    95  			decodedOyafile.Merge(decodedSecrets)
    96  		}
    97  	}
    98  	return decodedOyafile, nil
    99  }
   100  
   101  func decodeYaml(content []byte) (DecodedOyafile, error) {
   102  	reader := bytes.NewReader(content)
   103  	decoder := yaml.NewDecoder(reader)
   104  	var of DecodedOyafile
   105  	err := decoder.Decode(&of)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	return of, nil
   110  }
   111  
   112  func (raw *Oyafile) LookupKey(key string) (interface{}, bool, error) {
   113  	of, err := raw.Decode()
   114  	if err != nil {
   115  		return nil, false, err
   116  	}
   117  	val, ok := of[key]
   118  	return val, ok, nil
   119  }
   120  
   121  func (raw *Oyafile) IsRoot() (bool, error) {
   122  	_, hasProject, err := raw.LookupKey("Project")
   123  	if err != nil {
   124  		return false, err
   125  	}
   126  
   127  	rel, err := filepath.Rel(raw.RootDir, raw.Path)
   128  	if err != nil {
   129  		return false, err
   130  	}
   131  	return hasProject && rel == DefaultName, nil
   132  }
   133  
   134  // isEmptyYAML returns true if the Oyafile contains only blank characters
   135  // or YAML comments.
   136  func isEmptyYAML(oyafilePath string) (bool, error) {
   137  	file, err := os.Open(oyafilePath)
   138  	if err != nil {
   139  		return false, err
   140  	}
   141  	defer file.Close()
   142  
   143  	scanner := bufio.NewScanner(file)
   144  	for scanner.Scan() {
   145  		if isNode(scanner.Text()) {
   146  			return false, nil
   147  		}
   148  	}
   149  
   150  	return true, scanner.Err()
   151  }
   152  
   153  func isNode(line string) bool {
   154  	for _, c := range line {
   155  		switch c {
   156  		case '#':
   157  			return false
   158  		case ' ', '\t', '\n', '\f', '\r':
   159  			continue
   160  		default:
   161  			return true
   162  		}
   163  	}
   164  	return false
   165  }
   166  
   167  func fullPath(projectDir, name string) string {
   168  	if len(name) == 0 {
   169  		name = DefaultName
   170  	}
   171  	return path.Join(projectDir, name)
   172  }