From d3850c24f8a345e56c927dd1f48cbe0a6a468c4b Mon Sep 17 00:00:00 2001 From: Tim Shannon Date: Wed, 13 Apr 2016 16:29:17 +0000 Subject: [PATCH] Added last of web REST endpoints, need to test and start on UI --- datastore/ds.go | 12 +++---- datastore/log.go | 2 +- datastore/releases.go | 72 +++++++++++++++++++++++++++++---------- error.go | 9 ++++- project.go | 29 ++++++++++++++++ webProject.go | 79 ++++++++++++++++++++++++++++++++++++------- 6 files changed, 165 insertions(+), 38 deletions(-) diff --git a/datastore/ds.go b/datastore/ds.go index edbc9eb..bf5f817 100644 --- a/datastore/ds.go +++ b/datastore/ds.go @@ -69,9 +69,9 @@ func (ds *Store) Close() error { return ds.bolt.Close() } -func (ds *Store) get(bucket string, key TimeKey, result interface{}) error { +func (ds *Store) get(bucket string, key []byte, result interface{}) error { return ds.bolt.View(func(tx *bolt.Tx) error { - dsValue := tx.Bucket([]byte(bucket)).Get(key.Bytes()) + dsValue := tx.Bucket([]byte(bucket)).Get(key) if dsValue == nil { return ErrNotFound @@ -90,7 +90,7 @@ func (ds *Store) get(bucket string, key TimeKey, result interface{}) error { }) } -func (ds *Store) put(bucket string, key TimeKey, value interface{}) error { +func (ds *Store) put(bucket string, key []byte, value interface{}) error { var err error dsValue, ok := value.([]byte) if !ok { @@ -101,12 +101,12 @@ func (ds *Store) put(bucket string, key TimeKey, value interface{}) error { } return ds.bolt.Update(func(tx *bolt.Tx) error { - return tx.Bucket([]byte(bucket)).Put(key.Bytes(), dsValue) + return tx.Bucket([]byte(bucket)).Put(key, dsValue) }) } -func (ds *Store) delete(bucket string, key TimeKey) error { +func (ds *Store) delete(bucket string, key []byte) error { return ds.bolt.Update(func(tx *bolt.Tx) error { - return tx.Bucket([]byte(bucket)).Delete(key.Bytes()) + return tx.Bucket([]byte(bucket)).Delete(key) }) } diff --git a/datastore/log.go b/datastore/log.go index 834d557..89b005e 100644 --- a/datastore/log.go +++ b/datastore/log.go @@ -32,7 +32,7 @@ func (ds *Store) AddLog(version, stage, entry string) error { Log: entry, } - return ds.put(bucketLog, key, data) + return ds.put(bucketLog, key.Bytes(), data) } // LastVersion returns the last version in the log for the given stage. If stage is blank, diff --git a/datastore/releases.go b/datastore/releases.go index 8674517..ffcb5de 100644 --- a/datastore/releases.go +++ b/datastore/releases.go @@ -11,7 +11,8 @@ import ( "github.com/boltdb/bolt" ) -type release struct { +// Release is a record of the fully built and ready to deploy release file +type Release struct { When time.Time `json:"when"` Version string `json:"version"` FileName string `json:"fileName"` @@ -27,7 +28,7 @@ const ( func (ds *Store) AddRelease(version, fileName string, fileData []byte) error { fileKey := NewTimeKey() - r := &release{ + r := &Release{ When: fileKey.Time(), Version: version, FileName: fileName, @@ -49,33 +50,42 @@ func (ds *Store) AddRelease(version, fileName string, fileData []byte) error { }) } -func (ds *Store) Release(version string) { +// ReleaseFile returns a specific file from a release for the given file key +func (ds *Store) ReleaseFile(fileKey TimeKey) ([]byte, error) { + var fileData []byte + err := ds.get(bucketFiles, fileKey.Bytes(), &fileData) + if err != nil { + return nil, err + } + return fileData, nil +} + +// Release gets the release record for a specific version +func (ds *Store) Release(version string) (*Release, error) { + r := &Release{} + err := ds.get(bucketReleases, []byte(version), r) + if err != nil { + return nil, err + } + return r, nil } // Releases lists all the releases in a given project -func (ds *Store) Releases() ([]*Log, error) { - var vers []*Log +func (ds *Store) Releases() ([]*Release, error) { + var vers []*Release err := ds.bolt.View(func(tx *bolt.Tx) error { - c := tx.Bucket([]byte(bucketLog)).Cursor() - - var current = "" + c := tx.Bucket([]byte(bucketReleases)).Cursor() for k, v := c.Last(); k != nil; k, v = c.Prev() { - l := &Log{} - err := json.Unmarshal(v, l) + r := &Release{} + err := json.Unmarshal(v, r) if err != nil { return err } - // capture the newest entry for each version - if l.Version != current { - l.Log = "" // only care about date, ver and stage - vers = append(vers, l) - current = l.Version - } - + vers = append(vers, r) } return nil @@ -87,3 +97,31 @@ func (ds *Store) Releases() ([]*Log, error) { return vers, nil } + +// LastRelease lists the last release for a project +func (ds *Store) LastRelease() (*Release, error) { + var r *Release + + err := ds.bolt.View(func(tx *bolt.Tx) error { + c := tx.Bucket([]byte(bucketReleases)).Cursor() + + _, v := c.Last() + + err := json.Unmarshal(v, r) + if err != nil { + return err + } + + return nil + }) + + if err != nil { + return nil, err + } + + if r == nil { + return nil, ErrNotFound + } + + return r, nil +} diff --git a/error.go b/error.go index e99f3b8..c41b55f 100644 --- a/error.go +++ b/error.go @@ -10,6 +10,8 @@ import ( "fmt" "log" "net/http" + + "git.townsourced.com/ironsmith/datastore" ) const ( @@ -19,11 +21,16 @@ const ( // Err404 is a standard 404 error response var Err404 = errors.New("Resource not found") -func errHandled(err error, w http.ResponseWriter) bool { +func errHandled(err error, w http.ResponseWriter, r *http.Request) bool { if err == nil { return false } + if err == datastore.ErrNotFound { + four04(w, r) + return true + } + var status, errMsg string errMsg = err.Error() diff --git a/project.go b/project.go index cf0bc38..6e84ca4 100644 --- a/project.go +++ b/project.go @@ -228,6 +228,35 @@ func (p *Project) stageLog(version, stage string) (*datastore.Log, error) { return p.ds.StageLog(version, stage) } +func (p *Project) releases() ([]*datastore.Release, error) { + p.RLock() + defer p.RUnlock() + + return p.ds.Releases() +} + +func (p *Project) lastRelease() (*datastore.Release, error) { + p.RLock() + defer p.RUnlock() + + return p.ds.LastRelease() +} +func (p *Project) releaseData(version string) (*datastore.Release, error) { + p.RLock() + defer p.RUnlock() + + return p.ds.Release(version) +} + +func (p *Project) releaseFile(fileKey datastore.TimeKey) ([]byte, error) { + p.RLock() + defer p.RUnlock() + + return p.ds.ReleaseFile(fileKey) +} + +// releaseFile + func (p *Project) setData(new *Project) { p.Lock() defer p.Unlock() diff --git a/webProject.go b/webProject.go index 6b96fa3..a0dcf25 100644 --- a/webProject.go +++ b/webProject.go @@ -5,9 +5,11 @@ package main import ( + "bytes" "net/http" "net/url" "strings" + "time" ) // /path/// @@ -34,7 +36,7 @@ func splitPath(path string) (project, version, stage string) { /* /log/ - list all projects - /log/ - list all versions in a project, triggers new builds + /log/ - list all versions in a project, POST triggers new builds /log// - list combined output of all stages for a given version /log/// - list output of a given stage of a given version */ @@ -44,7 +46,7 @@ func logGet(w http.ResponseWriter, r *http.Request) { if prj == "" { ///log/ - list all projects pList, err := projects.webList() - if errHandled(err, w) { + if errHandled(err, w, r) { return } @@ -65,10 +67,10 @@ func logGet(w http.ResponseWriter, r *http.Request) { //project found if ver == "" { - ///log/ - list all versions in a project, triggers new builds + ///log/ - list all versions in a project vers, err := project.versions() - if errHandled(err, w) { + if errHandled(err, w, r) { return } respondJsend(w, &JSend{ @@ -82,7 +84,7 @@ func logGet(w http.ResponseWriter, r *http.Request) { if stg == "" { ///log// - list combined output of all stages for a given version logs, err := project.versionLog(ver) - if errHandled(err, w) { + if errHandled(err, w, r) { return } respondJsend(w, &JSend{ @@ -96,7 +98,7 @@ func logGet(w http.ResponseWriter, r *http.Request) { ///log/// - list output of a given stage of a given version log, err := project.stageLog(ver, stg) - if errHandled(err, w) { + if errHandled(err, w, r) { return } @@ -110,14 +112,18 @@ func logGet(w http.ResponseWriter, r *http.Request) { /* /release// - /release/ - list last release for a given project ?all returns all the releases for a project - /release// - list release for a given project version + /release/ - list last release for a given project + ?all returns all the releases for a project ?file returns the last release file + /release// - list release for a given project version ?file returns the file for a given release version */ func releaseGet(w http.ResponseWriter, r *http.Request) { - prj, ver, stg := splitPath(r.URL.Path) + prj, ver, _ := splitPath(r.URL.Path) values := r.URL.Query() + _, all := values["all"] + _, file := values["file"] + if prj == "" { four04(w, r) return @@ -132,18 +138,65 @@ func releaseGet(w http.ResponseWriter, r *http.Request) { //project found if ver == "" { - ///release/ - list last release for a given project ?all returns all the releases for a project + ///release/ - list last release for a given project + // ?all returns all the releases for a project ?file returns the last release file - vers, err := project.versions() - if errHandled(err, w) { + if all { + releases, err := project.releases() + if errHandled(err, w, r) { + return + } + + respondJsend(w, &JSend{ + Status: statusSuccess, + Data: releases, + }) + return + + } + + last, err := project.lastRelease() + if errHandled(err, w, r) { return } + + if file { + fileData, err := project.releaseFile(last.FileKey) + if errHandled(err, w, r) { + return + } + + http.ServeContent(w, r, last.FileName, time.Time{}, bytes.NewReader(fileData)) + return + } + respondJsend(w, &JSend{ Status: statusSuccess, - Data: vers, + Data: last, }) + return } //ver found + // /release// - list release for a given project version ?file returns the file for a given release version + + release, err := project.releaseData(ver) + if errHandled(err, w, r) { + return + } + + if file { + fileData, err := project.releaseFile(release.FileKey) + if errHandled(err, w, r) { + return + } + http.ServeContent(w, r, release.FileName, time.Time{}, bytes.NewReader(fileData)) + return + } + + respondJsend(w, &JSend{ + Status: statusSuccess, + Data: release, + }) }