Skip to content
Snippets Groups Projects
repository.go 3.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • package csbi
    
    import (
    	"fmt"
    	"io/fs"
    	"os"
    	"path/filepath"
    	"regexp"
    	"strings"
    
    	gpb "github.com/openconfig/gnmi/proto/gnmi"
    	log "github.com/sirupsen/logrus"
    )
    
    // Repository provides access to yang model files.
    type Repository interface {
    	// FindYANGFiles returns all YANG files matching the provided ModelData
    	FindYANGFiles(models []*gpb.ModelData) ([]string, []error)
    	// YANGPathsWithSuffix returns all YANG search paths with a '...' suffix
    	YANGPathsWithSuffix() ([]string, error)
    }
    
    // NewRepository returns a implementation of the Repository interface
    func NewRepository(basePath string) Repository {
    	return &repo{
    		fs: &filesystem{
    			root: basePath,
    		},
    	}
    }
    
    type repo struct {
    	fs Filesystem
    }
    
    func (r *repo) YANGPathsWithSuffix() ([]string, error) {
    	pathSet := make(map[string]struct{})
    	paths, err := r.fs.Glob(".yang")
    	out := make([]string, 0)
    	if err != nil {
    		return nil, err
    	}
    	for _, p := range paths {
    		p = p[:strings.LastIndex(p, "/")]
    		_, exist := pathSet[p]
    		if !exist {
    			pathSet[p] = struct{}{}
    			out = append(out, filepath.Join(p, "..."))
    		}
    	}
    	return out, nil
    }
    
    func (r *repo) FindYANGFiles(models []*gpb.ModelData) ([]string, []error) {
    	filePaths := make([]string, 0)
    	errs := make([]error, 0)
    	for _, model := range models {
    		if !(strings.Contains(model.Name, "augments") || strings.Contains(model.Name, "deviations")) {
    			path, err := searchYANGFile(r.fs, model.Name, model.Organization, model.Version)
    			if err != nil {
    				log.Error(err)
    				errs = append(errs, err)
    				continue
    			}
    			filePaths = append(filePaths, path)
    		} else {
    			log.WithFields(log.Fields{
    				"model": model.Name,
    			}).Info("augments and deviations are ignored")
    		}
    	}
    	return filePaths, errs
    }
    
    // The Filesystem interface defines the implementation required for any YANG
    // model repository data source.
    type Filesystem interface {
    	fs.FS
    	fs.GlobFS
    }
    
    type filesystem struct {
    	root string
    }
    
    func (osfs *filesystem) Open(name string) (fs.File, error) {
    	if err := osfs.fsValid(name); err != nil {
    		return nil, err
    	}
    	return os.Open(name)
    }
    
    func (osfs *filesystem) ReadDir(name string) ([]fs.DirEntry, error) {
    	if err := osfs.fsValid(name); err != nil {
    		return nil, err
    	}
    	return os.ReadDir(name)
    }
    
    func (osfs *filesystem) ReadFile(name string) ([]byte, error) {
    	if err := osfs.fsValid(name); err != nil {
    		return nil, err
    	}
    	return os.ReadFile(name)
    }
    
    func (osfs *filesystem) Stat(name string) (fs.FileInfo, error) {
    	if err := osfs.fsValid(name); err != nil {
    		return nil, err
    	}
    	return os.Stat(name)
    }
    
    func (osfs *filesystem) Glob(pattern string) (paths []string, err error) {
    	defer func() {
    		if e := recover(); e != nil {
    
    			err = e.(error) //nolint:errcheck
    
    			paths = nil
    		}
    	}()
    	_, err = osfs.ReadDir(osfs.root)
    	if err != nil {
    		return nil, err
    	}
    
    	paths = make([]string, 0)
    	re := regexp.MustCompile(pattern)
    	if err := filepath.WalkDir(osfs.root, func(path string, d fs.DirEntry, err error) error {
    		if re.Match([]byte(d.Name())) {
    			p := filepath.Join(path)
    			paths = append(paths, p)
    		}
    		return nil
    	}); err != nil {
    		return nil, err
    	}
    	return paths, nil
    }
    
    func (osfs *filesystem) fsValid(name string) error {
    	fullName := filepath.Join(osfs.root, name)
    	if !fs.ValidPath(fullName) {
    		return &fs.PathError{Err: fs.ErrInvalid}
    	}
    	return nil
    }
    
    func findBestMatch(paths []string, org, version string) (string, error) {
    	out := make([]string, 0)
    	for _, path := range paths {
    		if strings.Contains(path, org) {
    			out = append(out, path)
    		}
    	}
    	if len(out) > 0 {
    		return out[0], nil
    	}
    	return paths[0], nil
    }
    
    func searchYANGFile(fsys Filesystem, name, org, version string) (string, error) {
    	paths, err := fsys.Glob(name + ".yang")
    	if err != nil {
    		return "", err
    	}
    	if len(paths) == 0 {
    		return "", fmt.Errorf("did not find file for %v in %v", name, fsys)
    	}
    	return findBestMatch(paths, org, version)
    }