Skip to content
Merged
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
4 changes: 2 additions & 2 deletions helm-charts/secrets-operator/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: v0.11.7
version: v0.11.8
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "v0.11.7"
appVersion: "v0.11.8"
2 changes: 1 addition & 1 deletion helm-charts/secrets-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ controllerManager:
readOnlyRootFilesystem: true
image:
repository: infisical/kubernetes-operator
tag: v0.11.7
tag: v0.11.8
resources:
limits:
cpu: 500m
Expand Down
5 changes: 5 additions & 0 deletions internal/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ func (o V1TemplateOptions) String() string {
return o.Value
}

type V1Folder struct {
Name string `json:"name"`
Path string `json:"path"`
}

type Project struct {
ID string `json:"id"`
Name string `json:"name"`
Expand Down
36 changes: 36 additions & 0 deletions internal/template/v1/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1
import (
"bytes"
"fmt"
"sort"
"strings"
tpl "text/template"

Expand Down Expand Up @@ -165,5 +166,40 @@ func newTemplate(name, templateString string, ctx TemplateContext) (*tpl.Templat
}
return *child.Secret, nil
}
funcs["foldersIn"] = func(dir string) []model.V1Folder {
current := tree
for _, seg := range strings.Split(strings.Trim(dir, "/"), "/") {
if seg == "" {
continue
}
if current.Children == nil {
return []model.V1Folder{}
}
child, exists := current.Children[seg]
if !exists {
return []model.V1Folder{}
}
current = child
}

basePath := "/" + strings.Trim(dir, "/")

result := make([]model.V1Folder, 0)
for childName, child := range current.Children {
if len(child.Children) == 0 {
// A pure leaf is a secret, not a subdirectory. A node may carry
// both a Secret and Children when a secret key name collides with
// a folder segment; such a node is still a valid subdirectory.
continue
}
result = append(result, model.V1Folder{
Name: childName,
Path: strings.TrimRight(basePath, "/") + "/" + childName,
})
}

sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
return result
}
return tpl.New(name).Funcs(funcs).Parse(templateString)
}
75 changes: 75 additions & 0 deletions internal/template/v1/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,3 +504,78 @@ other_path: "{{ (secretFrom "/folder/other" "API_KEY").SecretPath }}"`
Expect(err.Error()).To(ContainSubstring("wrong number of args"))
})
})

var _ = Describe("RenderPerKeyTemplates with subdirectories", func() {

It("lists immediate subdirectory names under a path", func() {
tmpls := map[string]string{
"dirs": `{{ range foldersIn "/folder" }}{{ .Name }},{{ end }}`,
}

data, err := v1.RenderPerKeyTemplates(tmpls, subfolderCtx)
Expect(err).NotTo(HaveOccurred())
Expect(data).To(HaveKeyWithValue("dirs", []byte("other,subfolder,")))
})

It("exposes .Name and .Path for each subdirectory", func() {
tmpls := map[string]string{
"detail": `{{ range foldersIn "/folder" }}{{ .Name }}|{{ .Path }};{{ end }}`,
}

data, err := v1.RenderPerKeyTemplates(tmpls, subfolderCtx)
Expect(err).NotTo(HaveOccurred())
Expect(data).To(HaveKeyWithValue("detail", []byte("other|/folder/other;subfolder|/folder/subfolder;")))
})

It("lists top-level folders when passed the root path", func() {
tmpls := map[string]string{
"dirs": `{{ range foldersIn "/" }}{{ .Name }}={{ .Path }},{{ end }}`,
}

data, err := v1.RenderPerKeyTemplates(tmpls, subfolderCtx)
Expect(err).NotTo(HaveOccurred())
Expect(data).To(HaveKeyWithValue("dirs", []byte("folder=/folder,")))
})

It("returns an empty list without error for a path not in the tree", func() {
tmpls := map[string]string{
"dirs": `[{{ range foldersIn "/missing" }}{{ .Name }}{{ end }}]`,
}

data, err := v1.RenderPerKeyTemplates(tmpls, subfolderCtx)
Expect(err).NotTo(HaveOccurred())
Expect(data).To(HaveKeyWithValue("dirs", []byte("[]")))
})

It("does not include secret leaves, only folder nodes", func() {
tmpls := map[string]string{
// /folder/subfolder holds only secrets (DB_HOST, DB_PORT, API_KEY) and no subdirectories.
"dirs": `[{{ range foldersIn "/folder/subfolder" }}{{ .Name }}{{ end }}]`,
}

data, err := v1.RenderPerKeyTemplates(tmpls, subfolderCtx)
Expect(err).NotTo(HaveOccurred())
Expect(data).To(HaveKeyWithValue("dirs", []byte("[]")))
})

It("includes a folder whose name collides with a secret key at the same path", func() {
// A root secret key "db" and a "/db" folder produce a single tree node
// carrying both a Secret and Children. It must still be listed as a subdirectory.
collisionCtx := v1.NewTemplateContext(
v1.RenderContext{
RawSecrets: []api.Secret{
{SecretKey: "db", SecretValue: "some-value", SecretPath: "/"},
{SecretKey: "PASSWORD", SecretValue: "secret", SecretPath: "/db"},
},
},
)

tmpls := map[string]string{
"dirs": `{{ range foldersIn "/" }}{{ .Name }}={{ .Path }},{{ end }}`,
}

data, err := v1.RenderPerKeyTemplates(tmpls, collisionCtx)
Expect(err).NotTo(HaveOccurred())
Expect(data).To(HaveKeyWithValue("dirs", []byte("db=/db,")))
})
})