Skip to content

Commit add5b11

Browse files
committed
cmd/relui: add relui webserver for releases
This commit introduces relui, a prototype for running releases in a persistent server environment. The primary goals of using a long-running service for release management is to improve observability, scheduling, and testing of our release process. This change introduces a very basic webserver, and minimal styling. For golang/go#40279 Change-Id: I5958e5dc19e62df92b36c64583058cbcef8dd75c Reviewed-on: https://go-review.googlesource.com/c/build/+/243338 Run-TryBot: Alexander Rakoczy <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Andrew Bonventre <[email protected]> Reviewed-by: Carlos Amedee <[email protected]>
1 parent 7478086 commit add5b11

File tree

7 files changed

+264
-0
lines changed

7 files changed

+264
-0
lines changed

cmd/relui/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#golang.org/x/build/cmd/relui
2+
3+
```
4+
▀▀█ ▀
5+
▄ ▄▄ ▄▄▄ █ ▄ ▄ ▄▄▄
6+
█▀ ▀ █▀ █ █ █ █ █
7+
█ █▀▀▀▀ █ █ █ █
8+
█ ▀█▄▄▀ ▀▄▄ ▀▄▄▀█ ▄▄█▄▄
9+
```
10+
11+
relui is a web interface for managing the release process of Go.
12+

cmd/relui/index.html

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!--
2+
Copyright 2020 The Go Authors. All rights reserved.
3+
Use of this source code is governed by a BSD-style
4+
license that can be found in the LICENSE file.
5+
-->
6+
<!DOCTYPE html>
7+
<html lang="en">
8+
<title>Go Releases</title>
9+
<meta name="viewport" content="width=device-width, initial-scale=1" />
10+
<link rel="stylesheet" href="/styles.css" />
11+
<body class="Site">
12+
<header class="Site-header">
13+
<div class="Header">
14+
<h1 class="Header-title">Go Releases</h1>
15+
</div>
16+
</header>
17+
<main class="Site-content">
18+
<section class="Workflows">
19+
<h2>Workflows</h2>
20+
<ul class="WorkflowList">
21+
<li class="WorkflowList-item">
22+
<h3>Local Release - 20a838ab</h3>
23+
<form> </form>
24+
<ul class="TaskList">
25+
<li class="TaskList-item">
26+
<h4>Fetch blob - 20a838ab</h4>
27+
Status: created
28+
</li>
29+
</ul>
30+
</li>
31+
</ul>
32+
</section>
33+
</main>
34+
</body>
35+
</html>

cmd/relui/main.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"log"
9+
"net/http"
10+
"os"
11+
)
12+
13+
func main() {
14+
http.Handle("/", fileServerHandler(relativeFile("./static"), http.HandlerFunc(homeHandler)))
15+
port := os.Getenv("PORT")
16+
if port == "" {
17+
port = "8080"
18+
}
19+
20+
log.Printf("Listening on :" + port)
21+
log.Fatal(http.ListenAndServe(":"+port, http.DefaultServeMux))
22+
}

cmd/relui/static/styles.css

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2020 The Go Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style
4+
* license that can be found in the LICENSE file.
5+
*/
6+
*,
7+
:before,
8+
:after {
9+
box-sizing: border-box;
10+
}
11+
html,
12+
.Site {
13+
height: 100%;
14+
}
15+
.Site {
16+
display: flex;
17+
flex-direction: column;
18+
font-family: sans-serif;
19+
margin: 0;
20+
}
21+
h1,
22+
h2 {
23+
font-weight: 600;
24+
letter-spacing: 0.03rem;
25+
}
26+
27+
h3,
28+
h4 {
29+
font-weight: 600;
30+
letter-spacing: 0.08rem;
31+
}
32+
h5,
33+
h6 {
34+
font-weight: 500;
35+
letter-spacing: 0.08rem;
36+
}
37+
.Site-content {
38+
flex: 1 0 auto;
39+
padding: 0.625rem;
40+
width: 100%;
41+
}
42+
.Site-header {
43+
flex: none;
44+
}
45+
.Header {
46+
background: #e0ebf5;
47+
color: #375eab;
48+
padding: 0.625rem;
49+
}
50+
.Header-title {
51+
font-size: 1.5rem;
52+
margin: 0;
53+
}

cmd/relui/testing/test.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.Header { font-size: 10rem; }

cmd/relui/web.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"html/template"
9+
"log"
10+
"mime"
11+
"net/http"
12+
"os"
13+
"path"
14+
"path/filepath"
15+
)
16+
17+
func fileServerHandler(root string, next http.Handler) http.Handler {
18+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
19+
if r.URL.Path == "/" {
20+
next.ServeHTTP(w, r)
21+
return
22+
}
23+
if _, err := os.Stat(path.Join(root, r.URL.Path)); os.IsNotExist(err) {
24+
http.NotFound(w, r)
25+
return
26+
}
27+
w.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(r.URL.Path)))
28+
w.Header().Set("Cache-Control", "no-cache, private, max-age=0")
29+
30+
fs := http.FileServer(http.Dir(root))
31+
fs.ServeHTTP(w, r)
32+
})
33+
}
34+
35+
var homeTemplate = template.Must(template.ParseFiles(relativeFile("index.html")))
36+
37+
func homeHandler(w http.ResponseWriter, _ *http.Request) {
38+
if err := homeTemplate.Execute(w, nil); err != nil {
39+
log.Printf("homeHandlerFunc: %v", err)
40+
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
41+
}
42+
}
43+
44+
// relativeFile returns the path to the provided file or directory,
45+
// conditionally prepending a relative path depending on the environment.
46+
//
47+
// In tests the current directory is ".", but the command may be running from the module root.
48+
func relativeFile(base string) string {
49+
// Check to see if it is in "." first.
50+
if _, err := os.Stat(base); err == nil {
51+
return base
52+
}
53+
return filepath.Join("cmd/relui", base)
54+
}

cmd/relui/web_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"io/ioutil"
9+
"net/http"
10+
"net/http/httptest"
11+
"testing"
12+
)
13+
14+
func TestHomeHandler(t *testing.T) {
15+
req := httptest.NewRequest("GET", "/", nil)
16+
w := httptest.NewRecorder()
17+
18+
homeHandler(w, req)
19+
resp := w.Result()
20+
21+
if resp.StatusCode != http.StatusOK {
22+
t.Errorf("rep.StatusCode = %d, wanted %d", resp.StatusCode, http.StatusOK)
23+
}
24+
}
25+
26+
func TestFileServerHandler(t *testing.T) {
27+
h := fileServerHandler("./testing", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28+
w.Write([]byte("Home"))
29+
}))
30+
31+
cases := []struct {
32+
desc string
33+
path string
34+
wantCode int
35+
wantBody string
36+
wantHeaders map[string]string
37+
}{
38+
{
39+
desc: "fallback to next handler",
40+
path: "/",
41+
wantCode: http.StatusOK,
42+
wantBody: "Home",
43+
},
44+
{
45+
desc: "sets headers and returns file",
46+
path: "/test.css",
47+
wantCode: http.StatusOK,
48+
wantBody: ".Header { font-size: 10rem; }\n",
49+
wantHeaders: map[string]string{
50+
"Content-Type": "text/css; charset=utf-8",
51+
"Cache-Control": "no-cache, private, max-age=0",
52+
},
53+
},
54+
{
55+
desc: "handles missing file",
56+
path: "/foo.js",
57+
wantCode: http.StatusNotFound,
58+
wantBody: "404 page not found\n",
59+
},
60+
}
61+
for _, c := range cases {
62+
t.Run(c.desc, func(t *testing.T) {
63+
req := httptest.NewRequest("GET", c.path, nil)
64+
w := httptest.NewRecorder()
65+
66+
h.ServeHTTP(w, req)
67+
resp := w.Result()
68+
defer resp.Body.Close()
69+
70+
if resp.StatusCode != c.wantCode {
71+
t.Errorf("rep.StatusCode = %d, wanted %d", resp.StatusCode, c.wantCode)
72+
}
73+
b, err := ioutil.ReadAll(resp.Body)
74+
if err != nil {
75+
t.Errorf("resp.Body = _, %v, wanted no error", err)
76+
}
77+
if string(b) != c.wantBody {
78+
t.Errorf("resp.Body = %q, %v, wanted %q, %v", b, err, c.wantBody, nil)
79+
}
80+
for k, v := range c.wantHeaders {
81+
if resp.Header.Get(k) != v {
82+
t.Errorf("resp.Header.Get(%q) = %q, wanted %q", k, resp.Header.Get(k), v)
83+
}
84+
}
85+
})
86+
}
87+
}

0 commit comments

Comments
 (0)