package cmd import ( "fmt" "net/http" "net/url" "os" "path/filepath" "strings" "git.towk2.me/towk/makeshift/internal/archive" "git.towk2.me/towk/makeshift/pkg/client" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) var downloadCmd = cobra.Command{ Use: "download", Example: ` # set up environment export MAKESHIFT_HOST=http://localhost:5050 export MAKESHIFT_PATH=test # download a file or directory (as archive) makeshift download makeshift download --host http://localhost:5050.com --path test # download a file or directory and run plugins with profile data makeshift download --plugins smd,jinja2 --profile compute curl $MAKESHIFT_HOST/download/test?plugins=smd,jinja2&profile=test # download directory and extract it's contents automatically makeshift download -xr `, Short: "Download and modify files with plugins", PersistentPreRun: func(cmd *cobra.Command, args []string) { setenv(cmd, "host", "MAKESHIFT_HOST") setenv(cmd, "path", "MAKESHIFT_PATH") }, Run: func(cmd *cobra.Command, args []string) { var ( host, _ = cmd.Flags().GetString("host") path, _ = cmd.Flags().GetString("path") outputPath, _ = cmd.Flags().GetString("output") pluginNames, _ = cmd.Flags().GetStringSlice("plugins") profileIDs, _ = cmd.Flags().GetStringSlice("profiles") extract, _ = cmd.Flags().GetBool("extract") removeArchive, _ = cmd.Flags().GetBool("remove-archive") c = client.New(host) res *http.Response query string body []byte err error ) query = fmt.Sprintf("/download/%s?", path) if len(pluginNames) > 0 { query += "plugins=" + url.QueryEscape(strings.Join(pluginNames, ",")) } if len(profileIDs) > 0 { query += "&profiles=" + url.QueryEscape(strings.Join(profileIDs, ",")) } log.Debug(). Str("host", host). Str("path", path). Str("query", query). Str("output", outputPath). Strs("profiles", profileIDs). Strs("plugins", pluginNames). Send() res, body, err = c.MakeRequest(client.HTTPEnvelope{ Path: query, Method: http.MethodGet, }) if err != nil { log.Error().Err(err). Str("host", host). Str("path", path). Str("output", outputPath). 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). Str("path", path). Str("output", outputPath). Msg("response returned bad status") os.Exit(1) } // determine if output path is an archive or file switch res.Header.Get("FILETYPE") { case "archive": // write archive to disk with or without '-o' specified if outputPath == "" { outputPath = fmt.Sprintf("%s.tar.gz", path) writeFiles(outputPath, body) log.Debug().Str("path", outputPath).Msg("wrote archive to pre-determined path") } else { writeFiles(outputPath, body) log.Debug().Str("path", outputPath).Msg("wrote archive to specified path") } // 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") } } } case "file": // write to file if '-o' specified otherwise stdout if outputPath != "" { writeFiles(outputPath, body) log.Debug().Str("path", outputPath).Msg("wrote file to specified path") } else { fmt.Println(string(body)) } } }, } var downloadProfileCmd = &cobra.Command{ Use: "profile", Example: ` // download a profile makeshift download profile default `, Args: cobra.ExactArgs(1), Short: "Download a profile", // PreRun: func(cmd *cobra.Command, args []string) { // setenv(cmd, "host", "MAKESHIFT_HOST") // }, 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 body []byte query string err error ) log.Debug(). Str("host", host). Str("output", outputPath). Send() for _, profileID := range args { query = fmt.Sprintf("/profile/%s", profileID) res, body, err = c.MakeRequest(client.HTTPEnvelope{ Path: query, 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(). 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)) } } }, } var downloadPluginCmd = &cobra.Command{ Use: "plugin", Example: ` // download a plugin makeshift download plugin smd jinja2 `, Args: cobra.ExactArgs(1), Short: "Download a plugin", // PreRun: func(cmd *cobra.Command, args []string) { // setenv(cmd, "host", "MAKESHIFT_HOST") // }, 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 ) for _, pluginName := range args { query = fmt.Sprintf("/profile/%s", pluginName) res, body, err = c.MakeRequest(client.HTTPEnvelope{ Path: query, 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(). 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 { writeFiles(fmt.Sprintf("%s.so", pluginName), body) } } }, } func init() { downloadCmd.PersistentFlags().String("host", "http://localhost:5050", "Set the makeshift remote host (can be set with MAKESHIFT_HOST)") downloadCmd.PersistentFlags().StringP("output", "o", "", "Set the output path to write files") downloadCmd.Flags().StringP("path", "p", ".", "Set the path to list files (can be set with MAKESHIFT_PATH)") downloadCmd.Flags().StringSlice("profiles", []string{}, "Set the profile(s) to use to populate data store") downloadCmd.Flags().StringSlice("plugins", []string{}, "Set the plugin(s) to run before downloading files") downloadCmd.Flags().BoolP("extract", "x", false, "Set whether to extract archive locally after downloading") downloadCmd.Flags().BoolP("remove-archive", "r", false, "Set whether to remove the archive after extracting (used with '--extract' flag)") downloadCmd.AddCommand(downloadProfileCmd, downloadPluginCmd) rootCmd.AddCommand(&downloadCmd) } // helper to write downloaded files func writeFiles(path string, body []byte) { var err = os.WriteFile(path, body, 0o755) if err != nil { log.Error().Err(err).Msg("failed to write file(s) from download") os.Exit(1) } }