Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions pkg/manifests/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,47 @@ type Template interface {
}

func Render(dir string, svc *console.ServiceDeploymentForAgent, utilFactory util.Factory) ([]unstructured.Unstructured, error) {
if len(svc.Renderers) == 0 {
return renderDefault(dir, svc, utilFactory)
}

var allManifests []unstructured.Unstructured
for _, renderer := range svc.Renderers {
var manifests []unstructured.Unstructured
var err error

switch renderer.Type {
case console.RendererTypeAuto:
manifests, err = renderDefault(renderer.Path, svc, utilFactory)
case console.RendererTypeRaw:
manifests, err = NewRaw(renderer.Path).Render(svc, utilFactory)
case console.RendererTypeHelm:
svcCopy := *svc
if renderer.Helm != nil {
svcCopy.Helm = &console.ServiceDeploymentForAgent_Helm{
Values: renderer.Helm.Values,
ValuesFiles: renderer.Helm.ValuesFiles,
Release: renderer.Helm.Release,
}
}
manifests, err = NewHelm(renderer.Path).Render(&svcCopy, utilFactory)
case console.RendererTypeKustomize:
manifests, err = NewKustomize(renderer.Path).Render(svc, utilFactory)
default:
return nil, fmt.Errorf("unknown renderer type: %s", renderer.Type)
}

if err != nil {
return nil, fmt.Errorf("error rendering path %s with type %s: %w", renderer.Path, renderer.Type, err)
}

allManifests = append(allManifests, manifests...)
}

return allManifests, nil
}

func renderDefault(dir string, svc *console.ServiceDeploymentForAgent, utilFactory util.Factory) ([]unstructured.Unstructured, error) {
renderer := RendererRaw

_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
Expand Down
246 changes: 246 additions & 0 deletions pkg/manifests/template/template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
package template

import (
"context"
"log"
"net/http"
"path/filepath"
"sort"
"strings"
"time"

"github.com/gin-gonic/gin"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
console "github.com/pluralsh/console/go/client"
"github.com/samber/lo"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

var _ = Describe("Default template", func() {

svc := &console.ServiceDeploymentForAgent{
Namespace: "default",
Configuration: make([]*console.ServiceDeploymentForAgent_Configuration, 0),
}
Context("Render raw template with no renderers provided", func() {
const name = "nginx"
It("should successfully render the raw template", func() {
dir := filepath.Join("..", "..", "..", "test", "raw")
svc.Configuration = []*console.ServiceDeploymentForAgent_Configuration{
{
Name: "name",
Value: name,
},
}
svc.Cluster = &console.ServiceDeploymentForAgent_Cluster{
ID: "123",
Name: "test",
}
resp, err := Render(dir, svc, utilFactory)
Expect(err).NotTo(HaveOccurred())
Expect(len(resp)).To(Equal(1))
Expect(resp[0].GetName()).To(Equal(name))
})
It("should skip templating liquid", func() {
dir := filepath.Join("..", "..", "..", "test", "rawTemplated")
svc.Templated = lo.ToPtr(false)
svc.Renderers = []*console.RendererFragment{{Path: dir, Type: console.RendererTypeAuto}}
resp, err := Render(dir, svc, utilFactory)
Expect(err).NotTo(HaveOccurred())
Expect(len(resp)).To(Equal(1))
Expect(resp[0].GetName()).To(Equal(name))
})

})
})

var _ = Describe("Default template, AUTO", func() {

svc := &console.ServiceDeploymentForAgent{
Namespace: "default",
Configuration: make([]*console.ServiceDeploymentForAgent_Configuration, 0),
}
Context("Render raw template ", func() {
const name = "nginx"
It("should successfully render the raw template", func() {
dir := filepath.Join("..", "..", "..", "test", "raw")
svc.Configuration = []*console.ServiceDeploymentForAgent_Configuration{
{
Name: "name",
Value: name,
},
}
svc.Cluster = &console.ServiceDeploymentForAgent_Cluster{
ID: "123",
Name: "test",
}
svc.Renderers = []*console.RendererFragment{{Path: dir, Type: console.RendererTypeAuto}}
resp, err := Render(dir, svc, utilFactory)
Expect(err).NotTo(HaveOccurred())
Expect(len(resp)).To(Equal(1))
Expect(resp[0].GetName()).To(Equal(name))
})
It("should skip templating liquid", func() {
dir := filepath.Join("..", "..", "..", "test", "rawTemplated")
svc.Templated = lo.ToPtr(false)
svc.Renderers = []*console.RendererFragment{{Path: dir, Type: console.RendererTypeAuto}}
resp, err := Render(dir, svc, utilFactory)
Expect(err).NotTo(HaveOccurred())
Expect(len(resp)).To(Equal(1))
Expect(resp[0].GetName()).To(Equal(name))
})

})
})

var _ = Describe("KUSTOMIZE template, AUTO", func() {

svc := &console.ServiceDeploymentForAgent{
Namespace: "default",
Configuration: make([]*console.ServiceDeploymentForAgent_Configuration, 0),
}
Context("Render kustomize template ", func() {
It("should successfully render the kustomize template", func() {
dir := filepath.Join("..", "..", "..", "test", "mixed", "kustomize", "overlays", "dev")
svc.Cluster = &console.ServiceDeploymentForAgent_Cluster{
ID: "123",
Name: "test",
}
svc.Renderers = []*console.RendererFragment{{Path: dir, Type: console.RendererTypeAuto}}
resp, err := Render(dir, svc, utilFactory)
Expect(err).NotTo(HaveOccurred())
Expect(len(resp)).To(Equal(3))
sort.Slice(resp, func(i, j int) bool {
return resp[i].GetKind() < resp[j].GetKind()
})
Expect(resp[0].GetKind()).To(Equal("ConfigMap"))
Expect(strings.HasPrefix(resp[0].GetName(), "app-config")).Should(BeTrue())
Expect(resp[1].GetKind()).To(Equal("Deployment"))
Expect(resp[2].GetKind()).To(Equal("Secret"))
Expect(strings.HasPrefix(resp[2].GetName(), "credentials")).Should(BeTrue())
})

})
})

var _ = Describe("RAW and KUSTOMIZE and HELM renderers", Ordered, func() {
svc := &console.ServiceDeploymentForAgent{
Namespace: "default",
Name: "test",
Configuration: make([]*console.ServiceDeploymentForAgent_Configuration, 0),
}

r := gin.Default()
r.GET("/version", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"major": "1",
"minor": "21",
})
})

srv := &http.Server{
Addr: ":8080",
Handler: r,
}

BeforeAll(func() {
// Initializing the server in a goroutine so that
// it won't block the graceful shutdown handling below
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
Expect(err).NotTo(HaveOccurred())
}
}()
})
AfterAll(func() {

// The context is used to inform the server it has 5 seconds to finish
// the request it is currently handling
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown: ", err)
}

log.Println("Server exiting")
})

Context("Render RAW and KUSTOMIZE and HELM template", func() {
const name = "nginx"
It("should successfully render the raw and kustomize and helm templates", func() {
dir := filepath.Join("..", "..", "..", "test", "mixed")
dirRaw := filepath.Join("..", "..", "..", "test", "mixed", "raw")
dirKustomize := filepath.Join("..", "..", "..", "test", "mixed", "kustomize", "overlays", "dev")
dirHelm := filepath.Join("..", "..", "..", "test", "mixed", "helm", "yet-another-cloudwatch-exporter")
svc.Configuration = []*console.ServiceDeploymentForAgent_Configuration{
{
Name: "name",
Value: name,
},
}
svc.Cluster = &console.ServiceDeploymentForAgent_Cluster{
ID: "123",
Name: "test",
}
svc.Renderers = []*console.RendererFragment{
{Path: dirRaw, Type: console.RendererTypeRaw},
{Path: dirKustomize, Type: console.RendererTypeKustomize},
{
Path: dirHelm,
Type: console.RendererTypeHelm,
Helm: &console.HelmMinimalFragment{
Release: lo.ToPtr("my-release"),
ValuesFiles: func() []*string {
qa := "./values-qa.yaml"
prod := "./values-prod.yaml"
return []*string{&qa, &prod}
}(),
},
},
}
resp, err := Render(dir, svc, utilFactory)
Expect(err).NotTo(HaveOccurred())
Expect(len(resp)).To(Equal(5))
Expect(resp[0].GetName()).To(Equal(name))

// Find the ServiceMonitor resource to verify helm values
var serviceMonitor *unstructured.Unstructured
for _, r := range resp {
if r.GetKind() == "ServiceMonitor" {
serviceMonitor = &r
break
}
}
Expect(serviceMonitor).NotTo(BeNil())

// Verify prod values were applied (since values-prod.yaml is last)
Expect(serviceMonitor.GetNamespace()).To(Equal("prod-monitoring"))
labels := serviceMonitor.GetLabels()
Expect(labels["environment"]).To(Equal("prod"))

// Verify interval in spec
spec, found, err := unstructured.NestedMap(serviceMonitor.Object, "spec")
Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue())
endpoints, found, err := unstructured.NestedSlice(spec, "endpoints")
Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue())
Expect(len(endpoints)).To(Equal(1))
endpoint := endpoints[0].(map[string]interface{})
Expect(endpoint["interval"]).To(Equal("30s"))

sort.Slice(resp[1:4], func(i, j int) bool {
return resp[1+i].GetKind() < resp[1+j].GetKind()
})
Expect(resp[1].GetKind()).To(Equal("ConfigMap"))
Expect(strings.HasPrefix(resp[1].GetName(), "app-config")).Should(BeTrue())
Expect(resp[2].GetKind()).To(Equal("Deployment"))
Expect(resp[3].GetKind()).To(Equal("Secret"))
Expect(strings.HasPrefix(resp[3].GetName(), "credentials")).Should(BeTrue())
Expect(resp[4].GetKind()).To(Equal("ServiceMonitor"))
Expect(resp[4].GetName()).To(Equal("my-release"))
})

})
})
7 changes: 7 additions & 0 deletions test/mixed/helm/yet-another-cloudwatch-exporter/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v2
name: yet-another-cloudwatch-exporter
description: Yace - Yet Another CloudWatch Exporter
type: application
version: 0.1.0
appVersion: "v0.61.2"

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "yet-another-cloudwatch-exporter.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "yet-another-cloudwatch-exporter.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "yet-another-cloudwatch-exporter.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "yet-another-cloudwatch-exporter.labels" -}}
helm.sh/chart: {{ include "yet-another-cloudwatch-exporter.chart" . }}
{{ include "yet-another-cloudwatch-exporter.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "yet-another-cloudwatch-exporter.selectorLabels" -}}
app.kubernetes.io/name: {{ include "yet-another-cloudwatch-exporter.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "yet-another-cloudwatch-exporter.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "yet-another-cloudwatch-exporter.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: batch/v1
kind: Job
metadata:
name: pi
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-delete-policy": hook-succeeded, before-hook-creation

Check notice

Code scanning / Trivy

Workloads in the default namespace Low test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV110
Severity: LOW
Message: job pi in default namespace should set metadata.namespace to a non-default namespace
Link: KSV110
spec:
template:
spec:
containers:
- name: pi
image: perl:5.34.0
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]

Check warning

Code scanning / Trivy

Can elevate its own privileges Medium test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV001
Severity: MEDIUM
Message: Container 'pi' of Job 'pi' should set 'securityContext.allowPrivilegeEscalation' to false
Link: KSV001

Check notice

Code scanning / Trivy

Default capabilities: some containers do not drop all Low test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV003
Severity: LOW
Message: Container 'pi' of Job 'pi' should add 'ALL' to 'securityContext.capabilities.drop'
Link: KSV003

Check notice

Code scanning / Trivy

Default capabilities: some containers do not drop any Low test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV004
Severity: LOW
Message: Container 'pi' of 'job' 'pi' in 'default' namespace should set securityContext.capabilities.drop
Link: KSV004

Check notice

Code scanning / Trivy

CPU not limited Low test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV011
Severity: LOW
Message: Container 'pi' of Job 'pi' should set 'resources.limits.cpu'
Link: KSV011

Check warning

Code scanning / Trivy

Runs as root user Medium test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV012
Severity: MEDIUM
Message: Container 'pi' of Job 'pi' should set 'securityContext.runAsNonRoot' to true
Link: KSV012

Check failure

Code scanning / Trivy

Root file system is not read-only High test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV014
Severity: HIGH
Message: Container 'pi' of Job 'pi' should set 'securityContext.readOnlyRootFilesystem' to true
Link: KSV014

Check notice

Code scanning / Trivy

CPU requests not specified Low test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV015
Severity: LOW
Message: Container 'pi' of Job 'pi' should set 'resources.requests.cpu'
Link: KSV015

Check notice

Code scanning / Trivy

Memory requests not specified Low test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV016
Severity: LOW
Message: Container 'pi' of Job 'pi' should set 'resources.requests.memory'
Link: KSV016

Check notice

Code scanning / Trivy

Memory not limited Low test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV018
Severity: LOW
Message: Container 'pi' of Job 'pi' should set 'resources.limits.memory'
Link: KSV018

Check notice

Code scanning / Trivy

Runs with UID <= 10000 Low test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV020
Severity: LOW
Message: Container 'pi' of Job 'pi' should set 'securityContext.runAsUser' > 10000
Link: KSV020

Check notice

Code scanning / Trivy

Runs with GID <= 10000 Low test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV021
Severity: LOW
Message: Container 'pi' of Job 'pi' should set 'securityContext.runAsGroup' > 10000
Link: KSV021

Check notice

Code scanning / Trivy

Runtime/Default Seccomp profile not set Low test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV030
Severity: LOW
Message: Either Pod or Container should set 'securityContext.seccompProfile.type' to 'RuntimeDefault'
Link: KSV030

Check warning

Code scanning / Trivy

Seccomp policies disabled Medium test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV104
Severity: MEDIUM
Message: container "pi" of job "pi" in "default" namespace should specify a seccomp profile
Link: KSV104

Check notice

Code scanning / Trivy

Container capabilities must only include NET_BIND_SERVICE Low test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV106
Severity: LOW
Message: container should drop all
Link: KSV106

Check failure

Code scanning / Trivy

Default security context configured High test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV118
Severity: HIGH
Message: container pi in default namespace is using the default security context
Link: KSV118
restartPolicy: Never

Check failure

Code scanning / Trivy

Default security context configured High test

Artifact: test/mixed/helm/yet-another-cloudwatch-exporter/templates/job.yaml
Type: kubernetes
Vulnerability KSV118
Severity: HIGH
Message: job pi in default namespace is using the default security context, which allows root privileges
Link: KSV118
backoffLimit: 4
Loading
Loading