feat: initial implementation of run cmd
This commit is contained in:
parent
d408893389
commit
4d55a3edc2
1 changed files with 275 additions and 2 deletions
277
cmd/run.go
277
cmd/run.go
|
|
@ -1,6 +1,22 @@
|
||||||
package cmd
|
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{
|
var runCmd = &cobra.Command{
|
||||||
Use: "run",
|
Use: "run",
|
||||||
|
|
@ -13,16 +29,273 @@ var runCmd = &cobra.Command{
|
||||||
export MAKESHIFT_ROOT=/opt/makeshift
|
export MAKESHIFT_ROOT=/opt/makeshift
|
||||||
|
|
||||||
# run locally similar to 'download'
|
# 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
|
makeshift run --root $HOME/apps/makeshift -p help.txt --plugins jinja2 --profiles default
|
||||||
`,
|
`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Short: "Run locally with plugins and profiles",
|
Short: "Run locally with plugins and profiles",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
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() {
|
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)
|
rootCmd.AddCommand(runCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue