Skip to content

Instantly share code, notes, and snippets.

@blake
Last active April 11, 2022 16:54
Show Gist options
  • Save blake/8d7e7c284bbda160d72caedfec18328f to your computer and use it in GitHub Desktop.
Save blake/8d7e7c284bbda160d72caedfec18328f to your computer and use it in GitHub Desktop.
Helper script to generate HelmChart CRDs for use with the K3s Kubernetes
#!/usr/bin/env python3
# Copyright (c) 2019 Blake Covarrubias
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
"""Helper script to generate HelmChart CRDs for use with the K3s Kubernetes
This script creates HelmChart resource definitions for use on the K3s Kubernetes
distribution. The command line arguments closely resemble the behavior of
`helm install` which allows users to preserve their existing Helm chart
installation workflow while simultaneously simplifying the process of
generating the HelmChart CRDs necessary for K3s.
Requirements:
* Python 3.5 or higher
* PyYAML 5.1 or higher
Example: ./k3s-helmchart-generate.py stable/nginx-ingress --name nginx-ingress
Output:
---
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
name: nginx-ingress
namespace: kube-system
spec:
chart: stable/nginx-ingress
"""
import argparse
import yaml
class literal(str):
"""Literal string class. Inherits from str class."""
pass
def literal_representer(
dumper: yaml.dumper.Dumper, data: literal
) -> yaml.nodes.ScalarNode:
"""
PyYAML representer to format string as a literal, multiline string with
newlines preserved
:param dumper: PyYAML dumper object
:type dumper: class:`yaml.dumper.Dumper`
:param data: Literal string data
:type data: class:`literal`
:returns: A ScalarNode object for the new YAML representer
:rtype: class:`yaml.nodes.ScalarNode`
"""
# Return a scalar supporting literal YAML values ('|') of type string (tag)
# for values of type 'literal'
return dumper.represent_scalar(style="|", tag="tag:yaml.org,2002:str", value=data)
# Use the literal_representer to serialize 'literal' data types
yaml.add_representer(data_type=literal, representer=literal_representer)
def parse_set_args(arguments: list) -> dict:
"""
Parses --set arguments and combines them into a single dictionary
:param arguments: List of set arguments specified on the CLI
:type arguments: list
:returns: A dictionary containing all defined set values
:rtype: dict
"""
set_dict = dict()
for set_arg in arguments:
# Parse comma-separated arguments
arg_parts = set_arg.split(",")
# Add each argument to set_dict
for argument in arg_parts:
key, value = argument.split("=")
set_dict.update({key: value})
return set_dict
def read_values_files(files: list) -> dict:
"""
Reads contents of YAML valueFiles. Returns combined dictionary of values.
:param files: List of valuesFiles specified on the CLI
:type files: list
:returns: A combined dictionary containing all values
:rtype: dict
"""
values = dict()
for filename in files:
content = yaml.safe_load(filename)
values.update(content)
return values
def generate_helmchart(arguments: argparse.Namespace) -> dict:
"""
Returns a dictionary object representing a HelmChart resource
:param arguments: A :class:`argparse.Namespace` object
:type arguments: class:`argparse.Namespace`
:returns: A dictionary representing a HelmChart resource
:rtype: dict
"""
helmchart = dict(
apiVersion="helm.cattle.io/v1",
kind="HelmChart",
metadata={
"name": arguments.name,
"namespace": arguments.helmcontroller_namespace,
},
spec={"chart": arguments.chart},
)
# Optional HelmChart resource values
specification = dict()
if arguments.namespace:
specification["targetNamespace"] = arguments.namespace
if arguments.repo:
specification["repo"] = arguments.repo
if arguments.version:
specification["version"] = arguments.version
# Chart configuration variables
if arguments.set:
specification["set"] = parse_set_args(arguments.set)
if arguments.values:
values = read_values_files(arguments.values)
values_str = literal(yaml.dump(data=values))
specification["valuesContent"] = values_str
# Add additional values to HelmChart spec
if specification:
helmchart["spec"].update(specification)
return helmchart
def parse_arguments():
"""
Parse arguments provided by the command line.
"""
parser = argparse.ArgumentParser(
description=("This command generates K3s HelmChart resource manifests")
)
# Positional arguments
parser.add_argument("chart", metavar="CHART", help="A chart reference or a URL")
parser.add_argument("--name", required=True, help="Name of the chart")
parser.add_argument(
"--repo", help="chart repository URL where to locate the requested chart"
)
parser.add_argument(
"--version",
help=(
"Specify the exact chart version to install. If this is not "
"specified, the latest version is installed"
),
)
parser.add_argument(
"--helmcontroller-namespace",
"--tiller-namespace",
default="kube-system",
help='Namespace of Helm Controller (default "kube-system")',
)
parser.add_argument(
"--namespace",
"--target-namespace",
metavar="TARGET_NAMESPACE",
help=(
"Namespace to install the release into. Defaults to the Helm "
"Controller namespace."
),
)
parser.add_argument(
"--set",
"--set-string",
action="append",
help=(
"Set values on the command line (can specify multiple or separate"
" values with commas: key1=val1,key2=val2)"
),
)
parser.add_argument(
"--set_file",
action="append",
default=[],
type=open,
help="Specify values in a YAML file or a URL(can specify multiple) (default [])",
)
parser.add_argument(
"--values",
"-f",
action="append",
default=[],
type=open,
help="Specify values in a YAML file or a URL(can specify multiple) (default [])",
)
return parser.parse_args()
def main():
arguments = parse_arguments()
helmchart = generate_helmchart(arguments)
chart_manifest = yaml.dump(
data=helmchart, default_flow_style=False, explicit_start=True, sort_keys=False
)
print(chart_manifest)
if __name__ == "__main__":
main()
@debianmaster
Copy link

Thx

@chadmayfield
Copy link

chadmayfield commented Dec 28, 2019

Thanks for this, however I get a TypeError;

pi@node1:~ $ python3 --version
Python 3.7.3
pi@node1:~ $ ./k3s-helmchart-generate.py stable/prometheus-operator --name prometheus
Traceback (most recent call last):
  File "./k3s-helmchart-generate.py", line 237, in <module>
    main()
  File "./k3s-helmchart-generate.py", line 230, in main
    data=helmchart, default_flow_style=False, explicit_start=True, sort_keys=False
  File "/usr/lib/python3/dist-packages/yaml/__init__.py", line 200, in dump
    return dump_all([data], stream, Dumper=Dumper, **kwds)
TypeError: dump_all() got an unexpected keyword argument 'sort_keys'

@blake
Copy link
Author

blake commented Dec 28, 2019

Hi @chadmayfield, you are receiving that error because you do not have the correct version of PyYAML installed. This script requires version 5.1 or later, which supports the sort_keys argument.

I list this version as a requirement at the top of the script, but I realize this is easy to overlook. I moved this into a proper Git repo (https://github.com/blake/k3s-helmchart-generate) with a requirements.txt file to assist with proper dependency installation. See the repo for installation instructions.

@chadmayfield
Copy link

Perfect, thanks!

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