Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Menet committed Aug 3, 2023
0 parents commit 056ae6d
Show file tree
Hide file tree
Showing 14 changed files with 1,499 additions and 0 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Test and build Go
on: [push, pull_request]
jobs:

lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.20.x'
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v3

build:
name: Build
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
go-version: [1.20.x]
steps:

- name: Install Go
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}

- name: Check out code into the Go module directory
uses: actions/checkout@v3

- name: Get dependencies
run: |
go get -v -t -d ./...
- name: Test
run: go test -v .

- name: Build
run: go build -v .
27 changes: 27 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: goreleaser
on:
create:
tags:
- 'v*.*.*'

jobs:
releaser:
name: Release on GitHub
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 changes: 28 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
before:
hooks:
- go mod download
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
goarch:
- 386
- amd64
- arm
- arm64
archives:
- format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
82 changes: 82 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# sysdoc

`sysdoc` allows to model system dependencies in a text based manner and process these information in an automated way.

## Installation

Head to https://github.com/unprofession-al/sysdoc/releases/latest and grab the release fitting your need. Unpack the archive
and put the `sysdoc` binary somewhere in your `$PATH`.

## Usage

### Document the System

`sysdoc` reads a directory structure and finds files in this structure which are expected to describe your system architecture.
It assumes a hirachical structure (as for example the [C4 Model](https://c4model.com/) suggests) where every folder represents
layer. Each layer (and therefore folder) can contain a Frontmatter file (Markdown with a YAML header, usually a `README.md` file)
do describe the entity of the given layer.

_Details about the documentation format of system need to be documented here_

### Use the Documentation with `sysdoc`

```
# sysdoc -h
sysdoc allows to document dependencies between systems
Usage:
sysdoc [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
render renders system documentation in a given template to standard output
serve renders system documentation in a given d2lang template to a svg file and serves it over http
svg renders system documentation in a given d2lang template to a svg file
version Print version info
Flags:
--base string base directory of the sysdoc definitions (default ".")
--config string configuration file path (default "./sysdoc.yaml")
--focus strings elements to be focussed
--glob string glob to find sysdoc definitions (default "README.md")
-h, --help help for sysdoc
Use "sysdoc [command] --help" for more information about a command.
```

### Writing Renderers


_Details about the templating of renderers need to be documented here_

## Why `sysdoc`?

Commonly there are a few ways to document a system architecture:

__No documentation whatsoever:__ This is bad for apparent reasons.

__Some wiki pages:__ This is a good and easy approach but requires a decent amount of sustained editorial work to keep things
up to date. Particularly dependencies between systems owned by differend parties seem to be hard to get right and even harder
to keep maintained. Also, no added benefit is generated with an up to date documetation (other than for the documtations sake)
as the data is unstructured and usually hard to access from third party applications.

__Self-generated documentation:__ In an ideal setting, documentation is generated either by the software itself or by its run
time (kubernetes and some service mesh can do a pretty neat job with this). This should be the goal of a green field project
but seems to be an unrealistic effort for most project that are build ontop of some legacy or around some software that is not
build in-house/consumend as SaaS/of-the-shelve and therefore not exactly built to spec.

__Specialist tool:__ There are a bunch of [specialist tools](https://content.ardoq.com/en/gartner-magic-quadrant-for-enterprise-architecture-tools)
available to create a proper enterprise architecture documentation. These tools often cost a good amount of money to use and are a bit
overkill in many situations.

`sysdoc` attempts to provide an low profile, relatively easy and cost efficient way to document your system environment. It is
based on Markdown (with structured data provided as YAML or JSON), which allows you to keep everything neatly under control with
a SCM of your choice. As your system descriptions are provided in simple Markdown, the documentation is already usable as is, as
for example GitHub can render the information in a user consumable way. However, as the meta data of your systems cosist of stuctured
data (in particular the interfaces of a system and the dependencies from systems to interfaces of other systems), more information
can be generated. For example, the following questions can be answered quite easily:

- A system experiences a downtime. Which other systems are affected?
- A system will be subject of change. What dependencies do we have to take care of?
- ...
209 changes: 209 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package main

import (
"fmt"
"net/http"
"os"
"strings"

"github.com/carlmjohnson/versioninfo"
"github.com/spf13/cobra"
)

type App struct {
flags struct {
configfile string
base string
glob string
focus []string
render struct {
renderer string
}
svg struct {
renderer string
out string
}
serve struct {
renderer string
listener string
}
}

// entry point
Execute func() error
}

func NewApp() *App {
a := &App{}
appName := "sysdoc"

// root
rootCmd := &cobra.Command{
Use: appName,
Short: "sysdoc allows to document dependencies between systems",
}
rootCmd.PersistentFlags().StringVar(&a.flags.configfile, "config", "./sysdoc.yaml", "configuration file path")
rootCmd.PersistentFlags().StringVar(&a.flags.base, "base", ".", "base directory of the sysdoc definitions")
rootCmd.PersistentFlags().StringVar(&a.flags.glob, "glob", "README.md", "glob to find sysdoc definitions")
rootCmd.PersistentFlags().StringSliceVar(&a.flags.focus, "focus", []string{}, "elements to be focussed")
a.Execute = rootCmd.Execute

// render
renderCmd := &cobra.Command{
Use: "render",
Short: "renders system documentation in a given template to standard output",
Run: a.renderCmd,
}
renderCmd.PersistentFlags().StringVar(&a.flags.render.renderer, "renderer", "default", "name of the renederer (set of templates in the configuration file)")
rootCmd.AddCommand(renderCmd)

// svg
svgCmd := &cobra.Command{
Use: "svg",
Short: "renders system documentation in a given d2lang template to a svg file",
Run: a.svgCmd,
}
svgCmd.PersistentFlags().StringVar(&a.flags.svg.renderer, "renderer", "default", "name of the renederer (set of templates in the configuration file)")
svgCmd.PersistentFlags().StringVar(&a.flags.svg.out, "out", "sysdoc.svg", "name of the file to be written")
rootCmd.AddCommand(svgCmd)

// serve
serveCmd := &cobra.Command{
Use: "serve",
Short: "renders system documentation in a given d2lang template to a svg file and serves it over http",
Long: `With the subcommnad 'serve', a small web server is launched locally. Access the server via
browser and get a rendered SVG picture of your system architecture. The server provides
a simple API to change the focus as well as the renderer on the fly:
- To change the focus provide a list of elements, where every element is separated with a '+'.
- To switch the renderer provide the 'renderer' query parameter.
For example, focus on the elements 'A.AB' and 'C' and render the output with a renderer
called 'custom' using the following URL: http://localhost:8080/A.AB+C?renderer=custom`,
Run: a.serveCmd,
}
serveCmd.PersistentFlags().StringVar(&a.flags.serve.listener, "listener", "127.0.0.1:8080", "listener to be used by the http server")
serveCmd.PersistentFlags().StringVar(&a.flags.serve.renderer, "renderer", "serve", "name of the default renderer to be used")
rootCmd.AddCommand(serveCmd)

// version
versionCmd := &cobra.Command{
Use: "version",
Short: "Print version info",
Run: a.versionCmd,
}
rootCmd.AddCommand(versionCmd)

return a
}

func (a *App) renderCmd(cmd *cobra.Command, args []string) {
cfg, err := NewConfig(a.flags.configfile)
exitOnErr(err)

// build system
sys, errs := build(a.flags.base, a.flags.glob, a.flags.focus)
exitOnErr(errs...)

// render template
renderer, ok := cfg.Renderer[a.flags.render.renderer]
if !ok {
exitOnErr(fmt.Errorf("renderer %s not specified in %s", a.flags.render.renderer, a.flags.configfile))
}
out, err := render(sys, renderer)
exitOnErr(err)

fmt.Println(out)
}

func (a *App) svgCmd(cmd *cobra.Command, args []string) {
cfg, err := NewConfig(a.flags.configfile)
exitOnErr(err)

// build system
sys, errs := build(a.flags.base, a.flags.glob, a.flags.focus)
exitOnErr(errs...)

// render template
renderer, ok := cfg.Renderer[a.flags.svg.renderer]
if !ok {
exitOnErr(fmt.Errorf("renderer %s not specified in %s", a.flags.svg.renderer, a.flags.configfile))
}
out, err := render(sys, renderer)
exitOnErr(err)

// create svg
img, err := svg(out)
exitOnErr(err)

err = os.WriteFile(a.flags.svg.out, img, 0644)
exitOnErr(err)

fmt.Printf("file '%s' written...\n", a.flags.svg.out)
}

func (a *App) serveCmd(cmd *cobra.Command, args []string) {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
var focus []string
for _, e := range strings.Split(r.URL.Path, "+") {
focus = append(focus, strings.Trim(e, "/"))
}

rendererName := r.URL.Query().Get("renderer")
if rendererName == "" {
rendererName = a.flags.serve.renderer
}

cfg, err := NewConfig(a.flags.configfile)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}

// build system
sys, errs := build(a.flags.base, a.flags.glob, focus)
if len(errs) > 0 {
w.WriteHeader(http.StatusInternalServerError)
out := ""
for _, err = range errs {
out += fmt.Sprintf("%s\n", err.Error())
}
_, _ = w.Write([]byte(out))
return
}

// render template
renderer, ok := cfg.Renderer[rendererName]
if !ok {
exitOnErr(fmt.Errorf("renderer %s not specified in %s", rendererName, a.flags.configfile))
}
out, err := render(sys, renderer)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}

// create svg
img, err := svg(out)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}

w.Header().Set("Content-Type", "image/svg+xml")
_, _ = w.Write(img)
})

fmt.Printf("server listening on http://%s/, hit CTRL-C to stop server...\n", a.flags.serve.listener)
_ = http.ListenAndServe(a.flags.serve.listener, nil)
}

func (a *App) versionCmd(cmd *cobra.Command, args []string) {
fmt.Println("Version: ", versioninfo.Version)
fmt.Println("Revision: ", versioninfo.Revision)
fmt.Println("DirtyBuild:", versioninfo.DirtyBuild)
fmt.Println("LastCommit:", versioninfo.LastCommit)
}
Loading

0 comments on commit 056ae6d

Please sign in to comment.