README ¶
Scriptable helm charts
This project brings the starlark scripting language to helm charts.
Features
- Define APIs for helm charts
- Ease composition of charts
- Control deployment by overriding methods
- Compatible with helm
- Share a common service like a database manager or an ingress between a set of sub charts
- Use starlark methods in templates (replacement for
_helpers.tpl
) - Interact with kubernetes during installation
- Manage user credentials
- Manage certificates
- Act as glue code between helm charts
- Rendering of ytt templates
- Also available as kubernetes controller
- Easy embeddable and extendable
- Integration of kapp
Installation
Prerequisite
- Install
kubectl
e.g. usingbrew install kubernetes-cli
- Install
ytt
e.g. fromhttps://github.com/k14s/ytt/releases
- Install
kapp
e.g. fromhttps://github.com/k14s/kapp/releases
Install binary
- Download
shalm
(e.g. for mac os)
curl -L https://github.com/kramerul/shalm/releases/download/0.1.1/shalm-binary-darwin.tgz | tar xzvf -
Build shalm
from source
- Install
go
e.g. usingbrew install go
- Install
shalm
go get github.com/kramerul/shalm
Usage
shalm template <chart>
shalm apply <chart>
shalm delete <chart>
shalm package <chart>
A set of example charts can be found in the charts/examples
folder.
Charts can be given by path or by url. In case of an url, the chart must be packaged using shalm package
or zip
.
Writing charts
Just follow the rules of helm to write charts. Additionally, you can put a Chart.star
file in the charts folder
<chart>/
├── Chart.yaml
├── values.yaml
├── Chart.star
└── templates/
Using embedded ytt yaml templates
You can use ytt yaml templates to render kubernetes artifacts. You simply put them in the any folder inside a chart.
There is currently no support for data
, star
or text
files. The only value supplied to the templates is self
,
which is the current chart. You can access all values and methods within your chart. To use this feature, you need to override the template method
apiVersion: v1
kind: Namespace
metadata:
name: #@ self.namespace
def template(self,glob=''):
return self.eytt("ytt",glob=glob) # Use ytt templating with templates in directory 'ytt'
Using full featured ytt yaml templates
To use this feature, you need to override the template method
apiVersion: v1
kind: Namespace
metadata:
name: #@ self.namespace
def template(self,glob=''):
return self.ytt("yttx",self.helm()) # Use ytt templating with templates in directory 'yttx' feeding in output from another helm template
Packaging charts
You can package shalm
charts using the following command:
shalm package <shalm chart>
It's also possible to convert shalm
charts to helm
charts:
shalm package --helm <shalm chart>
In this case, the helm chart only includes two jobs (post-install
and pre-delete
hooks) which do the whole work.
kapp Support
Shalm charts can be applied/deleted using kapp. Therefore, you can pass --tool kapp
at the command line.
Examples
Share database
The following example shows how a database manager could be shared.
- Define an API for a database manager (e.g. mariadb)
def create_database(self,db="db",username="",password=""):
...
- Define a constructor for a service, which requires a database
def init(self,database=None):
if database:
database.create_database(db="uaa",username="uaa",password="randompass")
- Use the API within another chart
def init(self):
self.mariadb = chart("mariadb")
self.uaa = chart("uaa",database = self.mariadb)
Override apply, delete or template
With shalm
it's possible to override the apply
, delete
and template
methods. The following example illustrates how this could be done
def init(self):
self.mariadb = chart("mariadb")
self.uaa = chart("uaa",database = self.mariadb)
def apply(self,k8s):
self.mariadb.apply(k8s) # Apply mariadb stuff (recursive)
k8s.rollout_status("statefulset","mariadb-master") # Interact with kubernetes
self.uaa.apply(k8s) # Apply uaa stuff (recursive)
self.__apply(k8s) # Apply everthing defined in this chart (not recursive)
def template(self,glob=''):
return self.helm(glob=glob) # Use helm templating (default)
Vaults
Shalm provides the concept of vaults to store things like
- certificates
- user credentials
with the help of secrets in kubernetes.
It's also possible to extend shalm to provide other types of vaults:
- AWS users
- GCP users
- letsencrypt certificates
- ...
Create User Credentials
User credentials are used to manage username and password pairs. They are mapped to kubernets Secrets
.
If the secret doesn't exist, the username and password are created with random content, otherwise the fields are
read from the secret. The keys used to store the username and password inside the secret can be modified.
The content of username and password can only be accessed after the call to __apply
.
Therefore, you need to override the apply
method.
All user credentials created inside a Chart.star
file are automatically applied to kubernetes.
If you run shalm template
, the content of the username and password is undefined.
def init(self):
self.nats = chart("https://charts.bitnami.com/bitnami/nats-4.2.6.tgz")
self.auth = user_credential("nats-auth")
def apply(self,k8s):
self.__apply(k8s)
self.nats.auth["user"] = self.auth.username
self.nats.auth["password"] = self.auth.password
self.nats.apply(k8s)
Create Certificates
Shalm provides creation on self signed certificactes out of the box. These certificates can be used for
- Mutual TLS within your application
- Register k8s webhooks
The certificates are stored as secrets inside kubernetes. If you deploy your application again, the certificates will not be changed. Certificate rotation or renewal is not implemented yet. For this purpose you need to remove the secrets manually from kubernetes.
The following example will deploy 3 artifacts to kubernetes
- A secret
ca
, which containts your ca certificate - A secret
server
, which containts your server certificate - A configmap
configmap
, which containts the ca of your server certificate
def init(self):
self.ca = certificate("ca",is_ca=True,validity="P10Y",domains=["ca.com"]) # Create CA
self.cert = certificate("server",signer=self.ca,domains=["example.com"],validity="P1Y")
def template(self,glob=""):
return self.eytt("eytt") # use embedded ytt for templating
Put this template info eytt/configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: configmap
data:
#! render ca of server certificate
ca: #@ self.cert.ca
Using github repositories
It's possible to use github repositories directly as source for shalm charts.
shalm apply https://github.com/<repo>/archive/<branch-or-tag>.zip
shalm apply https://github.com/kramerul/cf-for-k8s/archive/shalm.zip --set domain=cf.example.com
The zip file always contains a root directory, which always stripped off.
Fragments
You can also specify a fragment to get only a part of a zip archive.
shalm apply https://github.com/kramerul/shalm/archive/master.zip#charts/shalm
Normally, a zip file always contains a root folder. This root folder is always added to the path given in the fragment to ease the usage.
Enterprise github repos
https://<host>/api/v3/repos/<owner>/<repo>/zipball/<branch>
Download credentials
Download credentials can be configured in$HOME/.shalm/config
. Example
credentials:
- url: https://github.tools.sap/
token: 123j9iasdfj2j3412934
Extending and embedding shalm
It's possible to extend shalm with starlark modules. See examples/extension
directory for details.
Using shalm controller
Charts can be also applied (in parts) using the shalm controller. Two proxy modes are support
local
the chartCR
is applied to the same cluster.remote
the chartCR
is applied to the cluster where the currentk8s
value points to.
These modes only behave different, if you are applying charts to different clusters.
Install shalm controller
shalm apply charts/shalm
Install a shalm chart using the controller
shalm apply --proxy remote <chart>
or from inside another shalm chart
def init(self):
self.mariadb = chart("mariadb",proxy="local")
Comparison
shalm | helm | ytt/kapp | kustomize | |
---|---|---|---|---|
Scripting | + | (3.1) | + | - |
API definition | + | - | (+) | - |
Reuse of existing charts | + | + | (+) | ? |
Only simple logic in templates | + | + | - | + |
Interaction with k8s | + | - | - | - |
Repository | + | + | - | - |
Mature technology | - | + | + | + |
Manage user credentials | + | - | - | - |
Manage user certificate | + | - | - | - |
Controller based installation | + | - | + | - |
Remove outdated objects | +(1) | + | + | - |
Migrate existing objects | +(1) | - | - | - |
(1): Must be implemented inside apply
method.
Reference
The following section describes the available methods inside Chart.star
Chart
chart("<url>",namespace=namespace,proxy='off',...)
An new chart is created.
If no namespace is given, the namespace is inherited from the parent chart.
Parameter | Description |
---|---|
url |
The chart is loaded from the given url. The url can be relative. In this case the chart is loaded from a path relative to the current chart location. |
namespace |
If no namespace is given, the namespace is inherited from the parent chart. |
suffix |
This suffix is appended to each chart name. The suffix is inhertied from the parent if no value is given |
proxy |
If local or remote , a proxy for the chart is returned. Applying or deleting a proxy chart is done by applying a CustomerResource to kubernetes. The installation process is then performed by the shalm-controller in the background |
... |
Additional parameters are passed to the init method of the corresponding chart. |
chart.apply(k8s)
Applies the chart recursive to k8s. This method can be overwritten.
Parameter | Description |
---|---|
8s |
See below |
chart.__apply(k8s, timeout=0, glob=pattern)
Applies the chart to k8s without recursion. This should only be used within apply
Parameter | Description |
---|---|
k8s |
See below |
timeout |
Timeout passed to kubectl apply . A timeout of zero means wait forever. |
glob |
Pattern used to find the templates. Default is "*.yaml" |
chart.delete(k8s)
Deletes the chart recursive from k8s. This method can be overwritten.
Parameter | Description |
---|---|
k8s |
See below |
chart.__delete(k8s, timeout=0, glob=pattern)
Deletes the chart from k8s without recursion. This should only be used within delete
Parameter | Description |
---|---|
k8s |
See below |
timeout |
Timeout passed to kubectl apply , A timeout of zero means wait forever. |
glob |
Pattern used to find the templates. Default is "*.y*ml" |
chart.template(glob=pattern)
Renders helm templates and returns a stream
. The default implementation of this methods renders
- all templates in directory
templates
usinghelm
- all templates in directory
ytt-templates
usingeytt
It's possible to override this method.
Parameter | Description |
---|---|
glob |
Pattern used to find the templates. Default is "*.y*ml" |
chart.helm(dir,glob=pattern)
Renders helm templates and returns a stream
.
Parameter | Description |
---|---|
dir |
Directory to search for templates |
glob |
Pattern used to find the templates. Default is "*.y*ml" |
chart.eytt(dir,glob=pattern)
Renders embedded ytt templates and returns a stream
.
Parameter | Description |
---|---|
dir |
Directory to search for ytt templates |
glob |
Pattern used to find the templates. Default is "*.y*ml" |
chart.ytt(*files)
Renders ytt templates using the ytt
binary and returns a stream
.
Parameter | Description |
---|---|
files |
These files are passed as -f option to ytt . You can also pass stream s returned from helm or eytt |
chart.load_yaml(name)
Load values from yaml file inside chart. The loaded values will override the existing values in self.
Parameter | Description |
---|---|
name |
Name of yaml file |
Attributes
Name | Description |
---|---|
name |
Name of the chart. Defaults to self.__class__.name |
namespace |
Default namespace of the chart given via command line |
__class__ |
Class of the chart. See chart_class for details |
K8s
k8s.delete(kind,name,namespaced=false,timeout=0,namespace=None,ignore_not_found=False)
Deletes one kubernetes object
Parameter | Description |
---|---|
kind |
k8s kind |
name |
name of k8s object |
timeout |
Timeout passed to kubectl apply . A timeout of zero means wait forever. |
namespaced |
If true object in the current namespace are deleted. Otherwise object in cluster scope will be deleted. Default is true |
namespace |
Override default namespace of chart |
ignore_not_found |
Ignore not found |
k8s.get(kind,name,namespaced=false,timeout=0,namespace=None,ignore_not_found=False)
Get one kubernetes object. The value is returned as a dict
.
Parameter | Description |
---|---|
kind |
k8s kind |
name |
name of k8s object |
timeout |
Timeout passed to kubectl get . A timeout of zero means wait forever. |
namespaced |
If true object in the current namespace are listed. Otherwise object in cluster scope will be listed. Default is true |
namespace |
Override default namespace of chart |
ignore_not_found |
Ignore not found |
k8s.watch(kind,name,namespaced=false,timeout=0,namespace=None,ignore_not_found=False)
Watch one kubernetes object. The value is returned as a iterator
.
Parameter | Description |
---|---|
kind |
k8s kind |
name |
name of k8s object |
timeout |
Timeout passed to kubectl watch . A timeout of zero means wait forever. |
namespaced |
If true object in the current namespace are listed. Otherwise object in cluster scope will be listed. Default is true |
namespace |
Override default namespace of chart |
ignore_not_found |
Ignore not found |
k8s.rollout_status(kind,name,timeout=0,namespace=None,ignore_not_found=False)
Wait for rollout status of one kubernetes object
Parameter | Description |
---|---|
kind |
k8s kind |
name |
name of k8s object |
timeout |
Timeout passed to kubectl apply . A timeout of zero means wait forever. |
namespace |
Override default namespace of chart |
ignore_not_found |
Ignore not found |
k8s.wait(kind,name,condition, timeout=0,namespace=None,ignore_not_found=False)
Wait for condition of one kubernetes object
Parameter | Description |
---|---|
kind |
k8s kind |
name |
name of k8s object |
condition |
condition |
timeout |
Timeout passed to kubectl apply . A timeout of zero means wait forever. |
namespace |
Override default namespace of chart |
ignore_not_found |
Ignore not found |
k8s.for_config(kube_config_content)
Create a new k8s object for a different k8s cluster
Parameter | Description |
---|---|
kube_config_content |
Content of kube config |
k8s.progress(value)
Report progress of installation
Parameter | Description |
---|---|
value |
A value between 0 and 100 |
user_credential
user_credential(name,username='',password='',username_key='username',password_key='password')
Creates a new user credential. All user credentials assigned to a root attribute inside a chart are automatically applied to kubernetes.
Parameter | Description |
---|---|
name |
The name of the kubernetes secret used to hold the information |
username |
Username. If it's empty it's either read from the secret or created with a random content. |
password |
Password. If it's empty it's either read from the secret or created with a random content. |
username_key |
The name of the key used to store the username inside the secret |
password_key |
The name of the key used to store the password inside the secret |
Name | Description |
---|---|
username |
Returns the content of the username attribute. It is only valid after calling chart.__apply(k8s) or it was set in the constructor. |
password |
Returns the content of the password attribute. It is only valid after calling chart.__apply(k8s) or it was set in the constructor. |
certificate
certificate(name,ca_key='ca.crt',private_key_key='tls.key',cert_key='tls.crt',is_ca=false,signer=None,domains=[],validity='P3M')
Creates a new certificate. All certificates assigned to a root attribute inside a chart are automatically applied to kubernetes.
Parameter | Description |
---|---|
name |
The name of the kubernetes secret used to hold the information |
ca_key |
The key which is used to store the CA into the secret |
private_key_key |
The key which is used to store the private key into the secret |
cert_key |
The key which is used to store the certificate into the secret |
is_ca |
|
signer |
The signing certificate |
validity |
The period if validity in ISO-8601 format |
domains |
The list of DNS names |
config_value
config_value(name,type='string',default='',description='Long description',options=[])
Creates a config value. The user is asked for the value.
Parameter | Description |
---|---|
name |
The name of the kubernetes secret used to hold the information. Also the name of the config value |
type |
Can be string ,password ,bool ,selection . Default if string |
default |
Default value |
description |
A description |
options |
Options. Only valid for type selection |
Name | Description |
---|---|
value |
The value given by the user |
struct
See bazel documentation. to_proto
and to_json
are not yet supported.
chart_class
The chart_class
represents the values read from the Chart.yaml
file
stream
The stream
class represents the values returned from template
, helm
, ytt
or eytt
methods. Streams have not methods.
They can be passed to other templating functions. You can use str
to convert them to strings
self.config=str(self.eytt("template-dir"))
Attributes
Name | Description |
---|---|
api_version |
API version |
name |
Name |
version |
Version |
description |
Description |
keywords |
Keywords |
home |
Home |
sources |
Sources |
icon |
Icon |
utility variables
Name | Description |
---|---|
version |
shalm version |
kube_version |
Kubernetes version |
Testing
Tests can be written in starlark. The following symbols are predefined
Name | Description |
---|---|
chart(url,...) |
Function to load shalm chart. The url can be given relative to the test script |
k8s |
In memory implemention of k8s |
env(name) |
Read environment variable |
assert.fail(msg) |
Make test fail with given message |
assert.true(cond,msg) |
Make test fail with given message if cond is false |
assert.eq(v1,v2) |
Assert equals |
assert.neq(v1,v2) |
Assert not equals |
c = chart("../charts/example/simple/mariadb")
c.apply(k8s)
mariadb = k8s.get("statefulset","mariadb-master")
assert.eq(mariadb.metadata.name,"mariadb-master")
assert.neq(mariadb.metadata.name,"mariadb-masterx")
Running tests
shalm test test/*.star
Difference to helm
- Subcharts are not loaded automatically. They must be loaded using the
chart
command - Global variables are not supported.
- The
--set
command line parameters are passed to theinit
method of the corresponding chart. It's not possible to set values (fromvalues.yaml
) directly. If you would like to set a lot of values, it's more convenient to write a separate shalm chart. shalm
doesn't track installed charts on a kubernetes cluster (except you are usingkapp
for deployment). It works more likekubectl apply
- The
.Release.Name
value is build as follows:<chart.name>-<chart.suffix>
. If no suffix is given, the hyphen is also ommited.
Documentation ¶
There is no documentation for this package.
Directories ¶
Path | Synopsis |
---|---|
api
|
|
v1alpha2
Package v1alpha2 contains API Schema definitions for the shalm v1alpha2 API group +kubebuilder:object:generate=true +groupName=kramerul.github.com
|
Package v1alpha2 contains API Schema definitions for the shalm v1alpha2 API group +kubebuilder:object:generate=true +groupName=kramerul.github.com |
examples
|
|
pkg
|
|