From a7f800bb144e2f70eb20cb3220234322d486de7d Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 13 Apr 2015 17:35:47 -0400 Subject: [PATCH] initial commit --- .gitignore | 21 ++++++ README.md | 28 ++++++++ badges/failing.png | Bin 0 -> 1590 bytes badges/passing.png | Bin 0 -> 1836 bytes badges/pending.png | Bin 0 -> 1436 bytes config.json | 6 ++ yoctobuild.go | 172 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 227 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 badges/failing.png create mode 100644 badges/passing.png create mode 100644 badges/pending.png create mode 100644 config.json create mode 100644 yoctobuild.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e72462 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ac21b0 --- /dev/null +++ b/README.md @@ -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//build?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//badge` as an image +in your page. diff --git a/badges/failing.png b/badges/failing.png new file mode 100644 index 0000000000000000000000000000000000000000..37052285e6152973c27043c281342b8b05b6e2eb GIT binary patch literal 1590 zcmV-62Fdw}P)x_QZs9@D*w}bxYHDg5B7kiQXC$Vb7b?A>ZPlLVAL8oj>P}8iPw$V^LiQ>h zk(zp5@LGK%?{qpNMhb-lF_P1Vg~vIupZ9vb5nY6Y$j(0>6rw0XE+&%+B)t%9Ur5r@ z(vX{*%k*8lcEMt?g!rsqzaFKfr6G1HjOY@QkX3g8tLmSHg6G5fSPC{k7jK197sdR* zajZ@4;rt&|6p|!CE?TV?6%`c-wl5^f$;rsb$Y6R|SsC82(CPXErV8(VABj?*8dIj9Lcpkj%cSMN-dru_Kky`_}<8;9& zdRf$Im&Jio@`zcPjeFhK;ke$(d`&rPiR8gcNSDZ1j%!!o92#6Wm8hsxD%8}}z~k}Y z(4j+&khr)w)YjGpd`e16Sb9*sxVRYA)ztxFSR@cMG&CS5Cx;PKRaM38i=~os`3i6f z1=KnnG#V|GdIQwC`RKnhgoLW?NPqN6=2!GYEmGFz`%ah=Ol-7G?NnE#{0FOFd=2K! zRT%Yh`0tUoVOX&e=Cy^$K5!U6lf%}mE`YwU1okmU$T~tIhAy6-5+#HZL-nGfBDA)) z1~x-M``m6fYtzW_>%P+yS;Wy4+p%Luz-~61!$i#7cIW9Ykp6f*uD73oyX!}oG%8#< zc^oS;GtqbLDlAE9Y~fQL0ry-Uru&GISldYK)HdPvV1`7*hc7$>gNjG&ntUiyGa(YP zd#`rl-sK-~#biNlStSI!4JuwaXC1u%bmhd*n~K&61vxpI1{#ef5C#4x?UT2IbsBZ_ z-I$md=1V>x5gQwe;Q0{LPB-Ylk!PG%4KYfC%$<8s{{Kq++I|{pQ!E5Y{*LsQ&Ym)z z{7u+LQJiRICQk5Rn%H{VzQQB#)gbZZqfn=1qV=6OU{xwY))7*CsVJ37WM^kHhr+@_ z^!N8O_M@Ytnb1wsTS%TZa$@LnaBvU?gMmd31+7W`KDl_q2!#lrZro6Y&rTdiTjOy| zIVV{kwfp(;`!k!5kJ)~SUQ~fG{q|%+yqiS$CvP1^!}fJe5XAN72;Ogk#5!!Dh3u0`2dUL*RlU zX!hJik{SY$3#UHCV=oHI`KX(mvj(oRvU1MKC;`L6!>qRGNCBkjV6|FVI#4^keP~+Hs8Xxd ztZq0Q4pwvG#;u7dY)-b|x5;VTa7g@5A=f#ks_kmzReg*(~UW{Ix`Gqno61KUmCv zuX1eBF}$KLEfn`T=R(Lm{3cXx7bb6X!jO^hj&>x??w?YMv;a`S3QIoN29 z@~tC?2l^wjiKNS(kZW{_5U%+C)3E5&0iDtz%Jxpvm6OCKmbB!{_5)IMOO||v1Y7@Q o|EP-Q`SY({?%nri00030|7+xE>na5N)c^nh07*qoM6N<$f~8y!bpQYW literal 0 HcmV?d00001 diff --git a/badges/passing.png b/badges/passing.png new file mode 100644 index 0000000000000000000000000000000000000000..1110a786edb0a185f3d55988d033ccfe3ab1b4c3 GIT binary patch literal 1836 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 - yoctobuild", title) +} + +func writeFooter(w http.ResponseWriter) { + fmt.Fprintf(w, "") +} + +func projectIndex(w http.ResponseWriter, r *http.Request) { + writeHeader(w, "index") + fmt.Fprintf(w, "

Projects:

    ") + for name := range projects { + fmt.Fprintf(w, `
  • %s
  • `, name, name) + } + fmt.Fprintf(w, "
") + writeFooter(w) +} + +func projectStatus(w http.ResponseWriter, r *http.Request) { + name := getProject(r) + writeHeader(w, name) + fmt.Fprintf(w, `

`, name) + if p, ok := projects[name]; ok && p.err != nil { + fmt.Fprintf(w, "Last built: %s
\nError:
%s

\nOutput:
\n
%s
\n", p.time, p.err, p.out) + } else if ok && !p.time.IsZero() { + fmt.Fprintf(w, "Last built: %s
\nOutput:
\n
%s
\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)) +}