|
| 1 | +#!/bin/bash |
| 2 | +# Copyright 2019, Oracle Corporation and/or its affiliates. All rights reserved. |
| 3 | +# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. |
| 4 | + |
| 5 | +# |
| 6 | +# This is a helper script for running arbitrary shell commands or scripts on |
| 7 | +# a kubernetes cluster using arbitrary images and zero or more arbitrary PVC mounts. |
| 8 | +# Run with '-h' to get usage, or see 'usage_exit()' below. |
| 9 | +# |
| 10 | +# Credit: |
| 11 | +# - This script is an expansion of https://gist.github.com/yuanying/3aa7d59dcce65470804ab43def646ab6 |
| 12 | +# |
| 13 | +# TBD: |
| 14 | +# - Image secret parameter hasn't been tested. |
| 15 | +# |
| 16 | + |
| 17 | +IMAGE='store/oracle/serverjre:8' |
| 18 | +IMAGEPULLPOLICY='IfNotPresent' |
| 19 | +IMAGEPULLSECRETS='' |
| 20 | +IPSCOMMA='' |
| 21 | + |
| 22 | +COMMAND='id' |
| 23 | +PODNAME='one-off' |
| 24 | +NAMESPACE='default' |
| 25 | +MAXSECS=30 |
| 26 | + |
| 27 | +VOL_MOUNTS='' |
| 28 | +VOLS='' |
| 29 | +VCOMMA='' |
| 30 | +CMFILES='' |
| 31 | +VOLUMECOUNT=0 |
| 32 | + |
| 33 | +EXITCODE=0 |
| 34 | + |
| 35 | +usage_exit() { |
| 36 | +cat << EOF |
| 37 | +
|
| 38 | + Usage: $(basename $0) [-i image] [-n namespace] [-c command] |
| 39 | + [-m pvc-name:mount-path] |
| 40 | + [-f local-file-name] |
| 41 | + [-t timeout] |
| 42 | +
|
| 43 | + Run a 'one-off' command on a kubernetes cluster via a temporary pod, |
| 44 | + optionally specify pvc mounts and additional files to load. |
| 45 | +
|
| 46 | + Parameters: |
| 47 | +
|
| 48 | + -i imagename - Image, default is "$IMAGE". |
| 49 | + -l imagepullpolicy - Image pull policy, default is "$IMAGEPULLPOLICY". |
| 50 | + -s imagesecret - Image pull secret. No default. |
| 51 | + -n namespace - Namespace, default is "$NAMESPACE". |
| 52 | + -p podname - Pod name, default is "$PODNAME". |
| 53 | + -c command - Command, default is "$COMMAND". |
| 54 | + -t timeout - Timeout in seconds, default is $MAXSECS seconds. |
| 55 | + -m pvc-name:mount-path - PVC name and mount location. No default. |
| 56 | + -m host-path:mount-path- Remote host-path and mount location. No default. |
| 57 | + Host-path must begin with a '/'. |
| 58 | + -f /path/filename - Copies local file to pod at /tmpmount/filename |
| 59 | +
|
| 60 | + You can specify -f, -m, and -s more than once. |
| 61 | +
|
| 62 | + Limitation: |
| 63 | +
|
| 64 | + Concurrent invocations that have the same namespace and podname will |
| 65 | + interfere with each-other. You can change these using '-n' and '-p'. |
| 66 | +
|
| 67 | + Sample usage: |
| 68 | +
|
| 69 | + # Use an alternate image and show the user/uid/gid for that image |
| 70 | + ./krun.sh -i store/oracle/weblogic:12.2.1.3 -c 'id' |
| 71 | + |
| 72 | + # Get the WL version in a WL image |
| 73 | + ./krun.sh -i store/oracle/weblogic:12.2.1.3 -c \\ |
| 74 | + 'source /u01/oracle/wlserver/server/bin/setWLSEnv.sh && java weblogic.version' |
| 75 | +
|
| 76 | + # Mount an existing pvc to /shared, and jar up a couple of its directories |
| 77 | + [ $? -eq 0 ] && ./krun.sh -m domainonpvwlst-weblogic-sample-pvc:/shared \\ |
| 78 | + -c 'jar cf /shared/pvarchive.jar /shared/domains /shared/logs' |
| 79 | + # ... then copy the jar locally |
| 80 | + [ $? -eq 0 ] && ./krun.sh -m domainonpvwlst-weblogic-sample-pvc:/shared \\ |
| 81 | + -c 'base64 /shared/pvarchive.jar' \\ |
| 82 | + | base64 -di > /tmp/pvarchive.jar |
| 83 | + # ... then delete the remote jar |
| 84 | + [ $? -eq 0 ] && ./krun.sh -m domainonpvwlst-weblogic-sample-pvc:/shared \\ |
| 85 | + -c 'rm -f /shared/pvarchive.jar' |
| 86 | +
|
| 87 | + # Mount a directory on the host to /scratch and 'ls' its contents |
| 88 | + ./krun.sh -m /tmp:/scratch -c 'ls /scratch' |
| 89 | + |
| 90 | + # Copy local files to /tmpmount in the pod, and run one of them as a script |
| 91 | + echo 'hi there from foo.sh!' > /tmp/foo.txt |
| 92 | + echo 'cat /tmpmount/foo.txt' > /tmp/foo.sh |
| 93 | + ./krun.sh -f /tmp/foo.txt -f /tmp/foo.sh -c "sh /tmpmount/foo.sh" |
| 94 | + rm -f /tmp/foo.txt /tmp/foo.sh |
| 95 | +
|
| 96 | +EOF |
| 97 | + |
| 98 | + exit 1 |
| 99 | +} |
| 100 | + |
| 101 | +delete_pod_and_cm() { |
| 102 | + kubectl delete -n ${NAMESPACE} pod ${PODNAME} --ignore-not-found > /dev/null 2>&1 |
| 103 | + kubectl delete -n ${NAMESPACE} cm ${PODNAME}-cm --ignore-not-found > /dev/null 2>&1 |
| 104 | +} |
| 105 | + |
| 106 | +while getopts m:t:i:c:n:f:p: OPT |
| 107 | +do |
| 108 | + case $OPT in |
| 109 | + i) IMAGE=$OPTARG |
| 110 | + ;; |
| 111 | + l) IMAGEPULLPOLICY=$OPTARG |
| 112 | + ;; |
| 113 | + s) IMAGEPULLSECRETS="${IMAGEPULLSECRETS}${IPSCOMMA}{\"name\": \"$OPTARG\"}" |
| 114 | + IPSCOMMA="," |
| 115 | + ;; |
| 116 | + p) PODNAME=$OPTARG |
| 117 | + ;; |
| 118 | + c) COMMAND=$OPTARG |
| 119 | + ;; |
| 120 | + n) NAMESPACE=$OPTARG |
| 121 | + ;; |
| 122 | + t) MAXSECS=$OPTARG |
| 123 | + ;; |
| 124 | + f) # local file to make available in /tmpmount |
| 125 | + if [ "$CMFILES" = "" ]; then |
| 126 | + VOL_MOUNTS="${VOL_MOUNTS}${VCOMMA}{\"name\": \"my-config-map-vol\",\"mountPath\": \"/tmpmount\"}" |
| 127 | + VOLS="${VOLS}${VCOMMA}{\"name\": \"my-config-map-vol\",\"configMap\": {\"name\": \"${PODNAME}-cm\"}}" |
| 128 | + VCOMMA="," |
| 129 | + fi |
| 130 | + CMFILES="--from-file $OPTARG $CMFILES" |
| 131 | + ;; |
| 132 | + m) # pvc or hostPath volume mount |
| 133 | + |
| 134 | + # parse "arg1:arg2" into arg1 and arg2 |
| 135 | + arg1="`echo $OPTARG | sed 's/:.*//g'`" |
| 136 | + arg2="`echo $OPTARG | sed 's/.*://g'`" |
| 137 | + [ "$arg1" = "" ] && usage_exit |
| 138 | + [ "$arg2" = "" ] && usage_exit |
| 139 | + |
| 140 | + mountPath="$arg2" |
| 141 | + VOLUMECOUNT=$((VOLUMECOUNT + 1)) |
| 142 | + volName="volume${VOLUMECOUNT}" |
| 143 | + VOL_MOUNTS="${VOL_MOUNTS}${VCOMMA}{\"name\": \"${volName}\",\"mountPath\": \"${mountPath}\"}" |
| 144 | + |
| 145 | + if [ "`echo $arg1 | grep -c '^/'`" = "1" ]; then |
| 146 | + # hostPath mount (since arg1 starts with '/') |
| 147 | + VOLS="${VOLS}${VCOMMA}{\"name\": \"${volName}\",\"hostPath\": {\"path\": \"${arg1}\"}}" |
| 148 | + else |
| 149 | + # pvc mount (since arg1 starts without a '/') |
| 150 | + VOLS="${VOLS}${VCOMMA}{\"name\": \"${volName}\",\"persistentVolumeClaim\": {\"claimName\": \"${arg1}\"}}" |
| 151 | + fi |
| 152 | + |
| 153 | + VCOMMA="," |
| 154 | + ;; |
| 155 | + *) usage_exit |
| 156 | + ;; |
| 157 | + esac |
| 158 | +done |
| 159 | +shift $(($OPTIND - 1)) |
| 160 | +[ ! -z "$1" ] && usage_exit |
| 161 | + |
| 162 | +# setup a tmp file /tmp/[script-file-name].[name-space].[pod-name].tmp.[pid] |
| 163 | + |
| 164 | +TEMPFILE="/tmp/$(basename $0).${NAMESPACE}.${PODNAME}.tmp.$$" |
| 165 | + |
| 166 | +# cleanup anything from previous run |
| 167 | + |
| 168 | +delete_pod_and_cm |
| 169 | + |
| 170 | +# create cm that contains any files the user specified on the command line: |
| 171 | + |
| 172 | +if [ ! -z "$CMFILES" ]; then |
| 173 | + kubectl create cm ${PODNAME}-cm -n ${NAMESPACE} ${CMFILES} > $TEMPFILE 2>&1 |
| 174 | + EXITCODE=$? |
| 175 | + if [ $EXITCODE -ne 0 ]; then |
| 176 | + echo "Error: kubectl create cm failed." |
| 177 | + # Since EXITCODE is non-zero, the script will skip |
| 178 | + # doing more stuff and cat the contents of $TEMPFILE below. |
| 179 | + fi |
| 180 | +fi |
| 181 | + |
| 182 | +# run teh command, honoring any mounts specified on the command line |
| 183 | + |
| 184 | +[ $EXITCODE -eq 0 ] && kubectl run -it --rm --restart=Never --tty --image=${IMAGE} ${PODNAME} -n ${NAMESPACE} --overrides " |
| 185 | +{ |
| 186 | + \"spec\": { |
| 187 | + \"hostNetwork\": true, |
| 188 | + \"containers\":[ |
| 189 | + { |
| 190 | + \"command\": [\"bash\"], |
| 191 | + \"args\": [\"-c\",\"${COMMAND} ; echo -n {EXITCODE=\$?}\"], |
| 192 | + \"stdin\": true, |
| 193 | + \"tty\": true, |
| 194 | + \"name\": \"${PODNAME}\", |
| 195 | + \"image\": \"${IMAGE}\", |
| 196 | + \"imagePullPolicy\": \"${IMAGEPULLPOLICY}\", |
| 197 | + \"volumeMounts\": [ |
| 198 | + ${VOL_MOUNTS} |
| 199 | + ] |
| 200 | + } |
| 201 | + ], |
| 202 | + \"imagePullSecrets\": [ |
| 203 | + ${IMAGEPULLSECRETS} |
| 204 | + ], |
| 205 | + \"volumes\": [ |
| 206 | + ${VOLS} |
| 207 | + ] |
| 208 | + } |
| 209 | +} |
| 210 | +" > $TEMPFILE 2>&1 & |
| 211 | + |
| 212 | +# If the background task doesn't complete within MAXSECS |
| 213 | +# then report a timeout and try kubectl describe the pod. |
| 214 | + |
| 215 | +STARTSECS=$SECONDS |
| 216 | +while [ $EXITCODE -eq 0 ] |
| 217 | +do |
| 218 | + JOBS="`jobs -pr`" |
| 219 | + [ "$JOBS" = "" ] && break |
| 220 | + if [ $((SECONDS - STARTSECS)) -gt $MAXSECS ]; then |
| 221 | + EXITCODE=98 |
| 222 | + echo "Error: Commmand timed out after $MAXSECS seconds." |
| 223 | + kubectl describe -n ${NAMESPACE} pod ${PODNAME} |
| 224 | + break |
| 225 | + fi |
| 226 | + sleep 0.1 |
| 227 | +done |
| 228 | + |
| 229 | +# If background task's out file doesn't contain '{EXITCODE=0}', |
| 230 | +# then it didn't finish or it failed... |
| 231 | + |
| 232 | +if [ $EXITCODE -eq 0 ]; then |
| 233 | + if [ ! "`grep -c '{EXITCODE=0}' $TEMPFILE`" -eq 1 ]; then |
| 234 | + EXITCODE=99 |
| 235 | + fi |
| 236 | +fi |
| 237 | + |
| 238 | +# Delete pod in case it timed out and is still running, plus delete |
| 239 | +# the config map of files (if any). This should also complete the |
| 240 | +# background task (which waits on the pod to finish running). |
| 241 | + |
| 242 | +delete_pod_and_cm |
| 243 | + |
| 244 | +# Show output from pod (or from failing 'kubectl create cm' command above) |
| 245 | + |
| 246 | +cat $TEMPFILE | sed 's/{EXITCODE=0}//' \ |
| 247 | + | grep -v "Unable to use a TTY - input is not a terminal or the right kind of file" \ |
| 248 | + | grep -v "If you don't see a command prompt, try pressing enter." \ |
| 249 | + | grep -v "Error attaching, falling back to logs:" |
| 250 | + |
| 251 | +# Delete output file from pod |
| 252 | + |
| 253 | +rm -f $TEMPFILE |
| 254 | + |
| 255 | +exit $EXITCODE |
| 256 | + |
| 257 | + |
0 commit comments