plugin.go (205 lines of code) (raw):

// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package openserverless import ( "errors" "flag" "fmt" "os" "path/filepath" "regexp" "strings" git "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/mitchellh/go-homedir" ) func pluginTool() error { flagSet := flag.NewFlagSet("plugin", flag.ExitOnError) flagSet.Usage = printPluginUsage err := flagSet.Parse(os.Args[1:]) if err != nil { return err } if flagSet.NArg() > 1 { flagSet.Usage() return errors.New("invalid number of arguments. Expected 1") } switch flagSet.Arg(0) { case "", "list": err := printPluginsHelp() if err != nil { return err } return nil default: return downloadPluginTasksFromRepo(flagSet.Arg(0)) } } func printPluginUsage() { fmt.Println(`Usage: ops -plugin <repo-url> Install/update plugins from a remote repository. The name of the repository must start with 'olaris-'.`) } func downloadPluginTasksFromRepo(repo string) error { isNameValid, repoName := checkGitRepo(repo) if !isNameValid { return fmt.Errorf("plugin repository must be a https url and plugin must start with 'olaris-'") } pluginDir, err := homedir.Expand("~/.ops/" + repoName) if err != nil { return err } if isDir(pluginDir) { fmt.Println("Updating plugin", repoName) r, err := git.PlainOpen(pluginDir) if err != nil { return err } // Get the working directory for the repository w, err := r.Worktree() if err != nil { return err } // Pull the latest changes from the origin remote and merge into the current branch err = w.Pull(&git.PullOptions{RemoteName: "origin"}) if err != nil { if err.Error() == "already up-to-date" { fmt.Println("The plugin repo is already up to date!") return nil } return err } return nil } if err := os.MkdirAll(pluginDir, 0755); err != nil { return err } // if not, clone cloneOpts := &git.CloneOptions{ URL: repo, Progress: os.Stderr, ReferenceName: plumbing.NewBranchReferenceName("main"), } fmt.Println("Downloading plugins:", repoName) _, err = git.PlainClone(pluginDir, false, cloneOpts) if err != nil { return err } return nil } func checkGitRepo(url string) (bool, string) { // Remove the ".git" extension if present url = strings.TrimSuffix(url, ".git") // Extract the repository name from the URL parts := strings.Split(url, "/") repoName := parts[len(parts)-1] // Check if the repository name matches the pattern "https://...olaris-*" matchProtocol, _ := regexp.MatchString(`^https://.*$`, url) matchName, _ := regexp.MatchString(`^olaris-.*$`, repoName) if matchName && matchProtocol { return true, repoName } return false, "" } func printPluginsHelp() error { plgs, err := newPlugins() if err != nil { return err } plgs.print() return nil } // GetOpsRootPlugins returns the map with all the olaris-*/opsroot.json files // in the local and ~/.ops folders, pointed by their plugin names. // If the same plugin is found in both folders, the one in the local folder // is used. // Useful to build the config map including the plugin configs func GetOpsRootPlugins() (map[string]string, error) { plgs, err := newPlugins() if err != nil { return nil, err } opsRoots := make(map[string]string) for _, path := range plgs.local { name := getPluginName(path) opsRootPath := joinpath(path, OPSROOT) opsRoots[name] = opsRootPath } for _, path := range plgs.ops { name := getPluginName(path) // if the plugin is already in the map, skip it if _, ok := opsRoots[name]; ok { continue } opsRootPath := joinpath(path, OPSROOT) opsRoots[name] = opsRootPath } return opsRoots, nil } // findTaskInPlugins returns the path to the plugin containing the task // or an error if the task is not found func findTaskInPlugins(plg string) (string, error) { plgs, err := newPlugins() if err != nil { return "", err } // check that plg is the suffix of a folder name in plgs.local for _, path := range plgs.local { folder := filepath.Base(path) if strings.TrimPrefix(folder, "olaris-") == plg { return path, nil } } // check that plg is the suffix of a folder name in plgs.ops for _, path := range plgs.ops { folder := filepath.Base(path) if strings.TrimPrefix(folder, "olaris-") == plg { return path, nil } } return "", &TaskNotFoundErr{input: plg} } // plugins struct holds the list of local and ~/.ops olaris-* folders type plugins struct { local []string ops []string } func newPlugins() (*plugins, error) { localDir := os.Getenv("OPS_ROOT_PLUGIN") localOlarisFolders := make([]string, 0) opsOlarisFolders := make([]string, 0) // Search in directory (localDir/olaris-*) dir := filepath.Join(localDir, "olaris-*") olarisFolders, err := filepath.Glob(dir) if err != nil { return nil, err } // filter all folders that are do not contain opsfile.yaml for _, folder := range olarisFolders { if !isDir(folder) || !exists(folder, OPSFILE) { continue } localOlarisFolders = append(localOlarisFolders, folder) } // Search in ~/.ops/olaris-* opsHome, err := homedir.Expand("~/.ops") if err != nil { return nil, err } olarisOpsFolders, err := filepath.Glob(filepath.Join(opsHome, "olaris-*")) if err != nil { return nil, err } for _, folder := range olarisOpsFolders { if !isDir(folder) || !exists(folder, OPSFILE) { continue } opsOlarisFolders = append(opsOlarisFolders, folder) } return &plugins{ local: localOlarisFolders, ops: opsOlarisFolders, }, nil } func (p *plugins) print() { if len(p.local) == 0 && len(p.ops) == 0 { debug("No plugins installed") fmt.Println("No plugins installed. Use 'ops -plugin' to add new ones.") return } fmt.Println("Plugins:") if len(p.local) > 0 { for _, plg := range p.local { plgName := getPluginName(plg) fmt.Printf(" %s (local)\n", plgName) } } if len(p.ops) > 0 { for _, plg := range p.ops { plgName := getPluginName(plg) fmt.Printf(" %s (ops)\n", plgName) } } } // getPluginName returns the plugin name from the plugin path, removing the // olaris- prefix func getPluginName(plg string) string { // remove olaris- prefix plgName := strings.TrimPrefix(filepath.Base(plg), "olaris-") return plgName }