github.com/criteo/command-launcher@v0.0.0-20230407142452-fb616f546e98/internal/repository/default-repo-index.go (about)

     1  package repository
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"sort"
     8  
     9  	log "github.com/sirupsen/logrus"
    10  
    11  	"github.com/criteo/command-launcher/internal/command"
    12  	"github.com/criteo/command-launcher/internal/config"
    13  	"github.com/criteo/command-launcher/internal/pkg"
    14  	"github.com/spf13/viper"
    15  )
    16  
    17  /*
    18  Internal data structure to represent the local repository index
    19  You can find an example of the file in the path specified by
    20  config "local_command_repository_dirname"
    21  
    22  Current implementation of the repoIndex is to scan all manifest.mf files
    23  in one level subfolders
    24  
    25  Further improvements could store commands as indexes, and laze load
    26  further information when necessary to reduce the startup time
    27  */
    28  type defaultRepoIndex struct {
    29  	id             string
    30  	packages       map[string]command.PackageManifest
    31  	packageDirs    map[string]string          // key is the package name, value is the package directory
    32  	groupCmds      map[string]command.Command // key is in form of [repo]>[package]>[group]>[cmd name]
    33  	executableCmds map[string]command.Command // key is in form of [repo]>[package]>[group]>[cmd name]
    34  	systemCmds     map[string]command.Command // key is the predefined system command name
    35  }
    36  
    37  func newDefaultRepoIndex(id string) (RepoIndex, error) {
    38  	repoIndex := defaultRepoIndex{
    39  		id:             id,
    40  		packages:       make(map[string]command.PackageManifest),
    41  		packageDirs:    make(map[string]string),
    42  		groupCmds:      map[string]command.Command{},
    43  		executableCmds: map[string]command.Command{},
    44  		systemCmds:     make(map[string]command.Command),
    45  	}
    46  
    47  	return &repoIndex, nil
    48  }
    49  
    50  func (repoIndex *defaultRepoIndex) loadPackages(repoDir string) error {
    51  	_, err := os.Stat(repoDir)
    52  	if !os.IsNotExist(err) {
    53  		files, err := os.ReadDir(repoDir)
    54  		if err != nil {
    55  			log.Errorf("cannot read the repo dir: %v", err)
    56  			return err
    57  		}
    58  
    59  		for _, f := range files {
    60  			if !f.IsDir() && f.Type()&os.ModeSymlink != os.ModeSymlink {
    61  				continue
    62  			}
    63  			if manifestFile, err := os.Open(filepath.Join(repoDir, f.Name(), "manifest.mf")); err == nil {
    64  				defer manifestFile.Close()
    65  				manifest, err := pkg.ReadManifest(manifestFile)
    66  				if err == nil {
    67  					repoIndex.packages[manifest.Name()] = manifest
    68  					repoIndex.packageDirs[manifest.Name()] = filepath.Join(repoDir, f.Name())
    69  				}
    70  			}
    71  		}
    72  	}
    73  	return err
    74  }
    75  
    76  func (repoIndex *defaultRepoIndex) extractCmds(repoDir string) {
    77  	sysPkgName := viper.GetString(config.SYSTEM_PACKAGE_KEY)
    78  	repoIndex.groupCmds = make(map[string]command.Command)
    79  	repoIndex.executableCmds = make(map[string]command.Command)
    80  	// initiate group cmds and exectuable cmds map
    81  	// the key is in format of [group]#[cmd name]
    82  	for _, pkg := range repoIndex.packages {
    83  		if pkg.Commands() != nil {
    84  			for _, cmd := range pkg.Commands() {
    85  				cmd.SetPackageDir(repoIndex.packageDirs[pkg.Name()])
    86  				cmd.SetNamespace(repoIndex.id, pkg.Name())
    87  				repoIndex.registerCmd(pkg, cmd,
    88  					sysPkgName != "" && pkg.Name() == sysPkgName && repoIndex.id == "default", // always use default repository for system package
    89  				)
    90  			}
    91  		}
    92  	}
    93  }
    94  
    95  func (repoIndex *defaultRepoIndex) Load(repoDir string) error {
    96  	err := repoIndex.loadPackages(repoDir)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	repoIndex.extractCmds(repoDir)
   101  	return nil
   102  }
   103  
   104  func (repoIndex *defaultRepoIndex) Add(pkg command.PackageManifest, repoDir string, pkgDirName string) error {
   105  	repoIndex.packages[pkg.Name()] = pkg
   106  	repoIndex.packageDirs[pkg.Name()] = filepath.Join(repoDir, pkgDirName)
   107  	repoIndex.extractCmds(repoDir)
   108  	return nil
   109  }
   110  
   111  func (repoIndex *defaultRepoIndex) Remove(pkgName string, repoDir string) error {
   112  	delete(repoIndex.packages, pkgName)
   113  	delete(repoIndex.packageDirs, pkgName)
   114  	repoIndex.extractCmds(repoDir)
   115  	return nil
   116  }
   117  
   118  func (repoIndex *defaultRepoIndex) Update(pkg command.PackageManifest, repoDir string, pkgDirName string) error {
   119  	repoIndex.packages[pkg.Name()] = pkg
   120  	repoIndex.packageDirs[pkg.Name()] = filepath.Join(repoDir, pkgDirName)
   121  	repoIndex.extractCmds(repoDir)
   122  	return nil
   123  }
   124  
   125  func (repoIndex *defaultRepoIndex) AllPackages() []command.PackageManifest {
   126  	pkgs := []command.PackageManifest{}
   127  	for _, p := range repoIndex.packages {
   128  		newPkg := p
   129  		pkgs = append(pkgs, newPkg)
   130  	}
   131  	return pkgs
   132  }
   133  
   134  func (repoIndex *defaultRepoIndex) Package(name string) (command.PackageManifest, error) {
   135  	if pkg, exists := repoIndex.packages[name]; exists {
   136  		return pkg, nil
   137  	}
   138  	return nil, fmt.Errorf("cannot find the package '%s'", name)
   139  }
   140  
   141  func (repoIndex *defaultRepoIndex) Command(pkg string, group string, name string) (command.Command, error) {
   142  	if cmd, exist := repoIndex.groupCmds[command.CmdID(repoIndex.id, pkg, group, name)]; exist {
   143  		return cmd, nil
   144  	}
   145  
   146  	if cmd, exist := repoIndex.executableCmds[command.CmdID(repoIndex.id, pkg, group, name)]; exist {
   147  		return cmd, nil
   148  	}
   149  
   150  	return nil, fmt.Errorf("cannot find the command %s %s", group, name)
   151  }
   152  
   153  func (repoIndex *defaultRepoIndex) AllCommands() []command.Command {
   154  	cmds := repoIndex.GroupCommands()
   155  	cmds = append(cmds, repoIndex.ExecutableCommands()...)
   156  	sort.Slice(cmds, func(i, j int) bool {
   157  		return cmds[i].ID() < cmds[j].ID()
   158  	})
   159  	return cmds
   160  }
   161  
   162  func (repoIndex *defaultRepoIndex) GroupCommands() []command.Command {
   163  	cmds := make([]command.Command, 0)
   164  	for _, v := range repoIndex.groupCmds {
   165  		cmds = append(cmds, v)
   166  	}
   167  	sort.Slice(cmds, func(i, j int) bool {
   168  		return cmds[i].ID() < cmds[j].ID()
   169  	})
   170  	return cmds
   171  }
   172  
   173  func (repoIndex *defaultRepoIndex) SystemLoginCommand() command.Command {
   174  	if cmd, exist := repoIndex.systemCmds[SYSTEM_LOGIN_COMMAND]; exist {
   175  		return cmd
   176  	}
   177  	return nil
   178  }
   179  
   180  func (repoIndex *defaultRepoIndex) SystemMetricsCommand() command.Command {
   181  	if cmd, exist := repoIndex.systemCmds[SYSTEM_METRICS_COMMAND]; exist {
   182  		return cmd
   183  	}
   184  	return nil
   185  }
   186  
   187  func (repoIndex *defaultRepoIndex) ExecutableCommands() []command.Command {
   188  	cmds := make([]command.Command, 0)
   189  	for _, v := range repoIndex.executableCmds {
   190  		cmds = append(cmds, v)
   191  	}
   192  	sort.Slice(cmds, func(i, j int) bool {
   193  		return cmds[i].ID() < cmds[j].ID()
   194  	})
   195  	return cmds
   196  }
   197  
   198  func (repoIndex *defaultRepoIndex) registerCmd(pkg command.PackageManifest, cmd command.Command, isSystemPkg bool) {
   199  	switch cmd.Type() {
   200  	case "group":
   201  		repoIndex.groupCmds[cmd.ID()] = cmd
   202  	case "executable":
   203  		repoIndex.executableCmds[cmd.ID()] = cmd
   204  	case "system":
   205  		if isSystemPkg {
   206  			repoIndex.extractSystemCmds(cmd)
   207  		}
   208  	}
   209  }
   210  
   211  func (repoIndex *defaultRepoIndex) extractSystemCmds(cmd command.Command) {
   212  	switch cmd.Name() {
   213  	case SYSTEM_LOGIN_COMMAND:
   214  		repoIndex.systemCmds[SYSTEM_LOGIN_COMMAND] = cmd
   215  	case SYSTEM_METRICS_COMMAND:
   216  		repoIndex.systemCmds[SYSTEM_METRICS_COMMAND] = cmd
   217  	}
   218  }