Skip to content

Instantly share code, notes, and snippets.

@taion809
Created November 8, 2023 04:33
Show Gist options
  • Save taion809/8ef80bb6f60fb7227101db4db07ab224 to your computer and use it in GitHub Desktop.
Save taion809/8ef80bb6f60fb7227101db4db07ab224 to your computer and use it in GitHub Desktop.
HTMX SSE K8S Demo
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
var clientset *kubernetes.Clientset
func generateK8sEvents(ctx context.Context) (watch.Interface, error) {
events, err := clientset.CoreV1().Events("").Watch(ctx, v1.ListOptions{})
if err != nil {
return nil, err
}
return events, nil
}
func index(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(indexTmpl))
}
func events(w http.ResponseWriter, r *http.Request) {
flush, ok := w.(http.Flusher)
if !ok {
log.Println("Error casting ResponseWriter to Flusher, SSE is not supported")
w.WriteHeader(http.StatusInternalServerError)
return
}
events, err := generateK8sEvents(r.Context())
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
defer events.Stop()
w.Header().Set("Content-Type", "text/event-stream")
card := fmt.Sprintf(cardTemplate, "Initial", "initial k8s event", "")
fmt.Fprintf(w, "data: %s\n\n", card)
flush.Flush()
for event := range events.ResultChan() {
log.Println("Event from events api", event.Type)
// fuck this stupid api
b, err := json.Marshal(event)
if err != nil {
log.Println("Failed to write to stream", err)
break
}
var extract map[string]interface{}
err = json.Unmarshal(b, &extract)
if err != nil {
log.Println("Failed to write to stream", err)
break
}
obj := extract["Object"].(map[string]interface{})
card = fmt.Sprintf(cardTemplate, obj["reason"], obj["message"], obj["lastTimestamp"])
fmt.Fprintf(w, "data: %v\n\n", card)
flush.Flush()
}
}
func main() {
// uses the current context in kubeconfig
// path-to-kubeconfig -- for example, /root/.kube/config
config, err := clientcmd.BuildConfigFromFlags("", "/home/nicholas/.kube/config")
if err != nil {
log.Fatalln("Unable to open k8s kube config", err)
}
// creates the clientset
clientset, err = kubernetes.NewForConfig(config)
if err != nil {
log.Fatalln("Unable to create k8s clientset", err)
}
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/", index)
r.Get("/events", events)
fmt.Println("starting server :3000")
if err := http.ListenAndServe(":3000", r); err != nil {
log.Fatalln(err)
}
}
package main
var indexTmpl = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hello Everybody!</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
<script src="https://unpkg.com/[email protected]" integrity="sha384-rgjA7mptc2ETQqXoYC3/zJvkU7K/aP44Y+z7xQuJiVnB/422P/Ak+F/AqFR7E4Wr" crossorigin="anonymous"></script>
<script src="https://unpkg.com/htmx.org/dist/ext/sse.js"></script>
</head>
<body>
<section class="section">
<div class="container">
<h1 class="title">
Hello World
</h1>
<div id="eventStream">
<p>waiting...</p>
</div>
<div hx-ext="sse" sse-connect="/events" sse-swap="message" hx-swap="afterbegin" hx-target="#eventStream">
Waiting for k8s events...
</div>
</div>
</section>
</body>
</html>
`
var cardTemplate = `<div class="card" style="margin-bottom:10px"><header class="card-header"><p class="card-header-title">%s</p></header><div class="card-content"><div class="content">%s<br><span>Received: <strong>%s</strong></span></div></div>`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment