Commit 8806185d authored by Benjamin Rokseth's avatar Benjamin Rokseth
Browse files

Initial commit

parents
.env
\ No newline at end of file
FROM golang:1.10 as builder
WORKDIR /go/src/app
COPY . .
RUN go get -d -v ./...
RUN go install -v ./...
RUN CGO_ENABLED=0 GOOS=linux go build
FROM alpine:3.7
RUN apk add --update --no-cache ca-certificates
COPY --from=builder /go/src/app/minotaur /minotaur
CMD /minotaur
## Minotaur
REST service for uploading and managing images and other media.
Exposes a simple API for uploading media and accessing.
Using Minio cloud storage and docker.
You can run minotaur separately, but we recommend using docker and docker-compose.
# Build
```
docker build -t minotaur .
or
docker-compose build minotaur
```
# Usage
Minotaur needs some ENV vars set for Minio Bucket storage:
create a .env file:
```
REGION=deichman
MINIO_ACCESS_KEY=<somerandomid>
MINIO_SECRET_KEY=<asecrethash>
```
To start:
```
docker-compose up -d
```
```
Usage of minotaur:
-l string
server listeing address (default ":1666")
-mid string
Minio access key
-msec string
Minio secret key
-mssl
Use ssl with minio
-murl string
Address of minio storage API (default "localhost:9000")
```
# Documentation
Standard setup exposes two services:
Minio DB : localhost:9000
API : localhost:9001
## Api
```
POST localhost:9001/api/images Upload Image (form data upload, e.g. curl -F "file=@./somefile.png" )
Response: JSON id, and length of uploaded image, and url to stored image in Minio
PUT localhost:9001/api/images/{id} Overwrite Image Object (form data upload, e.g. curl -XPUT -F "file=@./somefile.png" )
GET localhost:9001/api/images/{id} Get Image Object
DELETE localhost:9001/api/images/{id} Delete Image Object
```
\ No newline at end of file
version: '3'
volumes:
minio_data:
driver: local
minio_cfg:
driver: local
networks:
minotaur:
driver: bridge
services:
minio:
image: minio/minio:edge
networks:
- minotaur
environment:
MINIO_REGION: "${MINIO_REGION}"
MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY}"
MINIO_SECRET_KEY: "${MINIO_SECRET_KEY}"
volumes:
- minio_data:/data
- minio_cfg:/root/.minio
ports:
- "9000:80"
command:
- "minio"
- "server"
- "--address"
- ":80"
- "/data"
logging:
driver: "json-file"
options:
max-size: "1m"
max-file: "2"
minotaur:
container_name: minotaur
image: "digibib/minotaur:${GITREF:-latest}"
build:
context: .
dockerfile: Dockerfile
networks:
- minotaur
ports:
- "9001:1666"
logging:
driver: "json-file"
options:
max-size: "1m"
max-file: "2"
package main
import (
"crypto/rand"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"mime"
"net/http"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
//"github.com/h2non/bimg" // TODO: image processing -- sudo apt install libvips-tools
minio "github.com/minio/minio-go"
)
const maxUploadSize = 64 * 1024 * 1024 // 64 MB
type server struct {
minio *minio.Client // handle to minio bucket storage
mux *chi.Mux // HTTP Routes handled by server
}
type serverStats struct {
Objects int `json:"objects"`
}
func newServer(m *minio.Client) *server {
s := server{
minio: m,
}
s.mux = s.setupRouter()
return &s
}
func (s *server) ListenAndServe(addr string) error {
return http.ListenAndServe(addr, s.mux)
}
func (s *server) setupRouter() *chi.Mux {
r := chi.NewRouter()
r.Use(
middleware.Logger, // TODO remove when going live - too much noise
middleware.DefaultCompress,
middleware.Recoverer,
)
r.HandleFunc("/_test", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Test route\n"))
})
r.HandleFunc("/_status", s.statusHandler)
r.Route("/api", func(r chi.Router) {
r.Route("/images", func(r chi.Router) {
r.Get("/{id}", s.getImage)
r.Post("/", s.uploadImage)
r.Put("/", s.updateImage)
r.Delete("/{id}", s.deleteImage)
})
})
return r
}
func (s *server) statusHandler(w http.ResponseWriter, r *http.Request) {
stats := serverStats{
Objects: 666,
}
if err := json.NewEncoder(w).Encode(stats); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}
type Image struct {
Name string `json:"name"`
URL string `json:"url"`
Size int64 `json:"size"`
File io.Reader `json:"-"`
}
func (s *server) getImage(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
w.Write([]byte(id))
}
func (s *server) uploadImage(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
if err := r.ParseMultipartForm(maxUploadSize); err != nil {
http.Error(w, "FILE_TOO_BIG", http.StatusBadRequest)
return
}
file, _, err := r.FormFile("file")
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
defer file.Close()
fileBytes, err := ioutil.ReadAll(file)
if err != nil {
http.Error(w, "INVALID_FILE", http.StatusBadRequest)
return
}
filetype := http.DetectContentType(fileBytes)
fmt.Println(filetype)
if filetype != "image/jpeg" && filetype != "image/jpg" &&
filetype != "image/gif" && filetype != "image/png" &&
filetype != "application/pdf" && filetype != "image/svg" {
http.Error(w, "INVALID_FILE_TYPE", http.StatusBadRequest)
return
}
uid := randID(12)
fileEndings, err := mime.ExtensionsByType(filetype)
if err != nil {
http.Error(w, "INVALID_FILE_TYPE", http.StatusBadRequest)
return
}
fileName := uid + fileEndings[0]
fmt.Printf("FileType: %s, File: %s\n", filetype, fileName)
// Get length of multipart form
fileLen, _ := file.Seek(0, 2)
_, _ = file.Seek(0, 0)
img := Image{
Name: fileName,
URL: fmt.Sprintf("http://localhost:9000/images/%s", fileName),
Size: fileLen,
File: file,
}
// upload to minio
n, err := s.minio.PutObject("images", img.Name, img.File, img.Size, minio.PutObjectOptions{
ContentType: filetype,
UserMetadata: map[string]string{"Foo": "Bar"}, // TODO: Add metadata from params
})
if err != nil {
log.Println(err)
http.Error(w, "CANT_WRITE_FILE", http.StatusInternalServerError)
return
}
log.Printf("SUCCESS: Wrote %d bytes to storage\n", n)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(img); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}
// TODO UPDATE
func (s *server) updateImage(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
w.Write([]byte(id))
}
// TODO DELETE
func (s *server) deleteImage(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
w.Write([]byte(id))
}
func randID(l int) string {
b := make([]byte, l)
rand.Read(b)
return fmt.Sprintf("%x", b)
}
func main() {
var (
minioURL = flag.String("murl", "localhost:9000", "Address of minio storage API")
minioKey = flag.String("mid", "", "Minio access key")
minioSec = flag.String("msec", "", "Minio secret key")
minioSsl = flag.Bool("mssl", false, "Use ssl with minio")
listen = flag.String("l", ":1666", "server listeing address")
)
flag.Parse()
m, err := minio.New(*minioURL, *minioKey, *minioSec, *minioSsl)
if err != nil {
log.Fatal(err)
}
log.Printf("starting minotaur server; listening at %s", *listen)
s := newServer(m)
if err := s.ListenAndServe(*listen); err != nil {
log.Printf("listen and serve at %s failed: %v", *listen, err)
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment