Skip to content

Commit

Permalink
Merge 8547860 into f2835ea
Browse files Browse the repository at this point in the history
  • Loading branch information
bauglir committed Jul 30, 2020
2 parents f2835ea + 8547860 commit 0e6bb7c
Show file tree
Hide file tree
Showing 8 changed files with 509 additions and 37 deletions.
19 changes: 19 additions & 0 deletions docs/src/api.md
Expand Up @@ -11,6 +11,14 @@ Kroki
Diagram
```

### Service Management

```@autodocs
Modules = [ Kroki.Service ]
Order = [ :module, :type, :function ]
Filter = name -> "$name" !== "executeDockerCompose"
```

### Shorthands

```@autodocs
Expand All @@ -34,3 +42,14 @@ render
```@autodocs
Modules = [ Kroki.Documentation ]
```

### Service Management

```@autodocs
Modules = [ Kroki.Service ]
Order = [ :constant ]
```

```@docs
Kroki.Service.executeDockerCompose
```
5 changes: 4 additions & 1 deletion docs/src/index.md
Expand Up @@ -27,7 +27,10 @@ Kroki provides support for a wide array of diagramming languages such as
[many more](https://kroki.io/#support). The package can be configured to use
the publicly hosted server at [https://kroki.io](https://kroki.io) or
[self-hosted instances](https://docs.kroki.io/kroki/setup/install/) (see
[`render`](@ref) for configuration instructions).
[`setEndpoint!`](@ref Kroki.Service.setEndpoint!) for configuration
instructions). A basic configuration file (`docker-services.yml`) for [Docker
Compose](https://docs.docker.com/compose/) is available in the `support` folder
of the package for those interested in self-hosting the service.

The aim of the package is to make it easy to integrate descriptive diagrams
within code and docstrings (rendered as text), while upgrading the diagrams to
Expand Down
10 changes: 4 additions & 6 deletions src/Kroki.jl
Expand Up @@ -18,6 +18,9 @@ include("./kroki/documentation.jl")
using .Documentation
@setupDocstringMarkup()

include("./kroki/service.jl")
using .Service: ENDPOINT

"""
A representation of a diagram that can be rendered by a Kroki service.
Expand Down Expand Up @@ -134,11 +137,6 @@ RenderError(::Diagram, exception::Exception) = exception
Renders a [`Diagram`](@ref) through a Kroki service to the specified output
format.
A `KROKI_ENDPOINT` environment variable can be set, specifying the URI of a
specific instance of Kroki to use (e.g. when using a [privately hosted
instance](https://docs.kroki.io/kroki/setup/install/)). By default the
[publicly hosted service](https://kroki.io) is used.
If the Kroki service responds with an error throws an
[`InvalidDiagramSpecificationError`](@ref) or
[`InvalidOutputFormatError`](@ref) if a know type of error occurs. Other errors
Expand All @@ -152,7 +150,7 @@ render(diagram::Diagram, output_format::AbstractString) =
"GET",
join(
[
get(ENV, "KROKI_ENDPOINT", "https://kroki.io"),
ENDPOINT[],
lowercase("$(diagram.type)"),
output_format,
UriSafeBase64Payload(diagram),
Expand Down
172 changes: 172 additions & 0 deletions src/kroki/service.jl
@@ -0,0 +1,172 @@
"""
Defines functions and constants managing the Kroki service the rest of the
package uses to render diagrams. These services can be either local or remote.
This module also enables management of a local service instance, provided
[Docker](https://docker.com) and [Docker
Compose](https://docs.docker.com/compose/) are available on the system.
"""
module Service

using ..Documentation
@setupDocstringMarkup()

"""
The default [`ENDPOINT`](@ref) to use, i.e. the [publicly hosted
version](https://kroki.io).
"""
const DEFAULT_ENDPOINT = "https://kroki.io"

"""
A specialized `Exception` to include reporting instructions for specific types
of errors that may occur while trying to execute `docker-compose`.
"""
struct DockerComposeExecutionError <: Exception
message::String
end
Base.showerror(io::IO, error::DockerComposeExecutionError) = print(
io,
"""
An error occurred while executing `docker-compose`.
This may be caused by a change in its interface. If you believe this error to
be caused by Kroki.jl itself instead of a configuration error on the system,
please file an issue through https://github.com/bauglir/Kroki.jl/issues/new
including the error message below and a description of when the error occurred.
The reported error was:
$(error.message)
""",
)

"The currently active Kroki service endpoint being used."
const ENDPOINT = Ref{String}("https://kroki.io")

"Path to the Docker Compose definitions for running a local Kroki service."
const SERVICE_DEFINITION_FILE = realpath("$(@__DIR__)/../../support/docker-services.yml")

"""
Helper function for executing Docker Compose commands.
Returns captured stdout.
Throws an `ErrorException` if Docker and/or Docker Compose aren't available.
Throws a `DockerComposeExecutionError` if any other exception occurs during
execution.
"""
function executeDockerCompose(cmd::Vector{String})
captured_stderr = IOBuffer()
captured_stdout = IOBuffer()

try
run(pipeline(
`docker-compose --file $(SERVICE_DEFINITION_FILE) --project-name krokijl $cmd`;
stderr = captured_stderr,
stdout = captured_stdout,
))
catch exception
exception isa Base.IOError &&
throw(ErrorException("Missing dependencies! Docker and/or Docker Compose do not appear to be available"))

throw(DockerComposeExecutionError(String(take!(captured_stderr))))
end

String(take!(captured_stdout))
end
executeDockerCompose(cmd::String) = executeDockerCompose([cmd])
# The function should be called through indirection in cases where it needs to
# be mocked out in tests
const EXECUTE_DOCKER_COMPOSE = Ref{Any}(executeDockerCompose)

"""
Sets the [`ENDPOINT`](@ref) using a fallback mechanism if no `endpoint` is
provided.
The fallback mechanism checks for a `KROKI_ENDPOINT` environment variable
specifying an endpoint (e.g. to be used across Julia instances). If this
environment variable is also not present the [`DEFAULT_ENDPOINT`](@ref) is
used.
This can, for instance, be used in cases where a [privately hosted
instance](https://docs.kroki.io/kroki/setup/install/) is available or when a
local service has been [`start!`](@ref)ed.
Returns the value that [`ENDPOINT`](@ref) got set to.
# Examples
- `setEndpoint!()`
- `setEndpoint!("http://localhost:8000")`
"""
function setEndpoint!(
endpoint::AbstractString = get(ENV, "KROKI_ENDPOINT", DEFAULT_ENDPOINT),
)
ENDPOINT[] != endpoint && @info "Setting Kroki service endpoint to $(endpoint)."
ENDPOINT[] = String(endpoint)
end

"""
Starts the Kroki service components on the local system, optionally, ensuring
[`ENDPOINT`](@ref) points to them.
Pass `false` to the function to prevent the [`ENDPOINT`](@ref) from being
updated. The default behavior is to update.
"""
function start!(update_endpoint::Bool = true)
@info "Starting Kroki service components."
EXECUTE_DOCKER_COMPOSE[](["up", "--detach"])
update_endpoint && setEndpoint!("http://localhost:8000")
return
end

"""
Returns a `NamedTuple` where the keys are the names of the service components
and the values their corresponding 'running' state.
# Examples
```
julia> status()
(core = true, mermaid = false)
```
"""
function status()
states_to_bool = Pair{String, Bool}["running" => true, "stopped" => false]

services_by_state = map(states_to_bool) do (state, running)::Pair{String, Bool}
services_with_state =
EXECUTE_DOCKER_COMPOSE[](["ps", "--filter", "status=$state", "--services"])

service_symbols = Symbol.(split(services_with_state, '\n'; keepempty = false))

(; zip(service_symbols, Iterators.repeated(running))...)
end

merge(services_by_state...)
end

"""
Stops any running Kroki service components ensuring [`ENDPOINT`](@ref) no
longer points to the stopped service.
Cleans up left-over containers by default. This behavior can be turned off by
passing `false` to the function.
"""
function stop!(perform_cleanup::Bool = true)
@info "Stopping Kroki service components."
EXECUTE_DOCKER_COMPOSE[]("stop")
perform_cleanup && EXECUTE_DOCKER_COMPOSE[](["rm", "--force"])
setEndpoint!()
return
end

"Updates the Docker images for the individual Kroki service components."
function update!()
EXECUTE_DOCKER_COMPOSE[](["pull", "--quiet"])
return
end

# Ensure `ENDPOINT` has a valid value
__init__() = setEndpoint!()

end
18 changes: 18 additions & 0 deletions support/docker-services.yml
@@ -0,0 +1,18 @@
version: "3"
services:
core:
image: yuzutech/kroki
environment:
- KROKI_BPMN_HOST=bpmn
- KROKI_BLOCKDIAG_HOST=blockdiag
- KROKI_MERMAID_HOST=mermaid
# Enable inclusion of external PlantUML descriptions and Vega datasets
- KROKI_SAFE_MODE=unsafe
ports:
- 8000:8000
blockdiag:
image: yuzutech/kroki-blockdiag
bpmn:
image: yuzutech/kroki-bpmn
mermaid:
image: yuzutech/kroki-mermaid
1 change: 1 addition & 0 deletions test/Project.toml
@@ -1,2 +1,3 @@
[deps]
SimpleMock = "a896ed2c-15a5-4479-b61d-a0e88e2a1d25"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

0 comments on commit 0e6bb7c

Please sign in to comment.