From 1bdfc9e612a21cba3e23312df97a10836ae0b737 Mon Sep 17 00:00:00 2001 From: Tom Wright Date: Wed, 15 Apr 2020 19:26:21 +0100 Subject: [PATCH] Get docker running properly and add more logging --- Dockerfile | 71 ++++++++++++++++++++++------- README.md | 9 ++++ cmd/app/main.go | 10 +++-- internal/cache.go | 6 ++- internal/generator.go | 76 ++++++++++++++++++-------------- internal/http.go | 3 ++ mermaidcli/puppeteer-config.json | 5 +++ 7 files changed, 126 insertions(+), 54 deletions(-) create mode 100644 mermaidcli/puppeteer-config.json diff --git a/Dockerfile b/Dockerfile index f38de88..108d443 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] + diff --git a/README.md b/README.md index 33862c8..516624e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cmd/app/main.go b/cmd/app/main.go index 4c9e6db..3a115dd 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -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") } diff --git a/internal/cache.go b/internal/cache.go index 9c55000..2c57da7 100644 --- a/internal/cache.go +++ b/internal/cache.go @@ -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 diff --git a/internal/generator.go b/internal/generator.go index 514ba4f..1419c15 100644 --- a/internal/generator.go +++ b/internal/generator.go @@ -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 diff --git a/internal/http.go b/internal/http.go index 4cb1611..ccb28c6 100644 --- a/internal/http.go +++ b/internal/http.go @@ -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) diff --git a/mermaidcli/puppeteer-config.json b/mermaidcli/puppeteer-config.json new file mode 100644 index 0000000..486eea3 --- /dev/null +++ b/mermaidcli/puppeteer-config.json @@ -0,0 +1,5 @@ +{ + "args": [ + "--no-sandbox" + ] +} \ No newline at end of file