diff --git a/Makefile b/Makefile index 16580e8e4..323644f93 100644 --- a/Makefile +++ b/Makefile @@ -44,13 +44,13 @@ fmt: ## Format your code with gofmt $(GOFMT) -w . yamlfmt: ## Format your code with yamlfmt -ifeq (, $(shell which yamlfmt)) +ifeq (, $(shell which yamlfmt 2>/dev/null )) go install github.com/google/yamlfmt/cmd/yamlfmt@v0.9.0 endif yamlfmt . addlicense: ## Add licence header to all files -ifeq (, $(shell which addlicense)) +ifeq (, $(shell which addlicense 2>/dev/null )) go install github.com/google/addlicense@latest endif addlicense -f LICENSE-HEADER . @@ -89,13 +89,13 @@ ifneq (, $(shell $(GOFMT) -l . )) @echo "Please run '$(CYAN)make fmt$(RESET)' to format your code" @exit 1 endif -ifeq (, $(shell which staticcheck)) +ifeq (, $(shell which staticcheck 2>/dev/null )) go install honnef.co/go/tools/cmd/staticcheck@latest endif staticcheck ./... lint-yaml: ## Check the yaml is valid and correctly formatted -ifeq (, $(shell which yamlfmt)) +ifeq (, $(shell which yamlfmt 2>/dev/null )) go install github.com/google/yamlfmt/cmd/yamlfmt@v0.9.0 endif @echo "yamlfmt --quiet --lint ." @@ -103,7 +103,7 @@ endif || ( echo "Please run '$(CYAN)make yamlfmt$(RESET)' to fix it (if a format error)" && exit 1 ) lint-license-header: ## Check if all files have the license header -ifeq (, $(shell which addlicense)) +ifeq (, $(shell which addlicense 2>/dev/null )) go install github.com/google/addlicense@latest endif @echo "addlicense -check -f LICENSE-HEADER -ignore coverage/** ." @@ -140,7 +140,7 @@ env: ## Print useful environment variables to stdout @echo '$$(FILES#) :' $(shell echo $(FILES) | wc -w) setup: ## Setup some dev dependencies (eg: pre-commit) -ifeq (, $(shell which pre-commit)) +ifeq (, $(shell which pre-commit 2>/dev/null )) @echo "pre-commit is not installed. Check $(CYAN)https://pre-commit.com/#install$(RESET)" endif pre-commit install --install-hooks diff --git a/README.md b/README.md index 2f56bf185..da9b0504e 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,14 @@ tsuru is a command line for application developers on [tsuru](https://github.com/tsuru/tsuru). + +## Tsuru plugins + +Tsuru plugins are the standard way to extend tsuru-client functionality transparently. +Installing and using a plugin is done with: +``` +tsuru plugin install +tsuru +``` + +For developing a custom plugin, read about [Developing Tsuru Plugins](./pkg/cmd/plugin.md). diff --git a/internal/exec/exec_test.go b/internal/exec/exec_test.go index 4687d0f16..0e2148e2a 100644 --- a/internal/exec/exec_test.go +++ b/internal/exec/exec_test.go @@ -92,6 +92,8 @@ func TestOsExec(t *testing.T) { func init() { if runtime.GOOS == "windows" { - endLine = "\r\n" + // windows ends echo with \n or \r\n ?!? + endLine = "\n" + // endLine = "\r\n" } } diff --git a/pkg/cmd/plugin.md b/pkg/cmd/plugin.md new file mode 100644 index 000000000..804313f26 --- /dev/null +++ b/pkg/cmd/plugin.md @@ -0,0 +1,80 @@ +# Developing Tsuru Plugins + +Tsuru plugins are the standard way to extend tsuru-client functionality transparently. + +A tsuru-client plugin is any runnable file, located inside `~/.tsuru/plugins` directory. +It works by finding the runnable file (with or without extension) with that plugin name. + +A simple working example: +```bash +cat > ~/.tsuru/plugins/myplugin.sh <<"EOF" +#!/bin/sh +echo "Hello from tsuru plugin ${TSURU_PLUGIN_NAME}!" +echo " called with args: $@" +EOF + +chmod +x ~/.tsuru/plugins/myplugin.sh + +tsuru myplugin subcommands -flags + +##### printed: +# Hello from tsuru plugin myplugin! +# called with args: subcommands -flags +``` + +You may find available tsuru plugins on github, by searching for the topic [`tsuru-plugin`](https://github.com/topics/tsuru-plugin). +(If you are developing a plugin, please tag your github repo). + +## Distributing a tsuru plugin + +The best way to distribute a tsuru plugin is making it compatible with `tsuru plugin install`. +There are different approaches for distributing the plugin, +depending on the language used for building it. + +### script-like single file +If the plugin is bundled as a **script-like single file** (eg: shell script, python, ruby, etc...) +you may make it available for download on a public URL. +The name of the file is irrelevant on this case. + +### bundle of multiple files +If the plugin is bundled as **multiple files**, you should compact them inside a `.tar.gz` or `.zip` file, +and make it available for download on a public URL. +In this case, the file entrypoint must has the same name as the plugin (file extension is optional). +The CLI will call the binary at `~/.tsuru/plugins/myplugin/myplugin[.ext]`. + +### compiled binary +If the plugin is bundled as a **compiled binary**, you should create a `manifest.json` file +(as defined on issue [#172](https://github.com/tsuru/tsuru-client/issues/172)) +which tells where to download the appropriate binary: +```json +{ + "SchemaVersion": "1.0", + "Metadata": { + "Name": "", + "Version": "" + }, + "UrlPerPlatform": { + "/": "", + ... + } +} +``` + +Each supported os/arch (check the latest release), should be compacted as `.tar.gz` or `.zip` file. +All files (`manifest.json` and all compacted binaries) must available for download on public URLs. + +An example of such a plugin, hosted on github, is the +[`rpaasv2` plugin](https://github.com/tsuru/rpaas-operator/issues/124), +installed using this [manifest.json](https://github.com/tsuru/rpaas-operator/releases/latest/download/manifest.json). + +## Available ENV variables + +When a plugin is called, the main tsuru-client passes some additional environment variables: + +| env | description | +| ----------------- | ------------------------------------------------------ | +| TSURU_TARGET | tsuru server url (eg: https://tsuru.io) | +| TSURU_TOKEN | tsuru authentication token | +| TSURU_VERBOSITY | 0: default, 1: log requests, 2: log responses | +| TSURU_FORMAT | output format (json, table, etc...) as in [printer.OutputFormat](../printer/printer.go) | +| TSURU_PLUGIN_NAME | name called from the main tsuru-client |