Clean up code

This commit is contained in:
Lena Fuhrimann 2018-05-22 22:56:01 +02:00
parent 4e41814724
commit 53a807f38c
21 changed files with 95 additions and 113 deletions

6
Gopkg.lock generated
View file

@ -54,7 +54,7 @@
branch = "master"
name = "github.com/mitchellh/go-homedir"
packages = ["."]
revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
revision = "3864e76763d94a6df2f9960b16a20a33da9f9a66"
[[projects]]
name = "github.com/pkg/errors"
@ -88,7 +88,7 @@
"blake2b",
"ssh/terminal"
]
revision = "1a580b3eff7814fc9b40602fd35256c63b50f491"
revision = "a3beeb748656e13e54256fd2cde19e058f41f60f"
[[projects]]
branch = "release-branch.go1.10"
@ -107,7 +107,7 @@
"unix",
"windows"
]
revision = "7f59abf37be6a6007f075af1bc7f16f137bc176b"
revision = "c11f84a56e43e20a78cee75a7c034031ecf57d1f"
[[projects]]
name = "golang.org/x/text"

View file

@ -1,7 +1,7 @@
.PHONY: all lint test build-docker deploy-cf
all:
go build ./cmd/...
go build ./cmd/s3manager
lint:
gometalinter --vendor ./...

View file

@ -8,8 +8,8 @@ A Web GUI written in Go to manage S3 buckets from any provider.
## Install Dependencies
1. Install [Dep](https://github.com/golang/dep)
1. Run `dep ensure`
1. Install [Dep](https://github.com/golang/dep)
1. Run `dep ensure`
## Build and Run Locally

View file

@ -1,4 +1,4 @@
FROM golang:latest
FROM golang:1
WORKDIR /go/src/github.com/mastertinner/s3manager
COPY . .

View file

@ -69,12 +69,12 @@ func main() {
Handler(s3manager.DeleteBucketHandler(s3))
r.
Methods(http.MethodPost).
Headers(s3manager.HeaderContentType, s3manager.ContentTypeJSON).
Headers("Content-Type", "application/json; charset=utf-8").
Path("/api/buckets/{bucketName}/objects").
Handler(s3manager.CopyObjectHandler(s3))
r.
Methods(http.MethodPost).
HeadersRegexp(s3manager.HeaderContentType, s3manager.ContentTypeMultipartForm).
HeadersRegexp("Content-Type", "multipart/form-data").
Path("/api/buckets/{bucketName}/objects").
Handler(s3manager.CreateObjectHandler(s3))
r.

View file

@ -11,20 +11,18 @@ import (
"github.com/pkg/errors"
)
// objectWithIcon is a minio object with an added icon.
type objectWithIcon struct {
minio.ObjectInfo
Icon string
}
// bucketPage defines the details page of a bucket.
type bucketPage struct {
BucketName string
Objects []objectWithIcon
}
// BucketViewHandler shows the details page of a bucket.
func BucketViewHandler(s3 S3, tmplDir string) http.Handler {
type objectWithIcon struct {
minio.ObjectInfo
Icon string
}
type pageData struct {
BucketName string
Objects []objectWithIcon
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
bucketName := mux.Vars(r)["bucketName"]
@ -40,7 +38,7 @@ func BucketViewHandler(s3 S3, tmplDir string) http.Handler {
obj := objectWithIcon{object, icon(object.Key)}
objs = append(objs, obj)
}
page := bucketPage{
data := pageData{
BucketName: bucketName,
Objects: objs,
}
@ -49,12 +47,12 @@ func BucketViewHandler(s3 S3, tmplDir string) http.Handler {
p := filepath.Join(tmplDir, "bucket.html.tmpl")
t, err := template.ParseFiles(l, p)
if err != nil {
handleHTTPError(w, errors.Wrap(err, errParsingTemplates))
handleHTTPError(w, errors.Wrap(err, "error parsing template files"))
return
}
err = t.ExecuteTemplate(w, "layout", page)
err = t.ExecuteTemplate(w, "layout", data)
if err != nil {
handleHTTPError(w, errors.Wrap(err, errExecutingTemplate))
handleHTTPError(w, errors.Wrap(err, "error executing template"))
return
}
})
@ -63,7 +61,6 @@ func BucketViewHandler(s3 S3, tmplDir string) http.Handler {
// icon returns an icon for a file type.
func icon(fileName string) string {
e := path.Ext(fileName)
switch e {
case ".tgz", ".gz":
return "archive"
@ -72,6 +69,5 @@ func icon(fileName string) string {
case ".mp3", ".wav":
return "music_note"
}
return "insert_drive_file"
}

View file

@ -23,7 +23,7 @@ func TestBucketViewHandler(t *testing.T) {
expectedBodyContains string
}{
"renders a bucket containing a file": {
listObjectsV2Func: func(bucketName string, objectPrefix string, recursive bool, doneCh <-chan struct{}) <-chan minio.ObjectInfo {
listObjectsV2Func: func(string, string, bool, <-chan struct{}) <-chan minio.ObjectInfo {
objCh := make(chan minio.ObjectInfo)
go func() {
objCh <- minio.ObjectInfo{Key: "testFile"}
@ -36,7 +36,7 @@ func TestBucketViewHandler(t *testing.T) {
expectedBodyContains: "testFile",
},
"renders placeholder for an empty bucket": {
listObjectsV2Func: func(bucketName string, objectPrefix string, recursive bool, doneCh <-chan struct{}) <-chan minio.ObjectInfo {
listObjectsV2Func: func(string, string, bool, <-chan struct{}) <-chan minio.ObjectInfo {
objCh := make(chan minio.ObjectInfo)
close(objCh)
return objCh
@ -46,7 +46,7 @@ func TestBucketViewHandler(t *testing.T) {
expectedBodyContains: "No objects in",
},
"renders a bucket containing an archive": {
listObjectsV2Func: func(bucketName string, objectPrefix string, recursive bool, doneCh <-chan struct{}) <-chan minio.ObjectInfo {
listObjectsV2Func: func(string, string, bool, <-chan struct{}) <-chan minio.ObjectInfo {
objCh := make(chan minio.ObjectInfo)
go func() {
objCh <- minio.ObjectInfo{Key: "archive.tar.gz"}
@ -59,7 +59,7 @@ func TestBucketViewHandler(t *testing.T) {
expectedBodyContains: "archive",
},
"renders a bucket containing an image": {
listObjectsV2Func: func(bucketName string, objectPrefix string, recursive bool, doneCh <-chan struct{}) <-chan minio.ObjectInfo {
listObjectsV2Func: func(string, string, bool, <-chan struct{}) <-chan minio.ObjectInfo {
objCh := make(chan minio.ObjectInfo)
go func() {
objCh <- minio.ObjectInfo{Key: "testImage.png"}
@ -72,7 +72,7 @@ func TestBucketViewHandler(t *testing.T) {
expectedBodyContains: "photo",
},
"renders a bucket containing a sound file": {
listObjectsV2Func: func(bucketName string, objectPrefix string, recursive bool, doneCh <-chan struct{}) <-chan minio.ObjectInfo {
listObjectsV2Func: func(string, string, bool, <-chan struct{}) <-chan minio.ObjectInfo {
objCh := make(chan minio.ObjectInfo)
go func() {
objCh <- minio.ObjectInfo{Key: "testSound.mp3"}
@ -85,7 +85,7 @@ func TestBucketViewHandler(t *testing.T) {
expectedBodyContains: "music_note",
},
"returns error if the bucket doesn't exist": {
listObjectsV2Func: func(bucketName string, objectPrefix string, recursive bool, doneCh <-chan struct{}) <-chan minio.ObjectInfo {
listObjectsV2Func: func(string, string, bool, <-chan struct{}) <-chan minio.ObjectInfo {
objCh := make(chan minio.ObjectInfo)
go func() {
objCh <- minio.ObjectInfo{Err: s3manager.ErrBucketDoesNotExist}
@ -98,7 +98,7 @@ func TestBucketViewHandler(t *testing.T) {
expectedBodyContains: http.StatusText(http.StatusNotFound),
},
"returns error if there is an S3 error": {
listObjectsV2Func: func(bucketName string, objectPrefix string, recursive bool, doneCh <-chan struct{}) <-chan minio.ObjectInfo {
listObjectsV2Func: func(string, string, bool, <-chan struct{}) <-chan minio.ObjectInfo {
objCh := make(chan minio.ObjectInfo)
go func() {
objCh <- minio.ObjectInfo{Err: errors.New("mocked S3 error")}
@ -132,7 +132,7 @@ func TestBucketViewHandler(t *testing.T) {
url := fmt.Sprintf("%s/buckets/%s", ts.URL, tc.bucketName)
resp, err := http.Get(url)
assert.NoError(err, tcID)
assert.NoError(err)
defer func() {
err = resp.Body.Close()
if err != nil {
@ -141,10 +141,10 @@ func TestBucketViewHandler(t *testing.T) {
}()
body, err := ioutil.ReadAll(resp.Body)
assert.NoError(err, tcID)
assert.NoError(err)
assert.Equal(tc.expectedStatusCode, resp.StatusCode, tcID)
assert.Contains(string(body), tc.expectedBodyContains, tcID)
assert.Equal(tc.expectedStatusCode, resp.StatusCode)
assert.Contains(string(body), tc.expectedBodyContains)
})
}
}

View file

@ -21,12 +21,12 @@ func BucketsViewHandler(s3 S3, tmplDir string) http.Handler {
p := filepath.Join(tmplDir, "buckets.html.tmpl")
t, err := template.ParseFiles(l, p)
if err != nil {
handleHTTPError(w, errors.Wrap(err, errParsingTemplates))
handleHTTPError(w, errors.Wrap(err, "error parsing template files"))
return
}
err = t.ExecuteTemplate(w, "layout", buckets)
if err != nil {
handleHTTPError(w, errors.Wrap(err, errExecutingTemplate))
handleHTTPError(w, errors.Wrap(err, "error executing template"))
return
}
})

View file

@ -58,7 +58,7 @@ func TestBucketsViewHandler(t *testing.T) {
Handler(s3manager.BucketViewHandler(s3, tmplDir))
req, err := http.NewRequest(http.MethodGet, "/buckets", nil)
assert.NoError(err, tcID)
assert.NoError(err)
rr := httptest.NewRecorder()
handler := s3manager.BucketsViewHandler(s3, tmplDir)
@ -66,8 +66,8 @@ func TestBucketsViewHandler(t *testing.T) {
handler.ServeHTTP(rr, req)
resp := rr.Result()
assert.Equal(tc.expectedStatusCode, resp.StatusCode, tcID)
assert.Contains(rr.Body.String(), tc.expectedBodyContains, tcID)
assert.Equal(tc.expectedStatusCode, resp.StatusCode)
assert.Contains(rr.Body.String(), tc.expectedBodyContains)
})
}
}

View file

@ -9,26 +9,26 @@ import (
minio "github.com/minio/minio-go"
)
// copyObjectInfo is the information about an object to copy.
type copyObjectInfo struct {
BucketName string `json:"bucketName"`
ObjectName string `json:"objectName"`
SourceBucketName string `json:"sourceBucketName"`
SourceObjectName string `json:"sourceObjectName"`
}
// CopyObjectHandler copies an existing object under a new name.
func CopyObjectHandler(s3 S3) http.Handler {
// request is the information about an object to copy.
type request struct {
BucketName string `json:"bucketName"`
ObjectName string `json:"objectName"`
SourceBucketName string `json:"sourceBucketName"`
SourceObjectName string `json:"sourceObjectName"`
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var copy copyObjectInfo
err := json.NewDecoder(r.Body).Decode(&copy)
var req request
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
handleHTTPError(w, errors.Wrap(err, errDecodingBody))
handleHTTPError(w, errors.Wrap(err, "error decoding body JSON"))
return
}
src := minio.NewSourceInfo(copy.SourceBucketName, copy.SourceObjectName, nil)
dst, err := minio.NewDestinationInfo(copy.BucketName, copy.ObjectName, nil, nil)
src := minio.NewSourceInfo(req.SourceBucketName, req.SourceObjectName, nil)
dst, err := minio.NewDestinationInfo(req.BucketName, req.ObjectName, nil, nil)
if err != nil {
handleHTTPError(w, errors.Wrap(err, "error creating destination for copying"))
return
@ -39,11 +39,11 @@ func CopyObjectHandler(s3 S3) http.Handler {
return
}
w.Header().Set(HeaderContentType, ContentTypeJSON)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusCreated)
err = json.NewEncoder(w).Encode(copy)
err = json.NewEncoder(w).Encode(req)
if err != nil {
handleHTTPError(w, errors.Wrap(err, errEncodingJSON))
handleHTTPError(w, errors.Wrap(err, "error encoding JSON"))
return
}
})

View file

@ -14,7 +14,7 @@ func CreateBucketHandler(s3 S3) http.Handler {
var bucket minio.BucketInfo
err := json.NewDecoder(r.Body).Decode(&bucket)
if err != nil {
handleHTTPError(w, errors.Wrap(err, errDecodingBody))
handleHTTPError(w, errors.Wrap(err, "error decoding body JSON"))
return
}
@ -24,11 +24,11 @@ func CreateBucketHandler(s3 S3) http.Handler {
return
}
w.Header().Set(HeaderContentType, ContentTypeJSON)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusCreated)
err = json.NewEncoder(w).Encode(bucket)
if err != nil {
handleHTTPError(w, errors.Wrap(err, errEncodingJSON))
handleHTTPError(w, errors.Wrap(err, "error encoding JSON"))
return
}
})

View file

@ -19,15 +19,15 @@ func TestCreateBucketHandler(t *testing.T) {
expectedBodyContains string
}{
"creates a new bucket": {
makeBucketFunc: func(bucketName string, location string) error {
makeBucketFunc: func(string, string) error {
return nil
},
body: "{\"name\":\"myBucket\"}",
body: `{"name":"myBucket"}`,
expectedStatusCode: http.StatusCreated,
expectedBodyContains: "{\"name\":\"myBucket\",\"creationDate\":\"0001-01-01T00:00:00Z\"}\n",
expectedBodyContains: `{"name":"myBucket","creationDate":"0001-01-01T00:00:00Z"}`,
},
"returns error for empty request": {
makeBucketFunc: func(bucketName string, location string) error {
makeBucketFunc: func(string, string) error {
return nil
},
body: "",
@ -35,7 +35,7 @@ func TestCreateBucketHandler(t *testing.T) {
expectedBodyContains: http.StatusText(http.StatusUnprocessableEntity),
},
"returns error for malformed request": {
makeBucketFunc: func(bucketName string, location string) error {
makeBucketFunc: func(string, string) error {
return nil
},
body: "}",
@ -43,10 +43,10 @@ func TestCreateBucketHandler(t *testing.T) {
expectedBodyContains: http.StatusText(http.StatusUnprocessableEntity),
},
"returns error if there is an S3 error": {
makeBucketFunc: func(bucketName string, location string) error {
makeBucketFunc: func(string, string) error {
return errors.New("mocked S3 error")
},
body: "{\"name\":\"myBucket\"}",
body: `{"name":"myBucket"}`,
expectedStatusCode: http.StatusInternalServerError,
expectedBodyContains: http.StatusText(http.StatusInternalServerError),
},
@ -61,7 +61,7 @@ func TestCreateBucketHandler(t *testing.T) {
}
req, err := http.NewRequest(http.MethodPost, "/api/buckets", bytes.NewBufferString(tc.body))
assert.NoError(err, tcID)
assert.NoError(err)
rr := httptest.NewRecorder()
handler := s3manager.CreateBucketHandler(s3)
@ -69,8 +69,8 @@ func TestCreateBucketHandler(t *testing.T) {
handler.ServeHTTP(rr, req)
resp := rr.Result()
assert.Equal(tc.expectedStatusCode, resp.StatusCode, tcID)
assert.Contains(rr.Body.String(), tc.expectedBodyContains, tcID)
assert.Equal(tc.expectedStatusCode, resp.StatusCode)
assert.Contains(rr.Body.String(), tc.expectedBodyContains)
})
}
}

View file

@ -15,7 +15,7 @@ func CreateObjectHandler(s3 S3) http.Handler {
bucketName := mux.Vars(r)["bucketName"]
err := r.ParseMultipartForm(32 << 20)
if err != nil {
handleHTTPError(w, errors.Wrap(err, errParsingForm))
handleHTTPError(w, errors.Wrap(err, "error parsing multipart form"))
return
}
file, handler, err := r.FormFile("file")
@ -30,7 +30,7 @@ func CreateObjectHandler(s3 S3) http.Handler {
}
}()
_, err = s3.PutObject(bucketName, handler.Filename, file, 1, minio.PutObjectOptions{ContentType: contentTypeOctetStream})
_, err = s3.PutObject(bucketName, handler.Filename, file, 1, minio.PutObjectOptions{ContentType: "application/octet-stream"})
if err != nil {
handleHTTPError(w, errors.Wrap(err, "error putting object"))
return

View file

@ -17,14 +17,14 @@ func TestDeleteBucketHandler(t *testing.T) {
expectedBodyContains string
}{
"deletes an existing bucket": {
removeBucketFunc: func(bucketName string) error {
removeBucketFunc: func(string) error {
return nil
},
expectedStatusCode: http.StatusNoContent,
expectedBodyContains: "",
},
"returns error if there is an S3 error": {
removeBucketFunc: func(bucketName string) error {
removeBucketFunc: func(string) error {
return errors.New("mocked S3 error")
},
expectedStatusCode: http.StatusInternalServerError,
@ -41,7 +41,7 @@ func TestDeleteBucketHandler(t *testing.T) {
}
req, err := http.NewRequest(http.MethodDelete, "/api/buckets/bucketName", nil)
assert.NoError(err, tcID)
assert.NoError(err)
rr := httptest.NewRecorder()
handler := s3manager.DeleteBucketHandler(s3)
@ -49,8 +49,8 @@ func TestDeleteBucketHandler(t *testing.T) {
handler.ServeHTTP(rr, req)
resp := rr.Result()
assert.Equal(tc.expectedStatusCode, resp.StatusCode, tcID)
assert.Contains(rr.Body.String(), tc.expectedBodyContains, tcID)
assert.Equal(tc.expectedStatusCode, resp.StatusCode)
assert.Contains(rr.Body.String(), tc.expectedBodyContains)
})
}
}

View file

@ -17,14 +17,14 @@ func TestDeleteObjectHandler(t *testing.T) {
expectedBodyContains string
}{
"deletes an existing object": {
removeObjectFunc: func(bucketName string, objectName string) error {
removeObjectFunc: func(string, string) error {
return nil
},
expectedStatusCode: http.StatusNoContent,
expectedBodyContains: "",
},
"returns error if there is an S3 error": {
removeObjectFunc: func(bucketName string, objectName string) error {
removeObjectFunc: func(string, string) error {
return errors.New("mocked S3 error")
},
expectedStatusCode: http.StatusInternalServerError,
@ -41,15 +41,15 @@ func TestDeleteObjectHandler(t *testing.T) {
}
req, err := http.NewRequest(http.MethodDelete, "/api/buckets/bucketName/objects/objectName", nil)
assert.NoError(err, tcID)
assert.NoError(err)
rr := httptest.NewRecorder()
handler := s3manager.DeleteObjectHandler(s3)
handler.ServeHTTP(rr, req)
assert.Equal(tc.expectedStatusCode, rr.Code, tcID)
assert.Contains(rr.Body.String(), tc.expectedBodyContains, tcID)
assert.Equal(tc.expectedStatusCode, rr.Code)
assert.Contains(rr.Body.String(), tc.expectedBodyContains)
})
}
}

View file

@ -9,15 +9,6 @@ import (
"github.com/pkg/errors"
)
// Error codes commonly used throughout the application
const (
errDecodingBody = "error decoding body JSON"
errEncodingJSON = "error encoding JSON"
errExecutingTemplate = "error executing template"
errParsingForm = "error parsing form"
errParsingTemplates = "error parsing template files"
)
// Errors that may be returned from an S3 client.
var (
ErrBucketDoesNotExist = errors.New("The specified bucket does not exist.") // nolint: golint

View file

@ -23,8 +23,8 @@ func GetObjectHandler(s3 S3) http.Handler {
return
}
w.Header().Set(headerContentDisposition, fmt.Sprintf("attachment; filename=\"%s\"", objectName))
w.Header().Set(HeaderContentType, contentTypeOctetStream)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", objectName))
w.Header().Set("Content-Type", "application/octet-stream")
_, err = io.Copy(w, object)
if err != nil {
handleHTTPError(w, errors.Wrap(err, "error copying object to response writer"))

View file

@ -23,7 +23,7 @@ func TestGetObjectHandler(t *testing.T) {
expectedBodyContains string
}{
"returns error if there is an S3 error": {
getObjectFunc: func(bucketName string, objectName string, opts minio.GetObjectOptions) (*minio.Object, error) {
getObjectFunc: func(string, string, minio.GetObjectOptions) (*minio.Object, error) {
return nil, errors.New("mocked S3 error")
},
bucketName: "testBucket",
@ -52,7 +52,7 @@ func TestGetObjectHandler(t *testing.T) {
url := fmt.Sprintf("%s/buckets/%s/objects/%s", ts.URL, tc.bucketName, tc.objectName)
resp, err := http.Get(url)
assert.NoError(err, tcID)
assert.NoError(err)
defer func() {
err = resp.Body.Close()
if err != nil {
@ -61,10 +61,10 @@ func TestGetObjectHandler(t *testing.T) {
}()
body, err := ioutil.ReadAll(resp.Body)
assert.NoError(err, tcID)
assert.NoError(err)
assert.Equal(tc.expectedStatusCode, resp.StatusCode, tcID)
assert.Contains(string(body), tc.expectedBodyContains, tcID)
assert.Equal(tc.expectedStatusCode, resp.StatusCode)
assert.Contains(string(body), tc.expectedBodyContains)
})
}
}

View file

@ -1,11 +1,2 @@
// Package s3manager allows to interact with an S3 compatible storage.
package s3manager
// These are constants commonly used throughout the application.
const (
HeaderContentType = "Content-Type"
ContentTypeJSON = "application/json"
ContentTypeMultipartForm = "multipart/form-data"
headerContentDisposition = "Content-Disposition"
contentTypeOctetStream = "application/octet-stream"
)

View file

@ -83,7 +83,7 @@ function createBucket() {
url: '/api/buckets',
data: JSON.stringify(formData),
dataType: 'json',
contentType: 'application/json',
contentType: 'application/json; charset=utf-8',
success: function() { location.reload(); }
});
}

View file

@ -10,15 +10,19 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css"><Paste>
</head>
<body>
{{ template "content" . }}
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"
></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
<script>
$(document).ready(function(){
$('.modal').modal();