Skip to content
Snippets Groups Projects
Commit 1feda16e authored by ale's avatar ale
Browse files

Allow template customization

parent 521811cf
No related branches found
No related tags found
No related merge requests found
Pipeline #84654 passed
FROM golang:1.19 as build FROM golang:1.23 as build
ADD . /src ADD . /src
RUN cd /src && go build -tags netgo -o gitlab-review-float-env-dashboard ./main.go && strip gitlab-review-float-env-dashboard RUN cd /src && go build -tags netgo -o gitlab-review-float-env-dashboard ./main.go && strip gitlab-review-float-env-dashboard
......
gitlab-review-float-env-dashboard (0.2) unstable; urgency=medium
* Refactor template handling.
-- ale <ale@incal.net> Thu, 09 Jan 2025 08:59:46 +0100
gitlab-review-float-env-dashboard (0.1) unstable; urgency=medium gitlab-review-float-env-dashboard (0.1) unstable; urgency=medium
* First release. * First release.
......
...@@ -10,47 +10,28 @@ import ( ...@@ -10,47 +10,28 @@ import (
"io" "io"
"log" "log"
"net/http" "net/http"
"os"
) )
var ( var (
addr = flag.String("addr", ":3499", "address to listen on") addr = flag.String("addr", ":3499", "address to listen on")
jumpHost = flag.String("jump-host", "localhost", "SSH jump host") gitlabURL = flag.String("gitlab-url", "https://git.autistici.org", "Gitlab public URL")
hostname = flag.String("hostname", "", "(deprecated) see --jump-host")
hostConnFmt = flag.String(
"host-ssh-format",
"ssh -o UserKnownHostsFile=/dev/null -i ~/.vagrant.d/insecure_private_key -J {{.JumpHost}} root@{{.Host.IP}}",
"format string for host-specific SSH connections")
enableSOCKS = flag.Bool("enable-socks", false, "show SOCKS5 connection params")
title = flag.String("title", "VMine test cluster", "title string") title = flag.String("title", "VMine test cluster", "title string")
contentTplFile = flag.String("template", "", "custom content template file, use default if empty")
) )
var ( var (
dashTplSrc = `<!doctype html> defaultContentTplSrc = `
<html lang="en"> {{$jumpHost := "miscredenza.investici.org"}}
<head> {{$socksIP := (idx .Inventory 0).IP}}
<title>{{.Title}}</title>
<style type="text/css">
body { background: white; }
.name { font-size: 120%; }
.attrs { color: #666; font-size: 90%; }
.table { border: 0; width: 100%; }
.host {
padding-left: 40px;
background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHN0cm9rZS13aWR0aD0iMS41IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgY29sb3I9IiMwMDAwMDAiPjxwYXRoIGQ9Ik02IDE4LjAxbC4wMS0uMDExTTYgNi4wMWwuMDEtLjAxMSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48L3BhdGg+PHBhdGggZD0iTTIgOS40VjIuNmEuNi42IDAgMDEuNi0uNmgxOC44YS42LjYgMCAwMS42LjZ2Ni44YS42LjYgMCAwMS0uNi42SDIuNmEuNi42IDAgMDEtLjYtLjZ6TTIgMjEuNHYtNi44YS42LjYgMCAwMS42LS42aDE4LjhhLjYuNiAwIDAxLjYuNnY2LjhhLjYuNiAwIDAxLS42LjZIMi42YS42LjYgMCAwMS0uNi0uNnoiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIxLjUiPjwvcGF0aD48L3N2Zz4K") no-repeat left 5px;
}
</style>
</head>
<body>
<h1>{{.Title}}</h1>
<h4>Hosts</h4> <h4>Hosts</h4>
<p> <p>
You can connect to individual hosts over SSH via the jump host <i>{{.JumpHost}}</i>.<br> You can connect to individual hosts over SSH via the jump host <i>{{$jumpHost}}</i>.<br>
Download the required SSH private key here: Download the required SSH private key here:
<a href="https://github.com/hashicorp/vagrant/blob/main/keys/vagrant">Vagrant default insecure private key</a>, <a href="{{.GitlabURL}}/{{.ProjectName}}/-/jobs/{{.JobID}}/artifacts/browse/build-{{.JobID}}/ssh/vmkey">vmkey</a>.
if you do not already have it, and save it to <i>~/.vagrant.d/insecure_private_key</i>. Remember to <code>chmod 600</code> the private key file after you download it.
</p> </p>
<table class="table"> <table class="table">
...@@ -66,42 +47,68 @@ body { background: white; } ...@@ -66,42 +47,68 @@ body { background: white; }
</div> </div>
</td> </td>
<td> <td>
<pre>{{hostConn $host}}</pre> <pre>ssh -o UserKnownHostsFile=/dev/null -i vmkey -J {{$jumpHost}} root@{{$host.IP}}</pre>
</td> </td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>
</table> </table>
{{if .EnableSOCKS}}
<h4>HTTP connection (via SOCKS5 proxy)</h4> <h4>HTTP connection (via SOCKS5 proxy)</h4>
<p> <p>
Forward a local port to the SOCKS5 proxy running on {{.SocksIP}}, and run a Forward a local port to the SOCKS5 proxy running on {{$socksIP}}, and run a
private browser that uses it: private browser that uses it:
</p> </p>
<pre>ssh -nN -L 9051:{{.SocksIP}}:9051 {{.JumpHost}} & <pre>ssh -nN -L 9051:{{$socksIP}}:9051 {{$jumpHost}} &
chromium --user-data-dir=$(mktemp -d) --no-first-run --no-default-browser-check \ chromium --user-data-dir=$(mktemp -d) --no-first-run --no-default-browser-check \
--proxy-server=socks5://localhost:9051 https://... --proxy-server=socks5://localhost:9051 https://...
</pre> </pre>
{{end}} `
defaultContentTpl = template.Must(template.New("").Parse(defaultContentTplSrc))
dashTplSrc = `<!doctype html>
<html lang="en">
<head>
<title>{{.Title}}</title>
<style type="text/css">
body { background: white; }
.name { font-size: 120%; }
.attrs { color: #666; font-size: 90%; }
.table { border: 0; width: 100%; }
.host {
padding-left: 40px;
background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHN0cm9rZS13aWR0aD0iMS41IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgY29sb3I9IiMwMDAwMDAiPjxwYXRoIGQ9Ik02IDE4LjAxbC4wMS0uMDExTTYgNi4wMWwuMDEtLjAxMSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48L3BhdGg+PHBhdGggZD0iTTIgOS40VjIuNmEuNi42IDAgMDEuNi0uNmgxOC44YS42LjYgMCAwMS42LjZ2Ni44YS42LjYgMCAwMS0uNi42SDIuNmEuNi42IDAgMDEtLjYtLjZ6TTIgMjEuNHYtNi44YS42LjYgMCAwMS42LS42aDE4LjhhLjYuNiAwIDAxLjYuNnY2LjhhLjYuNiAwIDAxLS42LjZIMi42YS42LjYgMCAwMS0uNi0uNnoiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIxLjUiPjwvcGF0aD48L3N2Zz4K") no-repeat left 5px;
}
</style>
</head>
<body>
<h1>{{.Title}}</h1>
{{.Content}}
</body> </body>
</html>` </html>`
dashTpl = template.Must(template.New("").Funcs(map[string]interface{}{ dashTpl = template.Must(template.New("").Parse(dashTplSrc))
"hostConn": hostConnString,
}).Parse(dashTplSrc)) contentTpl *template.Template
hostConnTpl *template.Template
) )
func hostConnString(host *hostinfo) string { func parseTemplate() error {
var buf bytes.Buffer if *contentTplFile == "" {
hostConnTpl.Execute(&buf, map[string]interface{}{ contentTpl = defaultContentTpl
"JumpHost": *jumpHost, return nil
"Host": host, }
})
return buf.String() data, err := os.ReadFile(*contentTplFile)
if err != nil {
return err
}
contentTpl, err = template.New("").Parse(string(data))
return err
} }
type hostinfo struct { type hostinfo struct {
...@@ -112,6 +119,12 @@ type hostinfo struct { ...@@ -112,6 +119,12 @@ type hostinfo struct {
IP string `json:"ip"` IP string `json:"ip"`
} }
type payload struct {
Inventory []*hostinfo `json:"inv"`
JobID string `json:"job"`
ProjectName string `json:"proj"`
}
func decompress(input []byte) ([]byte, error) { func decompress(input []byte) ([]byte, error) {
var out bytes.Buffer var out bytes.Buffer
r := flate.NewReader(bytes.NewReader(input)) r := flate.NewReader(bytes.NewReader(input))
...@@ -120,7 +133,7 @@ func decompress(input []byte) ([]byte, error) { ...@@ -120,7 +133,7 @@ func decompress(input []byte) ([]byte, error) {
return out.Bytes(), err return out.Bytes(), err
} }
func decodePayload(encoded string) ([]*hostinfo, error) { func decodePayload(encoded string) (*payload, error) {
compressed, err := base64.URLEncoding.DecodeString(encoded) compressed, err := base64.URLEncoding.DecodeString(encoded)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -131,15 +144,18 @@ func decodePayload(encoded string) ([]*hostinfo, error) { ...@@ -131,15 +144,18 @@ func decodePayload(encoded string) ([]*hostinfo, error) {
return nil, err return nil, err
} }
var inventory []*hostinfo var p payload
err = json.Unmarshal(decompressed, &inventory) if err := json.Unmarshal(decompressed, &p); err != nil {
return inventory, err return nil, err
}
return &p, err
} }
func handleRequest(w http.ResponseWriter, req *http.Request) { func handleRequest(w http.ResponseWriter, req *http.Request) {
inventory, err := decodePayload(req.URL.Path) payload, err := decodePayload(req.URL.Path)
if err != nil { if err != nil {
log.Printf("error decoding payload: %s", err) log.Printf("error decoding payload: %v", err)
http.Error(w, "Bad Request", http.StatusBadRequest) http.Error(w, "Bad Request", http.StatusBadRequest)
return return
} }
...@@ -147,19 +163,33 @@ func handleRequest(w http.ResponseWriter, req *http.Request) { ...@@ -147,19 +163,33 @@ func handleRequest(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Cache-Control", "no-store") w.Header().Set("Cache-Control", "no-store")
// Render internal template.
var buf bytes.Buffer
if err := contentTpl.Execute(&buf, struct {
Inventory []*hostinfo
ProjectName string
JobID string
Title string
GitlabURL string
}{
Inventory: payload.Inventory,
ProjectName: payload.ProjectName,
JobID: payload.JobID,
Title: *title,
GitlabURL: *gitlabURL,
}); err != nil {
log.Printf("template error: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// nolint: errcheck // nolint: errcheck
dashTpl.Execute(w, struct { dashTpl.Execute(w, struct {
Inventory []*hostinfo
SocksIP string
JumpHost string
EnableSOCKS bool
Title string Title string
Content template.HTML
}{ }{
Inventory: inventory,
SocksIP: inventory[0].IP,
JumpHost: *jumpHost,
EnableSOCKS: *enableSOCKS,
Title: *title, Title: *title,
Content: template.HTML(buf.String()),
}) })
} }
...@@ -167,14 +197,8 @@ func main() { ...@@ -167,14 +197,8 @@ func main() {
log.SetFlags(0) log.SetFlags(0)
flag.Parse() flag.Parse()
if *hostname != "" { if err := parseTemplate(); err != nil {
*jumpHost = *hostname log.Fatalf("template error: %v", err)
}
var err error
hostConnTpl, err = template.New("hostconn").Parse(*hostConnFmt)
if err != nil {
log.Fatalf("error in host connection template: %v", err)
} }
http.Handle("/dash/", http.StripPrefix("/dash/", http.HandlerFunc(handleRequest))) http.Handle("/dash/", http.StripPrefix("/dash/", http.HandlerFunc(handleRequest)))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment