diff --git a/cmd/plugins.go b/cmd/plugins.go index 15043f0..99b8908 100644 --- a/cmd/plugins.go +++ b/cmd/plugins.go @@ -129,7 +129,7 @@ var pluginsInfoCmd = &cobra.Command{ # show information of a remote plugin makeshift plugins info jinja2 smd - # show information of a local plugin + # show information of a local plugin (same as 'makeshift inspect') makeshift plugins info --local $MAKESHIFT_ROOT/plugins/jinja2.so `, Short: "Show plugin information", diff --git a/cmd/upload.go b/cmd/upload.go index 260ff3f..ef0c9c7 100644 --- a/cmd/upload.go +++ b/cmd/upload.go @@ -12,6 +12,7 @@ import ( "git.towk2.me/towk/makeshift/internal/format" makeshift "git.towk2.me/towk/makeshift/pkg" "git.towk2.me/towk/makeshift/pkg/client" + "git.towk2.me/towk/makeshift/pkg/service" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -32,9 +33,9 @@ var uploadCmd = &cobra.Command{ # upload an archive (extracted and saved on server) makeshift upload -d @setup.tar.gz -t archive - # upload a new profile with a specific path (used to set remote location) - makeshift upload profile -d @kubernetes.json -p nodes/kubernetes.json - makeshift upload profile -d @slurm.json -d @compute.json -p nodes + # upload multiple files with a specific path (used to set remote location) + makeshift upload -d @kubernetes.json -p nodes/kubernetes.json + makeshift upload -d @slurm.json -d @compute.json -p nodes `, Short: "Upload files and directories", PersistentPreRun: func(cmd *cobra.Command, args []string) { @@ -79,15 +80,15 @@ var uploadCmd = &cobra.Command{ } var uploadProfilesCmd = &cobra.Command{ - Use: "profile [profileID]", + Use: "profile [profile_id]", Example: ` # upload a new profile - makeshift upload profile -d @compute.json + makeshift upload profile -d @compute.json kubernetes.json - # upload a new profile with a specific path (used for lookup) - makeshift upload profile -d @kubernetes.json -n k8s + # upload a new profile with a specific path + makeshift upload profile -d @kubernetes.json + makeshift upload profile -d '{"id": "custom", "data": {}}' kubernetes.json `, - Args: cobra.NoArgs, Short: "Upload a new profile", Run: func(cmd *cobra.Command, args []string) { var ( @@ -102,6 +103,28 @@ var uploadProfilesCmd = &cobra.Command{ err error ) + // load files from args + for i, path := range args { + body, err = os.ReadFile(path) + if err != nil { + log.Error().Err(err). + Int("index", i). + Str("path", path). + Msg("failed to read profile file") + continue + } + var profile *makeshift.Profile + err = json.Unmarshal(body, &profile) + if err != nil { + log.Error().Err(err). + Int("index", i). + Str("path", path). + Msg("failed to unmarshal profile") + } + profiles = append(profiles, profile) + } + + // send each loaded profile to server for _, profile := range profiles { if profile == nil { continue @@ -113,42 +136,25 @@ var uploadProfilesCmd = &cobra.Command{ continue } - // send data to server query = fmt.Sprintf("/profiles/%s", profile.ID) res, body, err = c.MakeRequest(client.HTTPEnvelope{ Path: query, Method: http.MethodPost, Body: body, }) - if err != nil { - log.Error().Err(err). - Str("host", host). - Str("query", query). - 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) - } + handleResponseError(res, host, query, err) } }, } var uploadPluginsCmd = &cobra.Command{ - Use: "plugin", + Use: "plugin [plugin_name]", Example: ` # upload a new plugin makeshift upload plugin -d @slurm.so # upload a new plugin with a specific name (used for lookups) - makeshift upload plugin -d @cobbler.so -n merge + makeshift upload plugin -d @cobbler.so `, Args: cobra.ExactArgs(1), Short: "Upload a new plugin", @@ -156,14 +162,47 @@ var uploadPluginsCmd = &cobra.Command{ // make one request be host positional argument (restricted to 1 for now) // temp := append(handleArgs(args), processDataArgs(dataArgs)...) var ( - inputData []*makeshift.Profile - dataArgs, _ = cmd.PersistentFlags().GetStringArray("data") + host, _ = cmd.Flags().GetString("host") + dataArgs, _ = cmd.Flags().GetStringArray("data") + + plugins = processFiles(dataArgs) + c = client.New(host) + res *http.Response + query string + body []byte + plugin makeshift.Plugin + err error ) - temp := processProfiles(dataArgs) - for _, data := range temp { - if data != nil { - inputData = append(inputData, data) + + // load files from args + for i, path := range args { + body, err = os.ReadFile(path) + if err != nil { + log.Error().Err(err). + Int("index", i). + Str("path", path). + Msg("failed to read plugin file") + continue } + + plugins[path] = body + } + + for path, contents := range plugins { + plugin, err = service.LoadPluginFromFile(path) + if err != nil { + log.Error().Err(err). + Str("path", path). + Msg("failed to load plugin from file") + } + + query = fmt.Sprintf("/plugins/%s", plugin.Name()) + res, _, err = c.MakeRequest(client.HTTPEnvelope{ + Path: query, + Method: http.MethodPost, + Body: contents, + }) + handleResponseError(res, host, query, err) } }, } @@ -207,7 +246,7 @@ func processFiles(args []string) map[string][]byte { // add loaded data to collection of all data collection[path] = contents } else { - log.Warn().Msg("only files can be uploaded") + log.Warn().Msg("only files can be uploaded (add @ before the path)") continue } diff --git a/pkg/service/plugins.go b/pkg/service/plugins.go index 0ab12eb..29d2f16 100644 --- a/pkg/service/plugins.go +++ b/pkg/service/plugins.go @@ -88,33 +88,33 @@ func (s *Service) GetPluginRaw() http.HandlerFunc { func (s *Service) CreatePlugin() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var ( - plugin makeshift.Plugin - path string - err error + pluginName = chi.URLParam(r, "name") + body []byte + path string + err error ) - plugin, err = getPluginFromRequestBody(r) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - // helper to check for valid plugin name var hasValidName = func(name string) bool { return name != "" && len(name) < 64 } // check for a valid plugin name - if !hasValidName(plugin.Name()) { + if !hasValidName(pluginName) { + http.Error(w, "invalid name for plugin", http.StatusBadRequest) + return + } + + body, err = io.ReadAll(r.Body) + if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - // save plugin at path using it's name - path = s.PathForPluginWithName(plugin.Name()) - err = SavePluginToFile(path, &plugin) + path = s.PathForPluginWithName(pluginName) + err = os.WriteFile(path, body, 0o777) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -139,22 +139,3 @@ func (s *Service) DeletePlugin() http.HandlerFunc { w.WriteHeader(http.StatusOK) } } - -func getPluginFromRequestBody(r *http.Request) (makeshift.Plugin, error) { - var ( - plugin makeshift.Plugin - body []byte - err error - ) - body, err = io.ReadAll(r.Body) - if err != nil { - return nil, err - } - - err = json.Unmarshal(body, &plugin) - if err != nil { - return nil, err - } - - return plugin, nil -}