Note: this is a heavy work in progress. YMMV. Should we change the name? It's a "cute" name but doesn't really help end-users and could be seen as fetishisation of Japanese culture. New name suggestions welcome!
A method to pre-package and ship applications on Dokku.
While Ansible is all well and good, having something native to Dokku for shipping applications is awesome. The omakase
package allows users to specify exactly what it means to be an app, while allowing for some minimal customization (which is against the omakase
spirit but here we are).
This package provides the above functionality by exposing the modules from ansible-dokku
within a single Golang binary. Users of ansible-dokku
based task lists should be able to use their existing tasks with minimal changes, while organizations can decide to expose apps in easy to use methods for their users.
go build
Create a tasks.yml
file:
---
- tasks:
- dokku_app:
app: inflector
- dokku_sync:
app: inflector
repository: http://github.com/cakephp/inflector.cakephp.org
Run it:
# from the same directory as the tasks.yml
omakase
A task file can also be specified via flag, and may be a file retrieved via http:
# alternate path
omakase --tasks path/to/task.yml
# html file
omakase --tasks http://dokku.com/omakase/example.yml
Some other ideas:
- This could be automatically applied from within a repository if a .dokku/tasks.yml was found. In such a case, certain tasks would be added to a denylist and would be ignored during the run (such as dokku_app or dokku_sync).
- Dokku may expose a command such as dokku app:install that would allow users to invoke omakase to install apps.
- A web ui could expose a web ui to customize remote task files and then call
omakase
directly on the generated output.
Each app recipe can have custom inputs as specified in the tasks.yml
. Inputs should not reference any variable context, and are extracted using a two-phase parsing method (extract-then-inject).
---
- inputs:
- name: name
default: "inflector"
description: "Name of app to be created"
required: true
tasks:
- dokku_app:
app: {{ .name }}
- dokku_sync:
app: {{ .name }}
repository: http://github.com/cakephp/inflector.cakephp.org
With the above, the following method is used to override the name
variable. Omitting will use the default value.
# from the same directory as the tasks.yml
omakase --name lollipop
Any inputs for a given task file will also show up in the --help
output.
Inputs are injected using golang's text/template
package via the gliderlabs/sigil
library, and as such have access to everything gliderlabs/sigil
does.
Inputs can have the following properties:
- name:
- type:
string
- default: ``
- type:
- default:
- type:
bool|float|int|string
- default: zero-value for the type
- type:
- description:
- type:
string
- default:
""
- type:
- required:
- type:
bool
- default:
false
- type:
- type:
- type: string
- default
string
- options:
bool
float
int
string
If all inputs are specified on the CLI, then they are injected as is. Otherwise, unless the --no-interactive
flag is specified, omakase
will ask for values for each input, with the cli-specified values merged onto the task file default values as defaults.
Finally, the following input keys are reserved for internal usage:
help
tasks
v
version
All implemented tasks should closely follow those available via the ansible-dokku
library. Additionally, omakase
will expose a few custom tasks that are specific to this package to ease migration from pure ansible.
Tasks will have both a name
and an execution context, where the context maps to a single implemented modules. Tasks can be templated out via the variables from the inputs
section, and may also use any functions exposed by gliderlabs/sigil
.
Task executors should be added by creating an tasks/${TASK_NAME}_task.go
. The Task name should be lower_underscore_case
. By way of example, a tasks/lollipop_task.go
would contain the following:
package main
type LollipopTask struct {
App string `required:"true" yaml:"app"`
State string `required:"true" yaml:"state" default:"present"`
}
func (t LollipopTask) DesiredState() string {
return t.State
}
func (t LollipopTask) Execute() (string, error) {
return "", nil
}
func (t *LollipopTask) SetDefaultDesiredState(state string) {
if t.State == "" {
t.State = state
}
}
func init() {
RegisterTask(&LollipopTask{})
}
The LollipopTask
struct contains the fields necessary for the task. The only necessary field is State
, which holds the desired state of the task. All other fields are completely custom for the task at hand.
The DesiredState()
function must return t.State
.
The Execute()
function should actually execute the task. The return values:
string
: a string holding the current stateerror
: Whether an error occurred during processing
Todo: How do we expose stdout? Should the Status object actually be more structured? Should it serialize to json directly for use by ansible?
The init()
function registers the task for usage within a recipe.