1
0
Fork 0
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:
Knut Ahlers 2015-07-06 21:41:21 +02:00
parent daa9b7eed7
commit 632ab60018
7 changed files with 185 additions and 32 deletions

30
config/config.go Normal file
View 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
View file

@ -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
View 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
View 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
}

View file

@ -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)
} }

View file

@ -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"]}
} }

View file

@ -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 {