mirror of
https://github.com/Luzifer/mondash.git
synced 2024-12-23 04:21:18 +00:00
Moved towards modular storage system
This commit is contained in:
parent
daa9b7eed7
commit
632ab60018
7 changed files with 185 additions and 32 deletions
30
config/config.go
Normal file
30
config/config.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package config // import "github.com/Luzifer/mondash/config"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Storage string
|
||||||
|
BaseURL string
|
||||||
|
APIToken string
|
||||||
|
|
||||||
|
S3 struct {
|
||||||
|
Bucket string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() *Config {
|
||||||
|
cfg := &Config{}
|
||||||
|
pflag.StringVar(&cfg.Storage, "storage", "s3", "Storage engine to use")
|
||||||
|
pflag.StringVar(&cfg.BaseURL, "baseurl", os.Getenv("BASE_URL"), "The Base-URL the application is running on for example https://mondash.org")
|
||||||
|
pflag.StringVar(&cfg.APIToken, "api-token", os.Getenv("API_TOKEN"), "API Token used for the /welcome dashboard (you can choose your own)")
|
||||||
|
|
||||||
|
// S3
|
||||||
|
pflag.StringVar(&cfg.S3.Bucket, "s3Bucket", os.Getenv("S3Bucket"), "Bucket to use for S3 storage")
|
||||||
|
|
||||||
|
pflag.Parse()
|
||||||
|
return cfg
|
||||||
|
}
|
26
main.go
26
main.go
|
@ -5,33 +5,31 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"log"
|
"github.com/Luzifer/mondash/config"
|
||||||
|
"github.com/Luzifer/mondash/storage"
|
||||||
"launchpad.net/goamz/aws"
|
|
||||||
"launchpad.net/goamz/s3"
|
|
||||||
|
|
||||||
"github.com/flosch/pongo2"
|
"github.com/flosch/pongo2"
|
||||||
"github.com/go-martini/martini"
|
"github.com/go-martini/martini"
|
||||||
|
|
||||||
_ "github.com/flosch/pongo2-addons"
|
_ "github.com/flosch/pongo2-addons"
|
||||||
)
|
)
|
||||||
|
|
||||||
var templates = make(map[string]*pongo2.Template)
|
var (
|
||||||
var s3Storage *s3.Bucket
|
templates = make(map[string]*pongo2.Template)
|
||||||
|
store storage.Storage
|
||||||
|
cfg *config.Config
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
preloadTemplates()
|
preloadTemplates()
|
||||||
|
|
||||||
// Initialize S3 storage
|
var err error
|
||||||
awsAuth, err := aws.EnvAuth()
|
cfg = config.Load()
|
||||||
|
store, err = storage.GetStorage(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
fmt.Printf("An error occurred while loading the storage handler: %s", err)
|
||||||
}
|
}
|
||||||
s3Conn := s3.New(awsAuth, aws.EUWest)
|
|
||||||
s3Storage = s3Conn.Bucket(os.Getenv("S3Bucket"))
|
|
||||||
|
|
||||||
m := martini.Classic()
|
m := martini.Classic()
|
||||||
|
|
||||||
|
@ -48,7 +46,7 @@ func main() {
|
||||||
m.Delete("/:dashid", handleDeleteDashboard)
|
m.Delete("/:dashid", handleDeleteDashboard)
|
||||||
m.Delete("/:dashid/:metricid", handleDeleteMetric)
|
m.Delete("/:dashid/:metricid", handleDeleteMetric)
|
||||||
|
|
||||||
go runWelcomePage()
|
go runWelcomePage(cfg)
|
||||||
|
|
||||||
// GO!
|
// GO!
|
||||||
m.Run()
|
m.Run()
|
||||||
|
|
36
storage/adapter.go
Normal file
36
storage/adapter.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package storage // import "github.com/Luzifer/mondash/storage"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Luzifer/mondash/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Storage is an interface to have all storage systems compatible to each other
|
||||||
|
type Storage interface {
|
||||||
|
Put(dashboardID string, data []byte) error
|
||||||
|
Get(dashboardID string) ([]byte, error)
|
||||||
|
Delete(dashboardID string) error
|
||||||
|
Exists(dashboardID string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotFoundError is a named error for more simple determination which
|
||||||
|
// type of error is thrown
|
||||||
|
type NotFoundError struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e NotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("Storage '%s' not found.", e.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStorage acts as a storage factory providing the storage named by input
|
||||||
|
// name parameter
|
||||||
|
func GetStorage(cfg *config.Config) (Storage, error) {
|
||||||
|
switch cfg.Storage {
|
||||||
|
case "s3":
|
||||||
|
return NewS3Storage(cfg), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, NotFoundError{cfg.Storage}
|
||||||
|
}
|
82
storage/s3.go
Normal file
82
storage/s3.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package storage // import "github.com/Luzifer/mondash/storage"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Luzifer/mondash/config"
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type S3Storage struct {
|
||||||
|
s3connection *s3.S3
|
||||||
|
cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewS3Storage(cfg *config.Config) *S3Storage {
|
||||||
|
s3connection := s3.New(&aws.Config{})
|
||||||
|
return &S3Storage{
|
||||||
|
s3connection: s3connection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3Storage) Put(dashboardID string, data []byte) error {
|
||||||
|
_, err := s.s3connection.PutObject(&s3.PutObjectInput{
|
||||||
|
Bucket: aws.String(s.cfg.S3.Bucket),
|
||||||
|
ContentType: aws.String("application/json"),
|
||||||
|
Key: aws.String(dashboardID),
|
||||||
|
// TODO: Private ACL
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3Storage) Get(dashboardID string) ([]byte, error) {
|
||||||
|
res, err := s.s3connection.GetObject(&s3.GetObjectInput{
|
||||||
|
Bucket: aws.String(s.cfg.S3.Bucket),
|
||||||
|
Key: aws.String(dashboardID),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3Storage) Delete(dashboardID string) error {
|
||||||
|
_, err := s.s3connection.DeleteObject(&s3.DeleteObjectInput{
|
||||||
|
Bucket: aws.String(s.cfg.S3.Bucket),
|
||||||
|
Key: aws.String(dashboardID),
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3Storage) Exists(dashboardID string) (bool, error) {
|
||||||
|
_, err := s.s3connection.HeadObject(&s3.HeadObjectInput{
|
||||||
|
Bucket: aws.String(s.cfg.S3.Bucket),
|
||||||
|
Key: aws.String(dashboardID),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if awsErr, ok := err.(awserr.Error); ok {
|
||||||
|
if strings.Contains(awsErr.Error(), "status code: 404") {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
} else {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
15
structs.go
15
structs.go
|
@ -3,26 +3,31 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"launchpad.net/goamz/s3"
|
|
||||||
"log"
|
"log"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Luzifer/mondash/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dashboard struct {
|
type dashboard struct {
|
||||||
DashboardID string `json:"-"`
|
DashboardID string `json:"-"`
|
||||||
APIKey string `json:"api_key"`
|
APIKey string `json:"api_key"`
|
||||||
Metrics dashboardMetrics `json:"metrics"`
|
Metrics dashboardMetrics `json:"metrics"`
|
||||||
|
storage storage.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDashboard(dashid string) (*dashboard, error) {
|
func loadDashboard(dashid string, store storage.Storage) (*dashboard, error) {
|
||||||
data, err := s3Storage.Get(dashid)
|
data, err := store.Get(dashid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &dashboard{}, errors.New("Dashboard not found")
|
return &dashboard{}, errors.New("Dashboard not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := &dashboard{DashboardID: dashid}
|
tmp := &dashboard{
|
||||||
|
DashboardID: dashid,
|
||||||
|
storage: store,
|
||||||
|
}
|
||||||
_ = json.Unmarshal(data, tmp)
|
_ = json.Unmarshal(data, tmp)
|
||||||
|
|
||||||
return tmp, nil
|
return tmp, nil
|
||||||
|
@ -34,7 +39,7 @@ func (d *dashboard) Save() {
|
||||||
log.Printf("Error while marshalling dashboard: %s", err)
|
log.Printf("Error while marshalling dashboard: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = s3Storage.Put(d.DashboardID, data, "application/json", s3.Private)
|
err = d.storage.Put(d.DashboardID, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error while storing dashboard: %s", err)
|
log.Printf("Error while storing dashboard: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,17 +18,18 @@ func handleRedirectWelcome(res http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCreateRandomDashboard(res http.ResponseWriter, req *http.Request) {
|
func handleCreateRandomDashboard(res http.ResponseWriter, req *http.Request) {
|
||||||
urlProposal := generateAPIKey()[0:20]
|
var urlProposal string
|
||||||
_, err := s3Storage.Get(urlProposal)
|
for {
|
||||||
for err == nil {
|
|
||||||
urlProposal = generateAPIKey()[0:20]
|
urlProposal = generateAPIKey()[0:20]
|
||||||
_, err = s3Storage.Get(urlProposal)
|
if exists, err := store.Exists(urlProposal); err == nil && !exists {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
http.Redirect(res, req, fmt.Sprintf("/%s", urlProposal), http.StatusTemporaryRedirect)
|
http.Redirect(res, req, fmt.Sprintf("/%s", urlProposal), http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDisplayDashboard(params martini.Params, res http.ResponseWriter) {
|
func handleDisplayDashboard(params martini.Params, res http.ResponseWriter) {
|
||||||
dash, err := loadDashboard(params["dashid"])
|
dash, err := loadDashboard(params["dashid"], store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dash = &dashboard{APIKey: generateAPIKey(), Metrics: dashboardMetrics{}}
|
dash = &dashboard{APIKey: generateAPIKey(), Metrics: dashboardMetrics{}}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +52,7 @@ func handleDisplayDashboard(params martini.Params, res http.ResponseWriter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDeleteDashboard(params martini.Params, req *http.Request, res http.ResponseWriter) {
|
func handleDeleteDashboard(params martini.Params, req *http.Request, res http.ResponseWriter) {
|
||||||
dash, err := loadDashboard(params["dashid"])
|
dash, err := loadDashboard(params["dashid"], store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(res, "This dashboard does not exist.", http.StatusInternalServerError)
|
http.Error(res, "This dashboard does not exist.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -62,7 +63,7 @@ func handleDeleteDashboard(params martini.Params, req *http.Request, res http.Re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = s3Storage.Del(params["dashid"])
|
store.Delete(params["dashid"])
|
||||||
http.Error(res, "OK", http.StatusOK)
|
http.Error(res, "OK", http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +81,7 @@ func handlePutMetric(params martini.Params, req *http.Request, res http.Response
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dash, err := loadDashboard(params["dashid"])
|
dash, err := loadDashboard(params["dashid"], store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(req.Header.Get("Authorization")) < 10 {
|
if len(req.Header.Get("Authorization")) < 10 {
|
||||||
http.Error(res, "APIKey is too insecure", http.StatusUnauthorized)
|
http.Error(res, "APIKey is too insecure", http.StatusUnauthorized)
|
||||||
|
@ -122,7 +123,7 @@ func handlePutMetric(params martini.Params, req *http.Request, res http.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDeleteMetric(params martini.Params, req *http.Request, res http.ResponseWriter) {
|
func handleDeleteMetric(params martini.Params, req *http.Request, res http.ResponseWriter) {
|
||||||
dash, err := loadDashboard(params["dashid"])
|
dash, err := loadDashboard(params["dashid"], store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dash = &dashboard{APIKey: req.Header.Get("Authorization"), Metrics: dashboardMetrics{}, DashboardID: params["dashid"]}
|
dash = &dashboard{APIKey: req.Header.Get("Authorization"), Metrics: dashboardMetrics{}, DashboardID: params["dashid"]}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,14 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Luzifer/mondash/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runWelcomePage() {
|
func runWelcomePage(cfg *config.Config) {
|
||||||
baseURL := os.Getenv("BASE_URL")
|
baseURL := cfg.BaseURL
|
||||||
welcomeAPIToken := os.Getenv("API_TOKEN")
|
welcomeAPIToken := cfg.APIToken
|
||||||
generateTicker := time.NewTicker(time.Minute)
|
generateTicker := time.NewTicker(time.Minute)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
Loading…
Reference in a new issue