Skip to content

Commit

Permalink
Post render (#192)
Browse files Browse the repository at this point in the history
* Post render

* Kustomize failing with vague nil pointer dereference error

* Add logic for kustomize in postrenderer

* Move post render to its own file

* Add test for postrender

Co-authored-by: DavisFrench <davisdavis27@gmail.com>
  • Loading branch information
aleksej-paschenko and DavisFrench authored Nov 16, 2021
1 parent 0c14643 commit b36e29d
Show file tree
Hide file tree
Showing 11 changed files with 435 additions and 11 deletions.
4 changes: 2 additions & 2 deletions cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func main() {
Log: logg,
Scheme: mgr.GetScheme(),
HelmFactoryFn: func(namespace string) (controllers.Helm, error) {
return chart.NewHelmClient(namespace)
return chart.NewHelmClient(namespace, mgr.GetClient())
},
Now: time.Now,
Group: group,
Expand All @@ -141,7 +141,7 @@ func main() {
Scheme: mgr.GetScheme(),
TemplateReader: storage,
HelmFactoryFn: func(namespace string) (controllers.Helm, error) {
return chart.NewHelmClient(namespace)
return chart.NewHelmClient(namespace, mgr.GetClient())
},
Recorder: eventBroadcaster.NewRecorder(clientgoscheme.Scheme, v1.EventSource{
Component: "ketch-controller",
Expand Down
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ require (
sigs.k8s.io/yaml v1.2.0
)

require sigs.k8s.io/kustomize/api v0.10.0

require (
cloud.google.com/go v0.57.0 // indirect
github.com/Azure/azure-sdk-for-go v42.3.0+incompatible // indirect
Expand Down Expand Up @@ -176,10 +178,9 @@ require (
k8s.io/component-base v0.21.3 // indirect
k8s.io/klog v1.0.0 // indirect
k8s.io/klog/v2 v2.8.0 // indirect
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 // indirect
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect
k8s.io/legacy-cloud-providers v0.18.8 // indirect
k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 // indirect
sigs.k8s.io/kustomize/api v0.8.5 // indirect
sigs.k8s.io/kustomize/kyaml v0.10.15 // indirect
sigs.k8s.io/kustomize/kyaml v0.12.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
)
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
Expand Down Expand Up @@ -1510,6 +1511,8 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKf
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0=
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/kubectl v0.21.0 h1:WZXlnG/yjcE4LWO2g6ULjFxtzK6H1TKzsfaBFuVIhNg=
k8s.io/kubectl v0.21.0/go.mod h1:EU37NukZRXn1TpAkMUoy8Z/B2u6wjHDS4aInsDzVvks=
k8s.io/legacy-cloud-providers v0.17.0/go.mod h1:DdzaepJ3RtRy+e5YhNtrCYwlgyK87j/5+Yfp0L9Syp8=
Expand Down Expand Up @@ -1539,10 +1542,14 @@ sigs.k8s.io/controller-runtime v0.9.5 h1:WThcFE6cqctTn2jCZprLICO6BaKZfhsT37uAapT
sigs.k8s.io/controller-runtime v0.9.5/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA=
sigs.k8s.io/kustomize/api v0.8.5 h1:bfCXGXDAbFbb/Jv5AhMj2BB8a5VAJuuQ5/KU69WtDjQ=
sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY=
sigs.k8s.io/kustomize/api v0.10.0 h1:HK5gVSlVV24AmZ2fTHUIchZ6osaYNegK1jAdx7lJ/mU=
sigs.k8s.io/kustomize/api v0.10.0/go.mod h1:syysqD8Oews9oghLfCitMCuCPxxu4MErSJ6uw8ge9jk=
sigs.k8s.io/kustomize/cmd/config v0.9.7/go.mod h1:MvXCpHs77cfyxRmCNUQjIqCmZyYsbn5PyQpWiq44nW0=
sigs.k8s.io/kustomize/kustomize/v4 v4.0.5/go.mod h1:C7rYla7sI8EnxHE/xEhRBSHMNfcL91fx0uKmUlUhrBk=
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
sigs.k8s.io/kustomize/kyaml v0.12.0 h1:k08l8SLwnKa/eXXB5GW2/OnEc/4gJF90VDFebsOwqw4=
sigs.k8s.io/kustomize/kyaml v0.12.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
Expand Down
21 changes: 15 additions & 6 deletions internal/chart/helm_client.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package chart

import (
"log"
"os"

"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/release"
"k8s.io/cli-runtime/pkg/genericclioptions"
"log"
"os"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// HelmClient performs helm install and uninstall operations for provided application helm charts.
type HelmClient struct {
cfg *action.Configuration
namespace string
c client.Client
}

// TemplateValuer is an interface that permits types that implement it (e.g. Application, Job)
Expand All @@ -26,12 +27,12 @@ type TemplateValuer interface {
}

// NewHelmClient returns a HelmClient instance.
func NewHelmClient(namespace string) (*HelmClient, error) {
func NewHelmClient(namespace string, c client.Client) (*HelmClient, error) {
cfg, err := getActionConfig(namespace)
if err != nil {
return nil, err
}
return &HelmClient{cfg: cfg, namespace: namespace}, nil
return &HelmClient{cfg: cfg, namespace: namespace, c: c}, nil
}

func getActionConfig(namespace string) (*action.Configuration, error) {
Expand All @@ -51,7 +52,7 @@ func getActionConfig(namespace string) (*action.Configuration, error) {
return actionConfig, nil
}

// Option to perform additional configuration of action.Install before running a chart installation.
// InstallOption to perform additional configuration of action.Install before running a chart installation.
type InstallOption func(install *action.Install)

// UpdateChart checks if the app chart is already installed and performs "helm install" or "helm update" operation.
Expand All @@ -76,6 +77,10 @@ func (c HelmClient) UpdateChart(tv TemplateValuer, config ChartConfig, opts ...I
clientInstall := action.NewInstall(c.cfg)
clientInstall.ReleaseName = appName
clientInstall.Namespace = c.namespace
clientInstall.PostRenderer = &postRender{
namespace: c.namespace,
cli: c.c,
}
for _, opt := range opts {
opt(clientInstall)
}
Expand All @@ -86,6 +91,10 @@ func (c HelmClient) UpdateChart(tv TemplateValuer, config ChartConfig, opts ...I
}
updateClient := action.NewUpgrade(c.cfg)
updateClient.Namespace = c.namespace
updateClient.PostRenderer = &postRender{
namespace: c.namespace,
cli: c.c,
}
return updateClient.Run(appName, chrt, vals)
}

Expand Down
78 changes: 78 additions & 0 deletions internal/chart/post_render.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package chart

import (
"bytes"
"context"
"strings"

"helm.sh/helm/v3/pkg/postrender"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/kustomize/api/krusty"
kTypes "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/filesys"
)

var _ postrender.PostRenderer = &postRender{}

type postRender struct {
cli client.Client
namespace string
}

func (p *postRender) Run(renderedManifests *bytes.Buffer) (modifiedManifests *bytes.Buffer, err error) {

var configMapList v1.ConfigMapList
opts := &client.ListOptions{Namespace: p.namespace}
if err := p.cli.List(context.Background(), &configMapList, opts); err != nil {
return nil, err
}

fs := filesys.MakeFsInMemory()
if err := fs.Mkdir(p.namespace); err != nil {
return nil, err
}

var postrenderFound bool
for _, cm := range configMapList.Items {
if !strings.HasSuffix(cm.Name, "-postrender") {
continue
}
postrenderFound = true

for k, v := range cm.Data {
fileName := p.namespace + "/" + k
if err := fs.WriteFile(fileName, []byte(v)); err != nil {
return nil, err
}
}
}

// return original manifests, otherwise begin postrender
if !postrenderFound {
return renderedManifests, nil
}

if err := fs.WriteFile(p.namespace+"/app.yaml", renderedManifests.Bytes()); err != nil {
return nil, err
}

kustomizer := krusty.MakeKustomizer(&krusty.Options{
PluginConfig: &kTypes.PluginConfig{
HelmConfig: kTypes.HelmConfig{
Enabled: true,
Command: "helm",
},
},
})

result, err := kustomizer.Run(fs, p.namespace)
if err != nil {
return nil, err
}
y, err := result.AsYaml()
if err != nil {
return nil, err
}
return bytes.NewBuffer(y), nil
}
73 changes: 73 additions & 0 deletions internal/chart/post_render_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package chart

import (
"bytes"
_ "embed"
"fmt"
"testing"

"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

//go:embed testdata/render_yamls/prerendered-manifests.yaml
var prerender []byte

//go:embed testdata/render_yamls/kustomization.yaml
var kustomizationYaml string

//go:embed testdata/render_yamls/nodeaffinity-patch.yaml
var nodeaffinityPatchYaml string

//go:embed testdata/render_yamls/nodeaffinity-postrender.yaml
var nodeaffinityPostrender string

func TestPostRenderRun(t *testing.T) {
tc := []struct {
name string
configmap corev1.ConfigMap
expected string
}{
{
name: "postrender not found",
configmap: corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "some-other-configmap",
Namespace: "fake",
},
},
expected: string(prerender),
},
{
name: "postrender found, patch nodeAffinity",
configmap: corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "nodeaffinity-postrender",
Namespace: "fake",
},
Data: map[string]string{
"kustomization.yaml": kustomizationYaml,
"patch.yaml": nodeaffinityPatchYaml,
},
},
expected: nodeaffinityPostrender,
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
client := fake.NewClientBuilder()
client.WithObjects(&tt.configmap)

pr := postRender{
namespace: "fake",
cli: client.Build(),
}
result, err := pr.Run(bytes.NewBuffer(prerender))
fmt.Println(result.String())
require.Nil(t, err)
require.Equal(t, tt.expected, result.String())
})
}
}
7 changes: 7 additions & 0 deletions internal/chart/testdata/render_yamls/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
resources:
- app.yaml
patches:
- path: patch.yaml
target:
kind: Deployment
name: ".*"
16 changes: 16 additions & 0 deletions internal/chart/testdata/render_yamls/nodeaffinity-patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: doesnotmatter
spec:
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: asdf
operator: In
values:
- asdf
Loading

0 comments on commit b36e29d

Please sign in to comment.