Skip to content

Instantly share code, notes, and snippets.

@Duologic
Last active July 27, 2020 16:17
Show Gist options
  • Select an option

  • Save Duologic/a97eb2180aac552ac76c291c4e346615 to your computer and use it in GitHub Desktop.

Select an option

Save Duologic/a97eb2180aac552ac76c291c4e346615 to your computer and use it in GitHub Desktop.
PoC: Generates a jsonnet lib from a Helm chart
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
DIRNAME=$(dirname $0)
if [ "$#" != 2 ] && [ "$#" != 3 ]&& [ "$#" != 4 ]; then
>&2 echo "arguments invalid"
echo "Usage: `basename $0` <path to helm chart> <output dir> ['<helm_args>'] [overwrite]"
exit 1
fi
set +u
OVERWRITTEN=false
if [ "$3" = "overwrite" ] || [ "$4" = "overwrite" ]; then
rm -rf $2/templates
rm -rf $2/generated.libsonnet
[ "$3" = "overwrite" ] && OVERWRITTEN=true
fi
set -u
if [ -e $2/generated.libsonnet ]; then
>&2 echo "$2/generated.libsonnet exists"
exit 1
fi
if [ -e $2/templates ]; then
>&2 echo "$2/templates exists"
exit 1
fi
NAME=$(basename $2)
TEMP=$(mktemp -d)
function finish {
rm -rf ${TEMP}
}
trap finish EXIT
HELMARGS=''
if [ -n $3 ] && ! $OVERWRITTEN; then
HELMARGS=$3
fi
helm template 'XX_HELM_RELEASE_XX' $1 ${HELMARGS} --namespace 'XX_HELM_NAMESPACE_XX' --output-dir ${TEMP}
TEMPLATES=$TEMP/*/templates
mkdir -p $2/templates
cp $TEMPLATES/* $2/templates
cp $DIRNAME/templated.libsonnet $2 # Should get replaced by a jsonnet lib
OUT="local templated = import 'templated.libsonnet';\n\n"
OUT=${OUT}'{'
for f in $(ls $2/templates)
do
OUT="${OUT}'$(basename $f|awk -F'.yaml' '{print $1}'|sed 's/-/_/g')': templated.configureHelmChart(importstr 'templates/$f', $._config),\n"
done
OUT=${OUT}'}'
echo -e ${OUT}
echo -e ${OUT} | jsonnetfmt - > $2/generated.libsonnet
if [ -e $2/main.libsonnet ]; then
echo "$2/main.libsonnet exists"
echo "Import generated libsonnet with \`import '$2/generated.libsonnet'\`"
else
cat <<EOF > $2/main.libsonnet
local generated = import 'generated.libsonnet';
generated {
_config+:: {
name: error 'must provide name',
namespace: error 'must provide namespace',
release: '$NAME',
},
// Add permanent patches here
}
EOF
echo "$2/main.libsonnet generated"
fi
christian{
lowercaseFirstChar(s)::
std.asciiLower(s[0]) + std.substr(s, 1, std.length(s)),
appendKind(m, o)::
if o == null then
m
else
local kind = $.lowercaseFirstChar(o.kind);
local name = o.metadata.name;
if kind == 'customResourceDefinition' then
m {
[kind]+: {
[name]+: o,
},
}
else
m {
[name]+: {
[kind]+: o,
},
}
,
replaceHelmVariablesKey(key, config)::
std.strReplace(
key,
'XX_HELM_RELEASE_XX-%s-' % config.release,
'',
)
,
replaceHelmVariablesString(key, config)::
std.strReplace(
std.strReplace(
std.strReplace(
key,
'XX_HELM_NAMESPACE_XX',
config.namespace
),
'XX_HELM_RELEASE_XX-%s' % config.release,
config.name
),
'XX_HELM_RELEASE_XX',
config.name,
)
,
replaceHelmVariables(obj, config)::
if std.type(obj) == 'object' then
{
[$.replaceHelmVariablesKey(key, config)]: $.replaceHelmVariables(obj[key], config)
for key in std.objectFields(obj)
}
else if std.type(obj) == 'array' then
std.map(function(x) $.replaceHelmVariables(x, config), obj)
else if std.type(obj) == 'string' then
$.replaceHelmVariablesString(obj, config)
else
obj,
configureHelmChart(yaml, config)::
std.prune(
$.replaceHelmVariables(
std.foldl(
$.appendKind,
std.native('parseYaml')(yaml),
{}
),
config
)
),
}
@Duologic

Duologic commented Jul 27, 2020

Copy link
Copy Markdown
Author

Executing:

$ helm repo add bitnami https://charts.bitnami.com/bitnami
$ sh generate_libsonnet.sh bitnami/redis lib/redis

Generates:

lib/redis/
├── generated.libsonnet
├── main.libsonnet
├── templated.libsonnet
└── templates
    ├── configmap.yaml
    ├── headless-svc.yaml
    ├── health-configmap.yaml
    ├── redis-master-statefulset.yaml
    ├── redis-master-svc.yaml
    ├── redis-slave-statefulset.yaml
    ├── redis-slave-svc.yaml
    └── secret.yaml

1 directory, 11 files

generated.libsonnet:

local templated = import 'templated.libsonnet';

{
  configmap: templated.configureHelmChart(importstr 'templates/configmap.yaml', $._config),
  headless_svc: templated.configureHelmChart(importstr 'templates/headless-svc.yaml', $._config),
  health_configmap: templated.configureHelmChart(importstr 'templates/health-configmap.yaml', $._config),
  redis_master_statefulset: templated.configureHelmChart(importstr 'templates/redis-master-statefulset.yaml', $._config),
  redis_master_svc: templated.configureHelmChart(importstr 'templates/redis-master-svc.yaml', $._config),
  redis_slave_statefulset: templated.configureHelmChart(importstr 'templates/redis-slave-statefulset.yaml', $._config),
  redis_slave_svc: templated.configureHelmChart(importstr 'templates/redis-slave-svc.yaml', $._config),
  secret: templated.configureHelmChart(importstr 'templates/secret.yaml', $._config),
}

main.libsonnet:

local generated = import 'generated.libsonnet';
generated {
  _config+:: {
    name: error 'must provide name',
    namespace: error 'must provide namespace',
    release: 'redis',
  },
}

@Duologic

Copy link
Copy Markdown
Author

Requires Helm and Jsonnet to generate, parseYaml is natively provided by Tanka.

@Duologic

Copy link
Copy Markdown
Author

If upstream changes come in, just overwrite the generated bits and retain the changes in main.libsonnet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment