feat: initial implementation of run cmd

This commit is contained in:
David Allen 2025-09-05 22:21:27 -06:00
parent d408893389
commit 4d55a3edc2
Signed by: towk
GPG key ID: 0430CDBE22619155

View file

@ -1,6 +1,22 @@
package cmd
import "github.com/spf13/cobra"
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",
@ -13,16 +29,273 @@ var runCmd = &cobra.Command{
export MAKESHIFT_ROOT=/opt/makeshift
# run locally similar to 'download'
makeshift run --plugins jinja2 --profiles default
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")
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)
}