Created
August 11, 2019 07:38
-
-
Save nilium/fce51f20bd98832868c5bcb9f4220d94 to your computer and use it in GitHub Desktop.
Bash script and jq module to make it easier to make small adjustments to Nomad jobs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Copyright Noel Cower 2019. | |
# Distributed under the Boost Software License, Version 1.0. | |
# (See accompanying file LICENSE_1_0.txt or copy at | |
# https://www.boost.org/LICENSE_1_0.txt) | |
# Usage: nomad-tweak <job> [options...] <jq-script> | |
# | |
# Options: | |
# -A, --apply | |
# Apply changes made to the job by sending an update request. | |
# -D, --dryrun (default) | |
# Do not apply changes made to the job and instead print the updated JSON. | |
# -r, -c, -S, --raw, --compact, ... | |
# Most jq arguments. | |
# VAR=VAL | |
# Shorthand for --arg VAR VAL. If VAL can be parsed as JSON, it is passed | |
# using --argjson instead. | |
# VAR=@FILE | |
# Same as VAR=VAL, but VAL is loaded from FILE. | |
# | |
# The jq-script passed is always prefixed with 'include "nomad";', and as such, | |
# all nomad.jq functions are available in the global namespace. | |
# | |
# Example: simple script to scale a Nomad job. | |
# This script could use more filtering of arguments, such as to avoid someone | |
# passing a '--' ahead of the script, but works as an example. | |
# | |
# #!/bin/bash | |
# # nomad-scale JOB GROUP COUNT [-a|--dry-run|...] | |
# set -e | |
# job="$1" | |
# group="$2" | |
# count="$3" | |
# shift 3 | |
# exec nomad-tweak "$job" \ | |
# group="$group" \ | |
# count="$count" \ | |
# "$@" -- \ | |
# 'group($group; .Count = $count)' | |
set -e | |
job="$(mktemp)" | |
trap 'rm "${job}"' EXIT | |
id="$(urlcode "$1")" | |
shift | |
args=() | |
pusharg() { | |
for it; do | |
args[${#args[@]}]="$it" | |
done | |
} | |
apply=0 | |
script='include "nomad"; . as $job' | |
scriptfile= | |
for arg; do | |
if [ $# -eq 1 ]; then | |
break | |
fi | |
case "$arg" in | |
--) | |
shift | |
break | |
;; | |
-apply|--apply|-A) | |
apply=1 | |
;; | |
-dryrun|-dry-run|--dry-run|--dryrun|-D) | |
apply=0 | |
;; | |
-[rcS]|--raw|--compact|--tab|--sort-keys) | |
pusharg "$arg" | |
;; | |
-f|-from-filename|--from-filename) | |
extra="${extra:+| }$(cat "${arg#-f}")" | |
shift | |
;; | |
-L) | |
pusharg "$arg" "$2" | |
shift | |
;; | |
-L?*) | |
pusharg "$arg" | |
;; | |
--arg|--argjson|--slurpfile) | |
pusharg "$arg" "$2" "$3" | |
shift 2 | |
;; | |
-arg|-argjson|-slurpfile) | |
pusharg "-$arg" "$2" "$3" | |
shift 2 | |
;; | |
?*=@*) | |
argname="${arg%%=*}" | |
arg="$(cat "${arg#*=}")" | |
if json="$(printf '%s' "$arg" | jq -c . 2>/dev/null)"; then | |
pusharg --argjson "$argname" "$arg" | |
else | |
pusharg --arg "$argname" "$arg" | |
fi | |
;; | |
?*=*) | |
argname="${arg%%=*}" | |
arg="${arg#*=}" | |
if json="$(printf '%s' "$arg" | jq -c . 2>/dev/null)"; then | |
pusharg --argjson "$argname" "$arg" | |
else | |
pusharg --arg "$argname" "$arg" | |
fi | |
;; | |
*) | |
break | |
;; | |
esac | |
shift | |
done | |
# Create script argument | |
pusharg "$script ${extra:-| .} $(printf '| %s' "$@")" | |
# Request job JSON | |
curl --silent --get --header 'Accept: application/json' "$NOMAD_ADDR/v1/job/$id" > "$job" | |
# Modify the job, then copy the JobModifyIndex (from the unmodified JSON) and set EnforceIndex | |
jq "${args[@]}" "$job" | | |
jq --argjson index "$(jq .JobModifyIndex "$job")" '{ JobModifyIndex: $index, EnforceIndex: true, Job: . }' | | |
case "$apply" in | |
0) | |
jq . | |
;; | |
1) | |
curl --silent --request POST --data-binary @- --header 'Content-Type: application/json' "$NOMAD_ADDR/v1/job/$id" | | |
jq . | |
;; | |
*) | |
echo "Unexpected state: apply=$apply" >&2 | |
exit 1 | |
;; | |
esac |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright Noel Cower 2019. | |
# Distributed under the Boost Software License, Version 1.0. | |
# (See accompanying file LICENSE_1_0.txt or copy at | |
# https://www.boost.org/LICENSE_1_0.txt) | |
module "nomad"; | |
## Auxiliary | |
# mapif/2 updates an entity in an array with the given sel if it matches | |
# filter. If it doesn't match, the nomatch selector is used. | |
def mapif(filter; sel; nomatch): | |
map(if filter then sel else nomatch end); | |
# mapif/2 updates an entity in an array with the given sel if it matches | |
# filter. | |
def mapif(filter; sel): | |
map(if filter then sel else . end); | |
# zip/1 combines two arrays into one by zipping their values together into | |
# arrays of of [[lhs[0], rhs[0]], [lhs[1], rhs[1]], ...]. | |
# | |
# The input is considered the left-hand side and the argument to the function | |
# the right-hand side for readability (i.e., lhs | zip(rhs)). | |
# | |
# Any elements not present on one side of the zip are null. | |
def zip($rhs): | |
. as $lhs | | |
([($lhs | length), ($rhs | length)] | max) as $len | | |
reduce range(0; $len) as $it ([]; . + [[$rhs[$it], $lhs[$it]]]); | |
# rzip/1 combines two arrays into one by zipping their values together into | |
# arrays of of [[lhs[0], rhs[0]], [lhs[1], rhs[1]], ...]. | |
# | |
# This is inverted from zip/1 and exists for convenience. | |
def rzip($lhs): | |
. as $rhs | | |
$lhs | zip($rhs); | |
# named/3 updates an entity with the given name using sel, otherwise it | |
# updates it with nomatch. | |
def named($name; sel; nomatch): | |
mapif(.Name == $name; sel; .); | |
# named/2 updates an entity with the given name using sel. | |
def named($name; sel): | |
named($name; sel; .); | |
# named/1 looks up the first entity with the given name. | |
def named($name): | |
map(select(name($name))) | first; | |
# name/1 returns true if an object's Name field is $name. | |
# This is a convenience function. | |
def name($name): | |
.Name == $name; | |
## Groups | |
# Read | |
# group/1 returns the first group with the given name. | |
def group($name): | |
.TaskGroups | named($name); | |
# Mutate | |
# group/2 applies sel to all groups with the given name. | |
def group($name; sel): | |
.TaskGroups |= named($name; sel); | |
# Filter | |
# groups/1 filters task groups and returns an array of groups that yield | |
# a non-false value for filter. | |
def groups(filter): | |
.TaskGroups | map(select(filter)); | |
## Tasks | |
# Read | |
# task/1 returns the first task with the given name. | |
def task($name): | |
.Tasks | named($name); | |
def task($group; $name): | |
group($group) | task($name); | |
# Mutate | |
# task/2 applies sel to all tasks with the given name. | |
def task(taskFilter; sel): | |
.Tasks |= named($name; sel); | |
def task($group; $name; sel): | |
group($group; task($name; sel)); | |
# Filter | |
# tasks/1 filters tasks and returns an array of tasks that yield a non-false | |
# value for filter. | |
def tasks(filter): | |
.Tasks | map(select(filter)); | |
def tasks($group; filter): | |
group($group) | tasks(filter); | |
## Services | |
# Read | |
# service/1 returns the first service with the given name. | |
def service($name): | |
.Services | named($name); | |
def service($task; $name): | |
task($task) | service($name); | |
def service($group; $task; $name): | |
group($group) | task($task) | service($name); | |
# Mutate | |
# service/2 applies sel to all services with the given name. | |
def service($name; sel): | |
.Services |= named($name; sel); | |
def service($task; $name; sel): | |
group($group; task($task; service($name; sel))); | |
def service($group; $task; $name; sel): | |
group($group; task($task; service($name; sel))); | |
# Filter | |
# services/1 filters services and returns an array of services that yield | |
# a non-false value for filter. | |
def services(filter): | |
.Services | map(select(filter)); | |
def services($task; filter): | |
task($task) | services(filter); | |
def services($group; $task; filter): | |
group($group) | task($task) | services(filter); | |
## Checks | |
# Read | |
# check/1 returns the first service check with the given name. | |
def check($name): | |
.Checks | named($name); | |
def check($service; $name): | |
service($service) | check($name); | |
def check($task; $service; $name): | |
task($task) | service($service) | check($name); | |
def check($group; $task; $service; $name): | |
group($group) | task($task) | service($service) | check($name); | |
# Mutate | |
def check($name; sel): | |
.Checks |= named($name; sel); | |
def check($service; $name; sel): | |
service($servie; check($name; sel)); | |
def check($task; $service; $name; sel): | |
task($task; service($servie; check($name; sel))); | |
def check($group; $task; $service; $name; sel): | |
group($group; task($task; service($servie; check($name; sel)))); | |
# Filter | |
# checks/1 filters service checks and returns an array of checks that yield | |
# a non-false value for filter. | |
def checks(filter): | |
.Checks | map(select(filter)); | |
def checks($service; filter): | |
service($service) | checks(filter); | |
def checks($task; $service; filter): | |
task($task) | service($service) | checks(filter); | |
def checks($group; $task; $service; filter): | |
group($group) | task($task) | service($service) | checks(filter); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment