diff --git a/.gitignore b/.gitignore index 00369d5e..cc18c5d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode .DS_Store -*.test \ No newline at end of file +*.test +test/templates/generated_* \ No newline at end of file diff --git a/pkg/common/common.go b/pkg/common/common.go new file mode 100644 index 00000000..b9595f98 --- /dev/null +++ b/pkg/common/common.go @@ -0,0 +1,94 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package common provides functions and steps implementations not specifically related to Kubernetes nor AWS. +package common + +import ( + "io/ioutil" + "os" + "path/filepath" + "text/template" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" +) + +type TemplateArgument struct { + Key string + EnvironmentVariable string + Default string + Mandatory bool +} + +// GetValue returns the value of the Environment Variable defined by 'TemplateArgument.EnvironmentVariable'. +// If 'TemplateArgument.EnvironmentVariable' is empty or the ENV. VAR. it defines is unset, 'TemplateArgument.Default' is returned. +// That is, if 'TemplateArgument.Mandatory' is not 'true', in which case, an error is returned. +func (ta TemplateArgument) GetValue() (string, error) { + if ta.Key == "" { + return "", errors.Errorf("'TemplateArgument.Key' can not be empty.") + } else if value, ok := os.LookupEnv(ta.EnvironmentVariable); ok { + return value, nil + } else if ta.Mandatory { + return "", errors.Errorf("'TemplateArgument.Mandatory'='true' but the Environment Variable '%s' defined by 'TemplateArgument.EnvironmentVariable' is not set", ta.EnvironmentVariable) + } else { + return ta.Default, nil + } +} + +// TemplateArgumentsToMap uses the elements of 'templateArguments' to populate the key:value pairs of the returned map. +// The key is the '.Key' variable of the corresponding element, and the value is the string returned by the 'GetValue' method of said element. +func TemplateArgumentsToMap(templateArguments ...TemplateArgument) (map[string]string, error) { + args := map[string]string{} + for i, ta := range templateArguments { + value, err := ta.GetValue() + if err != nil { + return args, errors.Errorf("'templateArguments[%d].GetValue()' failed. 'templateArguments[%d]'='%v'. error: '%v'", i, i, ta, err) + } + args[ta.Key] = value + } + return args, nil +} + +// GenerateFileFromTemplate applies the template defined in templatedFilePath to templateArgs. +// The generated file will be named 'generated_' and it will be created in the same directory of the template. +func GenerateFileFromTemplate(templatedFilePath string, templateArgs interface{}) (string, error) { + t, err := template.ParseFiles(templatedFilePath) + if err != nil { + return "", errors.Errorf("Error parsing templated file '%s': %v", templatedFilePath, err) + } + + templatedFileDir := filepath.Dir(templatedFilePath) + templatedFileName := filepath.Base(templatedFilePath) + generatedFilePath := filepath.Join(templatedFileDir, "generated_"+templatedFileName) + f, err := os.Create(generatedFilePath) + if err != nil { + return "", errors.Errorf("Error creating generated file '%s': %v", generatedFilePath, err) + } + defer f.Close() + + err = t.Execute(f, templateArgs) + if err != nil { + return "", errors.Errorf("Error executing template '%v' against '%s': %v", templateArgs, templatedFilePath, err) + } + + generated, err := ioutil.ReadFile(generatedFilePath) + if err != nil { + return "", errors.Errorf("Error reading generated file '%s': %v", generatedFilePath, err) + } + + log.Infof("Generated file '%s': \n %s", generatedFilePath, string(generated)) + + return generatedFilePath, nil +} diff --git a/pkg/common/common_test.go b/pkg/common/common_test.go new file mode 100644 index 00000000..aa6a4609 --- /dev/null +++ b/pkg/common/common_test.go @@ -0,0 +1,302 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/onsi/gomega" +) + +func TestGetValue(t *testing.T) { + var ( + g = gomega.NewWithT(t) + tests = []struct { + templateArgument TemplateArgument + setup func() + expectedValue string + expectError bool + }{ + // PositiveTests: + { // Mandatory, EnvironmentVariable set + templateArgument: TemplateArgument{ + Key: "key1", + EnvironmentVariable: "VAR1", + Mandatory: true, + Default: "fallback1", + }, + setup: func() { + os.Setenv("VAR1", "value1") + }, + expectedValue: "value1", + expectError: false, + }, + { // not Mandatory, EnvironmentVariable unset, Default not empty + templateArgument: TemplateArgument{ + Key: "key2", + EnvironmentVariable: "VAR2", + Mandatory: false, + Default: "fallback2", + }, + setup: func() { + os.Unsetenv("VAR2") + }, + expectedValue: "fallback2", + expectError: false, + }, + { // not Mandatory, EnvironmentVariable set empty + templateArgument: TemplateArgument{ + Key: "key3", + EnvironmentVariable: "VAR3", + Mandatory: false, + Default: "fallback3", + }, + setup: func() { + os.Setenv("VAR3", "") + }, + expectedValue: "", + expectError: false, + }, + { // not Mandatory, EnvironmentVariable unset, Default empty + templateArgument: TemplateArgument{ + Key: "key4", + EnvironmentVariable: "VAR4", + Mandatory: false, + Default: "", + }, + setup: func() { + os.Unsetenv("VAR4") + }, + expectedValue: "", + expectError: false, + }, + { // not Mandatory, EnvironmentVariable empty + templateArgument: TemplateArgument{ + Key: "key5", + EnvironmentVariable: "", + Mandatory: false, + Default: "fallback5", + }, + setup: func() {}, + expectedValue: "fallback5", + expectError: false, + }, + // NegativeTests: + { // Mandatory, EnvironmentVariable unset + templateArgument: TemplateArgument{ + Key: "key", + EnvironmentVariable: "VAR", + Mandatory: true, + Default: "fallback", + }, + setup: func() { + os.Unsetenv("VAR") + }, + expectedValue: "", + expectError: true, + }, + { // Key empty + templateArgument: TemplateArgument{ + Key: "", + EnvironmentVariable: "VAR", + Mandatory: true, + Default: "fallback", + }, + setup: func() { + os.Setenv("VAR", "value") + }, + expectedValue: "", + expectError: true, + }, + { // Mandatory, EnvironmentVariable empty + templateArgument: TemplateArgument{ + Key: "key", + EnvironmentVariable: "", + Mandatory: true, + Default: "fallback", + }, + setup: func() { + os.Setenv("VAR", "value") + }, + expectedValue: "", + expectError: true, + }, + } + ) + + for _, test := range tests { + test.setup() + value, err := test.templateArgument.GetValue() + if test.expectError { + g.Expect(err).Should(gomega.HaveOccurred()) + } else { + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + } + g.Expect(value).To(gomega.Equal(test.expectedValue)) + } +} + +func TestTemplateArgumentsToMap(t *testing.T) { + var ( + g = gomega.NewWithT(t) + tests = []struct { + templateArguments []TemplateArgument + setup func() + expectedArgs map[string]string + expectError bool + }{ + { // PositiveTest + templateArguments: []TemplateArgument{ + { // Mandatory, EnvironmentVariable set + Key: "key1", + EnvironmentVariable: "VAR1", + Mandatory: true, + Default: "fallback1", + }, + { // not Mandatory, EnvironmentVariable unset, Default not empty + Key: "key2", + EnvironmentVariable: "VAR2", + Mandatory: false, + Default: "fallback2", + }, + { // not Mandatory, EnvironmentVariable set empty + Key: "key3", + EnvironmentVariable: "VAR3", + Mandatory: false, + Default: "fallback3", + }, + { // not Mandatory, EnvironmentVariable unset, Default empty + Key: "key4", + EnvironmentVariable: "VAR4", + Mandatory: false, + Default: "", + }, + { // not Mandatory, EnvironmentVariable empty + Key: "key5", + EnvironmentVariable: "", + Mandatory: false, + Default: "fallback5", + }, + }, + setup: func() { + os.Setenv("VAR1", "value1") + os.Unsetenv("VAR2") + os.Setenv("VAR3", "") + os.Unsetenv("VAR4") + }, + expectedArgs: map[string]string{ + "key1": "value1", + "key2": "fallback2", + "key3": "", + "key4": "", + "key5": "fallback5", + }, + expectError: false, + }, + } + ) + + for _, test := range tests { + test.setup() + args, err := TemplateArgumentsToMap(test.templateArguments...) + if test.expectError { + g.Expect(err).Should(gomega.HaveOccurred()) + } else { + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + } + g.Expect(args).To(gomega.Equal(test.expectedArgs)) + } +} + +func TestGenerateFileFromTemplate(t *testing.T) { + type templateArgs struct { + Kind string + ApiVersion string + Name string + } + + fileToString := func(filePath string) string { + file, _ := ioutil.ReadFile(filePath) + return string(file) + } + + templateArgsToExpectedFileAsString := func(args templateArgs) string { + return `kind: ` + args.Kind + ` +apiVersion: ` + args.ApiVersion + ` +metadata: + name: ` + args.Name + } + + var ( + g = gomega.NewWithT(t) + testTemplatesPath, _ = filepath.Abs("../../test/templates") + tests = []struct { + templatedFilePath string + args templateArgs + expectedFilePath string + expectError bool + }{ + { // PositiveTest + templatedFilePath: testTemplatesPath + "/manifest.yaml", + args: templateArgs{ + Kind: "myKind", + ApiVersion: "myApiVersion", + Name: "myName", + }, + expectedFilePath: testTemplatesPath + "/generated_manifest.yaml", + expectError: false, + }, + { // NegativeTest: template.ParseFiles fails + templatedFilePath: testTemplatesPath + "/wrongName_manifest.yaml", + args: templateArgs{ + Kind: "myKind", + ApiVersion: "myApiVersion", + Name: "myName", + }, + expectedFilePath: "", + expectError: true, + }, + { // NegativeTest: template.Execute fails + templatedFilePath: testTemplatesPath + "/badKind_manifest.yaml", + args: templateArgs{ + Kind: "myKind", + ApiVersion: "myApiVersion", + Name: "myName", + }, + expectedFilePath: "", + expectError: true, + }, + } + ) + + for _, test := range tests { + generatedFilePath, err := GenerateFileFromTemplate(test.templatedFilePath, test.args) + + g.Expect(generatedFilePath).To(gomega.Equal(test.expectedFilePath)) + + if test.expectError { + g.Expect(err).Should(gomega.HaveOccurred()) + } else { + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + + generatedFileString := fileToString(generatedFilePath) + expectedFileString := templateArgsToExpectedFileAsString(test.args) + g.Expect(generatedFileString).To(gomega.Equal(expectedFileString)) + } + } +} diff --git a/test/templates/badKind_manifest.yaml b/test/templates/badKind_manifest.yaml new file mode 100644 index 00000000..a685e8f1 --- /dev/null +++ b/test/templates/badKind_manifest.yaml @@ -0,0 +1,4 @@ +kind: {{.Kindd}} +apiVersion: {{.ApiVersion}} +metadata: + name: {{.Name}} \ No newline at end of file diff --git a/test/templates/manifest.yaml b/test/templates/manifest.yaml new file mode 100644 index 00000000..3424d559 --- /dev/null +++ b/test/templates/manifest.yaml @@ -0,0 +1,4 @@ +kind: {{.Kind}} +apiVersion: {{.ApiVersion}} +metadata: + name: {{.Name}} \ No newline at end of file