|
| 1 | +#!/usr/bin/env bash |
| 2 | +# shellcheck disable=SC2059 |
| 3 | +# |
| 4 | +# This can be customized from the command line by setting the variable |
| 5 | +# values like this if jq is installed. |
| 6 | +# |
| 7 | +# $ ./upload-json-dashboard.sh -j external.json -d mypg -g 6300 |
| 8 | +# |
| 9 | +# Or like this if it isn't. |
| 10 | +# |
| 11 | +# $ ./upload-json-dashboard.sh -j external.json -d mypg -g 6300 -n DS_EXTERNALPG |
| 12 | +# |
| 13 | +# Help is available, by specifying the -h option. |
| 14 | +# |
| 15 | +# Note that you can run grape status -v to see the available |
| 16 | +# servers. |
| 17 | +# |
| 18 | +# This script was linted by shellcheck. |
| 19 | + |
| 20 | +# ================================================================ |
| 21 | +# Functions |
| 22 | +# ================================================================ |
| 23 | +function _help() { |
| 24 | + cat <<EOF |
| 25 | +USAGE |
| 26 | + $0 -h |
| 27 | +
|
| 28 | +DESCRIPTION |
| 29 | + Uploads a dashboard JSON file to a Grafana server. |
| 30 | +
|
| 31 | + The upload is limited to servers with simple authentication based |
| 32 | + on a username and password unless you override it using "-x" and |
| 33 | + "-n". |
| 34 | +
|
| 35 | + The local dashboard JSON file is created by exporting the |
| 36 | + dashboard from the Grafana UI with the "Export for sharing |
| 37 | + externally" checkbox checked. |
| 38 | +
|
| 39 | + This script is useful for transferring dashboards from one server |
| 40 | + to another. |
| 41 | +
|
| 42 | + Although the same function can be accomplished in the UI, this |
| 43 | + script allows updates to be automated from the command line. |
| 44 | +
|
| 45 | + This script requires that "curl" is installed. |
| 46 | +
|
| 47 | +OPTIONS |
| 48 | + -d NAME The Grafana data source name. |
| 49 | +
|
| 50 | + -f INT The Grafana dashboard parent folder id. |
| 51 | + If not specified, the top level is assumed. |
| 52 | +
|
| 53 | + -g PORT The Grafana URL prefix. |
| 54 | + An example might be something like: |
| 55 | + http://localhost:6300 |
| 56 | +
|
| 57 | + -h This help message. |
| 58 | +
|
| 59 | + -i NAME The plugin id. |
| 60 | + The default is 'postgres' |
| 61 | +
|
| 62 | + -j FILE The dashboard JSON file to upload. |
| 63 | +
|
| 64 | + -n NAME The name of the Grafana datasource variable. |
| 65 | + If not specified, the name will be extracted |
| 66 | + from the JSON file if the jq program is present. |
| 67 | +
|
| 68 | + -N Do not use simple authentication. |
| 69 | + The user is expected to use the -x option to add extra |
| 70 | + data. |
| 71 | +
|
| 72 | + -p PASSWORD The Grafana password. |
| 73 | + Normally you would want to use -P to prompt with no |
| 74 | + echo. |
| 75 | +
|
| 76 | + -P Prompt for the Grafana password. |
| 77 | +
|
| 78 | + -u USERNAME The Grafana username. |
| 79 | +
|
| 80 | + -v Increase the level of verbosity. |
| 81 | +
|
| 82 | + -x STRING Add extra curl arguments. |
| 83 | +
|
| 84 | +EXAMPLES |
| 85 | + # Example 1. |
| 86 | + # Get help |
| 87 | + \$ $0 -h |
| 88 | +
|
| 89 | + # Example 2. |
| 90 | + # Upload to a grape Grafana server. |
| 91 | + # The -u and -p options do not need to be specified. |
| 92 | + # Pipe the curl output in jq to make it more readable. |
| 93 | + # They are only shown for clarity. |
| 94 | + \$ grape server -v # find the port |
| 95 | + \$ $0 -u admin -p admin -g http://localhost:6400 -j x.json -d mypg -n DS_MYDB | jq |
| 96 | +
|
| 97 | + # Example 3. |
| 98 | + # Upload to a grape Grafana server. |
| 99 | + # Pipe the curl output in jq to make it more readable. |
| 100 | + # Use your own auth. |
| 101 | + \$ grape server -v # find the port |
| 102 | + \$ $0 -g http://localhost:6400 -j x.json -d mypg -N -x '-u admin:admin' -n DS_MYDB | jq |
| 103 | +
|
| 104 | +EOF |
| 105 | + exit 0 |
| 106 | +} |
| 107 | + |
| 108 | +# ================================================================ |
| 109 | +# Constants. |
| 110 | +# ================================================================ |
| 111 | +ERRFMT='\x1b[31mERROR:%d: %s\x1b[0m\n' |
| 112 | + |
| 113 | +# ================================================================ |
| 114 | +# Command line argument processing. |
| 115 | +# ================================================================ |
| 116 | +set -e |
| 117 | +DASH_AUTH=1 |
| 118 | +DASH_DS= |
| 119 | +DASH_EXTRA_CURL=() |
| 120 | +DASH_FOLDER=0 |
| 121 | +DASH_JSON= |
| 122 | +DASH_NAME= |
| 123 | +DASH_PLUGIN='postgres' |
| 124 | +DASH_PASSWORD='admin' |
| 125 | +DASH_USERNAME='admin' |
| 126 | +DASH_URL= |
| 127 | +VERBOSE=0 |
| 128 | + |
| 129 | +while getopts ':f:hd:g:i:j:n:Np:Pu:vx:' options ; do |
| 130 | + case ${options} in |
| 131 | + h ) |
| 132 | + _help |
| 133 | + ;; |
| 134 | + d ) |
| 135 | + DASH_DS=${OPTARG} |
| 136 | + ;; |
| 137 | + f ) |
| 138 | + DASH_FOLDER=${OPTARG} |
| 139 | + ;; |
| 140 | + g ) |
| 141 | + DASH_URL=${OPTARG} |
| 142 | + ;; |
| 143 | + i ) |
| 144 | + DASH_PLUGIN=${OPTARG} |
| 145 | + ;; |
| 146 | + j ) |
| 147 | + DASH_JSON=${OPTARG} |
| 148 | + ;; |
| 149 | + n ) |
| 150 | + DASH_NAME=${OPTARG} |
| 151 | + ;; |
| 152 | + N ) |
| 153 | + DASH_AUTH=0 |
| 154 | + ;; |
| 155 | + p ) |
| 156 | + DASH_PASSWORD=${OPTARG} |
| 157 | + ;; |
| 158 | + P ) |
| 159 | + read -r -p 'Password: ' -s DASH_PASSWORD |
| 160 | + printf '\n' |
| 161 | + ;; |
| 162 | + u ) |
| 163 | + DASH_USERNAME=${OPTARG} |
| 164 | + ;; |
| 165 | + v ) |
| 166 | + VERBOSE=$(( VERBOSE + 1 )) |
| 167 | + ;; |
| 168 | + x ) |
| 169 | + DASH_EXTRA_CURL=("${OPTARG//[ ]/}") |
| 170 | + ;; |
| 171 | + '?' ) |
| 172 | + printf "$ERRFMT" $LINENO "invalid option: -$OPTARG" 1>&2 |
| 173 | + exit 1 |
| 174 | + ;; |
| 175 | + esac |
| 176 | +done |
| 177 | +shift $((OPTIND -1)) |
| 178 | + |
| 179 | +# ================================================================ |
| 180 | +# Check required options. |
| 181 | +# ================================================================ |
| 182 | +if [ -n "$DASH_JSON" ] && [ -z "$DASH_NAME" ] ; then |
| 183 | + if jq --version &>/dev/null ; then |
| 184 | + # If jq is available, extract the variable name. |
| 185 | + DASH_NAME=$(jq '.__inputs[0].name' "$DASH_JSON" | awk -F'"' '{print $2}') |
| 186 | + fi |
| 187 | +fi |
| 188 | +# need bash 4 or later |
| 189 | +BASH_MAJOR=$(echo "$BASH_VERSION" | awk -F. '{print $1}') |
| 190 | +if (( BASH_MAJOR < 4 )) ; then |
| 191 | + # Need 4 or later to support "declare -A". |
| 192 | + printf "$ERRFMT" $LINENO "this version of bash is too old, must be 4 or later: $BASH_VERSION" 1>&2 |
| 193 | + exit 1 |
| 194 | +fi |
| 195 | + |
| 196 | +ERRCNT=0 |
| 197 | +declare -A ROPTS=( ['-d']="$DASH_DS" ['-j']="$DASH_JSON" ['-g']="$DASH_URL" ['-n']="$DASH_NAME") |
| 198 | +for RKEY in "${!ROPTS[@]}" ; do |
| 199 | + RVAL=${ROPTS[$RKEY]} |
| 200 | + if [ -z "$RVAL" ] ; then |
| 201 | + printf "$ERRFMT" \ |
| 202 | + $LINENO \ |
| 203 | + "missing required option: '$RKEY', see the help (-h) for more information" \ |
| 204 | + 1>&2 |
| 205 | + ERRCNT=$(( ERRCNT += 1)) |
| 206 | + fi |
| 207 | +done |
| 208 | +if (( ERRCNT )) ; then exit 1 ; fi |
| 209 | + |
| 210 | +# ================================================================ |
| 211 | +# Report the setup. |
| 212 | +# ================================================================ |
| 213 | +if (( VERBOSE )) ; then |
| 214 | + cat <<EOF |
| 215 | +Setup |
| 216 | + BASH_VERSION : $BASH_VERSION |
| 217 | + DASH_AUTH : $DASH_AUTH |
| 218 | + DASH_DS : $DASH_DS |
| 219 | + DASH_FOLDER : $DASH_FOLDER |
| 220 | + DASH_JSON : $DASH_JSON |
| 221 | + DASH_NAME : $DASH_NAME |
| 222 | + DASH_URL : $DASH_URL |
| 223 | + DASH_USERNAME : $DASH_USERNAME |
| 224 | + DASH_EXTRA_CURL : ${DASH_EXTRA_CURL[@]} |
| 225 | + VERBOSE : $VERBOSE |
| 226 | +EOF |
| 227 | +fi |
| 228 | + |
| 229 | +# ================================================================ |
| 230 | +# Other prerequisite checks. |
| 231 | +# ================================================================ |
| 232 | +if [ ! -f "$DASH_JSON" ] ; then |
| 233 | + printf "$ERRFMT" $LINENO "file does not exist: $DASH_JSON" 1>&2 |
| 234 | + exit 1 |
| 235 | +fi |
| 236 | +if ! grep '"__inputs"' "$DASH_JSON" &>/dev/null ; then |
| 237 | + printf "$ERRFMT" $LINENO "missing required record '__inputs' in $DASH_JSON" 1>&2 |
| 238 | + exit 1 |
| 239 | +fi |
| 240 | +if ! curl --version &>/dev/null ; then |
| 241 | + printf "$ERRFMT" $LINENO "curl is not installed, cannot continue" 1>&2 |
| 242 | + exit 1 |
| 243 | +fi |
| 244 | + |
| 245 | +# ================================================================ |
| 246 | +# Create the JSON with the variables set. |
| 247 | +# |
| 248 | +# Note the the "inputs" record MUST appear before the "dashboard" |
| 249 | +# record because the import operation picks up the first input |
| 250 | +# specification and ignores the rest. |
| 251 | +# |
| 252 | +# This is relevant because there are two sources of external variable |
| 253 | +# definitions in the dashboard description. The first is the "inputs" |
| 254 | +# record at the top level (shown below) and the other is the |
| 255 | +# "__inputs" record under each dashboard (not shown) which is created |
| 256 | +# by Grafana when it exports the dashboard. The top level definition |
| 257 | +# overrides all subsequent low level definitions which is why it must |
| 258 | +# appear first. |
| 259 | +# ================================================================ |
| 260 | +cat >x.json <<EOF |
| 261 | +{ |
| 262 | + "inputs": [{ |
| 263 | + "name": "${DASH_NAME}", |
| 264 | + "type": "datasource", |
| 265 | + "pluginId": "${DASH_PLUGIN}", |
| 266 | + "value": "${DASH_DS}" |
| 267 | + }], |
| 268 | + "dashboard": $(cat "${DASH_JSON}"), |
| 269 | + "folderId": ${DASH_FOLDER}, |
| 270 | + "overwrite": true |
| 271 | +} |
| 272 | +EOF |
| 273 | + |
| 274 | +# ================================================================ |
| 275 | +# Upload to the Grafana server. |
| 276 | +# ================================================================ |
| 277 | +if (( DASH_AUTH )) ; then |
| 278 | + AUTH=('-u' "${DASH_USERNAME}:${DASH_PASSWORD}") |
| 279 | +else |
| 280 | + AUTH=() |
| 281 | +fi |
| 282 | +if (( VERBOSE )) ; then set -x ; fi |
| 283 | +curl "${AUTH[@]}" "${DASH_EXTRA_CURL[@]}" \ |
| 284 | + -s \ |
| 285 | + -k \ |
| 286 | + -X POST \ |
| 287 | + -H "Accept: application/json" \ |
| 288 | + -H "Content-Type: application/json" \ |
| 289 | + -d @x.json \ |
| 290 | + "${DASH_URL}"/api/dashboards/import |
| 291 | +if (( VERBOSE )) ; then { set +x; } 2>/dev/null ; fi |
0 commit comments