Skip to content

Commit

Permalink
Web console for humans (#20)
Browse files Browse the repository at this point in the history
* Web console for humans

* Formatting

* Update chart.yaml

* Fix templates in dockerfile

* Rename 'info' to 'annotations'

* placements: Rename Date CreationTimestamp and use time.Time

* Don't break the API, deprecate instead

* Update swagger version
  • Loading branch information
fridim authored Sep 18, 2020
1 parent b29b374 commit 413ea33
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 8 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ USER ${USER_UID}
COPY build/github_known_hosts /ssh/known_hosts
env SSH_KNOWN_HOSTS /ssh/known_hosts
COPY --from=builder /agnostics/scheduler ./
COPY ./templates/ ./templates/
CMD ["./scheduler"]
4 changes: 4 additions & 0 deletions cmd/scheduler/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import(
"flag"
"github.com/redhat-gpe/agnostics/internal/api"
"github.com/redhat-gpe/agnostics/internal/console"
"github.com/redhat-gpe/agnostics/internal/config"
"github.com/redhat-gpe/agnostics/internal/git"
"github.com/redhat-gpe/agnostics/internal/log"
Expand All @@ -16,12 +17,14 @@ var debugFlag bool
var repositoryURL string
var sshPrivateKey string
var redisURL string
var templateDir string

func parseFlags() {
flag.StringVar(&repositoryURL, "git-url", "git@github.com:redhat-gpe/scheduler-config.git", "The URL of the git repository where the scheduler will find its configuration. SSH is assumed, unless the URL starts with 'http'.\nEnvironment variable: GIT_URL\n")
flag.StringVar(&sshPrivateKey, "git-ssh-private-key", "", "The path of the SSH private key used to authenticate to the git repository. Used only when 'git-url' is an SSH URL.\nEnvironment variable: GIT_SSH_PRIVATE_KEY\n")
flag.StringVar(&redisURL, "redis-url", "redis://localhost:6379", "The URL to access redis. The format is described by the IANA specification for the scheme, see https://www.iana.org/assignments/uri-schemes/prov/redis\nEnvironment variable: REDIS_URL\n")
flag.BoolVar(&debugFlag, "debug", false, "Debug mode.\nEnvironment variable: DEBUG\n")
flag.StringVar(&templateDir, "template-dir", "templates", "The directory containing the golang templates for the Console.\nEnvironment variable: REDIS_URL\n")
flag.Parse()
if e := os.Getenv("GIT_URL"); e != "" {
repositoryURL = e
Expand All @@ -44,5 +47,6 @@ func main() {
git.CloneRepository(repositoryURL, sshPrivateKey)
go watcher.ConsumePullQueue()
config.Load()
go console.Serve(templateDir)
api.Serve()
}
4 changes: 2 additions & 2 deletions deployments/helm/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
version: v0.1.5

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
appVersion: v0.1.3
appVersion: v0.1.5
16 changes: 16 additions & 0 deletions deployments/helm/templates/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ spec:
name: http
selector:
{{- include "scheduler.selectorLabels" . | nindent 4 }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "scheduler.name" . }}-console
labels:
{{- include "scheduler.labels" . | nindent 4 }}
spec:
type: {{ .Values.console.type }}
ports:
- port: {{ .Values.console.port }}
targetPort: {{ .Values.console.port }}
protocol: TCP
name: http-console
selector:
{{- include "scheduler.selectorLabels" . | nindent 4 }}

---
apiVersion: v1
Expand Down
4 changes: 4 additions & 0 deletions deployments/helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ service:
type: ClusterIP
port: 80

console:
type: ClusterIP
port: 8081

route:
enabled: true
annotations: {}
Expand Down
16 changes: 13 additions & 3 deletions docs/api-reference/swagger.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
openapi: "3.0.3"
info:
version: 1.0.0
version: 1.0.1
title: Scheduler
license:
name: MIT
Expand Down Expand Up @@ -259,22 +259,30 @@ components:
description: The UUID of the service in CloudForms. It used to identify placement and can be used later to retrieve a placement.
format: uuid

Annotations:
description: Key / Value map to provide optional information. Annotations can be used to filter or identify objects.
type: object
additionalProperties:
type: string
Placement:
type: object
description: A placement is a record of what(uuid) / where(cloud) / when(date) something was scheduled.
required:
- uuid
- cloud
- date
- creation_timestamp
- annotations
properties:
uuid:
$ref: "#/components/schemas/UUID"
cloud:
$ref: "#/components/schemas/Cloud"
date:
creation_timestamp:
description: The date (UTC and RFC3339 format) the placement was made.
type: string
format: date-time
annotations:
$ref: "#/components/schemas/Annotations"

Placements:
type: array
Expand Down Expand Up @@ -336,6 +344,8 @@ components:
description: The list of tolerations for this request. Any taint matching the a toleration will be ignored (all taints ignored == cloud can be selected).
items:
$ref: "#/components/schemas/Toleration"
annotations:
$ref: "#/components/schemas/Annotations"

Message:
type: object
Expand Down
3 changes: 2 additions & 1 deletion internal/api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ func v1PostSchedule(w http.ResponseWriter, req *http.Request, params httprouter.
result := v1.Placement{
UUID: t.UUID,
Cloud: results[0],
Date: time.Now().UTC().Format(time.RFC3339),
CreationTimestamp: time.Now().UTC().Round(time.Second),
Annotations: t.Annotations,
}
placement.Save(result)
if err := enc.Encode(result) ; err != nil {
Expand Down
12 changes: 10 additions & 2 deletions internal/api/v1/types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package v1

import("time")

// The Cloud is the main object that the scheduler work with.
type Cloud struct {
Name string `json:"name"`
Expand Down Expand Up @@ -36,6 +38,7 @@ type ScheduleQuery struct {
CloudPreference map[string]string `json:"cloud_preference"`
Tolerations []Toleration `json:"tolerations"`
UUID string `json:"uuid,omitempty"`
Annotations map[string]string `json:"annotations"`
}

type GitCommit struct {
Expand Down Expand Up @@ -98,8 +101,13 @@ type Toleration struct {
type Placement struct {
// The uuid of the CloudForms service
UUID string `json:"uuid,omitempty"`
// Date the placement was made. UTF and RFC3339
Date string `json:"date"`
// CreationTimestamp the placement was made. UTC and RFC3339
CreationTimestamp time.Time `json:"creation_timestamp"`
// Date the placement was made. UTC and RFC3339 (deprecated)
Date time.Time `json:"date"`
// The cloud where it was scheduled to.
Cloud Cloud `json:"cloud"`
// Annotations
// +optional
Annotations map[string]string `json:"annotations"`
}
76 changes: 76 additions & 0 deletions internal/console/console.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package console

import (
"encoding/json"
"html/template"
"github.com/julienschmidt/httprouter"
"github.com/redhat-gpe/agnostics/internal/config"
"github.com/redhat-gpe/agnostics/internal/api/v1"
"github.com/redhat-gpe/agnostics/internal/log"
"github.com/redhat-gpe/agnostics/internal/placement"
"io"
"path/filepath"
"net/http"
)

func marshal(data interface{}) string {
json, err := json.MarshalIndent(data, "", " ")
if err != nil {
log.Err.Fatal(err)
}
return string(json)
}

func countPlacements(name string) string{
reply, err := placement.GetCountPlacementsByCloud(name)
if err != nil {
return "ERROR"
}
return reply
}

func getDashboard(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
w.Header().Set("Content-Type", "text/html")
placements, err := placement.GetAll(100)
if err != nil{
w.WriteHeader(http.StatusInternalServerError)
io.WriteString(w, "ERROR")
log.Err.Println(err)
return
}

var fm = template.FuncMap{
"marshal": marshal,
"countPlacements": countPlacements,
}

clouds := config.GetClouds()

type HomeData struct {
Clouds map[string]v1.Cloud
Placements []v1.Placement
}

t := template.Must(
template.New("layout.tmpl").Funcs(fm).ParseGlob(
filepath.Join(templateDir,"/*.tmpl")))

t.ExecuteTemplate(w, "layout.tmpl", HomeData {
clouds,
placements,
})
}

var templateDir string

// Serve function is
func Serve(t string) {
templateDir = t
router := httprouter.New()

// Protected
router.GET("/", getDashboard)

log.Out.Println("Console listen on port :8081")
log.Err.Fatal(http.ListenAndServe(":8081", router))
}
5 changes: 5 additions & 0 deletions internal/placement/placement.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ func get(key string) (v1.Placement, error) {
if err := json.Unmarshal(reply, &p); err != nil {
return v1.Placement{}, err
}
if p.CreationTimestamp.IsZero() && ! p.Date.IsZero() {
// Probably an old record that doesn't have creation_timestamp field set.
// Use old deprecated field 'date'
p.CreationTimestamp = p.Date
}
return p, nil
}
}
Expand Down
35 changes: 35 additions & 0 deletions templates/clouds.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div class="pure-u-1 pure-u-md-1-2">
<h1>Clouds</h1>

<table class="pure-table pure-table-horizontal">
<tr>
<td>Name</td>
<td>Placements</td>
<td>Labels</td>
<td>Enabled</td>
<td>Taints</td>
</tr>

{{ range . }}
<tr>
<td><b>{{ .Name }}</b></td>
<td>{{ countPlacements .Name }}</td>
<td>
<ul>
{{ range $key, $val := .Labels }}
<li><code>{{ $key }}:&nbsp;{{ $val }}</code></li>
{{end}}
</ul>
</td>
<td>
{{ if eq .Enabled true }}
<span style="font-weight: bold; color: green">true</span>
{{ else }}
<span style="font-weight: bold; color: red">false</span>
{{ end }}
</td>
<td><pre>{{ marshal .Taints }}</pre></td>
</tr>
{{ end }}
</table>
</div>
28 changes: 28 additions & 0 deletions templates/layout.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Dashboard</title>
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.3/build/pure-min.css" integrity="sha384-cg6SkqEOCV1NbJoCu11+bm0NvBRc8IYLRGXkmNrqUBfTjmMYwNKPWBTIKyw9mHNJ" crossorigin="anonymous">
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.3/build/base-min.css">
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.3/build/grids-min.css">
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.3/build/grids-responsive-min.css">


<style>
.pure-g > div {
box-sizing: border-box;
padding-left: 1em;
}

tr:nth-child(even) {
background-color: #eeeeee;
}
</style>
</head>
<body>
<div class="pure-g">
{{ template "clouds.tmpl" .Clouds }}
{{ template "placements.tmpl" .Placements }}
</div>
</body>
</html>
33 changes: 33 additions & 0 deletions templates/placements.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<div class="pure-u-1 pure-u-md-1-2">
<h1>Placements</h1>

<p>Total: {{ countPlacements "all" }} placements</p>
<table class="pure-table pure-table-horizontal" style="font-size: 80%">
<caption>First 100 placements</caption>
<tr>
<td>Creation Timestamp</td>
<td>UUID</td>
<td>Annotations</td>
<td>Cloud Name</td>
</tr>

{{ range . }}
<tr>
<td>{{ .CreationTimestamp }}</td>
<td>
<span style="font-size: 80%">
<code>{{ .UUID }}</code>
</span>
</td>
<td>
<ul>
{{ range $key, $val := .Annotations }}
<li><code>{{ $key }}:&nbsp;{{ $val }}</code></li>
{{ end }}
</ul>
</td>
<td>{{ .Cloud.Name }}</td>
</tr>
{{ end }}
</table>
</div>

0 comments on commit 413ea33

Please sign in to comment.