-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Daniel Menet
committed
Aug 3, 2023
0 parents
commit 056ae6d
Showing
14 changed files
with
1,499 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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? | ||
- ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.