Skip to content

Commit

Permalink
do not use html templates
Browse files Browse the repository at this point in the history
  • Loading branch information
richabanker committed Sep 5, 2024
1 parent 2d48e33 commit 78deab7
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 164 deletions.
3 changes: 1 addition & 2 deletions staging/src/k8s.io/apiserver/pkg/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1191,7 +1191,6 @@ func SetHostnameFuncForTests(name string) {

func statuszOptions() statusz.Options {
return statusz.Options{
ComponentName: "apiserver",
StartTime: time.Now(),
StartTime: time.Now(),
}
}
126 changes: 35 additions & 91 deletions staging/src/k8s.io/component-base/statusz/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,109 +17,53 @@ limitations under the License.
package statusz

import (
"context"
"encoding/json"
"fmt"
"html/template"
"io"
"net/http"
"sync"
"time"
)

type statuszRegistry struct {
lock sync.Mutex
options Options
sections []section
type Options struct {
StartTime time.Time
GoVersion string
BinaryVersion string
CompatibilityVersion string
}

type section struct {
Title string
Func func(context.Context, io.Writer) error
type statuszRegistry struct {
lock sync.Mutex
options Options
}

func Register(opts Options) *statuszRegistry {
registry := &statuszRegistry{
options: opts,
func (reg *statuszRegistry) handleStatusz() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
statuszData := reg.populateStatuszData()
jsonData, err := json.MarshalIndent(statuszData, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprint(w, string(jsonData))
}
registry.addSection("default", defaultSection)
return registry
}

func (reg *statuszRegistry) addSection(title string, f func(ctx context.Context, wr io.Writer, opts Options) error) error {
reg.lock.Lock()
defer reg.lock.Unlock()
reg.sections = append(reg.sections, section{
Title: title,
Func: func(ctx context.Context, wr io.Writer) error {
err := f(ctx, wr, reg.options)
if err != nil {
failErr := template.Must(template.New("").Parse("<code>invalid HTML: {{.}}</code>")).Execute(wr, err)
if failErr != nil {
return fmt.Errorf("go/server: couldn't execute the error template for %q: %v (couldn't get HTML fragment: %v)", title, failErr, err)
}
return err
}
return nil
func (reg *statuszRegistry) populateStatuszData() StatuszResponse {
uptime := int(time.Since(reg.options.StartTime).Seconds())

return StatuszResponse{
StartTime: reg.options.StartTime.String(),
Uptime: fmt.Sprintf("%d hr %02d min %02d sec",
uptime/3600, (uptime/60)%60, uptime%60),
GoVersion: reg.options.GoVersion,
BinaryVersion: reg.options.BinaryVersion,
CompatibilityVersion: reg.options.CompatibilityVersion,
UsefulLinks: map[string]string{
"healthz": "/healthz",
"livez": "/livez",
"readyz": "/readyz",
"metrics": "/metrics",
},
})
return nil
}

func defaultSection(ctx context.Context, wr io.Writer, opts Options) error {
var data struct {
ServerName string
StartTime string
Uptime string
}

data.ServerName = opts.ComponentName
data.StartTime = opts.StartTime.Format(time.RFC1123)
uptime := int64(time.Since(opts.StartTime).Seconds())
data.Uptime = fmt.Sprintf("%d hr %02d min %02d sec",
uptime/3600, (uptime/60)%60, uptime%60)

if err := defaultTmp.Execute(wr, data); err != nil {
return fmt.Errorf("couldn't execute template: %v", err)
}

return nil
}

var defaultTmp = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<title>Status for {{.ServerName}}</title>
<style>
body {
font-family: sans-serif;
}
h1 {
clear: both;
width: 100%;
text-align: center;
font-size: 120%;
background: #eef;
}
.lefthand {
float: left;
width: 80%;
}
.righthand {
text-align: right;
}
td {
background-color: rgba(0, 0, 0, 0.05);
}
</style>
</head>
<body>
<h1>Status for {{.ServerName}}</h1>
<div>
<div class=lefthand>
Started: {{.StartTime}}<br>
Up {{.Uptime}}<br>
</body>
</html>
`))
47 changes: 17 additions & 30 deletions staging/src/k8s.io/component-base/statusz/statusz.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,41 @@ limitations under the License.
package statusz

import (
"bytes"
"context"
"fmt"
"net/http"
"time"

"k8s.io/apiserver/pkg/endpoints/metrics"
"k8s.io/klog/v2"
)

type Statusz struct {
registry *statuszRegistry
}

type Options struct {
ComponentName string
StartTime time.Time
type StatuszResponse struct {
StartTime string
Uptime string
GoVersion string
BinaryVersion string
CompatibilityVersion string
UsefulLinks map[string]string
}

type mux interface {
Handle(path string, handler http.Handler)
}

func (f Statusz) Install(m mux, opts Options) {
f.registry = Register(opts)
f.registry = register(opts)
f.registry.installHandler(m)
}

func register(opts Options) *statuszRegistry {
registry := &statuszRegistry{
options: opts,
}

return registry
}

func (reg *statuszRegistry) installHandler(m mux) {
reg.lock.Lock()
defer reg.lock.Unlock()
Expand All @@ -58,25 +65,5 @@ func (reg *statuszRegistry) installHandler(m mux) {
/* component = */ "",
/* deprecated */ false,
/* removedRelease */ "",
handleSections(reg.sections)))
}

// handleSections returns an http.HandlerFunc that serves the provided sections.
func handleSections(sections []section) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var individualCheckOutput bytes.Buffer
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
for _, section := range sections {
err := section.Func(context.Background(), w)
if err != nil {
fmt.Fprintf(&individualCheckOutput, "[-]%s failed: reason withheld\n", section.Title)
klog.V(2).Infof("%s section failed: %v", section.Title, err)
http.Error(w, fmt.Sprintf("%s%s section failed", individualCheckOutput.String(), section.Title), http.StatusInternalServerError)
return
}
fmt.Fprint(w)
}
individualCheckOutput.WriteTo(w)
}
reg.handleStatusz()))
}
77 changes: 36 additions & 41 deletions staging/src/k8s.io/component-base/statusz/statusz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,46 @@ limitations under the License.
package statusz

import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)

func TestStatusz(t *testing.T) {
timeNow := time.Now()
uptime := time.Since(timeNow)
binaryVersion := "1.32"
tests := []struct {
name string
opts Options
expectedTemplate *template.Template
expectedStatus int
name string
opts Options
wantResp *StatuszResponse
wantStatus int
}{
{
name: "default",
opts: Options{
ComponentName: "test-component",
StartTime: time.Now(),
StartTime: timeNow,
BinaryVersion: binaryVersion,
},
wantResp: &StatuszResponse{
StartTime: timeNow.String(),
Uptime: fmt.Sprintf("%d hr %02d min %02d sec",
uptime/3600, (uptime/60)%60, uptime%60),
BinaryVersion: binaryVersion,
UsefulLinks: map[string]string{
"healthz": "/healthz",
"livez": "/livez",
"readyz": "/readyz",
"metrics": "/metrics",
},
},
expectedTemplate: defaultTmp,
expectedStatus: http.StatusOK,
wantStatus: http.StatusOK,
},
}

Expand All @@ -48,50 +64,29 @@ func TestStatusz(t *testing.T) {
path := "/statusz"
req, err := http.NewRequest("GET", fmt.Sprintf("http://example.com%s", path), nil)
if err != nil {
t.Fatalf("case[%d] Unexpected error: %v", i, err)
t.Fatalf("case[%d] Unexpected error while creating request: %v", i, err)
}

w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
if w.Code != test.expectedStatus {
t.Errorf("case[%d] Expected: %v, got: %v", i, test.expectedStatus, w.Code)

if w.Code != test.wantStatus {
t.Fatalf("case[%d] want status code: %v, got: %v", i, test.wantStatus, w.Code)
}

c := w.Header().Get("Content-Type")
if c != "text/plain; charset=utf-8" {
t.Errorf("case[%d] Expected: %v, got: %v", i, "text/plain", c)
t.Fatalf("case[%d] want header: %v, got: %v", i, "text/plain", c)
}

data := prepareData(test.opts)
want := new(bytes.Buffer)
err = test.expectedTemplate.Execute(want, data)
if err != nil {
t.Fatalf("unexpected error while executing expected template: %v", err)
gotResp := &StatuszResponse{}
if err = json.Unmarshal(w.Body.Bytes(), gotResp); err != nil {
t.Fatalf("case[%d] Unexpected error while unmarshaling wantResponse: %v", i, err)
}

if w.Body.String() != want.String() {
t.Errorf("case[%d] Expected:\n%v\ngot:\n%v\n", i, test.expectedTemplate, w.Body.String())
if diff := cmp.Diff(test.wantResp, gotResp, cmpopts.IgnoreFields(StatuszResponse{}, "Uptime")); diff != "" {
t.Fatalf("Unexpected diff on response (-want,+got):\n%s", diff)
}
}

}

func prepareData(opts Options) struct {
ServerName string
StartTime string
Uptime string
} {
var data struct {
ServerName string
StartTime string
Uptime string
}

data.ServerName = opts.ComponentName
data.StartTime = opts.StartTime.Format(time.RFC1123)
uptime := int64(time.Since(opts.StartTime).Seconds())
data.Uptime = fmt.Sprintf("%d hr %02d min %02d sec",
uptime/3600, (uptime/60)%60, uptime%60)

return data
}

0 comments on commit 78deab7

Please sign in to comment.