From ef90f1dca6273350c12e0ef4ab41705790881172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= <torkel.odegaard@gmail.com> Date: Tue, 30 Dec 2014 10:37:00 +0100 Subject: [PATCH] Updated readme --- .../macaron-contrib/session/file.go | 259 ++++++++++++++++++ .../macaron-contrib/session/file_test.go | 34 +++ .../macaron-contrib/session/memory.go | 212 ++++++++++++++ .../macaron-contrib/session/memory_test.go | 27 ++ .../macaron-contrib/session/session_test.go | 200 ++++++++++++++ .../macaron-contrib/session/utils.go | 69 +++++ README.md | 47 +--- 7 files changed, 803 insertions(+), 45 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/file.go create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/file_test.go create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/memory_test.go create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go create mode 100644 Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/file.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/file.go new file mode 100644 index 00000000000..4dfb906ec7e --- /dev/null +++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/file.go @@ -0,0 +1,259 @@ +// Copyright 2013 Beego Authors +// Copyright 2014 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package session + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path" + "path/filepath" + "sync" + "time" + + "github.com/Unknwon/com" +) + +// FileSessionStore represents a file session store implementation. +type FileSessionStore struct { + p *FileProvider + sid string + lock sync.RWMutex + data map[interface{}]interface{} +} + +// NewFileSessionStore creates and returns a file session store. +func NewFileSessionStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileSessionStore { + return &FileSessionStore{ + p: p, + sid: sid, + data: kv, + } +} + +// Set sets value to given key in session. +func (s *FileSessionStore) Set(key, val interface{}) error { + s.lock.Lock() + defer s.lock.Unlock() + + s.data[key] = val + return nil +} + +// Get gets value by given key in session. +func (s *FileSessionStore) Get(key interface{}) interface{} { + s.lock.RLock() + defer s.lock.RUnlock() + + return s.data[key] +} + +// Delete delete a key from session. +func (s *FileSessionStore) Delete(key interface{}) error { + s.lock.Lock() + defer s.lock.Unlock() + + delete(s.data, key) + return nil +} + +// ID returns current session ID. +func (s *FileSessionStore) ID() string { + return s.sid +} + +// Release releases resource and save data to provider. +func (s *FileSessionStore) Release() error { + data, err := EncodeGob(s.data) + if err != nil { + return err + } + + return ioutil.WriteFile(s.p.filepath(s.sid), data, os.ModePerm) +} + +// Flush deletes all session data. +func (s *FileSessionStore) Flush() error { + s.lock.Lock() + defer s.lock.Unlock() + + s.data = make(map[interface{}]interface{}) + return nil +} + +// FileProvider represents a file session provider implementation. +type FileProvider struct { + lock sync.RWMutex + maxlifetime int64 + rootPath string +} + +// Init initializes file session provider with given root path. +func (p *FileProvider) Init(maxlifetime int64, rootPath string) error { + p.maxlifetime = maxlifetime + p.rootPath = rootPath + return nil +} + +func (p *FileProvider) filepath(sid string) string { + return path.Join(p.rootPath, string(sid[0]), string(sid[1]), sid) +} + +// Read returns raw session store by session ID. +func (p *FileProvider) Read(sid string) (_ RawStore, err error) { + p.lock.Lock() + defer p.lock.Unlock() + + filename := p.filepath(sid) + if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil { + return nil, err + } + + var f *os.File + if com.IsFile(filename) { + f, err = os.OpenFile(filename, os.O_RDWR, os.ModePerm) + } else { + f, err = os.Create(filename) + } + if err != nil { + return nil, err + } + defer f.Close() + + if err = os.Chtimes(filename, time.Now(), time.Now()); err != nil { + return nil, err + } + + var kv map[interface{}]interface{} + data, err := ioutil.ReadAll(f) + if err != nil { + return nil, err + } + if len(data) == 0 { + kv = make(map[interface{}]interface{}) + } else { + kv, err = DecodeGob(data) + if err != nil { + return nil, err + } + } + return NewFileSessionStore(p, sid, kv), nil +} + +// Exist returns true if session with given ID exists. +func (p *FileProvider) Exist(sid string) bool { + p.lock.Lock() + defer p.lock.Unlock() + + return com.IsFile(p.filepath(sid)) +} + +// Destory deletes a session by session ID. +func (p *FileProvider) Destory(sid string) error { + p.lock.Lock() + defer p.lock.Unlock() + + return os.Remove(p.filepath(sid)) +} + +func (p *FileProvider) regenerate(oldsid, sid string) (err error) { + filename := p.filepath(sid) + if com.IsExist(filename) { + return fmt.Errorf("new sid '%s' already exists", sid) + } + + oldname := p.filepath(oldsid) + if !com.IsFile(oldname) { + data, err := EncodeGob(make(map[interface{}]interface{})) + if err != nil { + return err + } + if err = os.MkdirAll(path.Dir(oldname), os.ModePerm); err != nil { + return err + } + if err = ioutil.WriteFile(oldname, data, os.ModePerm); err != nil { + return err + } + } + + if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil { + return err + } + if err = os.Rename(oldname, filename); err != nil { + return err + } + return nil +} + +// Regenerate regenerates a session store from old session ID to new one. +func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) { + p.lock.Lock() + if err := p.regenerate(oldsid, sid); err != nil { + p.lock.Unlock() + return nil, err + } + p.lock.Unlock() + + return p.Read(sid) +} + +// Count counts and returns number of sessions. +func (p *FileProvider) Count() int { + count := 0 + if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + if !fi.IsDir() { + count++ + } + return nil + }); err != nil { + log.Printf("error counting session files: %v", err) + return 0 + } + return count +} + +// GC calls GC to clean expired sessions. +func (p *FileProvider) GC() { + if !com.IsExist(p.rootPath) { + return + } + + p.lock.Lock() + defer p.lock.Unlock() + + if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + if !fi.IsDir() && + (fi.ModTime().Unix()+p.maxlifetime) < time.Now().Unix() { + return os.Remove(path) + } + return nil + }); err != nil { + log.Printf("error garbage collecting session files: %v", err) + } +} + +func init() { + Register("file", &FileProvider{}) +} diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/file_test.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/file_test.go new file mode 100644 index 00000000000..9c83555ab4e --- /dev/null +++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/file_test.go @@ -0,0 +1,34 @@ +// Copyright 2014 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package session + +import ( + "os" + "path" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func Test_FileProvider(t *testing.T) { + Convey("Test file session provider", t, func() { + dir := path.Join(os.TempDir(), "data/sessions") + os.RemoveAll(dir) + testProvider(Options{ + Provider: "file", + ProviderConfig: dir, + }) + }) +} diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go new file mode 100644 index 00000000000..040b8e697dc --- /dev/null +++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go @@ -0,0 +1,212 @@ +// Copyright 2013 Beego Authors +// Copyright 2014 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package session + +import ( + "container/list" + "fmt" + "sync" + "time" +) + +// MemSessionStore represents a in-memory session store implementation. +type MemSessionStore struct { + sid string + lock sync.RWMutex + data map[interface{}]interface{} + lastAccess time.Time +} + +// NewMemSessionStore creates and returns a memory session store. +func NewMemSessionStore(sid string) *MemSessionStore { + return &MemSessionStore{ + sid: sid, + data: make(map[interface{}]interface{}), + lastAccess: time.Now(), + } +} + +// Set sets value to given key in session. +func (s *MemSessionStore) Set(key, val interface{}) error { + s.lock.Lock() + defer s.lock.Unlock() + + s.data[key] = val + return nil +} + +// Get gets value by given key in session. +func (s *MemSessionStore) Get(key interface{}) interface{} { + s.lock.RLock() + defer s.lock.RUnlock() + + return s.data[key] +} + +// Delete delete a key from session. +func (s *MemSessionStore) Delete(key interface{}) error { + s.lock.Lock() + defer s.lock.Unlock() + + delete(s.data, key) + return nil +} + +// ID returns current session ID. +func (s *MemSessionStore) ID() string { + return s.sid +} + +// Release releases resource and save data to provider. +func (_ *MemSessionStore) Release() error { + return nil +} + +// Flush deletes all session data. +func (s *MemSessionStore) Flush() error { + s.lock.Lock() + defer s.lock.Unlock() + + s.data = make(map[interface{}]interface{}) + return nil +} + +// MemProvider represents a in-memory session provider implementation. +type MemProvider struct { + lock sync.RWMutex + maxLifetime int64 + data map[string]*list.Element + // A priority list whose lastAccess newer gets higer priority. + list *list.List +} + +// Init initializes memory session provider. +func (p *MemProvider) Init(maxLifetime int64, _ string) error { + p.maxLifetime = maxLifetime + return nil +} + +// update expands time of session store by given ID. +func (p *MemProvider) update(sid string) error { + p.lock.Lock() + defer p.lock.Unlock() + + if e, ok := p.data[sid]; ok { + e.Value.(*MemSessionStore).lastAccess = time.Now() + p.list.MoveToFront(e) + return nil + } + return nil +} + +// Read returns raw session store by session ID. +func (p *MemProvider) Read(sid string) (_ RawStore, err error) { + p.lock.RLock() + e, ok := p.data[sid] + p.lock.RUnlock() + + if ok { + if err = p.update(sid); err != nil { + return nil, err + } + return e.Value.(*MemSessionStore), nil + } + + // Create a new session. + p.lock.Lock() + defer p.lock.Unlock() + + s := NewMemSessionStore(sid) + p.data[sid] = p.list.PushBack(s) + return s, nil +} + +// Exist returns true if session with given ID exists. +func (p *MemProvider) Exist(sid string) bool { + p.lock.RLock() + defer p.lock.RUnlock() + + _, ok := p.data[sid] + return ok +} + +// Destory deletes a session by session ID. +func (p *MemProvider) Destory(sid string) error { + p.lock.Lock() + defer p.lock.Unlock() + + e, ok := p.data[sid] + if !ok { + return nil + } + + p.list.Remove(e) + delete(p.data, sid) + return nil +} + +// Regenerate regenerates a session store from old session ID to new one. +func (p *MemProvider) Regenerate(oldsid, sid string) (RawStore, error) { + if p.Exist(sid) { + return nil, fmt.Errorf("new sid '%s' already exists", sid) + } + + s, err := p.Read(oldsid) + if err != nil { + return nil, err + } + + if err = p.Destory(oldsid); err != nil { + return nil, err + } + + s.(*MemSessionStore).sid = sid + p.data[sid] = p.list.PushBack(s) + return s, nil +} + +// Count counts and returns number of sessions. +func (p *MemProvider) Count() int { + return p.list.Len() +} + +// GC calls GC to clean expired sessions. +func (p *MemProvider) GC() { + p.lock.RLock() + for { + // No session in the list. + e := p.list.Back() + if e == nil { + break + } + + if (e.Value.(*MemSessionStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() { + p.lock.RUnlock() + p.lock.Lock() + p.list.Remove(e) + delete(p.data, e.Value.(*MemSessionStore).sid) + p.lock.Unlock() + p.lock.RLock() + } else { + break + } + } + p.lock.RUnlock() +} + +func init() { + Register("memory", &MemProvider{list: list.New(), data: make(map[string]*list.Element)}) +} diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/memory_test.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/memory_test.go new file mode 100644 index 00000000000..41659bb6a1e --- /dev/null +++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/memory_test.go @@ -0,0 +1,27 @@ +// Copyright 2014 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package session + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func Test_MemProvider(t *testing.T) { + Convey("Test memory session provider", t, func() { + testProvider(Options{}) + }) +} diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go new file mode 100644 index 00000000000..327c1d0c6a5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go @@ -0,0 +1,200 @@ +// Copyright 2014 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package session + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Unknwon/macaron" + . "github.com/smartystreets/goconvey/convey" +) + +func Test_Version(t *testing.T) { + Convey("Check package version", t, func() { + So(Version(), ShouldEqual, _VERSION) + }) +} + +func Test_Sessioner(t *testing.T) { + Convey("Use session middleware", t, func() { + m := macaron.New() + m.Use(Sessioner()) + m.Get("/", func() {}) + + resp := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/", nil) + So(err, ShouldBeNil) + m.ServeHTTP(resp, req) + }) + + Convey("Register invalid provider that", t, func() { + Convey("Provider not exists", func() { + defer func() { + So(recover(), ShouldNotBeNil) + }() + + m := macaron.New() + m.Use(Sessioner(Options{ + Provider: "fake", + })) + }) + + Convey("Provider value is nil", func() { + defer func() { + So(recover(), ShouldNotBeNil) + }() + + Register("fake", nil) + }) + + Convey("Register twice", func() { + defer func() { + So(recover(), ShouldNotBeNil) + }() + + Register("memory", &MemProvider{}) + }) + }) +} + +func testProvider(opt Options) { + Convey("Basic operation", func() { + m := macaron.New() + m.Use(Sessioner(opt)) + + m.Get("/", func(ctx *macaron.Context, sess Store) { + sess.Set("uname", "unknwon") + }) + m.Get("/reg", func(ctx *macaron.Context, sess Store) { + raw, err := sess.RegenerateId(ctx) + So(err, ShouldBeNil) + So(raw, ShouldNotBeNil) + + uname := raw.Get("uname") + So(uname, ShouldNotBeNil) + So(uname, ShouldEqual, "unknwon") + }) + m.Get("/get", func(ctx *macaron.Context, sess Store) { + sid := sess.ID() + So(sid, ShouldNotBeEmpty) + + raw, err := sess.Read(sid) + So(err, ShouldBeNil) + So(raw, ShouldNotBeNil) + + uname := sess.Get("uname") + So(uname, ShouldNotBeNil) + So(uname, ShouldEqual, "unknwon") + + So(sess.Delete("uname"), ShouldBeNil) + So(sess.Get("uname"), ShouldBeNil) + + So(sess.Destory(ctx), ShouldBeNil) + }) + + resp := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/", nil) + So(err, ShouldBeNil) + m.ServeHTTP(resp, req) + + cookie := resp.Header().Get("Set-Cookie") + + resp = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/reg", nil) + So(err, ShouldBeNil) + req.Header.Set("Cookie", cookie) + m.ServeHTTP(resp, req) + + cookie = resp.Header().Get("Set-Cookie") + + resp = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/get", nil) + So(err, ShouldBeNil) + req.Header.Set("Cookie", cookie) + m.ServeHTTP(resp, req) + }) + + Convey("Regenrate empty session", func() { + m := macaron.New() + m.Use(Sessioner(opt)) + m.Get("/", func(ctx *macaron.Context, sess Store) { + raw, err := sess.RegenerateId(ctx) + So(err, ShouldBeNil) + So(raw, ShouldNotBeNil) + }) + + resp := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/", nil) + So(err, ShouldBeNil) + req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;") + m.ServeHTTP(resp, req) + }) + + Convey("GC session", func() { + m := macaron.New() + opt2 := opt + opt2.Gclifetime = 1 + m.Use(Sessioner(opt2)) + + m.Get("/", func(sess Store) { + sess.Set("uname", "unknwon") + So(sess.ID(), ShouldNotBeEmpty) + uname := sess.Get("uname") + So(uname, ShouldNotBeNil) + So(uname, ShouldEqual, "unknwon") + + So(sess.Flush(), ShouldBeNil) + So(sess.Get("uname"), ShouldBeNil) + + time.Sleep(2 * time.Second) + sess.GC() + So(sess.Count(), ShouldEqual, 0) + }) + + resp := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/", nil) + So(err, ShouldBeNil) + m.ServeHTTP(resp, req) + }) +} + +func Test_Flash(t *testing.T) { + Convey("Test flash", t, func() { + m := macaron.New() + m.Use(Sessioner()) + m.Get("/set", func(f *Flash) string { + f.Success("success") + f.Error("error") + f.Warning("warning") + f.Info("info") + return "" + }) + m.Get("/get", func() {}) + + resp := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/set", nil) + So(err, ShouldBeNil) + m.ServeHTTP(resp, req) + + resp = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/get", nil) + So(err, ShouldBeNil) + req.Header.Set("Cookie", "macaron_flash=error%3Derror%26info%3Dinfo%26success%3Dsuccess%26warning%3Dwarning; Path=/") + m.ServeHTTP(resp, req) + }) +} diff --git a/Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go b/Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go new file mode 100644 index 00000000000..a165de83070 --- /dev/null +++ b/Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go @@ -0,0 +1,69 @@ +// Copyright 2013 Beego Authors +// Copyright 2014 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package session + +import ( + "bytes" + "crypto/rand" + "encoding/gob" + "io" + + "github.com/Unknwon/com" +) + +func init() { + gob.Register([]interface{}{}) + gob.Register(map[int]interface{}{}) + gob.Register(map[string]interface{}{}) + gob.Register(map[interface{}]interface{}{}) + gob.Register(map[string]string{}) + gob.Register(map[int]string{}) + gob.Register(map[int]int{}) + gob.Register(map[int]int64{}) +} + +func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) { + for _, v := range obj { + gob.Register(v) + } + buf := bytes.NewBuffer(nil) + enc := gob.NewEncoder(buf) + err := enc.Encode(obj) + if err != nil { + return []byte(""), err + } + return buf.Bytes(), nil +} + +func DecodeGob(encoded []byte) (map[interface{}]interface{}, error) { + buf := bytes.NewBuffer(encoded) + dec := gob.NewDecoder(buf) + var out map[interface{}]interface{} + err := dec.Decode(&out) + if err != nil { + return nil, err + } + return out, nil +} + +// generateRandomKey creates a random key with the given strength. +func generateRandomKey(strength int) []byte { + k := make([]byte, strength) + if n, err := io.ReadFull(rand.Reader, k); n != strength || err != nil { + return com.RandomCreateBytes(strength) + } + return k +} diff --git a/README.md b/README.md index 02cae2bb0e9..6fdb471d847 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,3 @@ -grafana-pro -=========== - -This is very much a Work in Progess. The architecture and organisation has changed many times and I am not very happy -with the current structure and architecture. I started with a lot of unit tests and drove the design though that in -the begining, but then having changed the code and structure (and web framework libs, and database) I sort of -abandoned them. When the architecture is getting more stable I will write & require more unit & integration tests. - -# Architecture -I have researched a lot of go projects, found very few with a nice modular & loosly coupled archictecture. Go projects -organize code very differently, some treat packages as global singletons (which reads nicely and is nice to work with -but seems like a bad practice). I do miss generics for doing strongly typed but loosley coupled command/query style -internal buss messaging. - -The biggest challange for architecture is making Grafana Pro very modular in a way that all/most components can -run inproc or in seperate process (usefull when running Grafana Pro in a large scale SaaS setup). Been investigating -using ZeroMQ or NanoMsg to handle module communication (Both ZeroMQ and NanoMsg can handle inproc & tcp which a number of -different topologies). Running Grafana Pro in a SaaS setup might warrant that the alerting stuff runs out of proc on -other servers feeding of a queue for example, but for simple on prem stuff it would be cool if that could be run in the -same process. I am also thinking that some stuff might need swapping out/in depending on the setup (plugin model or just -interfaces with different implementations). - -# Building -* Just added a simple make file (the main binary package is in pkg/cmd/grafana-pro) -* Need to change to godep or some go lang dependency management system - -# Data access -Data access is very strange right now, instead of an interface I tried using public methods in the models package. -These method pointers are assigned in when the sqlstore is initialized in pkg/store/sqlstore. This is probably a -bad idea. I am thinking about either moving to simple interfaces. But I do not want a giant interface for all -data acess. Would much prefe a simple generic command/query interface but golang lacks generics which makes this -painful. But a generic command/query interface could still be good as it would make it easier to have some commands or -queries handled buy out of proc components (or inproc if we used someting like ZeroMQ for communication). Or it -could be overengineering thinking like this. - -# Grafana frontend -The grafana frontend is added as git submodule in order to easily sync with upstream grafana. -There should be a symbolic link from ./grafana/src to ./public . Need to add this to the Makefile. - -# TODO -* Add symbolik link between ./grafana/src ./public -* Hash & Salt password -* Unit tests for data access -* I switched recently from rethinkdb to sql (using a simple mini ORM lib called xorm, might need to switch to a more -popular ORM lib). +Work in progress Grafana 2.0 (with included Grafana backend) +[](https://app.wercker.com/project/bykey/0f109051cfaf2a6d94c0eebdc0dcaeae)