Skip to content

Commit f9a4189

Browse files
committed
support StatefulSet
1 parent fc7fdcc commit f9a4189

File tree

9 files changed

+291
-9
lines changed

9 files changed

+291
-9
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ Usage:
8989

9090
## Status
9191
Supported k8s resources:
92-
- deployment, daemonset
93-
- job, cronJob
94-
- service, Ingress
92+
- Deployment, DaemonSet, StatefulSet
93+
- Job, CronJob
94+
- Service, Ingress
9595
- PersistentVolumeClaim
96-
- RBAC (serviceaccount, (cluster-)role, (cluster-)rolebinding)
97-
- configs (configmap, secret)
96+
- RBAC (ServiceAccount, (cluster-)role, (cluster-)roleBinding)
97+
- configs (ConfigMap, Secret)
9898
- webhooks (cert, issuer, ValidatingWebhookConfiguration)
99-
- custom resource definitions
99+
- custom resource definitions (CRD)
100100

101101
### Known issues
102102
- Helmify will not overwrite `Chart.yaml` file if presented. Done on purpose.

examples/app/templates/nginx.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: {{ include "app.fullname" . }}-nginx
5+
labels:
6+
app: nginx
7+
{{- include "app.labels" . | nindent 4 }}
8+
spec:
9+
type: {{ .Values.nginx.type }}
10+
selector:
11+
app: nginx
12+
{{- include "app.selectorLabels" . | nindent 4 }}
13+
ports:
14+
{{- .Values.nginx.ports | toYaml | nindent 2 -}}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
apiVersion: apps/v1
2+
kind: StatefulSet
3+
metadata:
4+
name: {{ include "app.fullname" . }}-web
5+
labels:
6+
{{- include "app.labels" . | nindent 4 }}
7+
spec:
8+
replicas: {{ .Values.web.replicas }}
9+
selector:
10+
matchLabels:
11+
app: nginx
12+
serviceName: {{ include "app.fullname" . }}-nginx
13+
template:
14+
metadata:
15+
labels:
16+
app: nginx
17+
spec:
18+
containers:
19+
- env:
20+
- name: KUBERNETES_CLUSTER_DOMAIN
21+
value: {{ quote .Values.kubernetesClusterDomain }}
22+
image: {{ .Values.web.nginx.image.repository }}:{{ .Values.web.nginx.image.tag
23+
| default .Chart.AppVersion }}
24+
name: nginx
25+
ports:
26+
- containerPort: 80
27+
name: web
28+
resources: {}
29+
volumeMounts:
30+
- mountPath: /usr/share/nginx/html
31+
name: www
32+
updateStrategy: {}
33+
volumeClaimTemplates:
34+
- metadata:
35+
creationTimestamp: null
36+
name: www
37+
spec:
38+
accessModes:
39+
- ReadWriteOnce
40+
resources: {{ .Values.web.volumeClaims.www | toYaml | nindent 8 }}

examples/app/values.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,24 @@ myappService:
7979
port: 8443
8080
targetPort: https
8181
type: ClusterIP
82+
nginx:
83+
ports:
84+
- name: web
85+
port: 80
86+
targetPort: 0
87+
type: ClusterIP
8288
pvc:
8389
mySamplePvClaim:
8490
storageClass: manual
8591
storageLimit: 5Gi
8692
storageRequest: 3Gi
93+
web:
94+
nginx:
95+
image:
96+
repository: registry.k8s.io/nginx-slim
97+
tag: "0.8"
98+
replicas: 2
99+
volumeClaims:
100+
www:
101+
requests:
102+
storage: 1Gi

pkg/app/app.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package app
33
import (
44
"context"
55
"github.com/arttor/helmify/pkg/processor/job"
6+
"github.com/arttor/helmify/pkg/processor/statefulset"
67
"io"
78
"os"
89
"os/signal"
@@ -48,6 +49,7 @@ func Start(input io.Reader, config config.Config) error {
4849
crd.New(),
4950
daemonset.New(),
5051
deployment.New(),
52+
statefulset.New(),
5153
storage.New(),
5254
service.New(),
5355
service.NewIngress(),

pkg/helmify/values.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,16 @@ func (v *Values) Add(value interface{}, name ...string) (string, error) {
5454

5555
// AddYaml - adds given value to values and returns its helm template representation as Yaml {{ .Values.<valueName> | toYaml | indent i }}
5656
// indent <= 0 will be omitted.
57-
func (v *Values) AddYaml(value interface{}, indent int, name ...string) (string, error) {
57+
func (v *Values) AddYaml(value interface{}, indent int, newLine bool, name ...string) (string, error) {
5858
name = toCamelCase(name)
5959
err := unstructured.SetNestedField(*v, value, name...)
6060
if err != nil {
6161
return "", errors.Wrapf(err, "unable to set value: %v", name)
6262
}
6363
if indent > 0 {
64+
if newLine {
65+
return "{{ .Values." + strings.Join(name, ".") + fmt.Sprintf(" | toYaml | nindent %d }}", indent), nil
66+
}
6467
return "{{ .Values." + strings.Join(name, ".") + fmt.Sprintf(" | toYaml | indent %d }}", indent), nil
6568
}
6669
return "{{ .Values." + strings.Join(name, ".") + " | toYaml }}", nil

pkg/processor/configmap/configmap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func parseMapData(data map[string]string, configName string) (map[string]string,
104104
}
105105
if strings.Contains(value, "\n") {
106106
value = format.RemoveTrailingWhitespaces(value)
107-
templatedVal, err := values.AddYaml(value, 1, valuesNamePath...)
107+
templatedVal, err := values.AddYaml(value, 1, false, valuesNamePath...)
108108
if err != nil {
109109
logrus.WithError(err).Errorf("unable to process multiline configmap data: %v", valuesNamePath)
110110
continue
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package statefulset
2+
3+
import (
4+
"github.com/arttor/helmify/pkg/processor/pod"
5+
"io"
6+
"strings"
7+
"text/template"
8+
9+
"github.com/arttor/helmify/pkg/helmify"
10+
"github.com/arttor/helmify/pkg/processor"
11+
yamlformat "github.com/arttor/helmify/pkg/yaml"
12+
"github.com/iancoleman/strcase"
13+
"github.com/pkg/errors"
14+
appsv1 "k8s.io/api/apps/v1"
15+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
16+
"k8s.io/apimachinery/pkg/runtime"
17+
"k8s.io/apimachinery/pkg/runtime/schema"
18+
)
19+
20+
var statefulsetGVC = schema.GroupVersionKind{
21+
Group: "apps",
22+
Version: "v1",
23+
Kind: "StatefulSet",
24+
}
25+
26+
var statefulsetTempl, _ = template.New("statefulset").Parse(
27+
`{{- .Meta }}
28+
spec:
29+
{{ .Spec }}`)
30+
31+
// New creates processor for k8s StatefulSet resource.
32+
func New() helmify.Processor {
33+
return &statefulset{}
34+
}
35+
36+
type statefulset struct{}
37+
38+
// Process k8s StatefulSet object into template. Returns false if not capable of processing given resource type.
39+
func (d statefulset) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstructured) (bool, helmify.Template, error) {
40+
if obj.GroupVersionKind() != statefulsetGVC {
41+
return false, nil, nil
42+
}
43+
ss := appsv1.StatefulSet{}
44+
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &ss)
45+
if err != nil {
46+
return true, nil, errors.Wrap(err, "unable to cast to StatefulSet")
47+
}
48+
meta, err := processor.ProcessObjMeta(appMeta, obj)
49+
if err != nil {
50+
return true, nil, err
51+
}
52+
53+
ssSpec := ss.Spec
54+
ssSpecMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&ssSpec)
55+
if err != nil {
56+
return true, nil, err
57+
}
58+
delete((ssSpecMap["template"].(map[string]interface{}))["metadata"].(map[string]interface{}), "creationTimestamp")
59+
60+
values := helmify.Values{}
61+
62+
name := appMeta.TrimName(obj.GetName())
63+
nameCamel := strcase.ToLowerCamel(name)
64+
65+
if ssSpec.ServiceName != "" {
66+
servName := appMeta.TemplatedName(ssSpec.ServiceName)
67+
ssSpecMap["serviceName"] = servName
68+
}
69+
70+
if ssSpec.Replicas != nil {
71+
repl, err := values.Add(*ssSpec.Replicas, nameCamel, "replicas")
72+
if err != nil {
73+
return true, nil, err
74+
}
75+
ssSpecMap["replicas"] = repl
76+
}
77+
78+
for i, claim := range ssSpec.VolumeClaimTemplates {
79+
volName := claim.ObjectMeta.Name
80+
delete(((ssSpecMap["volumeClaimTemplates"].([]interface{}))[i]).(map[string]interface{}), "status")
81+
if claim.Spec.StorageClassName != nil {
82+
scName := appMeta.TemplatedName(*claim.Spec.StorageClassName)
83+
err = unstructured.SetNestedField(((ssSpecMap["volumeClaimTemplates"].([]interface{}))[i]).(map[string]interface{}), scName, "spec", "storageClassName")
84+
if err != nil {
85+
return true, nil, err
86+
}
87+
}
88+
if claim.Spec.VolumeName != "" {
89+
vName := appMeta.TemplatedName(claim.Spec.VolumeName)
90+
err = unstructured.SetNestedField(((ssSpecMap["volumeClaimTemplates"].([]interface{}))[i]).(map[string]interface{}), vName, "spec", "volumeName")
91+
if err != nil {
92+
return true, nil, err
93+
}
94+
}
95+
96+
resMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&claim.Spec.Resources)
97+
if err != nil {
98+
return true, nil, err
99+
}
100+
resName, err := values.AddYaml(resMap, 8, true, nameCamel, "volumeClaims", volName)
101+
if err != nil {
102+
return true, nil, err
103+
}
104+
err = unstructured.SetNestedField(((ssSpecMap["volumeClaimTemplates"].([]interface{}))[i]).(map[string]interface{}), resName, "spec", "resources")
105+
if err != nil {
106+
return true, nil, err
107+
}
108+
}
109+
110+
// process pod spec:
111+
podSpecMap, podValues, err := pod.ProcessSpec(nameCamel, appMeta, ssSpec.Template.Spec)
112+
if err != nil {
113+
return true, nil, err
114+
}
115+
err = values.Merge(podValues)
116+
if err != nil {
117+
return true, nil, err
118+
}
119+
err = unstructured.SetNestedMap(ssSpecMap, podSpecMap, "template", "spec")
120+
if err != nil {
121+
return true, nil, err
122+
}
123+
124+
spec, err := yamlformat.Marshal(ssSpecMap, 2)
125+
if err != nil {
126+
return true, nil, err
127+
}
128+
spec = strings.ReplaceAll(spec, "'", "")
129+
130+
return true, &result{
131+
values: values,
132+
data: struct {
133+
Meta string
134+
Spec string
135+
}{
136+
Meta: meta,
137+
Spec: spec,
138+
},
139+
}, nil
140+
}
141+
142+
type result struct {
143+
data struct {
144+
Meta string
145+
Spec string
146+
}
147+
values helmify.Values
148+
}
149+
150+
func (r *result) Filename() string {
151+
return "statefulset.yaml"
152+
}
153+
154+
func (r *result) Values() helmify.Values {
155+
return r.values
156+
}
157+
158+
func (r *result) Write(writer io.Writer) error {
159+
return statefulsetTempl.Execute(writer, r.data)
160+
}

test_data/sample-app.yaml

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,4 +282,51 @@ spec:
282282
- /bin/sh
283283
- -c
284284
- date; echo Hello from the Kubernetes cluster
285-
restartPolicy: OnFailure
285+
restartPolicy: OnFailure
286+
---
287+
apiVersion: v1
288+
kind: Service
289+
metadata:
290+
name: nginx
291+
labels:
292+
app: nginx
293+
spec:
294+
ports:
295+
- port: 80
296+
name: web
297+
clusterIP: None
298+
selector:
299+
app: nginx
300+
---
301+
apiVersion: apps/v1
302+
kind: StatefulSet
303+
metadata:
304+
name: web
305+
spec:
306+
serviceName: "nginx"
307+
replicas: 2
308+
selector:
309+
matchLabels:
310+
app: nginx
311+
template:
312+
metadata:
313+
labels:
314+
app: nginx
315+
spec:
316+
containers:
317+
- name: nginx
318+
image: registry.k8s.io/nginx-slim:0.8
319+
ports:
320+
- containerPort: 80
321+
name: web
322+
volumeMounts:
323+
- name: www
324+
mountPath: /usr/share/nginx/html
325+
volumeClaimTemplates:
326+
- metadata:
327+
name: www
328+
spec:
329+
accessModes: [ "ReadWriteOnce" ]
330+
resources:
331+
requests:
332+
storage: 1Gi

0 commit comments

Comments
 (0)