package cmd import ( "fmt" "io/fs" "net/http" "os" "os/exec" "path/filepath" makeshift "git.towk2.me/towk/makeshift/pkg" "git.towk2.me/towk/makeshift/pkg/client" "git.towk2.me/towk/makeshift/pkg/service" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) var pluginsCmd = &cobra.Command{ Use: "plugins", Short: "Manage, inspect, and compile plugins (requires Go build tools)", } var pluginsCompileCmd = &cobra.Command{ Use: "compile", Example: ` # compile plugin using Go build tools go build -buildmode=plugin -o lib/myplugin.so src/plugins/myplugin.go # try to compile all plugins in current directory cd src/plugins makeshift plugin compile # try to compile all plugins in specified directory makeshift plugin compile src/plugins # compile 'src/plugins/myplugin.go' and save to 'lib/myplugin.so' makeshift plugin compile src/plugins/myplugin.go -o lib/myplugin.so `, Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { var ( outputPath, _ = cmd.Flags().GetString("output") output []byte fileInfo os.FileInfo err error ) // make the directory err = os.MkdirAll(filepath.Dir(outputPath), 0o777) if err != nil { log.Fatal().Err(err).Msg("failed to make output directory") } // one arg passed, so determine if it is file or directory if len(args) > 0 { if fileInfo, err = os.Stat(args[0]); err == nil { if fileInfo.IsDir() { err = compilePluginsDir(args[0], outputPath) if err != nil { log.Fatal().Err(err). Bytes("output", output). Msg("failed to compile plugin") } } else { // not a directory so check if Go file so try and compile it if filepath.Ext(args[0]) == ".go" { output, err = compilePlugin(outputPath, args[0]) if err != nil { log.Fatal().Err(err). Bytes("output", output). Msg("failed to compile plugin") } } else { log.Fatal().Msg("argument is not a valid plugin (must be Go file)") } } } else if err != nil { log.Fatal().Err(err).Msgf("failed to stat provided plugin path") } } else { // no args passed, so use current directory err = compilePluginsDir(".", outputPath) if err != nil { log.Fatal().Err(err). Bytes("output", output). Msg("failed to compile plugin") } } }, } var pluginInspectCmd = &cobra.Command{ Use: "inspect", Args: cobra.MinimumNArgs(1), Example: ` # inspect a plugin and print its information makeshift plugin inspect lib/jinja2.so `, Run: func(cmd *cobra.Command, args []string) { for _, path := range args { var ( plugin makeshift.Plugin err error ) plugin, err = service.LoadPluginFromFile(path) if err != nil { log.Error().Err(err). Str("path", path). Msg("failed to load plugin from file") continue } log.Info().Any("plugin", map[string]any{ "name": plugin.Name(), "version": plugin.Version(), "description": plugin.Description(), "metadata": plugin.Metadata(), }).Send() } }, } var pluginInfoCmd = &cobra.Command{ Use: "info", Short: "Show plugin information", Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { var ( host, _ = cmd.Flags().GetString("host") outputPath, _ = cmd.Flags().GetString("output") c = client.New(host) res *http.Response query string body []byte err error ) log.Debug(). Str("host", host). Str("output", outputPath). Send() for _, pluginName := range args { query = fmt.Sprintf("/plugins/%s/info", pluginName) res, body, err = c.MakeRequest(client.HTTPEnvelope{ Path: query, Method: http.MethodGet, }) if err != nil { log.Error().Err(err). Str("host", host). Str("query", query). Msg("failed to make request") os.Exit(1) } if res.StatusCode != http.StatusOK { log.Error(). Any("status", map[string]any{ "code": res.StatusCode, "message": res.Status, }). Str("host", host). Msg("response returned bad status") os.Exit(1) } if outputPath != "" { writeFiles(outputPath, body) } else { fmt.Println(string(body)) } } }, } func init() { pluginsCompileCmd.Flags().StringP("output", "o", "", "Set the path to save compiled plugin") pluginsCmd.AddCommand(pluginsCompileCmd, pluginInspectCmd, pluginInfoCmd) rootCmd.AddCommand(pluginsCmd) } func compilePlugin(outputPath string, srcPath string) ([]byte, error) { var ( commandArgs string command *exec.Cmd ) // execute command to build the plugin commandArgs = fmt.Sprintf("go build -buildmode=plugin -o=%s %s", outputPath, srcPath) command = exec.Command("bash", "-c", commandArgs) return command.CombinedOutput() } func compilePluginsDir(dirpath string, outputPath string) error { err := filepath.WalkDir(dirpath, func(path string, d fs.DirEntry, err error) error { // not a directory and is Go file, so try and compile it if !d.IsDir() && filepath.Ext(path) == ".go" { var ( localOutputPath string = outputPath + "/" + path ) output, err := compilePlugin(localOutputPath, path) if err != nil { log.Fatal().Err(err). Bytes("output", output). Str("path", localOutputPath). Msg("failed to compile plugin") return err } } return nil }) return err }