Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin system and "renderers" #288

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/jk
/pkg/__std/*.go
/pkg/__std/lib/assets_vfsdata.go
/plugins/jk-plugin-echo/jk-plugin-echo
/std/internal/__std_generated.*
/std/node_modules
/std/dist
Expand Down
28 changes: 21 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
.PHONY: build-image std-install dep all install test api-reference FORCE

all: jk
PLUGINS = \
plugins/jk-plugin-echo/jk-plugin-echo \
$(NULL)

all: jk $(PLUGINS)

VERSION := $(shell git describe --tags)

jk: pkg/__std/lib/assets_vfsdata.go FORCE
ifneq ($(RW),yes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing the motivation here is that occasionally it's a pain to have it not build because you've introduced a new dependency. You can just issue go mod tidy when that's the case.

RO = -mod=readonly
endif

ifeq ($(STATIC),yes)
GO111MODULE=on go build -mod=readonly -a -tags netgo -o $@ -ldflags '-X main.Version=$(VERSION) -s -w -extldflags "-static"'
else
GO111MODULE=on go build -mod=readonly -o $@ -ldflags "-X main.Version=$(VERSION) -s -w"
A = -a
TAGS += -tags netgo
LDFLAGS += -extldflags "-static"
endif

jk: pkg/__std/lib/assets_vfsdata.go FORCE
GO111MODULE=on go build $(RO) $(A) $(TAGS) -o $@ -ldflags '-X main.Version=$(VERSION) -s -w $(LDFLAGS)'

pkg/__std/lib/assets_vfsdata.go: std/internal/__std_generated.ts std/dist/index.js
GO111MODULE=on go generate -mod=readonly ./pkg/__std/lib
GO111MODULE=on go generate $(RO) ./pkg/__std/lib

std/internal/__std_generated.ts: std/internal/*.fbs std/package.json std/generate.sh
std/generate.sh
Expand All @@ -31,10 +41,14 @@ $(module)/package.json: $(std_sources) std/internal/__std_generated.ts std/packa
cd std && npx tsc --declaration --emitDeclarationOnly --allowJs false --outdir ../$(module) || true
cp README.md LICENSE std/package.json std/internal/flatbuffers.d.ts $(module)

plugins/jk-plugin-echo/jk-plugin-echo: FORCE
GO111MODULE=on go build $(RO) $(A) $(TAGS) -o $@ -ldflags '-X main.Version=$(VERSION) -s -w $(LDFLAGS)' ./$(@D)

D := $(shell go env GOPATH)/bin
install: jk
install: all
mkdir -p $(D)
cp jk $(D)
$(foreach p,$(PLUGINS),cp $(p) $(D))

build-image:
docker build -t quay.io/justkidding/build -f build/Dockerfile build/
Expand Down
184 changes: 184 additions & 0 deletions docs/rfc/0002-plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Plugins

## Summary

We have found a number of use cases where we'd like to extend the
functionality of `jk` but don't want to link the core runtime with specific
libraries.

With a plugin system, we could implement these features outside of the
runtime and provide a simple way to extend `jk` and experiment with ideas.

The proposal is to:

- Use [go-plugin](https://github.com/hashicorp/go-plugin), the plugin system
underpinning terraform.
- Define a first integration point to extend `jk` functionality: `Render`
plugins.
- Implement a helm plugin in `@jkcfg/kubernetes` that renders helm charts and
make its Kubernetes objects available to the `jk` runtime for further
manipulation.

## Example

dlespiau marked this conversation as resolved.
Show resolved Hide resolved
1. A [helm](https://helm.sh/) `Renderer` plugin would render a helm chart
from values specified in `js` and return an array of Kubernetes objects.

1. A Dockerfile `Validator` plugin could validate Dockerfiles that we
write.

1. An [Open Policy Agent](https://www.openpolicyagent.org/) `Validator`
plugin would ensure a set of configuration files passes a policy written in
[Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)

## Motivation
dlespiau marked this conversation as resolved.
Show resolved Hide resolved

Let's take the helm `Renderer` example: the community is producing some
quality charts users would like to be able to reuse.

- It is counterproductive to start translating complex helm charts in `js`
instead of reusing them.
- It is often necessary to modify the Helm Chart Kubernetes objects in ways
the original authors haven't thought of. Importing them in `jk` allows just
that.

## Design

### General Flow

A call to a plugin a really an RPC call issued to a plugin server process.
`jk` is responsible for the life cycle of those plugin processes.

From an API point of view, it is envisioned that library would provide nice
wrapper objects. For instance the helm chart renderer could look like:

```js
const redis = new k8s.helm.Chart("redis", {
repo: "stable",
chart: "redis",
version: "3.10.0",
values: {
usePassword: true,
rbac: { create: true },
},
});

redis.render().then(std.log);
```

The `Chart` object, in this case, would be part of the `@jkcfg/kubernetes` library.

The `render()` function of the `Chart` is implemented with a standard library
RPC call that lands in the go part of `jk`. Then:

- `jk` checks if a helm renderer plugin is already running and spawns a new one if needed
- `jk` waits until the plugin process has started and is ready to accept RPCs.
- `jk` issues a RPC call to the plugin process
- The plugin process returns an answer
- The answer is serialized and sent back to the `js` vm

### The standard library `plugin` function

The core `plugin` RPC call is quite general:

```text
plugin(kind: string, url: string, input: JSON) -> JSON
```

- `kind` is the kind of plugin invoked (eg. `render`). Plugin binaries can
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does kind actually affect? I don't see it mentioned after here.

implement more than one kind of plugins.
- `url` identifies a plugin, for instance:
`https://jkcfg.github.io/plugins/helm/0.1.0/plugin.json`. This JSON file is
really plugin metadata (see next section). It is highly recommended, for
reproducibility, to ensure the plugin definition and binaries the URL points
to be immutable to encode a version in the URL.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"... to be to encode"

- `input` and the return value are generic JSON objects that wrapping library
code are responsible for understanding.

### Plugin definition

The plugin URL in the `plugin` call points to a JSON file describing the plugin:

```json
{
"name": "helm",
"version": "0.1.0",
"binaries": {
"linux-amd64": "https://jkcfg.github.io/plugins/helm/0.1.0/linux-amd64/jk-plugin-helm",
"darwin-amd64": "https://jkcfg.github.io/plugins/helm/0.1.0/darwin-amd64/jk-plugin-helm"
}
}
```

### Local plugins

For development purposes it is possible to point the `plugin` RPC call to a
local file by giving a relative path to the JSON plugin definition. The
`binaries` fields can point at plugins present in the `PATH`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another formulation of this would be to expect a relative path to the binary. This would make sense if the binaries are shipped with the code wrapping them, for example.


```json
{
"name": "echo",
"version": "0.1.0",
"binaries": {
"linux-amd64": "jk-plugin-echo",
"darwin-amd64": "jk-plugin-echo"
}
}
```

## Backward compatibility

New feature, no backward compatibility concerns.

## Drawbacks and limitations

### Complexity

Plugins do add more complexity to `jk`. Complexity creep is somewhat
unavoidable if we want to support things such as consuming helm charts. One
good thing about plugins is that, at the cost of a (simple) interface between
`jk` and plugins, most of the complexity is delegated to plugin code, not the
core.

We should ensure that plugins don't add any cognitive load on the user. By
wrapping plugin invocation in library objects we can make then mostly
transparent to the user with, maybe, the exception of plugin downloading.

## Hermeticity

Plugins have a high potential to break hermeticity. We should ensure our
plugins are made "in good faith", are self-contained and as deterministic as
possible.

I believe that's an ok price to pay for such extensibility power.

## Alternatives

- A generic `exec` standard library function that would execute any binary in
the path.

Problems with that approach:

1. Executing whatever is in the path doesn't play well with hermeticity.
Plugins have versions for reproducibility.
1. Packaging. One goal is to be able to package all the dependencies needed
for a `jk` script to run. Having the plugin abstraction with metadata
allows that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reckon it's worth mentioning | here -- i.e., no plugins, just pipe the output of another command into jk (or write files and use them as input to jk, etc.). One of the problems I remember from our discussions of this is that you can't have more than one source of input via |; another (slightly harder to pin down) is that it's better to have control from within JavaScript code, so you can e.g., encapsulate the fact of the external tooling.

## Unresolved questions

- How to download plugins. I'd like to have very little friction when using
plugins. `jk` should download plugins, somehow. This is linked to a more
general problem of downloading all needed dependencies to run a `jk` script.

- It would be nice to be able to cache dependencies/artifacts plugins needs
beyond the plugin binary itself. For instance, in the case of the helm
renderer, the plugin could cache the chart so subsequent `jk` runs don't need
to hit the network. While the plugin itself could do that caching, it'd be
even nicer if `jk` could help with that: the plugin could ask `jk` to cache
things on its behalf. We'd then be able to gather all dependencies in one
place for `jk` runs that don't hit the network at all.

- Similarly, `jk` could provide plugins a download API so downloading +
caching artifacts UX is consistent across plugins.
7 changes: 3 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,21 @@ module github.com/jkcfg/jk
require (
github.com/ghodss/yaml v1.0.0
github.com/google/flatbuffers v1.10.0
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd
github.com/hashicorp/go-plugin v1.0.1
github.com/hashicorp/hcl v1.0.0
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jkcfg/v8worker2 v0.0.0-20191022163158-90e467066938
github.com/pkg/errors v0.8.1
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3
github.com/stretchr/testify v1.2.2
github.com/stretchr/testify v1.3.0
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.1.0
golang.org/x/text v0.3.0
golang.org/x/tools v0.0.0-20190815212832-922a4ee32d1a // indirect
gopkg.in/yaml.v2 v2.2.1
)

go 1.13
30 changes: 28 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/flatbuffers v1.10.0 h1:wHCM5N1xsJ3VwePcIpVqnmjAqRXlR44gv4hpGi+/LIw=
github.com/google/flatbuffers v1.10.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd h1:rNuUHR+CvK1IS89MMtcF0EpcVMZtjKfPRp4MEmt/aTs=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE=
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jkcfg/v8worker2 v0.0.0-20190228210604-6677fe93c5c2 h1:yroXZIO0q4uBioF+qxfrstz58/0kCKGJsBFd1Vd2OYg=
github.com/jkcfg/v8worker2 v0.0.0-20190228210604-6677fe93c5c2/go.mod h1:V1TBZ48loRvHpVCQEQSt39QYggAjIWgCaA63Z7NuGPI=
github.com/jkcfg/v8worker2 v0.0.0-20191022163158-90e467066938 h1:5P29UIxG4BovHtyzcSarjn6sM0B54GhO2AU/T50Pr3s=
github.com/jkcfg/v8worker2 v0.0.0-20191022163158-90e467066938/go.mod h1:V1TBZ48loRvHpVCQEQSt39QYggAjIWgCaA63Z7NuGPI=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -24,23 +39,34 @@ github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg=
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190815212832-922a4ee32d1a h1:5qy6QUqKAbbhe26lVEbveBo0jK0xeNAAJ7gEi32NUkU=
golang.org/x/tools v0.0.0-20190815212832-922a4ee32d1a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.14.0 h1:ArxJuB1NWfPY6r9Gp9gqwplT0Ge7nqv9msgu03lHLmo=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
Expand Down
19 changes: 19 additions & 0 deletions pkg/plugin/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package plugin

import (
"runtime"
)

// Info holds plugin metadata.
type Info struct {
Name string `json:"name"`
Version string `json:"version"`
// Binaries maps `go env GOOS`-`go env GOARCH` strings to binary names.
// eg. "linux-amd64" -> "https://jkcfg.github.io/plugins/render/echo/0.1.0/jk-render-echo-linux-amd64"
Binaries map[string]string `json:"binaries"`
}

func (i *Info) binary() string {
k := runtime.GOOS + "-" + runtime.GOARCH
return i.Binaries[k]
}
Loading