Skip to content

Instantly share code, notes, and snippets.

@yonran
Created November 1, 2017 22:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yonran/c5590a52179aa4841d1067991338b920 to your computer and use it in GitHub Desktop.
Save yonran/c5590a52179aa4841d1067991338b920 to your computer and use it in GitHub Desktop.
Functions to replace kubernetes configmap and secret from directory
#!/usr/bin/env bash
# Convert a directory to a ConfigMap json file as when doing kubectl create configmap --from-file …
#
# Example usage:
# 1. kubectl create cm test-cm --from-file path/to/cmdir
#
# 2. Before running kubectl replace, you should diff the directories:
# dirToCm test-cm path/to/cmdir | cmToDir /tmp/test-cm-new
# kubectl get cm test-cm -o json | cmToDir /tmp/test-cm-orig
# diff /tmp/test-cm-orig /tmp/test-cm-new
#
# 3. When you are satisfied at the diff, run kubectl replace:
# dirToCm test-cm path/to/cmdir | kubectl replace -f -
function dirToCm() {
local name=
local USAGE="$(cat <<EOF
Convert files to configmap json structure. Supports --from-file and
--from-literal in the same way as kubectl create configmap.
Usage:
dirToCm --from-file=<FILESOURCE>... --from-literal=<KEY>=<VALUE>... <NAME>
Options:
--from-file=<FILESOURCE>... Source from files. Can be one of <FILE>,
<KEY>=<FILE>, or <DIR>. Can be specified multiple times or
comma-separated.
--from-literal=<KEY>=<VALUE>... Source from literal strings.
EOF
)"
local -a fileSources=() # each item is file, key=file, or directory
local -a literalSources=() # each item is key=value
while (($# > 0)); do
case "$1" in
--from-file) local oldIFS="$IFS"; IFS=,; fileSources+=($2); IFS="$oldIFS"; shift;;
--from-file=*) local oldIFS="$IFS"; IFS=,; fileSources+=(${1#*=}); IFS="$oldIFS";;
--from-literal) literalSources+=("$2"); shift;;
--from-literal=*) literalSources+=("${1#*=}");;
--help) echo "$USAGE"; return 0;;
-*) echo >&2 "Unknown flag \"$1\""$'\n'"$USAGE"; return 1;;
*) name="$1";;
esac
shift
done
if [[ -z "$name" ]]; then echo >&2 "Required argument NAME must be set"$'\n'"$USAGE"; return 1; fi
jq -n 0 > /dev/null || { echo >&2 "Make sure jq is installed" ; return 1; }
local CM="$(jq -n --arg name "$name" '{apiVersion: "v1", data: {}, kind: "ConfigMap", metadata: {name: $name}}')"
local literalSource
for literalSource in "${literalSources[@]:+"${literalSources[@]}"}"; do
local keyName="${literalSource%%=*}"
local value="${literalSource#*=}"
if [[ "$keyName" = "$literalSource" ]]; then
echo >&2 "invalid literal source $literalSource, expected key=value"
return 1
fi
CM=$(echo "$CM" | jq --arg keyName "$keyName" --arg value "$value" '.data[$keyName] = $value');
done
local fileSource
for fileSource in "${fileSources[@]:+"${fileSources[@]}"}"; do
local keyName="${fileSource%%=*}"
local filePath="${fileSource#*=}"
if [[ -f "$filePath" ]]; then
if [[ "$keyName" = "$fileSource" ]]; then keyName=$(basename "$fileSource"); fi
local content="$(cat "$filePath"; printf x)"; content="${content%x}"
CM=$(echo "$CM" | jq --arg keyName "$keyName" --arg content "$content" '.data[$keyName] = $content');
elif [[ -d "$filePath" ]]; then
if [[ "$keyName" != "$fileSource" ]]; then echo >&2 "cannot give a key name for a directory path $fileSource"; return 1; fi
local file
local RESET_DOTGLOB="$(shopt -p dotglob)"
shopt -s dotglob
for file in "$filePath"/*; do
# Hack to keep trailing newline in bash command substitution https://stackoverflow.com/a/15184414/471341
if ! {
if [[ -f "$file" ]]; then
keyName="$(basename "$file")"
local content="$(cat "$file"; printf x)"; content="${content%x}" &&
CM=$(echo "$CM" | jq --arg keyName "$keyName" --arg content "$content" '.data[$keyName] = $content');
fi
}; then
local returnval=$?
$RESET_DOTGLOB
return $returnval
fi
done
$RESET_DOTGLOB
else
echo >&2 "Not a file or directory: $filePath (--from-file $fileSource)"
return 1
fi
done
echo "$CM"
}
function dirToSecret() {
dirToCm "$@" | jq '{apiVersion: "v1", data: .data | to_entries | [.[] | .value |= @base64] | from_entries, kind: "Secret", metadata: {name: .metadata.name}}'
}
# Convert a ConfigMap json file to a directory.
# Useful for saving the existing configmap to the filesystem so it can be compared
# e.g. dirToCm /tmp/dir1 | cmToDir /tmp/dir2
# e.g. kubectl get cm … -o json | cmToDir /tmp/dir
# diff /tmp/dir path/to/new-cmdir
function cmToDir() {
jq -n '""|@base64d' > /dev/null || { echo >&2 "Make sure jq 1.5+ is installed (for @base64d)" ; return 1; }
if [ -z "${1:-}" ]; then
echo >&2 $'Missing parameter DIR\nUsage: kubectl get cm <NAME> -o json | cmToDir <DIR>'
return 1
fi
local dir="$1"
if ! [ -d "$1" ]; then
mkdir "$1" || return 2
fi
local CM=$(cat)
local ENTRIES=$(echo "$CM" | jq '.data | to_entries')
for i in $(seq 0 $(echo "$ENTRIES" | jq 'length - 1')); do
local filename=$(echo "$ENTRIES" | jq --arg i $i '.[$i|tonumber].key' -r)
echo >&2 "writing $i $filename"
# --join-output is like --raw-output but without any added newlines
echo "$ENTRIES" | jq --arg i $i '.[$i|tonumber].value' --join-output > "$dir/$filename"
done
}
function secretToDir() {
if [[ -z "${1:-}" ]]; then
echo >&2 $'Missing parameter DIR\nUsage: kubectl get secret <NAME> -o json | secretToDir <DIR>'
return 1
fi
jq '.data |= (to_entries | [.[] | .value |= @base64d] | from_entries)' | cmToDir "$1"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment