Skip to content
This repository was archived by the owner on Jun 27, 2025. It is now read-only.
Merged
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
115 changes: 115 additions & 0 deletions command/dispatch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package command

import (
"fmt"
"io/ioutil"
"os"
"strings"

flaghelper "github.com/hashicorp/nomad/helper/flag-helpers"
"github.com/jrasell/levant/levant"
"github.com/jrasell/levant/logging"
)

// DispatchCommand is the command implementation that allows users to
// dispatch a Nomad job.
type DispatchCommand struct {
args []string
Meta
}

// Help provides the help information for the dispatch command.
func (c *DispatchCommand) Help() string {
helpText := `
Usage: levant dispatch [options] <parameterized job> [input source]

Dispatch creates an instance of a parameterized job. A data payload to the
dispatched instance can be provided via stdin by using "-" or by specifying a
path to a file. Metadata can be supplied by using the meta flag one or more
times.

General Options:

-address=<http_address>
The Nomad HTTP API address including port which Levant will use to make
calls.

-log-level=<level>
Specify the verbosity level of Levant's logs. Valid values include DEBUG,
INFO, and WARN, in decreasing order of verbosity. The default is INFO.

Dispatch Options:

-meta <key>=<value>
Meta takes a key/value pair separated by "=". The metadata key will be
merged into the job's metadata. The job may define a default value for the
key which is overridden when dispatching. The flag can be provided more
than once to inject multiple metadata key/value pairs. Arbitrary keys are
not allowed. The parameterized job must allow the key to be merged.
`
return strings.TrimSpace(helpText)
}

// Synopsis is provides a brief summary of the dispatch command.
func (c *DispatchCommand) Synopsis() string {
return "Dispatch an instance of a parameterized job"
}

// Run triggers a run of the Levant dispatch functions.
func (c *DispatchCommand) Run(args []string) int {

var meta []string
var addr, logLevel string

flags := c.Meta.FlagSet("dispatch", FlagSetVars)
flags.Usage = func() { c.UI.Output(c.Help()) }
flags.Var((*flaghelper.StringFlag)(&meta), "meta", "")
flags.StringVar(&addr, "address", "", "")
flags.StringVar(&logLevel, "log-level", "INFO", "")

if err := flags.Parse(args); err != nil {
return 1
}

args = flags.Args()
if l := len(args); l < 1 || l > 2 {
c.UI.Error(c.Help())
return 1
}

logging.SetLevel(logLevel)

job := args[0]
var payload []byte
var readErr error

if len(args) == 2 {
switch args[1] {
case "-":
payload, readErr = ioutil.ReadAll(os.Stdin)
default:
payload, readErr = ioutil.ReadFile(args[1])
}
if readErr != nil {
c.UI.Error(fmt.Sprintf("Error reading input data: %v", readErr))
return 1
}
}

metaMap := make(map[string]string, len(meta))
for _, m := range meta {
split := strings.SplitN(m, "=", 2)
if len(split) != 2 {
c.UI.Error(fmt.Sprintf("Error parsing meta value: %v", m))
return 1
}
metaMap[split[0]] = split[1]
}

success := levant.TriggerDispatch(job, metaMap, payload, addr)
if !success {
return 1
}

return 0
}
5 changes: 5 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
Meta: meta,
}, nil
},
"dispatch": func() (cli.Command, error) {
return &command.DispatchCommand{
Meta: meta,
}, nil
},
"render": func() (cli.Command, error) {
return &command.RenderCommand{
Meta: meta,
Expand Down
70 changes: 70 additions & 0 deletions levant/dispatch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package levant

import (
nomad "github.com/hashicorp/nomad/api"
"github.com/jrasell/levant/levant/structs"
"github.com/jrasell/levant/logging"
)

// TriggerDispatch provides the main entry point into a Levant dispatch and
// is used to setup the clients before triggering the dispatch process.
func TriggerDispatch(job string, metaMap map[string]string, payload []byte, address string) bool {

client, err := newNomadClient(address)
if err != nil {
logging.Error("levant/dispatch: unable to setup Levant dispatch: %v", err)
return false
}

// TODO: Potential refactor so that dispatch does not need to use the
// levantDeployment object. Requires client refactor.
dep := &levantDeployment{}
dep.config = &structs.Config{}
dep.nomad = client

success := dep.dispatch(job, metaMap, payload)
if !success {
logging.Error("levant/dispatch: dispatch of job %v failed", job)
return false
}

logging.Info("levant/dispatch: dispatch of job %v successful", job)
return true
}

// dispatch triggers a new instance of a parameterized job of the job
// resulting in a Nomad job which is monitored to determine the eventual
// state.
func (l *levantDeployment) dispatch(job string, metaMap map[string]string, payload []byte) bool {

// Initiate the dispatch with the passed meta parameters.
eval, _, err := l.nomad.Jobs().Dispatch(job, metaMap, payload, nil)
if err != nil {
logging.Error("levant/dispatch: %v", err)
return false
}

logging.Info("levant/dispatch: triggering dispatch against job %s", job)

// If we didn't get an EvaluationID then we cannot continue.
if eval.EvalID == "" {
logging.Error("levant/dispatch: dispatched job %s did not return evaluation", job)
return false
}

// In order to correctly run the jobStatusChecker we need to correctly
// assign the dispatched job ID/Name based on the invoked job.
l.config.Job = &nomad.Job{}
l.config.Job.ID = &eval.DispatchedJobID
l.config.Job.Name = &eval.DispatchedJobID

// Perform the evaluation inspection to ensure to check for any possible
// errors in triggering the dispatch job.
err = l.evaluationInspector(&eval.EvalID)
if err != nil {
logging.Error("levant/dispatch: %v", err)
return false
}

return l.jobStatusChecker(&eval.EvalID)
}