port to new repo
This commit is contained in:
commit
8e4d7ddd72
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2022 Mya Pitzeruse
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
70
README.md
Normal file
70
README.md
Normal file
@ -0,0 +1,70 @@
|
||||
# emc
|
||||
|
||||
_The minimally declarative service catalog._
|
||||
|
||||
Pronounced "MC" as in the master of ceremonies.
|
||||
|
||||
## Background
|
||||
|
||||
In the last three jobs I've worked at, it's always been a hassle to trying to locate the various dashboards,
|
||||
documentation, and support for a given project. I joined effx to try and help address just that. As I've been spinning
|
||||
up a new cluster, I found myself wanting a landing page for the systems that I use regularly.
|
||||
|
||||
## Building your catalog
|
||||
|
||||
The `emc` service catalog is defined using a simple Golang script. This makes it easy for engineers to drop in their
|
||||
own functionality for rendering links, link groups, or services. For an example, see the provided `grafana` package
|
||||
which includes several of my personal dashboards for different systems.
|
||||
|
||||
```go
|
||||
// catalog.go
|
||||
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mjpitz/emc/catalog"
|
||||
"github.com/mjpitz/emc/catalog/grafana"
|
||||
"github.com/mjpitz/emc/catalog/linkgroup"
|
||||
"github.com/mjpitz/emc/catalog/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
catalog.Serve(
|
||||
catalog.Service(
|
||||
"Drone",
|
||||
service.LogoURL("https://path/to/drone-logo.png"),
|
||||
service.URL("https://drone.example.com"),
|
||||
service.Description("Drone is a self-service Continuous Integration platform for busy development teams."),
|
||||
service.Metadata("Contact", "drone@example.com"),
|
||||
service.LinkGroup(
|
||||
"Dashboards",
|
||||
linkgroup.Link("Drone", grafana.Drone("cicd", "drone")),
|
||||
linkgroup.Link("Golang", grafana.Golang("cicd", "drone")),
|
||||
linkgroup.Link("Litestream", grafana.Litestream("cicd", "drone")),
|
||||
linkgroup.Link("Redis Queue", grafana.Redis("cicd", "drone-redis-queue")),
|
||||
),
|
||||
service.LinkGroup(
|
||||
"Documentation",
|
||||
linkgroup.Link("docs.drone.io", "https://docs.drone.io/"),
|
||||
),
|
||||
),
|
||||
// ...
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Hosting your catalog
|
||||
|
||||
Once you've built your catalog, you can easily run a landing page by executing the catalog file.
|
||||
|
||||
```
|
||||
$ go run ./catalog.go
|
||||
```
|
||||
|
||||
This starts a web server for you to interact with on `localhost:8080`. If `:8080` is already in use, you can configure
|
||||
the bind address be passing the `-bind_address` flag with the desired host and port.
|
||||
|
||||
![Screenshot](screenshot.png)
|
78
catalog/grafana/dsl.go
Normal file
78
catalog/grafana/dsl.go
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2022 Mya Pitzeruse
|
||||
// The MIT License (MIT)
|
||||
|
||||
package grafana
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Grafana provides functionality for rendering links to dashboards. This implementation provides some shorthand calls
|
||||
// that assume the use of my personal dashboards (provided at https://github.com/mjpitz/mjpitz/tree/main/monitoring).
|
||||
type Grafana string
|
||||
|
||||
// Link renders a link to a grafana deployment given a dashboard id, namespace, and job (assumes a kubernetes based
|
||||
// deployment).
|
||||
func (g Grafana) Link(dashboard, namespace, job string) string {
|
||||
return fmt.Sprintf(
|
||||
"%s/d/%s?var-namespace=%s&var-job=%s",
|
||||
g, url.PathEscape(dashboard), url.QueryEscape(namespace), url.QueryEscape(job),
|
||||
)
|
||||
}
|
||||
|
||||
func (g Grafana) Maddy(namespace, job string) string {
|
||||
return g.Link("82a7b6b2c9516ef16c08616edc8a90c1", namespace, job)
|
||||
}
|
||||
|
||||
func (g Grafana) Redis(namespace, job string) string {
|
||||
return g.Link("36cf4a03d9f16d8a4221fecbbd2ff5c6", namespace, job)
|
||||
}
|
||||
|
||||
func (g Grafana) Drone(namespace, job string) string {
|
||||
return g.Link("2c241b4cea8d493ef632bf33b10d04cf", namespace, job)
|
||||
}
|
||||
|
||||
func (g Grafana) Litestream(namespace, job string) string {
|
||||
return g.Link("bf44e72e619451e2c85cda80fe17b28b", namespace, job)
|
||||
}
|
||||
|
||||
func (g Grafana) Golang(namespace, job string) string {
|
||||
return g.Link("8cd750f455ca9fc93a465fd9a34993cc", namespace, job)
|
||||
}
|
||||
|
||||
func (g Grafana) Gitea(namespace, job string) string {
|
||||
return g.Link("354a485fe64f93ea707a7f6e061ff71b", namespace, job)
|
||||
}
|
||||
|
||||
// default instance
|
||||
|
||||
var grafana = Grafana("https://grafana.pitz.tech")
|
||||
|
||||
func Link(dashboard, namespace, job string) string {
|
||||
return grafana.Link(dashboard, namespace, job)
|
||||
}
|
||||
|
||||
func Maddy(namespace, job string) string {
|
||||
return grafana.Maddy(namespace, job)
|
||||
}
|
||||
|
||||
func Redis(namespace, job string) string {
|
||||
return grafana.Redis(namespace, job)
|
||||
}
|
||||
|
||||
func Drone(namespace, job string) string {
|
||||
return grafana.Drone(namespace, job)
|
||||
}
|
||||
|
||||
func Litestream(namespace, job string) string {
|
||||
return grafana.Litestream(namespace, job)
|
||||
}
|
||||
|
||||
func Golang(namespace, job string) string {
|
||||
return grafana.Golang(namespace, job)
|
||||
}
|
||||
|
||||
func Gitea(namespace, job string) string {
|
||||
return grafana.Gitea(namespace, job)
|
||||
}
|
132
catalog/index.html.tpl
Normal file
132
catalog/index.html.tpl
Normal file
@ -0,0 +1,132 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Service Catalog</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background-color: #eaeaea;
|
||||
padding: 60px 0;
|
||||
margin: 0;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
div.catalog {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
div.row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
div.col {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
div.col-50 {
|
||||
max-width: 50%;
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
div.col-60 {
|
||||
max-width: 60%;
|
||||
min-width: 60%;
|
||||
}
|
||||
|
||||
div.col-40 {
|
||||
max-width: 40%;
|
||||
min-width: 40%;
|
||||
}
|
||||
|
||||
div.catalog h1 {
|
||||
text-align: center;
|
||||
font-size: 3rem;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
div.catalog div.service {
|
||||
background-color: #fff;
|
||||
border: 1px solid #aaa;
|
||||
padding: 20px 26px;
|
||||
border-radius: 5px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
div.catalog div.service img.logo {
|
||||
max-height: 48px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
div.catalog div.service h2.name {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin: 13px 0;
|
||||
}
|
||||
|
||||
div.catalog div.service p.url {}
|
||||
div.catalog div.service p.description {}
|
||||
|
||||
div.catalog div.service div.metadata {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.catalog div.service div.link-group {}
|
||||
div.catalog div.service div.link-group h3.label {}
|
||||
div.catalog div.service div.link-group li.link {}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="catalog">
|
||||
<h1>Service Catalog</h1>
|
||||
{{- range $service := .Services }}
|
||||
<div class="service">
|
||||
<div class="row">
|
||||
<div class="col col-60" style="padding-right: 10px">
|
||||
<div>
|
||||
{{- if $service.LogoURL }}
|
||||
<img class="logo" src="{{ $service.LogoURL }}" />
|
||||
{{- end }}
|
||||
|
||||
<h2 class="name">{{ $service.Label }}</h2>
|
||||
</div>
|
||||
|
||||
{{- if $service.URL }}
|
||||
<p class="url"><a href="{{ $service.URL }}">{{ $service.URL }}</a></p>
|
||||
{{- end }}
|
||||
|
||||
{{- if $service.Description }}
|
||||
<p class="description">{{ $service.Description }}</p>
|
||||
{{- end }}
|
||||
|
||||
{{- range $key, $value := $service.Metadata }}
|
||||
<div class="metadata row">
|
||||
<div class="col-50"><b>{{ $key }}</b></div>
|
||||
<div class="col-50">{{ $value }}</div>
|
||||
</div>
|
||||
{{- end }}
|
||||
</div>
|
||||
|
||||
<div class="col col-40">
|
||||
{{- range $group := $service.LinkGroups }}
|
||||
<div class="link-group">
|
||||
<h3 class="label">{{ $group.Label }}</h3>
|
||||
<ul>
|
||||
{{- range $link := $group.Links }}
|
||||
<li class="link">
|
||||
<a href="{{ $link.URL }}">{{ $link.Label }}</a>
|
||||
</li>
|
||||
{{- end }}
|
||||
</ul>
|
||||
</div>
|
||||
{{- end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end }}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
18
catalog/link/dsl.go
Normal file
18
catalog/link/dsl.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2022 Mya Pitzeruse
|
||||
// The MIT License (MIT)
|
||||
|
||||
package link
|
||||
|
||||
// New constructs a link from the given label and url.
|
||||
func New(label, url string) Spec {
|
||||
return Spec{
|
||||
Label: label,
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
// Spec defines the elements needed to render a link.
|
||||
type Spec struct {
|
||||
Label string
|
||||
URL string
|
||||
}
|
35
catalog/linkgroup/dsl.go
Normal file
35
catalog/linkgroup/dsl.go
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2022 Mya Pitzeruse
|
||||
// The MIT License (MIT)
|
||||
|
||||
package linkgroup
|
||||
|
||||
import (
|
||||
"github.com/mjpitz/emc/catalog/link"
|
||||
)
|
||||
|
||||
func New(label string, options ...Option) Spec {
|
||||
spec := Spec{
|
||||
Label: label,
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(&spec)
|
||||
}
|
||||
|
||||
return spec
|
||||
}
|
||||
|
||||
// Option defines an optional component of the spec.
|
||||
type Option func(spec *Spec)
|
||||
|
||||
// Spec defines the elements needed to render a link group.
|
||||
type Spec struct {
|
||||
Label string
|
||||
Links []link.Spec
|
||||
}
|
||||
|
||||
func Link(label, url string) Option {
|
||||
return func(spec *Spec) {
|
||||
spec.Links = append(spec.Links, link.New(label, url))
|
||||
}
|
||||
}
|
65
catalog/serve.go
Normal file
65
catalog/serve.go
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright (C) 2022 Mya Pitzeruse
|
||||
// The MIT License (MIT)
|
||||
|
||||
package catalog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"flag"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/mjpitz/emc/catalog/service"
|
||||
)
|
||||
|
||||
//go:embed index.html.tpl
|
||||
var catalog string
|
||||
|
||||
// Spec defines the requirements for hosting a catalog.
|
||||
type Spec struct {
|
||||
Services []service.Spec
|
||||
}
|
||||
|
||||
// Option defines an optional component of the spec.
|
||||
type Option func(spec *Spec)
|
||||
|
||||
// Service registers a known service with the catalog.
|
||||
func Service(label string, options ...service.Option) Option {
|
||||
return func(spec *Spec) {
|
||||
spec.Services = append(spec.Services, service.New(label, options...))
|
||||
}
|
||||
}
|
||||
|
||||
// Serve provides command line functionality for running the service catalog.
|
||||
func Serve(options ...Option) {
|
||||
addr := flag.String("bind_address", "127.0.0.1:8080", "the address the service should bind to when serving content")
|
||||
flag.Parse()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
t := template.Must(template.New("catalog").Parse(catalog))
|
||||
|
||||
spec := Spec{}
|
||||
for _, opt := range options {
|
||||
opt(&spec)
|
||||
}
|
||||
|
||||
html := bytes.NewBuffer(nil)
|
||||
err := t.Execute(html, spec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeContent(w, r, "", start, bytes.NewReader(html.Bytes()))
|
||||
})
|
||||
|
||||
log.Printf("serving on %s\n", *addr)
|
||||
err = http.ListenAndServe(*addr, http.DefaultServeMux)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
70
catalog/service/dsl.go
Normal file
70
catalog/service/dsl.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright (C) 2022 Mya Pitzeruse
|
||||
// The MIT License (MIT)
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/mjpitz/mjpitz/apps/emc/internal/catalog/linkgroup"
|
||||
)
|
||||
|
||||
// New constructs a spec given a label and set of options.
|
||||
func New(label string, options ...Option) Spec {
|
||||
spec := Spec{
|
||||
Label: label,
|
||||
Metadata: make(map[string]string),
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(&spec)
|
||||
}
|
||||
|
||||
return spec
|
||||
}
|
||||
|
||||
// Option defines an optional component of the spec.
|
||||
type Option func(spec *Spec)
|
||||
|
||||
// Spec defines the elements needed to render a service.
|
||||
type Spec struct {
|
||||
Label string
|
||||
LogoURL string
|
||||
Description string
|
||||
URL string
|
||||
Metadata map[string]string
|
||||
LinkGroups []linkgroup.Spec
|
||||
}
|
||||
|
||||
// LogoURL configures the icon for the service.
|
||||
func LogoURL(url string) Option {
|
||||
return func(spec *Spec) {
|
||||
spec.LogoURL = url
|
||||
}
|
||||
}
|
||||
|
||||
// Description specifies a short, brief description about the service.
|
||||
func Description(description string) Option {
|
||||
return func(spec *Spec) {
|
||||
spec.Description = description
|
||||
}
|
||||
}
|
||||
|
||||
// URL configures the services public facing URL that's presented to end users.
|
||||
func URL(url string) Option {
|
||||
return func(spec *Spec) {
|
||||
spec.URL = url
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata allows additional metadata to be attached to a service.
|
||||
func Metadata(key, value string) Option {
|
||||
return func(spec *Spec) {
|
||||
spec.Metadata[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// LinkGroup appends a group of links to the provided service.
|
||||
func LinkGroup(label string, options ...linkgroup.Option) Option {
|
||||
return func(spec *Spec) {
|
||||
spec.LinkGroups = append(spec.LinkGroups, linkgroup.New(label, options...))
|
||||
}
|
||||
}
|
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 406 KiB |
Loading…
x
Reference in New Issue
Block a user