diff --git a/README.md b/README.md index c7aca22..42a3e93 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,8 @@ You'll setup a project which will need the following information: * Choose between polling for changes, or triggered builds * Triggered builds will be triggered off of a web hook POST call 2. Script to build the repository - * Can have multiple scripts run in sequence 3. Script to test the repository - * Can have multiple scripts run in sequence 4. Script to build the release file - * Should create one file 5. Path to the release file 6. Script to set release name / version * If script doesn't return a unique name, ironsmith will append a timestamp diff --git a/datastore/releases.go b/datastore/releases.go index bee1c64..c1f8dc4 100644 --- a/datastore/releases.go +++ b/datastore/releases.go @@ -4,7 +4,12 @@ package datastore -import "time" +import ( + "encoding/json" + "time" + + "github.com/boltdb/bolt" +) type release struct { When time.Time `json:"when"` @@ -28,15 +33,17 @@ func (ds *Store) AddRelease(version string, fileData []byte) error { FileKey: fileKey, } - err := ds.put(bucketReleases, key, r) + dsValue, err := json.Marshal(r) if err != nil { return err } - err = ds.put(bucketFiles, fileKey, fileData) - if err != nil { - return err - } + return ds.bolt.Update(func(tx *bolt.Tx) error { + err = tx.Bucket([]byte(bucketReleases)).Put(key.Bytes(), dsValue) + if err != nil { + return err + } - return nil + return tx.Bucket([]byte(bucketFiles)).Put(fileKey.Bytes(), fileData) + }) } diff --git a/project.go b/project.go index d1a03ef..452257e 100644 --- a/project.go +++ b/project.go @@ -7,17 +7,28 @@ package main import ( "encoding/json" "errors" - "fmt" "io/ioutil" + "log" "os" "path/filepath" "strings" "sync" "time" + + "git.townsourced.com/ironsmith/datastore" ) const enabledProjectDir = "enabled" +//stages +const ( + stageLoad = "load" + stageFetch = "fetch" + stageBuild = "build" + stageTest = "test" + stageRelease = "release" +) + // Project is an ironsmith project that contains how to fetch, build, test, and release a project /* The project lifecycle goes like this, each step calling the next if successful @@ -43,6 +54,9 @@ type Project struct { filename string poll time.Duration + ds *datastore.Store + stage string + version string } const projectTemplateFilename = "template.project.json" @@ -91,36 +105,55 @@ func prepTemplateProject() error { return nil } -func (p *Project) load() error { +func (p *Project) errHandled(err error) bool { + if err == nil { + return false + } + + if p.ds == nil { + log.Printf("Error in project %s: %s", p.filename, err) + return true + } + + p.ds.AddLog(p.version, p.stage, err.Error()) + + return true +} + +func (p *Project) load() { + if p.filename == "" { - return fmt.Errorf("Invalid project file name") + p.errHandled(errors.New("Invalid project file name")) + return } if !projects.exists(p.filename) { // project has been deleted // don't continue polling // TODO: Clean up Project data folder? - return nil + return } data, err := ioutil.ReadFile(filepath.Join(projectDir, enabledProjectDir, p.filename)) - if err != nil { - return err - } - err = json.Unmarshal(data, p) - if err != nil { - return err + if p.errHandled(err) { + return } - err = p.prepData() - if err != nil { - return err + if p.errHandled(json.Unmarshal(data, p)) { + return } + p.stage = stageLoad + + if p.errHandled(p.prepData()) { + return + } + + //TODO: call fetch + if p.PollInterval != "" { p.poll, err = time.ParseDuration(p.PollInterval) - if err != nil { - //TODO: Log pollInterval parse failure in project store + if p.errHandled(err) { p.poll = 0 } } @@ -129,7 +162,6 @@ func (p *Project) load() error { //start polling } - return nil } // prepData makes sure the project's data folder and data store is created @@ -139,15 +171,20 @@ func (p *Project) load() error { */ func (p *Project) prepData() error { - var dirName = strings.TrimSuffix(p.filename, filepath.Ext(p.filename)) - err := os.MkdirAll(filepath.Join(dataDir, dirName), 0777) + var name = strings.TrimSuffix(p.filename, filepath.Ext(p.filename)) + var dir = filepath.Join(dataDir, name) + err := os.MkdirAll(dir, 0777) if err != nil { return err } - //TODO: Create data store + p.ds, err = datastore.Open(filepath.Join(dir, name+".ironsmith")) - return errors.New("TODO") + if err != nil { + return err + } + + return nil } type projectList struct { @@ -181,20 +218,27 @@ func (p *projectList) load() error { for i := range files { if !files[i].IsDir() && filepath.Ext(files[i].Name()) == ".json" { - prj := &Project{filename: files[i].Name()} + prj := &Project{ + filename: files[i].Name(), + Name: files[i].Name(), + version: "starting up", + stage: stageLoad, + } p.data[files[i].Name()] = prj - err = prj.load() - if err != nil { - delete(p.data, files[i].Name()) - return err - } + prj.load() } } return nil } +func (p *projectList) remove(name string) { + p.Lock() + delete(p.data, name) + p.Unlock() +} + func (p *projectList) exists(name string) bool { p.RLock() defer p.RUnlock()