Skip to content

Commit

Permalink
Merge pull request #14 from dastergon/add_server
Browse files Browse the repository at this point in the history
Created basic web site to serve postmortems
  • Loading branch information
icco authored Nov 4, 2019
2 parents 183140b + fbe97e1 commit 9e66dc7
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 1 deletion.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ go 1.13
require (
github.com/gernest/front v0.0.0-20181129160812-ed80ca338b88
github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.3
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
gopkg.in/russross/blackfriday.v2 v2.0.0
gopkg.in/yaml.v2 v2.2.4 // indirect
)
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ github.com/gernest/front v0.0.0-20181129160812-ed80ca338b88 h1:fqfzqvgJfq5Sw7VZy
github.com/gernest/front v0.0.0-20181129160812-ed80ca338b88/go.mod h1:FwEMwQ5+xky8tbzDLj72k2RAqXnFByLNwxg+9UZDtqU=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/russross/blackfriday.v2 v2.0.0 h1:+FlnIV8DSQnT7NZ43hcVKcdJdzZoeCmJj4Ql8gq5keA=
gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
10 changes: 10 additions & 0 deletions templates/categories.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{{define "title"}}Categories{{end}}

{{define "body"}}
<h3>Category</h3>
<ul>
{{ range . }}
<li><a href="/category/{{ . }}">{{ . }}</a></li>
{{end}}
</ul>
{{end}}
10 changes: 10 additions & 0 deletions templates/category.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{{define "title"}}Category: {{ .Category }}{{end}}

{{define "body"}}
<h3>Category: {{ .Category }}</h3>
<ul>
{{ range .Postmortems }}
<li><a href="/postmortem/{{ .UUID }}">{{ .Company}}: {{ .UUID }}</a></li>
{{end}}
</ul>
{{end}}
11 changes: 11 additions & 0 deletions templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{{define "title"}}Postmortems{{end}}

{{define "body"}}

<h1>Postmortems Index Page</h1>
<ul>
{{ range . }}
<li><a href="postmortem/{{ .UUID }}">{{ .Company }}: {{ .UUID }}</a></li>
{{end}}
</ul>
{{end}}
16 changes: 16 additions & 0 deletions templates/layout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{{define "layout"}}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{{template "title" .}}</title>
</head>
<body>
<header>
<a href="/">Home</a>
<a href="/categories">Categories</a>
</header>
{{ template "body" . }}
</body>
</html>
{{end}}
18 changes: 18 additions & 0 deletions templates/postmortem.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{{define "title"}}Postmortem{{end}}

{{define "body"}}
<h1>{{ .Company }}</h1>
<h3>Category</h3>

<ul>
{{ range .Categories }}
<li><a href="category/{{ . }}">{{ . }}</a></li>
{{end}}
</ul>

<p>
{{ .Description }}
</p>

<a href="{{ .URL }}">Source</a>, <a href="https://github.com/icco/postmortems/blob/master/data/{{ .UUID }}.md">Edit Metadata</a>
{{end}}
1 change: 0 additions & 1 deletion tmp/healthz

This file was deleted.

235 changes: 235 additions & 0 deletions tool/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package main

import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"text/template"

"github.com/gorilla/mux"
blackfriday "gopkg.in/russross/blackfriday.v2"
)

// createHandlers sets the routes for the web server.
func createHandlers() *mux.Router {
router := mux.NewRouter()
router.HandleFunc("/", indexHandler).Methods("GET")
router.HandleFunc("/postmortem/{id}", postmortemPageHandler).Methods("GET")
router.HandleFunc("/categories", categoriesPageHandler).Methods("GET")
router.HandleFunc("/category/{category}", categoryPageHandler).Methods("GET")
router.HandleFunc("/healthz", healthzHandler).Methods("GET")

return router
}

// healthzHandler serves an availability check endpoint.
func healthzHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "text/plain")

if _, err := w.Write([]byte("ok.")); err != nil {
log.Printf("Error writing response to healthz request")
}
}

// loadPostmortem loads the postmortem data in memory.
func loadPostmortem(dir, filename string) (*Postmortem, error) {
f, err := os.Open(filepath.Join(dir, filename))
if err != nil {
return nil, fmt.Errorf("error opening postmortem: %w", err)
}

pm, err := Parse(f)
if err != nil {
return nil, fmt.Errorf("error parsing file %s: %w", f.Name(), err)
}

return pm, nil
}

// loadPostmortems parses the postmortem files
// and returns a slice with their content.
func loadPostmortems(dir string) ([]*Postmortem, error) {
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("error opening data folder: %w", err)
}

var pms []*Postmortem

for _, path := range files {
pm, err := loadPostmortem(dir, path.Name())
if err != nil {
return nil, err
}

pms = append(pms, pm)
}

return pms, nil
}

func categoriesPageHandler(w http.ResponseWriter, r *http.Request) {
lp := filepath.Join("templates", "layout.html")
fp := filepath.Join("templates", "categories.html")

_, err := os.Stat(fp)
if err != nil {
if os.IsNotExist(err) {
// Template doesn't exist, return 404.
http.NotFound(w, r)
return
}
}

tmpl, err := template.ParseFiles(lp, fp)
if err != nil {
log.Println(err.Error())
http.Error(w, http.StatusText(500), http.StatusInternalServerError)

return
}

if err := tmpl.ExecuteTemplate(w, "layout", Categories); err != nil {
log.Println(err.Error())
http.Error(w, http.StatusText(500), http.StatusInternalServerError)
}
}

func getPosmortemByCategory(pms []*Postmortem, category string) []Postmortem {
var ctpm []Postmortem

for _, pm := range pms {
for _, c := range pm.Categories {
if c == category {
ctpm = append(ctpm, *pm)
}
}
}

return ctpm
}

func categoryPageHandler(w http.ResponseWriter, r *http.Request) {
ct := mux.Vars(r)["category"]

pms, err := loadPostmortems(*dir)
if err != nil {
log.Println(err.Error())
http.Error(w, http.StatusText(500), http.StatusInternalServerError)

return
}

ctpm := getPosmortemByCategory(pms, ct)

lp := filepath.Join("templates", "layout.html")
fp := filepath.Join("templates", "category.html")

_, err = os.Stat(fp)
if err != nil {
if os.IsNotExist(err) {
// Template doesn't exist, return 404.
http.NotFound(w, r)
return
}
}

tmpl, err := template.ParseFiles(lp, fp)
if err != nil {
log.Println(err.Error())
http.Error(w, http.StatusText(500), http.StatusInternalServerError)

return
}

page := struct {
Category string
Postmortems []Postmortem
}{
Category: ct,
Postmortems: ctpm,
}

if err := tmpl.ExecuteTemplate(w, "layout", page); err != nil {
log.Println(err.Error())
http.Error(w, http.StatusText(500), http.StatusInternalServerError)
}
}

func postmortemPageHandler(w http.ResponseWriter, r *http.Request) {
pmID := mux.Vars(r)["id"]

pm, err := loadPostmortem(*dir, pmID+".md")
if err != nil {
log.Println(err.Error())
http.Error(w, http.StatusText(500), http.StatusInternalServerError)
}

lp := filepath.Join("templates", "layout.html")
fp := filepath.Join("templates", "postmortem.html")

_, err = os.Stat(fp)
if err != nil {
if os.IsNotExist(err) {
// Template doesn't exist, return 404.
http.NotFound(w, r)
return
}
}

// Convert Markdown formatting of descriptions to HTML.
htmlDesc := blackfriday.Run([]byte(pm.Description))
pm.Description = string(htmlDesc)

tmpl, err := template.ParseFiles(lp, fp)
if err != nil {
log.Println(err.Error())
http.Error(w, http.StatusText(500), http.StatusInternalServerError)

return
}

if err := tmpl.ExecuteTemplate(w, "layout", pm); err != nil {
log.Println(err.Error())
http.Error(w, http.StatusText(500), http.StatusInternalServerError)
}
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
pms, err := loadPostmortems(*dir)
if err != nil {
log.Println(err.Error())
http.Error(w, http.StatusText(500), http.StatusInternalServerError)

return
}

lp := filepath.Join("templates", "layout.html")
fp := filepath.Join("templates", "index.html")

_, err = os.Stat(fp)
if err != nil {
if os.IsNotExist(err) {
// Template doesn't exist, return 404.
http.NotFound(w, r)
return
}
}

tmpl, err := template.ParseFiles(lp, fp)
if err != nil {
log.Println(err.Error())
http.Error(w, http.StatusText(500), http.StatusInternalServerError)

return
}

if err := tmpl.ExecuteTemplate(w, "layout", pms); err != nil {
log.Println(err.Error())
http.Error(w, http.StatusText(500), http.StatusInternalServerError)
}
}
18 changes: 18 additions & 0 deletions tool/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"time"
)

var (
Expand Down Expand Up @@ -84,6 +86,19 @@ func Generate(d string) error {
})
}

// Serve serves the content of the website.
func Serve() {
router := createHandlers()

port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

fmt.Printf("%s Server listening on *:%s\n", time.Now().Format("2006-01-02 15:04:05"), port)
log.Fatal(http.ListenAndServe(":"+port, router))
}

func main() {
flag.Usage = usage
flag.Parse()
Expand Down Expand Up @@ -111,6 +126,8 @@ func main() {
err = Generate(*dir)
case "validate":
err = ValidateDir(*dir)
case "serve":
Serve()
default:
log.Fatalf("%s is not a valid action", *action)
}
Expand All @@ -134,4 +151,5 @@ Actions:
extract Extract postmortems from the collection and create separate files.
generate Generate JSON files from the postmortem Markdown files.
validate Validate the postmortem files in the directory.
serve Serve the postmortem files in a small website.
`

0 comments on commit 9e66dc7

Please sign in to comment.