Skip to content

Instantly share code, notes, and snippets.

@harshavardhana
Last active November 9, 2017 18:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save harshavardhana/68c7145dc6dd6b973d7c0f5f14cb533c to your computer and use it in GitHub Desktop.
Save harshavardhana/68c7145dc6dd6b973d7c0f5f14cb533c to your computer and use it in GitHub Desktop.

Database parameters

MariaDB [(none)]> CREATE DATABASE minio_event_db CHARACTER SET utf8 COLLATE utf8_bin;
Query OK, 1 row affected (0.00 sec)

MariaDB [(none)]> CREATE USER 'minio'@'%' IDENTIFIED BY '@0bjectst0rage';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON minio_event_db.* to minio@'%' identified by '0bjectst0rage';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON minio_event_db.* to minio@localhost identified by '0bjectst0rage';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON minio_event_db.* TO 'minio'@'%' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON minio_event_db.* TO 'minio'@localhost WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

Structure

type EventInfo struct {
	EventType string
	Key       string
	Records   []minio.NotificationEvent
}

type ExifData struct {
	EventInfo
	ExifData map[string]*tiff.Tag
}

Webhook server code for lambda

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"

	"github.com/go-sql-driver/mysql"
	router "github.com/gorilla/mux"
	"github.com/minio/cli"
	minio "github.com/minio/minio-go"
	"github.com/rwcarlsen/goexif/exif"
	"github.com/rwcarlsen/goexif/mknote"
	"github.com/rwcarlsen/goexif/tiff"
)

const (
	// Queries for format=namespace mode.
	upsertRowForNSMySQL = `INSERT INTO %s (key_name, value)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE value=VALUES(value);
`
)

var minioClient *minio.Client
var upsertStatement *sql.Stmt

func init() {
	// build from other parameters
	config := mysql.Config{
		User:   "minio",
		Passwd: "0bjectst0rage",
		Net:    "tcp",
		Addr:   "127.0.0.1:3306",
		DBName: "minio_event_db",
	}

	db, err := sql.Open("mysql", config.FormatDSN())
	if err != nil {
		log.Fatalln(err)
	}

	// ping to check that server is actually reachable.
	if err = db.Ping(); err != nil {
		log.Fatalln(err)
	}

	// insert or update statement
	upsertStatement, err = db.Prepare(fmt.Sprintf(upsertRowForNSMySQL, "minio_ns_info"))
	if err != nil {
		log.Fatalln(err)
	}

	minioClient, err = minio.New("localhost:9000", "minio123", "minio12345", false)
	if err != nil {
		log.Fatalln(err)
	}
}

type walker struct {
	metadata map[string]*tiff.Tag
}

func (w walker) Walk(name exif.FieldName, tag *tiff.Tag) error {
	w.metadata["exif-"+strings.ToLower(string(name))] = tag
	return nil
}

func getExifData(r io.Reader) (map[string]*tiff.Tag, error) {
	var metadata = map[string]*tiff.Tag{}

	// Optionally register camera makenote data parsing - currently Nikon and
	// Canon are supported.
	exif.RegisterParsers(mknote.All...)

	x, err := exif.Decode(r)
	if err != nil {
		return nil, err
	}

	x.Walk(walker{metadata})

	return metadata, nil
}

type eventInfo struct {
	EventType string
	Key       string
	Records   []minio.NotificationEvent
}

type exifData struct {
	eventInfo
	ExifData map[string]*tiff.Tag
}

func postEventHandler(w http.ResponseWriter, r *http.Request) {
	if r.ContentLength == 0 {
		w.WriteHeader(http.StatusOK)
		w.(http.Flusher).Flush()
		return
	}

	ei := eventInfo{}
	jdecoder := json.NewDecoder(r.Body)
	if err := jdecoder.Decode(&ei); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	w.WriteHeader(http.StatusOK)
	w.(http.Flusher).Flush()
	if strings.HasPrefix(ei.EventType, "s3:ObjectCreated:") {
		for _, record := range ei.Records {
			objName, err := url.PathUnescape(record.S3.Object.Key)
			if err != nil {
				log.Println(err)
				return
			}
			obj, err := minioClient.GetObject(record.S3.Bucket.Name, objName, minio.GetObjectOptions{})
			if err != nil {
				log.Println(err)
				return
			}

			exifdata, err := getExifData(obj)
			if err != nil && err != io.EOF {
				obj.Close()
				log.Println(err)
				return
			}
			obj.Close()
			if err == nil {
				var b []byte
				b, err = json.Marshal(&exifData{
					eventInfo: ei,
					ExifData:  exifdata,
				})
				if err != nil {
					log.Println(err)
					return
				}

				// upsert row into the table
				if _, err = upsertStatement.Exec(ei.Key, b); err != nil {
					log.Println(err)
					return
				}
			}
		}
	}
}

func main() {
	app := cli.NewApp()
	app.Name = "webhook"
	app.Usage = "Simple Webhook Server"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "address, a",
			Value: ":8080",
			Usage: "Address to listen on",
		},
		cli.BoolFlag{
			Name:  "log,l",
			Usage: "Log to stderr",
		},
		cli.StringFlag{
			Name:  "cert,c",
			Value: "",
			Usage: "Certificate for TLS",
		},
		cli.StringFlag{
			Name:  "key,k",
			Value: "",
			Usage: "Key for TLS",
		},
	}
	app.Action = func(c *cli.Context) {
		address := ":8080"
		if c.String("address") != "" {
			address = c.String("address")
		}

		// Initialize router. `SkipClean(true)` stops gorilla/mux from
		// normalizing URL path.
		mux := router.NewRouter().SkipClean(true)

		// API Router
		apiRouter := mux.NewRoute().PathPrefix("/").Subrouter()
		apiRouter.Methods("POST").HandlerFunc(postEventHandler)

		cert := c.String("cert")
		key := c.String("key")
		// if cert or key are set, start TLS
		if cert != "" || key != "" {
			// Both cert and key must be set
			if cert == "" || key == "" {
				log.Fatalln("Both a certificate and key must be provided for TLS")
			}
			log.Printf("Listening on https://%s\n", address)
			log.Fatalln(http.ListenAndServeTLS(address, cert, key, mux))
		} else {
			log.Printf("Listening on http://%s\n", address)
			log.Fatalln(http.ListenAndServe(address, mux))
		}
	}

	app.Run(os.Args)

}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment