port to new repo

This commit is contained in:
Mya 2022-05-21 08:32:18 -05:00
commit 8e4d7ddd72
No known key found for this signature in database
GPG Key ID: C3ECFA648DAD27FA
11 changed files with 492 additions and 0 deletions

21
LICENSE Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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...))
}
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/mjpitz/emc
go 1.18

0
go.sum Normal file
View File

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB