Skip to content

Commit

Permalink
cmd/relui: add relui webserver for releases
Browse files Browse the repository at this point in the history
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 <alex@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
  • Loading branch information
toothrot committed Jul 29, 2020
1 parent 7478086 commit add5b11
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 0 deletions.
12 changes: 12 additions & 0 deletions cmd/relui/README.md
@@ -0,0 +1,12 @@
#golang.org/x/build/cmd/relui

```
▀▀█ ▀
▄ ▄▄ ▄▄▄ █ ▄ ▄ ▄▄▄
█▀ ▀ █▀ █ █ █ █ █
█ █▀▀▀▀ █ █ █ █
█ ▀█▄▄▀ ▀▄▄ ▀▄▄▀█ ▄▄█▄▄
```

relui is a web interface for managing the release process of Go.

35 changes: 35 additions & 0 deletions cmd/relui/index.html
@@ -0,0 +1,35 @@
<!--
Copyright 2020 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<!DOCTYPE html>
<html lang="en">
<title>Go Releases</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css" />
<body class="Site">
<header class="Site-header">
<div class="Header">
<h1 class="Header-title">Go Releases</h1>
</div>
</header>
<main class="Site-content">
<section class="Workflows">
<h2>Workflows</h2>
<ul class="WorkflowList">
<li class="WorkflowList-item">
<h3>Local Release - 20a838ab</h3>
<form> </form>
<ul class="TaskList">
<li class="TaskList-item">
<h4>Fetch blob - 20a838ab</h4>
Status: created
</li>
</ul>
</li>
</ul>
</section>
</main>
</body>
</html>
22 changes: 22 additions & 0 deletions cmd/relui/main.go
@@ -0,0 +1,22 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"log"
"net/http"
"os"
)

func main() {
http.Handle("/", fileServerHandler(relativeFile("./static"), http.HandlerFunc(homeHandler)))
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

log.Printf("Listening on :" + port)
log.Fatal(http.ListenAndServe(":"+port, http.DefaultServeMux))
}
53 changes: 53 additions & 0 deletions cmd/relui/static/styles.css
@@ -0,0 +1,53 @@
/*
* Copyright 2020 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
*,
:before,
:after {
box-sizing: border-box;
}
html,
.Site {
height: 100%;
}
.Site {
display: flex;
flex-direction: column;
font-family: sans-serif;
margin: 0;
}
h1,
h2 {
font-weight: 600;
letter-spacing: 0.03rem;
}

h3,
h4 {
font-weight: 600;
letter-spacing: 0.08rem;
}
h5,
h6 {
font-weight: 500;
letter-spacing: 0.08rem;
}
.Site-content {
flex: 1 0 auto;
padding: 0.625rem;
width: 100%;
}
.Site-header {
flex: none;
}
.Header {
background: #e0ebf5;
color: #375eab;
padding: 0.625rem;
}
.Header-title {
font-size: 1.5rem;
margin: 0;
}
1 change: 1 addition & 0 deletions cmd/relui/testing/test.css
@@ -0,0 +1 @@
.Header { font-size: 10rem; }
54 changes: 54 additions & 0 deletions cmd/relui/web.go
@@ -0,0 +1,54 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"html/template"
"log"
"mime"
"net/http"
"os"
"path"
"path/filepath"
)

func fileServerHandler(root string, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
next.ServeHTTP(w, r)
return
}
if _, err := os.Stat(path.Join(root, r.URL.Path)); os.IsNotExist(err) {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(r.URL.Path)))
w.Header().Set("Cache-Control", "no-cache, private, max-age=0")

fs := http.FileServer(http.Dir(root))
fs.ServeHTTP(w, r)
})
}

var homeTemplate = template.Must(template.ParseFiles(relativeFile("index.html")))

func homeHandler(w http.ResponseWriter, _ *http.Request) {
if err := homeTemplate.Execute(w, nil); err != nil {
log.Printf("homeHandlerFunc: %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}

// relativeFile returns the path to the provided file or directory,
// conditionally prepending a relative path depending on the environment.
//
// In tests the current directory is ".", but the command may be running from the module root.
func relativeFile(base string) string {
// Check to see if it is in "." first.
if _, err := os.Stat(base); err == nil {
return base
}
return filepath.Join("cmd/relui", base)
}
87 changes: 87 additions & 0 deletions cmd/relui/web_test.go
@@ -0,0 +1,87 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)

func TestHomeHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()

homeHandler(w, req)
resp := w.Result()

if resp.StatusCode != http.StatusOK {
t.Errorf("rep.StatusCode = %d, wanted %d", resp.StatusCode, http.StatusOK)
}
}

func TestFileServerHandler(t *testing.T) {
h := fileServerHandler("./testing", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Home"))
}))

cases := []struct {
desc string
path string
wantCode int
wantBody string
wantHeaders map[string]string
}{
{
desc: "fallback to next handler",
path: "/",
wantCode: http.StatusOK,
wantBody: "Home",
},
{
desc: "sets headers and returns file",
path: "/test.css",
wantCode: http.StatusOK,
wantBody: ".Header { font-size: 10rem; }\n",
wantHeaders: map[string]string{
"Content-Type": "text/css; charset=utf-8",
"Cache-Control": "no-cache, private, max-age=0",
},
},
{
desc: "handles missing file",
path: "/foo.js",
wantCode: http.StatusNotFound,
wantBody: "404 page not found\n",
},
}
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
req := httptest.NewRequest("GET", c.path, nil)
w := httptest.NewRecorder()

h.ServeHTTP(w, req)
resp := w.Result()
defer resp.Body.Close()

if resp.StatusCode != c.wantCode {
t.Errorf("rep.StatusCode = %d, wanted %d", resp.StatusCode, c.wantCode)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("resp.Body = _, %v, wanted no error", err)
}
if string(b) != c.wantBody {
t.Errorf("resp.Body = %q, %v, wanted %q, %v", b, err, c.wantBody, nil)
}
for k, v := range c.wantHeaders {
if resp.Header.Get(k) != v {
t.Errorf("resp.Header.Get(%q) = %q, wanted %q", k, resp.Header.Get(k), v)
}
}
})
}
}

0 comments on commit add5b11

Please sign in to comment.