From 8e57bcb7ea13736c3584a197e1625a901e796c47 Mon Sep 17 00:00:00 2001 From: Ben Ammann Date: Sun, 11 Sep 2022 21:18:11 +0200 Subject: [PATCH] (ref) #26: adds more tests --- cmd/root.go | 2 +- docs/readme.md | 6 +- pkg/config/const/const_test.go | 1 + .../{repository_root.go => repository.go} | 0 pkg/config/generic/repository_secret.go | 4 +- ...sitory_root_test.go => repository_test.go} | 0 pkg/render/render.go | 17 +- pkg/render/render_template.go | 24 ++- pkg/render/render_test.go | 155 ++++++++++++++++++ pkg/render/test_fs/.gitignore | 2 + pkg/render/test_fs/out/.gitkeep | 0 pkg/render/test_fs/render-test-default.json | 28 ++++ .../test_fs/templates/render-context.json | 20 +++ readme.md | 6 +- 14 files changed, 244 insertions(+), 21 deletions(-) create mode 100644 pkg/config/const/const_test.go rename pkg/config/generic/{repository_root.go => repository.go} (100%) rename pkg/config/generic/{repository_root_test.go => repository_test.go} (100%) create mode 100644 pkg/render/render_test.go create mode 100644 pkg/render/test_fs/.gitignore create mode 100644 pkg/render/test_fs/out/.gitkeep create mode 100644 pkg/render/test_fs/render-test-default.json create mode 100644 pkg/render/test_fs/templates/render-context.json diff --git a/cmd/root.go b/cmd/root.go index 47c3480..f4570d9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -155,6 +155,6 @@ func resolveContext() { func createRenderingEngine() { if projectCfg != nil { - renderingEngine = render.NewRenderingEngine(projectCfg) + renderingEngine = render.NewRenderingEngine(projectCfg, fs, fs) } } diff --git a/docs/readme.md b/docs/readme.md index 86048d3..b84b9f1 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -119,12 +119,12 @@ DATABASE_PASSWORD={{.Secrets.databasePassword}} You can have custom renderTargets to render files. For example `env` or `k8s`. You can than add multiple files to a renderTargets. ````bash -# always render .env.dist to .env +# always render empty.dist to .env # uses the targetName: env -git secrets add file .env.dist .env -t env +git secrets add file empty.dist .env -t env # now execute the rendering process -# this renders the .env.dist file to .env and fills out all variables using the default context +# this renders the empty.dist file to .env and fills out all variables using the default context # targetName: env git secrets render env diff --git a/pkg/config/const/const_test.go b/pkg/config/const/const_test.go new file mode 100644 index 0000000..4de00e8 --- /dev/null +++ b/pkg/config/const/const_test.go @@ -0,0 +1 @@ +package config_const diff --git a/pkg/config/generic/repository_root.go b/pkg/config/generic/repository.go similarity index 100% rename from pkg/config/generic/repository_root.go rename to pkg/config/generic/repository.go diff --git a/pkg/config/generic/repository_secret.go b/pkg/config/generic/repository_secret.go index 9792ea2..c6282fb 100644 --- a/pkg/config/generic/repository_secret.go +++ b/pkg/config/generic/repository_secret.go @@ -121,8 +121,8 @@ func (s *Secret) Decode() (string, error) { return s.OriginContext.DecodeValue(s.EncodedValue) } -// DecodeSecrets decodes the secrets of the current context and puts them into a map[string]string -func (c *Repository) DecodeSecrets() (SecretsMap, error) { +// GetSecretsMapDecoded decodes the secrets of the current context and puts them into a map[string]string +func (c *Repository) GetSecretsMapDecoded() (SecretsMap, error) { // create the secrets map secretsMap := make(SecretsMap) diff --git a/pkg/config/generic/repository_root_test.go b/pkg/config/generic/repository_test.go similarity index 100% rename from pkg/config/generic/repository_root_test.go rename to pkg/config/generic/repository_test.go diff --git a/pkg/render/render.go b/pkg/render/render.go index 5757113..e50bf41 100644 --- a/pkg/render/render.go +++ b/pkg/render/render.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" config_generic "github.com/benammann/git-secrets/pkg/config/generic" + "github.com/spf13/afero" "html/template" "io" "os" @@ -12,6 +13,8 @@ import ( type RenderingEngine struct { repository *config_generic.Repository + fsIn afero.Fs + fsOut afero.Fs } type RenderingContext struct { @@ -21,21 +24,23 @@ type RenderingContext struct { Configs config_generic.ConfigMap } -func NewRenderingEngine(repository *config_generic.Repository) *RenderingEngine { +func NewRenderingEngine(repository *config_generic.Repository, fsIn afero.Fs, fsOut afero.Fs) *RenderingEngine { return &RenderingEngine{ repository: repository, + fsIn: fsIn, + fsOut: fsOut, } } -// createTemplate creates a new template -func (e *RenderingEngine) createTemplate(fileIn string) (*template.Template, error) { - return createNewTemplate(fileIn) +func (e *RenderingEngine) createTemplate(fileIn string) (*template.Template, error) { + return createTemplate(e.fsIn, fileIn) } +// CreateRenderingContext creates the context which is used in the templates func (e *RenderingEngine) CreateRenderingContext(fileToRender *config_generic.FileToRender) (*RenderingContext, error) { // decode the secrets - secretsMap, errSecrets := e.repository.DecodeSecrets() + secretsMap, errSecrets := e.repository.GetSecretsMapDecoded() if errSecrets != nil { return nil, fmt.Errorf("could not create context secrets: %s", errSecrets.Error()) } @@ -69,7 +74,7 @@ func (e *RenderingEngine) RenderFile(fileToRender *config_generic.FileToRender) func (e *RenderingEngine) WriteFile(fileToRender *config_generic.FileToRender) (usedContext *RenderingContext, err error) { // open the file - fsFileOut, errFsFile := os.OpenFile(fileToRender.FileOut, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + fsFileOut, errFsFile := e.fsOut.OpenFile(fileToRender.FileOut, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if fsFileOut != nil { defer fsFileOut.Close() } diff --git a/pkg/render/render_template.go b/pkg/render/render_template.go index abd657c..8744804 100644 --- a/pkg/render/render_template.go +++ b/pkg/render/render_template.go @@ -2,11 +2,20 @@ package render import ( "encoding/base64" + "github.com/spf13/afero" "github.com/tcnksm/go-gitconfig" "html/template" - "log" + "io/fs" ) +type AferoConvFs struct { + aferoFs afero.Fs +} + +func (ac AferoConvFs) Open(name string) (fs.File, error) { + return ac.aferoFs.Open(name) +} + // getTemplateFunctions are added to the template and can be executed func getTemplateFunctions() template.FuncMap { return template.FuncMap{ @@ -27,15 +36,14 @@ func templateFunctionGitConfig(args ...interface{}) interface{} { if err != nil { globalVal, errGlobal := gitconfig.Global(args[0].(string)) if errGlobal != nil { - log.Fatalf("the key %s does not exist locally or globally", args[0].(string)) + return "" } return globalVal } return val } -// createNewTemplate creates a new template engine with all the extensions based on the file name -func createNewTemplate(pathToFile string) (*template.Template, error) { +func createTemplate(fs afero.Fs, pathToFile string) (*template.Template, error) { // create the new engine with file base name tpl := template.New("") @@ -43,7 +51,11 @@ func createNewTemplate(pathToFile string) (*template.Template, error) { // add the template functions tpl.Funcs(getTemplateFunctions()) - tpl, err := tpl.ParseFiles(pathToFile) + tpl, err := tpl.ParseFS(AferoConvFs{aferoFs: fs}, pathToFile) + + if err != nil { + return nil, err + } return tpl, err -} +} \ No newline at end of file diff --git a/pkg/render/render_test.go b/pkg/render/render_test.go new file mode 100644 index 0000000..5aff655 --- /dev/null +++ b/pkg/render/render_test.go @@ -0,0 +1,155 @@ +package render + +import ( + "bytes" + "embed" + "encoding/json" + "fmt" + config_generic "github.com/benammann/git-secrets/pkg/config/generic" + global_config "github.com/benammann/git-secrets/pkg/config/global" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "testing" +) + +const GlobalSecretKey = "gitSecretsTest" +const GlobalSecretValue = "eeSaoghoh8oi9leed7hai4looK3jae1N" + +const FileRenderTestDefault = "render-test-default.json" + +//go:embed test_fs +var testFiles embed.FS +var testFileAfero = &afero.FromIOFS{ + FS: testFiles, +} + +type TestRenderFunctions struct { + Base64Encode string `json:"base64Encode"` +} + +type TestRenderContext struct { + RenderContext *RenderingContext `json:"context"` + TestFunctions *TestRenderFunctions `json:"functions"` +} + +func createTestRepository(fileName string, selectedContextName string) (*config_generic.Repository, error) { + fileName = fmt.Sprintf("test_fs/%s", fileName) + globalConfig := global_config.NewGlobalConfigProvider(global_config.NewMemoryStorageProvider()) + _ = globalConfig.SetSecret(GlobalSecretKey, GlobalSecretValue, false) + mergeGlobalSecrets := make(map[string]string) + repository, errParse := config_generic.ParseRepository(afero.FromIOFS{ + FS: testFiles, + }, fileName, globalConfig, mergeGlobalSecrets) + if errParse != nil { + return nil, fmt.Errorf("could not parse: %s", errParse.Error()) + } + _, errSetContext := repository.SetSelectedContext(selectedContextName) + if errSetContext != nil { + return nil, fmt.Errorf("could not set context: %s", errSetContext.Error()) + } + return repository, nil +} + +func initRepository(t *testing.T, fileName string, selectedContextName string) (*config_generic.Repository, *RenderingEngine) { + repo, errParse := createTestRepository(fileName, selectedContextName) + assert.NotNil(t, repo) + assert.NoError(t, errParse) + return repo, NewRenderingEngine(repo, testFileAfero, testFileAfero) +} + +func TestNewRenderingEngine(t *testing.T) { + _, engine := initRepository(t, FileRenderTestDefault, "default") + assert.NotNil(t, engine) +} + +func TestRenderingEngine_CreateRenderingContext(t *testing.T) { + repo, engine := initRepository(t, FileRenderTestDefault, "default") + assert.NotNil(t, engine) + + file := &config_generic.FileToRender{ + FileIn: "fileIn", + FileOut: "fileOut", + } + + dbPassword := repo.GetCurrentSecret("databasePassword") + dbPasswordVal, errDecode := dbPassword.Decode() + assert.NoError(t, errDecode) + + ctx, err := engine.CreateRenderingContext(file) + assert.NoError(t, err) + assert.Equal(t, "default", ctx.ContextName) + assert.Equal(t, dbPasswordVal, ctx.Secrets["databasePassword"]) + assert.Equal(t, "3306", ctx.Configs["databasePort"]) +} + +func TestRenderingEngine_ExecuteTemplate(t *testing.T) { + _, engine := initRepository(t, FileRenderTestDefault, "default") + fileToRender := &config_generic.FileToRender{ + FileIn: "test_fs/templates/render-context.json", + } + + var bytesOut bytes.Buffer + usedContext, errExecute := engine.ExecuteTemplate(fileToRender, &bytesOut) + assert.NoError(t, errExecute) + assert.NotNil(t, usedContext) + + var renderedFileDecoded TestRenderContext + errDecode := json.Unmarshal(bytesOut.Bytes(), &renderedFileDecoded) + assert.NoError(t, errDecode) + assert.NotNil(t, renderedFileDecoded.RenderContext) + + renderContext := renderedFileDecoded.RenderContext + assert.Equal(t, "default", usedContext.ContextName) + assert.Equal(t, usedContext.ContextName, renderContext.ContextName) + assert.Equal(t, usedContext.File.FileIn, renderContext.File.FileIn) + assert.Equal(t, usedContext.File.FileOut, renderContext.File.FileOut) + assert.Equal(t, "3306", usedContext.Configs["databasePort"]) + assert.Equal(t, usedContext.Secrets["databasePort"], renderContext.Secrets["databasePort"]) + assert.Equal(t, "em8toheGhieh0Thu1ahz9Lou2ucheeh6", usedContext.Secrets["databasePassword"]) + assert.Equal(t, usedContext.Secrets["databasePassword"], renderContext.Secrets["databasePassword"]) + +} + +func TestRenderingEngine_RenderFile(t *testing.T) { + _, engine := initRepository(t, FileRenderTestDefault, "default") + fileToRender := &config_generic.FileToRender{ + FileIn: "test_fs/templates/render-context.json", + } + _, fileContents, err := engine.RenderFile(fileToRender) + assert.NoError(t, err) + assert.NotEqual(t, "", fileContents) +} + +func TestRenderingEngine_WriteFile(t *testing.T) { + _, engine := initRepository(t, FileRenderTestDefault, "default") + fileToRender := &config_generic.FileToRender{ + FileIn: "test_fs/templates/render-context.json", + FileOut: "render-context.json", + } + engine.fsOut = afero.NewMemMapFs() + _, err := engine.WriteFile(fileToRender) + assert.NoError(t, err) + + fileExists, errExists := afero.Exists(engine.fsOut, fileToRender.FileOut) + assert.NoError(t, errExists) + assert.True(t, fileExists) + +} + +func TestRenderingEngine_createTemplate(t *testing.T) { + fs := afero.FromIOFS{FS: testFiles} + + t.Run("create template for existing file", func(t *testing.T) { + tpl, err := createTemplate(fs, "test_fs/templates/render-context.json") + assert.NoError(t, err) + assert.NotNil(t, tpl) + }) + + t.Run("fail if file not exists", func(t *testing.T) { + tpl, err := createTemplate(fs, "test_fs/templates/missing-file") + assert.Error(t, err) + assert.Nil(t, tpl) + }) + + +} diff --git a/pkg/render/test_fs/.gitignore b/pkg/render/test_fs/.gitignore new file mode 100644 index 0000000..88d0762 --- /dev/null +++ b/pkg/render/test_fs/.gitignore @@ -0,0 +1,2 @@ +out/* +!out/.gitkeep \ No newline at end of file diff --git a/pkg/render/test_fs/out/.gitkeep b/pkg/render/test_fs/out/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pkg/render/test_fs/render-test-default.json b/pkg/render/test_fs/render-test-default.json new file mode 100644 index 0000000..891dbdd --- /dev/null +++ b/pkg/render/test_fs/render-test-default.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://raw.githubusercontent.com/benammann/git-secrets/dev-beta/schema/def/v1.json", + "version": 1, + "context": { + "default": { + "decryptSecret": { + "fromName": "gitsecretstest" + }, + "secrets": { + "databasePassword": "NkUBz4cXXAHkz23ygUZzgR0K+sBTzhPqtws+Jib1s9WXzCl8Iz05CI4ywaBHWJh3XzLnI8c9eG4qauIc" + }, + "configs": { + "databasePort": "3306" + } + }, + "prod": {} + }, + "renderFiles": { + "test-render-all": { + "files": [ + { + "fileIn": "templates/render-context.json", + "fileOut": "out/render-context.json" + } + ] + } + } +} \ No newline at end of file diff --git a/pkg/render/test_fs/templates/render-context.json b/pkg/render/test_fs/templates/render-context.json new file mode 100644 index 0000000..2e1d4cc --- /dev/null +++ b/pkg/render/test_fs/templates/render-context.json @@ -0,0 +1,20 @@ +{{$hello := "Hello World"}}{ + "context": { + "ContextName": "{{.ContextName}}", + "File": { + "FileIn": "{{.File.FileIn}}", + "FileOut": "{{.File.FileOut}}" + }, + "Secrets": { + "databasePassword": "{{.Secrets.databasePassword}}" + }, + "Configs": { + "databasePort": "{{.Configs.databasePort}}" + } + }, + "functions": { + "base64Encode": "{{Base64Encode $hello}}", + "gitConfig": "{{GitConfig "user.name"}}" + } +} + diff --git a/readme.md b/readme.md index bde8372..c7e669c 100644 --- a/readme.md +++ b/readme.md @@ -157,12 +157,12 @@ DATABASE_PASSWORD={{.Secrets.databasePassword}} You can have custom renderTargets to render files. For example `env` or `k8s`. You can than add multiple files to a renderTargets. ````bash -# always render .env.dist to .env +# always render empty.dist to .env # uses the targetName: env -git secrets add file .env.dist .env -t env +git secrets add file empty.dist .env -t env # now execute the rendering process -# this renders the .env.dist file to .env and fills out all variables using the default context +# this renders the empty.dist file to .env and fills out all variables using the default context # targetName: env git secrets render env