Get docker running properly and add more logging

This commit is contained in:
Tom Wright 2020-04-15 19:26:21 +01:00
parent 06b5694125
commit 1bdfc9e612
7 changed files with 126 additions and 54 deletions

View File

@ -1,19 +1,8 @@
# This stage builds the mermaidcli executable.
FROM node:12.12.0-buster as node
WORKDIR /root
# copy the mermaidcli node package into the container and install
COPY ./mermaidcli/* .
RUN npm install
# This stage builds the go executable.
FROM golang:1.13-buster as go
WORKDIR /root
COPY . .
COPY ./ ./
RUN go build -o bin/app cmd/app/main.go
@ -21,14 +10,66 @@ RUN go build -o bin/app cmd/app/main.go
# Final stage that will be pushed.
FROM debian:buster-slim
FROM node:12.12.0-buster-slim as node
WORKDIR /root
# copy the mermaidcli node package into the container and install
COPY ./mermaidcli/* ./
RUN npm install
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update 2>/dev/null && \
apt install -y --no-install-recommends \
ca-certificates \
gconf-service \
libasound2 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libc6 \
libcairo2 \
libcups2 \
libdbus-1-3 \
libexpat1 \
libfontconfig1 \
libgcc1 \
libgconf-2-4 \
libgdk-pixbuf2.0-0 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libstdc++6 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxss1 \
libxtst6 \
ca-certificates \
fonts-liberation \
libappindicator1 \
libnss3 \
lsb-release \
xdg-utils \
wget \
2>/dev/null
COPY --from=node /root/node_modules/.bin/mmdc ./mermaidcli
COPY --from=go /root/bin/app ./app
# We should now have all the required dependencies to build the proto files.
CMD ["./app", "--mermaid=./mermaidcli"]
RUN mkdir -p ./in
RUN mkdir -p ./out
RUN chmod 0777 ./in
RUN chmod 0777 ./out
CMD ["./app", "--mermaid=./node_modules/.bin/mmdc", "--in=./in", "--out=./out", "--puppeteer=./puppeteer-config.json"]

View File

@ -6,6 +6,15 @@ While this currently serves the diagrams via HTTP, it could easily be manipulate
## Basic usage
### Docker
Run the container:
```
docker run -d --name mermaid-server -p 80:80 tomwright/mermaid-server:latest
```
### Manually as a go command
Start the HTTP server:
```
go run cmd/app/main.go --mermaid=./mermaidcli/node_modules/.bin/mmdc --in=./in --out=./out

View File

@ -4,6 +4,7 @@ import (
"flag"
"fmt"
"github.com/tomwright/mermaid-server/internal"
"log"
"net/http"
"os"
)
@ -59,6 +60,7 @@ func main() {
mermaid := flag.String("mermaid", "", "The full path to the mermaidcli executable.")
in := flag.String("in", "", "Directory to store input files.")
out := flag.String("out", "", "Directory to store output files.")
puppeteer := flag.String("puppeteer", "", "Full path to optional puppeteer config.")
flag.Parse()
if *mermaid == "" {
@ -77,7 +79,7 @@ func main() {
}
cache := internal.NewDiagramCache()
generator := internal.NewGenerator(cache, *mermaid, *in, *out)
generator := internal.NewGenerator(cache, *mermaid, *in, *out, *puppeteer)
httpHandler := internal.GenerateHTTPHandler(generator)
@ -88,11 +90,11 @@ func main() {
Addr: ":80",
Handler: r,
}
_, _ = fmt.Fprintf(os.Stdout, "Listening on address %s", httpServer.Addr)
log.Printf("Listening on address %s", httpServer.Addr)
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
_, _ = fmt.Fprintf(os.Stderr, "Could not listen for http connections: %s", err)
log.Printf("Could not listen for http connections: %s", err)
os.Exit(1)
}
_, _ = fmt.Fprintf(os.Stdout, "Shutdown")
log.Printf("Shutdown")
}

View File

@ -1,5 +1,7 @@
package internal
import "fmt"
// DiagramCache provides the ability to cache diagram results.
type DiagramCache interface {
// Store stores a diagram in the cache.
@ -26,7 +28,7 @@ type inMemoryDiagramCache struct {
func (c *inMemoryDiagramCache) Store(diagram *Diagram) error {
id, err := diagram.ID()
if err != nil {
return err
return fmt.Errorf("cannot get diagram ID: %w", err)
}
c.idToDiagram[id] = diagram
return nil
@ -36,7 +38,7 @@ func (c *inMemoryDiagramCache) Store(diagram *Diagram) error {
func (c *inMemoryDiagramCache) Has(diagram *Diagram) (bool, error) {
id, err := diagram.ID()
if err != nil {
return false, err
return false, fmt.Errorf("cannot get diagram ID: %w", err)
}
if d, ok := c.idToDiagram[id]; ok && d != nil {
return true, nil

View File

@ -5,6 +5,8 @@ import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
)
@ -14,42 +16,44 @@ type Generator interface {
Generate(diagram *Diagram) error
}
func NewGenerator(cache DiagramCache, mermaidCLIPath string, inPath string, outPath string) Generator {
func NewGenerator(cache DiagramCache, mermaidCLIPath string, inPath string, outPath string, puppeteerConfigPath string) Generator {
return &cachingGenerator{
cache: cache,
mermaidCLIPath: mermaidCLIPath,
inPath: inPath,
outPath: outPath,
cache: cache,
mermaidCLIPath: mermaidCLIPath,
inPath: inPath,
outPath: outPath,
puppeteerConfigPath: puppeteerConfigPath,
}
}
// cachingGenerator is an implementation of Generator.
type cachingGenerator struct {
cache DiagramCache
mermaidCLIPath string
inPath string
outPath string
cache DiagramCache
mermaidCLIPath string
inPath string
outPath string
puppeteerConfigPath string
}
// Generate generates the given diagram.
func (c cachingGenerator) Generate(diagram *Diagram) error {
has, err := c.cache.Has(diagram)
if err != nil {
return err
return fmt.Errorf("cache.Has failed: %w", err)
}
if has {
cached, err := c.cache.Get(diagram)
if err != nil {
return err
return fmt.Errorf("cache.Get failed: %w", err)
}
*diagram = *cached
return nil
}
if err := c.generate(diagram); err != nil {
return err
return fmt.Errorf("cachingGenerater.generate failed: %w", err)
}
if err := c.cache.Store(diagram); err != nil {
return err
return fmt.Errorf("cache.Store failed: %w", err)
}
return nil
}
@ -58,35 +62,41 @@ func (c cachingGenerator) Generate(diagram *Diagram) error {
func (c cachingGenerator) generate(diagram *Diagram) error {
id, err := diagram.ID()
if err != nil {
return err
}
has, err := c.cache.Has(diagram)
if err != nil {
return err
}
if has {
cached, err := c.cache.Get(diagram)
if err != nil {
return err
}
*diagram = *cached
return nil
return fmt.Errorf("cannot get diagram ID: %w", err)
}
inPath := fmt.Sprintf("%s/%s.mmd", c.inPath, id)
outPath := fmt.Sprintf("%s/%s.svg", c.outPath, id)
if err := ioutil.WriteFile(inPath, diagram.description, 0644); err != nil {
return err
return fmt.Errorf("could not write to input file [%s]: %w", inPath, err)
}
cmd := exec.Command(c.mermaidCLIPath, "-i", inPath, "-o", outPath)
var stdOut bytes.Buffer
cmd.Stdout = bufio.NewWriter(&stdOut)
if err := cmd.Run(); err != nil {
return fmt.Errorf("%w: %s", err, string(stdOut.Bytes()))
_, err = os.Stat(c.mermaidCLIPath)
if os.IsNotExist(err) {
return fmt.Errorf("mermaid executable does not exist: %w", err)
}
if err != nil {
return fmt.Errorf("could not stat mermaid executable: %w", err)
}
args := []string{
"-i", inPath,
"-o", outPath,
}
if c.puppeteerConfigPath != "" {
args = append(args, "-p", c.puppeteerConfigPath)
}
cmd := exec.Command(c.mermaidCLIPath, args...)
var stdOut bytes.Buffer
var stdErr bytes.Buffer
cmd.Stdout = bufio.NewWriter(&stdOut)
cmd.Stderr = bufio.NewWriter(&stdErr)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed when executing mermaid: %w: %s: %s", err, string(stdOut.Bytes()), string(stdErr.Bytes()))
}
log.Printf("generated with output: %s: %s", string(stdOut.Bytes()), string(stdErr.Bytes()))
diagram.Output = outPath

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
@ -30,6 +31,8 @@ func writeSVG(rw http.ResponseWriter, data []byte, status int) {
}
func writeErr(rw http.ResponseWriter, err error, status int) {
log.Printf("[%d] %s", status, err)
writeJSON(rw, map[string]interface{}{
"error": err,
}, status)

View File

@ -0,0 +1,5 @@
{
"args": [
"--no-sandbox"
]
}