From 419e9781bf7a114d3dcaf806179d9d25cfd09142 Mon Sep 17 00:00:00 2001 From: David Allen Date: Mon, 18 Aug 2025 11:01:19 -0600 Subject: [PATCH] feat: added root, download, list, and serve cmd implementations --- cmd/download.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++-- cmd/list.go | 59 ++++++++++++++++++++++++++++++++++++++- cmd/root.go | 59 ++++++++++++++++++++++++++++++++------- cmd/serve.go | 53 +++++++++++++++++++++++++++++++++-- 4 files changed, 229 insertions(+), 16 deletions(-) diff --git a/cmd/download.go b/cmd/download.go index 9aca225..1fb211b 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -1,19 +1,89 @@ package cmd import ( - "git.towk2.me/towk/configurator/pkg/util" + "fmt" + "net/http" + "os" + "path/filepath" + "time" + + "git.towk2.me/towk/configurator/pkg/client" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) var downloadCmd = cobra.Command{ Use: "download", + Example: ` + # download a file or directory (as archive) + configurator download + configurator download --host https://example.com --path test + configurator download --plugins smd,jinja2 --profile compute + curl $CONFIGURATOR_HOST/download/test?plugins=smd,jinja2 +`, + Short: "Download and modify files with plugins", + PreRun: func(cmd *cobra.Command, args []string) { + setenv(&host, "CONFIGURATOR_HOST") + setenv(&path, "CONFIGURATOR_PATH") + }, Run: func(cmd *cobra.Command, args []string) { + var ( + c = client.New(host) + res *http.Response + body []byte + err error + ) - util.MakeRequest() + log.Debug(). + Str("host", host). + Str("path", path). + Str("output", outputPath). + Send() + + // set output path to match path if empty + if outputPath == "" { + if path != "." || path != "" { + outputPath = filepath.Base(path) + } else { + outputPath = fmt.Sprintf("%d.file", time.Now().Unix()) + } + } + + // make request to /download endpoint + // _, err = c.Download(outputPath, client.HTTPEnvelope{ + // Path: fmt.Sprintf("/download/%s", path), + // Method: http.MethodGet, + // }) + + res, body, err = c.MakeRequest(client.HTTPEnvelope{ + Path: fmt.Sprintf("/download/%s", path), + Method: http.MethodGet, + }) + if err != nil { + log.Error().Err(err).Str("host", host).Msg("failed to make request") + os.Exit(1) + } + if res.StatusCode != http.StatusOK { + log.Error().Int("status", res.StatusCode).Str("host", host).Msg("response returned bad status") + os.Exit(1) + } + if outputPath != "" { + err = os.WriteFile(outputPath, body, 0o755) + if err != nil { + log.Error().Err(err).Msg("failed to write file(s) from download") + os.Exit(1) + } + } else { + fmt.Println(string(body)) + } }, } func init() { + downloadCmd.Flags().StringVar(&host, "host", "http://localhost:5050", "Set the configurator remote host (can be set with CONFIGURATOR_HOST)") + downloadCmd.Flags().StringVarP(&path, "path", "p", ".", "Set the path to list files (can be set with CONFIGURATOR_PATH)") + downloadCmd.Flags().StringVarP(&outputPath, "output", "o", "", "Set the output path to write files") + rootCmd.AddCommand(&downloadCmd) } diff --git a/cmd/list.go b/cmd/list.go index 558281d..f350a43 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -1,10 +1,67 @@ package cmd -import "github.com/spf13/cobra" +import ( + "encoding/json" + "fmt" + "net/http" + "os" + + "git.towk2.me/towk/configurator/pkg/client" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) var listCmd = &cobra.Command{ Use: "list", + Example: ` + # list files in a remote data directory + configurator list --path test + configurator list --host https://example.com --path test + curl https://example.com/list/test +`, + Args: cobra.NoArgs, + Short: "List all files in a remote data directory", + PreRun: func(cmd *cobra.Command, args []string) { + setenv(&host, "CONFIGURATOR_HOST") + setenv(&path, "CONFIGURATOR_PATH") + }, Run: func(cmd *cobra.Command, args []string) { + var ( + c = client.New(host) + body []byte + output []string + err error + ) + log.Debug(). + Str("host", host). + Str("path", path). + Send() + + // make request to /list endpoint + _, body, err = c.MakeRequest(client.HTTPEnvelope{ + Path: fmt.Sprintf("/list/%s", path), + Method: http.MethodGet, + }) + if err != nil { + log.Error().Err(err).Str("url", host).Msg("failed to make request") + os.Exit(1) + } + + err = json.Unmarshal(body, &output) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal response body") + os.Exit(1) + } + + // show the list of files and directories + log.Info().Strs("output", output).Send() }, } + +func init() { + listCmd.Flags().StringVar(&host, "host", "http://localhost:5050", "Set the configurator remote host (can be set with CONFIGURATOR_HOST)") + listCmd.Flags().StringVarP(&path, "path", "p", ".", "Set the path to list files (can be set with CONFIGURATOR_PATH)") + + rootCmd.AddCommand(listCmd) +} diff --git a/cmd/root.go b/cmd/root.go index 2e70c6a..da93c0c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,21 +3,38 @@ package cmd import ( "fmt" "os" + "slices" + "strings" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) +var ( + host string + path string + outputPath string + rootPath string + logLevel string + timeout int +) var rootCmd = cobra.Command{ - Use: "configurator", - Run: func(cmd *cobra.Command, args []string) { - + Use: "configurator", + Short: "Extensible configuration builder to download files", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // set the logging level + level, err := strToLogLevel(logLevel) + if err != nil { + log.Error().Err(err).Msg("failed to convert log level argument") + os.Exit(1) + } + zerolog.SetGlobalLevel(level) }, } func Execute() { - // run initialization code first - initEnv() - + // run the main program if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -26,12 +43,34 @@ func Execute() { func init() { // initialize the config a single time + rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "Set the log level output") } -func initConfigFromFile(path string) { - +func strToLogLevel(ll string) (zerolog.Level, error) { + levels := []string{"debug", "info", "warn", "disabled"} + if index := slices.Index(levels, ll); index >= 0 { + // handle special case to map index == 3 to zerolog.Disabled == 7 + switch index { + case 3: + return zerolog.Disabled, nil + } + return zerolog.Level(index), nil + } + return -100, fmt.Errorf( + "invalid log level (options: %s)", strings.Join(levels, ", "), + ) // use 'info' by default } -func initEnv() { - +func setenv(v *string, key string) { + t := os.Getenv(key) + if t != "" { + *v = t + } } + +// func setenv(cmd *cobra.Command, varname string, envvar string) { +// v := os.Getenv(envvar) +// if v != "" { +// cmd.Flags().Set(varname, v) +// } +// } diff --git a/cmd/serve.go b/cmd/serve.go index 0135819..0358149 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -1,20 +1,67 @@ package cmd import ( + "time" + "git.towk2.me/towk/configurator/pkg/service" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) var serveCmd = &cobra.Command{ - Use: "serve", + Use: "serve", + Args: cobra.NoArgs, + PreRun: func(cmd *cobra.Command, args []string) { + // try and set flags using env vars + setenv(&host, "CONFIGURATOR_HOST") + setenv(&rootPath, "CONFIGURATOR_ROOT") + }, Run: func(cmd *cobra.Command, args []string) { - server := service.New() - err := server.Serve() + var ( + host string + rootPath string + server *service.Service + err error + ) + + // get vars but don't modify + host, _ = cmd.Flags().GetString("host") + rootPath, _ = cmd.Flags().GetString("root") + + // set the server values + server = service.New() + server.Addr = host + server.RootPath = rootPath + server.Timeout = time.Duration(timeout) * time.Second + + // show some debugging information + log.Debug(). + Str("host", host). + Any("paths", map[string]string{ + "root": rootPath, + "data": server.PathForData(), + "profiles": server.PathForProfiles(), + "plugins": server.PathForPlugins(), + "metadata": server.PathForMetadata(), + }). + Send() + + // make the default directories and files if flag is passed + if cmd.Flags().Changed("init") { + server.Init() + } + + // serve and log why the server closed + err = server.Serve() log.Error().Err(err).Msg("server closed") }, } func init() { + serveCmd.Flags().Bool("init", false, "Initializes default files at specified with the '--root' flag") + serveCmd.Flags().StringVar(&host, "host", "localhost:5050", "Set the configurator server host (can be set with CONFIGURATOR_HOST)") + serveCmd.Flags().StringVar(&rootPath, "root", "./", "Set the root path to serve files (can be set with CONFIGURATOR_ROOT)") + serveCmd.Flags().IntVarP(&timeout, "timeout", "t", 60, "Set the timeout in seconds for requests.") + rootCmd.AddCommand(serveCmd) }