diff --git a/cmd/wasmcloud/handler_delete.go b/cmd/wasmcloud/handler_delete.go index af7028f..9be6663 100644 --- a/cmd/wasmcloud/handler_delete.go +++ b/cmd/wasmcloud/handler_delete.go @@ -23,9 +23,12 @@ func (handlerDeleteCmd) Usage() string { $ wasmcloud delete filename.wasm Deletes a handler on wasmcloud. Please run this with care. No data loss will -happen, but support will need to be contacted. +happen, but support will need to be contacted if you want to un-delete a +handler. -Flags: +See the following command for a list of your deleted handlers: + +$ wasmcloud list -show-deleted ` } diff --git a/cmd/wasmcloud/handler_update.go b/cmd/wasmcloud/handler_update.go new file mode 100644 index 0000000..cf50bc1 --- /dev/null +++ b/cmd/wasmcloud/handler_update.go @@ -0,0 +1,95 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "time" + + "github.com/google/subcommands" + "tulpa.dev/within/wasmcloud/cmd/internal" +) + +type handlerUpdateCmd struct { + abi string +} + +func (handlerUpdateCmd) Name() string { return "update" } +func (handlerUpdateCmd) Synopsis() string { return "update a handler" } +func (handlerUpdateCmd) Usage() string { + return `wasmcloud update [options] + +$ wasmcloud update shaman-of-hearts-23813 filename.wasm + +Updates a handler on wasmcloud. Returns new details about the handler. + +Flags: +` +} + +func (h *handlerUpdateCmd) SetFlags(fs *flag.FlagSet) { + fs.StringVar(&h.abi, "abi", "cwa", "WebAssembly ABI to use for the handler") +} + +func (h handlerUpdateCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + if fs.NArg() != 2 { + fmt.Println("usage: wasmcloud update [options] ") + return subcommands.ExitUsageError + } + + hname := fs.Arg(0) + fname := fs.Arg(1) + data, err := ioutil.ReadFile(fname) + if err != nil { + log.Fatal(err) + } + + hdlr := internal.Handler{ + ABI: h.abi, + WASM: data, + } + + bodyData, _ := json.Marshal(hdlr) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, *apiServer+"/api/handler/update?name="+hname, bytes.NewBuffer(bodyData)) + if err != nil { + log.Fatal(err) + } + withAPI(req) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatal(err) + } + + if resp.StatusCode != http.StatusOK { + io.Copy(os.Stdout, resp.Body) + return subcommands.ExitFailure + } + + type apiResp struct { + ID int `json:"ID"` + CreatedAt time.Time `json:"CreatedAt"` + UpdatedAt time.Time `json:"UpdatedAt"` + Name string `json:"Name"` + Path string `json:"Path"` + } + + var result apiResp + err = json.NewDecoder(resp.Body).Decode(&result) + resp.Body.Close() + if err != nil { + log.Fatal(err) + } + + fmt.Println("updated handler") + fmt.Printf("name: %s\nIPFS ID: %s\n", result.Name, result.Path) + + return subcommands.ExitSuccess +} diff --git a/cmd/wasmcloud/main.go b/cmd/wasmcloud/main.go index 465f48b..299cc58 100644 --- a/cmd/wasmcloud/main.go +++ b/cmd/wasmcloud/main.go @@ -27,6 +27,7 @@ func main() { subcommands.Register(&handlerListCmd{}, "handlers") subcommands.Register(&handlerLogsCmd{}, "handlers") subcommands.Register(&handlerInvokeCmd{}, "handlers") + subcommands.Register(&handlerUpdateCmd{}, "handlers") subcommands.Register(namegenCmd{}, "utils") subcommands.Register(&runCmd{}, "utils") diff --git a/cmd/wasmcloudd/handler.go b/cmd/wasmcloudd/handler.go index 7a2c11c..751f896 100644 --- a/cmd/wasmcloudd/handler.go +++ b/cmd/wasmcloudd/handler.go @@ -3,6 +3,7 @@ package main import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "net/http" "time" @@ -37,6 +38,77 @@ func deleteHandler(w http.ResponseWriter, r *http.Request, u *User) { w.WriteHeader(http.StatusNoContent) } +func updateHandler(w http.ResponseWriter, r *http.Request, u *User) { + ctx := r.Context() + q := r.URL.Query() + name := q.Get("name") + if name == "" { + http.NotFound(w, r) + return + } + + var hdlr Handler + err := db.Where("name = ? AND user_id = ?", name, u.ID).First(&hdlr).Error + if err != nil { + ln.Error(ctx, err) + http.NotFound(w, r) + return + } + + data, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, 16*1024*1024)) + if err != nil { + ln.Error(ctx, err) + http.Error(w, "invalid data", http.StatusBadRequest) + return + } + r.Body.Close() + + var uHdlr internal.Handler + err = json.Unmarshal(data, &uHdlr) + if err != nil { + ln.Error(ctx, err) + http.Error(w, "not json", http.StatusBadRequest) + return + } + + cid, err := uploadHandler(uHdlr) + if err != nil { + ln.Error(ctx, err) + http.Error(w, "wasm validation failure", http.StatusBadRequest) + return + } + + hdlr.Path = cid + if err := db.Save(&hdlr).Error; err != nil { + ln.Error(ctx, err) + http.Error(w, "database error, contact support", http.StatusInternalServerError) + return + } +} + +func uploadHandler(hdlr internal.Handler) (string, error) { + sh := shell.NewShell(*ipfsURL) + + switch string(hdlr.ABI) { + case string(internal.CWA), string(internal.Dagger): + default: + return "", fmt.Errorf("unsupported ABI %s", hdlr.ABI) + } + + buf := bytes.NewBuffer(hdlr.WASM) + _, err := wasm.DecodeModule(buf) + if err != nil { + return "", fmt.Errorf("can't decode module: %w", err) + } + + cid, err := sh.Add(bytes.NewBuffer(hdlr.WASM)) + if err != nil { + return "", fmt.Errorf("can't upload module to IPFS: %w", err) + } + + return cid, nil +} + func createHandler(w http.ResponseWriter, r *http.Request, u *User) { if !u.CanCreateHandlers { http.Error(w, "you can't create handlers, contact support", http.StatusForbidden) @@ -44,7 +116,6 @@ func createHandler(w http.ResponseWriter, r *http.Request, u *User) { } ctx := r.Context() - sh := shell.NewShell(*ipfsURL) data, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, 16*1024*1024)) if err != nil { ln.Error(ctx, err) @@ -61,25 +132,10 @@ func createHandler(w http.ResponseWriter, r *http.Request, u *User) { return } - switch string(hdlr.ABI) { - case string(internal.CWA), string(internal.Dagger): - default: - ln.Log(ctx, ln.Info("unknown ABI"), ln.F{"abi": hdlr.ABI}) - http.Error(w, "unknown ABI", http.StatusBadRequest) - return - } - - buf := bytes.NewBuffer(hdlr.WASM) - _, err = wasm.DecodeModule(buf) + cid, err := uploadHandler(hdlr) if err != nil { ln.Error(ctx, err) - http.Error(w, "not webassembly", http.StatusBadRequest) - } - - cid, err := sh.Add(bytes.NewBuffer(hdlr.WASM)) - if err != nil { - ln.Error(ctx, err) - http.Error(w, "can't upload wasm to storage", http.StatusInternalServerError) + http.Error(w, "wasm validation error", http.StatusBadRequest) return } diff --git a/cmd/wasmcloudd/main.go b/cmd/wasmcloudd/main.go index 72f3c84..53a666c 100644 --- a/cmd/wasmcloudd/main.go +++ b/cmd/wasmcloudd/main.go @@ -59,6 +59,7 @@ func main() { rtr.HandleFunc("/api/handler", makeHandler(true, listHandlers)).Methods(http.MethodGet) rtr.HandleFunc("/api/handler/delete", makeHandler(true, deleteHandler)).Methods(http.MethodDelete) rtr.HandleFunc("/api/handler/create", makeHandler(true, createHandler)).Methods(http.MethodPost) + rtr.HandleFunc("/api/handler/update", makeHandler(true, updateHandler)).Methods(http.MethodPost) rtr.HandleFunc("/api/handler/logs", makeHandler(true, getLogs)).Methods(http.MethodGet) rtr.HandleFunc("/api/whoami", makeHandler(true, apiWhoami))