From 32d534bcead7ee53e99899f0239f4da0d359485c Mon Sep 17 00:00:00 2001 From: David Allen Date: Sat, 30 Aug 2025 23:18:47 -0600 Subject: [PATCH 1/6] chore: updated README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dadab6f..7bcfc54 100644 --- a/README.md +++ b/README.md @@ -209,4 +209,5 @@ There are some features still missing that will be added later. 5. Optionally build plugins directly into the main driver 6. Protected routes that require authentication 7. Configuration file for persistent runs -8. `Dockerfile` and `docker-compose.yml` files \ No newline at end of file +8. `Dockerfile` and `docker-compose.yml` files to build containers +9. Including certs with requests \ No newline at end of file From 18edb93d2c8cdb00a7d6dc0b68e2045d4fe7081b Mon Sep 17 00:00:00 2001 From: David Allen Date: Sat, 30 Aug 2025 23:19:18 -0600 Subject: [PATCH 2/6] feat: added delete cmd --- cmd/delete.go | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 cmd/delete.go diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 0000000..85dbd71 --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,123 @@ +package cmd + +import ( + "fmt" + "net/http" + + "git.towk2.me/towk/makeshift/pkg/client" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +var deleteCmd = &cobra.Command{ + Use: "delete", + Example: ` + # set up environment + export MAKESHIFT_HOST=http://localhost:5050 + export MAKESHIFT_PATH=test + + # delete a file or directory (cannot delete root) + makeshift delete -p help.txt + makeshift delete --host http://localhost:5555 --path templates +`, + Short: "Delete files and directories", + 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") + paths, _ = cmd.Flags().GetStringSlice("path") + + c = client.New(host) + res *http.Response + query string + err error + ) + for _, path := range paths { + if path == "" { + log.Warn().Msg("skipping empty path") + continue + } + + query = fmt.Sprintf("/delete/%s?", path) + res, _, err = c.MakeRequest(client.HTTPEnvelope{ + Path: query, + Method: http.MethodDelete, + }) + handleResponseError(res, host, query, err) + } + }, +} + +var deleteProfilesCmd = &cobra.Command{ + Use: "profiles", + Example: ` + # delete profile(s) by its ID + makeshift delete profiles kubernetes slurm compute +`, + Args: cobra.MinimumNArgs(1), + Short: "Delete profile(s)", + Run: func(cmd *cobra.Command, args []string) { + var ( + host, _ = cmd.Flags().GetString("host") + + c = client.New(host) + res *http.Response + query string + err error + ) + + for _, profileID := range args { + if profileID == "default" { + log.Warn().Msg("cannot delete the default profile") + continue + } + + query = fmt.Sprintf("/profiles/%s", profileID) + res, _, err = c.MakeRequest(client.HTTPEnvelope{ + Path: query, + Method: http.MethodDelete, + }) + handleResponseError(res, host, query, err) + } + }, +} + +var deletePluginsCmd = &cobra.Command{ + Use: "plugins", + Example: ` + # delete plugin(s) by name + makeshift delete plugins weather slurm user +`, + Args: cobra.MinimumNArgs(1), + Short: "Delete plugin(s)", + Run: func(cmd *cobra.Command, args []string) { + var ( + host, _ = cmd.Flags().GetString("host") + + c = client.New(host) + res *http.Response + query string + err error + ) + + for _, pluginName := range args { + query = fmt.Sprintf("/plugins/%s", pluginName) + res, _, err = c.MakeRequest(client.HTTPEnvelope{ + Path: query, + Method: http.MethodDelete, + }) + handleResponseError(res, host, query, err) + } + }, +} + +func init() { + deleteCmd.PersistentFlags().String("host", "http://localhost:5050", "Set the makeshift server host (can be set with MAKESHIFT_HOST)") + deleteCmd.Flags().StringSliceP("path", "p", []string{}, "Set the paths to delete files and directories") + deleteCmd.AddCommand(deleteProfilesCmd, deletePluginsCmd) + + rootCmd.AddCommand(deleteCmd) +} From 5d350717f4e999cc6483401e6dc7e5b530acc204 Mon Sep 17 00:00:00 2001 From: David Allen Date: Sat, 30 Aug 2025 23:19:45 -0600 Subject: [PATCH 3/6] chore: updated references in Makefile --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index c1e7a87..5e8d1f1 100644 --- a/Makefile +++ b/Makefile @@ -36,13 +36,13 @@ container-testing: binaries plugins: $(plugin_binaries) # how to make each plugin -lib/%.so: pkg/generator/plugins/%/*.go +lib/%.so: pkg/plugins/%/*.go mkdir -p lib go build -buildmode=plugin -o $@ $< docs: - go doc github.com/OpenCHAMI/cmd - go doc github.com/OpenCHAMI/pkg/${prog} + go doc git.towk2.me/towk/makeshift/cmd + go doc git.towk2.me/towk/makeshift/pkg/${prog} # remove executable and all built plugins .PHONY: clean From ebb95a29ec9ee1ec67fd3aff616e0de3baa25db2 Mon Sep 17 00:00:00 2001 From: David Allen Date: Sat, 30 Aug 2025 23:20:47 -0600 Subject: [PATCH 4/6] refactor: renamed userdata plugin to mapper --- pkg/plugins/mapper/mapper.go | 30 ++++++++++++++++++++++++++++++ pkg/plugins/userdata/userdata.go | 30 ------------------------------ 2 files changed, 30 insertions(+), 30 deletions(-) create mode 100644 pkg/plugins/mapper/mapper.go delete mode 100644 pkg/plugins/userdata/userdata.go diff --git a/pkg/plugins/mapper/mapper.go b/pkg/plugins/mapper/mapper.go new file mode 100644 index 0000000..1fded1f --- /dev/null +++ b/pkg/plugins/mapper/mapper.go @@ -0,0 +1,30 @@ +package main + +import "git.towk2.me/towk/makeshift/pkg/storage" + +type Mapper struct{} + +func (p *Mapper) Name() string { return "jinja2" } +func (p *Mapper) Version() string { return "test" } +func (p *Mapper) Description() string { return "Renders Jinja 2 templates" } +func (p *Mapper) Metadata() map[string]string { + return map[string]string{ + "author.name": "David J. Allen", + "author.email": "davidallendj@gmail.com", + } +} + +func (p *Mapper) Init() error { + // nothing to initialize + return nil +} + +func (p *Mapper) Run(data storage.KVStore, args []string) error { + return nil +} + +func (p *Mapper) Clean() error { + return nil +} + +var Makeshift Mapper diff --git a/pkg/plugins/userdata/userdata.go b/pkg/plugins/userdata/userdata.go deleted file mode 100644 index ae5d31f..0000000 --- a/pkg/plugins/userdata/userdata.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import "git.towk2.me/towk/makeshift/pkg/storage" - -type UserData struct{} - -func (p *UserData) Name() string { return "jinja2" } -func (p *UserData) Version() string { return "test" } -func (p *UserData) Description() string { return "Renders Jinja 2 templates" } -func (p *UserData) Metadata() map[string]string { - return map[string]string{ - "author.name": "David J. Allen", - "author.email": "davidallendj@gmail.com", - } -} - -func (p *UserData) Init() error { - // nothing to initialize - return nil -} - -func (p *UserData) Run(data storage.KVStore, args []string) error { - return nil -} - -func (p *UserData) Clean() error { - return nil -} - -var Makeshift UserData From d88ab2c01fab9272d03b6a821642b96b37a0aa77 Mon Sep 17 00:00:00 2001 From: David Allen Date: Sat, 30 Aug 2025 23:21:17 -0600 Subject: [PATCH 5/6] feat: added compile-plugins.sh script --- bin/.gitkeep | 0 bin/compile-plugins.sh | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+) delete mode 100644 bin/.gitkeep create mode 100644 bin/compile-plugins.sh diff --git a/bin/.gitkeep b/bin/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/bin/compile-plugins.sh b/bin/compile-plugins.sh new file mode 100644 index 0000000..278bc7c --- /dev/null +++ b/bin/compile-plugins.sh @@ -0,0 +1,26 @@ +#!/usr/bin bash + + +function compile_default_plugins() { + makeshift_exe=./makeshift + go_exe=go + + # make sure go build tools are installed + if command -v $go_exe >/dev/null 2>&1; then + # make sure that MAKESHIFT_ROOT is set + if [[ ! -v MAKESHIFT_ROOT ]]; then + # Compile the default external plugins + go build + $makeshift_exe compile pkg/plugins/jinja2/jinja2.go -o $MAKESHIFT_ROOT/plugins/jinja2.go + $makeshift_exe compile pkg/plugins/smd/smd.go -o $MAKESHIFT_ROOT/plugins/smd.so + $makeshift_exe compile pkg/plugins/userdata/userdata.go -o $MAKESHIFT_ROOT/plugins/userdata.go + else + echo "requires MAKESHIFT_ROOT to be set" + fi + else + echo "Go build tools must be installed" + fi +} + + +compile_default_plugins \ No newline at end of file From fbed466c3d349619f99f8b9ff9a38f450c1e2235 Mon Sep 17 00:00:00 2001 From: David Allen Date: Sat, 30 Aug 2025 23:30:46 -0600 Subject: [PATCH 6/6] refactor: updated cmd and pkg implementations --- cmd/list.go | 86 ++-------------- cmd/plugins.go | 4 +- cmd/root.go | 31 ++++++ cmd/run.go | 6 +- cmd/upload.go | 218 +++++++++++++++++++++++++--------------- pkg/client/client.go | 41 ++++++++ pkg/service/plugins.go | 16 +-- pkg/service/profiles.go | 43 ++++++-- pkg/service/routes.go | 32 +++--- pkg/service/service.go | 6 +- 10 files changed, 287 insertions(+), 196 deletions(-) diff --git a/cmd/list.go b/cmd/list.go index 0de2797..1fa3fe6 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -81,10 +81,10 @@ var listPluginsCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { var ( host, _ = cmd.Flags().GetString("host") - path, _ = cmd.Flags().GetString("path") c = client.New(host) res *http.Response + query string plugins []string body []byte err error @@ -96,24 +96,7 @@ var listPluginsCmd = &cobra.Command{ Path: "/plugins", Method: http.MethodGet, }) - if err != nil { - log.Error().Err(err). - Str("host", host). - Str("path", path). - 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). - Msg("response returned bad status") - os.Exit(1) - } + handleResponseError(res, host, "/plugins", err) err = json.Unmarshal(body, &plugins) if err != nil { log.Error().Err(err). @@ -123,28 +106,12 @@ var listPluginsCmd = &cobra.Command{ } else { for _, pluginName := range args { // make request to /list endpoint + query = fmt.Sprintf("/plugins/%s/info", pluginName) res, body, err = c.MakeRequest(client.HTTPEnvelope{ - Path: fmt.Sprintf("/plugins/%s/info", pluginName), + Path: query, Method: http.MethodGet, }) - if err != nil { - log.Error().Err(err). - Str("host", host). - Str("path", path). - 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). - Msg("response returned bad status") - os.Exit(1) - } + handleResponseError(res, host, query, err) plugins = append(plugins, string(body)) } @@ -159,12 +126,12 @@ var listProfilesCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { var ( host, _ = cmd.Flags().GetString("host") - path, _ = cmd.Flags().GetString("path") c = client.New(host) res *http.Response profiles []makeshift.Profile body []byte + query string err error ) @@ -174,24 +141,7 @@ var listProfilesCmd = &cobra.Command{ Path: "/profiles", Method: http.MethodGet, }) - if err != nil { - log.Error().Err(err). - Str("host", host). - Str("path", path). - 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). - Msg("response returned bad status") - os.Exit(1) - } + handleResponseError(res, host, "/profiles", err) err = json.Unmarshal(body, &profiles) if err != nil { @@ -202,28 +152,12 @@ var listProfilesCmd = &cobra.Command{ } else { for _, profileID := range args { // make request to /list endpoint + query = fmt.Sprintf("/profiles/%s", profileID) res, body, err = c.MakeRequest(client.HTTPEnvelope{ - Path: fmt.Sprintf("/profiles/%s", profileID), + Path: fmt.Sprintf(query), Method: http.MethodGet, }) - if err != nil { - log.Error().Err(err). - Str("host", host). - Str("path", path). - 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). - Msg("response returned bad status") - os.Exit(1) - } + handleResponseError(res, host, query, err) var profile makeshift.Profile err = json.Unmarshal(body, &profile) if err != nil { diff --git a/cmd/plugins.go b/cmd/plugins.go index 812b5a8..8917b1c 100644 --- a/cmd/plugins.go +++ b/cmd/plugins.go @@ -178,8 +178,8 @@ var pluginsInfoCmd = &cobra.Command{ } func init() { - pluginsCompileCmd.PersistentFlags().StringP("output", "o", "", "Set the path to save compiled plugin") - pluginsInfoCmd.PersistentFlags().String("host", "http://localhost:5050", "Set the makeshift remote host (can be set with MAKESHIFT_HOST)") + pluginsCompileCmd.Flags().StringP("output", "o", "", "Set the path to save compiled plugin (matches source type, i.e. uses files or directory)") + pluginsInfoCmd.Flags().String("host", "http://localhost:5050", "Set the makeshift remote host (can be set with MAKESHIFT_HOST)") pluginsCmd.AddCommand(pluginsCompileCmd, pluginsInspectCmd, pluginsInfoCmd) rootCmd.AddCommand(pluginsCmd) diff --git a/cmd/root.go b/cmd/root.go index caa383c..fb354f0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "net/http" "os" logger "git.towk2.me/towk/makeshift/pkg/log" @@ -78,6 +79,16 @@ func setenv(cmd *cobra.Command, varname string, envvar string) { } } +func setenvp(cmd *cobra.Command, varname string, envvar string) { + if cmd.Flags().Changed(varname) { + return + } + val := os.Getenv(envvar) + if val != "" { + cmd.PersistentFlags().Set(varname, val) + } +} + func initLogger() { // initialize the logger logfile, _ := rootCmd.PersistentFlags().GetString("log-file") @@ -87,3 +98,23 @@ func initLogger() { os.Exit(1) } } + +func handleResponseError(res *http.Response, host, query string, err error) { + 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) + } +} diff --git a/cmd/run.go b/cmd/run.go index 399aa83..ab698e7 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -5,14 +5,16 @@ import "github.com/spf13/cobra" var runCmd = &cobra.Command{ Use: "run", Example: ` + NOTE: This command is not implemented yet! + # set up environment export MAKESHIFT_HOST=http://localhost:5050 export MAKESHIFT_PATH=help.txt - export MAKESHIFT_ROOT=./test + export MAKESHIFT_ROOT=/opt/makeshift # run locally similar to 'download' makeshift run --plugins jinja2 --profiles default - makeshift run --root ./test -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", diff --git a/cmd/upload.go b/cmd/upload.go index 12b58b1..ecdd638 100644 --- a/cmd/upload.go +++ b/cmd/upload.go @@ -4,72 +4,127 @@ import ( "bufio" "encoding/json" "fmt" + "net/http" "os" "strings" "git.towk2.me/towk/makeshift/internal/format" + makeshift "git.towk2.me/towk/makeshift/pkg" + "git.towk2.me/towk/makeshift/pkg/client" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) var ( inputFormat format.DataFormat = format.JSON - dataArgs []string ) var uploadCmd = &cobra.Command{ Use: "upload", Example: ` - # upload a single file - makeshift upload -d @compute-base.yaml -t file - - # upload a single file with contents without specify type - makeshift upload -d '{"name": "John Smith", "email": "john.smith@example.com"}' + # upload a single file in root directory + makeshift upload -d @compute-base.yaml # upload a directory - makeshift upload -d @setup/ -t directory + makeshift upload -d @setup/ # 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 -@compute.json -p nodes `, + Short: "Upload files and directories", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + setenv(cmd, "host", "MAKESHIFT_HOST") + setenv(cmd, "path", "MAKESHIFT_PATH") + }, Run: func(cmd *cobra.Command, args []string) { - // make one request be host positional argument (restricted to 1 for now) - var inputData []map[string]any - temp := append(handleArgs(args), processDataArgs(dataArgs)...) - for _, data := range temp { - if data != nil { - inputData = append(inputData, data) + var ( + dataArgs, _ = cmd.Flags().GetStringArray("data") + + inputData = processFiles(dataArgs) + useDirectoryPath = len(inputData) > 1 + ) + for path, contents := range inputData { + log.Info().Str("path", path).Int("size", len(contents)).Send() + if useDirectoryPath { + + } else { + } } }, } -var uploadProfileCmd = &cobra.Command{ - Use: "profile", +var uploadProfilesCmd = &cobra.Command{ + Use: "profile [profileID]", Example: ` # upload a new profile makeshift upload profile -d @compute.json - # upload a new profile with a specific name (used for lookups) + # upload a new profile with a specific path (used for lookup) makeshift upload profile -d @kubernetes.json -n k8s `, - Args: cobra.ExactArgs(1), + Args: cobra.NoArgs, Short: "Upload a new profile", Run: func(cmd *cobra.Command, args []string) { // make one request be host positional argument (restricted to 1 for now) - var inputData []map[string]any - temp := append(handleArgs(args), processDataArgs(dataArgs)...) - for _, data := range temp { - if data != nil { - inputData = append(inputData, data) + var ( + // inputData []map[string]any = append(handleArgs(args), processDataArgs(dataArgs)...) + host, _ = cmd.Flags().GetString("host") + dataArgs, _ = cmd.Flags().GetStringArray("data") + profiles = processProfiles(dataArgs) + + c = client.New(host) + res *http.Response + query string + body []byte + err error + ) + + for _, profile := range profiles { + if profile == nil { + continue + } + + body, err = json.Marshal(profile) + if err != nil { + log.Error().Err(err).Msg("failed to marshal profile") + 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) } } - }, } -var uploadPluginCmd = &cobra.Command{ +var uploadPluginsCmd = &cobra.Command{ Use: "plugin", Example: ` # upload a new plugin @@ -82,8 +137,12 @@ var uploadPluginCmd = &cobra.Command{ Short: "Upload a new plugin", Run: func(cmd *cobra.Command, args []string) { // make one request be host positional argument (restricted to 1 for now) - var inputData []map[string]any - temp := append(handleArgs(args), processDataArgs(dataArgs)...) + // temp := append(handleArgs(args), processDataArgs(dataArgs)...) + var ( + inputData []*makeshift.Profile + dataArgs, _ = cmd.PersistentFlags().GetStringArray("data") + ) + temp := processProfiles(dataArgs) for _, data := range temp { if data != nil { inputData = append(inputData, data) @@ -93,27 +152,62 @@ var uploadPluginCmd = &cobra.Command{ } func init() { - uploadProfileCmd.Flags().VarP(&inputFormat, "format", "F", "Set the input format for profile") + uploadCmd.PersistentFlags().String("host", "http://localhost:5050", "Set the makeshift remote host (can be set with MAKESHIFT_HOST)") + uploadCmd.PersistentFlags().StringArrayP("data", "d", []string{}, "Set the data to send to specified host (prepend @ for files)") + uploadCmd.Flags().StringP("path", "p", ".", "Set the path to list files (can be set with MAKESHIFT_PATH)") - uploadCmd.AddCommand(uploadProfileCmd, uploadPluginCmd) + uploadProfilesCmd.Flags().VarP(&inputFormat, "format", "F", "Set the input format for profile") + + uploadCmd.AddCommand(uploadProfilesCmd, uploadPluginsCmd) rootCmd.AddCommand(uploadCmd) } -// processDataArgs takes a slice of strings that check for the @ symbol and loads +func processFiles(args []string) map[string][]byte { + // load data either from file or directly from args + var collection = make(map[string][]byte, len(args)) + for _, arg := range args { + // if arg is empty string, then skip and continue + if len(arg) > 0 { + // determine if we're reading from file to load contents + if strings.HasPrefix(arg, "@") { + var ( + path string = strings.TrimLeft(arg, "@") + contents []byte + err error + ) + contents, err = os.ReadFile(path) + if err != nil { + log.Error().Err(err).Str("path", path).Msg("failed to read file") + continue + } + + // skip empty files + if len(contents) == 0 { + log.Warn().Str("path", path).Msg("file is empty") + continue + } + + // add loaded data to collection of all data + collection[path] = contents + } else { + log.Warn().Msg("only files can be uploaded") + + continue + } + } + } + return collection +} + +// processProfiles takes a slice of strings that check for the @ symbol and loads // the contents from the file specified in place (which replaces the path). // // NOTE: The purpose is to make the input arguments uniform for our request. This // function is meant to handle data passed with the `-d/--data` flag and positional // args from the CLI. -func processDataArgs(args []string) []map[string]any { - // JSON representation - type ( - JSONObject = map[string]any - JSONArray = []JSONObject - ) - +func processProfiles(args []string) []*makeshift.Profile { // load data either from file or directly from args - var collection = make(JSONArray, len(args)) + var collection = make([]*makeshift.Profile, len(args)) for i, arg := range args { // if arg is empty string, then skip and continue if len(arg) > 0 { @@ -122,7 +216,7 @@ func processDataArgs(args []string) []map[string]any { var ( path string = strings.TrimLeft(arg, "@") contents []byte - data JSONArray + data *makeshift.Profile err error ) contents, err = os.ReadFile(path) @@ -138,17 +232,17 @@ func processDataArgs(args []string) []map[string]any { } // convert/validate input data - data, err = parseInput(contents, format.DataFormatFromFileExt(path, inputFormat)) + data, err = parseProfile(contents, format.DataFormatFromFileExt(path, inputFormat)) if err != nil { log.Error().Err(err).Str("path", path).Msg("failed to validate input from file") } // add loaded data to collection of all data - collection = append(collection, data...) + collection = append(collection, data) } else { // input should be a valid JSON var ( - data JSONArray + data *makeshift.Profile input = []byte(arg) err error ) @@ -160,57 +254,23 @@ func processDataArgs(args []string) []map[string]any { if err != nil { log.Error().Err(err).Msgf("failed to unmarshal input for argument %d", i) } - return data + return []*makeshift.Profile{data} } } } return collection } -func handleArgs(args []string) []map[string]any { - // JSON representation - type ( - JSONObject = map[string]any - JSONArray = []JSONObject - ) - // no file to load, so we just use the joined args (since each one is a new line) - // and then stop +func parseProfile(contents []byte, dataFormat format.DataFormat) (*makeshift.Profile, error) { var ( - collection JSONArray - data []byte - err error - ) - - if len(dataArgs) > 0 { - return nil - } - data, err = ReadStdin() - if err != nil { - log.Error().Err(err).Msg("failed to read from standard input") - return nil - } - if len(data) == 0 { - log.Warn().Msg("no data found from standard input") - return nil - } - fmt.Println(string(data)) - collection, err = parseInput([]byte(data), inputFormat) - if err != nil { - log.Error().Err(err).Msg("failed to validate input from arg") - } - return collection -} - -func parseInput(contents []byte, dataFormat format.DataFormat) ([]map[string]any, error) { - var ( - data []map[string]any + data *makeshift.Profile err error ) // convert/validate JSON input format err = format.Unmarshal(contents, &data, dataFormat) if err != nil { - return nil, fmt.Errorf("failed to unmarshal data: %v", err) + return nil, fmt.Errorf("failed to unmarshal profile: %v", err) } return data, nil } diff --git a/pkg/client/client.go b/pkg/client/client.go index f9cf5d9..028fced 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -1,12 +1,16 @@ package client import ( + "crypto/tls" + "crypto/x509" "fmt" "io" "mime/multipart" + "net" "net/http" "os" "strings" + "time" "git.towk2.me/towk/makeshift/pkg/util" "github.com/cavaliergopher/grab/v3" @@ -107,6 +111,43 @@ func (c *Client) UploadMultipartFile(uri, key, path string) (*http.Response, err return resp, nil } +func (c *Client) LoadCertificateFromPath(path string) error { + cacert, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read certificate at path: %s", path) + } + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(cacert) + err = c.LoadCertificateFromPool(certPool) + if err != nil { + return fmt.Errorf("could not initialize certificate from pool: %v", err) + } + return nil +} + +func (c *Client) LoadCertificateFromPool(certPool *x509.CertPool) error { + // make sure we have a valid cert pool + if certPool == nil { + return fmt.Errorf("invalid cert pool") + } + + // make sure that we can access the internal client + c.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: certPool, + InsecureSkipVerify: false, + }, + DisableKeepAlives: true, + Dial: (&net.Dialer{ + Timeout: 120 * time.Second, + KeepAlive: 120 * time.Second, + }).Dial, + TLSHandshakeTimeout: 120 * time.Second, + ResponseHeaderTimeout: 120 * time.Second, + } + return nil +} + func mustOpen(f string) *os.File { r, err := os.Open(f) if err != nil { diff --git a/pkg/service/plugins.go b/pkg/service/plugins.go index 0dd516e..0ab12eb 100644 --- a/pkg/service/plugins.go +++ b/pkg/service/plugins.go @@ -8,6 +8,7 @@ import ( makeshift "git.towk2.me/towk/makeshift/pkg" "github.com/go-chi/chi/v5" + "github.com/rs/zerolog/log" ) func (s *Service) ListPlugins() http.HandlerFunc { @@ -124,24 +125,17 @@ func (s *Service) CreatePlugin() http.HandlerFunc { func (s *Service) DeletePlugin() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var ( - path string - plugin makeshift.Plugin - err error + pluginName = chi.URLParam(r, "name") + path = s.PathForPluginWithName(pluginName) + err error ) - plugin, err = getPluginFromRequestBody(r) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - path = s.PathForPluginWithName(plugin.Name()) + log.Debug().Str("path", path).Send() err = os.Remove(path) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - w.WriteHeader(http.StatusOK) } } diff --git a/pkg/service/profiles.go b/pkg/service/profiles.go index edef023..e951e26 100644 --- a/pkg/service/profiles.go +++ b/pkg/service/profiles.go @@ -88,13 +88,10 @@ func (s *Service) GetProfile() http.HandlerFunc { func (s *Service) CreateProfile() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - type input struct { - Path string `json:"path"` - Profile *makeshift.Profile `json:"profile"` - } var ( body, contents []byte - in input + path string + profile *makeshift.Profile err error ) @@ -105,23 +102,24 @@ func (s *Service) CreateProfile() http.HandlerFunc { } // use the request info to build profile - err = json.Unmarshal(body, &in) + err = json.Unmarshal(body, &profile) if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) + http.Error(w, fmt.Sprintf("failed to unmarshal profile: %v", err.Error()), http.StatusBadRequest) return } // serialize just the profile part - contents, err = json.Marshal(in.Profile) + contents, err = json.Marshal(profile) if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) + http.Error(w, fmt.Sprintf("failed to marshal profile: %v", err.Error()), http.StatusBadRequest) return } // create a new profile on disk - err = os.WriteFile(in.Path, contents, os.ModePerm) + path = s.PathForProfileWithID(profile.ID) + err = os.WriteFile(path, contents, os.ModePerm) if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) + http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -129,6 +127,29 @@ func (s *Service) CreateProfile() http.HandlerFunc { } } +func (s *Service) DeleteProfile() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var ( + profileID = chi.URLParam(r, "id") + path string + err error + ) + + if profileID == "default" { + http.Error(w, "cannot delete the default profile", http.StatusBadRequest) + return + } + + path = s.PathForProfileWithID(profileID) + err = os.Remove(path) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) + } +} + func (s *Service) SetProfileData() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var ( diff --git a/pkg/service/routes.go b/pkg/service/routes.go index 42e7338..23d2d05 100644 --- a/pkg/service/routes.go +++ b/pkg/service/routes.go @@ -181,18 +181,9 @@ func (s *Service) Download() http.HandlerFunc { func (s *Service) Upload() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - - } -} - -func (s *Service) UploadPlugin() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - } -} - -func (s *Service) UploadProfile() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { + var ( + _ = s.PathForData() + strings.TrimPrefix(r.URL.Path, "/upload") + ) } } @@ -239,6 +230,23 @@ func (s *Service) List() http.HandlerFunc { } } +func (s *Service) Delete() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var ( + path = s.PathForData() + strings.TrimPrefix(r.URL.Path, "/delete") + err error + ) + + err = os.RemoveAll(path) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + } +} + func (s *Service) GetStatus(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") err := json.NewEncoder(w).Encode(map[string]any{ diff --git a/pkg/service/service.go b/pkg/service/service.go index b75bd57..362a08c 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -98,15 +98,15 @@ func (s *Service) Serve() error { } else { // general router.Get("/download/*", s.Download()) - router.Post("/upload/", s.Upload()) - router.Post("/upload/plugin", s.UploadPlugin()) - router.Post("/upload/profile", s.UploadProfile()) + router.Post("/upload/*", s.Upload()) router.Get("/list/*", s.List()) + router.Delete("/delete/*", s.Delete()) // profiles router.Get("/profiles", s.ListProfiles()) router.Get("/profiles/{id}", s.GetProfile()) router.Post("/profiles/{id}", s.CreateProfile()) + router.Delete("/profiles/{id}", s.DeleteProfile()) router.Get("/profiles/{id}/data", s.GetProfileData()) router.Post("/profiles/{id}/data", s.SetProfileData()) router.Delete("/profiles/{id}/data", s.DeleteProfileData())