package cmd import ( "fmt" "io/fs" "net/url" "os" "path/filepath" "strings" "time" "git.towk2.me/towk/makeshift/internal/archive" makeshift "git.towk2.me/towk/makeshift/pkg" "git.towk2.me/towk/makeshift/pkg/service" "git.towk2.me/towk/makeshift/pkg/storage" "git.towk2.me/towk/makeshift/pkg/util" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) var runCmd = &cobra.Command{ Use: "run", Example: ` NOTE: This command is not implemented yet! # set up environment export MAKESHIFT_HOST=http://localhost:5050 export MAKESHIFT_PATH=help.txt export MAKESHIFT_ROOT=/opt/makeshift # run locally similar to 'download' makeshift run -p help.txt --plugins jinja2 --profiles default makeshift run --root $HOME/apps/makeshift -p help.txt --plugins jinja2 --profiles default `, Args: cobra.NoArgs, Short: "Run locally with plugins and profiles", Run: func(cmd *cobra.Command, args []string) { var ( host, _ = cmd.Flags().GetString("host") path, _ = cmd.Flags().GetString("path") rootPath, _ = cmd.Flags().GetString("root") outputPath, _ = cmd.Flags().GetString("output") cacertPath, _ = cmd.Flags().GetString("cacert") keyfile, _ = cmd.Flags().GetString("keyfile") timeout, _ = cmd.Flags().GetInt("timeout") pluginNames, _ = cmd.Flags().GetStringSlice("plugins") pluginArgs, _ = cmd.Flags().GetStringSlice("plugin-args") profileIDs, _ = cmd.Flags().GetStringSlice("profiles") extract, _ = cmd.Flags().GetBool("extract") removeArchive, _ = cmd.Flags().GetBool("remove-archive") contents []byte parsed *url.URL localServer *service.Service fileInfo os.FileInfo out *os.File store *storage.MemoryStorage = new(storage.MemoryStorage) hooks []makeshift.Hook errs []error err error ) // parse the host to remove scheme if needed parsed, err = url.Parse(host) if err != nil { log.Warn().Err(err). Str("host", host). Msg("could not parse host") } // set the server values localServer = service.New() localServer.Addr = parsed.Host localServer.RootPath = rootPath localServer.CACertFile = cacertPath localServer.CACertKeyfile = keyfile localServer.Timeout = time.Duration(timeout) * time.Second // initialize storage store.Init() store.SetKWArgs(&pluginKWArgs) // prepare the profiles errs = localServer.LoadProfiles(profileIDs, store, errs) if len(errs) > 0 { log.Error(). Errs("errs", errs). Msg("errors occurred loading profiles") err = util.FormatErrors("failed to load plugins", "", errs) errs = []error{} } // prepare the plugins // determine if path is directory, file, or exists if fileInfo, err = os.Stat(path); err == nil { if fileInfo.IsDir() { // get the final archive path archivePath := fmt.Sprintf("%s.tar.gz", path) log.Debug(). Str("archive_path", archivePath). Str("type", "directory"). Msg("Service.Download()") out, err = os.Create(archivePath) if err != nil { log.Error(). Err(err). Str("path", archivePath). Msg("failed to create named file") return } // get a list of filenames to archive filenamesToArchive := []string{} filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error { if !d.IsDir() { filenamesToArchive = append(filenamesToArchive, path) } return nil }) log.Debug().Strs("files", filenamesToArchive).Send() // prepare plugins hooks, errs = localServer.LoadPlugins(pluginNames, store, pluginArgs, errs) if len(errs) > 0 { log.Error(). Errs("errs", errs). Strs("plugins", pluginNames). Strs("args", pluginArgs). Msg("errors occurred loading plugins") errs = []error{} return } // create an archive of the directory, run hooks, and download err = archive.Create(filenamesToArchive, out, hooks) if err != nil { log.Error(). Err(err). Str("path", archivePath). Msg("failed to create archive") return } // load the final archive contents, err = os.ReadFile(archivePath) if err != nil { log.Error(). Err(err). Str("path", archivePath). Msg("failed to read archive contents") return } // clean up the temporary archive err = os.Remove(archivePath) if err != nil { log.Error().Err(err).Msg("failed to remove temporary archive") return } // extract files if '-x' flag is passed if extract { var ( dir = filepath.Dir(outputPath) base = strings.TrimSuffix(filepath.Base(outputPath), ".tar.gz") ) err = archive.Expand(outputPath, fmt.Sprintf("%s/%s", dir, base)) if err != nil { log.Error().Err(err). Str("path", outputPath). Msg("failed to expand archive") os.Exit(1) } } // optionally, remove archive if '-r' flag is passed // NOTE: this can only be used if `-x` flag is set if removeArchive { if !extract { log.Warn().Msg("requires '-x/--extract' flag to be set to 'true'") } else { err = os.Remove(outputPath) if err != nil { log.Error().Err(err). Str("path", outputPath). Msg("failed to remove archive") } } } } else { // download individual file log.Debug(). Str("type", "file"). Msg("Service.Download()") contents, err = os.ReadFile(path) if err != nil { log.Error(). Err(err). Msg("failed to read file to download") return } // prepare plugins store.Set("file", contents) hooks, errs = localServer.LoadPlugins(pluginNames, store, pluginArgs, errs) if len(errs) > 0 { log.Error(). Strs("plugins", pluginNames). Strs("args", pluginArgs). Errs("errs", errs). Msg("errors occurred loading plugins") errs = []error{} return } if len(hooks) > 0 { // run pre-hooks to modify the contents of the file before archiving log.Debug().Int("hook_count", len(hooks)).Msg("running hooks") for _, hook := range hooks { log.Debug().Any("hook", map[string]any{ "store": hook.Data, "args": hook.Args, "plugin": map[string]string{ "name": hook.Plugin.Name(), "description": hook.Plugin.Description(), "version": hook.Plugin.Version(), }, }).Send() err = hook.Init() if err != nil { log.Error(). Err(err). Str("plugin", hook.Plugin.Name()). Msg("failed to initialize plugin") continue } err = hook.Run() if err != nil { log.Error(). Err(err). Str("plugin", hook.Plugin.Name()). Msg("failed to run plugin") continue } err = hook.Cleanup() if err != nil { log.Error(). Err(err). Str("plugin", hook.Plugin.Name()). Msg("failed to cleanup plugin") continue } } // take the contents from the last hook and update files var ( hook = hooks[len(hooks)-1] data any ) data, err = hook.Data.Get("out") if err != nil { log.Error(). Err(err). Str("plugin", hook.Plugin.Name()). Msg("failed to get data from hook") return } // write to file if '-o' specified otherwise stdout if outputPath != "" { writeFiles(outputPath, data.([]byte)) log.Debug().Str("path", outputPath).Msg("wrote file to specified path") } else { fmt.Println(string(data.([]byte))) } } else { // write contents to file // send non-processed file back as response } } } }, } func init() { runCmd.PersistentFlags().String("host", "http://localhost:5050", "Set the makeshift remote host (can be set with MAKESHIFT_HOST)") runCmd.PersistentFlags().StringP("output", "o", "", "Set the output path to write files") runCmd.PersistentFlags().String("cacert", "", "Set the CA certificate path to load") runCmd.Flags().StringP("path", "p", ".", "Set the path to list files (can be set with MAKESHIFT_PATH)") runCmd.Flags().StringSlice("profiles", []string{}, "Set the profile(s) to use to populate data store") runCmd.Flags().StringSlice("plugins", []string{}, "Set the plugin(s) to run before downloading files") runCmd.Flags().StringSlice("plugin-args", []string{}, "Set the argument list to pass to plugin(s)") runCmd.Flags().Var(&pluginKWArgs, "plugin-kwargs", "Set the argument map to pass to plugin(s)") runCmd.Flags().BoolP("extract", "x", false, "Set whether to extract archive locally after downloading") runCmd.Flags().BoolP("remove-archive", "r", false, "Set whether to remove the archive after extracting (used with '--extract' flag)") rootCmd.AddCommand(runCmd) }