Fixed several issues, contineued web work
Fixed lots of issues with thread saftey and had to rethink some stuff Fixed order issues with timekeys Starting to flesh out the web REST API
This commit is contained in:
parent
7d2fa0a6ef
commit
b9c945ed92
129
cycle.go
129
cycle.go
@ -5,12 +5,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -20,83 +17,6 @@ import (
|
|||||||
"git.townsourced.com/ironsmith/datastore"
|
"git.townsourced.com/ironsmith/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Project) errHandled(err error) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
vlog("Error in project %s: %s\n", p.id(), err)
|
|
||||||
|
|
||||||
if p.ds == nil {
|
|
||||||
log.Printf("Error in project %s: %s\n", p.id(), err)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
//clean up version folder if it exists
|
|
||||||
|
|
||||||
if p.version != "" {
|
|
||||||
err = os.RemoveAll(p.workingDir())
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error deleting the version directory project %s version %s: %s\n",
|
|
||||||
p.id(), p.version, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
lerr := p.ds.AddLog(p.version, p.stage, err.Error())
|
|
||||||
if lerr != nil {
|
|
||||||
log.Printf("Error logging an error in project %s: Original error %s, Logging Error: %s",
|
|
||||||
p.id(), err, lerr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Project) id() string {
|
|
||||||
if p.filename == "" {
|
|
||||||
panic("invalid project filename")
|
|
||||||
}
|
|
||||||
return strings.TrimSuffix(p.filename, filepath.Ext(p.filename))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Project) dir() string {
|
|
||||||
return filepath.Join(dataDir, p.id())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Project) workingDir() string {
|
|
||||||
if p.hash == "" {
|
|
||||||
panic(fmt.Sprintf("Working dir called with no version hash set for project %s", p.id()))
|
|
||||||
}
|
|
||||||
|
|
||||||
//It's probably overkill to use a sha1 hash to identify the build folder, when putting a simple
|
|
||||||
// timestamp on instead would work just fine, but I like having the working dir tied directly to the
|
|
||||||
// version returned by project script
|
|
||||||
|
|
||||||
return filepath.Join(p.dir(), p.hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepData makes sure the project's data folder and data store is created
|
|
||||||
/*
|
|
||||||
folder structure
|
|
||||||
projectDataFolder/<project-name>/<project-version>
|
|
||||||
|
|
||||||
*/
|
|
||||||
func (p *Project) prepData() error {
|
|
||||||
err := os.MkdirAll(p.dir(), 0777)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ds, err = datastore.Open(filepath.Join(p.dir(), p.id()+".ironsmith"))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Project life cycle:
|
Project life cycle:
|
||||||
(Load Project file) -> (Fetch) -> (Build) -> (Test) -> (Release) - > (Sleep for polling period) ->
|
(Load Project file) -> (Fetch) -> (Build) -> (Test) -> (Release) - > (Sleep for polling period) ->
|
||||||
@ -107,20 +27,19 @@ Project life cycle:
|
|||||||
// load is the beginning of the cycle. Loads / reloads the project file to make sure that the scripts are up-to-date
|
// load is the beginning of the cycle. Loads / reloads the project file to make sure that the scripts are up-to-date
|
||||||
// call's fetch and triggers the next poll if one exists
|
// call's fetch and triggers the next poll if one exists
|
||||||
func (p *Project) load() {
|
func (p *Project) load() {
|
||||||
p.version = ""
|
p.setStage(stageLoad)
|
||||||
p.hash = ""
|
p.setVersion("")
|
||||||
|
|
||||||
vlog("Entering %s stage for Project: %s\n", stageLoad, p.id())
|
|
||||||
|
|
||||||
if p.filename == "" {
|
if p.filename == "" {
|
||||||
p.errHandled(errors.New("Invalid project file name"))
|
p.errHandled(errors.New("Invalid project file name"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !projects.exists(p.filename) {
|
if _, ok := projects.get(p.id()); !ok {
|
||||||
// project has been deleted
|
// project has been deleted
|
||||||
// don't continue polling
|
// don't continue polling
|
||||||
// move project data to deleted folder with a timestamp
|
// move project data to deleted folder with a timestamp
|
||||||
|
p.close()
|
||||||
p.errHandled(os.Rename(p.dir(), filepath.Join(dataDir, deletedProjectDir,
|
p.errHandled(os.Rename(p.dir(), filepath.Join(dataDir, deletedProjectDir,
|
||||||
strconv.FormatInt(time.Now().Unix(), 10), p.id())))
|
strconv.FormatInt(time.Now().Unix(), 10), p.id())))
|
||||||
return
|
return
|
||||||
@ -131,30 +50,19 @@ func (p *Project) load() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.errHandled(json.Unmarshal(data, p)) {
|
new := &Project{}
|
||||||
|
if p.errHandled(json.Unmarshal(data, new)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.stage = stageLoad
|
p.setData(new)
|
||||||
|
|
||||||
if p.errHandled(p.prepData()) {
|
if p.errHandled(os.MkdirAll(p.dir(), 0777)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.PollInterval != "" {
|
|
||||||
p.poll, err = time.ParseDuration(p.PollInterval)
|
|
||||||
if p.errHandled(err) {
|
|
||||||
p.poll = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.fetch()
|
p.fetch()
|
||||||
|
|
||||||
if p.errHandled(p.ds.Close()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.ds = nil
|
|
||||||
|
|
||||||
//full cycle completed
|
//full cycle completed
|
||||||
|
|
||||||
if p.poll > 0 {
|
if p.poll > 0 {
|
||||||
@ -167,9 +75,8 @@ func (p *Project) load() {
|
|||||||
// then it runs the version script in the temp directory to see if there is a newer version of the
|
// then it runs the version script in the temp directory to see if there is a newer version of the
|
||||||
// fetched code, if there is then the temp dir is renamed to the version name
|
// fetched code, if there is then the temp dir is renamed to the version name
|
||||||
func (p *Project) fetch() {
|
func (p *Project) fetch() {
|
||||||
p.stage = stageFetch
|
p.setStage(stageFetch)
|
||||||
|
|
||||||
vlog("Entering %s stage for Project: %s\n", p.stage, p.id())
|
|
||||||
tempDir := filepath.Join(p.dir(), strconv.FormatInt(time.Now().Unix(), 10))
|
tempDir := filepath.Join(p.dir(), strconv.FormatInt(time.Now().Unix(), 10))
|
||||||
|
|
||||||
if p.errHandled(os.MkdirAll(tempDir, 0777)) {
|
if p.errHandled(os.MkdirAll(tempDir, 0777)) {
|
||||||
@ -189,9 +96,9 @@ func (p *Project) fetch() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.version = strings.TrimSpace(string(version))
|
p.setVersion(strings.TrimSpace(string(version)))
|
||||||
|
|
||||||
lVer, err := p.ds.LatestVersion()
|
lVer, err := p.ds.LastVersion("")
|
||||||
if err != datastore.ErrNotFound && p.errHandled(err) {
|
if err != datastore.ErrNotFound && p.errHandled(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -204,8 +111,6 @@ func (p *Project) fetch() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.hash = fmt.Sprintf("%x", sha1.Sum([]byte(p.version)))
|
|
||||||
|
|
||||||
//remove any existing data that matches version hash
|
//remove any existing data that matches version hash
|
||||||
if p.errHandled(os.RemoveAll(p.workingDir())) {
|
if p.errHandled(os.RemoveAll(p.workingDir())) {
|
||||||
return
|
return
|
||||||
@ -225,14 +130,12 @@ func (p *Project) fetch() {
|
|||||||
|
|
||||||
// continue to build
|
// continue to build
|
||||||
p.build()
|
p.build()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// build runs the build scripts to build the project which should result in the a single file
|
// build runs the build scripts to build the project which should result in the a single file
|
||||||
// configured in the ReleaseFile section of the project file
|
// configured in the ReleaseFile section of the project file
|
||||||
func (p *Project) build() {
|
func (p *Project) build() {
|
||||||
p.stage = stageBuild
|
p.setStage(stageBuild)
|
||||||
vlog("Entering %s stage for Project: %s Version: %s\n", p.stage, p.id(), p.version)
|
|
||||||
|
|
||||||
output, err := runCmd(p.Build, p.workingDir())
|
output, err := runCmd(p.Build, p.workingDir())
|
||||||
|
|
||||||
@ -250,9 +153,7 @@ func (p *Project) build() {
|
|||||||
|
|
||||||
// test runs the test scripts
|
// test runs the test scripts
|
||||||
func (p *Project) test() {
|
func (p *Project) test() {
|
||||||
p.stage = stageTest
|
p.setStage(stageTest)
|
||||||
vlog("Entering %s stage for Project: %s Version: %s\n", p.stage, p.id(), p.version)
|
|
||||||
|
|
||||||
output, err := runCmd(p.Test, p.workingDir())
|
output, err := runCmd(p.Test, p.workingDir())
|
||||||
|
|
||||||
if p.errHandled(err) {
|
if p.errHandled(err) {
|
||||||
@ -265,13 +166,11 @@ func (p *Project) test() {
|
|||||||
|
|
||||||
// Tests passed, onto release
|
// Tests passed, onto release
|
||||||
p.release()
|
p.release()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// release runs the release scripts and builds the release file
|
// release runs the release scripts and builds the release file
|
||||||
func (p *Project) release() {
|
func (p *Project) release() {
|
||||||
p.stage = stageRelease
|
p.setStage(stageRelease)
|
||||||
vlog("Entering %s stage for Project: %s Version: %s\n", p.stage, p.id(), p.version)
|
|
||||||
|
|
||||||
output, err := runCmd(p.Release, p.workingDir())
|
output, err := runCmd(p.Release, p.workingDir())
|
||||||
|
|
||||||
|
@ -29,10 +29,6 @@ func NewTimeKey() TimeKey {
|
|||||||
nsec := t.Nanosecond()
|
nsec := t.Nanosecond()
|
||||||
|
|
||||||
return TimeKey{
|
return TimeKey{
|
||||||
rBits[0], //random
|
|
||||||
rBits[1],
|
|
||||||
rBits[2],
|
|
||||||
rBits[3],
|
|
||||||
byte(sec >> 56), // seconds
|
byte(sec >> 56), // seconds
|
||||||
byte(sec >> 48),
|
byte(sec >> 48),
|
||||||
byte(sec >> 40),
|
byte(sec >> 40),
|
||||||
@ -45,12 +41,16 @@ func NewTimeKey() TimeKey {
|
|||||||
byte(nsec >> 16),
|
byte(nsec >> 16),
|
||||||
byte(nsec >> 8),
|
byte(nsec >> 8),
|
||||||
byte(nsec),
|
byte(nsec),
|
||||||
|
rBits[0], //random
|
||||||
|
rBits[1],
|
||||||
|
rBits[2],
|
||||||
|
rBits[3],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time returns the time portion of a timekey
|
// Time returns the time portion of a timekey
|
||||||
func (k TimeKey) Time() time.Time {
|
func (k TimeKey) Time() time.Time {
|
||||||
buf := k[4:]
|
buf := k[:]
|
||||||
|
|
||||||
sec := int64(buf[7]) | int64(buf[6])<<8 | int64(buf[5])<<16 | int64(buf[4])<<24 |
|
sec := int64(buf[7]) | int64(buf[6])<<8 | int64(buf[5])<<16 | int64(buf[4])<<24 |
|
||||||
int64(buf[3])<<32 | int64(buf[2])<<40 | int64(buf[1])<<48 | int64(buf[0])<<56
|
int64(buf[3])<<32 | int64(buf[2])<<40 | int64(buf[1])<<48 | int64(buf[0])<<56
|
||||||
|
@ -11,11 +11,12 @@ import (
|
|||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type log struct {
|
// Log is a version log entry for a project
|
||||||
When time.Time `json:"when"`
|
type Log struct {
|
||||||
Version string `json:"version"`
|
When time.Time `json:"when,omitempty"`
|
||||||
Stage string `json:"stage"`
|
Version string `json:"version,omitempty"`
|
||||||
Log string `json:"log"`
|
Stage string `json:"stage,omitempty"`
|
||||||
|
Log string `json:"log,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const bucketLog = "log"
|
const bucketLog = "log"
|
||||||
@ -24,7 +25,7 @@ const bucketLog = "log"
|
|||||||
func (ds *Store) AddLog(version, stage, entry string) error {
|
func (ds *Store) AddLog(version, stage, entry string) error {
|
||||||
key := NewTimeKey()
|
key := NewTimeKey()
|
||||||
|
|
||||||
data := &log{
|
data := &Log{
|
||||||
When: key.Time(),
|
When: key.Time(),
|
||||||
Version: version,
|
Version: version,
|
||||||
Stage: stage,
|
Stage: stage,
|
||||||
@ -34,23 +35,26 @@ func (ds *Store) AddLog(version, stage, entry string) error {
|
|||||||
return ds.put(bucketLog, key, data)
|
return ds.put(bucketLog, key, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LatestVersion returns the latest version (successful or otherwise) for the current project
|
// LastVersion returns the last version in the log for the given stage. If stage is blank,
|
||||||
func (ds *Store) LatestVersion() (string, error) {
|
// then it returns the last of any stage
|
||||||
|
func (ds *Store) LastVersion(stage string) (string, error) {
|
||||||
version := ""
|
version := ""
|
||||||
|
|
||||||
err := ds.bolt.View(func(tx *bolt.Tx) error {
|
err := ds.bolt.View(func(tx *bolt.Tx) error {
|
||||||
c := tx.Bucket([]byte(bucketLog)).Cursor()
|
c := tx.Bucket([]byte(bucketLog)).Cursor()
|
||||||
|
|
||||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||||
l := &log{}
|
l := &Log{}
|
||||||
err := json.Unmarshal(v, l)
|
err := json.Unmarshal(v, l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.Version != "" {
|
if l.Version != "" {
|
||||||
version = l.Version
|
if stage == "" || l.Stage == stage {
|
||||||
return nil
|
version = l.Version
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,3 +67,38 @@ func (ds *Store) LatestVersion() (string, error) {
|
|||||||
|
|
||||||
return version, nil
|
return version, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Versions lists the versions in a given project, including the last stage that version got to
|
||||||
|
func (ds *Store) Versions() ([]*Log, error) {
|
||||||
|
var vers []*Log
|
||||||
|
|
||||||
|
err := ds.bolt.View(func(tx *bolt.Tx) error {
|
||||||
|
c := tx.Bucket([]byte(bucketLog)).Cursor()
|
||||||
|
|
||||||
|
var current = ""
|
||||||
|
|
||||||
|
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||||
|
l := &Log{}
|
||||||
|
err := json.Unmarshal(v, l)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return vers, nil
|
||||||
|
}
|
||||||
|
3
main.go
3
main.go
@ -37,7 +37,7 @@ func init() {
|
|||||||
go func() {
|
go func() {
|
||||||
for sig := range c {
|
for sig := range c {
|
||||||
if sig == os.Interrupt {
|
if sig == os.Interrupt {
|
||||||
projects.closeAll()
|
projects.stopAll()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,7 +91,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//start web server
|
//start web server
|
||||||
|
|
||||||
err = startServer()
|
err = startServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error Starting web server: %s", err)
|
log.Fatalf("Error Starting web server: %s", err)
|
||||||
|
238
project.go
238
project.go
@ -5,10 +5,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -51,8 +54,8 @@ type Project struct {
|
|||||||
Version string `json:"version"` //Script to generate the version num of the current build, should be indempotent
|
Version string `json:"version"` //Script to generate the version num of the current build, should be indempotent
|
||||||
|
|
||||||
ReleaseFile string `json:"releaseFile"`
|
ReleaseFile string `json:"releaseFile"`
|
||||||
PollInterval string `json:"pollInterval"` // if not poll interval is specified, this project is trigger only
|
PollInterval string `json:"pollInterval,omitempty"` // if not poll interval is specified, this project is trigger only
|
||||||
TriggerSecret string `json:"triggerSecret"` //secret to be included with a trigger call
|
TriggerSecret string `json:"triggerSecret,omitempty"` //secret to be included with a trigger call
|
||||||
|
|
||||||
filename string
|
filename string
|
||||||
poll time.Duration
|
poll time.Duration
|
||||||
@ -60,6 +63,195 @@ type Project struct {
|
|||||||
stage string
|
stage string
|
||||||
version string
|
version string
|
||||||
hash string
|
hash string
|
||||||
|
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) errHandled(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
vlog("Error in project %s: %s\n", p.id(), err)
|
||||||
|
|
||||||
|
if p.ds == nil {
|
||||||
|
log.Printf("Error in project %s: %s\n", p.id(), err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
//clean up version folder if it exists
|
||||||
|
if p.version != "" {
|
||||||
|
err = os.RemoveAll(p.workingDir())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error deleting the version directory project %s version %s: %s\n",
|
||||||
|
p.id(), p.version, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
lerr := p.ds.AddLog(p.version, p.stage, err.Error())
|
||||||
|
if lerr != nil {
|
||||||
|
log.Printf("Error logging an error in project %s: Original error %s, Logging Error: %s",
|
||||||
|
p.id(), err, lerr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func projectID(filename string) string {
|
||||||
|
return strings.TrimSuffix(filename, filepath.Ext(filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) id() string {
|
||||||
|
if p.filename == "" {
|
||||||
|
panic("invalid project filename")
|
||||||
|
}
|
||||||
|
return projectID(p.filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) dir() string {
|
||||||
|
return filepath.Join(dataDir, p.id())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) workingDir() string {
|
||||||
|
if p.hash == "" {
|
||||||
|
panic(fmt.Sprintf("Working dir called with no version hash set for project %s", p.id()))
|
||||||
|
}
|
||||||
|
|
||||||
|
//It's probably overkill to use a sha1 hash to identify the build folder, when putting a simple
|
||||||
|
// timestamp on instead would work just fine, but I like having the working dir tied directly to the
|
||||||
|
// version returned by project script
|
||||||
|
|
||||||
|
return filepath.Join(p.dir(), p.hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepData makes sure the project's data folder and data store is created
|
||||||
|
/*
|
||||||
|
folder structure
|
||||||
|
projectDataFolder/<project-name>/<project-version>
|
||||||
|
|
||||||
|
*/
|
||||||
|
func (p *Project) open() error {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
|
||||||
|
if p.ds != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ds, err := datastore.Open(filepath.Join(p.dir(), p.id()+".ironsmith"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ds = ds
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) setVersion(version string) {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
|
||||||
|
p.version = version
|
||||||
|
if version == "" {
|
||||||
|
p.hash = ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.hash = fmt.Sprintf("%x", sha1.Sum([]byte(version)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) setStage(stage string) {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
|
||||||
|
if p.version != "" {
|
||||||
|
vlog("Entering %s stage for Project: %s Version: %s\n", p.stage, p.id(), p.version)
|
||||||
|
} else {
|
||||||
|
vlog("Entering %s stage for Project: %s\n", p.stage, p.id())
|
||||||
|
}
|
||||||
|
|
||||||
|
p.stage = stage
|
||||||
|
}
|
||||||
|
|
||||||
|
type webProject struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ReleaseVersion string `json:"releaseVersion"` //last successfully released version
|
||||||
|
LastVersion string `json:"lastVersion"` //last version success or otherwise
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) webData() (*webProject, error) {
|
||||||
|
p.RLock()
|
||||||
|
defer p.RUnlock()
|
||||||
|
|
||||||
|
last, err := p.ds.LastVersion("")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := p.ds.LastVersion(stageRelease)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d := &webProject{
|
||||||
|
Name: p.Name,
|
||||||
|
ID: p.id(),
|
||||||
|
LastVersion: last,
|
||||||
|
ReleaseVersion: release,
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) versions() ([]*datastore.Log, error) {
|
||||||
|
p.RLock()
|
||||||
|
defer p.RUnlock()
|
||||||
|
|
||||||
|
return p.ds.Versions()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) setData(new *Project) {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
|
||||||
|
p.Name = new.Name
|
||||||
|
|
||||||
|
p.Fetch = new.Fetch
|
||||||
|
p.Build = new.Build
|
||||||
|
p.Test = new.Test
|
||||||
|
p.Release = new.Release
|
||||||
|
p.Version = new.Version
|
||||||
|
|
||||||
|
p.ReleaseFile = new.ReleaseFile
|
||||||
|
p.PollInterval = new.PollInterval
|
||||||
|
p.TriggerSecret = new.TriggerSecret
|
||||||
|
|
||||||
|
if p.PollInterval != "" {
|
||||||
|
var err error
|
||||||
|
p.poll, err = time.ParseDuration(p.PollInterval)
|
||||||
|
if p.errHandled(err) {
|
||||||
|
p.poll = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Project) close() error {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
if p.ds == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := p.ds.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ds = nil
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectTemplateFilename = "template.project.json"
|
const projectTemplateFilename = "template.project.json"
|
||||||
@ -147,12 +339,12 @@ func (p *projectList) load() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *projectList) exists(name string) bool {
|
func (p *projectList) get(name string) (*Project, bool) {
|
||||||
p.RLock()
|
p.RLock()
|
||||||
defer p.RUnlock()
|
defer p.RUnlock()
|
||||||
|
|
||||||
_, ok := p.data[name]
|
prj, ok := p.data[name]
|
||||||
return ok
|
return prj, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *projectList) add(name string) {
|
func (p *projectList) add(name string) {
|
||||||
@ -165,9 +357,14 @@ func (p *projectList) add(name string) {
|
|||||||
Name: name,
|
Name: name,
|
||||||
stage: stageLoad,
|
stage: stageLoad,
|
||||||
}
|
}
|
||||||
p.data[name] = prj
|
p.data[projectID(name)] = prj
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
err := prj.open()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error opening datastore for Project: %s Error: %s\n", prj.id(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
prj.load()
|
prj.load()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -180,7 +377,7 @@ func (p *projectList) removeMissing(names []string) {
|
|||||||
for i := range p.data {
|
for i := range p.data {
|
||||||
found := false
|
found := false
|
||||||
for k := range names {
|
for k := range names {
|
||||||
if names[k] == i {
|
if projectID(names[k]) == i {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,17 +389,36 @@ func (p *projectList) removeMissing(names []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *projectList) closeAll() {
|
func (p *projectList) stopAll() {
|
||||||
p.RLock()
|
p.RLock()
|
||||||
defer p.RUnlock()
|
defer p.RUnlock()
|
||||||
|
|
||||||
for i := range p.data {
|
for i := range p.data {
|
||||||
if p.data[i].ds != nil {
|
err := p.data[i].close()
|
||||||
_ = p.data[i].ds.Close()
|
if err != nil {
|
||||||
|
log.Printf("Error closing project datastore for Project: %s Error: %s\n", p.data[i].id(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *projectList) webList() ([]*webProject, error) {
|
||||||
|
p.RLock()
|
||||||
|
defer p.RUnlock()
|
||||||
|
|
||||||
|
list := make([]*webProject, 0, len(p.data))
|
||||||
|
|
||||||
|
for i := range p.data {
|
||||||
|
prj, err := p.data[i].webData()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
list = append(list, prj)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
// startProjectLoader polls for new projects
|
// startProjectLoader polls for new projects
|
||||||
func startProjectLoader() {
|
func startProjectLoader() {
|
||||||
dir, err := os.Open(filepath.Join(projectDir, enabledProjectDir))
|
dir, err := os.Open(filepath.Join(projectDir, enabledProjectDir))
|
||||||
@ -228,7 +444,7 @@ func startProjectLoader() {
|
|||||||
for i := range files {
|
for i := range files {
|
||||||
if !files[i].IsDir() && filepath.Ext(files[i].Name()) == ".json" {
|
if !files[i].IsDir() && filepath.Ext(files[i].Name()) == ".json" {
|
||||||
names[i] = files[i].Name()
|
names[i] = files[i].Name()
|
||||||
if !projects.exists(files[i].Name()) {
|
if _, ok := projects.get(projectID(files[i].Name())); !ok {
|
||||||
projects.add(files[i].Name())
|
projects.add(files[i].Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ Routes
|
|||||||
|
|
||||||
/project/ - list all projects
|
/project/ - list all projects
|
||||||
/project/<project-id> - list all versions in a project, triggers new builds
|
/project/<project-id> - list all versions in a project, triggers new builds
|
||||||
/project/<project-id>/<version> - list combined output of all stages
|
/project/<project-id>/<version> - list combined output of all stages for a given version
|
||||||
/project/<project-id>/<version>/<stage. - list output of a given stage of a given version
|
/project/<project-id>/<version>/<stage. - list output of a given stage of a given version
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
@ -5,13 +5,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// /project/<project-id>/<version>/<stage>
|
// /path/<project-id>/<version>/<stage>
|
||||||
func splitPath(path string) (project, version, stage string) {
|
func splitPath(path string) (project, version, stage string) {
|
||||||
s := strings.Split(path, "/")
|
s := strings.Split(path, "/")
|
||||||
if len(s) < 3 {
|
if len(s) < 3 {
|
||||||
@ -33,8 +32,46 @@ func splitPath(path string) (project, version, stage string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /project/*
|
||||||
func projectGet(w http.ResponseWriter, r *http.Request) {
|
func projectGet(w http.ResponseWriter, r *http.Request) {
|
||||||
prj, ver, stg := splitPath(r.URL.Path)
|
prj, ver, _ := splitPath(r.URL.Path)
|
||||||
|
|
||||||
|
//values := r.URL.Query()
|
||||||
|
|
||||||
|
if prj == "" {
|
||||||
|
//get all projects
|
||||||
|
pList, err := projects.webList()
|
||||||
|
if errHandled(err, w) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondJsend(w, &JSend{
|
||||||
|
Status: statusSuccess,
|
||||||
|
Data: pList,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
project, ok := projects.get(prj)
|
||||||
|
if !ok {
|
||||||
|
four04(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//project found
|
||||||
|
|
||||||
|
if ver == "" {
|
||||||
|
//list versions
|
||||||
|
vers, err := project.versions()
|
||||||
|
if errHandled(err, w) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondJsend(w, &JSend{
|
||||||
|
Status: statusSuccess,
|
||||||
|
Data: vers,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("Project: %s Version: %s Stage: %s\n", prj, ver, stg)
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user