Finished cli

This commit is contained in:
Tobias B 2021-11-15 14:34:17 +01:00
parent 1ac988bcdb
commit 5a74b3ce06
No known key found for this signature in database
GPG Key ID: 5EF4C92355A3B53D
6 changed files with 199 additions and 15 deletions

View File

@ -1,6 +1,7 @@
package main
import (
"fmt"
"github.com/superioz/aqua/internal/aqcli"
"github.com/urfave/cli/v2"
"os"
@ -18,6 +19,6 @@ func main() {
err := app.Run(os.Args)
if err != nil {
panic(err)
fmt.Printf("%v", err)
}
}

1
go.mod
View File

@ -16,6 +16,7 @@ require (
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/golang/protobuf v1.3.3 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/h2non/filetype v1.1.1 // indirect
github.com/joho/godotenv v1.4.0 // indirect
github.com/json-iterator/go v1.1.9 // indirect
github.com/leodido/go-urn v1.2.0 // indirect

2
go.sum
View File

@ -24,6 +24,8 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4=
github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=

View File

@ -1,9 +1,18 @@
package aqcli
import (
"bytes"
"encoding/json"
"fmt"
"github.com/superioz/aqua/internal/handler"
"github.com/superioz/aqua/pkg/shttp"
"github.com/urfave/cli/v2"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
)
var UploadCommand = &cli.Command{
@ -12,16 +21,21 @@ var UploadCommand = &cli.Command{
ArgsUsage: "<file [file2 file3 ...]>",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "host",
Aliases: []string{"h"},
Usage: "Specifies to which host to upload to",
Value: "localhost:8765",
Name: "host",
Usage: "Specifies to which host to upload to",
Value: "http://localhost:8765",
},
&cli.StringFlag{
Name: "token",
Aliases: []string{"t"},
Usage: "Token used for authorization",
},
&cli.IntFlag{
Name: "expires",
Aliases: []string{"e"},
Value: -1,
Usage: "Time in seconds when the file should expire. -1 = never.",
},
},
Action: func(c *cli.Context) error {
paths := c.Args().Slice()
@ -31,13 +45,14 @@ var UploadCommand = &cli.Command{
}
host := c.String("host")
if !strings.HasPrefix(host, "http") {
// defaults to https
host = "https://" + host
}
token := c.String("token")
expires := c.Int("expires")
fmt.Println("Host: " + host)
fmt.Println("Token: " + token)
fmt.Println(paths)
var files []*os.File
for _, path := range paths {
file, err := os.Open(path)
if err != nil {
@ -45,14 +60,60 @@ var UploadCommand = &cli.Command{
return fmt.Errorf("could not open file: %v", err)
}
files = append(files, file)
id, err := doPostRequest(host, token, file, &handler.RequestMetadata{
Expiration: int64(expires),
})
if err != nil {
// one of the file does not exist
return fmt.Errorf("could not upload file %s: %v", path, err)
}
file.Close()
fmt.Printf("Uploaded file %s to %s/%s", path, host, id)
}
return doPostRequest(host, token, files)
return nil
},
}
func doPostRequest(host string, token string, files []*os.File) error {
return nil
type postResponse struct {
Id string
}
func doPostRequest(host string, token string, file *os.File, metadata *handler.RequestMetadata) (string, error) {
md, err := json.Marshal(metadata)
if err != nil {
return "", err
}
values := map[string]io.Reader{
"file": file,
"metadata": bytes.NewReader(md),
}
client := &http.Client{
Timeout: 30 * time.Second,
}
res, err := shttp.Upload(client, host+"/upload", values, map[string]string{
"Authorization": "Bearer " + token,
})
if err != nil {
return "", err
}
if res.StatusCode != http.StatusOK {
return "", fmt.Errorf("bad status code: %d", res.StatusCode)
}
resData, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}
var resp postResponse
err = json.Unmarshal(resData, &resp)
if err != nil {
return "", err
}
return resp.Id, nil
}

View File

@ -2,6 +2,7 @@ package handler
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/superioz/aqua/internal/config"
"github.com/superioz/aqua/internal/storage"
@ -117,6 +118,13 @@ func (u *UploadHandler) Upload(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"msg": "could not store file"})
}
expiresIn := "never"
if metadata.Expiration >= 0 {
expiresIn = fmt.Sprintf("%ds", metadata.Expiration)
}
klog.Infof("Stored file %s (expiresIn: %s)", sf.Id, expiresIn)
c.JSON(http.StatusOK, gin.H{"id": sf.Id})
}

111
pkg/shttp/shttp.go Normal file
View File

@ -0,0 +1,111 @@
package shttp
import (
"bytes"
"fmt"
"github.com/h2non/filetype"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"os"
"strings"
)
// Upload takes an url and multiple reader, that are used to fill in a multipart form.
//
// Heavily inspired by: https://stackoverflow.com/a/20397167/11155150 but with the standard http package,
// there are not many other ways to do that, so it doesn't really matter.
func Upload(client *http.Client, url string, values map[string]io.Reader, header map[string]string) (*http.Response, error) {
// Prepare a form that you will submit to that URL.
var b bytes.Buffer
w := multipart.NewWriter(&b)
for key, r := range values {
var fw io.Writer
var err error
if x, ok := r.(io.Closer); ok {
defer x.Close()
}
// Add an image file
if file, ok := r.(*os.File); ok {
mime, err := GetFileType(file)
if err != nil {
return nil, err
}
fw, err = createFormFile(w, key, file.Name(), mime)
if err != nil {
return nil, err
}
} else {
// Add other fields
fw, err = w.CreateFormField(key)
if err != nil {
return nil, err
}
}
// Write to form field
_, err = io.Copy(fw, r)
if err != nil {
return nil, err
}
}
w.Close()
req, err := http.NewRequest("POST", url, &b)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", w.FormDataContentType())
for key, val := range header {
req.Header.Set(key, val)
}
res, err := client.Do(req)
if err != nil {
return nil, err
}
return res, nil
}
// createFormFile is copied from multipart.CreateFormFile, because it
// always sets the content type to `application/octet-stream`.
func createFormFile(w *multipart.Writer, fieldname, filename, contentType string) (io.Writer, error) {
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
escapeQuotes(fieldname), escapeQuotes(filename)))
h.Set("Content-Type", contentType)
return w.CreatePart(h)
}
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string {
return quoteEscaper.Replace(s)
}
// GetFileType returns the MIME type of given file by reading
// the file header of the file that is located on disk.
func GetFileType(file *os.File) (string, error) {
f, err := os.Open(file.Name())
if err != nil {
return "", err
}
head := make([]byte, 261)
f.Read(head)
kind, err := filetype.Match(head)
if err != nil {
return "", err
}
if kind == filetype.Unknown {
return "", fmt.Errorf("unknown file type for %s", file.Name())
}
return kind.MIME.Value, nil
}