initial commit
This commit is contained in:
commit
a7f800bb14
|
@ -0,0 +1,21 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
yoctobuild
|
|
@ -0,0 +1,28 @@
|
|||
# yoctoBuild
|
||||
|
||||
yoctoBuild is a bare-bones build service for your personal projects. Since many
|
||||
hosted build services do not work outside of GitHub and BitBucket, this was
|
||||
made to work with Gogs for light usage where hosting Jenkins would be absurd.
|
||||
|
||||
yoctoBuild is unaware of git, Mercurial, scss, etc. It depends on a bash
|
||||
executable and proper configuration.
|
||||
|
||||
## Usage
|
||||
|
||||
Run `yoctobuild` in its directory or copy the badges and a config file to a
|
||||
working directory. Provide a `-secret` on startup, and then set your Gogs/Git
|
||||
hooks/etc to send a request to `/projects/<your project>/build?secret=<your
|
||||
secret>`.
|
||||
|
||||
The config file consists of a map of projects and their steps. The build server
|
||||
does nothing but create a working directory for the build so the build steps
|
||||
must check out code, perform tests, and manage any dependencies. The config
|
||||
file should only be modified by owners of the server it lives on as it may
|
||||
execute arbitrary commands on your behalf.
|
||||
|
||||
This build service is very simple. It does not watch for branches or anything
|
||||
of the like. You must configure a project for each individual item that you
|
||||
wish to watch.
|
||||
|
||||
Badges may be served by embedding `/projects/<your project>/badge` as an image
|
||||
in your page.
|
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"test": {
|
||||
"before": "sleep 5; touch y",
|
||||
"after": "touch x"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var (
|
||||
configPath = flag.String("config", "./config.json", "Path to config file")
|
||||
badgePath = flag.String("badges", "./badges/", "Path to badges")
|
||||
addr = flag.String("addr", ":3001", "Address to serve on")
|
||||
secret = flag.String("secret", "12345", "Secret to authorize builds")
|
||||
|
||||
projects map[string]*project
|
||||
)
|
||||
|
||||
type project struct {
|
||||
Before string
|
||||
After string
|
||||
out string
|
||||
err error
|
||||
time time.Time
|
||||
}
|
||||
|
||||
func runBuild(name string) {
|
||||
steps := fmt.Sprintf("mkdir -p %s; cd %s; %s",
|
||||
name, name, projects[name].Before)
|
||||
script := bytes.NewBufferString(steps)
|
||||
projects[name].time = time.Time{}
|
||||
|
||||
bash := exec.Command("bash")
|
||||
stdin, _ := bash.StdinPipe()
|
||||
|
||||
io.Copy(stdin, script)
|
||||
stdin.Close()
|
||||
|
||||
out, err := bash.CombinedOutput()
|
||||
|
||||
projects[name].out = string(out)
|
||||
projects[name].err = err
|
||||
projects[name].time = time.Now()
|
||||
|
||||
if err == nil {
|
||||
runPostBuild(name)
|
||||
}
|
||||
}
|
||||
|
||||
func runPostBuild(name string) {
|
||||
steps := fmt.Sprintf("cd %s; %s",
|
||||
name, projects[name].After)
|
||||
script := bytes.NewBufferString(steps)
|
||||
|
||||
bash := exec.Command("bash")
|
||||
stdin, _ := bash.StdinPipe()
|
||||
|
||||
io.Copy(stdin, script)
|
||||
stdin.Close()
|
||||
|
||||
if out, err := bash.CombinedOutput(); err != nil {
|
||||
projects[name].out = string(out)
|
||||
projects[name].err = err
|
||||
}
|
||||
}
|
||||
|
||||
func readConfig() {
|
||||
if f, err := ioutil.ReadFile(*configPath); err != nil {
|
||||
log.Fatal("Could not access configuration.", err)
|
||||
} else {
|
||||
if err := json.Unmarshal(f, &projects); err != nil {
|
||||
log.Fatal("Could not read configuration.", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getProject(r *http.Request) string {
|
||||
vars := mux.Vars(r)
|
||||
return vars["project"]
|
||||
}
|
||||
|
||||
// TODO: Make these templates or something
|
||||
func writeHeader(w http.ResponseWriter, title string) {
|
||||
fmt.Fprintf(w, "<html><head><title>%s - yoctobuild</title></head><body>", title)
|
||||
}
|
||||
|
||||
func writeFooter(w http.ResponseWriter) {
|
||||
fmt.Fprintf(w, "<body></html>")
|
||||
}
|
||||
|
||||
func projectIndex(w http.ResponseWriter, r *http.Request) {
|
||||
writeHeader(w, "index")
|
||||
fmt.Fprintf(w, "<p>Projects:</p><ul>")
|
||||
for name := range projects {
|
||||
fmt.Fprintf(w, `<li><a href="/projects/%s">%s</a></li>`, name, name)
|
||||
}
|
||||
fmt.Fprintf(w, "</ul>")
|
||||
writeFooter(w)
|
||||
}
|
||||
|
||||
func projectStatus(w http.ResponseWriter, r *http.Request) {
|
||||
name := getProject(r)
|
||||
writeHeader(w, name)
|
||||
fmt.Fprintf(w, `<p><img src="/projects/%s/badge" /></p>`, name)
|
||||
if p, ok := projects[name]; ok && p.err != nil {
|
||||
fmt.Fprintf(w, "Last built: %s<br>\nError: <pre>%s</pre><br>\nOutput:<br>\n<pre>%s</pre>\n", p.time, p.err, p.out)
|
||||
} else if ok && !p.time.IsZero() {
|
||||
fmt.Fprintf(w, "Last built: %s<br>\nOutput:<br>\n<pre>%s</pre>\n", p.time, p.out)
|
||||
}
|
||||
writeFooter(w)
|
||||
}
|
||||
|
||||
func projectBadge(w http.ResponseWriter, r *http.Request) {
|
||||
name, file := getProject(r), ""
|
||||
|
||||
if p, ok := projects[name]; ok && p.err != nil {
|
||||
file = "failing.png"
|
||||
} else if ok && !p.time.IsZero() {
|
||||
file = "passing.png"
|
||||
} else {
|
||||
file = "pending.png"
|
||||
}
|
||||
|
||||
// http.ServeFile is neat, but it likes to write its own status codes that are wrong
|
||||
if f, err := os.Open(filepath.Join(*badgePath, file)); err != nil {
|
||||
http.NotFound(w, r)
|
||||
} else {
|
||||
w.Header().Set("Cache-Control", "no-cache, private")
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.WriteHeader(200)
|
||||
io.Copy(w, f)
|
||||
}
|
||||
}
|
||||
|
||||
func projectBuild(w http.ResponseWriter, r *http.Request) {
|
||||
name := getProject(r)
|
||||
|
||||
get, err := url.ParseQuery(r.URL.RawQuery)
|
||||
if err != nil || get.Get("secret") != *secret {
|
||||
w.WriteHeader(401)
|
||||
return
|
||||
}
|
||||
|
||||
go runBuild(name)
|
||||
fmt.Fprintf(w, "Build scheduled.\n")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
readConfig()
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.Handle("/", http.RedirectHandler("/projects", 301))
|
||||
r.HandleFunc("/projects", projectIndex)
|
||||
r.HandleFunc("/projects/{project}", projectStatus)
|
||||
r.HandleFunc("/projects/{project}/badge", projectBadge)
|
||||
r.HandleFunc("/projects/{project}/build", projectBuild)
|
||||
|
||||
log.Fatal(http.ListenAndServe(*addr, r))
|
||||
}
|
Loading…
Reference in New Issue