Skip to content

Instantly share code, notes, and snippets.

@0sc
Created January 4, 2022 19:03
Show Gist options
  • Save 0sc/64d8710cf17aa3894a22a039abc02777 to your computer and use it in GitHub Desktop.
Save 0sc/64d8710cf17aa3894a22a039abc02777 to your computer and use it in GitHub Desktop.
Code snippets for using Imgproxy with Minio.
IMGPROXY_KEY=$(xxd -g 2 -l 64 -p /dev/random | tr -d '\n')
IMGPROXY_SALT=$(xxd -g 2 -l 64 -p /dev/random | tr -d '\n')
ACCESS_SECRET=$(xxd -g 2 -l 5 -p /dev/random | tr -d '\n')
ACCESS_KEY=$(xxd -g 2 -l 5 -p /dev/random | tr -d '\n')
REGION=us-east-1
DEFAULT_BUCKET=sample
services:
app:
image: golang:bullseye
command: /bin/sh -c "go mod init && go mod tidy && go run main.go"
ports:
- "50100:8080"
environment:
- ACCESS_KEY=${ACCESS_KEY:-aws-access-key}
- ACCESS_SECRET=${ACCESS_SECRET:-aws-access-secret}
- IMG_UPLOAD_BUCKET=${DEFAULT_BUCKET:-sample}
- IMGPROXY_PUBLIC_URL=http://localhost:50200
- IMGPROXY_SIGNING_KEY=${IMGPROXY_KEY}
- IMGPROXY_SIGNING_SALT=${IMGPROXY_SALT}
- RESOURCE_ENDPOINT=http://minio:9000
- RESOURCE_REGION=${REGION:-us-east-1}
volumes:
- ./main.go:/go/src/app/main.go
working_dir: /go/src/app
depends_on:
- minio
- imgproxy
imgproxy:
image: darthsim/imgproxy:latest
ports:
- "50200:8080"
environment:
- AWS_ACCESS_KEY_ID=${ACCESS_KEY:-aws-access-key}
- AWS_SECRET_ACCESS_KEY=${ACCESS_SECRET:-aws-access-secret}
- IMGPROXY_ALLOWED_SOURCES=s3://
- IMGPROXY_DEVELOPMENT_ERRORS_MODE=true
- IMGPROXY_KEY=${IMGPROXY_KEY}
- IMGPROXY_S3_ENDPOINT=http://minio:9000
- IMGPROXY_S3_REGION=${REGION:-us-east-1}
- IMGPROXY_SALT=${IMGPROXY_SALT}
- IMGPROXY_USE_S3=true
links:
- minio
minio:
image: minio/minio
command: server /data --console-address ":9001"
hostname: minio
ports:
- "50300:9000"
- "50400:9001"
environment:
- MINIO_DEFAULT_BUCKET=${DEFAULT_BUCKET:-sample}
- MINIO_ROOT_PASSWORD=${ACCESS_SECRET:-aws-access-secret}
- MINIO_ROOT_USER=${ACCESS_KEY:-aws-access-key}
- MINIO_SITE_REGION=${REGION:-us-east-1}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
volumes:
- data:/data
volumes:
data:
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"path"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
s3 "github.com/fclairamb/afero-s3"
"github.com/google/uuid"
"github.com/kelseyhightower/envconfig"
"github.com/pkg/errors"
"github.com/spf13/afero"
)
type config struct {
BaseURL string `envconfig:"IMGPROXY_PUBLIC_URL"`
SigningKeyHex string `envconfig:"IMGPROXY_SIGNING_KEY"`
SigningSaltHex string `envconfig:"IMGPROXY_SIGNING_SALT"`
}
type imageHandler struct {
baseURL string
signingKey []byte
signingSalt []byte
storage afero.Fs
}
func newimageHandler(cfg config, storage afero.Fs) (*imageHandler, error) {
// Prepare the url signing key and salt
key, err := hex.DecodeString(cfg.SigningKeyHex)
if err != nil {
return nil, errors.Wrap(err, "Key expected to be hex-encoded string")
}
salt, err := hex.DecodeString(cfg.SigningSaltHex)
if err != nil {
return nil, errors.Wrap(err, "Salt expected to be hex-encoded string")
}
img := &imageHandler{
baseURL: cfg.BaseURL,
signingKey: key,
signingSalt: salt,
storage: storage,
}
return img, nil
}
func (ih *imageHandler) store(key string, image io.Reader) error {
file, err := ih.storage.OpenFile(key, os.O_WRONLY, 0777)
if err != nil {
return errors.Wrap(err, "failed to open image")
}
defer file.Close()
_, err = io.Copy(file, image)
if err != nil {
return errors.Wrap(err, "failed to store image")
}
return file.Close()
}
func (ih *imageHandler) sign(path string) string {
mac := hmac.New(sha256.New, ih.signingKey)
mac.Write(ih.signingSalt) // FIXME: possible error?
mac.Write([]byte(path)) // FIXME: possible error?
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}
func (ih *imageHandler) generateURL(imgURI string) string {
// TODO: The properties are set here for simplicity.
// but will be an argument to the function in a real application
resize := "fill"
width := 300
height := 300
gravity := "no"
enlarge := 1
extension := "png"
imgURI = base64.RawURLEncoding.EncodeToString([]byte(imgURI))
path := fmt.Sprintf("/rs:%s:%d:%d:%d/g:%s/%s.%s", resize, width, height, enlarge, gravity, imgURI, extension)
return fmt.Sprintf("%s/%s%s", ih.baseURL, ih.sign(path), path)
}
func (ih *imageHandler) handler(bucket string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Maximum upload of 10 MB files
err := r.ParseMultipartForm(10 << 20)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Get the file from the request
// Note that the argument to FormFile should match the name attribute
// of the upload input tag in the form.
file, handler, err := r.FormFile("image")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer file.Close()
// Generate a unique key for the image
// We'll be using a uuid for this example
// but any unique string goes
//
// FYI this is not a 100% reliable way to determine file type
// but it's good enough for this example
imgKey := uuid.NewString() + path.Ext(handler.Filename)
// Store the image
err = ih.store(imgKey, file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// TODO: save the image key in the database as needed.
// Generate the URL for the image
imgURL := ih.generateURL(fmt.Sprintf("s3://%s/%s", bucket, imgKey))
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Image URL: %s", imgURL)
}
}
func main() {
var cfg struct {
// Extra configuration for setting up connection to the file storage
AccessKey string `envconfig:"ACCESS_KEY"`
AccessSecret string `envconfig:"ACCESS_SECRET"`
Bucket string `envconfig:"IMG_UPLOAD_BUCKET"`
Endpoint string `envconfig:"RESOURCE_ENDPOINT"`
Region string `envconfig:"RESOURCE_REGION"`
ImgConfig config
}
err := envconfig.Process("", &cfg)
if err != nil {
panic(err)
}
// Create a file system for storing the images
sess, err := session.NewSession(&aws.Config{
Region: aws.String(cfg.Region),
Endpoint: aws.String(cfg.Endpoint),
Credentials: credentials.NewStaticCredentials(cfg.AccessKey, cfg.AccessSecret, ""),
S3ForcePathStyle: aws.Bool(true),
})
if err != nil {
panic(err)
}
img, err := newimageHandler(cfg.ImgConfig, s3.NewFs(cfg.Bucket, sess))
if err != nil {
panic(err)
}
mux := http.NewServeMux()
mux.Handle("/", img.handler(cfg.Bucket))
fmt.Println("Starting server on port", ":8080")
http.ListenAndServe(":8080", mux)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment